Hi Martin,

On Wed, Jun 01, 2016 at 02:55:03PM +0200, Martin Kepplinger wrote:
> This adds a driver for the Pegasus Notetaker Pen. When connected,
> this uses the Pen as an input tablet.
> 
> This device was sold in various different brandings, for example
>       "Pegasus Mobile Notetaker M210",
>       "Genie e-note The Notetaker",
>       "Staedtler Digital ballpoint pen 990 01",
>       "IRISnotes Express" or
>       "NEWLink Digital Note Taker".
> 
> Here's an example, so that you know what we are talking about:
> http://www.genie-online.de/genie-e-note-2/
> 
> https://pegatech.blogspot.com/ seems to be a remaining official resource.
> 
> This device can also transfer saved (offline recorded handwritten) data and
> there are userspace programs that do this, see https://launchpad.net/m210
> (Well, alternatively there are really fast scanners out there :)
> 
> It's *really* fun to use as an input tablet though! So let's support this
> for everybody.
> 
> Signed-off-by: Martin Kepplinger <mart...@posteo.de>
> ---
> 
> I thought about PM again and I think this is how it's supposed to be, and
> how it's done in many other usb input drivers.
> 
> I'm running it and don't have any problems. It feels more finished now.
> 
> Any objections? thanks!

2 more tiny nits, and I think I'll be able to merge this.

