Re: [PATCH v4] input: tablet: add Pegasus Notetaker tablet driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Martin,

On Wed, May 25, 2016 at 09:44:34AM +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.staedtler.com/en/products/ink-writing-instruments/ballpoint-pens/digital-pen-990-01-digital-ballpoint-pen
> 
> http://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.
> 
> There's no way to disable the device. When the pen is out of range, we just
> don't get any URBs and don't do anything.
> Like all other mouses or input tablets, we don't use runtime PM.
> 
> Signed-off-by: Martin Kepplinger <martink@xxxxxxxxx>
> ---
> 
> Thanks for having a look. Any more suggestions on this?
> 
> revision history
> ================
> 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 | 373 +++++++++++++++++++++++++++++++
>  3 files changed, 389 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..b7bf585
> --- /dev/null
> +++ b/drivers/input/tablet/pegasus_notetaker.c
> @@ -0,0 +1,373 @@
> +/*
> + * Pegasus Mobile Notetaker Pen input tablet driver
> + *
> + * Copyright (c) 2016 Martin Kepplinger <martink@xxxxxxxxx>
> + */
> +
> +/*
> + * 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 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 * sizeof(u8) + 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)
> +{
> +	static u8 cmd[4] = {NOTETAKER_SET_CMD, NOTETAKER_SET_MODE, 0x00, 0x00};

"static" is bad for drivers, what if you connect 2 devices? Do:

	u8 cmd[] = { NOTETAKER_SET_CMD, NOTETAKER_SET_MODE, led, mode };

instead.

> +
> +	cmd[2] = led;
> +	cmd[3] = 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 = 0;

Why do you initialize y but not x (none is actually needed).

> +
> +	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:
> +		memcpy(&x, data + 2, 2);
> +		memcpy(&y, data + 4, 2);

Why not

		x = le16_to_cpup(data + 2);
		y = le16_to_cpup(data + 4);

> +
> +		/* ignore pen up events */
> +		if (x == 0 && y == 0)
> +			break;
> +
> +		if (data[1] & PEN_BUTTON_PRESSED) {
> +			input_report_key(dev, BTN_TOUCH, 0);
> +			input_report_key(dev, BTN_RIGHT, 1);
> +		} else if (data[1] & PEN_TIP) {
> +			input_report_key(dev, BTN_TOUCH, 1);
> +			input_report_key(dev, BTN_RIGHT, 0);
> +		} else {
> +			input_report_key(dev, BTN_TOUCH, 0);
> +			input_report_key(dev, BTN_RIGHT, 0);
> +		}

Can we say:

		input_report_key(dev, BTN_TOUCH, data[1] & PEN_TIP);
		input_report_key(dev, BTN_RIGHT, data[1] & PEN_BUTTON_PRESSED);

instead?

> +
> +		input_report_key(dev, BTN_TOOL_PEN, 1);
> +		input_report_abs(dev, ABS_X, (s16)le16_to_cpu(x));
> +		input_report_abs(dev, ABS_Y, le16_to_cpu(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);
> +
> +	pegasus->irq->dev = pegasus->usbdev;

Is this assignment really needed?

> +	if (usb_submit_urb(pegasus->irq, GFP_KERNEL))
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static void pegasus_close(struct input_dev *dev)
> +{
> +	struct pegasus *pegasus = input_get_drvdata(dev);
> +
> +	cancel_work_sync(&pegasus->init);
> +
> +	usb_kill_urb(pegasus->irq);

First kill, then cancel, as Oliver mentioned.

> +}
> +
> +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)
> +		return -ENODEV;
> +
> +	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->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_mem;

No, you need to jump to err_free_dma here.

> +	}
> +
> +	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);
> +
> +	error = input_register_device(pegasus->dev);
> +	if (error)
> +		goto err_free_dma;

Should be "goto err_free_urb;" which should happen before err_free_dma.

> +
> +	return 0;
> +
> +err_free_dma:
> +	usb_free_coherent(dev, pegasus->data_len,
> +			  pegasus->data, pegasus->data_dma);
> +
> +	usb_free_urb(pegasus->irq);
> +err_free_mem:
> +	kfree(pegasus);

You are leaking input device here.

> +	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 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,
> +	.id_table	= pegasus_ids,
> +};
> +
> +module_usb_driver(pegasus_driver);
> +
> +MODULE_AUTHOR("Martin Kepplinger <martink@xxxxxxxxx>");
> +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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux