/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright 2010, Gleb Smirnoff * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #define USB_DEBUG_VAR uep_debug #include #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);