5ef36a3960a36178ec4eed820959e9600320d1a7
[dragonfly.git] / sys / dev / usbmisc / ugensa / ugensa.c
1 /* $DragonFly: src/sys/dev/usbmisc/ugensa/ugensa.c,v 1.6 2008/08/19 11:28:48 matthias Exp $ */
2 /* $OpenBSD: umsm.c,v 1.15 2007/06/14 10:11:16 mbalmer Exp $ */
3
4 /*
5  * Copyright (c) 2006 Jonathan Gray <jsg@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 DISCLAIMS 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 /*
21  * Generic USB serial driver used for devices where hardware specific
22  * don't apply or doesn't make sense (for example Qualcomm MSM EVDO, UMTS
23  * and other similar communication devices).
24  */
25
26 #include <sys/param.h>
27 #include <sys/systm.h>
28 #include <sys/kernel.h>
29 #include <sys/device.h>
30 #include <sys/conf.h>
31 #include <sys/tty.h>
32 #include <sys/types.h>
33 #include <sys/bus.h>
34 #include <sys/module.h>
35
36 #include <bus/usb/usb.h>
37 #include <bus/usb/usbdi.h>
38 #include <bus/usb/usbdi_util.h>
39 #include <bus/usb/usbcdc.h>
40 #include <dev/usbmisc/ucom/ucomvar.h>
41
42 #ifdef UGENSA_DEBUG
43 static int      ugensadebug = 1;
44 #define DPRINTFN(n, x)  do { if (ugensadebug > (n)) kprintf x; } while (0)
45 #else
46 #define DPRINTFN(n, x)
47 #endif
48 #define DPRINTF(x) DPRINTFN(0, x)
49
50 #define UGENSABUFSZ             4096
51 #define UGENSA_INTR_INTERVAL    100     /* ms */
52
53 struct ugensa_softc {
54         struct ucom_softc        sc_ucom;
55         int                      sc_iface_no;
56
57         /* interrupt ep */
58         int                      sc_intr_number;
59         usbd_pipe_handle         sc_intr_pipe;
60         u_char                  *sc_intr_buf;
61         int                      sc_isize;
62
63         u_char                   sc_lsr;        /* Local status register */
64         u_char                   sc_msr;        /* Status register */
65         u_char                   sc_dtr;        /* Current DTR state */
66         u_char                   sc_rts;        /* Current RTS state */
67 };
68
69 static device_probe_t ugensa_match;
70 static device_attach_t ugensa_attach;
71 static device_detach_t ugensa_detach;
72
73 int ugensa_open(void *, int);
74 void ugensa_close(void *, int);
75 void ugensa_intr(usbd_xfer_handle, usbd_private_handle, usbd_status);
76 void ugensa_get_status(void *, int, u_char *, u_char *);
77 void ugensa_set(void *, int, int, int);
78
79 static device_method_t ugensa_methods[] = {
80         /* Device interface */
81         DEVMETHOD(device_probe, ugensa_match),
82         DEVMETHOD(device_attach, ugensa_attach),
83         DEVMETHOD(device_detach, ugensa_detach),
84         { 0, 0 }
85 };
86
87 static driver_t ugensa_driver = { 
88         "ucom",
89         ugensa_methods,
90         sizeof (struct ugensa_softc)
91 };
92
93 struct ucom_callback ugensa_callback = {
94         ugensa_get_status,
95         ugensa_set,
96         NULL,
97         NULL,
98         ugensa_open,
99         ugensa_close,
100         NULL,
101         NULL
102 };
103
104 static const struct usb_devno ugensa_devs[] = {
105         { USB_DEVICE(0x05c6, 0x6000) }, /* Qualcomm HSDPA MSM */
106         { USB_DEVICE(0x05c6, 0x6613) }, /* Qualcomm HSDPA MSM */
107         { USB_DEVICE(0x0c88, 0x17da) }, /* Kyocera KPC650 */
108         { USB_DEVICE(0x0f3d, 0x0112) }, /* AirPrime PC5220 */
109         { USB_DEVICE(0x1199, 0x0017) }, /* Sierra Wireless EM5625 */
110         { USB_DEVICE(0x1199, 0x0018) }, /* Sierra Wireless MC5720 */
111         { USB_DEVICE(0x1199, 0x0019) }, /* Sierra Wireless AirCard 595 */
112         { USB_DEVICE(0x1199, 0x0020) }, /* Sierra Wireless MC5725 */
113         { USB_DEVICE(0x1199, 0x0021) }, /* Sierra Wireless AirCard 597E */
114         { USB_DEVICE(0x1199, 0x0112) }, /* Sierra Wireless AirCard 580 */
115         { USB_DEVICE(0x1199, 0x0120) }, /* Sierra Wireless AirCard 595U */
116         { USB_DEVICE(0x1199, 0x0218) }, /* Sierra Wireless MC5720 */
117         { USB_DEVICE(0x1199, 0x0220) }, /* Sierra Wireless MC5725 */
118         { USB_DEVICE(0x1199, 0x6802) }, /* Sierra Wireless MC8755 */
119         { USB_DEVICE(0x1199, 0x6803) }, /* Sierra Wireless MC8765 */
120         { USB_DEVICE(0x1199, 0x6804) }, /* Sierra Wireless MC8755 */
121         { USB_DEVICE(0x1199, 0x6812) }, /* Sierra Wireless MC8775 */
122         { USB_DEVICE(0x1199, 0x6813) }, /* Sierra Wireless MC8755 */
123         { USB_DEVICE(0x1199, 0x6820) }, /* Sierra Wireless AirCard 875 */
124         { USB_DEVICE(0x1199, 0x6832) }, /* Sierra Wireless MC8780 */
125         { USB_DEVICE(0x1199, 0x6833) }, /* Sierra Wireless MC8781 */
126         { USB_DEVICE(0x1199, 0x6850) }, /* Sierra Wireless AirCard 880 */
127         { USB_DEVICE(0x1199, 0x6851) }, /* Sierra Wireless AirCard 881 */
128         { USB_DEVICE(0x1199, 0x6852) }, /* Sierra Wireless AirCard 880E */
129         { USB_DEVICE(0x1199, 0x6853) }, /* Sierra Wireless AirCard 881E */
130         { USB_DEVICE(0x1199, 0x6855) }, /* Sierra Wireless AirCard 880U */
131         { USB_DEVICE(0x1199, 0x6856) }, /* Sierra Wireless AirCard 881U */
132         { USB_DEVICE(0x12d1, 0x1001) }, /* Huawei Mobile Connect */
133         { USB_DEVICE(0x12d1, 0x1003) }, /* Huawei Mobile E220 */
134         { USB_DEVICE(0x12d1, 0x1004) }, /* Huawei Mobile E220 */
135         { USB_DEVICE(0x1410, 0x1100) }, /* Novatel Wireless Merlin XS620/S640 */
136         { USB_DEVICE(0x1410, 0x1110) }, /* Novatel Wireless Merlin S620/V620 */
137         { USB_DEVICE(0x1410, 0x1120) }, /* Novatel Wireless Merlin EX720 */
138         { USB_DEVICE(0x1410, 0x1130) }, /* Novatel Wireless Merlin S720 */
139         { USB_DEVICE(0x1410, 0x1400) }, /* Novatel Wireless Merlin U730 */
140         { USB_DEVICE(0x1410, 0x1410) }, /* Novatel Wireless Merlin U740 */
141         { USB_DEVICE(0x1410, 0x1420) }, /* Novatel Wireless Expedite EU870D */
142         { USB_DEVICE(0x1410, 0x1430) }, /* Novatel Wireless Merlin XU870 */
143         { USB_DEVICE(0x1410, 0x2100) }, /* Novatel Wireless Expedite EV620 */
144         { USB_DEVICE(0x1410, 0x2110) }, /* Novatel Wireless Merlin ES620,
145                                            Merlin ES720, Ovation U720 */
146         { USB_DEVICE(0x1410, 0x2130) }, /* Novatel Wireless Merlin ES620 */
147         { USB_DEVICE(0x1410, 0x2410) }, /* Novatel Wireless Expedite EU740 */
148         { USB_DEVICE(0x1410, 0x4100) }, /* Novatel Wireless Ovation MC727 */
149         { USB_DEVICE(0x1410, 0x4400) }, /* Novatel Wireless Ovation MC950D */
150         { USB_DEVICE(0x16d5, 0x6501) }, /* AnyDATA ADU-E100A/D/H */
151         { USB_DEVICE(0x413c, 0x8114) }, /* Dell Wireless 5700 */
152         { USB_DEVICE(0x413c, 0x8115) }, /* Dell Wireless 5500 */
153         { USB_DEVICE(0x413c, 0x8116) }, /* Dell Wireless 5505 */
154         { USB_DEVICE(0x413c, 0x8117) }, /* Dell Wireless 5700 */
155         { USB_DEVICE(0x413c, 0x8118) }, /* Dell Wireless 5510 */
156         { USB_DEVICE(0x413c, 0x8128) }, /* Dell Wireless 5700 */
157         { USB_DEVICE(0x413c, 0x8136) }, /* Dell Wireless 5520 */
158         { USB_DEVICE(0x413c, 0x8137) }, /* Dell Wireless 5520 */
159 };
160
161 DRIVER_MODULE(ugensa, uhub, ugensa_driver, ucom_devclass, usbd_driver_load, 0);
162 MODULE_DEPEND(ugensa, usb, 1, 1, 1);
163 MODULE_DEPEND(ugensa, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER);
164 MODULE_VERSION(ugensa, 1);
165
166 static int
167 ugensa_match(device_t self)
168 {
169         struct usb_attach_arg *uaa = device_get_ivars(self);
170
171         if (uaa->iface == NULL)
172                 return UMATCH_NONE;
173
174         return (usb_lookup(ugensa_devs, uaa->vendor, uaa->product) != NULL) ?
175             UMATCH_VENDOR_IFACESUBCLASS : UMATCH_NONE;
176 }
177
178 static int
179 ugensa_attach(device_t self)
180 {
181         struct ugensa_softc *sc = device_get_softc(self);
182         struct usb_attach_arg *uaa = device_get_ivars(self);
183         struct ucom_softc *ucom;
184         usb_interface_descriptor_t *id;
185         usb_endpoint_descriptor_t *ed;
186         int i;
187
188         ucom = &sc->sc_ucom;
189         bzero(sc, sizeof (struct ugensa_softc));
190
191         ucom->sc_dev = self;
192         ucom->sc_udev = uaa->device;
193         ucom->sc_iface = uaa->iface;
194
195         id = usbd_get_interface_descriptor(ucom->sc_iface);
196
197         sc->sc_iface_no = id->bInterfaceNumber;
198         ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1;
199         for (i = 0; i < id->bNumEndpoints; i++) {
200                 ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i);
201                 if (ed == NULL) {
202                         device_printf(ucom->sc_dev, "no endpoint descriptor "
203                                       "found for %d\n", i);
204                         goto error;
205                 }
206
207                 if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
208                     UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
209                         sc->sc_intr_number = ed->bEndpointAddress;
210                         sc->sc_isize = UGETW(ed->wMaxPacketSize);
211                 } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
212                     UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
213                         ucom->sc_bulkin_no = ed->bEndpointAddress;
214                 else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
215                     UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
216                         ucom->sc_bulkout_no = ed->bEndpointAddress;
217         }
218         if (ucom->sc_bulkin_no == -1 || ucom->sc_bulkout_no == -1) {
219                 device_printf(ucom->sc_dev, "missing endpoint\n");
220                 goto error;
221         }
222
223         sc->sc_dtr = sc->sc_rts = -1;
224
225         ucom->sc_parent = sc;
226         ucom->sc_portno = UCOM_UNK_PORTNO;
227         ucom->sc_ibufsize = UGENSABUFSZ;
228         ucom->sc_obufsize = UGENSABUFSZ;
229         ucom->sc_ibufsizepad = UGENSABUFSZ;
230         ucom->sc_opkthdrlen = 0;
231         ucom->sc_callback = &ugensa_callback;
232
233         usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, ucom->sc_udev,
234                            ucom->sc_dev);
235
236         DPRINTF(("%s: in = 0x%x, out = 0x%x\n",
237                  device_get_nameunit(ucom->sc_dev), ucom->sc_bulkin_no,
238                  ucom->sc_bulkout_no));
239
240         ucom_attach(&sc->sc_ucom);
241
242         return 0;
243
244 error:
245         ucom->sc_dying = 1;
246         return ENXIO;
247 }
248
249 static int
250 ugensa_detach(device_t self)
251 {
252         struct ugensa_softc *sc = device_get_softc(self);
253         int rv = 0;
254
255         /* close the interrupt endpoint if that is opened */
256         if (sc->sc_intr_pipe != NULL) {
257                 usbd_abort_pipe(sc->sc_intr_pipe);
258                 usbd_close_pipe(sc->sc_intr_pipe);
259                 kfree(sc->sc_intr_buf, M_USBDEV);
260                 sc->sc_intr_pipe = NULL;
261         }
262
263         DPRINTF(("ugensa_detach: sc=%p\n", sc));
264         sc->sc_ucom.sc_dying = 1;
265         rv = ucom_detach(&sc->sc_ucom);
266         usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_ucom.sc_udev,
267                            sc->sc_ucom.sc_dev);
268
269         return (rv);
270 }
271
272 #if 0 /* not yet */
273 int
274 ugensa_activate(struct device *self, enum devact act)
275 {
276         struct ugensa_softc *sc = (struct ugensa_softc *)self;
277         int rv = 0;
278
279         switch (act) {
280         case DVACT_ACTIVATE:
281                 break;
282
283         case DVACT_DEACTIVATE:
284                 if (sc->sc_subdev != NULL)
285                         rv = config_deactivate(sc->sc_subdev);
286                 sc->sc_dying = 1;
287                 break;
288         }
289         return (rv);
290 }
291 #endif
292
293 int
294 ugensa_open(void *addr, int portno)
295 {
296         struct ugensa_softc *sc = addr;
297         int err;
298
299         if (sc->sc_ucom.sc_dying)
300                 return (ENXIO);
301
302         if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) {
303                 sc->sc_intr_buf = kmalloc(sc->sc_isize, M_USBDEV, M_WAITOK);
304                 err = usbd_open_pipe_intr(sc->sc_ucom.sc_iface,
305                     sc->sc_intr_number, USBD_SHORT_XFER_OK, &sc->sc_intr_pipe,
306                     sc, sc->sc_intr_buf, sc->sc_isize, ugensa_intr,
307                     UGENSA_INTR_INTERVAL);
308                 if (err) {
309                         device_printf(sc->sc_ucom.sc_dev,
310                             "cannot open interrupt pipe (addr %d)\n",
311                             sc->sc_intr_number);
312                         return (EIO);
313                 }
314         }
315
316         return (0);
317 }
318
319 void
320 ugensa_close(void *addr, int portno)
321 {
322         struct ugensa_softc *sc = addr;
323         int err;
324
325         if (sc->sc_ucom.sc_dying)
326                 return;
327
328         if (sc->sc_intr_pipe != NULL) {
329                 err = usbd_abort_pipe(sc->sc_intr_pipe);
330                 if (err)
331                         device_printf(sc->sc_ucom.sc_dev,
332                             "abort interrupt pipe failed: %s\n",
333                             usbd_errstr(err));
334                 err = usbd_close_pipe(sc->sc_intr_pipe);
335                 if (err)
336                         device_printf(sc->sc_ucom.sc_dev,
337                             "close interrupt pipe failed: %s\n",
338                             usbd_errstr(err));
339                 kfree(sc->sc_intr_buf, M_USBDEV);
340                 sc->sc_intr_pipe = NULL;
341         }
342
343 }
344
345 void
346 ugensa_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
347 {
348         struct ugensa_softc *sc = priv;
349         usb_cdc_notification_t *buf;
350         u_char mstatus;
351
352         buf = (usb_cdc_notification_t *)sc->sc_intr_buf;
353         if (sc->sc_ucom.sc_dying)
354                 return;
355
356         if (status != USBD_NORMAL_COMPLETION) {
357                 if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
358                         return;
359
360                 device_printf(sc->sc_ucom.sc_dev,
361                     "ugensa_intr: abnormal status: %s\n", usbd_errstr(status));
362                 usbd_clear_endpoint_stall_async(sc->sc_intr_pipe);
363                 return;
364         }
365
366         if (buf->bmRequestType != UCDC_NOTIFICATION) {
367                 DPRINTF(("%s: umsm_intr: unknown message type(0x%02x)\n",
368                     sc->sc_dev.dv_xname, buf->bmRequestType));
369                 return;
370         }
371
372         if (buf->bNotification == UCDC_N_SERIAL_STATE) {
373                 /* invalid message length, discard it */
374                 if (UGETW(buf->wLength) != 2)
375                         return;
376                 /* XXX: sc_lsr is always 0 */
377                 sc->sc_lsr = sc->sc_msr = 0;
378                 mstatus = buf->data[0];
379                 if (ISSET(mstatus, UCDC_N_SERIAL_RI))
380                         sc->sc_msr |= UMSR_RI;
381                 if (ISSET(mstatus, UCDC_N_SERIAL_DSR))
382                         sc->sc_msr |= UMSR_DSR;
383                 if (ISSET(mstatus, UCDC_N_SERIAL_DCD))
384                         sc->sc_msr |= UMSR_DCD;
385         } else if (buf->bNotification != UCDC_N_CONNECTION_SPEED_CHANGE) {
386                 DPRINTF(("%s: umsm_intr: unknown notify message (0x%02x)\n",
387                     sc->sc_dev.dv_xname, buf->bNotification));
388                 return;
389         }
390
391         ucom_status_change(&sc->sc_ucom);
392 }
393
394 void
395 ugensa_get_status(void *addr, int portno, u_char *lsr, u_char *msr)
396 {
397         struct ugensa_softc *sc = addr;
398
399         if (lsr != NULL)
400                 *lsr = sc->sc_lsr;
401         if (msr != NULL)
402                 *msr = sc->sc_msr;
403 }
404
405 void
406 ugensa_set(void *addr, int portno, int reg, int onoff)
407 {
408         struct ugensa_softc *sc = addr;
409         usb_device_request_t req;
410         int ls;
411
412         switch (reg) {
413         case UCOM_SET_DTR:
414                 if (sc->sc_dtr == onoff)
415                         return;
416                 sc->sc_dtr = onoff;
417                 break;
418         case UCOM_SET_RTS:
419                 if (sc->sc_rts == onoff)
420                         return;
421                 sc->sc_rts = onoff;
422                 break;
423         default:
424                 return;
425         }
426
427         /* build an usb request */
428         ls = (sc->sc_dtr ? UCDC_LINE_DTR : 0) |
429             (sc->sc_rts ? UCDC_LINE_RTS : 0);
430         req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
431         req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
432         USETW(req.wValue, ls);
433         USETW(req.wIndex, sc->sc_iface_no);
434         USETW(req.wLength, 0);
435
436         (void)usbd_do_request(sc->sc_ucom.sc_udev, &req, 0);
437 }
438