/* * uppc: USB Pocket PC serial over usb driver * * Copyright 2003 Sam Lawrance * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. KEEP THE LOVE. * * $Id: uppc.c,v 1.9 2004/09/12 02:39:44 sam Exp $ */ /* * Supported devices are #defined below. * These would normally be added into src/sys/dev/usb/usbdevs. */ #define USB_PRODUCT_CASIO_2001 0x2001 #define USB_PRODUCT_CASIO_EM500 0x2002 #define USB_PRODUCT_COMPAQ_H3850 0x0003 #define USB_PRODUCT_COMPAQ_0032 0x0032 #define USB_VENDOR_DELL 0x413c #define USB_PRODUCT_DELL_AXIM 0x4001 #define USB_PRODUCT_HP_JORNADA_548 0x1016 #define USB_PRODUCT_HP_JORNADA_568 0x1116 #define USB_PRODUCT_HP_2016 0x2016 #define USB_PRODUCT_HP_2116 0x2116 #define USB_PRODUCT_HP_2216 0x2216 #define USB_PRODUCT_HP_3016 0x3016 #define USB_PRODUCT_HP_3116 0x3116 #define USB_PRODUCT_HP_3216 0x3216 #define USB_PRODUCT_HP_4016 0x4016 #define USB_PRODUCT_HP_4116 0x4116 #define USB_PRODUCT_HP_4216 0x4216 #define USB_PRODUCT_HP_5016 0x5016 #define USB_PRODUCT_HP_5116 0x5116 #define USB_PRODUCT_HP_5216 0x5216 #define USB_VENDOR_TOSHIBA 0x0930 #define USB_PRODUCT_TOSHIBA_UNK 0x0700 #define USB_PRODUCT_TOSHIBA_E330 0x0707 #define USB_PRODUCT_TOSHIBA_E740 0x0706 #define USB_PRODUCT_SAMSUNG_I730 0x6662 #include #include #include #include #include #include #include #include #include #include #include #include #if __FreeBSD_version >= 500014 #include #else #include #endif #include #include #include #include #include #include "usbdevs.h" #include #ifndef USBDEVNAME #define USBDEVNAME device_get_nameunit #endif static int uppcrtvendor = 0; static int uppcrtproduct = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, uppc, CTLFLAG_RW, 0, "USB uppc"); /* Rather than the user having to compile in their device id, * let them set a single dynamic runtime id via sysctl. * If user has multiple device types they will need to be * compiled in, but this should suit most people. */ SYSCTL_INT(_hw_usb_uppc, OID_AUTO, vendor, CTLFLAG_RW, &uppcrtvendor, 0, "uppc runtime vendor ID"); SYSCTL_INT(_hw_usb_uppc, OID_AUTO, product, CTLFLAG_RW, &uppcrtproduct, 0, "uppc runtime product ID"); #ifdef USB_DEBUG static int uppcdebug = 1; SYSCTL_INT(_hw_usb_uppc, OID_AUTO, debug, CTLFLAG_RW, &uppcdebug, 0, "uppc debug level"); #define DPRINTF(x) do { \ if (uppcdebug) \ logprintf x; \ } while (0) #define DPRINTFN(n, x) do { \ if (uppcdebug > (n)) \ logprintf x; \ } while (0) #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define UPPCIBUFSIZE 16384 #define UPPCOBUFSIZE 16384 #define UPPC_CONFIG_INDEX 1 #define UPPC_IFACE_INDEX 0 struct uppc_type { struct usb_devno uv_dev; u_int16_t uv_flags; }; /* New supported products go in here */ static const struct uppc_type uppc_devs[] = { {{ USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_H3850 }, 0}, {{ USB_VENDOR_CASIO, USB_PRODUCT_CASIO_2001 }, 0}, {{ USB_VENDOR_CASIO, USB_PRODUCT_CASIO_EM500 }, 0}, {{ USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_H3850 }, 0}, {{ USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_0032 }, 0}, {{ USB_VENDOR_DELL, USB_PRODUCT_DELL_AXIM }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_JORNADA_548 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_JORNADA_568 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_2016 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_2116 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_2216 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_3016 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_3116 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_3216 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_4016 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_4116 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_4216 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_5016 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_5116 }, 0}, {{ USB_VENDOR_HP, USB_PRODUCT_HP_5216 }, 0}, {{ USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_UNK }, 0}, {{ USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_E330 }, 0}, {{ USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_E740 }, 0}, {{ USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_I730 }, 0}, }; /* The ucom_softc structure plus anything else we might need. */ struct uppc_softc { struct ucom_softc sc_ucom; usbd_interface_handle sc_iface; int sc_lcr; /* UCDC line state */ struct usb_devno sc_devno; /* May be needed to support */ /* other models. */ }; /* Callback routines for ucom. */ static void uppc_set(void *, int, int, int); static void uppc_close(void *, int); /* Callback structure for ucom. */ struct ucom_callback uppc_callback = { NULL, uppc_set, NULL, NULL, NULL, uppc_close, NULL, NULL, }; /* Support routines. */ static void uppc_dtr(struct uppc_softc *sc, int onoff); static void uppc_rts(struct uppc_softc *sc, int onoff); static void uppc_break(struct uppc_softc* sc, int onoff); #define uppc_lookup(v, p) ((const struct uppc_type *)usb_lookup(uppc_devs, v, p)) USB_MATCH(uppc) { USB_MATCH_START(uppc, uaa); if (uaa->iface != NULL) return (UMATCH_NONE); /* Check the runtime device before the table */ if (uaa->vendor == uppcrtvendor && uaa->product == uppcrtproduct) return UMATCH_VENDOR_PRODUCT; return (uppc_lookup(uaa->vendor, uaa->product) != NULL ? UMATCH_VENDOR_PRODUCT : UMATCH_NONE); } /* device attach */ USB_ATTACH(uppc) { /* This gives us a valid struct uppc_softc *sc */ USB_ATTACH_START(uppc, sc, uaa); usbd_device_handle dev = uaa->device; usbd_interface_handle iface; usb_interface_descriptor_t *id; usb_endpoint_descriptor_t *ed; char *devinfo; const char *devname; int i; usbd_status err; struct ucom_softc *ucom = &sc->sc_ucom; DPRINTFN(10,("\nuppc_attach: sc=%p\n", sc)); devinfo = malloc(1024, M_USBDEV, M_WAITOK); /* PPC line control (dtr/rts) */ sc->sc_lcr = 0; sc->sc_devno.ud_product = uaa->product; sc->sc_devno.ud_vendor = uaa->vendor; ucom->sc_dev = self; ucom->sc_udev = dev; devname = USBDEVNAME(ucom->sc_dev); /* Move the device into the configured state. */ err = usbd_set_config_index(dev, UPPC_CONFIG_INDEX, 1); if (err) { printf("\n%s: failed to set configuration, err=%s\n", devname, usbd_errstr(err)); goto attachfail; } err = usbd_device2interface_handle(dev, UPPC_IFACE_INDEX, &iface); if (err) { printf("\n%s: failed to get interface, err=%s\n", devname, usbd_errstr(err)); goto attachfail; } usbd_devinfo(dev, 0, devinfo); if (uaa->vendor == uppcrtvendor && uaa->product == uppcrtproduct) printf("%s: %s (rt)\n", devname, devinfo); else printf("%s: %s\n", devname, devinfo); id = usbd_get_interface_descriptor(iface); ucom->sc_iface = iface; switch(uaa->product) { /* This is where we do any device-specific init stuff */ case USB_PRODUCT_COMPAQ_H3850: break; } ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; for (i = 0; i < id->bNumEndpoints; i++) { int addr, dir, attr; ed = usbd_interface2endpoint_descriptor(iface, i); if (ed == NULL) { printf("%s: could not read endpoint descriptor" ": %s\n", devname, usbd_errstr(err)); goto attachfail; } addr = ed->bEndpointAddress; dir = UE_GET_DIR(ed->bEndpointAddress); attr = ed->bmAttributes & UE_XFERTYPE; DPRINTF(("%s: endpoint=%d dir=%d attr=%d\n", devname, addr, dir, attr)); if (dir == UE_DIR_IN && attr == UE_BULK) ucom->sc_bulkin_no = addr; else if (dir == UE_DIR_OUT && attr == UE_BULK) ucom->sc_bulkout_no = addr; else if (attr == UE_INTERRUPT) printf("%s: ignoring interrupt endpoint\n", devname); else { printf("%s: unexpected endpoint\n", devname); goto attachfail; } } if (ucom->sc_bulkin_no == -1) { printf("%s: Could not find data bulk in\n", devname); goto attachfail; } if (ucom->sc_bulkout_no == -1) { printf("%s: Could not find data bulk out\n", devname); goto attachfail; } ucom->sc_portno = 0; ucom->sc_parent = sc; /* Bulkin, out set above. */ ucom->sc_ibufsize = UPPCIBUFSIZE; ucom->sc_obufsize = UPPCOBUFSIZE; ucom->sc_ibufsizepad = UPPCIBUFSIZE; ucom->sc_opkthdrlen = 0; ucom->sc_callback = &uppc_callback; #if __FreeBSD_version >= 501000 usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, ucom->sc_udev, USBDEV(ucom->sc_dev)); #endif DPRINTF(("%s: in=0x%x out=0x%x\n", devname, ucom->sc_bulkin_no, ucom->sc_bulkout_no)); ucom_attach(&sc->sc_ucom); free(devinfo, M_USBDEV); USB_ATTACH_SUCCESS_RETURN; attachfail: DPRINTF(("%s: uppc_attach: attach failed\n", devname)); ucom->sc_dying = 1; free(devinfo, M_USBDEV); USB_ATTACH_ERROR_RETURN; } /* device detach */ USB_DETACH(uppc) { USB_DETACH_START(uppc, sc); int rv = 0; DPRINTF(("%s: uppc_detach: sc=%p\n", USBDEVNAME(sc->sc_ucom.sc_dev), sc)); sc->sc_ucom.sc_dying = 1; rv = ucom_detach(&sc->sc_ucom); usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_ucom.sc_udev, USBDEV(sc->sc_ucom.sc_dev)); return rv; } void uppc_dtr(struct uppc_softc* sc, int onoff) { usb_device_request_t req; usbd_status err; int retries = 3; struct ucom_softc *ucom = &sc->sc_ucom; DPRINTF(("%s: uppc_dtr: onoff=%x\n", USBDEVNAME(ucom->sc_dev), onoff)); /* Avoid sending unnecessary requests */ if (onoff && (sc->sc_lcr & UCDC_LINE_DTR)) return; if (!onoff && !(sc->sc_lcr & UCDC_LINE_DTR)) return; /* Other parameters depend on reg */ req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; sc->sc_lcr = onoff ? sc->sc_lcr | UCDC_LINE_DTR : sc->sc_lcr & ~UCDC_LINE_DTR; USETW(req.wValue, sc->sc_lcr); USETW(req.wIndex, 0x0); USETW(req.wLength, 0); /* Fire off the request a few times if necessary */ while (retries) { err = usbd_do_request(ucom->sc_udev, &req, NULL); if (!err) break; retries--; } } void uppc_rts(struct uppc_softc* sc, int onoff) { usb_device_request_t req; usbd_status err; int retries = 3; struct ucom_softc *ucom = &sc->sc_ucom; DPRINTF(("%s: uppc_rts: onoff=%x\n", USBDEVNAME(ucom->sc_dev), onoff)); /* Avoid sending unnecessary requests */ if (onoff && (sc->sc_lcr & UCDC_LINE_RTS)) return; if (!onoff && !(sc->sc_lcr & UCDC_LINE_RTS)) return; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; sc->sc_lcr = onoff ? sc->sc_lcr | UCDC_LINE_RTS : sc->sc_lcr & ~UCDC_LINE_RTS; USETW(req.wValue, sc->sc_lcr); USETW(req.wIndex, 0x0); USETW(req.wLength, 0); while (retries) { err = usbd_do_request(ucom->sc_udev, &req, NULL); if (!err) break; retries--; } } void uppc_break(struct uppc_softc* sc, int onoff) { usb_device_request_t req; usbd_status err; int retries = 3; struct ucom_softc *ucom = &sc->sc_ucom; DPRINTF(("%s: uppc_break: onoff=%x\n", USBDEVNAME(ucom->sc_dev), onoff)); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_BREAK; USETW(req.wValue, onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); USETW(req.wIndex, 0x0); USETW(req.wLength, 0); while (retries) { err = usbd_do_request(ucom->sc_udev, &req, NULL); if (!err) break; retries--; } } /* Callback for setting DTR & RTS */ void uppc_set(void *addr, int portno, int reg, int onoff) { struct ucom_softc* ucom = addr; /* DTR/RTS state is stored in our uppc_softc. * We are called from ucom; it updates its own mcr. */ switch (reg) { case UCOM_SET_DTR: uppc_dtr(addr, onoff); break; case UCOM_SET_RTS: uppc_rts(addr, onoff); break; case UCOM_SET_BREAK: uppc_break(addr, onoff); break; default: printf("%s: unhandled set request: reg=%x onoff=%x\n", USBDEVNAME(ucom->sc_dev), reg, onoff); return; } } void uppc_close(void *addr, int portno) { /* I'm not sure if this is needed, but it doesn't hurt. */ uppc_dtr(addr, 0); } static device_method_t uppc_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, uppc_match), DEVMETHOD(device_attach, uppc_attach), DEVMETHOD(device_detach, uppc_detach), { 0, 0 } }; static driver_t uppc_driver = { "ucom", uppc_methods, sizeof (struct uppc_softc) }; DRIVER_MODULE(uppc, uhub, uppc_driver, ucom_devclass, usbd_driver_load, 0); MODULE_DEPEND(uppc, usb, 1, 1, 1); MODULE_DEPEND(uppc, ucom,UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER);