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