diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index fe3b23e435675520dea87eba59b00ef69208e2cb..9845eab9ebfe051776c29733af6d23e0e752484c 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -85,5 +85,7 @@ source "drivers/staging/rtl8187se/Kconfig" source "drivers/staging/rspiusb/Kconfig" +source "drivers/staging/mimio/Kconfig" + endif # !STAGING_EXCLUDE_BUILD endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index bd4cb9285a2f66f6ce007280aad7d4b179727fe1..968f333a659bb0916e5a9c343d01fa54c974acaf 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_PANEL) += panel/ obj-$(CONFIG_ALTERA_PCIE_CHDMA) += altpciechdma/ obj-$(CONFIG_RTL8187SE) += rtl8187se/ obj-$(CONFIG_USB_RSPI) += rspiusb/ +obj-$(CONFIG_INPUT_MIMIO) += mimio/ diff --git a/drivers/staging/mimio/Kconfig b/drivers/staging/mimio/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..c0ba4c800dfff7fc3a7599aaedaeecc500fac1a1 --- /dev/null +++ b/drivers/staging/mimio/Kconfig @@ -0,0 +1,10 @@ +config INPUT_MIMIO + tristate "Mimio Xi interactive whiteboard support" + depends on USB + default N + help + Say Y here if you want to use a Mimio Xi interactive + whiteboard device. + + To compile this driver as a module, choose M here: the + module will be called mimio. diff --git a/drivers/staging/mimio/Makefile b/drivers/staging/mimio/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..77807ee0450ea620c24f36dd4e86cf58ac7bb441 --- /dev/null +++ b/drivers/staging/mimio/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_INPUT_MIMIO) += mimio.o diff --git a/drivers/staging/mimio/mimio.c b/drivers/staging/mimio/mimio.c new file mode 100644 index 0000000000000000000000000000000000000000..1ba8103f5003db16e3382b88dabec5d5654fed23 --- /dev/null +++ b/drivers/staging/mimio/mimio.c @@ -0,0 +1,914 @@ +/* + * Hardware event => input event mapping: + * + * + * + input.h:#define BTN_TOOL_PEN 0x140 black + input.h:#define BTN_TOOL_RUBBER 0x141 blue + input.h:#define BTN_TOOL_BRUSH 0x142 green + input.h:#define BTN_TOOL_PENCIL 0x143 red + input.h:#define BTN_TOOL_AIRBRUSH 0x144 eraser + input.h:#define BTN_TOOL_FINGER 0x145 small eraser + input.h:#define BTN_TOOL_MOUSE 0x146 mimio interactive + input.h:#define BTN_TOOL_LENS 0x147 mimio interactive but1 + input.h:#define LOCALBTN_TOOL_EXTRA1 0x14a mimio interactive but2 == BTN_TOUCH + input.h:#define LOCALBTN_TOOL_EXTRA2 0x14b mimio extra pens (orange, brown, yellow, purple) == BTN_STYLUS + input.h:#define LOCALBTN_TOOL_EXTRA3 0x14c unused == BTN_STYLUS2 + input.h:#define BTN_TOOL_DOUBLETAP 0x14d unused + input.h:#define BTN_TOOL_TRIPLETAP 0x14e unused + * + * MIMIO_EV_PENDOWN(MIMIO_PEN_K) => EV_KEY BIT(BTN_TOOL_PEN) + * MIMIO_EV_PENDOWN(MIMIO_PEN_B) => EV_KEY BIT(BTN_TOOL_RUBBER) + * MIMIO_EV_PENDOWN(MIMIO_PEN_G) => EV_KEY BIT(BTN_TOOL_BRUSH) + * MIMIO_EV_PENDOWN(MIMIO_PEN_R) => EV_KEY BIT(BTN_TOOL_PENCIL) + * MIMIO_EV_PENDOWN(MIMIO_PEN_E) => EV_KEY BIT(BTN_TOOL_AIRBRUSH) + * MIMIO_EV_PENDOWN(MIMIO_PEN_ES) => EV_KEY BIT(BTN_TOOL_FINGER) + * MIMIO_EV_PENDOWN(MIMIO_PEN_I) => EV_KEY BIT(BTN_TOOL_MOUSE) + * MIMIO_EV_PENDOWN(MIMIO_PEN_IL) => EV_KEY BIT(BTN_TOOL_LENS) + * MIMIO_EV_PENDOWN(MIMIO_PEN_IR) => EV_KEY BIT(BTN_TOOL_DOUBLETAP) + * MIMIO_EV_PENDOWN(MIMIO_PEN_EX) => EV_KEY BIT(BTN_TOOL_TRIPLETAP) + * MIMIO_EV_PENDATA => EV_ABS BIT(ABS_X), BIT(ABS_Y) + * MIMIO_EV_MEMRESET => EV_KEY BIT(BTN_0) + * MIMIO_EV_ACC(ACC_NEWPAGE) => EV_KEY BIT(BTN_1) + * MIMIO_EV_ACC(ACC_TAGPAGE) => EV_KEY BIT(BTN_2) + * MIMIO_EV_ACC(ACC_PRINTPAGE) => EV_KEY BIT(BTN_3) + * MIMIO_EV_ACC(ACC_MAXIMIZE) => EV_KEY BIT(BTN_4) + * MIMIO_EV_ACC(ACC_FINDCTLPNL) => EV_KEY BIT(BTN_5) + * + * + * open issues: + * - cold-load of data captured when mimio in standalone mode not yet + * supported; need to snoop Win32 box to see datastream for this. + * - mimio mouse not yet supported; need to snoop Win32 box to see the + * datastream for this. + */ +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION "v0.031" +#define DRIVER_AUTHOR "mwilder@cs.nmsu.edu" +#define DRIVER_DESC "USB mimio-xi driver" + +enum {UPVALUE, DOWNVALUE, MOVEVALUE}; + +#define MIMIO_XRANGE_MAX 9600 +#define MIMIO_YRANGE_MAX 4800 + +#define LOCALBTN_TOOL_EXTRA1 BTN_TOUCH +#define LOCALBTN_TOOL_EXTRA2 BTN_STYLUS +#define LOCALBTN_TOOL_EXTRA3 BTN_STYLUS2 + +#define MIMIO_VENDOR_ID 0x08d3 +#define MIMIO_PRODUCT_ID 0x0001 +#define MIMIO_MAXPAYLOAD (8) +#define MIMIO_MAXNAMELEN (64) +#define MIMIO_TXWAIT (1) +#define MIMIO_TXDONE (2) + +#define MIMIO_EV_PENDOWN (0x22) +#define MIMIO_EV_PENDATA (0x24) +#define MIMIO_EV_PENUP (0x51) +#define MIMIO_EV_MEMRESET (0x45) +#define MIMIO_EV_ACC (0xb2) + +#define MIMIO_PEN_K (1) /* black pen */ +#define MIMIO_PEN_B (2) /* blue pen */ +#define MIMIO_PEN_G (3) /* green pen */ +#define MIMIO_PEN_R (4) /* red pen */ +/* 5, 6, 7, 8 are extra pens */ +#define MIMIO_PEN_E (9) /* big eraser */ +#define MIMIO_PEN_ES (10) /* lil eraser */ +#define MIMIO_PENJUMP_START (10) +#define MIMIO_PENJUMP (6) +#define MIMIO_PEN_I (17) /* mimio interactive */ +#define MIMIO_PEN_IL (18) /* mimio interactive button 1 */ +#define MIMIO_PEN_IR (19) /* mimio interactive button 2 */ + +#define MIMIO_PEN_MAX (MIMIO_PEN_IR) + +#define ACC_DONE (0) +#define ACC_NEWPAGE (1) +#define ACC_TAGPAGE (2) +#define ACC_PRINTPAGE (4) +#define ACC_MAXIMIZE (8) +#define ACC_FINDCTLPNL (16) + +#define isvalidtxsize(n) ((n) > 0 && (n) <= MIMIO_MAXPAYLOAD) + + +struct pktbuf { + unsigned char instr; + unsigned char buf[16]; + unsigned char *p; + unsigned char *q; +}; + +struct usbintendpt { + dma_addr_t dma; + struct urb *urb; + unsigned char *buf; + struct usb_endpoint_descriptor *desc; +}; + +struct mimio { + struct input_dev *idev; + struct usb_device *udev; + struct usb_interface *uifc; + int open; + int present; + int greeted; + int txflags; + char phys[MIMIO_MAXNAMELEN]; + struct usbintendpt in; + struct usbintendpt out; + struct pktbuf pktbuf; + unsigned char minor; + wait_queue_head_t waitq; + spinlock_t txlock; + void (*rxhandler)(struct mimio *, unsigned char *, unsigned int); + int last_pen_down; +}; + +static void mimio_close(struct input_dev *); +static void mimio_dealloc(struct mimio *); +static void mimio_disconnect(struct usb_interface *); +static int mimio_greet(struct mimio *); +static void mimio_irq_in(struct urb *); +static void mimio_irq_out(struct urb *); +static int mimio_open(struct input_dev *); +static int mimio_probe(struct usb_interface *, const struct usb_device_id *); +static void mimio_rx_handler(struct mimio *, unsigned char *, unsigned int); +static int mimio_tx(struct mimio *, const char *, int); + +static char mimio_name[] = "VirtualInk mimio-Xi"; +static struct usb_device_id mimio_table [] = { + { USB_DEVICE(MIMIO_VENDOR_ID, MIMIO_PRODUCT_ID) }, + { USB_DEVICE(0x0525, 0xa4a0) }, /* gadget zero firmware */ + { } +}; + +MODULE_DEVICE_TABLE(usb, mimio_table); + +static struct usb_driver mimio_driver = { + .name = "mimio", + .probe = mimio_probe, + .disconnect = mimio_disconnect, + .id_table = mimio_table, +}; + +static DECLARE_MUTEX(disconnect_sem); + +static void mimio_close(struct input_dev *idev) +{ + struct mimio *mimio; + + mimio = input_get_drvdata(idev); + if (!mimio) { + dev_err(&idev->dev, "null mimio attached to input device\n"); + return; + } + + if (mimio->open <= 0) + dev_err(&idev->dev, "mimio not open.\n"); + else + mimio->open--; + + if (mimio->present == 0 && mimio->open == 0) + mimio_dealloc(mimio); +} + +static void mimio_dealloc(struct mimio *mimio) +{ + if (mimio == NULL) + return; + + usb_kill_urb(mimio->in.urb); + + usb_kill_urb(mimio->out.urb); + + if (mimio->idev) { + input_unregister_device(mimio->idev); + if (mimio->idev->grab) + input_close_device(mimio->idev->grab); + else + dev_dbg(&mimio->idev->dev, "mimio->idev->grab == NULL" + " -- didn't call input_close_device\n"); + } + + usb_free_urb(mimio->in.urb); + + usb_free_urb(mimio->out.urb); + + if (mimio->in.buf) { + usb_buffer_free(mimio->udev, MIMIO_MAXPAYLOAD, mimio->in.buf, + mimio->in.dma); + } + + if (mimio->out.buf) + usb_buffer_free(mimio->udev, MIMIO_MAXPAYLOAD, mimio->out.buf, + mimio->out.dma); + + if (mimio->idev) + input_free_device(mimio->idev); + + kfree(mimio); +} + +static void mimio_disconnect(struct usb_interface *ifc) +{ + struct mimio *mimio; + + down(&disconnect_sem); + + mimio = usb_get_intfdata(ifc); + usb_set_intfdata(ifc, NULL); + dev_dbg(&mimio->idev->dev, "disconnect\n"); + + if (mimio) { + mimio->present = 0; + + if (mimio->open <= 0) + mimio_dealloc(mimio); + } + + up(&disconnect_sem); +} + +static int mimio_greet(struct mimio *mimio) +{ + const struct grtpkt { + int nbytes; + unsigned delay; + char data[8]; + } grtpkts[] = { + { 3, 0, { 0x11, 0x55, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 5, 0, { 0x53, 0x55, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00 } }, + { 5, 0, { 0x43, 0x55, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00 } }, + { 5, 0, { 0x33, 0x55, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00 } }, + { 5, 0, { 0x13, 0x00, 0x5e, 0x02, 0x4f, 0x00, 0x00, 0x00 } }, + { 5, 0, { 0x13, 0x00, 0x04, 0x03, 0x14, 0x00, 0x00, 0x00 } }, + { 5, 2, { 0x13, 0x00, 0x00, 0x04, 0x17, 0x00, 0x00, 0x00 } }, + { 5, 0, { 0x13, 0x00, 0x0d, 0x08, 0x16, 0x00, 0x00, 0x00 } }, + { 5, 0, { 0x13, 0x00, 0x4d, 0x01, 0x5f, 0x00, 0x00, 0x00 } }, + { 3, 0, { 0xf1, 0x55, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 7, 2, { 0x52, 0x55, 0x00, 0x07, 0x31, 0x55, 0x64, 0x00 } }, + { 0, 0, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + }; + int rslt; + const struct grtpkt *pkt; + + for (pkt = grtpkts; pkt->nbytes; pkt++) { + rslt = mimio_tx(mimio, pkt->data, pkt->nbytes); + if (rslt) + return rslt; + if (pkt->delay) + msleep(pkt->delay); + } + + return 0; +} + +static void mimio_irq_in(struct urb *urb) +{ + int rslt; + char *data; + const char *reason = "going down"; + struct mimio *mimio; + + mimio = urb->context; + + if (mimio == NULL) + /* paranoia */ + return; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ETIMEDOUT: + reason = "timeout -- unplugged?"; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(&mimio->idev->dev, "%s.\n", reason); + return; + default: + dev_dbg(&mimio->idev->dev, "unknown urb-status: %d.\n", + urb->status); + goto exit; + } + data = mimio->in.buf; + + if (mimio->rxhandler) + mimio->rxhandler(mimio, data, urb->actual_length); +exit: + /* + * Keep listening to device on same urb. + */ + rslt = usb_submit_urb(urb, GFP_ATOMIC); + if (rslt) + dev_err(&mimio->idev->dev, "usb_submit_urb failure: %d.\n", + rslt); +} + +static void mimio_irq_out(struct urb *urb) +{ + unsigned long flags; + struct mimio *mimio; + + mimio = urb->context; + + if (urb->status) + dev_dbg(&mimio->idev->dev, "urb-status: %d.\n", urb->status); + + spin_lock_irqsave(&mimio->txlock, flags); + mimio->txflags |= MIMIO_TXDONE; + spin_unlock_irqrestore(&mimio->txlock, flags); + wmb(); + wake_up(&mimio->waitq); +} + +static int mimio_open(struct input_dev *idev) +{ + int rslt; + struct mimio *mimio; + + rslt = 0; + down(&disconnect_sem); + mimio = input_get_drvdata(idev); + dev_dbg(&idev->dev, "mimio_open\n"); + + if (mimio == NULL) { + dev_err(&idev->dev, "null mimio.\n"); + rslt = -ENODEV; + goto exit; + } + + if (mimio->open++) + goto exit; + + if (mimio->present && !mimio->greeted) { + struct urb *urb = mimio->in.urb; + mimio->in.urb->dev = mimio->udev; + rslt = usb_submit_urb(mimio->in.urb, GFP_KERNEL); + if (rslt) { + dev_err(&idev->dev, "usb_submit_urb failure " + "(res = %d: %s). Not greeting.\n", + rslt, + (!urb ? "urb is NULL" : + (urb->hcpriv ? "urb->hcpriv is non-NULL" : + (!urb->complete ? "urb is not complete" : + (urb->number_of_packets <= 0 ? "urb has no packets" : + (urb->interval <= 0 ? "urb interval too small" : + "urb interval too large or some other error")))))); + rslt = -EIO; + goto exit; + } + rslt = mimio_greet(mimio); + if (rslt == 0) { + dev_dbg(&idev->dev, "Mimio greeted OK.\n"); + mimio->greeted = 1; + } else { + dev_dbg(&idev->dev, "Mimio greet Failure (%d)\n", + rslt); + } + } + +exit: + up(&disconnect_sem); + return rslt; +} + +static int mimio_probe(struct usb_interface *ifc, + const struct usb_device_id *id) +{ + char path[64]; + int pipe, maxp; + struct mimio *mimio; + struct usb_device *udev; + struct usb_host_interface *hostifc; + struct input_dev *input_dev; + int res = 0; + int i; + + udev = interface_to_usbdev(ifc); + + mimio = kzalloc(sizeof(struct mimio), GFP_KERNEL); + if (!mimio) + return -ENOMEM; + + input_dev = input_allocate_device(); + if (!input_dev) { + mimio_dealloc(mimio); + return -ENOMEM; + } + + mimio->uifc = ifc; + mimio->udev = udev; + mimio->pktbuf.p = mimio->pktbuf.buf; + mimio->pktbuf.q = mimio->pktbuf.buf; + /* init_input_dev(mimio->idev); */ + mimio->idev = input_dev; + init_waitqueue_head(&mimio->waitq); + spin_lock_init(&mimio->txlock); + hostifc = ifc->cur_altsetting; + + if (hostifc->desc.bNumEndpoints != 2) { + dev_err(&udev->dev, "Unexpected endpoint count: %d.\n", + hostifc->desc.bNumEndpoints); + mimio_dealloc(mimio); + return -ENODEV; + } + + mimio->in.desc = &(hostifc->endpoint[0].desc); + mimio->out.desc = &(hostifc->endpoint[1].desc); + + mimio->in.buf = usb_buffer_alloc(udev, MIMIO_MAXPAYLOAD, GFP_KERNEL, + &mimio->in.dma); + mimio->out.buf = usb_buffer_alloc(udev, MIMIO_MAXPAYLOAD, GFP_KERNEL, + &mimio->out.dma); + + if (mimio->in.buf == NULL || mimio->out.buf == NULL) { + dev_err(&udev->dev, "usb_buffer_alloc failure.\n"); + mimio_dealloc(mimio); + return -ENOMEM; + } + + mimio->in.urb = usb_alloc_urb(0, GFP_KERNEL); + mimio->out.urb = usb_alloc_urb(0, GFP_KERNEL); + + if (mimio->in.urb == NULL || mimio->out.urb == NULL) { + dev_err(&udev->dev, "usb_alloc_urb failure.\n"); + mimio_dealloc(mimio); + return -ENOMEM; + } + + /* + * Build the input urb. + */ + pipe = usb_rcvintpipe(udev, mimio->in.desc->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + if (maxp > MIMIO_MAXPAYLOAD) + maxp = MIMIO_MAXPAYLOAD; + usb_fill_int_urb(mimio->in.urb, udev, pipe, mimio->in.buf, maxp, + mimio_irq_in, mimio, mimio->in.desc->bInterval); + mimio->in.urb->transfer_dma = mimio->in.dma; + mimio->in.urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* + * Build the output urb. + */ + pipe = usb_sndintpipe(udev, mimio->out.desc->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + if (maxp > MIMIO_MAXPAYLOAD) + maxp = MIMIO_MAXPAYLOAD; + usb_fill_int_urb(mimio->out.urb, udev, pipe, mimio->out.buf, maxp, + mimio_irq_out, mimio, mimio->out.desc->bInterval); + mimio->out.urb->transfer_dma = mimio->out.dma; + mimio->out.urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* + * Build input device info + */ + usb_make_path(udev, path, 64); + snprintf(mimio->phys, MIMIO_MAXNAMELEN, "%s/input0", path); + input_set_drvdata(input_dev, mimio); + /* input_dev->dev = &ifc->dev; */ + input_dev->open = mimio_open; + input_dev->close = mimio_close; + input_dev->name = mimio_name; + input_dev->phys = mimio->phys; + input_dev->dev.parent = &ifc->dev; + + input_dev->id.bustype = BUS_USB; + input_dev->id.vendor = le16_to_cpu(udev->descriptor.idVendor); + input_dev->id.product = le16_to_cpu(udev->descriptor.idProduct); + input_dev->id.version = le16_to_cpu(udev->descriptor.bcdDevice); + + input_dev->evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS); + for (i = BTN_TOOL_PEN; i <= LOCALBTN_TOOL_EXTRA2; ++i) + set_bit(i, input_dev->keybit); + + input_dev->keybit[BIT_WORD(BTN_MISC)] |= BIT_MASK(BTN_0) | + BIT_MASK(BTN_1) | + BIT_MASK(BTN_2) | + BIT_MASK(BTN_3) | + BIT_MASK(BTN_4) | + BIT_MASK(BTN_5); + /* input_dev->keybit[BTN_MOUSE] |= BIT(BTN_LEFT); */ + input_dev->absbit[0] |= BIT_MASK(ABS_X) | BIT_MASK(ABS_Y); + input_set_abs_params(input_dev, ABS_X, 0, MIMIO_XRANGE_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, MIMIO_YRANGE_MAX, 0, 0); + input_dev->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); + +#if 0 + input_dev->absmin[ABS_X] = 0; + input_dev->absmin[ABS_Y] = 0; + input_dev->absmax[ABS_X] = 9600; + input_dev->absmax[ABS_Y] = 4800; + input_dev->absfuzz[ABS_X] = 0; + input_dev->absfuzz[ABS_Y] = 0; + input_dev->absflat[ABS_X] = 0; + input_dev->absflat[ABS_Y] = 0; +#endif + +#if 0 + /* this will just reduce the precision */ + input_dev->absfuzz[ABS_X] = 8; /* experimental; may need to change */ + input_dev->absfuzz[ABS_Y] = 8; /* experimental; may need to change */ +#endif + + /* + * Register the input device. + */ + res = input_register_device(mimio->idev); + if (res) { + dev_err(&udev->dev, "input_register_device failure (%d)\n", + res); + mimio_dealloc(mimio); + return -EIO; + } + dev_dbg(&mimio->idev->dev, "input: %s on %s (res = %d).\n", + input_dev->name, input_dev->phys, res); + + usb_set_intfdata(ifc, mimio); + mimio->present = 1; + + /* + * Submit the input urb to the usb subsystem. + */ + mimio->in.urb->dev = mimio->udev; + res = usb_submit_urb(mimio->in.urb, GFP_KERNEL); + if (res) { + dev_err(&mimio->idev->dev, "usb_submit_urb failure (%d)\n", + res); + mimio_dealloc(mimio); + return -EIO; + } + + /* + * Attempt to greet the mimio after giving + * it some post-init settling time. + * + * note: sometimes this sleep interval isn't + * long enough to permit the device to re-init + * after a hot-swap; maybe need to bump it up. + * + * As it is, this probably breaks module unloading support! + */ + msleep(1024); + + res = mimio_greet(mimio); + if (res == 0) { + dev_dbg(&mimio->idev->dev, "Mimio greeted OK.\n"); + mimio->greeted = 1; + mimio->rxhandler = mimio_rx_handler; + } else { + dev_dbg(&mimio->idev->dev, "Mimio greet Failure (%d)\n", res); + } + + return 0; +} + +static int handle_mimio_rx_penupdown(struct mimio *mimio, + int down, + const char *const instr[], + const int instr_ofst[]) +{ + int penid, x; + if (mimio->pktbuf.q - mimio->pktbuf.p < (down ? 4 : 3)) + return 1; /* partial pkt */ + + if (down) { + x = *mimio->pktbuf.p ^ *(mimio->pktbuf.p + 1) ^ + *(mimio->pktbuf.p + 2); + if (x != *(mimio->pktbuf.p + 3)) { + dev_dbg(&mimio->idev->dev, "EV_PEN%s: bad xsum.\n", + down ? "DOWN":"UP"); + /* skip this event data */ + mimio->pktbuf.p += 4; + /* decode any remaining events */ + return 0; + } + penid = mimio->pktbuf.instr = *(mimio->pktbuf.p + 2); + if (penid > MIMIO_PEN_MAX) { + dev_dbg(&mimio->idev->dev, + "Unmapped penID (not in [0, %d]): %d\n", + MIMIO_PEN_MAX, (int)mimio->pktbuf.instr); + penid = mimio->pktbuf.instr = 0; + } + mimio->last_pen_down = penid; + } else { + penid = mimio->last_pen_down; + } + dev_dbg(&mimio->idev->dev, "%s (id %d, code %d) %s.\n", instr[penid], + instr_ofst[penid], penid, down ? "down" : "up"); + + if (instr_ofst[penid] >= 0) { + int code = BTN_TOOL_PEN + instr_ofst[penid]; + int value = down ? DOWNVALUE : UPVALUE; + if (code > KEY_MAX) + dev_dbg(&mimio->idev->dev, "input_event will ignore " + "-- code (%d) > KEY_MAX\n", code); + if (!test_bit(code, mimio->idev->keybit)) + dev_dbg(&mimio->idev->dev, "input_event will ignore " + "-- bit for code (%d) not enabled\n", code); + if (!!test_bit(code, mimio->idev->key) == value) + dev_dbg(&mimio->idev->dev, "input_event will ignore " + "-- bit for code (%d) already set to %d\n", + code, value); + if (value != DOWNVALUE) { + /* input_regs(mimio->idev, regs); */ + input_report_key(mimio->idev, code, value); + input_sync(mimio->idev); + } else { + /* wait until we get some coordinates */ + } + } else { + dev_dbg(&mimio->idev->dev, "penID offset[%d] == %d is < 0 " + "- not sending\n", penid, instr_ofst[penid]); + } + mimio->pktbuf.p += down ? 4 : 3; /* 3 for up, 4 for down */ + return 0; +} + +/* + * Stay tuned for partial-packet excitement. + * + * This routine buffers data packets received from the mimio device + * in the mimio's data space. This buffering is necessary because + * the mimio's in endpoint can serve us partial packets of data, and + * we want the driver to support the servicing of multiple mimios. + * Empirical evidence gathered so far suggests that the method of + * buffering packet data in the mimio's data space works. Previous + * versions of this driver did not buffer packet data in each mimio's + * data-space, and were therefore not able to service multiple mimios. + * Note that since the caller of this routine is running in interrupt + * context, care needs to be taken to ensure that this routine does not + * become bloated, and it may be that another spinlock is needed in each + * mimio to guard the buffered packet data properly. + */ +static void mimio_rx_handler(struct mimio *mimio, + unsigned char *data, + unsigned int nbytes) +{ + struct device *dev = &mimio->idev->dev; + unsigned int x; + unsigned int y; + static const char * const instr[] = { + "?0", + "black pen", "blue pen", "green pen", "red pen", + "brown pen", "orange pen", "purple pen", "yellow pen", + "big eraser", "lil eraser", + "?11", "?12", "?13", "?14", "?15", "?16", + "mimio interactive", "interactive button1", + "interactive button2" + }; + + /* Mimio Interactive gives: + * down: [0x22 0x01 0x11 0x32 0x24] + * b1 : [0x22 0x01 0x12 0x31 0x24] + * b2 : [0x22 0x01 0x13 0x30 0x24] + */ + static const int instr_ofst[] = { + -1, + 0, 1, 2, 3, + 9, 9, 9, 9, + 4, 5, + -1, -1, -1, -1, -1, -1, + 6, 7, 8, + }; + + memcpy(mimio->pktbuf.q, data, nbytes); + mimio->pktbuf.q += nbytes; + + while (mimio->pktbuf.p < mimio->pktbuf.q) { + int t = *mimio->pktbuf.p; + switch (t) { + case MIMIO_EV_PENUP: + case MIMIO_EV_PENDOWN: + if (handle_mimio_rx_penupdown(mimio, + t == MIMIO_EV_PENDOWN, + instr, instr_ofst)) + return; /* partial packet */ + break; + + case MIMIO_EV_PENDATA: + if (mimio->pktbuf.q - mimio->pktbuf.p < 6) + /* partial pkt */ + return; + x = *mimio->pktbuf.p ^ *(mimio->pktbuf.p + 1) ^ + *(mimio->pktbuf.p + 2) ^ + *(mimio->pktbuf.p + 3) ^ + *(mimio->pktbuf.p + 4); + if (x != *(mimio->pktbuf.p + 5)) { + dev_dbg(dev, "EV_PENDATA: bad xsum.\n"); + mimio->pktbuf.p += 6; /* skip this event data */ + break; /* decode any remaining events */ + } + x = *(mimio->pktbuf.p + 1); + x <<= 8; + x |= *(mimio->pktbuf.p + 2); + y = *(mimio->pktbuf.p + 3); + y <<= 8; + y |= *(mimio->pktbuf.p + 4); + dev_dbg(dev, "coord: (%d, %d)\n", x, y); + if (instr_ofst[mimio->pktbuf.instr] >= 0) { + int code = BTN_TOOL_PEN + + instr_ofst[mimio->last_pen_down]; +#if 0 + /* Utter hack to ensure we get forwarded _AND_ + * so we can identify when a complete signal is + * received */ + mimio->idev->abs[ABS_Y] = -1; + mimio->idev->abs[ABS_X] = -1; +#endif + /* input_regs(mimio->idev, regs); */ + input_report_abs(mimio->idev, ABS_X, x); + input_report_abs(mimio->idev, ABS_Y, y); + /* fake a penup */ + change_bit(code, mimio->idev->key); + input_report_key(mimio->idev, + code, + DOWNVALUE); + /* always sync here */ + mimio->idev->sync = 0; + input_sync(mimio->idev); + } + mimio->pktbuf.p += 6; + break; + case MIMIO_EV_MEMRESET: + if (mimio->pktbuf.q - mimio->pktbuf.p < 7) + /* partial pkt */ + return; + dev_dbg(dev, "mem-reset.\n"); + /* input_regs(mimio->idev, regs); */ + input_event(mimio->idev, EV_KEY, BTN_0, 1); + input_event(mimio->idev, EV_KEY, BTN_0, 0); + input_sync(mimio->idev); + mimio->pktbuf.p += 7; + break; + case MIMIO_EV_ACC: + if (mimio->pktbuf.q - mimio->pktbuf.p < 4) + /* partial pkt */ + return; + x = *mimio->pktbuf.p ^ *(mimio->pktbuf.p + 1) ^ + *(mimio->pktbuf.p + 2); + if (x != *(mimio->pktbuf.p + 3)) { + dev_dbg(dev, "EV_ACC: bad xsum.\n"); + mimio->pktbuf.p += 4; /* skip this event data */ + break; /* decode any remaining events */ + } + switch (*(mimio->pktbuf.p + 2)) { + case ACC_NEWPAGE: + dev_dbg(&mimio->idev->dev, "new-page.\n"); + /* input_regs(mimio->idev, regs); */ + input_event(mimio->idev, EV_KEY, BTN_1, 1); + input_event(mimio->idev, EV_KEY, BTN_1, 0); + input_sync(mimio->idev); + break; + case ACC_TAGPAGE: + dev_dbg(&mimio->idev->dev, "tag-page.\n"); + /* input_regs(mimio->idev, regs); */ + input_event(mimio->idev, EV_KEY, BTN_2, 1); + input_event(mimio->idev, EV_KEY, BTN_2, 0); + input_sync(mimio->idev); + break; + case ACC_PRINTPAGE: + dev_dbg(&mimio->idev->dev, "print-page.\n"); + /* input_regs(mimio->idev, regs);*/ + input_event(mimio->idev, EV_KEY, BTN_3, 1); + input_event(mimio->idev, EV_KEY, BTN_3, 0); + input_sync(mimio->idev); + break; + case ACC_MAXIMIZE: + dev_dbg(&mimio->idev->dev, + "maximize-window.\n"); + /* input_regs(mimio->idev, regs); */ + input_event(mimio->idev, EV_KEY, BTN_4, 1); + input_event(mimio->idev, EV_KEY, BTN_4, 0); + input_sync(mimio->idev); + break; + case ACC_FINDCTLPNL: + dev_dbg(&mimio->idev->dev, "find-ctl-panel.\n"); + /* input_regs(mimio->idev, regs); */ + input_event(mimio->idev, EV_KEY, BTN_5, 1); + input_event(mimio->idev, EV_KEY, BTN_5, 0); + input_sync(mimio->idev); + break; + case ACC_DONE: + dev_dbg(&mimio->idev->dev, "acc-done.\n"); + /* no event is dispatched to the input + * subsystem for this device event. + */ + break; + default: + dev_dbg(dev, "unknown acc event.\n"); + break; + } + mimio->pktbuf.p += 4; + break; + default: + mimio->pktbuf.p++; + break; + } + } + + /* + * No partial event was received, so reset mimio's pktbuf ptrs. + */ + mimio->pktbuf.p = mimio->pktbuf.q = mimio->pktbuf.buf; +} + +static int mimio_tx(struct mimio *mimio, const char *buf, int nbytes) +{ + int rslt; + int timeout; + unsigned long flags; + DECLARE_WAITQUEUE(wait, current); + + if (!(isvalidtxsize(nbytes))) { + dev_err(&mimio->idev->dev, "invalid arg: nbytes: %d.\n", + nbytes); + return -EINVAL; + } + + /* + * Init the out urb and copy the data to send. + */ + mimio->out.urb->dev = mimio->udev; + mimio->out.urb->transfer_buffer_length = nbytes; + memcpy(mimio->out.urb->transfer_buffer, buf, nbytes); + + /* + * Send the data. + */ + spin_lock_irqsave(&mimio->txlock, flags); + mimio->txflags = MIMIO_TXWAIT; + rslt = usb_submit_urb(mimio->out.urb, GFP_ATOMIC); + spin_unlock_irqrestore(&mimio->txlock, flags); + dev_dbg(&mimio->idev->dev, "rslt: %d.\n", rslt); + + if (rslt) { + dev_err(&mimio->idev->dev, "usb_submit_urb failure: %d.\n", + rslt); + return rslt; + } + + /* + * Wait for completion to be signalled (the mimio_irq_out + * completion routine will or MIMIO_TXDONE in with txflags). + */ + timeout = HZ; + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&mimio->waitq, &wait); + + while (timeout && ((mimio->txflags & MIMIO_TXDONE) == 0)) { + timeout = schedule_timeout(timeout); + rmb(); + } + + if ((mimio->txflags & MIMIO_TXDONE) == 0) + dev_dbg(&mimio->idev->dev, "tx timed out.\n"); + + /* + * Now that completion has been signalled, + * unlink the urb so that it can be recycled. + */ + set_current_state(TASK_RUNNING); + remove_wait_queue(&mimio->waitq, &wait); + usb_unlink_urb(mimio->out.urb); + + return rslt; +} + +static int __init mimio_init(void) +{ + int rslt; + + rslt = usb_register(&mimio_driver); + if (rslt != 0) { + err("%s: usb_register failure: %d", __func__, rslt); + return rslt; + } + + printk(KERN_INFO KBUILD_MODNAME ":" + DRIVER_DESC " " DRIVER_VERSION "\n"); + return rslt; +} + +static void __exit mimio_exit(void) +{ + usb_deregister(&mimio_driver); +} + +module_init(mimio_init); +module_exit(mimio_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL");