1/*	$OpenBSD: ugold.c,v 1.7 2014/12/11 18:39:27 mpi Exp $   */
2
3/*
4 * Copyright (c) 2013 Takayoshi SASANO <sasano@openbsd.org>
5 * Copyright (c) 2013 Martin Pieuchot <mpi@openbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20/* Driver for Microdia's HID based TEMPer Temperature sensor */
21
22#include <sys/cdefs.h>
23__FBSDID("$FreeBSD$");
24
25#include <sys/stdint.h>
26#include <sys/stddef.h>
27#include <sys/param.h>
28#include <sys/queue.h>
29#include <sys/types.h>
30#include <sys/systm.h>
31#include <sys/kernel.h>
32#include <sys/bus.h>
33#include <sys/module.h>
34#include <sys/lock.h>
35#include <sys/mutex.h>
36#include <sys/condvar.h>
37#include <sys/sysctl.h>
38#include <sys/sx.h>
39#include <sys/unistd.h>
40#include <sys/callout.h>
41#include <sys/malloc.h>
42#include <sys/priv.h>
43#include <sys/conf.h>
44
45#include <dev/hid/hid.h>
46
47#include <dev/usb/usb.h>
48#include <dev/usb/usbdi.h>
49#include <dev/usb/usbhid.h>
50#include <dev/usb/usb_process.h>
51#include <dev/usb/usbdi_util.h>
52#include "usbdevs.h"
53
54#define	USB_DEBUG_VAR usb_debug
55#include <dev/usb/usb_debug.h>
56
57#define	UGOLD_INNER		0
58#define	UGOLD_OUTER		1
59#define	UGOLD_MAX_SENSORS	2
60
61#define	UGOLD_CMD_DATA		0x80
62#define	UGOLD_CMD_INIT		0x82
63
64enum {
65	UGOLD_INTR_DT,
66	UGOLD_N_TRANSFER,
67};
68
69/*
70 * This driver only uses two of the three known commands for the
71 * TEMPerV1.2 device.
72 *
73 * The first byte of the answer corresponds to the command and the
74 * second one seems to be the size (in bytes) of the answer.
75 *
76 * The device always sends 8 bytes and if the length of the answer
77 * is less than that, it just leaves the last bytes untouched.  That
78 * is why most of the time the last n bytes of the answers are the
79 * same.
80 *
81 * The third command below seems to generate two answers with a
82 * string corresponding to the device, for example:
83 *	'TEMPer1F' and '1.1Per1F' (here Per1F is repeated).
84 */
85static uint8_t cmd_data[8] = {0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00};
86static uint8_t cmd_init[8] = {0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00};
87
88#if 0
89static uint8_t cmd_type[8] = {0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00};
90
91#endif
92
93struct ugold_softc;
94struct ugold_readout_msg {
95	struct usb_proc_msg hdr;
96	struct ugold_softc *sc;
97};
98
99struct ugold_softc {
100	struct usb_device *sc_udev;
101	struct usb_xfer *sc_xfer[UGOLD_N_TRANSFER];
102
103	struct callout sc_callout;
104	struct mtx sc_mtx;
105	struct ugold_readout_msg sc_readout_msg[2];
106
107	int	sc_num_sensors;
108	int	sc_sensor[UGOLD_MAX_SENSORS];
109  	int	sc_calib[UGOLD_MAX_SENSORS];
110	int	sc_valid[UGOLD_MAX_SENSORS];
111	uint8_t	sc_report_id;
112	uint8_t	sc_iface_index[2];
113};
114
115/* prototypes */
116
117static device_probe_t ugold_probe;
118static device_attach_t ugold_attach;
119static device_detach_t ugold_detach;
120
121static usb_proc_callback_t ugold_readout_msg;
122
123static usb_callback_t ugold_intr_callback;
124
125static devclass_t ugold_devclass;
126
127static device_method_t ugold_methods[] = {
128	DEVMETHOD(device_probe, ugold_probe),
129	DEVMETHOD(device_attach, ugold_attach),
130	DEVMETHOD(device_detach, ugold_detach),
131
132	DEVMETHOD_END
133};
134
135static driver_t ugold_driver = {
136	.name = "ugold",
137	.methods = ugold_methods,
138	.size = sizeof(struct ugold_softc),
139};
140
141static const STRUCT_USB_HOST_ID ugold_devs[] = {
142	{USB_VPI(USB_VENDOR_CHICONY2, USB_PRODUCT_CHICONY2_TEMPER, 0)},
143};
144
145DRIVER_MODULE(ugold, uhub, ugold_driver, ugold_devclass, NULL, NULL);
146MODULE_DEPEND(ugold, usb, 1, 1, 1);
147MODULE_DEPEND(ugold, hid, 1, 1, 1);
148MODULE_VERSION(ugold, 1);
149USB_PNP_HOST_INFO(ugold_devs);
150
151static const struct usb_config ugold_config[UGOLD_N_TRANSFER] = {
152	[UGOLD_INTR_DT] = {
153		.type = UE_INTERRUPT,
154		.endpoint = UE_ADDR_ANY,
155		.direction = UE_DIR_IN,
156		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
157		.bufsize = 0,		/* use wMaxPacketSize */
158		.callback = &ugold_intr_callback,
159		.if_index = 1,
160	},
161};
162
163static void
164ugold_timeout(void *arg)
165{
166	struct ugold_softc *sc = arg;
167
168	usb_proc_explore_lock(sc->sc_udev);
169	(void)usb_proc_explore_msignal(sc->sc_udev,
170	    &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]);
171	usb_proc_explore_unlock(sc->sc_udev);
172
173	callout_reset(&sc->sc_callout, 6 * hz, &ugold_timeout, sc);
174}
175
176static int
177ugold_probe(device_t dev)
178{
179	struct usb_attach_arg *uaa;
180
181	uaa = device_get_ivars(dev);
182	if (uaa->usb_mode != USB_MODE_HOST)
183		return (ENXIO);
184	if (uaa->info.bInterfaceClass != UICLASS_HID)
185		return (ENXIO);
186	if (uaa->info.bIfaceIndex != 0)
187		return (ENXIO);
188
189	return (usbd_lookup_id_by_uaa(ugold_devs, sizeof(ugold_devs), uaa));
190}
191
192static int
193ugold_attach(device_t dev)
194{
195	struct ugold_softc *sc = device_get_softc(dev);
196	struct usb_attach_arg *uaa = device_get_ivars(dev);
197	struct sysctl_oid *sensor_tree;
198	uint16_t d_len;
199	void *d_ptr;
200	int error;
201	int i;
202
203	sc->sc_udev = uaa->device;
204	sc->sc_readout_msg[0].hdr.pm_callback = &ugold_readout_msg;
205	sc->sc_readout_msg[0].sc = sc;
206	sc->sc_readout_msg[1].hdr.pm_callback = &ugold_readout_msg;
207	sc->sc_readout_msg[1].sc = sc;
208	sc->sc_iface_index[0] = uaa->info.bIfaceIndex;
209	sc->sc_iface_index[1] = uaa->info.bIfaceIndex + 1;
210
211	device_set_usb_desc(dev);
212	mtx_init(&sc->sc_mtx, "ugold lock", NULL, MTX_DEF | MTX_RECURSE);
213	callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
214
215	/* grab all interfaces from other drivers */
216	for (i = 0;; i++) {
217		if (i == uaa->info.bIfaceIndex)
218			continue;
219		if (usbd_get_iface(uaa->device, i) == NULL)
220			break;
221
222		usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
223	}
224
225	/* figure out report ID */
226	error = usbd_req_get_hid_desc(uaa->device, NULL,
227	    &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
228
229	if (error)
230		goto detach;
231
232	(void)hid_report_size_max(d_ptr, d_len, hid_input, &sc->sc_report_id);
233
234	free(d_ptr, M_TEMP);
235
236	error = usbd_transfer_setup(uaa->device,
237	    sc->sc_iface_index, sc->sc_xfer, ugold_config,
238	    UGOLD_N_TRANSFER, sc, &sc->sc_mtx);
239	if (error)
240		goto detach;
241
242	sensor_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
243	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors",
244	    CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "");
245
246	if (sensor_tree == NULL) {
247		error = ENOMEM;
248		goto detach;
249	}
250	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
251	    SYSCTL_CHILDREN(sensor_tree),
252	    OID_AUTO, "inner", CTLFLAG_RD, &sc->sc_sensor[UGOLD_INNER], 0,
253	    "Inner temperature in microCelcius");
254
255	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
256	    SYSCTL_CHILDREN(sensor_tree),
257	    OID_AUTO, "inner_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_INNER], 0,
258	    "Inner temperature is valid");
259
260	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
261	    SYSCTL_CHILDREN(sensor_tree),
262	    OID_AUTO, "inner_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_INNER], 0,
263	    "Inner calibration temperature in microCelcius");
264
265	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
266	    SYSCTL_CHILDREN(sensor_tree),
267	    OID_AUTO, "outer", CTLFLAG_RD, &sc->sc_sensor[UGOLD_OUTER], 0,
268	    "Outer temperature in microCelcius");
269
270	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
271	    SYSCTL_CHILDREN(sensor_tree),
272	    OID_AUTO, "outer_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_OUTER], 0,
273	    "Outer calibration temperature in microCelcius");
274
275	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
276	    SYSCTL_CHILDREN(sensor_tree),
277	    OID_AUTO, "outer_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_OUTER], 0,
278	    "Outer temperature is valid");
279
280	mtx_lock(&sc->sc_mtx);
281	usbd_transfer_start(sc->sc_xfer[UGOLD_INTR_DT]);
282	ugold_timeout(sc);
283	mtx_unlock(&sc->sc_mtx);
284
285	return (0);
286
287detach:
288	DPRINTF("error=%s\n", usbd_errstr(error));
289	ugold_detach(dev);
290	return (error);
291}
292
293static int
294ugold_detach(device_t dev)
295{
296	struct ugold_softc *sc = device_get_softc(dev);
297
298	callout_drain(&sc->sc_callout);
299
300	usb_proc_explore_lock(sc->sc_udev);
301	usb_proc_explore_mwait(sc->sc_udev,
302	    &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]);
303	usb_proc_explore_unlock(sc->sc_udev);
304
305	usbd_transfer_unsetup(sc->sc_xfer, UGOLD_N_TRANSFER);
306
307	mtx_destroy(&sc->sc_mtx);
308
309	return (0);
310}
311
312static int
313ugold_ds75_temp(uint8_t msb, uint8_t lsb)
314{
315	/* DS75: 12bit precision mode : 0.0625 degrees Celsius ticks */
316	/* NOTE: MSB has a sign bit for negative temperatures */
317	int32_t temp = (msb << 24) | ((lsb & 0xF0) << 16);
318	return (((int64_t)temp * (int64_t)1000000LL) >> 24);
319}
320
321static void
322ugold_intr_callback(struct usb_xfer *xfer, usb_error_t error)
323{
324	struct ugold_softc *sc = usbd_xfer_softc(xfer);
325	struct usb_page_cache *pc;
326	uint8_t buf[8];
327	int temp;
328	int len;
329
330	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
331
332	switch (USB_GET_STATE(xfer)) {
333	case USB_ST_TRANSFERRED:
334		memset(buf, 0, sizeof(buf));
335
336		pc = usbd_xfer_get_frame(xfer, 0);
337		usbd_copy_out(pc, 0, buf, MIN(len, sizeof(buf)));
338
339		switch (buf[0]) {
340		case UGOLD_CMD_INIT:
341			if (sc->sc_num_sensors)
342				break;
343
344			sc->sc_num_sensors = MIN(buf[1], UGOLD_MAX_SENSORS) /* XXX */ ;
345
346			DPRINTF("%d sensor%s type ds75/12bit (temperature)\n",
347			    sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s");
348			break;
349		case UGOLD_CMD_DATA:
350			switch (buf[1]) {
351			case 4:
352				temp = ugold_ds75_temp(buf[4], buf[5]);
353				sc->sc_sensor[UGOLD_OUTER] = temp + sc->sc_calib[UGOLD_OUTER];
354				sc->sc_valid[UGOLD_OUTER] = 1;
355				/* FALLTHROUGH */
356			case 2:
357				temp = ugold_ds75_temp(buf[2], buf[3]);
358				sc->sc_sensor[UGOLD_INNER] = temp + sc->sc_calib[UGOLD_INNER];
359				sc->sc_valid[UGOLD_INNER] = 1;
360				break;
361			default:
362				DPRINTF("invalid data length (%d bytes)\n", buf[1]);
363			}
364			break;
365		default:
366			DPRINTF("unknown command 0x%02x\n", buf[0]);
367			break;
368		}
369		/* FALLTHROUGH */
370	case USB_ST_SETUP:
371tr_setup:
372		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
373		usbd_transfer_submit(xfer);
374		break;
375	default:			/* Error */
376		if (error != USB_ERR_CANCELLED) {
377			/* try clear stall first */
378			usbd_xfer_set_stall(xfer);
379			goto tr_setup;
380		}
381		break;
382	}
383}
384
385static int
386ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
387{
388	return (usbd_req_set_report(sc->sc_udev, &sc->sc_mtx, cmd, len,
389	    sc->sc_iface_index[1], UHID_OUTPUT_REPORT, sc->sc_report_id));
390}
391
392static void
393ugold_readout_msg(struct usb_proc_msg *pm)
394{
395	struct ugold_softc *sc = ((struct ugold_readout_msg *)pm)->sc;
396
397	usb_proc_explore_unlock(sc->sc_udev);
398
399	mtx_lock(&sc->sc_mtx);
400	if (sc->sc_num_sensors == 0)
401		ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
402
403	ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data));
404	mtx_unlock(&sc->sc_mtx);
405
406	usb_proc_explore_lock(sc->sc_udev);
407}
408