> 
> revision history
> ================
> v7 add usb_driver power management
> v6 minor cleanup (thanks Dmitry)
> v5 fix various bugs (thanks Oliver and Dmitry)
> v4 use normal work queue instead of a kernel thread (thanks to Oliver Neukum)
> v3 fix reporting low pen battery and add USB list to CC
> v2 minor cleanup (remove unnecessary variables)
> v1 initial release
> 
> 
>  drivers/input/tablet/Kconfig             |  15 ++
>  drivers/input/tablet/Makefile            |   1 +
>  drivers/input/tablet/pegasus_notetaker.c | 412 
> +++++++++++++++++++++++++++++++
>  3 files changed, 428 insertions(+)
>  create mode 100644 drivers/input/tablet/pegasus_notetaker.c
> 
> diff --git a/drivers/input/tablet/Kconfig b/drivers/input/tablet/Kconfig
> index 623bb9e..a2b9f97 100644
> --- a/drivers/input/tablet/Kconfig
> +++ b/drivers/input/tablet/Kconfig
> @@ -73,6 +73,21 @@ config TABLET_USB_KBTAB
>         To compile this driver as a module, choose M here: the
>         module will be called kbtab.
>  
> +config TABLET_USB_PEGASUS
> +     tristate "Pegasus Mobile Notetaker Pen input tablet support"
> +     depends on USB_ARCH_HAS_HCD
> +     select USB
> +     help
> +       Say Y here if you want to use the Pegasus Mobile Notetaker,
> +       also known as:
> +       Genie e-note The Notetaker,
> +       Staedtler Digital ballpoint pen 990 01,
> +       IRISnotes Express or
> +       NEWLink Digital Note Taker.
> +
> +       To compile this driver as a module, choose M here: the
> +       module will be called pegasus_notetaker.
> +
>  config TABLET_SERIAL_WACOM4
>       tristate "Wacom protocol 4 serial tablet support"
>       select SERIO
> diff --git a/drivers/input/tablet/Makefile b/drivers/input/tablet/Makefile
> index 2e13010..200fc4e 100644
> --- a/drivers/input/tablet/Makefile
> +++ b/drivers/input/tablet/Makefile
> @@ -8,4 +8,5 @@ obj-$(CONFIG_TABLET_USB_AIPTEK)       += aiptek.o
>  obj-$(CONFIG_TABLET_USB_GTCO)        += gtco.o
>  obj-$(CONFIG_TABLET_USB_HANWANG) += hanwang.o
>  obj-$(CONFIG_TABLET_USB_KBTAB)       += kbtab.o
> +obj-$(CONFIG_TABLET_USB_PEGASUS) += pegasus_notetaker.o
>  obj-$(CONFIG_TABLET_SERIAL_WACOM4) += wacom_serial4.o
> diff --git a/drivers/input/tablet/pegasus_notetaker.c 
> b/drivers/input/tablet/pegasus_notetaker.c
> new file mode 100644
> index 0000000..f403494
> --- /dev/null
> +++ b/drivers/input/tablet/pegasus_notetaker.c
> @@ -0,0 +1,412 @@
> +/*
> + * Pegasus Mobile Notetaker Pen input tablet driver
> + *
> + * Copyright (c) 2016 Martin Kepplinger <mart...@posteo.de>
> + */
> +
> +/*
> + * request packet (control endpoint):
> + * |-------------------------------------|
> + * | Report ID | Nr of bytes | command   |
> + * | (1 byte)  | (1 byte)    | (n bytes) |
> + * |-------------------------------------|
> + * | 0x02      | n           |           |
> + * |-------------------------------------|
> + *
> + * data packet after set xy mode command, 0x80 0xb5 0x02 0x01
> + * and pen is in range:
> + *
> + * byte      byte name               value (bits)
> + * --------------------------------------------
> + * 0 status                  0 1 0 0 0 0 X X
> + * 1 color                   0 0 0 0 H 0 S T
> + * 2 X low
> + * 3 X high
> + * 4 Y low
> + * 5 Y high
> + *
> + * X X       battery state:
> + *   no state reported       0x00
> + *   battery low             0x01
> + *   battery good            0x02
> + *
> + * H Hovering
> + * S Switch 1 (pen button)
> + * T Tip
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/hid.h>
> +#include <linux/usb/input.h>
> +
> +/* USB HID defines */
> +#define USB_REQ_GET_REPORT           0x01
> +#define USB_REQ_SET_REPORT           0x09
> +
> +#define USB_VENDOR_ID_PEGASUSTECH    0x0e20
> +#define USB_DEVICE_ID_PEGASUS_NOTETAKER_EN100        0x0101
> +
> +/* device specific defines */
> +#define NOTETAKER_REPORT_ID          0x02
> +#define NOTETAKER_SET_CMD            0x80
> +#define NOTETAKER_SET_MODE           0xb5
> +
> +#define NOTETAKER_LED_MOUSE             0x02
> +#define PEN_MODE_XY                     0x01
> +
> +#define SPECIAL_COMMAND                      0x80
> +#define BUTTON_PRESSED                       0xb5
> +#define COMMAND_VERSION                      0xa9
> +
> +/* in xy data packet */
> +#define BATTERY_NO_REPORT            0x40
> +#define BATTERY_LOW                  0x41
> +#define BATTERY_GOOD                 0x42
> +#define PEN_BUTTON_PRESSED           BIT(1)
> +#define PEN_TIP                              BIT(0)
> +
> +struct pegasus {
> +     unsigned char *data;
> +     u8 data_len;
> +     dma_addr_t data_dma;
> +     struct input_dev *dev;
> +     struct usb_device *usbdev;
> +     struct usb_interface *intf;
> +     struct urb *irq;
> +     char name[128];
> +     char phys[64];
> +     struct work_struct init;
> +};
> +
> +static void pegasus_control_msg(struct pegasus *pegasus, u8 *data, int len)
> +{
> +     const int sizeof_buf = len + 2;
> +     int result;
> +     u8 *cmd_buf;
> +
> +     cmd_buf = kmalloc(sizeof_buf, GFP_KERNEL);
> +     if (!cmd_buf)
> +             return;
> +
> +     cmd_buf[0] = NOTETAKER_REPORT_ID;
> +     cmd_buf[1] = len;
> +     memcpy(cmd_buf + 2, data, len);
> +
> +     result = usb_control_msg(pegasus->usbdev,
> +                              usb_sndctrlpipe(pegasus->usbdev, 0),
> +                              USB_REQ_SET_REPORT,
> +                              USB_TYPE_VENDOR | USB_DIR_OUT,
> +                              0, 0, cmd_buf, sizeof_buf,
> +                              USB_CTRL_SET_TIMEOUT);
> +
> +     if (result != sizeof_buf)
> +             dev_err(&pegasus->usbdev->dev, "control msg error\n");
> +
> +     kfree(cmd_buf);
> +}
> +
> +static void pegasus_set_mode(struct pegasus *pegasus, u8 mode, u8 led)
> +{
> +     u8 cmd[] = {NOTETAKER_SET_CMD, NOTETAKER_SET_MODE, led, mode};
> +
> +     pegasus_control_msg(pegasus, cmd, sizeof(cmd));
> +}
> +
> +static void pegasus_parse_packet(struct pegasus *pegasus)
> +{
> +     unsigned char *data = pegasus->data;
> +     struct input_dev *dev = pegasus->dev;
> +     u16 x, y;
> +
> +     switch (data[0]) {
> +     case SPECIAL_COMMAND:
> +             /* device button pressed */
> +             if (data[1] == BUTTON_PRESSED)
> +                     schedule_work(&pegasus->init);
> +
> +             break;
> +     /* xy data */
> +     case BATTERY_LOW:
> +             dev_warn_once(&dev->dev, "Pen battery low\n");
> +     case BATTERY_NO_REPORT:
> +     case BATTERY_GOOD:
> +             x = le16_to_cpup((__le16 *)&data[2]);
> +             y = le16_to_cpup((__le16 *)&data[4]);
> +
> +             /* pen-up event */
> +             if (x == 0 && y == 0)
> +                     break;
> +
> +             input_report_key(dev, BTN_TOUCH, data[1] & PEN_TIP);
> +             input_report_key(dev, BTN_RIGHT, data[1] & PEN_BUTTON_PRESSED);
> +             input_report_key(dev, BTN_TOOL_PEN, 1);
> +             input_report_abs(dev, ABS_X, (s16)x);
> +             input_report_abs(dev, ABS_Y, y);
> +
> +             input_sync(dev);
> +             break;
> +     default:
> +             dev_warn_once(&pegasus->usbdev->dev,
> +                           "unknown answer from device\n");
> +     }
> +}
> +
> +static void pegasus_irq(struct urb *urb)
> +{
> +     struct pegasus *pegasus = urb->context;
> +     struct usb_device *dev = pegasus->usbdev;
> +     int retval;
> +
> +     switch (urb->status) {
> +     case 0:
> +             pegasus_parse_packet(pegasus);
> +             usb_mark_last_busy(pegasus->usbdev);
> +             break;
> +     case -ECONNRESET:
> +     case -ENOENT:
> +     case -ESHUTDOWN:
> +             dev_err(&dev->dev, "%s - urb shutting down with status: %d",
> +                     __func__, urb->status);
> +             return;
> +     default:
> +             dev_err(&dev->dev, "%s - nonzero urb status received: %d",
> +                     __func__, urb->status);
> +             break;
> +     }
> +
> +     retval = usb_submit_urb(urb, GFP_ATOMIC);
> +     if (retval)
> +             dev_err(&dev->dev, "%s - usb_submit_urb failed with result %d",
> +                     __func__, retval);
> +}
> +
> +static void pegasus_init(struct work_struct *work)
> +{
> +     struct pegasus *pegasus = container_of(work, struct pegasus, init);
> +
> +     pegasus_set_mode(pegasus, PEN_MODE_XY, NOTETAKER_LED_MOUSE);
> +}
> +
> +static int pegasus_open(struct input_dev *dev)
> +{
> +     struct pegasus *pegasus = input_get_drvdata(dev);
> +     int retval = 0;
> +
> +     retval = usb_autopm_get_interface(pegasus->intf);
> +     if (retval)
> +             return retval;
> +
> +     pegasus->irq->dev = pegasus->usbdev;
> +     if (usb_submit_urb(pegasus->irq, GFP_KERNEL))
> +             retval = -EIO;
> +
> +     usb_autopm_put_interface(pegasus->intf);
> +
> +     return retval;
> +}
> +
> +static void pegasus_close(struct input_dev *dev)
> +{
> +     struct pegasus *pegasus = input_get_drvdata(dev);
> +     int autopm_error;
> +
> +     autopm_error = usb_autopm_get_interface(pegasus->intf);
> +     usb_kill_urb(pegasus->irq);
> +     cancel_work_sync(&pegasus->init);
> +
> +     if (!autopm_error)
> +             usb_autopm_put_interface(pegasus->intf);
> +}
> +
> +static int pegasus_probe(struct usb_interface *intf,
> +                      const struct usb_device_id *id)
> +{
> +     struct usb_device *dev = interface_to_usbdev(intf);
> +     struct usb_endpoint_descriptor *endpoint;
> +     struct pegasus *pegasus;
> +     struct input_dev *input_dev;
> +     int error;
> +     int pipe, maxp;
> +
> +     /* We control interface 0 */
> +     if (intf->cur_altsetting->desc.bInterfaceNumber == 1)

Should it be >=1 ?

> +             return -ENODEV;
> +
> +     /* Sanity check that the device has an endpoint */
> +     if (intf->altsetting[0].desc.bNumEndpoints < 1) {
> +             dev_err(&intf->dev, "Invalid number of endpoints\n");
> +             return -EINVAL;
> +     }
> +     endpoint = &intf->cur_altsetting->endpoint[0].desc;
> +
> +     pegasus = kzalloc(sizeof(*pegasus), GFP_KERNEL);
> +     input_dev = input_allocate_device();
> +     if (!pegasus || !input_dev) {
> +             error = -ENOMEM;
> +             goto err_free_mem;
> +     }
> +
> +     pegasus->usbdev = dev;
> +     pegasus->dev = input_dev;
> +     pegasus->intf = intf;
> +
> +     pegasus->data = usb_alloc_coherent(dev, endpoint->wMaxPacketSize,
> +                                        GFP_KERNEL, &pegasus->data_dma);
> +     if (!pegasus->data) {
> +             error = -ENOMEM;
> +             goto err_free_mem;
> +     }
> +
> +     pegasus->irq = usb_alloc_urb(0, GFP_KERNEL);
> +     if (!pegasus->irq) {
> +             error = -ENOMEM;
> +             goto err_free_dma;
> +     }
> +
> +     pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
> +     maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
> +     pegasus->data_len = maxp;
> +
> +     usb_fill_int_urb(pegasus->irq, dev, pipe, pegasus->data, maxp,
> +                      pegasus_irq, pegasus, endpoint->bInterval);
> +
> +     pegasus->irq->transfer_dma = pegasus->data_dma;
> +     pegasus->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +
> +     if (dev->manufacturer)
> +             strlcpy(pegasus->name, dev->manufacturer,
> +                     sizeof(pegasus->name));
> +
> +     if (dev->product) {
> +             if (dev->manufacturer)
> +                     strlcat(pegasus->name, " ", sizeof(pegasus->name));
> +             strlcat(pegasus->name, dev->product, sizeof(pegasus->name));
> +     }
> +
> +     if (!strlen(pegasus->name))
> +             snprintf(pegasus->name, sizeof(pegasus->name),
> +                      "USB Pegasus Device %04x:%04x",
> +                      le16_to_cpu(dev->descriptor.idVendor),
> +                      le16_to_cpu(dev->descriptor.idProduct));
> +
> +     usb_make_path(dev, pegasus->phys, sizeof(pegasus->phys));
> +     strlcat(pegasus->phys, "/input0", sizeof(pegasus->phys));
> +
> +     INIT_WORK(&pegasus->init, pegasus_init);
> +
> +     usb_set_intfdata(intf, pegasus);
> +
> +     input_dev->name = pegasus->name;
> +     input_dev->phys = pegasus->phys;
> +     usb_to_input_id(dev, &input_dev->id);
> +     input_dev->dev.parent = &intf->dev;
> +
> +     input_set_drvdata(input_dev, pegasus);
> +
> +     input_dev->open = pegasus_open;
> +     input_dev->close = pegasus_close;
> +
> +     __set_bit(EV_ABS, input_dev->evbit);
> +     __set_bit(EV_KEY, input_dev->evbit);
> +
> +     __set_bit(ABS_X, input_dev->absbit);
> +     __set_bit(ABS_Y, input_dev->absbit);
> +
> +     __set_bit(BTN_TOUCH, input_dev->keybit);
> +     __set_bit(BTN_RIGHT, input_dev->keybit);
> +     __set_bit(BTN_TOOL_PEN, input_dev->keybit);
> +
> +     __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
> +     __set_bit(INPUT_PROP_POINTER, input_dev->propbit);
> +
> +     input_set_abs_params(input_dev, ABS_X, -1500, 1500, 8, 0);
> +     input_set_abs_params(input_dev, ABS_Y, 1600, 3000, 8, 0);
> +
> +     pegasus_set_mode(pegasus, PEN_MODE_XY, NOTETAKER_LED_MOUSE);

I wonder if we should move it to pegasus_open(), there is no need to do
that until there are users of the device.

> +
> +     error = input_register_device(pegasus->dev);
> +     if (error)
> +             goto err_free_urb;
> +
> +     return 0;
> +
> +err_free_urb:
> +     usb_free_urb(pegasus->irq);
> +err_free_dma:
> +     usb_free_coherent(dev, pegasus->data_len,
> +                       pegasus->data, pegasus->data_dma);
> +err_free_mem:
> +     input_free_device(input_dev);
> +     kfree(pegasus);
> +     usb_set_intfdata(intf, NULL);
> +
> +     return error;
> +}
> +
> +static void pegasus_disconnect(struct usb_interface *intf)
> +{
> +     struct pegasus *pegasus = usb_get_intfdata(intf);
> +
> +     input_unregister_device(pegasus->dev);
> +
> +     usb_free_urb(pegasus->irq);
> +     usb_free_coherent(interface_to_usbdev(intf),
> +                       pegasus->data_len, pegasus->data,
> +                       pegasus->data_dma);
> +     kfree(pegasus);
> +     usb_set_intfdata(intf, NULL);
> +}
> +
> +static int pegasus_suspend(struct usb_interface *intf, pm_message_t message)
> +{
> +     struct pegasus *pegasus = usb_get_intfdata(intf);
> +
> +     mutex_lock(&pegasus->dev->mutex);
> +     usb_kill_urb(pegasus->irq);
> +     mutex_unlock(&pegasus->dev->mutex);
> +
> +     return 0;
> +}
> +
> +static int pegasus_resume(struct usb_interface *intf)
> +{
> +     struct pegasus *pegasus = usb_get_intfdata(intf);
> +     int retval = 0;
> +
> +     mutex_lock(&pegasus->dev->mutex);
> +     if (pegasus->dev->users && usb_submit_urb(pegasus->irq, GFP_NOIO) < 0)
> +             retval = -EIO;
> +     mutex_unlock(&pegasus->dev->mutex);
> +
> +     return retval;
> +}
> +
> +static int pegasus_reset_resume(struct usb_interface *intf)
> +{
> +     return pegasus_resume(intf);
> +}
> +
> +static const struct usb_device_id pegasus_ids[] = {
> +     { USB_DEVICE(USB_VENDOR_ID_PEGASUSTECH,
> +                  USB_DEVICE_ID_PEGASUS_NOTETAKER_EN100) },
> +     { }
> +};
> +MODULE_DEVICE_TABLE(usb, pegasus_ids);
> +
> +static struct usb_driver pegasus_driver = {
> +     .name           = "pegasus_notetaker",
> +     .probe          = pegasus_probe,
> +     .disconnect     = pegasus_disconnect,
> +     .suspend        = pegasus_suspend,
> +     .resume         = pegasus_resume,
> +     .reset_resume   = pegasus_reset_resume,
> +     .id_table       = pegasus_ids,
> +     .supports_autosuspend = 1,
> +};
> +
> +module_usb_driver(pegasus_driver);
> +
> +MODULE_AUTHOR("Martin Kepplinger <mart...@posteo.de>");
> +MODULE_DESCRIPTION("Pegasus Mobile Notetaker Pen tablet driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.1.4
> 

Thanks.

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to