diff options
Diffstat (limited to 'freebsd/sys/dev/usb/input/uep.c')
-rw-r--r-- | freebsd/sys/dev/usb/input/uep.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/freebsd/sys/dev/usb/input/uep.c b/freebsd/sys/dev/usb/input/uep.c new file mode 100644 index 00000000..93441974 --- /dev/null +++ b/freebsd/sys/dev/usb/input/uep.c @@ -0,0 +1,446 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright 2010, Gleb Smirnoff <glebius@FreeBSD.org> + * 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. + * + * $FreeBSD$ + */ + +/* + * http://www.eeti.com.tw/pdf/Software%20Programming%20Guide_v2.0.pdf + */ + +#include <rtems/bsd/sys/param.h> +#include <sys/bus.h> +#include <sys/callout.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/sysctl.h> +#include <sys/systm.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbhid.h> +#include <rtems/bsd/local/usbdevs.h> + +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/tty.h> + +#define USB_DEBUG_VAR uep_debug +#include <dev/usb/usb_debug.h> + +#ifdef USB_DEBUG +static int uep_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW, 0, "USB uep"); +SYSCTL_INT(_hw_usb_uep, OID_AUTO, debug, CTLFLAG_RWTUN, + &uep_debug, 0, "Debug level"); +#endif + +#define UEP_MAX_X 2047 +#define UEP_MAX_Y 2047 + +#define UEP_DOWN 0x01 +#define UEP_PACKET_LEN_MAX 16 +#define UEP_PACKET_LEN_REPORT 5 +#define UEP_PACKET_LEN_REPORT2 6 +#define UEP_PACKET_DIAG 0x0a +#define UEP_PACKET_REPORT_MASK 0xe0 +#define UEP_PACKET_REPORT 0x80 +#define UEP_PACKET_REPORT_PRESSURE 0xc0 +#define UEP_PACKET_REPORT_PLAYER 0xa0 +#define UEP_PACKET_LEN_MASK + +#define UEP_FIFO_BUF_SIZE 8 /* bytes */ +#define UEP_FIFO_QUEUE_MAXLEN 50 /* units */ + +enum { + UEP_INTR_DT, + UEP_N_TRANSFER, +}; + +struct uep_softc { + struct mtx mtx; + + struct usb_xfer *xfer[UEP_N_TRANSFER]; + struct usb_fifo_sc fifo; + + u_int pollrate; + u_int state; +#define UEP_ENABLED 0x01 + + /* Reassembling buffer. */ + u_char buf[UEP_PACKET_LEN_MAX]; + uint8_t buf_len; +}; + +static usb_callback_t uep_intr_callback; + +static device_probe_t uep_probe; +static device_attach_t uep_attach; +static device_detach_t uep_detach; + +static usb_fifo_cmd_t uep_start_read; +static usb_fifo_cmd_t uep_stop_read; +static usb_fifo_open_t uep_open; +static usb_fifo_close_t uep_close; + +static void uep_put_queue(struct uep_softc *, u_char *); + +static struct usb_fifo_methods uep_fifo_methods = { + .f_open = &uep_open, + .f_close = &uep_close, + .f_start_read = &uep_start_read, + .f_stop_read = &uep_stop_read, + .basename[0] = "uep", +}; + +static int +get_pkt_len(u_char *buf) +{ + if (buf[0] == UEP_PACKET_DIAG) { + int len; + + len = buf[1] + 2; + if (len > UEP_PACKET_LEN_MAX) { + DPRINTF("bad packet len %u\n", len); + return (UEP_PACKET_LEN_MAX); + } + + return (len); + } + + switch (buf[0] & UEP_PACKET_REPORT_MASK) { + case UEP_PACKET_REPORT: + return (UEP_PACKET_LEN_REPORT); + case UEP_PACKET_REPORT_PRESSURE: + case UEP_PACKET_REPORT_PLAYER: + case UEP_PACKET_REPORT_PRESSURE | UEP_PACKET_REPORT_PLAYER: + return (UEP_PACKET_LEN_REPORT2); + default: + DPRINTF("bad packet len 0\n"); + return (0); + } +} + +static void +uep_process_pkt(struct uep_softc *sc, u_char *buf) +{ + int32_t x, y; + + if ((buf[0] & 0xFE) != 0x80) { + DPRINTF("bad input packet format 0x%.2x\n", buf[0]); + return; + } + + /* + * Packet format is 5 bytes: + * + * 1000000T + * 0000AAAA + * 0AAAAAAA + * 0000BBBB + * 0BBBBBBB + * + * T: 1=touched 0=not touched + * A: bits of axis A position, MSB to LSB + * B: bits of axis B position, MSB to LSB + * + * For the unit I have, which is CTF1020-S from CarTFT.com, + * A = X and B = Y. But in NetBSD uep(4) it is other way round :) + * + * The controller sends a stream of T=1 events while the + * panel is touched, followed by a single T=0 event. + * + */ + + x = (buf[1] << 7) | buf[2]; + y = (buf[3] << 7) | buf[4]; + + DPRINTFN(2, "x %u y %u\n", x, y); + + uep_put_queue(sc, buf); +} + +static void +uep_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uep_softc *sc = usbd_xfer_softc(xfer); + int len; + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + { + struct usb_page_cache *pc; + u_char buf[17], *p; + int pkt_len; + + if (len > (int)sizeof(buf)) { + DPRINTF("bad input length %d\n", len); + goto tr_setup; + } + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, len); + + /* + * The below code mimics Linux a lot. I don't know + * why NetBSD reads complete packets, but we need + * to reassamble 'em like Linux does (tries?). + */ + if (sc->buf_len > 0) { + int res; + + if (sc->buf_len == 1) + sc->buf[1] = buf[0]; + + if ((pkt_len = get_pkt_len(sc->buf)) == 0) + goto tr_setup; + + res = pkt_len - sc->buf_len; + memcpy(sc->buf + sc->buf_len, buf, res); + uep_process_pkt(sc, sc->buf); + sc->buf_len = 0; + + p = buf + res; + len -= res; + } else + p = buf; + + if (len == 1) { + sc->buf[0] = buf[0]; + sc->buf_len = 1; + + goto tr_setup; + } + + while (len > 0) { + if ((pkt_len = get_pkt_len(p)) == 0) + goto tr_setup; + + /* full packet: process */ + if (pkt_len <= len) { + uep_process_pkt(sc, p); + } else { + /* incomplete packet: save in buffer */ + memcpy(sc->buf, p, len); + sc->buf_len = len; + } + p += pkt_len; + len -= pkt_len; + } + } + case USB_ST_SETUP: + tr_setup: + /* check if we can put more data into the FIFO */ + if (usb_fifo_put_bytes_max(sc->fifo.fp[USB_FIFO_RX]) != 0) { + usbd_xfer_set_frame_len(xfer, 0, + usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + + default: + if (error != USB_ERR_CANCELLED) { + /* try clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static const struct usb_config uep_config[UEP_N_TRANSFER] = { + [UEP_INTR_DT] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uep_intr_callback, + }, +}; + +static const STRUCT_USB_HOST_ID uep_devs[] = { + {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL, 0)}, + {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL2, 0)}, + {USB_VPI(USB_VENDOR_EGALAX2, USB_PRODUCT_EGALAX2_TPANEL, 0)}, +}; + +static int +uep_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != 0) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(uep_devs, sizeof(uep_devs), uaa)); +} + +static int +uep_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uep_softc *sc = device_get_softc(dev); + int error; + + device_set_usb_desc(dev); + + mtx_init(&sc->mtx, "uep lock", NULL, MTX_DEF); + + error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, + sc->xfer, uep_config, UEP_N_TRANSFER, sc, &sc->mtx); + + if (error) { + DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error)); + goto detach; + } + + error = usb_fifo_attach(uaa->device, sc, &sc->mtx, &uep_fifo_methods, + &sc->fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + if (error) { + DPRINTF("usb_fifo_attach error=%s\n", usbd_errstr(error)); + goto detach; + } + + sc->buf_len = 0; + + return (0); + +detach: + uep_detach(dev); + + return (ENOMEM); /* XXX */ +} + +static int +uep_detach(device_t dev) +{ + struct uep_softc *sc = device_get_softc(dev); + + usb_fifo_detach(&sc->fifo); + + usbd_transfer_unsetup(sc->xfer, UEP_N_TRANSFER); + + mtx_destroy(&sc->mtx); + + return (0); +} + +static void +uep_start_read(struct usb_fifo *fifo) +{ + struct uep_softc *sc = usb_fifo_softc(fifo); + u_int rate; + + if ((rate = sc->pollrate) > 1000) + rate = 1000; + + if (rate > 0 && sc->xfer[UEP_INTR_DT] != NULL) { + usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); + usbd_xfer_set_interval(sc->xfer[UEP_INTR_DT], 1000 / rate); + sc->pollrate = 0; + } + + usbd_transfer_start(sc->xfer[UEP_INTR_DT]); +} + +static void +uep_stop_read(struct usb_fifo *fifo) +{ + struct uep_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); +} + +static void +uep_put_queue(struct uep_softc *sc, u_char *buf) +{ + usb_fifo_put_data_linear(sc->fifo.fp[USB_FIFO_RX], buf, + UEP_PACKET_LEN_REPORT, 1); +} + +static int +uep_open(struct usb_fifo *fifo, int fflags) +{ + if (fflags & FREAD) { + struct uep_softc *sc = usb_fifo_softc(fifo); + + if (sc->state & UEP_ENABLED) + return (EBUSY); + if (usb_fifo_alloc_buffer(fifo, UEP_FIFO_BUF_SIZE, + UEP_FIFO_QUEUE_MAXLEN)) + return (ENOMEM); + + sc->state |= UEP_ENABLED; + } + + return (0); +} + +static void +uep_close(struct usb_fifo *fifo, int fflags) +{ + if (fflags & FREAD) { + struct uep_softc *sc = usb_fifo_softc(fifo); + + sc->state &= ~(UEP_ENABLED); + usb_fifo_free_buffer(fifo); + } +} + +static devclass_t uep_devclass; + +static device_method_t uep_methods[] = { + DEVMETHOD(device_probe, uep_probe), + DEVMETHOD(device_attach, uep_attach), + DEVMETHOD(device_detach, uep_detach), + { 0, 0 }, +}; + +static driver_t uep_driver = { + .name = "uep", + .methods = uep_methods, + .size = sizeof(struct uep_softc), +}; + +DRIVER_MODULE(uep, uhub, uep_driver, uep_devclass, NULL, NULL); +MODULE_DEPEND(uep, usb, 1, 1, 1); +MODULE_VERSION(uep, 1); +USB_PNP_HOST_INFO(uep_devs); |