2010/9/29 Greg KH <gregkh@xxxxxxx>: > On Wed, Sep 29, 2010 at 12:16:50PM +0900, Tomoki Sekiyama wrote: >> This is the driver code for YUREX (leg-shakes sensosr) I showed you >> at the party after LinuxCon Japan. > > Heh, thanks for posting the patch, I like it a lot. Thanks for your review of the patch! >> Meywa-Denki/Kayac YUREX is a leg-shakes sensor device. >> See http://bbu.kayac.com/en/about/ for further information. >> This driver support read/write the leg-shakes counter in the device >> via a device file /dev/yurex[0-9]*. > > Is there any reason this has to be a character device? It is a character device to use fasync operation to enable a userland daemon to know the counter update without polling. But yes, it might be over-spec. > The way you use > the device seems to be just like a sysfs file. Could it be done as a > sysfs instead? If so, it would reduce the ammount of code here. > > Are there userspace tools that are expecting the /dev/ node, or is it > just used as you showed me at the meeting by running 'cat' and 'echo' on > the device node? I've tried the sysfs version, attached below (tested with 2.6.36-rc5). We can do the same thing I've showed at the party with echo or cat, like below; $ cat /sys/bus/usb/drivers/yurex/2-2.1:1.0/bbu 5827 ("bbu" is the unit of legs-shake displayed on the device.) I hope you like this version more. :) Thanks, ~ Meywa-Denki/Kayac YUREX is a leg-shakes sensor device. See http://bbu.kayac.com/en/about/ for further information. This driver support read/write of the leg-shakes counter(BBU) and LED status, and LCD animation control via sysfs attributes. Signed-off-by: Tomoki Sekiyama <tomoki.sekiyama@xxxxxxxxx> --- drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 + drivers/usb/misc/Kconfig | 13 ++ drivers/usb/misc/Makefile | 1 + drivers/usb/misc/yurex.c | 479 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 497 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/misc/yurex.c diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 3f72924..ea0b9b6 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1661,6 +1661,7 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1006) }, { HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1007) }, { HID_USB_DEVICE(USB_VENDOR_ID_IMATION, USB_DEVICE_ID_DISC_STAKKA) }, + { HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_JESS_YUREX) }, { HID_USB_DEVICE(USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO) }, { HID_USB_DEVICE(USB_VENDOR_ID_KWORLD, USB_DEVICE_ID_KWORLD_RADIO_FM700) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_GPEN_560) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 765a4f5..c1a07c1 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -303,6 +303,9 @@ #define USB_VENDOR_ID_IMATION 0x0718 #define USB_DEVICE_ID_DISC_STAKKA 0xd000 +#define USB_VENDOR_ID_JESS 0x0c45 +#define USB_DEVICE_ID_JESS_YUREX 0x1010 + #define USB_VENDOR_ID_KBGEAR 0x084e #define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001 diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 55660ea..f9bca24 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -231,3 +231,16 @@ config USB_ISIGHTFW driver beforehand. Tools for doing so are available at http://bersace03.free.fr +config USB_YUREX + tristate "USB YUREX driver support" + depends on USB + help + Say Y here if you want to connect a YUREX to your computer's + USB port. The YUREX is a leg-shakes sensor. See + <http://bbu.kayac.com/en/> for further information. + This driver supports read/write of leg-shakes counter (BBU), + LED status, and LCD animation control via sysfs. + + To compile this driver as a module, choose M here: the + module will be called yurex. + diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 717703e..d203ff6 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_USB_TEST) += usbtest.o obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o obj-$(CONFIG_USB_USS720) += uss720.o obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o +obj-$(CONFIG_USB_YUREX) += yurex.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c new file mode 100644 index 0000000..7248381 --- /dev/null +++ b/drivers/usb/misc/yurex.c @@ -0,0 +1,479 @@ +/* + * Driver for Meywa-Denki & KAYAC YUREX + * + * Copyright (C) 2010 Tomoki Sekiyama (tomoki.sekiyama@xxxxxxxxx) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/mutex.h> +#include <asm/uaccess.h> +#include <linux/usb.h> +#include <linux/hid.h> + +#define DRIVER_AUTHOR "Tomoki Sekiyama" +#define DRIVER_DESC "Driver for Meywa-Denki & KAYAC YUREX" + +#define YUREX_VENDOR_ID 0x0c45 +#define YUREX_PRODUCT_ID 0x1010 + +#define CMD_ACK '!' +#define CMD_ANIMATE 'A' +#define CMD_COUNT 'C' +#define CMD_LED 'L' +#define CMD_READ 'R' +#define CMD_SET 'S' +#define CMD_VERSION 'V' +#define CMD_EOF 0x0d +#define CMD_PADDING 0xff + +#define YUREX_BUF_SIZE 8 +#define YUREX_WRITE_TIMEOUT (HZ) + +/* table of devices that work with this driver */ +static struct usb_device_id yurex_table [] = { + { USB_DEVICE(YUREX_VENDOR_ID, YUREX_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, yurex_table); + +/* Structure to hold all of our device specific stuff */ +struct usb_yurex { + struct usb_device *udev; + struct usb_interface *interface; + __u8 int_in_endpointAddr; + struct urb *urb; /* URB for interrupt in */ + unsigned char *int_buffer; /* buffer for intterupt in */ + struct urb *cntl_urb; /* URB for control msg */ + struct usb_ctrlrequest *cntl_req; /* req for control msg */ + unsigned char *cntl_buffer; /* buffer for control msg */ + + struct kref kref; + struct mutex io_mutex; + wait_queue_head_t waitq; + + spinlock_t lock; + __s64 bbu; /* BBU(leg-shake count) val */ + int led; /* LED status */ +}; +#define to_yurex_dev(d) container_of(d, struct usb_yurex, kref) + +static struct usb_driver yurex_driver; + + +static void yurex_control_callback(struct urb *urb) +{ + struct usb_yurex *dev = urb->context; + int status = urb->status; + + if (status) { + err("%s - control failed: %d\n", __func__, status); + wake_up_interruptible(&dev->waitq); + return; + } + /* on success, sender woken up by CMD_ACK int in, or timeout */ +} + +static void yurex_delete(struct kref *kref) +{ + struct usb_yurex *dev = to_yurex_dev(kref); + + dbg("yurex_delete"); + + usb_put_dev(dev->udev); + if (dev->urb) { + usb_kill_urb(dev->urb); + if (dev->int_buffer) + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + dev->int_buffer, dev->urb->transfer_dma); + usb_free_urb(dev->urb); + } + kfree(dev); +} + +/* send a control command to the device */ +static int yurex_command(struct usb_yurex *dev, char cmd, + const char *arg, size_t size) +{ + int i, retval = 0; + unsigned long long c; + signed long timeout = 0; + DEFINE_WAIT(wait); + + kref_get(&dev->kref); + mutex_lock(&dev->io_mutex); + if (!dev->interface) { /* alreaday disconnected */ + mutex_unlock(&dev->io_mutex); + kref_put(&dev->kref, yurex_delete); + return -ENODEV; + } + + memset(dev->cntl_buffer, CMD_PADDING, YUREX_BUF_SIZE); + + switch (cmd) { + case CMD_ANIMATE: /* start animation on LCD display */ + dev->cntl_buffer[0] = cmd; + dev->cntl_buffer[1] = 0x01; + dev->cntl_buffer[2] = CMD_EOF; + break; + case CMD_LED: /* set LED status */ + dev->led = simple_strtoul(arg, NULL, 0) ? 1 : 0; + dev->cntl_buffer[0] = cmd; + dev->cntl_buffer[1] = dev->led + '0'; + dev->cntl_buffer[2] = CMD_EOF; + break; + case CMD_SET: /* set BBU (leg-shakes counter) */ + c = dev->bbu = simple_strtoull(arg, NULL, 0); + dev->cntl_buffer[0] = CMD_SET; + for (i = 1; i < 6; i++) { + dev->cntl_buffer[i] = (c>>32) & 0xff; + c <<= 8; + } + dev->cntl_buffer[6] = CMD_EOF; + break; + case CMD_READ: /* read request of BBU */ + case CMD_VERSION: /* read request of device version */ + dev->cntl_buffer[0] = cmd; + dev->cntl_buffer[1] = 0x00; + dev->cntl_buffer[2] = CMD_EOF; + break; + default: + mutex_unlock(&dev->io_mutex); + return -EINVAL; + } + + /* send the data as the control msg */ + prepare_to_wait(&dev->waitq, &wait, TASK_INTERRUPTIBLE); + dbg("%s - submit %c", __func__, dev->cntl_buffer[0]); + retval = usb_submit_urb(dev->cntl_urb, GFP_KERNEL); + if (retval >= 0) + timeout = schedule_timeout(YUREX_WRITE_TIMEOUT); + finish_wait(&dev->waitq, &wait); + + mutex_unlock(&dev->io_mutex); + kref_put(&dev->kref, yurex_delete); + + if (retval < 0) { + err("%s - failed to send bulk msg, error %d", __func__, retval); + return retval; + } + return timeout ? 0 : -EIO; +} + +/* sysfs attributes */ + +static ssize_t yurex_attr_bbu_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + unsigned long flags; + struct usb_interface *intf = to_usb_interface(dev); + struct usb_yurex *y = usb_get_intfdata(intf); + + spin_lock_irqsave(&y->lock, flags); + ret = sprintf(buf, "%lld\n", y->bbu); + spin_unlock_irqrestore(&y->lock, flags); + return ret; +} + +static ssize_t yurex_attr_bbu_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_yurex *y = usb_get_intfdata(intf); + + yurex_command(y, CMD_SET, buf, count); + return count; +} + +static ssize_t yurex_attr_led_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_yurex *y = usb_get_intfdata(intf); + + return sprintf(buf, "%d\n", y->led); +} + +static ssize_t yurex_attr_led_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_yurex *y = usb_get_intfdata(intf); + + yurex_command(y, CMD_LED, buf, count); + return count; +} + +static ssize_t yurex_attr_animate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_yurex *y = usb_get_intfdata(intf); + + yurex_command(y, CMD_ANIMATE, buf, count); + return count; +} + +static DEVICE_ATTR(bbu, S_IWUGO | S_IRUGO, yurex_attr_bbu_show, yurex_attr_bbu_set); +static DEVICE_ATTR(led, S_IWUGO | S_IRUGO, yurex_attr_led_show, yurex_attr_led_set); +static DEVICE_ATTR(animate, S_IWUGO, NULL, yurex_attr_animate); + +static struct attribute *yurex_attrs[] = { + &dev_attr_bbu.attr, + &dev_attr_led.attr, + &dev_attr_animate.attr, + NULL +}; + +static struct attribute_group yurex_attr_grp = { + .attrs = yurex_attrs, +}; + + +static void yurex_interrupt(struct urb *urb) +{ + struct usb_yurex *dev = urb->context; + unsigned char *buf = dev->int_buffer; + int status = urb->status; + unsigned long flags; + int retval, i; + + switch (status) { + case 0: /*success*/ + break; + case -EOVERFLOW: + err("%s - overflow with length %d, actual length is %d", + __func__, YUREX_BUF_SIZE, dev->urb->actual_length); + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EILSEQ: + /* The device is terminated, clean up */ + return; + default: + err("%s - unknown status received: %d", __func__, status); + goto exit; + } + + /* handle received message */ + switch (buf[0]) { + case CMD_COUNT: + case CMD_READ: + if (buf[6] == CMD_EOF) { + spin_lock_irqsave(&dev->lock, flags); + dev->bbu = 0; + for (i = 1; i < 6; i++) { + dev->bbu += buf[i]; + if (i != 5) + dev->bbu <<= 8; + } + dbg("%s count: %lld", __func__, dev->bbu); + spin_unlock_irqrestore(&dev->lock, flags); + } + else + dbg("data format error - no EOF"); + break; + case CMD_ACK: + dbg("%s ack: %c", __func__, buf[1]); + wake_up_interruptible(&dev->waitq); + break; + } + +exit: + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) { + err("%s - usb_submit_urb failed: %d", + __func__, retval); + } +} + +static int yurex_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_yurex *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int retval = -ENOMEM; + int i; + DEFINE_WAIT(wait); + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + err("Out of memory"); + goto error; + } + kref_init(&dev->kref); + mutex_init(&dev->io_mutex); + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->waitq); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* set up the endpoint information */ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) { + dev->int_in_endpointAddr = endpoint->bEndpointAddress; + break; + } + } + if (!dev->int_in_endpointAddr) { + retval = -ENODEV; + err("Could not find endpoints"); + goto error; + } + + + /* allocate control URB */ + dev->cntl_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->cntl_urb) { + err("Could not allocate control URB"); + goto error; + } + + /* allocate buffer for control req */ + dev->cntl_req = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE, + GFP_KERNEL, + &dev->cntl_urb->setup_dma); + if (!dev->cntl_req) { + err("Could not allocate cntl_req"); + goto error; + } + + /* allocate buffer for control msg */ + dev->cntl_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE, + GFP_KERNEL, + &dev->cntl_urb->transfer_dma); + if (!dev->cntl_buffer) { + err("Could not allocate cntl_buffer"); + goto error; + } + + /* configure control URB */ + dev->cntl_req->bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE; + dev->cntl_req->bRequest = HID_REQ_SET_REPORT; + dev->cntl_req->wValue = cpu_to_le16((HID_OUTPUT_REPORT + 1) << 8); + dev->cntl_req->wIndex = cpu_to_le16(iface_desc->desc.bInterfaceNumber); + dev->cntl_req->wLength = cpu_to_le16(YUREX_BUF_SIZE); + + usb_fill_control_urb(dev->cntl_urb, dev->udev, + usb_sndctrlpipe(dev->udev, 0), + (void*)dev->cntl_req, dev->cntl_buffer, + YUREX_BUF_SIZE, yurex_control_callback, dev); + dev->cntl_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + + /* allocate interrupt URB */ + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb) { + err("Could not allocate URB"); + goto error; + } + + /* allocate buffer for interrupt in */ + dev->int_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE, + GFP_KERNEL, &dev->urb->transfer_dma); + if (!dev->int_buffer) { + err("Could not allocate int_buffer"); + goto error; + } + + /* configure interrupt URB */ + usb_fill_int_urb(dev->urb, dev->udev, + usb_rcvintpipe(dev->udev, dev->int_in_endpointAddr), + dev->int_buffer, YUREX_BUF_SIZE, yurex_interrupt, + dev, 1); + if (usb_submit_urb(dev->urb, GFP_KERNEL)) { + retval = -EIO; + err("Could not submitting URB"); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + dev->bbu = -1; + dev->led = 0; + + retval = sysfs_create_group(&interface->dev.kobj, &yurex_attr_grp); + if (retval) + goto error; + + dev_info(&interface->dev, "USB Yurex device attached\n"); + + return 0; + +error: + if (dev) + /* this frees allocated memory */ + kref_put(&dev->kref, yurex_delete); + return retval; +} + +static void yurex_disconnect(struct usb_interface *interface) +{ + struct usb_yurex *dev; + dev = usb_get_intfdata(interface); + sysfs_remove_group(&interface->dev.kobj, &yurex_attr_grp); + usb_set_intfdata(interface, NULL); + + /* prevent more I/O from starting */ + mutex_lock(&dev->io_mutex); + dev->interface = NULL; + mutex_unlock(&dev->io_mutex); + + /* wakeup waiters */ + wake_up_interruptible(&dev->waitq); + + /* decrement our usage count */ + kref_put(&dev->kref, yurex_delete); + + dev_info(&interface->dev, "USB Yurex device disconnected"); +} + +static struct usb_driver yurex_driver = { + .name = "yurex", + .probe = yurex_probe, + .disconnect = yurex_disconnect, + .id_table = yurex_table, +}; + +static int __init usb_yurex_init(void) +{ + int result; + + /* register this driver with the USB subsystem */ + result = usb_register(&yurex_driver); + if (result) + err("usb_register failed. Error number %d", result); + + return result; +} + +static void __exit usb_yurex_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&yurex_driver); +} + +module_init(usb_yurex_init); +module_exit(usb_yurex_exit); + +MODULE_LICENSE("GPL"); -- 1.6.2.5 -- 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