xref: /openbsd-src/sys/dev/usb/uslcom.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: uslcom.c,v 1.23 2011/07/03 15:47:17 matthew 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 *, int);
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 	sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
245 }
246 
247 int
248 uslcom_detach(struct device *self, int flags)
249 {
250 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
251 	int rv = 0;
252 
253 	if (sc->sc_subdev != NULL) {
254 		rv = config_detach(sc->sc_subdev, flags);
255 		sc->sc_subdev = NULL;
256 	}
257 
258 	return (rv);
259 }
260 
261 int
262 uslcom_activate(struct device *self, int act)
263 {
264 	struct uslcom_softc *sc = (struct uslcom_softc *)self;
265 	int rv = 0;
266 
267 	switch (act) {
268 	case DVACT_DEACTIVATE:
269 		if (sc->sc_subdev != NULL)
270 			rv = config_deactivate(sc->sc_subdev);
271 		sc->sc_dying = 1;
272 		break;
273 	}
274 	return (rv);
275 }
276 
277 int
278 uslcom_open(void *vsc, int portno)
279 {
280 	struct uslcom_softc *sc = vsc;
281 	usb_device_request_t req;
282 	usbd_status err;
283 
284 	if (sc->sc_dying)
285 		return (EIO);
286 
287 	req.bmRequestType = USLCOM_WRITE;
288 	req.bRequest = USLCOM_UART;
289 	USETW(req.wValue, USLCOM_UART_ENABLE);
290 	USETW(req.wIndex, portno);
291 	USETW(req.wLength, 0);
292 	err = usbd_do_request(sc->sc_udev, &req, NULL);
293 	if (err)
294 		return (EIO);
295 
296 	return (0);
297 }
298 
299 void
300 uslcom_close(void *vsc, int portno)
301 {
302 	struct uslcom_softc *sc = vsc;
303 	usb_device_request_t req;
304 
305 	if (sc->sc_dying)
306 		return;
307 
308 	req.bmRequestType = USLCOM_WRITE;
309 	req.bRequest = USLCOM_UART;
310 	USETW(req.wValue, USLCOM_UART_DISABLE);
311 	USETW(req.wIndex, portno);
312 	USETW(req.wLength, 0);
313 	usbd_do_request(sc->sc_udev, &req, NULL);
314 }
315 
316 void
317 uslcom_set(void *vsc, int portno, int reg, int onoff)
318 {
319 	struct uslcom_softc *sc = vsc;
320 	usb_device_request_t req;
321 	int ctl;
322 
323 	switch (reg) {
324 	case UCOM_SET_DTR:
325 		ctl = onoff ? USLCOM_CTRL_DTR_ON : 0;
326 		ctl |= USLCOM_CTRL_DTR_SET;
327 		break;
328 	case UCOM_SET_RTS:
329 		ctl = onoff ? USLCOM_CTRL_RTS_ON : 0;
330 		ctl |= USLCOM_CTRL_RTS_SET;
331 		break;
332 	case UCOM_SET_BREAK:
333 		uslcom_break(sc, portno, onoff);
334 		return;
335 	default:
336 		return;
337 	}
338 	req.bmRequestType = USLCOM_WRITE;
339 	req.bRequest = USLCOM_CTRL;
340 	USETW(req.wValue, ctl);
341 	USETW(req.wIndex, portno);
342 	USETW(req.wLength, 0);
343 	usbd_do_request(sc->sc_udev, &req, NULL);
344 }
345 
346 int
347 uslcom_param(void *vsc, int portno, struct termios *t)
348 {
349 	struct uslcom_softc *sc = (struct uslcom_softc *)vsc;
350 	usbd_status err;
351 	usb_device_request_t req;
352 	int data;
353 
354 	if (t->c_ospeed <= 0 || t->c_ospeed > 921600)
355 		return (EINVAL);
356 
357 	req.bmRequestType = USLCOM_WRITE;
358 	req.bRequest = USLCOM_BAUD_RATE;
359 	USETW(req.wValue, USLCOM_BAUD_REF / t->c_ospeed);
360 	USETW(req.wIndex, portno);
361 	USETW(req.wLength, 0);
362 	err = usbd_do_request(sc->sc_udev, &req, NULL);
363 	if (err)
364 		return (EIO);
365 
366 	if (ISSET(t->c_cflag, CSTOPB))
367 		data = USLCOM_STOP_BITS_2;
368 	else
369 		data = USLCOM_STOP_BITS_1;
370 	if (ISSET(t->c_cflag, PARENB)) {
371 		if (ISSET(t->c_cflag, PARODD))
372 			data |= USLCOM_PARITY_ODD;
373 		else
374 			data |= USLCOM_PARITY_EVEN;
375 	} else
376 		data |= USLCOM_PARITY_NONE;
377 	switch (ISSET(t->c_cflag, CSIZE)) {
378 	case CS5:
379 		data |= USLCOM_SET_DATA_BITS(5);
380 		break;
381 	case CS6:
382 		data |= USLCOM_SET_DATA_BITS(6);
383 		break;
384 	case CS7:
385 		data |= USLCOM_SET_DATA_BITS(7);
386 		break;
387 	case CS8:
388 		data |= USLCOM_SET_DATA_BITS(8);
389 		break;
390 	}
391 
392 	req.bmRequestType = USLCOM_WRITE;
393 	req.bRequest = USLCOM_DATA;
394 	USETW(req.wValue, data);
395 	USETW(req.wIndex, portno);
396 	USETW(req.wLength, 0);
397 	err = usbd_do_request(sc->sc_udev, &req, NULL);
398 	if (err)
399 		return (EIO);
400 
401 #if 0
402 	/* XXX flow control */
403 	if (ISSET(t->c_cflag, CRTSCTS))
404 		/*  rts/cts flow ctl */
405 	} else if (ISSET(t->c_iflag, IXON|IXOFF)) {
406 		/*  xon/xoff flow ctl */
407 	} else {
408 		/* disable flow ctl */
409 	}
410 #endif
411 
412 	return (0);
413 }
414 
415 void
416 uslcom_get_status(void *vsc, int portno, u_char *lsr, u_char *msr)
417 {
418 	struct uslcom_softc *sc = vsc;
419 
420 	if (msr != NULL)
421 		*msr = sc->sc_msr;
422 	if (lsr != NULL)
423 		*lsr = sc->sc_lsr;
424 }
425 
426 void
427 uslcom_break(void *vsc, int portno, int onoff)
428 {
429 	struct uslcom_softc *sc = vsc;
430 	usb_device_request_t req;
431 	int brk = onoff ? USLCOM_BREAK_ON : USLCOM_BREAK_OFF;
432 
433 	req.bmRequestType = USLCOM_WRITE;
434 	req.bRequest = USLCOM_BREAK;
435 	USETW(req.wValue, brk);
436 	USETW(req.wIndex, portno);
437 	USETW(req.wLength, 0);
438 	usbd_do_request(sc->sc_udev, &req, NULL);
439 }
440