xref: /openbsd-src/sys/dev/usb/uslcom.c (revision 4c1e55dc91edd6e69ccc60ce855900fbc12cf34f)
1 /*	$OpenBSD: uslcom.c,v 1.24 2011/09/16 13:36:18 yuo Exp $	*/
2 
3 /*
4  * Copyright (c) 2006 Jonathan Gray <jsg@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/systm.h>
21 #include <sys/kernel.h>
22 #include <sys/conf.h>
23 #include <sys/tty.h>
24 #include <sys/device.h>
25 
26 #include <dev/usb/usb.h>
27 #include <dev/usb/usbdi.h>
28 #include <dev/usb/usbdi_util.h>
29 #include <dev/usb/usbdevs.h>
30 
31 #include <dev/usb/usbdevs.h>
32 #include <dev/usb/ucomvar.h>
33 
34 #ifdef USLCOM_DEBUG
35 #define DPRINTFN(n, x)  do { if (uslcomdebug > (n)) printf x; } while (0)
36 int	uslcomdebug = 0;
37 #else
38 #define DPRINTFN(n, x)
39 #endif
40 #define DPRINTF(x) DPRINTFN(0, x)
41 
42 #define USLCOMBUFSZ		256
43 #define USLCOM_CONFIG_NO	0
44 #define USLCOM_IFACE_NO		0
45 
46 #define USLCOM_SET_DATA_BITS(x)	(x << 8)
47 
48 #define USLCOM_WRITE		0x41
49 #define USLCOM_READ		0xc1
50 
51 #define USLCOM_UART		0x00
52 #define USLCOM_BAUD_RATE	0x01
53 #define USLCOM_DATA		0x03
54 #define USLCOM_BREAK		0x05
55 #define USLCOM_CTRL		0x07
56 
57 #define USLCOM_UART_DISABLE	0x00
58 #define USLCOM_UART_ENABLE	0x01
59 
60 #define USLCOM_CTRL_DTR_ON	0x0001
61 #define USLCOM_CTRL_DTR_SET	0x0100
62 #define USLCOM_CTRL_RTS_ON	0x0002
63 #define USLCOM_CTRL_RTS_SET	0x0200
64 #define USLCOM_CTRL_CTS		0x0010
65 #define USLCOM_CTRL_DSR		0x0020
66 #define USLCOM_CTRL_DCD		0x0080
67 
68 
69 #define USLCOM_BAUD_REF		0x384000
70 
71 #define USLCOM_STOP_BITS_1	0x00
72 #define USLCOM_STOP_BITS_2	0x02
73 
74 #define USLCOM_PARITY_NONE	0x00
75 #define USLCOM_PARITY_ODD	0x10
76 #define USLCOM_PARITY_EVEN	0x20
77 
78 #define USLCOM_BREAK_OFF	0x00
79 #define USLCOM_BREAK_ON		0x01
80 
81 
82 struct uslcom_softc {
83 	struct device		 sc_dev;
84 	usbd_device_handle	 sc_udev;
85 	usbd_interface_handle	 sc_iface;
86 	struct device		*sc_subdev;
87 
88 	u_char			 sc_msr;
89 	u_char			 sc_lsr;
90 
91 	u_char			 sc_dying;
92 };
93 
94 void	uslcom_get_status(void *, int portno, u_char *lsr, u_char *msr);
95 void	uslcom_set(void *, int, int, int);
96 int	uslcom_param(void *, int, struct termios *);
97 int	uslcom_open(void *sc, int portno);
98 void	uslcom_close(void *, int);
99 void	uslcom_break(void *sc, int portno, int onoff);
100 
101 struct ucom_methods uslcom_methods = {
102 	uslcom_get_status,
103 	uslcom_set,
104 	uslcom_param,
105 	NULL,
106 	uslcom_open,
107 	uslcom_close,
108 	NULL,
109 	NULL,
110 };
111 
112 static const struct usb_devno uslcom_devs[] = {
113 	{ USB_VENDOR_BALTECH,		USB_PRODUCT_BALTECH_CARDREADER },
114 	{ USB_VENDOR_CLIPSAL,		USB_PRODUCT_CLIPSAL_5500PCU },
115 	{ USB_VENDOR_DYNASTREAM,	USB_PRODUCT_DYNASTREAM_ANTDEVBOARD },
116 	{ USB_VENDOR_DYNASTREAM,	USB_PRODUCT_DYNASTREAM_ANT2USB },
117 	{ USB_VENDOR_GEMPLUS,		USB_PRODUCT_GEMPLUS_PROXPU },
118 	{ USB_VENDOR_JABLOTRON,		USB_PRODUCT_JABLOTRON_PC60B },
119 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_ARGUSISP },
120 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_AEROCOMM },
121 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_BSM7DUSB },
122 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_CP210X_1 },
123 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_CP210X_2 },
124 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_CRUMB128 },
125 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_DEGREECONT },
126 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_DESKTOPMOBILE },
127 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_EDG1228 },
128 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_IPLINK1220 },
129 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_LIPOWSKY_HARP },
130 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_LIPOWSKY_JTAG },
131 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_LIPOWSKY_LIN },
132 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_MC35PU },
133 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_POLOLU },
134 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_RIGBLASTER },
135 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_RIGTALK },
136 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_SUNNTO },
137 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_TRACIENT },
138 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_TRAQMATE },
139 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_USBCOUNT50 },
140 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_USBPULSE100 },
141 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_USBSCOPE50 },
142 	{ USB_VENDOR_SILABS,		USB_PRODUCT_SILABS_USBWAVE12 },
143 	{ USB_VENDOR_SILABS2,		USB_PRODUCT_SILABS2_DCU11CLONE },
144 	{ USB_VENDOR_SILABS3,		USB_PRODUCT_SILABS3_GPRS_MODEM },
145 	{ USB_VENDOR_USI,		USB_PRODUCT_USI_MC60 },
146 	{ USB_VENDOR_VAISALA,		USB_PRODUCT_VAISALA_USBINSTCABLE }
147 };
148 
149 int uslcom_match(struct device *, void *, void *);
150 void uslcom_attach(struct device *, struct device *, void *);
151 int uslcom_detach(struct device *, int);
152 int uslcom_activate(struct device *, int);
153 
154 struct cfdriver uslcom_cd = {
155 	NULL, "uslcom", DV_DULL
156 };
157 
158 const struct cfattach uslcom_ca = {
159 	sizeof(struct uslcom_softc),
160 	uslcom_match,
161 	uslcom_attach,
162 	uslcom_detach,
163 	uslcom_activate,
164 };
165 
166 int
167 uslcom_match(struct device *parent, void *match, void *aux)
168 {
169 	struct usb_attach_arg *uaa = aux;
170 
171 	if (uaa->iface != NULL)
172 		return UMATCH_NONE;
173 
174 	return (usb_lookup(uslcom_devs, uaa->vendor, uaa->product) != NULL) ?
175 	    UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
176 }
177 
178 void
179 uslcom_attach(struct device *parent, struct device *self, void *aux)
180 {
181 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
182 	struct usb_attach_arg *uaa = aux;
183 	struct ucom_attach_args uca;
184 	usb_interface_descriptor_t *id;
185 	usb_endpoint_descriptor_t *ed;
186 	usbd_status error;
187 	int i;
188 
189 	bzero(&uca, sizeof(uca));
190 	sc->sc_udev = uaa->device;
191 
192 	if (usbd_set_config_index(sc->sc_udev, USLCOM_CONFIG_NO, 1) != 0) {
193 		printf("%s: could not set configuration no\n",
194 		    sc->sc_dev.dv_xname);
195 		sc->sc_dying = 1;
196 		return;
197 	}
198 
199 	/* get the first interface handle */
200 	error = usbd_device2interface_handle(sc->sc_udev, USLCOM_IFACE_NO,
201 	    &sc->sc_iface);
202 	if (error != 0) {
203 		printf("%s: could not get interface handle\n",
204 		    sc->sc_dev.dv_xname);
205 		sc->sc_dying = 1;
206 		return;
207 	}
208 
209 	id = usbd_get_interface_descriptor(sc->sc_iface);
210 
211 	uca.bulkin = uca.bulkout = -1;
212 	for (i = 0; i < id->bNumEndpoints; i++) {
213 		ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
214 		if (ed == NULL) {
215 			printf("%s: no endpoint descriptor found for %d\n",
216 			    sc->sc_dev.dv_xname, i);
217 			sc->sc_dying = 1;
218 			return;
219 		}
220 
221 		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
222 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
223 			uca.bulkin = ed->bEndpointAddress;
224 		else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
225 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
226 			uca.bulkout = ed->bEndpointAddress;
227 	}
228 
229 	if (uca.bulkin == -1 || uca.bulkout == -1) {
230 		printf("%s: missing endpoint\n", sc->sc_dev.dv_xname);
231 		sc->sc_dying = 1;
232 		return;
233 	}
234 
235 	uca.ibufsize = USLCOMBUFSZ;
236 	uca.obufsize = USLCOMBUFSZ;
237 	uca.ibufsizepad = USLCOMBUFSZ;
238 	uca.opkthdrlen = 0;
239 	uca.device = sc->sc_udev;
240 	uca.iface = sc->sc_iface;
241 	uca.methods = &uslcom_methods;
242 	uca.arg = sc;
243 	uca.info = NULL;
244 
245 	sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
246 }
247 
248 int
249 uslcom_detach(struct device *self, int flags)
250 {
251 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
252 	int rv = 0;
253 
254 	if (sc->sc_subdev != NULL) {
255 		rv = config_detach(sc->sc_subdev, flags);
256 		sc->sc_subdev = NULL;
257 	}
258 
259 	return (rv);
260 }
261 
262 int
263 uslcom_activate(struct device *self, int act)
264 {
265 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
266 	int rv = 0;
267 
268 	switch (act) {
269 	case DVACT_DEACTIVATE:
270 		if (sc->sc_subdev != NULL)
271 			rv = config_deactivate(sc->sc_subdev);
272 		sc->sc_dying = 1;
273 		break;
274 	}
275 	return (rv);
276 }
277 
278 int
279 uslcom_open(void *vsc, int portno)
280 {
281 	struct uslcom_softc *sc = vsc;
282 	usb_device_request_t req;
283 	usbd_status err;
284 
285 	if (sc->sc_dying)
286 		return (EIO);
287 
288 	req.bmRequestType = USLCOM_WRITE;
289 	req.bRequest = USLCOM_UART;
290 	USETW(req.wValue, USLCOM_UART_ENABLE);
291 	USETW(req.wIndex, portno);
292 	USETW(req.wLength, 0);
293 	err = usbd_do_request(sc->sc_udev, &req, NULL);
294 	if (err)
295 		return (EIO);
296 
297 	return (0);
298 }
299 
300 void
301 uslcom_close(void *vsc, int portno)
302 {
303 	struct uslcom_softc *sc = vsc;
304 	usb_device_request_t req;
305 
306 	if (sc->sc_dying)
307 		return;
308 
309 	req.bmRequestType = USLCOM_WRITE;
310 	req.bRequest = USLCOM_UART;
311 	USETW(req.wValue, USLCOM_UART_DISABLE);
312 	USETW(req.wIndex, portno);
313 	USETW(req.wLength, 0);
314 	usbd_do_request(sc->sc_udev, &req, NULL);
315 }
316 
317 void
318 uslcom_set(void *vsc, int portno, int reg, int onoff)
319 {
320 	struct uslcom_softc *sc = vsc;
321 	usb_device_request_t req;
322 	int ctl;
323 
324 	switch (reg) {
325 	case UCOM_SET_DTR:
326 		ctl = onoff ? USLCOM_CTRL_DTR_ON : 0;
327 		ctl |= USLCOM_CTRL_DTR_SET;
328 		break;
329 	case UCOM_SET_RTS:
330 		ctl = onoff ? USLCOM_CTRL_RTS_ON : 0;
331 		ctl |= USLCOM_CTRL_RTS_SET;
332 		break;
333 	case UCOM_SET_BREAK:
334 		uslcom_break(sc, portno, onoff);
335 		return;
336 	default:
337 		return;
338 	}
339 	req.bmRequestType = USLCOM_WRITE;
340 	req.bRequest = USLCOM_CTRL;
341 	USETW(req.wValue, ctl);
342 	USETW(req.wIndex, portno);
343 	USETW(req.wLength, 0);
344 	usbd_do_request(sc->sc_udev, &req, NULL);
345 }
346 
347 int
348 uslcom_param(void *vsc, int portno, struct termios *t)
349 {
350 	struct uslcom_softc *sc = (struct uslcom_softc *)vsc;
351 	usbd_status err;
352 	usb_device_request_t req;
353 	int data;
354 
355 	if (t->c_ospeed <= 0 || t->c_ospeed > 921600)
356 		return (EINVAL);
357 
358 	req.bmRequestType = USLCOM_WRITE;
359 	req.bRequest = USLCOM_BAUD_RATE;
360 	USETW(req.wValue, USLCOM_BAUD_REF / t->c_ospeed);
361 	USETW(req.wIndex, portno);
362 	USETW(req.wLength, 0);
363 	err = usbd_do_request(sc->sc_udev, &req, NULL);
364 	if (err)
365 		return (EIO);
366 
367 	if (ISSET(t->c_cflag, CSTOPB))
368 		data = USLCOM_STOP_BITS_2;
369 	else
370 		data = USLCOM_STOP_BITS_1;
371 	if (ISSET(t->c_cflag, PARENB)) {
372 		if (ISSET(t->c_cflag, PARODD))
373 			data |= USLCOM_PARITY_ODD;
374 		else
375 			data |= USLCOM_PARITY_EVEN;
376 	} else
377 		data |= USLCOM_PARITY_NONE;
378 	switch (ISSET(t->c_cflag, CSIZE)) {
379 	case CS5:
380 		data |= USLCOM_SET_DATA_BITS(5);
381 		break;
382 	case CS6:
383 		data |= USLCOM_SET_DATA_BITS(6);
384 		break;
385 	case CS7:
386 		data |= USLCOM_SET_DATA_BITS(7);
387 		break;
388 	case CS8:
389 		data |= USLCOM_SET_DATA_BITS(8);
390 		break;
391 	}
392 
393 	req.bmRequestType = USLCOM_WRITE;
394 	req.bRequest = USLCOM_DATA;
395 	USETW(req.wValue, data);
396 	USETW(req.wIndex, portno);
397 	USETW(req.wLength, 0);
398 	err = usbd_do_request(sc->sc_udev, &req, NULL);
399 	if (err)
400 		return (EIO);
401 
402 #if 0
403 	/* XXX flow control */
404 	if (ISSET(t->c_cflag, CRTSCTS))
405 		/*  rts/cts flow ctl */
406 	} else if (ISSET(t->c_iflag, IXON|IXOFF)) {
407 		/*  xon/xoff flow ctl */
408 	} else {
409 		/* disable flow ctl */
410 	}
411 #endif
412 
413 	return (0);
414 }
415 
416 void
417 uslcom_get_status(void *vsc, int portno, u_char *lsr, u_char *msr)
418 {
419 	struct uslcom_softc *sc = vsc;
420 
421 	if (msr != NULL)
422 		*msr = sc->sc_msr;
423 	if (lsr != NULL)
424 		*lsr = sc->sc_lsr;
425 }
426 
427 void
428 uslcom_break(void *vsc, int portno, int onoff)
429 {
430 	struct uslcom_softc *sc = vsc;
431 	usb_device_request_t req;
432 	int brk = onoff ? USLCOM_BREAK_ON : USLCOM_BREAK_OFF;
433 
434 	req.bmRequestType = USLCOM_WRITE;
435 	req.bRequest = USLCOM_BREAK;
436 	USETW(req.wValue, brk);
437 	USETW(req.wIndex, portno);
438 	USETW(req.wLength, 0);
439 	usbd_do_request(sc->sc_udev, &req, NULL);
440 }
441