xref: /openbsd-src/sys/dev/usb/ugold.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: ugold.c,v 1.6 2014/04/29 12:53:33 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 base TEMPer Temperature sensor */
21 
22 #include <sys/param.h>
23 #include <sys/systm.h>
24 #include <sys/kernel.h>
25 #include <sys/malloc.h>
26 #include <sys/device.h>
27 #include <sys/sensors.h>
28 
29 #include <dev/usb/usb.h>
30 #include <dev/usb/usbhid.h>
31 
32 #include <dev/usb/usbdi.h>
33 #include <dev/usb/usbdevs.h>
34 #include <dev/usb/uhidev.h>
35 #include <dev/usb/hid.h>
36 
37 #define UGOLD_INNER		0
38 #define UGOLD_OUTER		1
39 #define UGOLD_MAX_SENSORS	2
40 
41 #define UGOLD_CMD_DATA		0x80
42 #define UGOLD_CMD_INIT		0x82
43 
44 /*
45  * This driver only uses two of the three known commands for the
46  * TEMPerV1.2 device.
47  *
48  * The first byte of the answer corresponds to the command and the
49  * second one seems to be the size (in bytes) of the answer.
50  *
51  * The device always sends 8 bytes and if the length of the answer
52  * is less than that, it just leaves the last bytes untouched.  That
53  * is why most of the time the last n bytes of the answers are the
54  * same.
55  *
56  * The third command below seems to generate two answers with a
57  * string corresponding to the device, for example:
58  *	'TEMPer1F' and '1.1Per1F' (here Per1F is repeated).
59  */
60 static uint8_t cmd_data[8] = { 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00 };
61 static uint8_t cmd_init[8] = { 0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00 };
62 #if 0
63 static uint8_t cmd_type[8] = { 0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00 };
64 #endif
65 
66 struct ugold_softc {
67 	struct uhidev		 sc_hdev;
68 	struct usbd_device	*sc_udev;
69 
70 	int			 sc_num_sensors;
71 
72 	struct ksensor		 sc_sensor[UGOLD_MAX_SENSORS];
73 	struct ksensordev	 sc_sensordev;
74 	struct sensor_task	*sc_sensortask;
75 };
76 
77 const struct usb_devno ugold_devs[] = {
78 	{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPER },
79 };
80 
81 int 	ugold_match(struct device *, void *, void *);
82 void	ugold_attach(struct device *, struct device *, void *);
83 int 	ugold_detach(struct device *, int);
84 
85 void	ugold_intr(struct uhidev *, void *, u_int);
86 void	ugold_refresh(void *);
87 
88 int	ugold_issue_cmd(struct ugold_softc *, uint8_t *, int);
89 
90 struct cfdriver ugold_cd = {
91 	NULL, "ugold", DV_DULL
92 };
93 
94 const struct cfattach ugold_ca = {
95 	sizeof(struct ugold_softc), ugold_match, ugold_attach, ugold_detach,
96 };
97 
98 int
99 ugold_match(struct device *parent, void *match, void *aux)
100 {
101 	struct uhidev_attach_arg *uha = aux;
102 	int size;
103 	void *desc;
104 
105 	if (uha->reportid == UHIDEV_CLAIM_ALLREPORTID)
106 		return (UMATCH_NONE);
107 
108 	if (usb_lookup(ugold_devs, uha->uaa->vendor, uha->uaa->product) == NULL)
109 		return (UMATCH_NONE);
110 
111 	/*
112 	 * XXX Only match the sensor interface.
113 	 *
114 	 * Does it makes sense to attach various uhidev(4) to these
115 	 * non-standard HID devices?
116 	 */
117 	uhidev_get_report_desc(uha->parent, &desc, &size);
118 	if (hid_is_collection(desc, size, uha->reportid,
119 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD)))
120 		return (UMATCH_NONE);
121 
122 	return (UMATCH_VENDOR_PRODUCT);
123 
124 }
125 
126 void
127 ugold_attach(struct device *parent, struct device *self, void *aux)
128 {
129 	struct ugold_softc *sc = (struct ugold_softc *)self;
130 	struct uhidev_attach_arg *uha = aux;
131 	int size, repid;
132 	void *desc;
133 
134 	sc->sc_udev = uha->parent->sc_udev;
135 	sc->sc_hdev.sc_intr = ugold_intr;
136 	sc->sc_hdev.sc_parent = uha->parent;
137 	sc->sc_hdev.sc_report_id = uha->reportid;
138 
139 	uhidev_get_report_desc(uha->parent, &desc, &size);
140 	repid = uha->reportid;
141 	sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
142 	sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
143 	sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
144 
145 	if (uhidev_open(&sc->sc_hdev)) {
146 		printf(", unable to open interrupt pipe\n");
147 		return;
148 	}
149 
150 	strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.sc_dev.dv_xname,
151 	    sizeof(sc->sc_sensordev.xname));
152 
153 	sc->sc_sensor[UGOLD_OUTER].type = SENSOR_TEMP;
154 	strlcpy(sc->sc_sensor[UGOLD_OUTER].desc, "outer",
155 	    sizeof(sc->sc_sensor[UGOLD_OUTER].desc));
156 
157 	sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
158 	strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
159 	    sizeof(sc->sc_sensor[UGOLD_INNER].desc));
160 
161 	/* 0.1Hz */
162 	sc->sc_sensortask = sensor_task_register(sc, ugold_refresh, 6);
163 	if (sc->sc_sensortask == NULL) {
164 		printf(", unable to register update task\n");
165 		return;
166 	}
167 	printf("\n");
168 
169 	sensordev_install(&sc->sc_sensordev);
170 }
171 
172 int
173 ugold_detach(struct device *self, int flags)
174 {
175 	struct ugold_softc *sc = (struct ugold_softc *)self;
176 	int i;
177 
178 	if (sc->sc_sensortask != NULL) {
179 		sensor_task_unregister(sc->sc_sensortask);
180 		sensordev_deinstall(&sc->sc_sensordev);
181 	}
182 
183 	for (i = 0; i < sc->sc_num_sensors; i++)
184 		sensor_detach(&sc->sc_sensordev, &sc->sc_sensor[i]);
185 
186 	if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
187 		uhidev_close(&sc->sc_hdev);
188 
189 	return (0);
190 }
191 
192 static int
193 ugold_ds75_temp(uint8_t msb, uint8_t lsb)
194 {
195 	/* DS75: 12bit precision mode : 0.0625 degrees Celsius ticks */
196 	return (((msb * 100) + ((lsb >> 4) * 25 / 4)) * 10000) + 273150000;
197 }
198 
199 void
200 ugold_intr(struct uhidev *addr, void *ibuf, u_int len)
201 {
202 	struct ugold_softc *sc = (struct ugold_softc *)addr;
203 	uint8_t *buf = ibuf;
204 	int i, temp;
205 
206 	switch (buf[0]) {
207 	case UGOLD_CMD_INIT:
208 		if (sc->sc_num_sensors)
209 			break;
210 
211 		sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
212 
213 		for (i = 0; i < sc->sc_num_sensors; i++) {
214 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
215 			sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
216 		}
217 
218 		printf("%s: %d sensor%s type ds75/12bit (temperature)\n",
219 		    sc->sc_hdev.sc_dev.dv_xname, sc->sc_num_sensors,
220 		    (sc->sc_num_sensors == 1) ? "" : "s");
221 		break;
222 	case UGOLD_CMD_DATA:
223 		switch (buf[1]) {
224 		case 4:
225 			temp = ugold_ds75_temp(buf[4], buf[5]);
226 			sc->sc_sensor[UGOLD_OUTER].value = temp;
227 			sc->sc_sensor[UGOLD_OUTER].flags &= ~SENSOR_FINVALID;
228 			/* FALLTHROUGH */
229 		case 2:
230 			temp = ugold_ds75_temp(buf[2], buf[3]);
231 			sc->sc_sensor[UGOLD_INNER].value = temp;
232 			sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
233 			break;
234 		default:
235 			printf("%s: invalid data length (%d bytes)\n",
236 				sc->sc_hdev.sc_dev.dv_xname, buf[1]);
237 		}
238 		break;
239 	default:
240 		printf("%s: unknown command 0x%02x\n",
241 		    sc->sc_hdev.sc_dev.dv_xname, buf[0]);
242 	}
243 }
244 
245 void
246 ugold_refresh(void *arg)
247 {
248 	struct ugold_softc *sc = arg;
249 	int i;
250 
251 	if (sc->sc_num_sensors == 0)
252 		ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
253 
254 	if (ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data))) {
255 		for (i = 0; i < sc->sc_num_sensors; i++)
256 			sc->sc_sensor[i].flags |= SENSOR_FINVALID;
257 	}
258 }
259 
260 int
261 ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
262 {
263 	return uhidev_set_report_async(&sc->sc_hdev, UHID_OUTPUT_REPORT,
264 	    sc->sc_hdev.sc_report_id, cmd, len);
265 }
266