xref: /openbsd-src/sys/dev/usb/uslcom.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /*	$OpenBSD: uslcom.c,v 1.19 2008/03/22 02:50:02 jsg 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 };
147 
148 int uslcom_match(struct device *, void *, void *);
149 void uslcom_attach(struct device *, struct device *, void *);
150 int uslcom_detach(struct device *, int);
151 int uslcom_activate(struct device *, enum devact);
152 
153 struct cfdriver uslcom_cd = {
154 	NULL, "uslcom", DV_DULL
155 };
156 
157 const struct cfattach uslcom_ca = {
158 	sizeof(struct uslcom_softc),
159 	uslcom_match,
160 	uslcom_attach,
161 	uslcom_detach,
162 	uslcom_activate,
163 };
164 
165 int
166 uslcom_match(struct device *parent, void *match, void *aux)
167 {
168 	struct usb_attach_arg *uaa = aux;
169 
170 	if (uaa->iface != NULL)
171 		return UMATCH_NONE;
172 
173 	return (usb_lookup(uslcom_devs, uaa->vendor, uaa->product) != NULL) ?
174 	    UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
175 }
176 
177 void
178 uslcom_attach(struct device *parent, struct device *self, void *aux)
179 {
180 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
181 	struct usb_attach_arg *uaa = aux;
182 	struct ucom_attach_args uca;
183 	usb_interface_descriptor_t *id;
184 	usb_endpoint_descriptor_t *ed;
185 	usbd_status error;
186 	int i;
187 
188 	bzero(&uca, sizeof(uca));
189 	sc->sc_udev = uaa->device;
190 
191 	if (usbd_set_config_index(sc->sc_udev, USLCOM_CONFIG_NO, 1) != 0) {
192 		printf("%s: could not set configuration no\n",
193 		    sc->sc_dev.dv_xname);
194 		sc->sc_dying = 1;
195 		return;
196 	}
197 
198 	/* get the first interface handle */
199 	error = usbd_device2interface_handle(sc->sc_udev, USLCOM_IFACE_NO,
200 	    &sc->sc_iface);
201 	if (error != 0) {
202 		printf("%s: could not get interface handle\n",
203 		    sc->sc_dev.dv_xname);
204 		sc->sc_dying = 1;
205 		return;
206 	}
207 
208 	id = usbd_get_interface_descriptor(sc->sc_iface);
209 
210 	uca.bulkin = uca.bulkout = -1;
211 	for (i = 0; i < id->bNumEndpoints; i++) {
212 		ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
213 		if (ed == NULL) {
214 			printf("%s: no endpoint descriptor found for %d\n",
215 			    sc->sc_dev.dv_xname, i);
216 			sc->sc_dying = 1;
217 			return;
218 		}
219 
220 		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
221 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
222 			uca.bulkin = ed->bEndpointAddress;
223 		else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
224 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
225 			uca.bulkout = ed->bEndpointAddress;
226 	}
227 
228 	if (uca.bulkin == -1 || uca.bulkout == -1) {
229 		printf("%s: missing endpoint\n", sc->sc_dev.dv_xname);
230 		sc->sc_dying = 1;
231 		return;
232 	}
233 
234 	uca.ibufsize = USLCOMBUFSZ;
235 	uca.obufsize = USLCOMBUFSZ;
236 	uca.ibufsizepad = USLCOMBUFSZ;
237 	uca.opkthdrlen = 0;
238 	uca.device = sc->sc_udev;
239 	uca.iface = sc->sc_iface;
240 	uca.methods = &uslcom_methods;
241 	uca.arg = sc;
242 	uca.info = NULL;
243 
244 	usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev,
245 	    &sc->sc_dev);
246 
247 	sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
248 }
249 
250 int
251 uslcom_detach(struct device *self, int flags)
252 {
253 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
254 	int rv = 0;
255 
256 	sc->sc_dying = 1;
257 	if (sc->sc_subdev != NULL) {
258 		rv = config_detach(sc->sc_subdev, flags);
259 		sc->sc_subdev = NULL;
260 	}
261 
262 	usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev,
263 			   &sc->sc_dev);
264 
265 	return (rv);
266 }
267 
268 int
269 uslcom_activate(struct device *self, enum devact act)
270 {
271 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
272 	int rv = 0;
273 
274 	switch (act) {
275 	case DVACT_ACTIVATE:
276 		break;
277 
278 	case DVACT_DEACTIVATE:
279 		if (sc->sc_subdev != NULL)
280 			rv = config_deactivate(sc->sc_subdev);
281 		sc->sc_dying = 1;
282 		break;
283 	}
284 	return (rv);
285 }
286 
287 int
288 uslcom_open(void *vsc, int portno)
289 {
290 	struct uslcom_softc *sc = vsc;
291 	usb_device_request_t req;
292 	usbd_status err;
293 
294 	if (sc->sc_dying)
295 		return (EIO);
296 
297 	req.bmRequestType = USLCOM_WRITE;
298 	req.bRequest = USLCOM_UART;
299 	USETW(req.wValue, USLCOM_UART_ENABLE);
300 	USETW(req.wIndex, portno);
301 	USETW(req.wLength, 0);
302 	err = usbd_do_request(sc->sc_udev, &req, NULL);
303 	if (err)
304 		return (EIO);
305 
306 	return (0);
307 }
308 
309 void
310 uslcom_close(void *vsc, int portno)
311 {
312 	struct uslcom_softc *sc = vsc;
313 	usb_device_request_t req;
314 
315 	if (sc->sc_dying)
316 		return;
317 
318 	req.bmRequestType = USLCOM_WRITE;
319 	req.bRequest = USLCOM_UART;
320 	USETW(req.wValue, USLCOM_UART_DISABLE);
321 	USETW(req.wIndex, portno);
322 	USETW(req.wLength, 0);
323 	usbd_do_request(sc->sc_udev, &req, NULL);
324 }
325 
326 void
327 uslcom_set(void *vsc, int portno, int reg, int onoff)
328 {
329 	struct uslcom_softc *sc = vsc;
330 	usb_device_request_t req;
331 	int ctl;
332 
333 	switch (reg) {
334 	case UCOM_SET_DTR:
335 		ctl = onoff ? USLCOM_CTRL_DTR_ON : 0;
336 		ctl |= USLCOM_CTRL_DTR_SET;
337 		break;
338 	case UCOM_SET_RTS:
339 		ctl = onoff ? USLCOM_CTRL_RTS_ON : 0;
340 		ctl |= USLCOM_CTRL_RTS_SET;
341 		break;
342 	case UCOM_SET_BREAK:
343 		uslcom_break(sc, portno, onoff);
344 		return;
345 	default:
346 		return;
347 	}
348 	req.bmRequestType = USLCOM_WRITE;
349 	req.bRequest = USLCOM_CTRL;
350 	USETW(req.wValue, ctl);
351 	USETW(req.wIndex, portno);
352 	USETW(req.wLength, 0);
353 	usbd_do_request(sc->sc_udev, &req, NULL);
354 }
355 
356 int
357 uslcom_param(void *vsc, int portno, struct termios *t)
358 {
359 	struct uslcom_softc *sc = (struct uslcom_softc *)vsc;
360 	usbd_status err;
361 	usb_device_request_t req;
362 	int data;
363 
364 	if (t->c_ospeed <= 0 || t->c_ospeed > 921600)
365 		return (EINVAL);
366 
367 	req.bmRequestType = USLCOM_WRITE;
368 	req.bRequest = USLCOM_BAUD_RATE;
369 	USETW(req.wValue, USLCOM_BAUD_REF / t->c_ospeed);
370 	USETW(req.wIndex, portno);
371 	USETW(req.wLength, 0);
372 	err = usbd_do_request(sc->sc_udev, &req, NULL);
373 	if (err)
374 		return (EIO);
375 
376 	if (ISSET(t->c_cflag, CSTOPB))
377 		data = USLCOM_STOP_BITS_2;
378 	else
379 		data = USLCOM_STOP_BITS_1;
380 	if (ISSET(t->c_cflag, PARENB)) {
381 		if (ISSET(t->c_cflag, PARODD))
382 			data |= USLCOM_PARITY_ODD;
383 		else
384 			data |= USLCOM_PARITY_EVEN;
385 	} else
386 		data |= USLCOM_PARITY_NONE;
387 	switch (ISSET(t->c_cflag, CSIZE)) {
388 	case CS5:
389 		data |= USLCOM_SET_DATA_BITS(5);
390 		break;
391 	case CS6:
392 		data |= USLCOM_SET_DATA_BITS(6);
393 		break;
394 	case CS7:
395 		data |= USLCOM_SET_DATA_BITS(7);
396 		break;
397 	case CS8:
398 		data |= USLCOM_SET_DATA_BITS(8);
399 		break;
400 	}
401 
402 	req.bmRequestType = USLCOM_WRITE;
403 	req.bRequest = USLCOM_DATA;
404 	USETW(req.wValue, data);
405 	USETW(req.wIndex, portno);
406 	USETW(req.wLength, 0);
407 	err = usbd_do_request(sc->sc_udev, &req, NULL);
408 	if (err)
409 		return (EIO);
410 
411 #if 0
412 	/* XXX flow control */
413 	if (ISSET(t->c_cflag, CRTSCTS))
414 		/*  rts/cts flow ctl */
415 	} else if (ISSET(t->c_iflag, IXON|IXOFF)) {
416 		/*  xon/xoff flow ctl */
417 	} else {
418 		/* disable flow ctl */
419 	}
420 #endif
421 
422 	return (0);
423 }
424 
425 void
426 uslcom_get_status(void *vsc, int portno, u_char *lsr, u_char *msr)
427 {
428 	struct uslcom_softc *sc = vsc;
429 
430 	if (msr != NULL)
431 		*msr = sc->sc_msr;
432 	if (lsr != NULL)
433 		*lsr = sc->sc_lsr;
434 }
435 
436 void
437 uslcom_break(void *vsc, int portno, int onoff)
438 {
439 	struct uslcom_softc *sc = vsc;
440 	usb_device_request_t req;
441 	int brk = onoff ? USLCOM_BREAK_ON : USLCOM_BREAK_OFF;
442 
443 	req.bmRequestType = USLCOM_WRITE;
444 	req.bRequest = USLCOM_BREAK;
445 	USETW(req.wValue, brk);
446 	USETW(req.wIndex, portno);
447 	USETW(req.wLength, 0);
448 	usbd_do_request(sc->sc_udev, &req, NULL);
449 }
450