From 14bf62cde79423a02a590e02664ed29a36facec1 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Thu, 18 Mar 2010 16:19:43 +0100 Subject: HID: add driver for Roccat Kone gaming mouse This Patch adds support for Kone gaming mouse from Roccat. It provides access to profiles, settings, firmware, weight, actual settings etc. through sysfs attributes. Event handling of this mouse differs from standard hid behaviour in that tilt button press is reported in each move event which results in strange behaviour if not handled by the driver. This is a heavily reworked version of the previously introduced driver. The changes include most of the previously raised concerns, memory leak and other fixes, code cleanups, adoption of additional achieved knowlege about the hardware and is (IMHO) a much better version than before even when I exchanged reduced USB-IO with a bigger memory consumption. I refused to implement one mentioned point: Removing the 'just-because-we-can' attributes. Motivation: Reading the clipped in weight: I'm no gamer and can't determine the usefulness of this feature but if the manufacturer implements such a feature it might make sense to someone and I would unwillingly limit the functionality besides its such a small feature. Reading the actual profile and dpi settings: Here I can testify that one can get lost of the actual settings when switching back and forth. The manufacturers windows driver has the ability for on-screen-display of the values and there is a mouse in the market that has an lcd on the underside of it to show these values. So I think this feature makes sense not only for me and shouldn't be removed. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina diff --git a/Documentation/ABI/testing/sysfs-driver-hid-roccat-kone b/Documentation/ABI/testing/sysfs-driver-hid-roccat-kone new file mode 100644 index 0000000..88340a2 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-roccat-kone @@ -0,0 +1,111 @@ +What: /sys/bus/usb/devices/-:./actual_dpi +Date: March 2010 +Contact: Stefan Achatz +Description: It is possible to switch the dpi setting of the mouse with the + press of a button. + When read, this file returns the raw number of the actual dpi + setting reported by the mouse. This number has to be further + processed to receive the real dpi value. + + VALUE DPI + 1 800 + 2 1200 + 3 1600 + 4 2000 + 5 2400 + 6 3200 + + This file is readonly. + +What: /sys/bus/usb/devices/-:./actual_profile +Date: March 2010 +Contact: Stefan Achatz +Description: When read, this file returns the number of the actual profile. + This file is readonly. + +What: /sys/bus/usb/devices/-:./firmware_version +Date: March 2010 +Contact: Stefan Achatz +Description: When read, this file returns the raw integer version number of the + firmware reported by the mouse. Using the integer value eases + further usage in other programs. To receive the real version + number the decimal point has to be shifted 2 positions to the + left. E.g. a returned value of 138 means 1.38 + This file is readonly. + +What: /sys/bus/usb/devices/-:./kone_driver_version +Date: March 2010 +Contact: Stefan Achatz +Description: When read, this file returns the driver version. + The format of the string is "v..". + This attribute is used by the userland tools to find the sysfs- + paths of installed kone-mice and determine the capabilites of + the driver. Versions of this driver for old kernels replace + usbhid instead of generic-usb. The way to scan for this file + has been chosen to provide a consistent way for all supported + kernel versions. + This file is readonly. + +What: /sys/bus/usb/devices/-:./profile[1-5] +Date: March 2010 +Contact: Stefan Achatz +Description: The mouse can store 5 profiles which can be switched by the + press of a button. A profile holds informations like button + mappings, sensitivity, the colors of the 5 leds and light + effects. + When read, these files return the respective profile. The + returned data is 975 bytes in size. + When written, this file lets one write the respective profile + data back to the mouse. The data has to be 975 bytes long. + The mouse will reject invalid data, whereas the profile number + stored in the profile doesn't need to fit the number of the + store. + +What: /sys/bus/usb/devices/-:./settings +Date: March 2010 +Contact: Stefan Achatz +Description: When read, this file returns the settings stored in the mouse. + The size of the data is 36 bytes and holds information like the + startup_profile, tcu state and calibration_data. + When written, this file lets write settings back to the mouse. + The data has to be 36 bytes long. The mouse will reject invalid + data. + +What: /sys/bus/usb/devices/-:./startup_profile +Date: March 2010 +Contact: Stefan Achatz +Description: The integer value of this attribute ranges from 1 to 5. + When read, this attribute returns the number of the profile + that's active when the mouse is powered on. + When written, this file sets the number of the startup profile + and the mouse activates this profile immediately. + +What: /sys/bus/usb/devices/-:./tcu +Date: March 2010 +Contact: Stefan Achatz +Description: The mouse has a "Tracking Control Unit" which lets the user + calibrate the laser power to fit the mousepad surface. + When read, this file returns the current state of the TCU, + where 0 means off and 1 means on. + Writing 0 in this file will switch the TCU off. + Writing 1 in this file will start the calibration which takes + around 6 seconds to complete and activates the TCU. + +What: /sys/bus/usb/devices/-:./weight +Date: March 2010 +Contact: Stefan Achatz +Description: The mouse can be equipped with one of four supplied weights + ranging from 5 to 20 grams which are recognized by the mouse + and its value can be read out. When read, this file returns the + raw value returned by the mouse which eases further processing + in other software. + The values map to the weights as follows: + + VALUE WEIGHT + 0 none + 1 5g + 2 10g + 3 15g + 4 20g + + This file is readonly. diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 71d4c07..d819b02 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -268,6 +268,13 @@ config HID_QUANTA ---help--- Support for Quanta Optical Touch dual-touch panels. +config HID_ROCCAT_KONE + tristate "Roccat Kone" if EMBEDDED + depends on USB_HID + default !EMBEDDED + ---help--- + Support for Roccat Kone mouse. + config HID_SAMSUNG tristate "Samsung" if EMBEDDED depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0b2618f..08b83cc 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_HID_ORTEK) += hid-ortek.o obj-$(CONFIG_HID_QUANTA) += hid-quanta.o obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o +obj-$(CONFIG_HID_ROCCAT_KONE) += hid-roccat-kone.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 2e2aa75..5c5a821 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1346,6 +1346,7 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 797e064..014acfc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -399,6 +399,9 @@ #define USB_VENDOR_ID_PRODIGE 0x05af #define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062 +#define USB_VENDOR_ID_ROCCAT 0x1e7d +#define USB_DEVICE_ID_ROCCAT_KONE 0x2ced + #define USB_VENDOR_ID_SAITEK 0x06a3 #define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c new file mode 100644 index 0000000..2b1412e --- /dev/null +++ b/drivers/hid/hid-roccat-kone.c @@ -0,0 +1,1006 @@ +/* + * Roccat Kone driver for Linux + * + * Copyright (c) 2010 Stefan Achatz + */ + +/* + * 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; either version 2 of the License, or (at your option) + * any later version. + */ + +/* + * Roccat Kone is a gamer mouse which consists of a mouse part and a keyboard + * part. The keyboard part enables the mouse to execute stored macros with mixed + * key- and button-events. + * + * TODO implement on-the-fly polling-rate change + * The windows driver has the ability to change the polling rate of the + * device on the press of a mousebutton. + * Is it possible to remove and reinstall the urb in raw-event- or any + * other handler, or to defer this action to be executed somewhere else? + * + * TODO implement notification mechanism for overlong macro execution + * If user wants to execute an overlong macro only the names of macroset + * and macro are given. Should userland tap hidraw or is there an + * additional streaming mechanism? + * + * TODO is it possible to overwrite group for sysfs attributes via udev? + */ + +#include +#include +#include +#include +#include +#include "hid-ids.h" +#include "hid-roccat-kone.h" + +static void kone_set_settings_checksum(struct kone_settings *settings) +{ + uint16_t checksum = 0; + unsigned char *address = (unsigned char *)settings; + int i; + + for (i = 0; i < sizeof(struct kone_settings) - 2; ++i, ++address) + checksum += *address; + settings->checksum = cpu_to_le16(checksum); +} + +/* + * Checks success after writing data to mouse + * On success returns 0 + * On failure returns errno + */ +static int kone_check_write(struct usb_device *usb_dev) +{ + int len; + unsigned char *data; + + data = kmalloc(1, GFP_KERNEL); + if (!data) + return -ENOMEM; + + do { + /* + * Mouse needs 50 msecs until it says ok, but there are + * 30 more msecs needed for next write to work. + */ + msleep(80); + + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_IN, + kone_command_confirm_write, 0, data, 1, + USB_CTRL_SET_TIMEOUT); + + if (len != 1) { + kfree(data); + return -EIO; + } + + /* + * value of 3 seems to mean something like + * "not finished yet, but it looks good" + * So check again after a moment. + */ + } while (*data == 3); + + if (*data == 1) { /* everything alright */ + kfree(data); + return 0; + } else { /* unknown answer */ + dev_err(&usb_dev->dev, "got retval %d when checking write\n", + *data); + kfree(data); + return -EIO; + } +} + +/* + * Reads settings from mouse and stores it in @buf + * @buf has to be alloced with GFP_KERNEL + * On success returns 0 + * On failure returns errno + */ +static int kone_get_settings(struct usb_device *usb_dev, + struct kone_settings *buf) +{ + int len; + + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + kone_command_settings, 0, buf, + sizeof(struct kone_settings), USB_CTRL_SET_TIMEOUT); + + if (len != sizeof(struct kone_settings)) + return -EIO; + + return 0; +} + +/* + * Writes settings from @buf to mouse + * On success returns 0 + * On failure returns errno + */ +static int kone_set_settings(struct usb_device *usb_dev, + struct kone_settings const *settings) +{ + int len; + + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + kone_command_settings, 0, (char *)settings, + sizeof(struct kone_settings), + USB_CTRL_SET_TIMEOUT); + + if (len != sizeof(struct kone_settings)) + return -EIO; + + if (kone_check_write(usb_dev)) + return -EIO; + + return 0; +} + +/* + * Reads profile data from mouse and stores it in @buf + * @number: profile number to read + * On success returns 0 + * On failure returns errno + */ +static int kone_get_profile(struct usb_device *usb_dev, + struct kone_profile *buf, int number) +{ + int len; + + if (number < 1 || number > 5) + return -EINVAL; + + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + kone_command_profile, number, buf, + sizeof(struct kone_profile), USB_CTRL_SET_TIMEOUT); + + if (len != sizeof(struct kone_profile)) + return -EIO; + + return 0; +} + +/* + * Writes profile data to mouse. + * @number: profile number to write + * On success returns 0 + * On failure returns errno + */ +static int kone_set_profile(struct usb_device *usb_dev, + struct kone_profile const *profile, int number) +{ + int len; + + if (number < 1 || number > 5) + return -EINVAL; + + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + kone_command_profile, number, (char *)profile, + sizeof(struct kone_profile), + USB_CTRL_SET_TIMEOUT); + + if (len != sizeof(struct kone_profile)) + return len; + + if (kone_check_write(usb_dev)) + return -EIO; + + return 0; +} + +/* + * Reads value of "fast-clip-weight" and stores it in @result + * On success returns 0 + * On failure returns errno + */ +static int kone_get_weight(struct usb_device *usb_dev, int *result) +{ + int len; + uint8_t *data; + + data = kmalloc(1, GFP_KERNEL); + if (!data) + return -ENOMEM; + + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + kone_command_weight, 0, data, 1, USB_CTRL_SET_TIMEOUT); + + if (len != 1) { + kfree(data); + return -EIO; + } + *result = (int)*data; + kfree(data); + return 0; +} + +/* + * Reads firmware_version of mouse and stores it in @result + * On success returns 0 + * On failure returns errno + */ +static int kone_get_firmware_version(struct usb_device *usb_dev, int *result) +{ + int len; + unsigned char *data; + + data = kmalloc(2, GFP_KERNEL); + if (!data) + return -ENOMEM; + + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + kone_command_firmware_version, 0, data, 2, + USB_CTRL_SET_TIMEOUT); + + if (len != 2) { + kfree(data); + return -EIO; + } + *result = le16_to_cpu(*data); + kfree(data); + return 0; +} + +static ssize_t kone_sysfs_read_settings(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + struct device *dev = container_of(kobj, struct device, kobj); + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct kone_settings)) + return 0; + + if (off + count > sizeof(struct kone_settings)) + count = sizeof(struct kone_settings) - off; + + mutex_lock(&kone->kone_lock); + memcpy(buf, &kone->settings + off, count); + mutex_unlock(&kone->kone_lock); + + return count; +} + +/* + * Writing settings automatically activates startup_profile. + * This function keeps values in kone_device up to date and assumes that in + * case of error the old data is still valid + */ +static ssize_t kone_sysfs_write_settings(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + struct device *dev = container_of(kobj, struct device, kobj); + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0, difference; + + /* I need to get my data in one piece */ + if (off != 0 || count != sizeof(struct kone_settings)) + return -EINVAL; + + mutex_lock(&kone->kone_lock); + difference = memcmp(buf, &kone->settings, sizeof(struct kone_settings)); + if (difference) { + retval = kone_set_settings(usb_dev, + (struct kone_settings const *)buf); + if (!retval) + memcpy(&kone->settings, buf, + sizeof(struct kone_settings)); + } + mutex_unlock(&kone->kone_lock); + + if (retval) + return retval; + + /* + * If we get here, treat settings as okay and update actual values + * according to startup_profile + */ + kone->actual_profile = kone->settings.startup_profile; + kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi; + + return sizeof(struct kone_settings); +} + +static ssize_t kone_sysfs_read_profilex(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count, int number) { + struct device *dev = container_of(kobj, struct device, kobj); + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct kone_profile)) + return 0; + + if (off + count > sizeof(struct kone_profile)) + count = sizeof(struct kone_profile) - off; + + mutex_lock(&kone->kone_lock); + memcpy(buf, &kone->profiles[number - 1], sizeof(struct kone_profile)); + mutex_unlock(&kone->kone_lock); + + return count; +} + +static ssize_t kone_sysfs_read_profile1(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 1); +} + +static ssize_t kone_sysfs_read_profile2(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 2); +} + +static ssize_t kone_sysfs_read_profile3(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 3); +} + +static ssize_t kone_sysfs_read_profile4(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 4); +} + +static ssize_t kone_sysfs_read_profile5(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 5); +} + +/* Writes data only if different to stored data */ +static ssize_t kone_sysfs_write_profilex(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count, int number) { + struct device *dev = container_of(kobj, struct device, kobj); + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + struct kone_profile *profile; + int retval = 0, difference; + + /* I need to get my data in one piece */ + if (off != 0 || count != sizeof(struct kone_profile)) + return -EINVAL; + + profile = &kone->profiles[number - 1]; + + mutex_lock(&kone->kone_lock); + difference = memcmp(buf, profile, sizeof(struct kone_profile)); + if (difference) { + retval = kone_set_profile(usb_dev, + (struct kone_profile const *)buf, number); + if (!retval) + memcpy(profile, buf, sizeof(struct kone_profile)); + } + mutex_unlock(&kone->kone_lock); + + if (retval) + return retval; + + return sizeof(struct kone_profile); +} + +static ssize_t kone_sysfs_write_profile1(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 1); +} + +static ssize_t kone_sysfs_write_profile2(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 2); +} + +static ssize_t kone_sysfs_write_profile3(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 3); +} + +static ssize_t kone_sysfs_write_profile4(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 4); +} + +static ssize_t kone_sysfs_write_profile5(struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) { + return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 5); +} + +static ssize_t kone_sysfs_show_actual_profile(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_profile); +} + +static ssize_t kone_sysfs_show_actual_dpi(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_dpi); +} + +/* weight is read each time, since we don't get informed when it's changed */ +static ssize_t kone_sysfs_show_weight(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int weight = 0; + int retval; + + mutex_lock(&kone->kone_lock); + retval = kone_get_weight(usb_dev, &weight); + mutex_unlock(&kone->kone_lock); + + if (retval) + return retval; + return snprintf(buf, PAGE_SIZE, "%d\n", weight); +} + +static ssize_t kone_sysfs_show_firmware_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", kone->firmware_version); +} + +static ssize_t kone_sysfs_show_tcu(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.tcu); +} + +static int kone_tcu_command(struct usb_device *usb_dev, int number) +{ + int len; + char *value; + + value = kmalloc(1, GFP_KERNEL); + if (!value) + return -ENOMEM; + + *value = number; + + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + kone_command_calibrate, 0, value, 1, + USB_CTRL_SET_TIMEOUT); + + kfree(value); + return ((len != 1) ? -EIO : 0); +} + +/* + * Calibrating the tcu is the only action that changes settings data inside the + * mouse, so this data needs to be reread + */ +static ssize_t kone_sysfs_set_tcu(struct device *dev, + struct device_attribute *attr, char const *buf, size_t size) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval; + unsigned long state; + + retval = strict_strtoul(buf, 10, &state); + if (retval) + return retval; + + if (state != 0 && state != 1) + return -EINVAL; + + mutex_lock(&kone->kone_lock); + + if (state == 1) { /* state activate */ + retval = kone_tcu_command(usb_dev, 1); + if (retval) + goto exit_unlock; + retval = kone_tcu_command(usb_dev, 2); + if (retval) + goto exit_unlock; + ssleep(5); /* tcu needs this time for calibration */ + retval = kone_tcu_command(usb_dev, 3); + if (retval) + goto exit_unlock; + retval = kone_tcu_command(usb_dev, 0); + if (retval) + goto exit_unlock; + retval = kone_tcu_command(usb_dev, 4); + if (retval) + goto exit_unlock; + /* + * Kone needs this time to settle things. + * Reading settings too early will result in invalid data. + * Roccat's driver waits 1 sec, maybe this time could be + * shortened. + */ + ssleep(1); + } + + /* calibration changes values in settings, so reread */ + retval = kone_get_settings(usb_dev, &kone->settings); + if (retval) + goto exit_no_settings; + + /* only write settings back if activation state is different */ + if (kone->settings.tcu != state) { + kone->settings.tcu = state; + kone_set_settings_checksum(&kone->settings); + + retval = kone_set_settings(usb_dev, &kone->settings); + if (retval) { + dev_err(&usb_dev->dev, "couldn't set tcu state\n"); + /* + * try to reread valid settings into buffer overwriting + * first error code + */ + retval = kone_get_settings(usb_dev, &kone->settings); + if (retval) + goto exit_no_settings; + goto exit_unlock; + } + } + + retval = size; +exit_no_settings: + dev_err(&usb_dev->dev, "couldn't read settings\n"); +exit_unlock: + mutex_unlock(&kone->kone_lock); + return retval; +} + +static ssize_t kone_sysfs_show_startup_profile(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.startup_profile); +} + +static ssize_t kone_sysfs_set_startup_profile(struct device *dev, + struct device_attribute *attr, char const *buf, size_t size) +{ + struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval; + unsigned long new_startup_profile; + + retval = strict_strtoul(buf, 10, &new_startup_profile); + if (retval) + return retval; + + if (new_startup_profile < 1 || new_startup_profile > 5) + return -EINVAL; + + mutex_lock(&kone->kone_lock); + + kone->settings.startup_profile = new_startup_profile; + kone_set_settings_checksum(&kone->settings); + + retval = kone_set_settings(usb_dev, &kone->settings); + + mutex_unlock(&kone->kone_lock); + + if (retval) + return retval; + + /* changing the startup profile immediately activates this profile */ + kone->actual_profile = new_startup_profile; + kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi; + + return size; +} + +/* + * This file is used by userland software to find devices that are handled by + * this driver. This provides a consistent way for actual and older kernels + * where this driver replaced usbhid instead of generic-usb. + * Driver capabilities are determined by version number. + */ +static ssize_t kone_sysfs_show_driver_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, DRIVER_VERSION "\n"); +} + +/* + * Read actual dpi settings. + * Returns raw value for further processing. Refer to enum kone_polling_rates to + * get real value. + */ +static DEVICE_ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL); + +static DEVICE_ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL); + +/* + * The mouse can be equipped with one of four supplied weights from 5 to 20 + * grams which are recognized and its value can be read out. + * This returns the raw value reported by the mouse for easy evaluation by + * software. Refer to enum kone_weights to get corresponding real weight. + */ +static DEVICE_ATTR(weight, 0440, kone_sysfs_show_weight, NULL); + +/* + * Prints firmware version stored in mouse as integer. + * The raw value reported by the mouse is returned for easy evaluation, to get + * the real version number the decimal point has to be shifted 2 positions to + * the left. E.g. a value of 138 means 1.38. + */ +static DEVICE_ATTR(firmware_version, 0440, + kone_sysfs_show_firmware_version, NULL); + +/* + * Prints state of Tracking Control Unit as number where 0 = off and 1 = on + * Writing 0 deactivates tcu and writing 1 calibrates and activates the tcu + */ +static DEVICE_ATTR(tcu, 0660, kone_sysfs_show_tcu, kone_sysfs_set_tcu); + +/* Prints and takes the number of the profile the mouse starts with */ +static DEVICE_ATTR(startup_profile, 0660, + kone_sysfs_show_startup_profile, + kone_sysfs_set_startup_profile); + +static DEVICE_ATTR(kone_driver_version, 0440, + kone_sysfs_show_driver_version, NULL); + +static struct attribute *kone_attributes[] = { + &dev_attr_actual_dpi.attr, + &dev_attr_actual_profile.attr, + &dev_attr_weight.attr, + &dev_attr_firmware_version.attr, + &dev_attr_tcu.attr, + &dev_attr_startup_profile.attr, + &dev_attr_kone_driver_version.attr, + NULL +}; + +static struct attribute_group kone_attribute_group = { + .attrs = kone_attributes +}; + +static struct bin_attribute kone_settings_attr = { + .attr = { .name = "settings", .mode = 0660 }, + .size = sizeof(struct kone_settings), + .read = kone_sysfs_read_settings, + .write = kone_sysfs_write_settings +}; + +static struct bin_attribute kone_profile1_attr = { + .attr = { .name = "profile1", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profile1, + .write = kone_sysfs_write_profile1 +}; + +static struct bin_attribute kone_profile2_attr = { + .attr = { .name = "profile2", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profile2, + .write = kone_sysfs_write_profile2 +}; + +static struct bin_attribute kone_profile3_attr = { + .attr = { .name = "profile3", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profile3, + .write = kone_sysfs_write_profile3 +}; + +static struct bin_attribute kone_profile4_attr = { + .attr = { .name = "profile4", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profile4, + .write = kone_sysfs_write_profile4 +}; + +static struct bin_attribute kone_profile5_attr = { + .attr = { .name = "profile5", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profile5, + .write = kone_sysfs_write_profile5 +}; + +static int kone_create_sysfs_attributes(struct usb_interface *intf) +{ + int retval; + + retval = sysfs_create_group(&intf->dev.kobj, &kone_attribute_group); + if (retval) + goto exit_1; + + retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_settings_attr); + if (retval) + goto exit_2; + + retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile1_attr); + if (retval) + goto exit_3; + + retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile2_attr); + if (retval) + goto exit_4; + + retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile3_attr); + if (retval) + goto exit_5; + + retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile4_attr); + if (retval) + goto exit_6; + + retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile5_attr); + if (retval) + goto exit_7; + + return 0; + +exit_7: + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile4_attr); +exit_6: + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile3_attr); +exit_5: + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile2_attr); +exit_4: + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile1_attr); +exit_3: + sysfs_remove_bin_file(&intf->dev.kobj, &kone_settings_attr); +exit_2: + sysfs_remove_group(&intf->dev.kobj, &kone_attribute_group); +exit_1: + return retval; +} + +static void kone_remove_sysfs_attributes(struct usb_interface *intf) +{ + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile5_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile4_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile3_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile2_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile1_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &kone_settings_attr); + sysfs_remove_group(&intf->dev.kobj, &kone_attribute_group); +} + +static int kone_init_kone_device_struct(struct usb_device *usb_dev, + struct kone_device *kone) +{ + uint i; + int retval; + + mutex_init(&kone->kone_lock); + + for (i = 0; i < 5; ++i) { + retval = kone_get_profile(usb_dev, &kone->profiles[i], i + 1); + if (retval) + return retval; + } + + retval = kone_get_settings(usb_dev, &kone->settings); + if (retval) + return retval; + + retval = kone_get_firmware_version(usb_dev, &kone->firmware_version); + if (retval) + return retval; + + kone->actual_profile = kone->settings.startup_profile; + kone->actual_dpi = kone->profiles[kone->actual_profile].startup_dpi; + + return 0; +} + +/* + * Since IGNORE_MOUSE quirk moved to hid-apple, there is no way to bind only to + * mousepart if usb_hid is compiled into the kernel and kone is compiled as + * module. + * Secial behaviour is bound only to mousepart since only mouseevents contain + * additional notifications. + */ +static int kone_init_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct kone_device *kone; + int retval; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_MOUSE) { + + kone = kzalloc(sizeof(*kone), GFP_KERNEL); + if (!kone) { + dev_err(&hdev->dev, "can't alloc device descriptor\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, kone); + + retval = kone_init_kone_device_struct(usb_dev, kone); + if (retval) { + dev_err(&hdev->dev, + "couldn't init struct kone_device\n"); + goto exit_free; + } + retval = kone_create_sysfs_attributes(intf); + if (retval) { + dev_err(&hdev->dev, "cannot create sysfs files\n"); + goto exit_free; + } + } else { + hid_set_drvdata(hdev, NULL); + } + + return 0; +exit_free: + kfree(kone); + return retval; +} + + +static void kone_remove_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_MOUSE) { + kone_remove_sysfs_attributes(intf); + kfree(hid_get_drvdata(hdev)); + } +} + +static int kone_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int retval; + + retval = hid_parse(hdev); + if (retval) { + dev_err(&hdev->dev, "parse failed\n"); + goto exit; + } + + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (retval) { + dev_err(&hdev->dev, "hw start failed\n"); + goto exit; + } + + retval = kone_init_specials(hdev); + if (retval) { + dev_err(&hdev->dev, "couldn't install mouse\n"); + goto exit_stop; + } + + return 0; + +exit_stop: + hid_hw_stop(hdev); +exit: + return retval; +} + +static void kone_remove(struct hid_device *hdev) +{ + kone_remove_specials(hdev); + hid_hw_stop(hdev); +} + +/* + * Is called for keyboard- and mousepart. + * Only mousepart gets informations about special events in its extended event + * structure. + */ +static int kone_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct kone_device *kone = hid_get_drvdata(hdev); + struct kone_mouse_event *event = (struct kone_mouse_event *)data; + + /* keyboard events are always processed by default handler */ + if (size != sizeof(struct kone_mouse_event)) + return 0; + + /* + * Firmware 1.38 introduced new behaviour for tilt buttons. + * Pressed tilt button is reported in each movement event. + * Workaround sends only one event per press. + */ + if (kone->last_tilt_state == event->tilt) + event->tilt = 0; + else + kone->last_tilt_state = event->tilt; + + /* + * handle special events and keep actual profile and dpi values + * up to date + */ + switch (event->event) { + case kone_mouse_event_osd_dpi: + dev_dbg(&hdev->dev, "osd dpi event. actual dpi %d\n", + event->value); + return 1; /* return 1 if event was handled */ + case kone_mouse_event_switch_dpi: + kone->actual_dpi = event->value; + dev_dbg(&hdev->dev, "switched dpi to %d\n", event->value); + return 1; + case kone_mouse_event_osd_profile: + dev_dbg(&hdev->dev, "osd profile event. actual profile %d\n", + event->value); + return 1; + case kone_mouse_event_switch_profile: + kone->actual_profile = event->value; + kone->actual_dpi = kone->profiles[kone->actual_profile - 1]. + startup_dpi; + dev_dbg(&hdev->dev, "switched profile to %d\n", event->value); + return 1; + case kone_mouse_event_call_overlong_macro: + dev_dbg(&hdev->dev, "overlong macro called, button %d %s/%s\n", + event->macro_key, + kone->profiles[kone->actual_profile - 1]. + button_infos[event->macro_key].macro_set_name, + kone->profiles[kone->actual_profile - 1]. + button_infos[event->macro_key].macro_name + ); + return 1; + } + + return 0; /* do further processing */ +} + +static const struct hid_device_id kone_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, kone_devices); + +static struct hid_driver kone_driver = { + .name = "kone", + .id_table = kone_devices, + .probe = kone_probe, + .remove = kone_remove, + .raw_event = kone_raw_event +}; + +static int kone_init(void) +{ + return hid_register_driver(&kone_driver); +} + +static void kone_exit(void) +{ + hid_unregister_driver(&kone_driver); +} + +module_init(kone_init); +module_exit(kone_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); diff --git a/drivers/hid/hid-roccat-kone.h b/drivers/hid/hid-roccat-kone.h new file mode 100644 index 0000000..ee6898c --- /dev/null +++ b/drivers/hid/hid-roccat-kone.h @@ -0,0 +1,214 @@ +#ifndef __HID_ROCCAT_KONE_H +#define __HID_ROCCAT_KONE_H + +/* + * Copyright (c) 2010 Stefan Achatz + */ + +/* + * 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; either version 2 of the License, or (at your option) + * any later version. + */ + +#include + +#define DRIVER_VERSION "v0.3.0" +#define DRIVER_AUTHOR "Stefan Achatz" +#define DRIVER_DESC "USB Roccat Kone driver" +#define DRIVER_LICENSE "GPL v2" + +#pragma pack(push) +#pragma pack(1) + +struct kone_keystroke { + uint8_t key; + uint8_t action; + uint16_t period; /* in milliseconds */ +}; + +enum kone_keystroke_buttons { + kone_keystroke_button_1 = 0xf0, /* left mouse button */ + kone_keystroke_button_2 = 0xf1, /* right mouse button */ + kone_keystroke_button_3 = 0xf2, /* wheel */ + kone_keystroke_button_9 = 0xf3, /* side button up */ + kone_keystroke_button_8 = 0xf4 /* side button down */ +}; + +enum kone_keystroke_actions { + kone_keystroke_action_press = 0, + kone_keystroke_action_release = 1 +}; + +struct kone_button_info { + uint8_t number; /* range 1-8 */ + uint8_t type; + uint8_t macro_type; /* 0 = short, 1 = overlong */ + uint8_t macro_set_name[16]; /* can be max 15 chars long */ + uint8_t macro_name[16]; /* can be max 15 chars long */ + uint8_t count; + struct kone_keystroke keystrokes[20]; +}; + +enum kone_button_info_types { + /* valid button types until firmware 1.32 */ + kone_button_info_type_button_1 = 0x1, /* click (left mouse button) */ + kone_button_info_type_button_2 = 0x2, /* menu (right mouse button)*/ + kone_button_info_type_button_3 = 0x3, /* scroll (wheel) */ + kone_button_info_type_double_click = 0x4, + kone_button_info_type_key = 0x5, + kone_button_info_type_macro = 0x6, + kone_button_info_type_off = 0x7, + /* TODO clarify function and rename */ + kone_button_info_type_osd_xy_prescaling = 0x8, + kone_button_info_type_osd_dpi = 0x9, + kone_button_info_type_osd_profile = 0xa, + kone_button_info_type_button_9 = 0xb, /* ie forward */ + kone_button_info_type_button_8 = 0xc, /* ie backward */ + kone_button_info_type_dpi_up = 0xd, /* internal */ + kone_button_info_type_dpi_down = 0xe, /* internal */ + kone_button_info_type_button_7 = 0xf, /* tilt left */ + kone_button_info_type_button_6 = 0x10, /* tilt right */ + kone_button_info_type_profile_up = 0x11, /* internal */ + kone_button_info_type_profile_down = 0x12, /* internal */ + /* additional valid button types since firmware 1.38 */ + kone_button_info_type_multimedia_open_player = 0x20, + kone_button_info_type_multimedia_next_track = 0x21, + kone_button_info_type_multimedia_prev_track = 0x22, + kone_button_info_type_multimedia_play_pause = 0x23, + kone_button_info_type_multimedia_stop = 0x24, + kone_button_info_type_multimedia_mute = 0x25, + kone_button_info_type_multimedia_volume_up = 0x26, + kone_button_info_type_multimedia_volume_down = 0x27 +}; + +struct kone_light_info { + uint8_t number; /* number of light 1-5 */ + uint8_t mod; /* 1 = on, 2 = off */ + uint8_t red; /* range 0x00-0xff */ + uint8_t green; /* range 0x00-0xff */ + uint8_t blue; /* range 0x00-0xff */ +}; + +struct kone_profile { + uint16_t size; /* always 975 */ + uint16_t unused; /* always 0 */ + + /* + * range 1-5 + * This number does not need to correspond with location where profile + * saved + */ + uint8_t profile; /* range 1-5 */ + + uint16_t main_sensitivity; /* range 100-1000 */ + uint8_t xy_sensitivity_enabled; /* 1 = on, 2 = off */ + uint16_t x_sensitivity; /* range 100-1000 */ + uint16_t y_sensitivity; /* range 100-1000 */ + uint8_t dpi_rate; /* bit 1 = 800, ... */ + uint8_t startup_dpi; /* range 1-6 */ + uint8_t polling_rate; /* 1 = 125Hz, 2 = 500Hz, 3 = 1000Hz */ + /* kone has no dcu + * value is always 2 in firmwares <= 1.32 and + * 1 in firmwares > 1.32 + */ + uint8_t dcu_flag; + uint8_t light_effect_1; /* range 1-3 */ + uint8_t light_effect_2; /* range 1-5 */ + uint8_t light_effect_3; /* range 1-4 */ + uint8_t light_effect_speed; /* range 0-255 */ + + struct kone_light_info light_infos[5]; + struct kone_button_info button_infos[8]; + + uint16_t checksum; /* \brief holds checksum of struct */ +}; + +enum kone_polling_rates { + kone_polling_rate_125 = 1, + kone_polling_rate_500 = 2, + kone_polling_rate_1000 = 3 +}; + +struct kone_settings { + uint16_t size; /* always 36 */ + uint8_t startup_profile; /* 1-5 */ + uint8_t unknown1; + uint8_t tcu; /* 0 = off, 1 = on */ + uint8_t unknown2[23]; + uint8_t calibration_data[4]; + uint8_t unknown3[2]; + uint16_t checksum; +}; + +/* + * 12 byte mouse event read by interrupt_read + */ +struct kone_mouse_event { + uint8_t report_number; /* always 1 */ + uint8_t button; + uint16_t x; + uint16_t y; + uint8_t wheel; /* up = 1, down = -1 */ + uint8_t tilt; /* right = 1, left = -1 */ + uint8_t unknown; + uint8_t event; + uint8_t value; /* press = 0, release = 1 */ + uint8_t macro_key; /* 0 to 8 */ +}; + +enum kone_mouse_events { + /* osd events are thought to be display on screen */ + kone_mouse_event_osd_dpi = 0xa0, + kone_mouse_event_osd_profile = 0xb0, + /* TODO clarify meaning and occurence of kone_mouse_event_calibration */ + kone_mouse_event_calibration = 0xc0, + kone_mouse_event_call_overlong_macro = 0xe0, + /* switch events notify if user changed values wiht mousebutton click */ + kone_mouse_event_switch_dpi = 0xf0, + kone_mouse_event_switch_profile = 0xf1 +}; + +enum kone_commands { + kone_command_profile = 0x5a, + kone_command_settings = 0x15a, + kone_command_firmware_version = 0x25a, + kone_command_weight = 0x45a, + kone_command_calibrate = 0x55a, + kone_command_confirm_write = 0x65a, + kone_command_firmware = 0xe5a +}; + +#pragma pack(pop) + +struct kone_device { + /* + * Storing actual values when we get informed about changes since there + * is no way of getting this information from the device on demand + */ + int actual_profile, actual_dpi; + /* Used for neutralizing abnormal tilt button behaviour */ + int last_tilt_state; + /* + * It's unlikely that multiple sysfs attributes are accessed at a time, + * so only one mutex is used to secure hardware access and profiles and + * settings of this struct. + */ + struct mutex kone_lock; + + /* + * Storing the data here reduces IO and ensures that data is available + * when its needed (E.g. interrupt handler). + */ + struct kone_profile profiles[5]; + struct kone_settings settings; + + /* + * firmware doesn't change unless firmware update is implemented, + * so it's read only once + */ + int firmware_version; +}; + +#endif -- cgit v0.10.2 From 23d386d85a9144612c4a13733aa1ca6e5a21f4a2 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Mon, 22 Mar 2010 16:33:15 +0100 Subject: HID: fixup Kconfig entry for Roccat Kone Make it independent on CONFIG_EMBEDDED (to be in sync with other "full-fledged" HID drivers which are not simple quirks) and provide a little bit better text description. Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index d819b02..44b4691 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -269,9 +269,8 @@ config HID_QUANTA Support for Quanta Optical Touch dual-touch panels. config HID_ROCCAT_KONE - tristate "Roccat Kone" if EMBEDDED + tristate "Roccat Kone Mouse support" depends on USB_HID - default !EMBEDDED ---help--- Support for Roccat Kone mouse. -- cgit v0.10.2 From ed28f04b2753ce1b07b9c3dab7d186c43ce19e8c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 30 Mar 2010 02:52:41 +0900 Subject: HID: update gfp/slab.h includes Implicit slab.h inclusion via percpu.h is about to go away. Make sure gfp.h or slab.h is included as necessary. Signed-off-by: Tejun Heo Cc: Stephen Rothwell Cc: Jiri Kosina Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 2b1412e..7b11784 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "hid-ids.h" #include "hid-roccat-kone.h" -- cgit v0.10.2 From 236db47c2b3b69464d50c695ab2ddd516cf64520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Tue, 30 Mar 2010 22:33:50 +0200 Subject: HID: new driver for PicoLCD device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add basic driver for PicoLCD graphics device. Initially support keypad with input device and provide support for debugging communication via events file from debugfs. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/Documentation/ABI/testing/sysfs-driver-hid-picolcd b/Documentation/ABI/testing/sysfs-driver-hid-picolcd new file mode 100644 index 0000000..6fb4f21 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-picolcd @@ -0,0 +1,17 @@ +What: /sys/bus/usb/devices/-:./::./operation_mode +Date: March 2010 +Contact: Bruno Prémont +Description: Make it possible to switch the PicoLCD device between LCD + (firmware) and bootloader (flasher) operation modes. + + Reading: returns list of available modes, the active mode being + enclosed in brackets ('[' and ']') + + Writing: causes operation mode switch. Permitted values are + the non-active mode names listed when read, optionally followed + by a delay value expressed in ms. + + Note: when switching mode the current PicoLCD HID device gets + disconnected and reconnects after above delay (default value + is 5 seconds though this default should not be relied on). + diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 71d4c07..138ba6a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -262,6 +262,24 @@ config HID_PETALYNX ---help--- Support for Petalynx Maxter remote control. +config HID_PICOLCD + tristate "PicoLCD (graphic version)" + depends on USB_HID + ---help--- + This provides support for Minibox PicoLCD devices, currently + only the graphical ones are supported. + + This includes support for the following device features: + - Keypad + - Switching between Firmware and Flash mode + Features that are not (yet) supported: + - Framebuffer for monochrome 256x64 display + - Backlight control + - Contrast control + - IR + - General purpose outputs + - EEProm / Flash access + config HID_QUANTA tristate "Quanta Optical Touch" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0b2618f..7fd1614 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_HID_ORTEK) += hid-ortek.o obj-$(CONFIG_HID_QUANTA) += hid-quanta.o obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o +obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 2e2aa75..bb11fb4 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1335,6 +1335,8 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 797e064..783b41d 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -341,6 +341,8 @@ #define USB_VENDOR_ID_MICROCHIP 0x04d8 #define USB_DEVICE_ID_PICKIT1 0x0032 #define USB_DEVICE_ID_PICKIT2 0x0033 +#define USB_DEVICE_ID_PICOLCD 0xc002 +#define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002 #define USB_VENDOR_ID_MICROSOFT 0x045e #define USB_DEVICE_ID_SIDEWINDER_GV 0x003b diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c new file mode 100644 index 0000000..c7855f3 --- /dev/null +++ b/drivers/hid/hid-picolcd.c @@ -0,0 +1,1183 @@ +/*************************************************************************** + * Copyright (C) 2010 by Bruno Prémont * + * * + * Based on Logitech G13 driver (v0.4) * + * Copyright (C) 2009 by Rick L. Vinyard, Jr. * + * * + * 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 of the License. * + * * + * This driver is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this software. If not see . * + ***************************************************************************/ + +#include +#include +#include +#include "hid-ids.h" +#include "usbhid/usbhid.h" +#include + +#include +#include + +#include + +#define PICOLCD_NAME "PicoLCD (graphic)" + +/* Report numbers */ +#define REPORT_ERROR_CODE 0x10 /* LCD: IN[16] */ +#define ERR_SUCCESS 0x00 +#define ERR_PARAMETER_MISSING 0x01 +#define ERR_DATA_MISSING 0x02 +#define ERR_BLOCK_READ_ONLY 0x03 +#define ERR_BLOCK_NOT_ERASABLE 0x04 +#define ERR_BLOCK_TOO_BIG 0x05 +#define ERR_SECTION_OVERFLOW 0x06 +#define ERR_INVALID_CMD_LEN 0x07 +#define ERR_INVALID_DATA_LEN 0x08 +#define REPORT_KEY_STATE 0x11 /* LCD: IN[2] */ +#define REPORT_IR_DATA 0x21 /* LCD: IN[63] */ +#define REPORT_EE_DATA 0x32 /* LCD: IN[63] */ +#define REPORT_MEMORY 0x41 /* LCD: IN[63] */ +#define REPORT_LED_STATE 0x81 /* LCD: OUT[1] */ +#define REPORT_BRIGHTNESS 0x91 /* LCD: OUT[1] */ +#define REPORT_CONTRAST 0x92 /* LCD: OUT[1] */ +#define REPORT_RESET 0x93 /* LCD: OUT[2] */ +#define REPORT_LCD_CMD 0x94 /* LCD: OUT[63] */ +#define REPORT_LCD_DATA 0x95 /* LCD: OUT[63] */ +#define REPORT_LCD_CMD_DATA 0x96 /* LCD: OUT[63] */ +#define REPORT_EE_READ 0xa3 /* LCD: OUT[63] */ +#define REPORT_EE_WRITE 0xa4 /* LCD: OUT[63] */ +#define REPORT_ERASE_MEMORY 0xb2 /* LCD: OUT[2] */ +#define REPORT_READ_MEMORY 0xb3 /* LCD: OUT[3] */ +#define REPORT_WRITE_MEMORY 0xb4 /* LCD: OUT[63] */ +#define REPORT_SPLASH_RESTART 0xc1 /* LCD: OUT[1] */ +#define REPORT_EXIT_KEYBOARD 0xef /* LCD: OUT[2] */ +#define REPORT_VERSION 0xf1 /* LCD: IN[2],OUT[1] Bootloader: IN[2],OUT[1] */ +#define REPORT_BL_ERASE_MEMORY 0xf2 /* Bootloader: IN[36],OUT[4] */ +#define REPORT_BL_READ_MEMORY 0xf3 /* Bootloader: IN[36],OUT[4] */ +#define REPORT_BL_WRITE_MEMORY 0xf4 /* Bootloader: IN[36],OUT[36] */ +#define REPORT_DEVID 0xf5 /* LCD: IN[5], OUT[1] Bootloader: IN[5],OUT[1] */ +#define REPORT_SPLASH_SIZE 0xf6 /* LCD: IN[4], OUT[1] */ +#define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */ +#define REPORT_EXIT_FLASHER 0xff /* Bootloader: OUT[2] */ + +/* Input device + * + * The PicoLCD has an IR receiver header, a built-in keypad with 5 keys + * and header for 4x4 key matrix. The built-in keys are part of the matrix. + */ +static const unsigned short def_keymap[] = { + KEY_RESERVED, /* none */ + KEY_BACK, /* col 4 + row 1 */ + KEY_HOMEPAGE, /* col 3 + row 1 */ + KEY_RESERVED, /* col 2 + row 1 */ + KEY_RESERVED, /* col 1 + row 1 */ + KEY_SCROLLUP, /* col 4 + row 2 */ + KEY_OK, /* col 3 + row 2 */ + KEY_SCROLLDOWN, /* col 2 + row 2 */ + KEY_RESERVED, /* col 1 + row 2 */ + KEY_RESERVED, /* col 4 + row 3 */ + KEY_RESERVED, /* col 3 + row 3 */ + KEY_RESERVED, /* col 2 + row 3 */ + KEY_RESERVED, /* col 1 + row 3 */ + KEY_RESERVED, /* col 4 + row 4 */ + KEY_RESERVED, /* col 3 + row 4 */ + KEY_RESERVED, /* col 2 + row 4 */ + KEY_RESERVED, /* col 1 + row 4 */ +}; +#define PICOLCD_KEYS ARRAY_SIZE(def_keymap) + +/* Description of in-progress IO operation, used for operations + * that trigger response from device */ +struct picolcd_pending { + struct hid_report *out_report; + struct hid_report *in_report; + struct completion ready; + int raw_size; + u8 raw_data[64]; +}; + +/* Per device data structure */ +struct picolcd_data { + struct hid_device *hdev; +#ifdef CONFIG_DEBUG_FS + int addr_sz; +#endif + u8 version[2]; + /* input stuff */ + u8 pressed_keys[2]; + struct input_dev *input_keys; + struct input_dev *input_cir; + unsigned short keycode[PICOLCD_KEYS]; + + /* Housekeeping stuff */ + spinlock_t lock; + struct mutex mutex; + struct picolcd_pending *pending; + int status; +#define PICOLCD_BOOTLOADER 1 +#define PICOLCD_FAILED 2 +}; + + +/* Find a given report */ +#define picolcd_in_report(id, dev) picolcd_report(id, dev, HID_INPUT_REPORT) +#define picolcd_out_report(id, dev) picolcd_report(id, dev, HID_OUTPUT_REPORT) + +static struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir) +{ + struct list_head *feature_report_list = &hdev->report_enum[dir].report_list; + struct hid_report *report = NULL; + + list_for_each_entry(report, feature_report_list, list) { + if (report->id == id) + return report; + } + dev_warn(&hdev->dev, "No report with id 0x%x found\n", id); + return NULL; +} + +#ifdef CONFIG_DEBUG_FS +static void picolcd_debug_out_report(struct picolcd_data *data, + struct hid_device *hdev, struct hid_report *report); +#define usbhid_submit_report(a, b, c) \ + do { \ + picolcd_debug_out_report(hid_get_drvdata(a), a, b); \ + usbhid_submit_report(a, b, c); \ + } while (0) +#endif + +/* Submit a report and wait for a reply from device - if device fades away + * or does not respond in time, return NULL */ +static struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, + int report_id, const u8 *raw_data, int size) +{ + struct picolcd_data *data = hid_get_drvdata(hdev); + struct picolcd_pending *work; + struct hid_report *report = picolcd_out_report(report_id, hdev); + unsigned long flags; + int i, j, k; + + if (!report || !data) + return NULL; + if (data->status & PICOLCD_FAILED) + return NULL; + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) + return NULL; + + init_completion(&work->ready); + work->out_report = report; + work->in_report = NULL; + work->raw_size = 0; + + mutex_lock(&data->mutex); + spin_lock_irqsave(&data->lock, flags); + for (i = k = 0; i < report->maxfield; i++) + for (j = 0; j < report->field[i]->report_count; j++) { + hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0); + k++; + } + data->pending = work; + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + wait_for_completion_interruptible_timeout(&work->ready, HZ*2); + spin_lock_irqsave(&data->lock, flags); + data->pending = NULL; + spin_unlock_irqrestore(&data->lock, flags); + mutex_unlock(&data->mutex); + return work; +} + +/* + * input class device + */ +static int picolcd_raw_keypad(struct picolcd_data *data, + struct hid_report *report, u8 *raw_data, int size) +{ + /* + * Keypad event + * First and second data bytes list currently pressed keys, + * 0x00 means no key and at most 2 keys may be pressed at same time + */ + int i, j; + + /* determine newly pressed keys */ + for (i = 0; i < size; i++) { + unsigned int key_code; + if (raw_data[i] == 0) + continue; + for (j = 0; j < sizeof(data->pressed_keys); j++) + if (data->pressed_keys[j] == raw_data[i]) + goto key_already_down; + for (j = 0; j < sizeof(data->pressed_keys); j++) + if (data->pressed_keys[j] == 0) { + data->pressed_keys[j] = raw_data[i]; + break; + } + input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]); + if (raw_data[i] < PICOLCD_KEYS) + key_code = data->keycode[raw_data[i]]; + else + key_code = KEY_UNKNOWN; + if (key_code != KEY_UNKNOWN) { + dbg_hid(PICOLCD_NAME " got key press for %u:%d", + raw_data[i], key_code); + input_report_key(data->input_keys, key_code, 1); + } + input_sync(data->input_keys); +key_already_down: + continue; + } + + /* determine newly released keys */ + for (j = 0; j < sizeof(data->pressed_keys); j++) { + unsigned int key_code; + if (data->pressed_keys[j] == 0) + continue; + for (i = 0; i < size; i++) + if (data->pressed_keys[j] == raw_data[i]) + goto key_still_down; + input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]); + if (data->pressed_keys[j] < PICOLCD_KEYS) + key_code = data->keycode[data->pressed_keys[j]]; + else + key_code = KEY_UNKNOWN; + if (key_code != KEY_UNKNOWN) { + dbg_hid(PICOLCD_NAME " got key release for %u:%d", + data->pressed_keys[j], key_code); + input_report_key(data->input_keys, key_code, 0); + } + input_sync(data->input_keys); + data->pressed_keys[j] = 0; +key_still_down: + continue; + } + return 1; +} + +static int picolcd_raw_cir(struct picolcd_data *data, + struct hid_report *report, u8 *raw_data, int size) +{ + /* Need understanding of CIR data format to implement ... */ + return 1; +} + +static int picolcd_check_version(struct hid_device *hdev) +{ + struct picolcd_data *data = hid_get_drvdata(hdev); + struct picolcd_pending *verinfo; + int ret = 0; + + if (!data) + return -ENODEV; + + verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0); + if (!verinfo) { + dev_err(&hdev->dev, "no version response from PicoLCD"); + return -ENODEV; + } + + if (verinfo->raw_size == 2) { + if (data->status & PICOLCD_BOOTLOADER) { + dev_info(&hdev->dev, "PicoLCD, bootloader version %d.%d\n", + verinfo->raw_data[0], verinfo->raw_data[1]); + data->version[0] = verinfo->raw_data[0]; + data->version[1] = verinfo->raw_data[1]; + } else { + dev_info(&hdev->dev, "PicoLCD, firmware version %d.%d\n", + verinfo->raw_data[1], verinfo->raw_data[0]); + data->version[0] = verinfo->raw_data[1]; + data->version[1] = verinfo->raw_data[0]; + } + } else { + dev_err(&hdev->dev, "confused, got unexpected version response from PicoLCD\n"); + ret = -EINVAL; + } + kfree(verinfo); + return ret; +} + +/* + * Reset our device and wait for answer to VERSION request + */ +static int picolcd_reset(struct hid_device *hdev) +{ + struct picolcd_data *data = hid_get_drvdata(hdev); + struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev); + unsigned long flags; + + if (!data || !report || report->maxfield != 1) + return -ENODEV; + + spin_lock_irqsave(&data->lock, flags); + if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) + data->status |= PICOLCD_BOOTLOADER; + + /* perform the reset */ + hid_set_field(report->field[0], 0, 1); + usbhid_submit_report(hdev, report, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + + return picolcd_check_version(hdev); +} + +/* + * The "operation_mode" sysfs attribute + */ +static ssize_t picolcd_operation_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct picolcd_data *data = dev_get_drvdata(dev); + + if (data->status & PICOLCD_BOOTLOADER) + return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n"); + else + return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n"); +} + +static ssize_t picolcd_operation_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct picolcd_data *data = dev_get_drvdata(dev); + struct hid_report *report = NULL; + size_t cnt = count; + int timeout = 5000; + unsigned u; + unsigned long flags; + + if (cnt >= 3 && strncmp("lcd", buf, 3) == 0) { + if (data->status & PICOLCD_BOOTLOADER) + report = picolcd_out_report(REPORT_EXIT_FLASHER, data->hdev); + buf += 3; + cnt -= 3; + } else if (cnt >= 10 && strncmp("bootloader", buf, 10) == 0) { + if (!(data->status & PICOLCD_BOOTLOADER)) + report = picolcd_out_report(REPORT_EXIT_KEYBOARD, data->hdev); + buf += 10; + cnt -= 10; + } + if (!report) + return -EINVAL; + + while (cnt > 0 && (*buf == ' ' || *buf == '\t')) { + buf++; + cnt--; + } + while (cnt > 0 && (buf[cnt-1] == '\n' || buf[cnt-1] == '\r')) + cnt--; + if (cnt > 0) { + if (sscanf(buf, "%u", &u) != 1) + return -EINVAL; + if (u > 30000) + return -EINVAL; + else + timeout = u; + } + + spin_lock_irqsave(&data->lock, flags); + hid_set_field(report->field[0], 0, timeout & 0xff); + hid_set_field(report->field[0], 1, (timeout >> 8) & 0xff); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + return count; +} + +static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show, + picolcd_operation_mode_store); + + +#ifdef CONFIG_DEBUG_FS +/* + * Helper code for HID report level dumping/debugging + */ +static const char *error_codes[] = { + "success", "parameter missing", "data_missing", "block readonly", + "block not erasable", "block too big", "section overflow", + "invalid command length", "invalid data length", +}; + +static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data, + const size_t data_len) +{ + int i, j; + for (i = j = 0; i < data_len && j + 3 < dst_sz; i++) { + dst[j++] = hex_asc[(data[i] >> 4) & 0x0f]; + dst[j++] = hex_asc[data[i] & 0x0f]; + dst[j++] = ' '; + } + if (j < dst_sz) { + dst[j--] = '\0'; + dst[j] = '\n'; + } else + dst[j] = '\0'; +} + +static void picolcd_debug_out_report(struct picolcd_data *data, + struct hid_device *hdev, struct hid_report *report) +{ + u8 raw_data[70]; + int raw_size = (report->size >> 3) + 1; + char *buff; +#define BUFF_SZ 256 + + /* Avoid unnecessary overhead if debugfs is disabled */ + if (!hdev->debug_events) + return; + + buff = kmalloc(BUFF_SZ, GFP_ATOMIC); + if (!buff) + return; + + snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ", + report->id, raw_size); + hid_debug_event(hdev, buff); + if (raw_size + 5 > sizeof(raw_data)) { + hid_debug_event(hdev, " TOO BIG\n"); + return; + } else { + raw_data[0] = report->id; + hid_output_report(report, raw_data); + dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size); + hid_debug_event(hdev, buff); + } + + switch (report->id) { + case REPORT_LED_STATE: + /* 1 data byte with GPO state */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_LED_STATE", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tGPO state: 0x%02x\n", raw_data[1]); + hid_debug_event(hdev, buff); + break; + case REPORT_BRIGHTNESS: + /* 1 data byte with brightness */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_BRIGHTNESS", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tBrightness: 0x%02x\n", raw_data[1]); + hid_debug_event(hdev, buff); + break; + case REPORT_CONTRAST: + /* 1 data byte with contrast */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_CONTRAST", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tContrast: 0x%02x\n", raw_data[1]); + hid_debug_event(hdev, buff); + break; + case REPORT_RESET: + /* 2 data bytes with reset duration in ms */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_RESET", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tDuration: 0x%02x%02x (%dms)\n", + raw_data[2], raw_data[1], raw_data[2] << 8 | raw_data[1]); + hid_debug_event(hdev, buff); + break; + case REPORT_LCD_CMD: + /* 63 data bytes with LCD commands */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_LCD_CMD", report->id, raw_size-1); + hid_debug_event(hdev, buff); + /* TODO: format decoding */ + break; + case REPORT_LCD_DATA: + /* 63 data bytes with LCD data */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_LCD_CMD", report->id, raw_size-1); + /* TODO: format decoding */ + hid_debug_event(hdev, buff); + break; + case REPORT_LCD_CMD_DATA: + /* 63 data bytes with LCD commands and data */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_LCD_CMD", report->id, raw_size-1); + /* TODO: format decoding */ + hid_debug_event(hdev, buff); + break; + case REPORT_EE_READ: + /* 3 data bytes with read area description */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_EE_READ", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); + hid_debug_event(hdev, buff); + break; + case REPORT_EE_WRITE: + /* 3+1..20 data bytes with write area description */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_EE_WRITE", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); + hid_debug_event(hdev, buff); + if (raw_data[3] == 0) { + snprintf(buff, BUFF_SZ, "\tNo data\n"); + } else if (raw_data[3] + 4 <= raw_size) { + snprintf(buff, BUFF_SZ, "\tData: "); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); + } else { + snprintf(buff, BUFF_SZ, "\tData overflowed\n"); + } + hid_debug_event(hdev, buff); + break; + case REPORT_ERASE_MEMORY: + case REPORT_BL_ERASE_MEMORY: + /* 3 data bytes with pointer inside erase block */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_ERASE_MEMORY", report->id, raw_size-1); + hid_debug_event(hdev, buff); + switch (data->addr_sz) { + case 2: + snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x\n", + raw_data[2], raw_data[1]); + break; + case 3: + snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x%02x\n", + raw_data[3], raw_data[2], raw_data[1]); + break; + default: + snprintf(buff, BUFF_SZ, "\tNot supported\n"); + } + hid_debug_event(hdev, buff); + break; + case REPORT_READ_MEMORY: + case REPORT_BL_READ_MEMORY: + /* 4 data bytes with read area description */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_READ_MEMORY", report->id, raw_size-1); + hid_debug_event(hdev, buff); + switch (data->addr_sz) { + case 2: + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); + break; + case 3: + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", + raw_data[3], raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); + break; + default: + snprintf(buff, BUFF_SZ, "\tNot supported\n"); + } + hid_debug_event(hdev, buff); + break; + case REPORT_WRITE_MEMORY: + case REPORT_BL_WRITE_MEMORY: + /* 4+1..32 data bytes with write adrea description */ + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_WRITE_MEMORY", report->id, raw_size-1); + hid_debug_event(hdev, buff); + switch (data->addr_sz) { + case 2: + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); + hid_debug_event(hdev, buff); + if (raw_data[3] == 0) { + snprintf(buff, BUFF_SZ, "\tNo data\n"); + } else if (raw_data[3] + 4 <= raw_size) { + snprintf(buff, BUFF_SZ, "\tData: "); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); + } else { + snprintf(buff, BUFF_SZ, "\tData overflowed\n"); + } + break; + case 3: + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", + raw_data[3], raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); + hid_debug_event(hdev, buff); + if (raw_data[4] == 0) { + snprintf(buff, BUFF_SZ, "\tNo data\n"); + } else if (raw_data[4] + 5 <= raw_size) { + snprintf(buff, BUFF_SZ, "\tData: "); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]); + } else { + snprintf(buff, BUFF_SZ, "\tData overflowed\n"); + } + break; + default: + snprintf(buff, BUFF_SZ, "\tNot supported\n"); + } + hid_debug_event(hdev, buff); + break; + case REPORT_SPLASH_RESTART: + /* TODO */ + break; + case REPORT_EXIT_KEYBOARD: + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_EXIT_KEYBOARD", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n", + raw_data[1] | (raw_data[2] << 8), + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + break; + case REPORT_VERSION: + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_VERSION", report->id, raw_size-1); + hid_debug_event(hdev, buff); + break; + case REPORT_DEVID: + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_DEVID", report->id, raw_size-1); + hid_debug_event(hdev, buff); + break; + case REPORT_SPLASH_SIZE: + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_SPLASH_SIZE", report->id, raw_size-1); + hid_debug_event(hdev, buff); + break; + case REPORT_HOOK_VERSION: + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_HOOK_VERSION", report->id, raw_size-1); + hid_debug_event(hdev, buff); + break; + case REPORT_EXIT_FLASHER: + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "REPORT_VERSION", report->id, raw_size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n", + raw_data[1] | (raw_data[2] << 8), + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + break; + default: + snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", + "", report->id, raw_size-1); + hid_debug_event(hdev, buff); + break; + } + wake_up_interruptible(&hdev->debug_wait); + kfree(buff); +} + +static void picolcd_debug_raw_event(struct picolcd_data *data, + struct hid_device *hdev, struct hid_report *report, + u8 *raw_data, int size) +{ + char *buff; + +#define BUFF_SZ 256 + /* Avoid unnecessary overhead if debugfs is disabled */ + if (!hdev->debug_events) + return; + + buff = kmalloc(BUFF_SZ, GFP_ATOMIC); + if (!buff) + return; + + switch (report->id) { + case REPORT_ERROR_CODE: + /* 2 data bytes with affected report and error code */ + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_ERROR_CODE", report->id, size-1); + hid_debug_event(hdev, buff); + if (raw_data[2] < ARRAY_SIZE(error_codes)) + snprintf(buff, BUFF_SZ, "\tError code 0x%02x (%s) in reply to report 0x%02x\n", + raw_data[2], error_codes[raw_data[2]], raw_data[1]); + else + snprintf(buff, BUFF_SZ, "\tError code 0x%02x in reply to report 0x%02x\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + break; + case REPORT_KEY_STATE: + /* 2 data bytes with key state */ + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_KEY_STATE", report->id, size-1); + hid_debug_event(hdev, buff); + if (raw_data[1] == 0) + snprintf(buff, BUFF_SZ, "\tNo key pressed\n"); + else if (raw_data[2] == 0) + snprintf(buff, BUFF_SZ, "\tOne key pressed: 0x%02x (%d)\n", + raw_data[1], raw_data[1]); + else + snprintf(buff, BUFF_SZ, "\tTwo keys pressed: 0x%02x (%d), 0x%02x (%d)\n", + raw_data[1], raw_data[1], raw_data[2], raw_data[2]); + hid_debug_event(hdev, buff); + break; + case REPORT_IR_DATA: + /* Up to 20 byes of IR scancode data */ + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_IR_DATA", report->id, size-1); + hid_debug_event(hdev, buff); + if (raw_data[1] == 0) { + snprintf(buff, BUFF_SZ, "\tUnexpectedly 0 data length\n"); + hid_debug_event(hdev, buff); + } else if (raw_data[1] + 1 <= size) { + snprintf(buff, BUFF_SZ, "\tData length: %d\n\tIR Data: ", + raw_data[1]-1); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[1]-1); + hid_debug_event(hdev, buff); + } else { + snprintf(buff, BUFF_SZ, "\tOverflowing data length: %d\n", + raw_data[1]-1); + hid_debug_event(hdev, buff); + } + break; + case REPORT_EE_DATA: + /* Data buffer in response to REPORT_EE_READ or REPORT_EE_WRITE */ + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_EE_DATA", report->id, size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); + hid_debug_event(hdev, buff); + if (raw_data[3] == 0) { + snprintf(buff, BUFF_SZ, "\tNo data\n"); + hid_debug_event(hdev, buff); + } else if (raw_data[3] + 4 <= size) { + snprintf(buff, BUFF_SZ, "\tData: "); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); + hid_debug_event(hdev, buff); + } else { + snprintf(buff, BUFF_SZ, "\tData overflowed\n"); + hid_debug_event(hdev, buff); + } + break; + case REPORT_MEMORY: + /* Data buffer in response to REPORT_READ_MEMORY or REPORT_WRTIE_MEMORY */ + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_MEMORY", report->id, size-1); + hid_debug_event(hdev, buff); + switch (data->addr_sz) { + case 2: + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); + hid_debug_event(hdev, buff); + if (raw_data[3] == 0) { + snprintf(buff, BUFF_SZ, "\tNo data\n"); + } else if (raw_data[3] + 4 <= size) { + snprintf(buff, BUFF_SZ, "\tData: "); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); + } else { + snprintf(buff, BUFF_SZ, "\tData overflowed\n"); + } + break; + case 3: + snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", + raw_data[3], raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); + hid_debug_event(hdev, buff); + if (raw_data[4] == 0) { + snprintf(buff, BUFF_SZ, "\tNo data\n"); + } else if (raw_data[4] + 5 <= size) { + snprintf(buff, BUFF_SZ, "\tData: "); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]); + } else { + snprintf(buff, BUFF_SZ, "\tData overflowed\n"); + } + break; + default: + snprintf(buff, BUFF_SZ, "\tNot supported\n"); + } + hid_debug_event(hdev, buff); + break; + case REPORT_VERSION: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_VERSION", report->id, size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n", + raw_data[2], raw_data[1]); + hid_debug_event(hdev, buff); + break; + case REPORT_BL_ERASE_MEMORY: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_BL_ERASE_MEMORY", report->id, size-1); + hid_debug_event(hdev, buff); + /* TODO */ + break; + case REPORT_BL_READ_MEMORY: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_BL_READ_MEMORY", report->id, size-1); + hid_debug_event(hdev, buff); + /* TODO */ + break; + case REPORT_BL_WRITE_MEMORY: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_BL_WRITE_MEMORY", report->id, size-1); + hid_debug_event(hdev, buff); + /* TODO */ + break; + case REPORT_DEVID: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_DEVID", report->id, size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tSerial: 0x%02x%02x%02x%02x\n", + raw_data[1], raw_data[2], raw_data[3], raw_data[4]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tType: 0x%02x\n", + raw_data[5]); + hid_debug_event(hdev, buff); + break; + case REPORT_SPLASH_SIZE: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_SPLASH_SIZE", report->id, size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tTotal splash space: %d\n", + (raw_data[2] << 8) | raw_data[1]); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tUsed splash space: %d\n", + (raw_data[4] << 8) | raw_data[3]); + hid_debug_event(hdev, buff); + break; + case REPORT_HOOK_VERSION: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "REPORT_HOOK_VERSION", report->id, size-1); + hid_debug_event(hdev, buff); + snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n", + raw_data[1], raw_data[2]); + hid_debug_event(hdev, buff); + break; + default: + snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", + "", report->id, size-1); + hid_debug_event(hdev, buff); + break; + } + wake_up_interruptible(&hdev->debug_wait); + kfree(buff); +} +#else +#define picolcd_debug_raw_event(data, hdev, report, raw_data, size) +#endif + +/* + * Handle raw report as sent by device + */ +static int picolcd_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *raw_data, int size) +{ + struct picolcd_data *data = hid_get_drvdata(hdev); + unsigned long flags; + int ret = 0; + + if (!data) + return 1; + + if (report->id == REPORT_KEY_STATE) { + if (data->input_keys) + ret = picolcd_raw_keypad(data, report, raw_data+1, size-1); + } else if (report->id == REPORT_IR_DATA) { + if (data->input_cir) + ret = picolcd_raw_cir(data, report, raw_data+1, size-1); + } else { + spin_lock_irqsave(&data->lock, flags); + /* + * We let the caller of picolcd_send_and_wait() check if the + * report we got is one of the expected ones or not. + */ + if (data->pending) { + memcpy(data->pending->raw_data, raw_data+1, size-1); + data->pending->raw_size = size-1; + data->pending->in_report = report; + complete(&data->pending->ready); + } + spin_unlock_irqrestore(&data->lock, flags); + } + + picolcd_debug_raw_event(data, hdev, report, raw_data, size); + return 1; +} + +/* initialize keypad input device */ +static int picolcd_init_keys(struct picolcd_data *data, + struct hid_report *report) +{ + struct hid_device *hdev = data->hdev; + struct input_dev *idev; + int error, i; + + if (!report) + return -ENODEV; + if (report->maxfield != 1 || report->field[0]->report_count != 2 || + report->field[0]->report_size != 8) { + dev_err(&hdev->dev, "unsupported KEY_STATE report"); + return -EINVAL; + } + + idev = input_allocate_device(); + if (idev == NULL) { + dev_err(&hdev->dev, "failed to allocate input device"); + return -ENOMEM; + } + input_set_drvdata(idev, hdev); + memcpy(data->keycode, def_keymap, sizeof(def_keymap)); + idev->name = hdev->name; + idev->phys = hdev->phys; + idev->uniq = hdev->uniq; + idev->id.bustype = hdev->bus; + idev->id.vendor = hdev->vendor; + idev->id.product = hdev->product; + idev->id.version = hdev->version; + idev->dev.parent = hdev->dev.parent; + idev->keycode = &data->keycode; + idev->keycodemax = PICOLCD_KEYS; + idev->keycodesize = sizeof(data->keycode[0]); + input_set_capability(idev, EV_MSC, MSC_SCAN); + set_bit(EV_REP, idev->evbit); + for (i = 0; i < PICOLCD_KEYS; i++) + input_set_capability(idev, EV_KEY, data->keycode[i]); + error = input_register_device(idev); + if (error) { + dev_err(&hdev->dev, "error registering the input device"); + input_free_device(idev); + return error; + } + data->input_keys = idev; + return 0; +} + +static void picolcd_exit_keys(struct picolcd_data *data) +{ + struct input_dev *idev = data->input_keys; + + data->input_keys = NULL; + if (idev) + input_unregister_device(idev); +} + +/* initialize CIR input device */ +static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report) +{ + /* support not implemented yet */ + return 0; +} + +static inline void picolcd_exit_cir(struct picolcd_data *data) +{ +} + +static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) +{ + struct hid_report *report; + int error; + + error = picolcd_check_version(hdev); + if (error) + return error; + + if (data->version[0] != 0 && data->version[1] != 3) + dev_info(&hdev->dev, "Device with untested firmware revision, " + "please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", + dev_name(&hdev->dev)); + + /* Setup keypad input device */ + error = picolcd_init_keys(data, picolcd_in_report(REPORT_KEY_STATE, hdev)); + if (error) + goto err; + + /* Setup CIR input device */ + error = picolcd_init_cir(data, picolcd_in_report(REPORT_IR_DATA, hdev)); + if (error) + goto err; + +#ifdef CONFIG_DEBUG_FS + report = picolcd_out_report(REPORT_READ_MEMORY, hdev); + if (report && report->maxfield == 1 && report->field[0]->report_size == 8) + data->addr_sz = report->field[0]->report_count - 1; + else + data->addr_sz = -1; +#endif + return 0; +err: + picolcd_exit_cir(data); + picolcd_exit_keys(data); + return error; +} + +static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data) +{ + struct hid_report *report; + int error; + + error = picolcd_check_version(hdev); + if (error) + return error; + + if (data->version[0] != 1 && data->version[1] != 0) + dev_info(&hdev->dev, "Device with untested bootloader revision, " + "please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", + dev_name(&hdev->dev)); + +#ifdef CONFIG_DEBUG_FS + report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev); + if (report && report->maxfield == 1 && report->field[0]->report_size == 8) + data->addr_sz = report->field[0]->report_count - 1; + else + data->addr_sz = -1; +#endif + return 0; +} + +static int picolcd_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct picolcd_data *data; + int error = -ENOMEM; + + dbg_hid(PICOLCD_NAME " hardware probe...\n"); + + /* + * Let's allocate the picolcd data structure, set some reasonable + * defaults, and associate it with the device + */ + data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL); + if (data == NULL) { + dev_err(&hdev->dev, "can't allocate space for Minibox PicoLCD device data\n"); + error = -ENOMEM; + goto err_no_cleanup; + } + + spin_lock_init(&data->lock); + mutex_init(&data->mutex); + data->hdev = hdev; + if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) + data->status |= PICOLCD_BOOTLOADER; + hid_set_drvdata(hdev, data); + + /* Parse the device reports and start it up */ + error = hid_parse(hdev); + if (error) { + dev_err(&hdev->dev, "device report parse failed\n"); + goto err_cleanup_data; + } + + /* We don't use hidinput but hid_hw_start() fails if nothing is + * claimed. So spoof claimed input. */ + hdev->claimed = HID_CLAIMED_INPUT; + error = hid_hw_start(hdev, 0); + hdev->claimed = 0; + if (error) { + dev_err(&hdev->dev, "hardware start failed\n"); + goto err_cleanup_data; + } + + error = hdev->ll_driver->open(hdev); + if (error) { + dev_err(&hdev->dev, "failed to open input interrupt pipe for key and IR events\n"); + goto err_cleanup_hid_hw; + } + + error = device_create_file(&hdev->dev, &dev_attr_operation_mode); + if (error) { + dev_err(&hdev->dev, "failed to create sysfs attributes\n"); + goto err_cleanup_hid_ll; + } + + if (data->status & PICOLCD_BOOTLOADER) + error = picolcd_probe_bootloader(hdev, data); + else + error = picolcd_probe_lcd(hdev, data); + if (error) + goto err_cleanup_sysfs; + + dbg_hid(PICOLCD_NAME " activated and initialized\n"); + return 0; + +err_cleanup_sysfs: + device_remove_file(&hdev->dev, &dev_attr_operation_mode); +err_cleanup_hid_ll: + hdev->ll_driver->close(hdev); +err_cleanup_hid_hw: + hid_hw_stop(hdev); +err_cleanup_data: + kfree(data); +err_no_cleanup: + hid_set_drvdata(hdev, NULL); + + return error; +} + +static void picolcd_remove(struct hid_device *hdev) +{ + struct picolcd_data *data = hid_get_drvdata(hdev); + unsigned long flags; + + dbg_hid(PICOLCD_NAME " hardware remove...\n"); + spin_lock_irqsave(&data->lock, flags); + data->status |= PICOLCD_FAILED; + spin_unlock_irqrestore(&data->lock, flags); + + device_remove_file(&hdev->dev, &dev_attr_operation_mode); + hdev->ll_driver->close(hdev); + hid_hw_stop(hdev); + hid_set_drvdata(hdev, NULL); + + /* Shortcut potential pending reply that will never arrive */ + spin_lock_irqsave(&data->lock, flags); + if (data->pending) + complete(&data->pending->ready); + spin_unlock_irqrestore(&data->lock, flags); + + /* Cleanup input */ + picolcd_exit_cir(data); + picolcd_exit_keys(data); + + mutex_destroy(&data->mutex); + /* Finally, clean up the picolcd data itself */ + kfree(data); +} + +static const struct hid_device_id picolcd_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) }, + { } +}; +MODULE_DEVICE_TABLE(hid, picolcd_devices); + +static struct hid_driver picolcd_driver = { + .name = "hid-picolcd", + .id_table = picolcd_devices, + .probe = picolcd_probe, + .remove = picolcd_remove, + .raw_event = picolcd_raw_event, +}; + +static int __init picolcd_init(void) +{ + return hid_register_driver(&picolcd_driver); +} + +static void __exit picolcd_exit(void) +{ + hid_unregister_driver(&picolcd_driver); +} + +module_init(picolcd_init); +module_exit(picolcd_exit); +MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From b8c21cf697d165999cc21a90e6caa73690ac6190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Tue, 30 Mar 2010 22:34:30 +0200 Subject: HID: add framebuffer support to PicoLCD device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add framebuffer support to PicoLCD device with use of deferred-io. Only changed areas of framebuffer get sent to device in order to save USB bandwidth and especially resources on PicoLCD device or allow higher refresh rate for a small area. Changed tiles are determined while updating shadow framebuffer. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/Documentation/ABI/testing/sysfs-driver-hid-picolcd b/Documentation/ABI/testing/sysfs-driver-hid-picolcd index 6fb4f21..14f52d7 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-picolcd +++ b/Documentation/ABI/testing/sysfs-driver-hid-picolcd @@ -15,3 +15,20 @@ Description: Make it possible to switch the PicoLCD device between LCD disconnected and reconnects after above delay (default value is 5 seconds though this default should not be relied on). + +What: /sys/bus/usb/devices/-:./::./fb_update_rate +Date: March 2010 +Contact: Bruno Prémont +Description: Make it possible to adjust defio refresh rate. + + Reading: returns list of available refresh rates (expressed in Hz), + the active refresh rate being enclosed in brackets ('[' and ']') + + Writing: accepts new refresh rate expressed in integer Hz + within permitted rates. + + Note: As device can barely do 2 complete refreshes a second + it only makes sense to adjust this value if only one or two + tiles get changed and it's not appropriate to expect the application + to flush it's tiny changes explicitely at higher than default rate. + diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 138ba6a..a813ea9 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -265,6 +265,11 @@ config HID_PETALYNX config HID_PICOLCD tristate "PicoLCD (graphic version)" depends on USB_HID + select FB_DEFERRED_IO if FB + select FB_SYS_FILLRECT if FB + select FB_SYS_COPYAREA if FB + select FB_SYS_IMAGEBLIT if FB + select FB_SYS_FOPS if FB ---help--- This provides support for Minibox PicoLCD devices, currently only the graphical ones are supported. @@ -272,8 +277,8 @@ config HID_PICOLCD This includes support for the following device features: - Keypad - Switching between Firmware and Flash mode - Features that are not (yet) supported: - Framebuffer for monochrome 256x64 display + Features that are not (yet) supported: - Backlight control - Contrast control - IR diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index c7855f3..e144647 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -24,6 +24,9 @@ #include "usbhid/usbhid.h" #include +#include +#include + #include #include @@ -69,6 +72,59 @@ #define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */ #define REPORT_EXIT_FLASHER 0xff /* Bootloader: OUT[2] */ +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +/* Framebuffer + * + * The PicoLCD use a Topway LCD module of 256x64 pixel + * This display area is tiled over 4 controllers with 8 tiles + * each. Each tile has 8x64 pixel, each data byte representing + * a 1-bit wide vertical line of the tile. + * + * The display can be updated at a tile granularity. + * + * Chip 1 Chip 2 Chip 3 Chip 4 + * +----------------+----------------+----------------+----------------+ + * | Tile 1 | Tile 1 | Tile 1 | Tile 1 | + * +----------------+----------------+----------------+----------------+ + * | Tile 2 | Tile 2 | Tile 2 | Tile 2 | + * +----------------+----------------+----------------+----------------+ + * ... + * +----------------+----------------+----------------+----------------+ + * | Tile 8 | Tile 8 | Tile 8 | Tile 8 | + * +----------------+----------------+----------------+----------------+ + */ +#define PICOLCDFB_NAME "picolcdfb" +#define PICOLCDFB_WIDTH (256) +#define PICOLCDFB_HEIGHT (64) +#define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8) + +#define PICOLCDFB_UPDATE_RATE_LIMIT 10 +#define PICOLCDFB_UPDATE_RATE_DEFAULT 2 + +/* Framebuffer visual structures */ +static const struct fb_fix_screeninfo picolcdfb_fix = { + .id = PICOLCDFB_NAME, + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_MONO01, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .line_length = PICOLCDFB_WIDTH / 8, + .accel = FB_ACCEL_NONE, +}; + +static const struct fb_var_screeninfo picolcdfb_var = { + .xres = PICOLCDFB_WIDTH, + .yres = PICOLCDFB_HEIGHT, + .xres_virtual = PICOLCDFB_WIDTH, + .yres_virtual = PICOLCDFB_HEIGHT, + .width = 103, + .height = 26, + .bits_per_pixel = 1, + .grayscale = 1, +}; +#endif /* CONFIG_FB */ + /* Input device * * The PicoLCD has an IR receiver header, a built-in keypad with 5 keys @@ -118,6 +174,16 @@ struct picolcd_data { struct input_dev *input_cir; unsigned short keycode[PICOLCD_KEYS]; +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) + /* Framebuffer stuff */ + u8 fb_update_rate; + u8 fb_bpp; + u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */ + u8 *fb_bitmap; /* framebuffer */ + struct fb_info *fb_info; + struct fb_deferred_io fb_defio; +#endif /* CONFIG_FB */ + /* Housekeeping stuff */ spinlock_t lock; struct mutex mutex; @@ -125,6 +191,7 @@ struct picolcd_data { int status; #define PICOLCD_BOOTLOADER 1 #define PICOLCD_FAILED 2 +#define PICOLCD_READY_FB 4 }; @@ -197,6 +264,486 @@ static struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, return work; } +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +/* Send a given tile to PicoLCD */ +static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int tile) +{ + struct picolcd_data *data = hid_get_drvdata(hdev); + struct hid_report *report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, hdev); + struct hid_report *report2 = picolcd_out_report(REPORT_LCD_DATA, hdev); + unsigned long flags; + u8 *tdata; + int i; + + if (!report1 || report1->maxfield != 1 || !report2 || report2->maxfield != 1) + return -ENODEV; + + spin_lock_irqsave(&data->lock, flags); + hid_set_field(report1->field[0], 0, chip << 2); + hid_set_field(report1->field[0], 1, 0x02); + hid_set_field(report1->field[0], 2, 0x00); + hid_set_field(report1->field[0], 3, 0x00); + hid_set_field(report1->field[0], 4, 0xb8 | tile); + hid_set_field(report1->field[0], 5, 0x00); + hid_set_field(report1->field[0], 6, 0x00); + hid_set_field(report1->field[0], 7, 0x40); + hid_set_field(report1->field[0], 8, 0x00); + hid_set_field(report1->field[0], 9, 0x00); + hid_set_field(report1->field[0], 10, 32); + + hid_set_field(report2->field[0], 0, (chip << 2) | 0x01); + hid_set_field(report2->field[0], 1, 0x00); + hid_set_field(report2->field[0], 2, 0x00); + hid_set_field(report2->field[0], 3, 32); + + tdata = data->fb_vbitmap + (tile * 4 + chip) * 64; + for (i = 0; i < 64; i++) + if (i < 32) + hid_set_field(report1->field[0], 11 + i, tdata[i]); + else + hid_set_field(report2->field[0], 4 + i - 32, tdata[i]); + + usbhid_submit_report(data->hdev, report1, USB_DIR_OUT); + usbhid_submit_report(data->hdev, report2, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + return 0; +} + +/* Translate a single tile*/ +static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp, + int chip, int tile) +{ + int i, b, changed = 0; + u8 tdata[64]; + u8 *vdata = vbitmap + (tile * 4 + chip) * 64; + + if (bpp == 1) { + for (b = 7; b >= 0; b--) { + const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32; + for (i = 0; i < 64; i++) { + tdata[i] <<= 1; + tdata[i] |= (bdata[i/8] >> (7 - i % 8)) & 0x01; + } + } + } else if (bpp == 8) { + for (b = 7; b >= 0; b--) { + const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8; + for (i = 0; i < 64; i++) { + tdata[i] <<= 1; + tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00; + } + } + } else { + /* Oops, we should never get here! */ + WARN_ON(1); + return 0; + } + + for (i = 0; i < 64; i++) + if (tdata[i] != vdata[i]) { + changed = 1; + vdata[i] = tdata[i]; + } + return changed; +} + +/* Reconfigure LCD display */ +static int picolcd_fb_reset(struct picolcd_data *data, int clear) +{ + struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev); + int i, j; + unsigned long flags; + static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 }; + + if (!report || report->maxfield != 1) + return -ENODEV; + + spin_lock_irqsave(&data->lock, flags); + for (i = 0; i < 4; i++) { + for (j = 0; j < report->field[0]->maxusage; j++) + if (j == 0) + hid_set_field(report->field[0], j, i << 2); + else if (j < sizeof(mapcmd)) + hid_set_field(report->field[0], j, mapcmd[j]); + else + hid_set_field(report->field[0], j, 0); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + } + + data->status |= PICOLCD_READY_FB; + spin_unlock_irqrestore(&data->lock, flags); + + if (data->fb_bitmap) { + if (clear) { + memset(data->fb_vbitmap, 0xff, PICOLCDFB_SIZE); + memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); + } else { + /* invert 1 byte in each tile to force resend */ + for (i = 0; i < PICOLCDFB_SIZE; i += 64) + data->fb_vbitmap[i] = ~data->fb_vbitmap[i]; + } + } + + /* schedule first output of framebuffer */ + if (data->fb_info) + schedule_delayed_work(&data->fb_info->deferred_work, 0); + + return 0; +} + +/* Update fb_vbitmap from the screen_base and send changed tiles to device */ +static void picolcd_fb_update(struct picolcd_data *data) +{ + int chip, tile, n; + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (!(data->status & PICOLCD_READY_FB)) { + spin_unlock_irqrestore(&data->lock, flags); + picolcd_fb_reset(data, 0); + } else { + spin_unlock_irqrestore(&data->lock, flags); + } + + /* + * Translate the framebuffer into the format needed by the PicoLCD. + * See display layout above. + * Do this one tile after the other and push those tiles that changed. + * + * Wait for our IO to complete as otherwise we might flood the queue! + */ + n = 0; + for (chip = 0; chip < 4; chip++) + for (tile = 0; tile < 8; tile++) + if (picolcd_fb_update_tile(data->fb_vbitmap, + data->fb_bitmap, data->fb_bpp, chip, tile)) { + n += 2; + if (n >= HID_OUTPUT_FIFO_SIZE / 2) { + usbhid_wait_io(data->hdev); + n = 0; + } + picolcd_fb_send_tile(data->hdev, chip, tile); + } + if (n) + usbhid_wait_io(data->hdev); +} + +/* Stub to call the system default and update the image on the picoLCD */ +static void picolcd_fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + if (!info->par) + return; + sys_fillrect(info, rect); + + schedule_delayed_work(&info->deferred_work, 0); +} + +/* Stub to call the system default and update the image on the picoLCD */ +static void picolcd_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + if (!info->par) + return; + sys_copyarea(info, area); + + schedule_delayed_work(&info->deferred_work, 0); +} + +/* Stub to call the system default and update the image on the picoLCD */ +static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + if (!info->par) + return; + sys_imageblit(info, image); + + schedule_delayed_work(&info->deferred_work, 0); +} + +/* + * this is the slow path from userspace. they can seek and write to + * the fb. it's inefficient to do anything less than a full screen draw + */ +static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + if (!info->par) + return -ENODEV; + ret = fb_sys_write(info, buf, count, ppos); + if (ret >= 0) + schedule_delayed_work(&info->deferred_work, 0); + return ret; +} + +static int picolcd_fb_blank(int blank, struct fb_info *info) +{ + if (!info->par) + return -ENODEV; + /* We let fb notification do this for us via lcd/backlight device */ + return 0; +} + +static void picolcd_fb_destroy(struct fb_info *info) +{ + struct picolcd_data *data = info->par; + info->par = NULL; + if (data) + data->fb_info = NULL; + fb_deferred_io_cleanup(info); + framebuffer_release(info); +} + +static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + __u32 bpp = var->bits_per_pixel; + __u32 activate = var->activate; + + /* only allow 1/8 bit depth (8-bit is grayscale) */ + *var = picolcdfb_var; + var->activate = activate; + if (bpp >= 8) + var->bits_per_pixel = 8; + else + var->bits_per_pixel = 1; + return 0; +} + +static int picolcd_set_par(struct fb_info *info) +{ + struct picolcd_data *data = info->par; + u8 *o_fb, *n_fb; + if (info->var.bits_per_pixel == data->fb_bpp) + return 0; + /* switch between 1/8 bit depths */ + if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8) + return -EINVAL; + + o_fb = data->fb_bitmap; + n_fb = vmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel); + if (!n_fb) + return -ENOMEM; + + fb_deferred_io_cleanup(info); + /* translate FB content to new bits-per-pixel */ + if (info->var.bits_per_pixel == 1) { + int i, b; + for (i = 0; i < PICOLCDFB_SIZE; i++) { + u8 p = 0; + for (b = 0; b < 8; b++) { + p <<= 1; + p |= o_fb[i*8+b] ? 0x01 : 0x00; + } + } + info->fix.visual = FB_VISUAL_MONO01; + info->fix.line_length = PICOLCDFB_WIDTH / 8; + } else { + int i; + for (i = 0; i < PICOLCDFB_SIZE * 8; i++) + n_fb[i] = o_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = PICOLCDFB_WIDTH; + } + + data->fb_bitmap = n_fb; + data->fb_bpp = info->var.bits_per_pixel; + info->screen_base = (char __force __iomem *)n_fb; + info->fix.smem_start = (unsigned long)n_fb; + info->fix.smem_len = PICOLCDFB_SIZE*data->fb_bpp; + fb_deferred_io_init(info); + vfree(o_fb); + return 0; +} + +/* Note this can't be const because of struct fb_info definition */ +static struct fb_ops picolcdfb_ops = { + .owner = THIS_MODULE, + .fb_destroy = picolcd_fb_destroy, + .fb_read = fb_sys_read, + .fb_write = picolcd_fb_write, + .fb_blank = picolcd_fb_blank, + .fb_fillrect = picolcd_fb_fillrect, + .fb_copyarea = picolcd_fb_copyarea, + .fb_imageblit = picolcd_fb_imageblit, + .fb_check_var = picolcd_fb_check_var, + .fb_set_par = picolcd_set_par, +}; + + +/* Callback from deferred IO workqueue */ +static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist) +{ + picolcd_fb_update(info->par); +} + +static const struct fb_deferred_io picolcd_fb_defio = { + .delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT, + .deferred_io = picolcd_fb_deferred_io, +}; + + +/* + * The "fb_update_rate" sysfs attribute + */ +static ssize_t picolcd_fb_update_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct picolcd_data *data = dev_get_drvdata(dev); + unsigned i, fb_update_rate = data->fb_update_rate; + size_t ret = 0; + + for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++) + if (ret >= PAGE_SIZE) + break; + else if (i == fb_update_rate) + ret += snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i); + else + ret += snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i); + if (ret > 0) + buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n'; + return ret; +} + +static ssize_t picolcd_fb_update_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct picolcd_data *data = dev_get_drvdata(dev); + int i; + unsigned u; + + if (count < 1 || count > 10) + return -EINVAL; + + i = sscanf(buf, "%u", &u); + if (i != 1) + return -EINVAL; + + if (u > PICOLCDFB_UPDATE_RATE_LIMIT) + return -ERANGE; + else if (u == 0) + u = PICOLCDFB_UPDATE_RATE_DEFAULT; + + data->fb_update_rate = u; + data->fb_defio.delay = HZ / data->fb_update_rate; + return count; +} + +static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show, + picolcd_fb_update_rate_store); + +/* initialize Framebuffer device */ +static int picolcd_init_framebuffer(struct picolcd_data *data) +{ + struct device *dev = &data->hdev->dev; + struct fb_info *info = NULL; + int error = -ENOMEM; + u8 *fb_vbitmap = NULL; + u8 *fb_bitmap = NULL; + + fb_bitmap = vmalloc(PICOLCDFB_SIZE*picolcdfb_var.bits_per_pixel); + if (fb_bitmap == NULL) { + dev_err(dev, "can't get a free page for framebuffer\n"); + goto err_nomem; + } + + fb_vbitmap = kmalloc(PICOLCDFB_SIZE, GFP_KERNEL); + if (fb_vbitmap == NULL) { + dev_err(dev, "can't alloc vbitmap image buffer\n"); + goto err_nomem; + } + + data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT; + data->fb_defio = picolcd_fb_defio; + info = framebuffer_alloc(0, dev); + if (info == NULL) { + dev_err(dev, "failed to allocate a framebuffer\n"); + goto err_nomem; + } + + info->fbdefio = &data->fb_defio; + info->screen_base = (char __force __iomem *)fb_bitmap; + info->fbops = &picolcdfb_ops; + info->var = picolcdfb_var; + info->fix = picolcdfb_fix; + info->fix.smem_len = PICOLCDFB_SIZE; + info->fix.smem_start = (unsigned long)fb_bitmap; + info->par = data; + info->flags = FBINFO_FLAG_DEFAULT; + + data->fb_vbitmap = fb_vbitmap; + data->fb_bitmap = fb_bitmap; + data->fb_bpp = picolcdfb_var.bits_per_pixel; + error = picolcd_fb_reset(data, 1); + if (error) { + dev_err(dev, "failed to configure display\n"); + goto err_cleanup; + } + error = device_create_file(dev, &dev_attr_fb_update_rate); + if (error) { + dev_err(dev, "failed to create sysfs attributes\n"); + goto err_cleanup; + } + data->fb_info = info; + error = register_framebuffer(info); + if (error) { + dev_err(dev, "failed to register framebuffer\n"); + goto err_sysfs; + } + fb_deferred_io_init(info); + /* schedule first output of framebuffer */ + schedule_delayed_work(&info->deferred_work, 0); + return 0; + +err_sysfs: + device_remove_file(dev, &dev_attr_fb_update_rate); +err_cleanup: + data->fb_vbitmap = NULL; + data->fb_bitmap = NULL; + data->fb_bpp = 0; + data->fb_info = NULL; + +err_nomem: + framebuffer_release(info); + vfree(fb_bitmap); + kfree(fb_vbitmap); + return error; +} + +static void picolcd_exit_framebuffer(struct picolcd_data *data) +{ + struct fb_info *info = data->fb_info; + u8 *fb_vbitmap = data->fb_vbitmap; + u8 *fb_bitmap = data->fb_bitmap; + + if (!info) + return; + + data->fb_vbitmap = NULL; + data->fb_bitmap = NULL; + data->fb_bpp = 0; + data->fb_info = NULL; + device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); + fb_deferred_io_cleanup(info); + unregister_framebuffer(info); + vfree(fb_bitmap); + kfree(fb_vbitmap); +} + + +#else +static inline int picolcd_fb_reset(struct picolcd_data *data, int clear) +{ + return 0; +} +static inline int picolcd_init_framebuffer(struct picolcd_data *data) +{ + return 0; +} +static void picolcd_exit_framebuffer(struct picolcd_data *data) +{ +} +#endif /* CONFIG_FB */ + /* * input class device */ @@ -314,6 +861,7 @@ static int picolcd_reset(struct hid_device *hdev) struct picolcd_data *data = hid_get_drvdata(hdev); struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev); unsigned long flags; + int error; if (!data || !report || report->maxfield != 1) return -ENODEV; @@ -327,7 +875,16 @@ static int picolcd_reset(struct hid_device *hdev) usbhid_submit_report(hdev, report, USB_DIR_OUT); spin_unlock_irqrestore(&data->lock, flags); - return picolcd_check_version(hdev); + error = picolcd_check_version(hdev); + if (error) + return error; + +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) + if (data->fb_info) + schedule_delayed_work(&data->fb_info->deferred_work, 0); +#endif /* CONFIG_FB */ + + return 0; } /* @@ -1005,6 +1562,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) if (error) goto err; + /* Set up the framebuffer device */ + error = picolcd_init_framebuffer(data); + if (error) + goto err; + #ifdef CONFIG_DEBUG_FS report = picolcd_out_report(REPORT_READ_MEMORY, hdev); if (report && report->maxfield == 1 && report->field[0]->report_size == 8) @@ -1014,6 +1576,7 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) #endif return 0; err: + picolcd_exit_framebuffer(data); picolcd_exit_cir(data); picolcd_exit_keys(data); return error; @@ -1143,6 +1706,8 @@ static void picolcd_remove(struct hid_device *hdev) complete(&data->pending->ready); spin_unlock_irqrestore(&data->lock, flags); + /* Clean up the framebuffer */ + picolcd_exit_framebuffer(data); /* Cleanup input */ picolcd_exit_cir(data); picolcd_exit_keys(data); diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 56d06cd..3e7909b 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -623,6 +623,7 @@ int usbhid_wait_io(struct hid_device *hid) return 0; } +EXPORT_SYMBOL_GPL(usbhid_wait_io); static int hid_set_idle(struct usb_device *dev, int ifnum, int report, int idle) { -- cgit v0.10.2 From f1c21761408c968ed1deb8f54fd60be9471999c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Tue, 30 Mar 2010 22:35:27 +0200 Subject: HID: add backlight support to PicoLCD device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add backlight support to PicoLCD device. Backlight support depends on backlight class and is only being compiled if backlight class has been selected. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a813ea9..588b9ac 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -278,8 +278,8 @@ config HID_PICOLCD - Keypad - Switching between Firmware and Flash mode - Framebuffer for monochrome 256x64 display + - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE) Features that are not (yet) supported: - - Backlight control - Contrast control - IR - General purpose outputs diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index e144647..0ea7a3f 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -183,6 +184,11 @@ struct picolcd_data { struct fb_info *fb_info; struct fb_deferred_io fb_defio; #endif /* CONFIG_FB */ +#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) + struct backlight_device *backlight; + u8 lcd_brightness; + u8 lcd_power; +#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ /* Housekeeping stuff */ spinlock_t lock; @@ -729,7 +735,7 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data) kfree(fb_vbitmap); } - +#define picolcd_fbinfo(d) ((d)->fb_info) #else static inline int picolcd_fb_reset(struct picolcd_data *data, int clear) { @@ -742,8 +748,107 @@ static inline int picolcd_init_framebuffer(struct picolcd_data *data) static void picolcd_exit_framebuffer(struct picolcd_data *data) { } +#define picolcd_fbinfo(d) NULL #endif /* CONFIG_FB */ +#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) +/* + * backlight class device + */ +static int picolcd_get_brightness(struct backlight_device *bdev) +{ + struct picolcd_data *data = bl_get_data(bdev); + return data->lcd_brightness; +} + +static int picolcd_set_brightness(struct backlight_device *bdev) +{ + struct picolcd_data *data = bl_get_data(bdev); + struct hid_report *report = picolcd_out_report(REPORT_BRIGHTNESS, data->hdev); + unsigned long flags; + + if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) + return -ENODEV; + + data->lcd_brightness = bdev->props.brightness & 0x0ff; + data->lcd_power = bdev->props.power; + spin_lock_irqsave(&data->lock, flags); + hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + return 0; +} + +static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb) +{ + return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev)); +} + +static const struct backlight_ops picolcd_blops = { + .update_status = picolcd_set_brightness, + .get_brightness = picolcd_get_brightness, + .check_fb = picolcd_check_bl_fb, +}; + +static int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report) +{ + struct device *dev = &data->hdev->dev; + struct backlight_device *bdev; + struct backlight_properties props; + if (!report) + return -ENODEV; + if (report->maxfield != 1 || report->field[0]->report_count != 1 || + report->field[0]->report_size != 8) { + dev_err(dev, "unsupported BRIGHTNESS report"); + return -EINVAL; + } + + memset(&props, 0, sizeof(props)); + props.max_brightness = 0xff; + bdev = backlight_device_register(dev_name(dev), dev, data, + &picolcd_blops, &props); + if (IS_ERR(bdev)) { + dev_err(dev, "failed to register backlight\n"); + return PTR_ERR(bdev); + } + bdev->props.brightness = 0xff; + data->lcd_brightness = 0xff; + data->backlight = bdev; + picolcd_set_brightness(bdev); + return 0; +} + +static void picolcd_exit_backlight(struct picolcd_data *data) +{ + struct backlight_device *bdev = data->backlight; + + data->backlight = NULL; + if (bdev) + backlight_device_unregister(bdev); +} + +static inline int picolcd_resume_backlight(struct picolcd_data *data) +{ + if (!data->backlight) + return 0; + return picolcd_set_brightness(data->backlight); +} + +#else +static inline int picolcd_init_backlight(struct picolcd_data *data, + struct hid_report *report) +{ + return 0; +} +static inline void picolcd_exit_backlight(struct picolcd_data *data) +{ +} +static inline int picolcd_resume_backlight(struct picolcd_data *data) +{ + return 0; +} +#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ + /* * input class device */ @@ -879,6 +984,7 @@ static int picolcd_reset(struct hid_device *hdev) if (error) return error; + picolcd_resume_backlight(data); #if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) if (data->fb_info) schedule_delayed_work(&data->fb_info->deferred_work, 0); @@ -1567,6 +1673,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) if (error) goto err; + /* Setup backlight class device */ + error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev)); + if (error) + goto err; + #ifdef CONFIG_DEBUG_FS report = picolcd_out_report(REPORT_READ_MEMORY, hdev); if (report && report->maxfield == 1 && report->field[0]->report_size == 8) @@ -1576,6 +1687,7 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) #endif return 0; err: + picolcd_exit_backlight(data); picolcd_exit_framebuffer(data); picolcd_exit_cir(data); picolcd_exit_keys(data); @@ -1707,6 +1819,7 @@ static void picolcd_remove(struct hid_device *hdev) spin_unlock_irqrestore(&data->lock, flags); /* Clean up the framebuffer */ + picolcd_exit_backlight(data); picolcd_exit_framebuffer(data); /* Cleanup input */ picolcd_exit_cir(data); -- cgit v0.10.2 From e8d931bb5977a5b36cc8e9b0fd2f26cb80a6c207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Tue, 30 Mar 2010 22:36:07 +0200 Subject: HID: add lcd support to PicoLCD device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add lcd support to PicoLCD device. LCD support depends on lcd class and is only being compiled if lcd class has been selected. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 588b9ac..399edc5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -279,8 +279,8 @@ config HID_PICOLCD - Switching between Firmware and Flash mode - Framebuffer for monochrome 256x64 display - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE) + - Contrast control (needs CONFIG_LCD_CLASS_DEVICE) Features that are not (yet) supported: - - Contrast control - IR - General purpose outputs - EEProm / Flash access diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 0ea7a3f..99a4883 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -184,6 +185,10 @@ struct picolcd_data { struct fb_info *fb_info; struct fb_deferred_io fb_defio; #endif /* CONFIG_FB */ +#if defined(CONFIG_LCD_CLASS_DEVICE) || defined(CONFIG_LCD_CLASS_DEVICE_MODULE) + struct lcd_device *lcd; + u8 lcd_contrast; +#endif #if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) struct backlight_device *backlight; u8 lcd_brightness; @@ -849,6 +854,99 @@ static inline int picolcd_resume_backlight(struct picolcd_data *data) } #endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ +#if defined(CONFIG_LCD_CLASS_DEVICE) || defined(CONFIG_LCD_CLASS_DEVICE_MODULE) +/* + * lcd class device + */ +static int picolcd_get_contrast(struct lcd_device *ldev) +{ + struct picolcd_data *data = lcd_get_data(ldev); + return data->lcd_contrast; +} + +static int picolcd_set_contrast(struct lcd_device *ldev, int contrast) +{ + struct picolcd_data *data = lcd_get_data(ldev); + struct hid_report *report = picolcd_out_report(REPORT_CONTRAST, data->hdev); + unsigned long flags; + + if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) + return -ENODEV; + + data->lcd_contrast = contrast & 0x0ff; + spin_lock_irqsave(&data->lock, flags); + hid_set_field(report->field[0], 0, data->lcd_contrast); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + return 0; +} + +static int picolcd_check_lcd_fb(struct lcd_device *ldev, struct fb_info *fb) +{ + return fb && fb == picolcd_fbinfo((struct picolcd_data *)lcd_get_data(ldev)); +} + +static struct lcd_ops picolcd_lcdops = { + .get_contrast = picolcd_get_contrast, + .set_contrast = picolcd_set_contrast, + .check_fb = picolcd_check_lcd_fb, +}; + +static int picolcd_init_lcd(struct picolcd_data *data, struct hid_report *report) +{ + struct device *dev = &data->hdev->dev; + struct lcd_device *ldev; + + if (!report) + return -ENODEV; + if (report->maxfield != 1 || report->field[0]->report_count != 1 || + report->field[0]->report_size != 8) { + dev_err(dev, "unsupported CONTRAST report"); + return -EINVAL; + } + + ldev = lcd_device_register(dev_name(dev), dev, data, &picolcd_lcdops); + if (IS_ERR(ldev)) { + dev_err(dev, "failed to register LCD\n"); + return PTR_ERR(ldev); + } + ldev->props.max_contrast = 0x0ff; + data->lcd_contrast = 0xe5; + data->lcd = ldev; + picolcd_set_contrast(ldev, 0xe5); + return 0; +} + +static void picolcd_exit_lcd(struct picolcd_data *data) +{ + struct lcd_device *ldev = data->lcd; + + data->lcd = NULL; + if (ldev) + lcd_device_unregister(ldev); +} + +static inline int picolcd_resume_lcd(struct picolcd_data *data) +{ + if (!data->lcd) + return 0; + return picolcd_set_contrast(data->lcd, data->lcd_contrast); +} +#else +static inline int picolcd_init_lcd(struct picolcd_data *data, + struct hid_report *report) +{ + return 0; +} +static inline void picolcd_exit_lcd(struct picolcd_data *data) +{ +} +static inline int picolcd_resume_lcd(struct picolcd_data *data) +{ + return 0; +} +#endif /* CONFIG_LCD_CLASS_DEVICE */ + /* * input class device */ @@ -984,6 +1082,7 @@ static int picolcd_reset(struct hid_device *hdev) if (error) return error; + picolcd_resume_lcd(data); picolcd_resume_backlight(data); #if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) if (data->fb_info) @@ -1673,6 +1772,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) if (error) goto err; + /* Setup lcd class device */ + error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev)); + if (error) + goto err; + /* Setup backlight class device */ error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev)); if (error) @@ -1688,6 +1792,7 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) return 0; err: picolcd_exit_backlight(data); + picolcd_exit_lcd(data); picolcd_exit_framebuffer(data); picolcd_exit_cir(data); picolcd_exit_keys(data); @@ -1820,6 +1925,7 @@ static void picolcd_remove(struct hid_device *hdev) /* Clean up the framebuffer */ picolcd_exit_backlight(data); + picolcd_exit_lcd(data); picolcd_exit_framebuffer(data); /* Cleanup input */ picolcd_exit_cir(data); -- cgit v0.10.2 From 467d6523065187d4c081b078755da4103d7ffacb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Tue, 30 Mar 2010 22:36:49 +0200 Subject: HID: add GPO (leds) support to PicoLCD device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add leds support to PicoLCD device to drive the GPO pins. GPO support depends on leds class and is only being compiled if leds class has been selected. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 399edc5..34f6593 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -278,11 +278,11 @@ config HID_PICOLCD - Keypad - Switching between Firmware and Flash mode - Framebuffer for monochrome 256x64 display - - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE) - - Contrast control (needs CONFIG_LCD_CLASS_DEVICE) + - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE) + - Contrast control (needs CONFIG_LCD_CLASS_DEVICE) + - General purpose outputs (needs CONFIG_LEDS_CLASS) Features that are not (yet) supported: - IR - - General purpose outputs - EEProm / Flash access config HID_QUANTA diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 99a4883..5176773 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -29,6 +29,8 @@ #include #include +#include + #include #include @@ -194,6 +196,11 @@ struct picolcd_data { u8 lcd_brightness; u8 lcd_power; #endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ +#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) + /* LED stuff */ + u8 led_state; + struct led_classdev *led[8]; +#endif /* CONFIG_LEDS_CLASS */ /* Housekeeping stuff */ spinlock_t lock; @@ -947,6 +954,153 @@ static inline int picolcd_resume_lcd(struct picolcd_data *data) } #endif /* CONFIG_LCD_CLASS_DEVICE */ +#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +/** + * LED class device + */ +static void picolcd_leds_set(struct picolcd_data *data) +{ + struct hid_report *report; + unsigned long flags; + + if (!data->led[0]) + return; + report = picolcd_out_report(REPORT_LED_STATE, data->hdev); + if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) + return; + + spin_lock_irqsave(&data->lock, flags); + hid_set_field(report->field[0], 0, data->led_state); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); +} + +static void picolcd_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct device *dev; + struct hid_device *hdev; + struct picolcd_data *data; + int i, state = 0; + + dev = led_cdev->dev->parent; + hdev = container_of(dev, struct hid_device, dev); + data = hid_get_drvdata(hdev); + for (i = 0; i < 8; i++) { + if (led_cdev != data->led[i]) + continue; + state = (data->led_state >> i) & 1; + if (value == LED_OFF && state) { + data->led_state &= ~(1 << i); + picolcd_leds_set(data); + } else if (value != LED_OFF && !state) { + data->led_state |= 1 << i; + picolcd_leds_set(data); + } + break; + } +} + +static enum led_brightness picolcd_led_get_brightness(struct led_classdev *led_cdev) +{ + struct device *dev; + struct hid_device *hdev; + struct picolcd_data *data; + int i, value = 0; + + dev = led_cdev->dev->parent; + hdev = container_of(dev, struct hid_device, dev); + data = hid_get_drvdata(hdev); + for (i = 0; i < 8; i++) + if (led_cdev == data->led[i]) { + value = (data->led_state >> i) & 1; + break; + } + return value ? LED_FULL : LED_OFF; +} + +static int picolcd_init_leds(struct picolcd_data *data, struct hid_report *report) +{ + struct device *dev = &data->hdev->dev; + struct led_classdev *led; + size_t name_sz = strlen(dev_name(dev)) + 8; + char *name; + int i, ret = 0; + + if (!report) + return -ENODEV; + if (report->maxfield != 1 || report->field[0]->report_count != 1 || + report->field[0]->report_size != 8) { + dev_err(dev, "unsupported LED_STATE report"); + return -EINVAL; + } + + for (i = 0; i < 8; i++) { + led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + if (!led) { + dev_err(dev, "can't allocate memory for LED %d\n", i); + ret = -ENOMEM; + goto err; + } + name = (void *)(&led[1]); + snprintf(name, name_sz, "%s::GPO%d", dev_name(dev), i); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = picolcd_led_get_brightness; + led->brightness_set = picolcd_led_set_brightness; + + data->led[i] = led; + ret = led_classdev_register(dev, data->led[i]); + if (ret) { + data->led[i] = NULL; + kfree(led); + dev_err(dev, "can't register LED %d\n", i); + goto err; + } + } + return 0; +err: + for (i = 0; i < 8; i++) + if (data->led[i]) { + led = data->led[i]; + data->led[i] = NULL; + led_classdev_unregister(led); + kfree(led); + } + return ret; +} + +static void picolcd_exit_leds(struct picolcd_data *data) +{ + struct led_classdev *led; + int i; + + for (i = 0; i < 8; i++) { + led = data->led[i]; + data->led[i] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } +} + +#else +static inline int picolcd_init_leds(struct picolcd_data *data, + struct hid_report *report) +{ + return 0; +} +static void picolcd_exit_leds(struct picolcd_data *data) +{ +} +static inline int picolcd_leds_set(struct picolcd_data *data) +{ + return 0; +} +#endif /* CONFIG_LEDS_CLASS */ + /* * input class device */ @@ -1089,6 +1243,7 @@ static int picolcd_reset(struct hid_device *hdev) schedule_delayed_work(&data->fb_info->deferred_work, 0); #endif /* CONFIG_FB */ + picolcd_leds_set(data); return 0; } @@ -1782,6 +1937,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) if (error) goto err; + /* Setup the LED class devices */ + error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev)); + if (error) + goto err; + #ifdef CONFIG_DEBUG_FS report = picolcd_out_report(REPORT_READ_MEMORY, hdev); if (report && report->maxfield == 1 && report->field[0]->report_size == 8) @@ -1791,6 +1951,7 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) #endif return 0; err: + picolcd_exit_leds(data); picolcd_exit_backlight(data); picolcd_exit_lcd(data); picolcd_exit_framebuffer(data); @@ -1923,6 +2084,8 @@ static void picolcd_remove(struct hid_device *hdev) complete(&data->pending->ready); spin_unlock_irqrestore(&data->lock, flags); + /* Cleanup LED */ + picolcd_exit_leds(data); /* Clean up the framebuffer */ picolcd_exit_backlight(data); picolcd_exit_lcd(data); -- cgit v0.10.2 From 9bbf2b98ba11d00bd73e3254e15cfe17ccaff6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Tue, 30 Mar 2010 22:38:09 +0200 Subject: HID: add experimental access to PicoLCD device's EEPROM and FLASH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PicoLCD device has a small amount of EEPROM and also provides access to its FLASH where firmware and splash image are saved. In flasher mode FLASH access is the only active feature. Give read/write access to both via debugfs files. NOTE: EEPROM and FLASH access should be switched to better suited API, until then the will reside in debugfs Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 34f6593..a2ecd83 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -281,9 +281,9 @@ config HID_PICOLCD - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE) - Contrast control (needs CONFIG_LCD_CLASS_DEVICE) - General purpose outputs (needs CONFIG_LEDS_CLASS) + - EEProm / Flash access (via debugfs) Features that are not (yet) supported: - IR - - EEProm / Flash access config HID_QUANTA tristate "Quanta Optical Touch" diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 5176773..66f9cfd 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -169,6 +169,10 @@ struct picolcd_pending { struct picolcd_data { struct hid_device *hdev; #ifdef CONFIG_DEBUG_FS + struct dentry *debug_reset; + struct dentry *debug_eeprom; + struct dentry *debug_flash; + struct mutex mutex_flash; int addr_sz; #endif u8 version[2]; @@ -1314,6 +1318,357 @@ static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show, #ifdef CONFIG_DEBUG_FS /* + * The "reset" file + */ +static int picolcd_debug_reset_show(struct seq_file *f, void *p) +{ + if (picolcd_fbinfo((struct picolcd_data *)f->private)) + seq_printf(f, "all fb\n"); + else + seq_printf(f, "all\n"); + return 0; +} + +static int picolcd_debug_reset_open(struct inode *inode, struct file *f) +{ + return single_open(f, picolcd_debug_reset_show, inode->i_private); +} + +static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct picolcd_data *data = ((struct seq_file *)f->private_data)->private; + char buf[32]; + size_t cnt = min(count, sizeof(buf)-1); + if (copy_from_user(buf, user_buf, cnt)) + return -EFAULT; + + while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n')) + cnt--; + buf[cnt] = '\0'; + if (strcmp(buf, "all") == 0) { + picolcd_reset(data->hdev); + picolcd_fb_reset(data, 1); + } else if (strcmp(buf, "fb") == 0) { + picolcd_fb_reset(data, 1); + } else { + return -EINVAL; + } + return count; +} + +static const struct file_operations picolcd_debug_reset_fops = { + .owner = THIS_MODULE, + .open = picolcd_debug_reset_open, + .read = seq_read, + .llseek = seq_lseek, + .write = picolcd_debug_reset_write, + .release = single_release, +}; + +/* + * The "eeprom" file + */ +static int picolcd_debug_eeprom_open(struct inode *i, struct file *f) +{ + f->private_data = i->i_private; + return 0; +} + +static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u, + size_t s, loff_t *off) +{ + struct picolcd_data *data = f->private_data; + struct picolcd_pending *resp; + u8 raw_data[3]; + ssize_t ret = -EIO; + + if (s == 0) + return -EINVAL; + if (*off > 0x0ff) + return 0; + + /* prepare buffer with info about what we want to read (addr & len) */ + raw_data[0] = *off & 0xff; + raw_data[1] = (*off >> 8) && 0xff; + raw_data[2] = s < 20 ? s : 20; + if (*off + raw_data[2] > 0xff) + raw_data[2] = 0x100 - *off; + resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data, + sizeof(raw_data)); + if (!resp) + return -EIO; + + if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) { + /* successful read :) */ + ret = resp->raw_data[2]; + if (ret > s) + ret = s; + if (copy_to_user(u, resp->raw_data+3, ret)) + ret = -EFAULT; + else + *off += ret; + } /* anything else is some kind of IO error */ + + kfree(resp); + return ret; +} + +static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u, + size_t s, loff_t *off) +{ + struct picolcd_data *data = f->private_data; + struct picolcd_pending *resp; + ssize_t ret = -EIO; + u8 raw_data[23]; + + if (s == 0) + return -EINVAL; + if (*off > 0x0ff) + return -ENOSPC; + + memset(raw_data, 0, sizeof(raw_data)); + raw_data[0] = *off & 0xff; + raw_data[1] = (*off >> 8) && 0xff; + raw_data[2] = s < 20 ? s : 20; + if (*off + raw_data[2] > 0xff) + raw_data[2] = 0x100 - *off; + + if (copy_from_user(raw_data+3, u, raw_data[2])) + return -EFAULT; + resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data, + sizeof(raw_data)); + + if (!resp) + return -EIO; + + if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) { + /* check if written data matches */ + if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) { + *off += raw_data[2]; + ret = raw_data[2]; + } + } + kfree(resp); + return ret; +} + +/* + * Notes: + * - read/write happens in chunks of at most 20 bytes, it's up to userspace + * to loop in order to get more data. + * - on write errors on otherwise correct write request the bytes + * that should have been written are in undefined state. + */ +static const struct file_operations picolcd_debug_eeprom_fops = { + .owner = THIS_MODULE, + .open = picolcd_debug_eeprom_open, + .read = picolcd_debug_eeprom_read, + .write = picolcd_debug_eeprom_write, + .llseek = generic_file_llseek, +}; + +/* + * The "flash" file + */ +static int picolcd_debug_flash_open(struct inode *i, struct file *f) +{ + f->private_data = i->i_private; + return 0; +} + +/* record a flash address to buf (bounds check to be done by caller) */ +static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off) +{ + buf[0] = off & 0xff; + buf[1] = (off >> 8) & 0xff; + if (data->addr_sz == 3) + buf[2] = (off >> 16) & 0xff; + return data->addr_sz == 2 ? 2 : 3; +} + +/* read a given size of data (bounds check to be done by caller) */ +static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id, + char __user *u, size_t s, loff_t *off) +{ + struct picolcd_pending *resp; + u8 raw_data[4]; + ssize_t ret = 0; + int len_off, err = -EIO; + + while (s > 0) { + err = -EIO; + len_off = _picolcd_flash_setaddr(data, raw_data, *off); + raw_data[len_off] = s > 32 ? 32 : s; + resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1); + if (!resp || !resp->in_report) + goto skip; + if (resp->in_report->id == REPORT_MEMORY || + resp->in_report->id == REPORT_BL_READ_MEMORY) { + if (memcmp(raw_data, resp->raw_data, len_off+1) != 0) + goto skip; + if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) { + err = -EFAULT; + goto skip; + } + *off += raw_data[len_off]; + s -= raw_data[len_off]; + ret += raw_data[len_off]; + err = 0; + } +skip: + kfree(resp); + if (err) + return ret > 0 ? ret : err; + } + return ret; +} + +static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u, + size_t s, loff_t *off) +{ + struct picolcd_data *data = f->private_data; + + if (s == 0) + return -EINVAL; + if (*off > 0x05fff) + return 0; + if (*off + s > 0x05fff) + s = 0x06000 - *off; + + if (data->status & PICOLCD_BOOTLOADER) + return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off); + else + return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off); +} + +/* erase block aligned to 64bytes boundary */ +static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id, + loff_t *off) +{ + struct picolcd_pending *resp; + u8 raw_data[3]; + int len_off; + ssize_t ret = -EIO; + + if (*off & 0x3f) + return -EINVAL; + + len_off = _picolcd_flash_setaddr(data, raw_data, *off); + resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off); + if (!resp || !resp->in_report) + goto skip; + if (resp->in_report->id == REPORT_MEMORY || + resp->in_report->id == REPORT_BL_ERASE_MEMORY) { + if (memcmp(raw_data, resp->raw_data, len_off) != 0) + goto skip; + ret = 0; + } +skip: + kfree(resp); + return ret; +} + +/* write a given size of data (bounds check to be done by caller) */ +static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id, + const char __user *u, size_t s, loff_t *off) +{ + struct picolcd_pending *resp; + u8 raw_data[36]; + ssize_t ret = 0; + int len_off, err = -EIO; + + while (s > 0) { + err = -EIO; + len_off = _picolcd_flash_setaddr(data, raw_data, *off); + raw_data[len_off] = s > 32 ? 32 : s; + if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) { + err = -EFAULT; + goto skip; + } + resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, + len_off+1+raw_data[len_off]); + if (!resp || !resp->in_report) + goto skip; + if (resp->in_report->id == REPORT_MEMORY || + resp->in_report->id == REPORT_BL_WRITE_MEMORY) { + if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0) + goto skip; + *off += raw_data[len_off]; + s -= raw_data[len_off]; + ret += raw_data[len_off]; + err = 0; + } +skip: + kfree(resp); + if (err) + break; + } + return ret > 0 ? ret : err; +} + +static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u, + size_t s, loff_t *off) +{ + struct picolcd_data *data = f->private_data; + ssize_t err, ret = 0; + int report_erase, report_write; + + if (s == 0) + return -EINVAL; + if (*off > 0x5fff) + return -ENOSPC; + if (s & 0x3f) + return -EINVAL; + if (*off & 0x3f) + return -EINVAL; + + if (data->status & PICOLCD_BOOTLOADER) { + report_erase = REPORT_BL_ERASE_MEMORY; + report_write = REPORT_BL_WRITE_MEMORY; + } else { + report_erase = REPORT_ERASE_MEMORY; + report_write = REPORT_WRITE_MEMORY; + } + mutex_lock(&data->mutex_flash); + while (s > 0) { + err = _picolcd_flash_erase64(data, report_erase, off); + if (err) + break; + err = _picolcd_flash_write(data, report_write, u, 64, off); + if (err < 0) + break; + ret += err; + *off += err; + s -= err; + if (err != 64) + break; + } + mutex_unlock(&data->mutex_flash); + return ret > 0 ? ret : err; +} + +/* + * Notes: + * - concurrent writing is prevented by mutex and all writes must be + * n*64 bytes and 64-byte aligned, each write being preceeded by an + * ERASE which erases a 64byte block. + * If less than requested was written or an error is returned for an + * otherwise correct write request the next 64-byte block which should + * have been written is in undefined state (mostly: original, erased, + * (half-)written with write error) + * - reading can happend without special restriction + */ +static const struct file_operations picolcd_debug_flash_fops = { + .owner = THIS_MODULE, + .open = picolcd_debug_flash_open, + .read = picolcd_debug_flash_read, + .write = picolcd_debug_flash_write, + .llseek = generic_file_llseek, +}; + + +/* * Helper code for HID report level dumping/debugging */ static const char *error_codes[] = { @@ -1788,9 +2143,66 @@ static void picolcd_debug_raw_event(struct picolcd_data *data, wake_up_interruptible(&hdev->debug_wait); kfree(buff); } + +static void picolcd_init_devfs(struct picolcd_data *data, + struct hid_report *eeprom_r, struct hid_report *eeprom_w, + struct hid_report *flash_r, struct hid_report *flash_w, + struct hid_report *reset) +{ + struct hid_device *hdev = data->hdev; + + mutex_init(&data->mutex_flash); + + /* reset */ + if (reset) + data->debug_reset = debugfs_create_file("reset", 0600, + hdev->debug_dir, data, &picolcd_debug_reset_fops); + + /* eeprom */ + if (eeprom_r || eeprom_w) + data->debug_eeprom = debugfs_create_file("eeprom", + (eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0), + hdev->debug_dir, data, &picolcd_debug_eeprom_fops); + + /* flash */ + if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8) + data->addr_sz = flash_r->field[0]->report_count - 1; + else + data->addr_sz = -1; + if (data->addr_sz == 2 || data->addr_sz == 3) { + data->debug_flash = debugfs_create_file("flash", + (flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0), + hdev->debug_dir, data, &picolcd_debug_flash_fops); + } else if (flash_r || flash_w) + dev_warn(&hdev->dev, "Unexpected FLASH access reports, " + "please submit rdesc for review\n"); +} + +static void picolcd_exit_devfs(struct picolcd_data *data) +{ + struct dentry *dent; + + dent = data->debug_reset; + data->debug_reset = NULL; + if (dent) + debugfs_remove(dent); + dent = data->debug_eeprom; + data->debug_eeprom = NULL; + if (dent) + debugfs_remove(dent); + dent = data->debug_flash; + data->debug_flash = NULL; + if (dent) + debugfs_remove(dent); + mutex_destroy(&data->mutex_flash); +} #else #define picolcd_debug_raw_event(data, hdev, report, raw_data, size) -#endif +#define picolcd_init_devfs(data, eeprom_r, eeprom_w, flash_r, flash_w, reset) +static void picolcd_exit_devfs(struct picolcd_data *data) +{ +} +#endif /* CONFIG_DEBUG_FS */ /* * Handle raw report as sent by device @@ -1900,7 +2312,6 @@ static inline void picolcd_exit_cir(struct picolcd_data *data) static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) { - struct hid_report *report; int error; error = picolcd_check_version(hdev); @@ -1942,13 +2353,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) if (error) goto err; -#ifdef CONFIG_DEBUG_FS - report = picolcd_out_report(REPORT_READ_MEMORY, hdev); - if (report && report->maxfield == 1 && report->field[0]->report_size == 8) - data->addr_sz = report->field[0]->report_count - 1; - else - data->addr_sz = -1; -#endif + picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev), + picolcd_out_report(REPORT_EE_WRITE, hdev), + picolcd_out_report(REPORT_READ_MEMORY, hdev), + picolcd_out_report(REPORT_WRITE_MEMORY, hdev), + picolcd_out_report(REPORT_RESET, hdev)); return 0; err: picolcd_exit_leds(data); @@ -1962,7 +2371,6 @@ err: static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data) { - struct hid_report *report; int error; error = picolcd_check_version(hdev); @@ -1974,13 +2382,9 @@ static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data "please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", dev_name(&hdev->dev)); -#ifdef CONFIG_DEBUG_FS - report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev); - if (report && report->maxfield == 1 && report->field[0]->report_size == 8) - data->addr_sz = report->field[0]->report_count - 1; - else - data->addr_sz = -1; -#endif + picolcd_init_devfs(data, NULL, NULL, + picolcd_out_report(REPORT_BL_READ_MEMORY, hdev), + picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL); return 0; } @@ -2073,6 +2477,7 @@ static void picolcd_remove(struct hid_device *hdev) data->status |= PICOLCD_FAILED; spin_unlock_irqrestore(&data->lock, flags); + picolcd_exit_devfs(data); device_remove_file(&hdev->dev, &dev_attr_operation_mode); hdev->ll_driver->close(hdev); hid_hw_stop(hdev); -- cgit v0.10.2 From eb741103f17a19fccf7c795ed1d9662196acc6e5 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 1 Apr 2010 08:24:42 +0200 Subject: HID: picolcd: fix build failure Using copy_{to,from}_user requires the include of linux/uaccess.h. Reported-by: Stephen Rothwell Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 66f9cfd..0eacc6b 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -35,6 +35,7 @@ #include #include +#include #define PICOLCD_NAME "PicoLCD (graphic)" -- cgit v0.10.2 From 5435f2818ea08bcb381dcd2a99b1607b2a42f329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Sun, 11 Apr 2010 12:17:45 +0200 Subject: HID: hid-picolcd depends on LCD_CLASS_DEVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HID_PICOLCD should depend on LCD_CLASS_DEVICE, otherwise the build fails when HID_PICOLCD=y and LCD_CLASS_DEVICE=m: hid-picolcd.c:(.text+0x84523f): undefined reference to `lcd_device_unregister' hid-picolcd.c:(.text+0x8478ab): undefined reference to `lcd_device_register' hid-picolcd.c:(.text+0x84c15f): undefined reference to `lcd_device_unregister' Same applies to FB, BACKLIGHT_CLASS_DEVICE and LEDS_CLASS. Add suboptions for those features to handle the deps on kbuild side and just check HID_PICOLCD_* in the code. Reported-by: Randy Dunlap Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a2ecd83..0e8aa63 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -265,11 +265,6 @@ config HID_PETALYNX config HID_PICOLCD tristate "PicoLCD (graphic version)" depends on USB_HID - select FB_DEFERRED_IO if FB - select FB_SYS_FILLRECT if FB - select FB_SYS_COPYAREA if FB - select FB_SYS_IMAGEBLIT if FB - select FB_SYS_FOPS if FB ---help--- This provides support for Minibox PicoLCD devices, currently only the graphical ones are supported. @@ -277,14 +272,54 @@ config HID_PICOLCD This includes support for the following device features: - Keypad - Switching between Firmware and Flash mode - - Framebuffer for monochrome 256x64 display - - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE) - - Contrast control (needs CONFIG_LCD_CLASS_DEVICE) - - General purpose outputs (needs CONFIG_LEDS_CLASS) - EEProm / Flash access (via debugfs) + Features selectively enabled: + - Framebuffer for monochrome 256x64 display + - Backlight control + - Contrast control + - General purpose outputs Features that are not (yet) supported: - IR +config HID_PICOLCD_FB + bool "Framebuffer support" if EMBEDDED + default !EMBEDDED + depends on HID_PICOLCD + depends on HID_PICOLCD=FB || FB=y + select FB_DEFERRED_IO + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + ---help--- + Provide access to PicoLCD's 256x64 monochrome display via a + frambuffer device. + +config HID_PICOLCD_BACKLIGHT + bool "Backlight control" if EMBEDDED + default !EMBEDDED + depends on HID_PICOLCD + depends on HID_PICOLCD=BACKLIGHT_CLASS_DEVICE || BACKLIGHT_CLASS_DEVICE=y + ---help--- + Provide access to PicoLCD's backlight control via backlight + class. + +config HID_PICOLCD_LCD + bool "Contrast control" if EMBEDDED + default !EMBEDDED + depends on HID_PICOLCD + depends on HID_PICOLCD=LCD_CLASS_DEVICE || LCD_CLASS_DEVICE=y + ---help--- + Provide access to PicoLCD's LCD contrast via lcd class. + +config HID_PICOLCD_LEDS + bool "GPO via leds class" if EMBEDDED + default !EMBEDDED + depends on HID_PICOLCD + depends on HID_PICOLCD=LEDS_CLASS || LEDS_CLASS=y + ---help--- + Provide access to PicoLCD's GPO pins via leds class. + config HID_QUANTA tristate "Quanta Optical Touch" depends on USB_HID diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 0eacc6b..0fbc7d3 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -77,7 +77,7 @@ #define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */ #define REPORT_EXIT_FLASHER 0xff /* Bootloader: OUT[2] */ -#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +#ifdef CONFIG_HID_PICOLCD_FB /* Framebuffer * * The PicoLCD use a Topway LCD module of 256x64 pixel @@ -128,7 +128,7 @@ static const struct fb_var_screeninfo picolcdfb_var = { .bits_per_pixel = 1, .grayscale = 1, }; -#endif /* CONFIG_FB */ +#endif /* CONFIG_HID_PICOLCD_FB */ /* Input device * @@ -183,7 +183,7 @@ struct picolcd_data { struct input_dev *input_cir; unsigned short keycode[PICOLCD_KEYS]; -#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +#ifdef CONFIG_HID_PICOLCD_FB /* Framebuffer stuff */ u8 fb_update_rate; u8 fb_bpp; @@ -191,21 +191,21 @@ struct picolcd_data { u8 *fb_bitmap; /* framebuffer */ struct fb_info *fb_info; struct fb_deferred_io fb_defio; -#endif /* CONFIG_FB */ -#if defined(CONFIG_LCD_CLASS_DEVICE) || defined(CONFIG_LCD_CLASS_DEVICE_MODULE) +#endif /* CONFIG_HID_PICOLCD_FB */ +#ifdef CONFIG_HID_PICOLCD_LCD struct lcd_device *lcd; u8 lcd_contrast; -#endif -#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) +#endif /* CONFIG_HID_PICOLCD_LCD */ +#ifdef CONFIG_HID_PICOLCD_BACKLIGHT struct backlight_device *backlight; u8 lcd_brightness; u8 lcd_power; -#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ +#ifdef CONFIG_HID_PICOLCD_LEDS /* LED stuff */ u8 led_state; struct led_classdev *led[8]; -#endif /* CONFIG_LEDS_CLASS */ +#endif /* CONFIG_HID_PICOLCD_LEDS */ /* Housekeeping stuff */ spinlock_t lock; @@ -287,7 +287,7 @@ static struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, return work; } -#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +#ifdef CONFIG_HID_PICOLCD_FB /* Send a given tile to PicoLCD */ static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int tile) { @@ -766,9 +766,9 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data) { } #define picolcd_fbinfo(d) NULL -#endif /* CONFIG_FB */ +#endif /* CONFIG_HID_PICOLCD_FB */ -#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) +#ifdef CONFIG_HID_PICOLCD_BACKLIGHT /* * backlight class device */ @@ -864,9 +864,9 @@ static inline int picolcd_resume_backlight(struct picolcd_data *data) { return 0; } -#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ +#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ -#if defined(CONFIG_LCD_CLASS_DEVICE) || defined(CONFIG_LCD_CLASS_DEVICE_MODULE) +#ifdef CONFIG_HID_PICOLCD_LCD /* * lcd class device */ @@ -957,9 +957,9 @@ static inline int picolcd_resume_lcd(struct picolcd_data *data) { return 0; } -#endif /* CONFIG_LCD_CLASS_DEVICE */ +#endif /* CONFIG_HID_PICOLCD_LCD */ -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#ifdef CONFIG_HID_PICOLCD_LEDS /** * LED class device */ @@ -1104,7 +1104,7 @@ static inline int picolcd_leds_set(struct picolcd_data *data) { return 0; } -#endif /* CONFIG_LEDS_CLASS */ +#endif /* CONFIG_HID_PICOLCD_LEDS */ /* * input class device @@ -1243,10 +1243,10 @@ static int picolcd_reset(struct hid_device *hdev) picolcd_resume_lcd(data); picolcd_resume_backlight(data); -#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +#ifdef CONFIG_HID_PICOLCD_FB if (data->fb_info) schedule_delayed_work(&data->fb_info->deferred_work, 0); -#endif /* CONFIG_FB */ +#endif /* CONFIG_HID_PICOLCD_FB */ picolcd_leds_set(data); return 0; -- cgit v0.10.2 From 76d17e6ca30204532c631d092de41febb3f76b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Sun, 25 Apr 2010 21:31:40 +0200 Subject: HID: fix picolcd's version parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During grouping of version checking code bootloader mode's version bytes got swapped. Fix their order. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 0fbc7d3..6f71c60 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -1196,16 +1196,14 @@ static int picolcd_check_version(struct hid_device *hdev) } if (verinfo->raw_size == 2) { + data->version[0] = verinfo->raw_data[1]; + data->version[1] = verinfo->raw_data[0]; if (data->status & PICOLCD_BOOTLOADER) { dev_info(&hdev->dev, "PicoLCD, bootloader version %d.%d\n", - verinfo->raw_data[0], verinfo->raw_data[1]); - data->version[0] = verinfo->raw_data[0]; - data->version[1] = verinfo->raw_data[1]; + verinfo->raw_data[1], verinfo->raw_data[0]); } else { dev_info(&hdev->dev, "PicoLCD, firmware version %d.%d\n", verinfo->raw_data[1], verinfo->raw_data[0]); - data->version[0] = verinfo->raw_data[1]; - data->version[1] = verinfo->raw_data[0]; } } else { dev_err(&hdev->dev, "confused, got unexpected version response from PicoLCD\n"); -- cgit v0.10.2 From 0b5adf92ec793c665b0de63ac146d190a921c391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Sun, 25 Apr 2010 21:29:16 +0200 Subject: HID: split picolcd's operation_mode sysfs attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original operation_mode sysfs attribute accepts the operation mode as main value with an option delay as second value to change the start-up delay on mode change. As it is preferred to have exactly one value per sysfs attribute, extract this delay into a separate sysfs attribute called operation_mode_delay. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/Documentation/ABI/testing/sysfs-driver-hid-picolcd b/Documentation/ABI/testing/sysfs-driver-hid-picolcd index 14f52d7..08579e7 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-picolcd +++ b/Documentation/ABI/testing/sysfs-driver-hid-picolcd @@ -8,12 +8,21 @@ Description: Make it possible to switch the PicoLCD device between LCD enclosed in brackets ('[' and ']') Writing: causes operation mode switch. Permitted values are - the non-active mode names listed when read, optionally followed - by a delay value expressed in ms. + the non-active mode names listed when read. Note: when switching mode the current PicoLCD HID device gets - disconnected and reconnects after above delay (default value - is 5 seconds though this default should not be relied on). + disconnected and reconnects after above delay (see attribute + operation_mode_delay for its value). + + +What: /sys/bus/usb/devices/-:./::./operation_mode_delay +Date: April 2010 +Contact: Bruno Prémont +Description: Delay PicoLCD waits before restarting in new mode when + operation_mode has changed. + + Reading/Writing: It is expressed in ms and permitted range is + 0..30000ms. What: /sys/bus/usb/devices/-:./::./fb_update_rate diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 6f71c60..aa6f2e1 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -177,6 +177,7 @@ struct picolcd_data { int addr_sz; #endif u8 version[2]; + unsigned short opmode_delay; /* input stuff */ u8 pressed_keys[2]; struct input_dev *input_keys; @@ -1270,8 +1271,7 @@ static ssize_t picolcd_operation_mode_store(struct device *dev, struct picolcd_data *data = dev_get_drvdata(dev); struct hid_report *report = NULL; size_t cnt = count; - int timeout = 5000; - unsigned u; + int timeout = data->opmode_delay; unsigned long flags; if (cnt >= 3 && strncmp("lcd", buf, 3) == 0) { @@ -1288,20 +1288,10 @@ static ssize_t picolcd_operation_mode_store(struct device *dev, if (!report) return -EINVAL; - while (cnt > 0 && (*buf == ' ' || *buf == '\t')) { - buf++; - cnt--; - } while (cnt > 0 && (buf[cnt-1] == '\n' || buf[cnt-1] == '\r')) cnt--; - if (cnt > 0) { - if (sscanf(buf, "%u", &u) != 1) - return -EINVAL; - if (u > 30000) - return -EINVAL; - else - timeout = u; - } + if (cnt != 0) + return -EINVAL; spin_lock_irqsave(&data->lock, flags); hid_set_field(report->field[0], 0, timeout & 0xff); @@ -1314,6 +1304,34 @@ static ssize_t picolcd_operation_mode_store(struct device *dev, static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show, picolcd_operation_mode_store); +/* + * The "operation_mode_delay" sysfs attribute + */ +static ssize_t picolcd_operation_mode_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct picolcd_data *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay); +} + +static ssize_t picolcd_operation_mode_delay_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct picolcd_data *data = dev_get_drvdata(dev); + unsigned u; + if (sscanf(buf, "%u", &u) != 1) + return -EINVAL; + if (u > 30000) + return -EINVAL; + else + data->opmode_delay = u; + return count; +} + +static DEVICE_ATTR(operation_mode_delay, 0644, picolcd_operation_mode_delay_show, + picolcd_operation_mode_delay_store); + #ifdef CONFIG_DEBUG_FS /* @@ -2409,6 +2427,7 @@ static int picolcd_probe(struct hid_device *hdev, spin_lock_init(&data->lock); mutex_init(&data->mutex); data->hdev = hdev; + data->opmode_delay = 5000; if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) data->status |= PICOLCD_BOOTLOADER; hid_set_drvdata(hdev, data); @@ -2436,24 +2455,32 @@ static int picolcd_probe(struct hid_device *hdev, goto err_cleanup_hid_hw; } - error = device_create_file(&hdev->dev, &dev_attr_operation_mode); + error = device_create_file(&hdev->dev, &dev_attr_operation_mode_delay); if (error) { dev_err(&hdev->dev, "failed to create sysfs attributes\n"); goto err_cleanup_hid_ll; } + error = device_create_file(&hdev->dev, &dev_attr_operation_mode); + if (error) { + dev_err(&hdev->dev, "failed to create sysfs attributes\n"); + goto err_cleanup_sysfs1; + } + if (data->status & PICOLCD_BOOTLOADER) error = picolcd_probe_bootloader(hdev, data); else error = picolcd_probe_lcd(hdev, data); if (error) - goto err_cleanup_sysfs; + goto err_cleanup_sysfs2; dbg_hid(PICOLCD_NAME " activated and initialized\n"); return 0; -err_cleanup_sysfs: +err_cleanup_sysfs2: device_remove_file(&hdev->dev, &dev_attr_operation_mode); +err_cleanup_sysfs1: + device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); err_cleanup_hid_ll: hdev->ll_driver->close(hdev); err_cleanup_hid_hw: @@ -2478,6 +2505,7 @@ static void picolcd_remove(struct hid_device *hdev) picolcd_exit_devfs(data); device_remove_file(&hdev->dev, &dev_attr_operation_mode); + device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); hdev->ll_driver->close(hdev); hid_hw_stop(hdev); hid_set_drvdata(hdev, NULL); -- cgit v0.10.2 From 9d71ea057bc4823058d8fe27d34e987eb9880457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Sun, 2 May 2010 16:05:05 +0200 Subject: HID: add PM support to PicoLCD device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add PM support in order to turn off backlight on suspend, restore it on resume and especially restore complete state on reset-resume. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index aa6f2e1..95253b3 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -763,7 +763,7 @@ static inline int picolcd_init_framebuffer(struct picolcd_data *data) { return 0; } -static void picolcd_exit_framebuffer(struct picolcd_data *data) +static inline void picolcd_exit_framebuffer(struct picolcd_data *data) { } #define picolcd_fbinfo(d) NULL @@ -852,6 +852,18 @@ static inline int picolcd_resume_backlight(struct picolcd_data *data) return picolcd_set_brightness(data->backlight); } +#ifdef CONFIG_PM +static void picolcd_suspend_backlight(struct picolcd_data *data) +{ + int bl_power = data->lcd_power; + if (!data->backlight) + return; + + data->backlight->props.power = FB_BLANK_POWERDOWN; + picolcd_set_brightness(data->backlight); + data->lcd_power = data->backlight->props.power = bl_power; +} +#endif /* CONFIG_PM */ #else static inline int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report) @@ -865,6 +877,9 @@ static inline int picolcd_resume_backlight(struct picolcd_data *data) { return 0; } +static inline void picolcd_suspend_backlight(struct picolcd_data *data) +{ +} #endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ #ifdef CONFIG_HID_PICOLCD_LCD @@ -1098,7 +1113,7 @@ static inline int picolcd_init_leds(struct picolcd_data *data, { return 0; } -static void picolcd_exit_leds(struct picolcd_data *data) +static inline void picolcd_exit_leds(struct picolcd_data *data) { } static inline int picolcd_leds_set(struct picolcd_data *data) @@ -2214,9 +2229,18 @@ static void picolcd_exit_devfs(struct picolcd_data *data) mutex_destroy(&data->mutex_flash); } #else -#define picolcd_debug_raw_event(data, hdev, report, raw_data, size) -#define picolcd_init_devfs(data, eeprom_r, eeprom_w, flash_r, flash_w, reset) -static void picolcd_exit_devfs(struct picolcd_data *data) +static inline void picolcd_debug_raw_event(struct picolcd_data *data, + struct hid_device *hdev, struct hid_report *report, + u8 *raw_data, int size) +{ +} +static inline void picolcd_init_devfs(struct picolcd_data *data, + struct hid_report *eeprom_r, struct hid_report *eeprom_w, + struct hid_report *flash_r, struct hid_report *flash_w, + struct hid_report *reset) +{ +} +static inline void picolcd_exit_devfs(struct picolcd_data *data) { } #endif /* CONFIG_DEBUG_FS */ @@ -2259,6 +2283,46 @@ static int picolcd_raw_event(struct hid_device *hdev, return 1; } +#ifdef CONFIG_PM +static int picolcd_suspend(struct hid_device *hdev, pm_message_t message) +{ + if (message.event & PM_EVENT_AUTO) + return 0; + + picolcd_suspend_backlight(hid_get_drvdata(hdev)); + dbg_hid(PICOLCD_NAME " device ready for suspend\n"); + return 0; +} + +static int picolcd_resume(struct hid_device *hdev) +{ + int ret; + ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); + if (ret) + dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); + return 0; +} + +static int picolcd_reset_resume(struct hid_device *hdev) +{ + int ret; + ret = picolcd_reset(hdev); + if (ret) + dbg_hid(PICOLCD_NAME " resetting our device failed: %d\n", ret); + ret = picolcd_fb_reset(hid_get_drvdata(hdev), 0); + if (ret) + dbg_hid(PICOLCD_NAME " restoring framebuffer content failed: %d\n", ret); + ret = picolcd_resume_lcd(hid_get_drvdata(hdev)); + if (ret) + dbg_hid(PICOLCD_NAME " restoring lcd failed: %d\n", ret); + ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); + if (ret) + dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); + picolcd_leds_set(hid_get_drvdata(hdev)); + return 0; +} +#endif + /* initialize keypad input device */ static int picolcd_init_keys(struct picolcd_data *data, struct hid_report *report) @@ -2544,6 +2608,11 @@ static struct hid_driver picolcd_driver = { .probe = picolcd_probe, .remove = picolcd_remove, .raw_event = picolcd_raw_event, +#ifdef CONFIG_PM + .suspend = picolcd_suspend, + .resume = picolcd_resume, + .reset_resume = picolcd_reset_resume, +#endif }; static int __init picolcd_init(void) -- cgit v0.10.2 From 369db2a6008e8fc3cf5006fa8aab71bd58adfc1f Mon Sep 17 00:00:00 2001 From: Rafi Rubin Date: Tue, 4 May 2010 14:20:15 -0400 Subject: HID: ntrig: add sensitivity and responsiveness support The old rejection size thresholds were too high for the 12" devices. Larger surfaces like the Dell Studio17 exacerbated the problem since contact size is reported on the same logical scale, making a contact look smaller to the larger screen. Since we have observed erroneous ghost events from these devices we still need to filter the incoming stream. The prior size threshold filter is still in place, though with defaults set to leave it off. This patch adds the two new classes of filters, those that reject live frames before activation, and those that reject empty frames until deactivation. These filters are expressed in terms of a simple state machine for clarity (I hope). The activation filter has two components, slack and size, events are discarded until either is satisfied. Slack is defined as the number of seemingly good contacts to read before accepting the stream as valid (if the threshold is reached in the middle of a frame the remainder of that frame is still discarded). The deactivation filter discards empty frames until hitting a deactivate slack. This time measured in frames. N-Trig devices emit 5-8 (observed so far) empty frames at the end of multitouch activity. Ignoring the first few enables us to safely and gracefully handle erroneous empty frames, thus preventing a change in the tool state which would otherwise result in things like broken lines or dragged objects being dropped in bad places. Also, now that devices with different logical densities have been observed, the aforementioned sizes are scaled from physical to logical scales once those scales are identified. Hopefully this should mean that a given threshold value means the same thing across differing devices. Signed-off-by: Rafi Rubin Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index 4777bbf..227d015 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -24,6 +24,13 @@ #define NTRIG_DUPLICATE_USAGES 0x001 +static unsigned int min_width; +static unsigned int min_height; +static unsigned int activate_slack = 1; +static unsigned int deactivate_slack = 4; +static unsigned int activation_width = 64; +static unsigned int activation_height = 32; + struct ntrig_data { /* Incoming raw values for a single contact */ __u16 x, y, w, h; @@ -37,6 +44,28 @@ struct ntrig_data { __u8 mt_footer[4]; __u8 mt_foot_count; + + /* The current activation state. */ + __s8 act_state; + + /* Empty frames to ignore before recognizing the end of activity */ + __s8 deactivate_slack; + + /* Frames to ignore before acknowledging the start of activity */ + __s8 activate_slack; + + /* Minimum size contact to accept */ + __u16 min_width; + __u16 min_height; + + /* Threshold to override activation slack */ + __u16 activation_width; + __u16 activation_height; + + __u16 sensor_logical_width; + __u16 sensor_logical_height; + __u16 sensor_physical_width; + __u16 sensor_physical_height; }; /* @@ -49,6 +78,8 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { + struct ntrig_data *nd = hid_get_drvdata(hdev); + /* No special mappings needed for the pen and single touch */ if (field->physical) return 0; @@ -62,6 +93,21 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, input_set_abs_params(hi->input, ABS_X, field->logical_minimum, field->logical_maximum, 0, 0); + + if (!nd->sensor_logical_width) { + nd->sensor_logical_width = + field->logical_maximum - + field->logical_minimum; + nd->sensor_physical_width = + field->physical_maximum - + field->physical_minimum; + nd->activation_width = activation_width * + nd->sensor_logical_width / + nd->sensor_physical_width; + nd->min_width = min_width * + nd->sensor_logical_width / + nd->sensor_physical_width; + } return 1; case HID_GD_Y: hid_map_usage(hi, usage, bit, max, @@ -69,6 +115,21 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, input_set_abs_params(hi->input, ABS_Y, field->logical_minimum, field->logical_maximum, 0, 0); + + if (!nd->sensor_logical_height) { + nd->sensor_logical_height = + field->logical_maximum - + field->logical_minimum; + nd->sensor_physical_height = + field->physical_maximum - + field->physical_minimum; + nd->activation_height = activation_height * + nd->sensor_logical_height / + nd->sensor_physical_height; + nd->min_height = min_height * + nd->sensor_logical_height / + nd->sensor_physical_height; + } return 1; } return 0; @@ -201,20 +262,68 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, if (nd->mt_foot_count != 4) break; - /* Pen activity signal, trigger end of touch. */ + /* Pen activity signal. */ if (nd->mt_footer[2]) { + /* + * When the pen deactivates touch, we see a + * bogus frame with ContactCount > 0. + * We can + * save a bit of work by ensuring act_state < 0 + * even if deactivation slack is turned off. + */ + nd->act_state = deactivate_slack - 1; nd->confidence = 0; break; } - /* If the contact was invalid */ - if (!(nd->confidence && nd->mt_footer[0]) - || nd->w <= 250 - || nd->h <= 190) { - nd->confidence = 0; + /* + * The first footer value indicates the presence of a + * finger. + */ + if (nd->mt_footer[0]) { + /* + * We do not want to process contacts under + * the size threshold, but do not want to + * ignore them for activation state + */ + if (nd->w < nd->min_width || + nd->h < nd->min_height) + nd->confidence = 0; + } else break; + + if (nd->act_state > 0) { + /* + * Contact meets the activation size threshold + */ + if (nd->w >= nd->activation_width && + nd->h >= nd->activation_height) { + if (nd->id) + /* + * first contact, activate now + */ + nd->act_state = 0; + else { + /* + * avoid corrupting this frame + * but ensure next frame will + * be active + */ + nd->act_state = 1; + break; + } + } else + /* + * Defer adjusting the activation state + * until the end of the frame. + */ + break; } + /* Discarding this contact */ + if (!nd->confidence) + break; + /* emit a normal (X, Y) for the first point only */ if (nd->id == 0) { /* @@ -227,8 +336,15 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, input_event(input, EV_ABS, ABS_X, nd->x); input_event(input, EV_ABS, ABS_Y, nd->y); } + + /* Emit MT events */ input_event(input, EV_ABS, ABS_MT_POSITION_X, nd->x); input_event(input, EV_ABS, ABS_MT_POSITION_Y, nd->y); + + /* + * Translate from height and width to size + * and orientation. + */ if (nd->w > nd->h) { input_event(input, EV_ABS, ABS_MT_ORIENTATION, 1); @@ -248,12 +364,88 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, break; case HID_DG_CONTACTCOUNT: /* End of a multitouch group */ - if (!nd->reading_mt) + if (!nd->reading_mt) /* Just to be sure */ break; nd->reading_mt = 0; - if (nd->first_contact_touch) { + + /* + * Activation state machine logic: + * + * Fundamental states: + * state > 0: Inactive + * state <= 0: Active + * state < -deactivate_slack: + * Pen termination of touch + * + * Specific values of interest + * state == activate_slack + * no valid input since the last reset + * + * state == 0 + * general operational state + * + * state == -deactivate_slack + * read sufficient empty frames to accept + * the end of input and reset + */ + + if (nd->act_state > 0) { /* Currently inactive */ + if (value) + /* + * Consider each live contact as + * evidence of intentional activity. + */ + nd->act_state = (nd->act_state > value) + ? nd->act_state - value + : 0; + else + /* + * Empty frame before we hit the + * activity threshold, reset. + */ + nd->act_state = nd->activate_slack; + + /* + * Entered this block inactive and no + * coordinates sent this frame, so hold off + * on button state. + */ + break; + } else { /* Currently active */ + if (value && nd->act_state >= + nd->deactivate_slack) + /* + * Live point: clear accumulated + * deactivation count. + */ + nd->act_state = 0; + else if (nd->act_state <= nd->deactivate_slack) + /* + * We've consumed the deactivation + * slack, time to deactivate and reset. + */ + nd->act_state = + nd->activate_slack; + else { /* Move towards deactivation */ + nd->act_state--; + break; + } + } + + if (nd->first_contact_touch && nd->act_state <= 0) { + /* + * Check to see if we're ready to start + * emitting touch events. + * + * Note: activation slack will decrease over + * the course of the frame, and it will be + * inconsistent from the start to the end of + * the frame. However if the frame starts + * with slack, first_contact_touch will still + * be 0 and we will not get to this point. + */ input_report_key(input, BTN_TOOL_DOUBLETAP, 1); input_report_key(input, BTN_TOUCH, 1); } else { @@ -263,7 +455,7 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, break; default: - /* fallback to the generic hidinput handling */ + /* fall-back to the generic hidinput handling */ return 0; } } @@ -293,6 +485,16 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) } nd->reading_mt = 0; + nd->min_width = 0; + nd->min_height = 0; + nd->activate_slack = activate_slack; + nd->act_state = activate_slack; + nd->deactivate_slack = -deactivate_slack; + nd->sensor_logical_width = 0; + nd->sensor_logical_height = 0; + nd->sensor_physical_width = 0; + nd->sensor_physical_height = 0; + hid_set_drvdata(hdev, nd); ret = hid_parse(hdev); -- cgit v0.10.2 From eab32f5f65574c7484ed883c2245758f5a98878c Mon Sep 17 00:00:00 2001 From: Rafi Rubin Date: Tue, 4 May 2010 14:20:17 -0400 Subject: HID: ntrig: add sysfs access to filter parameters This should make it a little more convenient to tweak the filtering parameters on the fly. Also unlike load-time parameters, this provides independent tuning for each device conntected. Signed-off-by: Rafi Rubin Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index 227d015..2c08365 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -68,6 +68,287 @@ struct ntrig_data { __u16 sensor_physical_height; }; + +static ssize_t show_phys_width(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->sensor_physical_width); +} + +static DEVICE_ATTR(sensor_physical_width, S_IRUGO, show_phys_width, NULL); + +static ssize_t show_phys_height(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->sensor_physical_height); +} + +static DEVICE_ATTR(sensor_physical_height, S_IRUGO, show_phys_height, NULL); + +static ssize_t show_log_width(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->sensor_logical_width); +} + +static DEVICE_ATTR(sensor_logical_width, S_IRUGO, show_log_width, NULL); + +static ssize_t show_log_height(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->sensor_logical_height); +} + +static DEVICE_ATTR(sensor_logical_height, S_IRUGO, show_log_height, NULL); + +static ssize_t show_min_width(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->min_width * + nd->sensor_physical_width / + nd->sensor_logical_width); +} + +static ssize_t set_min_width(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (val > nd->sensor_physical_width) + return -EINVAL; + + nd->min_width = val * nd->sensor_logical_width / + nd->sensor_physical_width; + + return count; +} + +static DEVICE_ATTR(min_width, S_IWUSR | S_IRUGO, show_min_width, set_min_width); + +static ssize_t show_min_height(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->min_height * + nd->sensor_physical_height / + nd->sensor_logical_height); +} + +static ssize_t set_min_height(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (val > nd->sensor_physical_height) + return -EINVAL; + + nd->min_height = val * nd->sensor_logical_height / + nd->sensor_physical_height; + + return count; +} + +static DEVICE_ATTR(min_height, S_IWUSR | S_IRUGO, show_min_height, + set_min_height); + +static ssize_t show_activate_slack(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->activate_slack); +} + +static ssize_t set_activate_slack(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (val > 0x7f) + return -EINVAL; + + nd->activate_slack = val; + + return count; +} + +static DEVICE_ATTR(activate_slack, S_IWUSR | S_IRUGO, show_activate_slack, + set_activate_slack); + +static ssize_t show_activation_width(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->activation_width * + nd->sensor_physical_width / + nd->sensor_logical_width); +} + +static ssize_t set_activation_width(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (val > nd->sensor_physical_width) + return -EINVAL; + + nd->activation_width = val * nd->sensor_logical_width / + nd->sensor_physical_width; + + return count; +} + +static DEVICE_ATTR(activation_width, S_IWUSR | S_IRUGO, show_activation_width, + set_activation_width); + +static ssize_t show_activation_height(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", nd->activation_height * + nd->sensor_physical_height / + nd->sensor_logical_height); +} + +static ssize_t set_activation_height(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (val > nd->sensor_physical_height) + return -EINVAL; + + nd->activation_height = val * nd->sensor_logical_height / + nd->sensor_physical_height; + + return count; +} + +static DEVICE_ATTR(activation_height, S_IWUSR | S_IRUGO, + show_activation_height, set_activation_height); + +static ssize_t show_deactivate_slack(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + return sprintf(buf, "%d\n", -nd->deactivate_slack); +} + +static ssize_t set_deactivate_slack(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct ntrig_data *nd = hid_get_drvdata(hdev); + + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + /* + * No more than 8 terminal frames have been observed so far + * and higher slack is highly likely to leave the single + * touch emulation stuck down. + */ + if (val > 7) + return -EINVAL; + + nd->deactivate_slack = -val; + + return count; +} + +static DEVICE_ATTR(deactivate_slack, S_IWUSR | S_IRUGO, show_deactivate_slack, + set_deactivate_slack); + +static struct attribute *sysfs_attrs[] = { + &dev_attr_sensor_physical_width.attr, + &dev_attr_sensor_physical_height.attr, + &dev_attr_sensor_logical_width.attr, + &dev_attr_sensor_logical_height.attr, + &dev_attr_min_height.attr, + &dev_attr_min_width.attr, + &dev_attr_activate_slack.attr, + &dev_attr_activation_width.attr, + &dev_attr_activation_height.attr, + &dev_attr_deactivate_slack.attr, + NULL +}; + +static struct attribute_group ntrig_attribute_group = { + .attrs = sysfs_attrs +}; + /* * this driver is aimed at two firmware versions in circulation: * - dual pen/finger single touch @@ -546,6 +827,8 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) if (report) usbhid_submit_report(hdev, report, USB_DIR_OUT); + ret = sysfs_create_group(&hdev->dev.kobj, + &ntrig_attribute_group); return 0; err_free: @@ -555,6 +838,8 @@ err_free: static void ntrig_remove(struct hid_device *hdev) { + sysfs_remove_group(&hdev->dev.kobj, + &ntrig_attribute_group); hid_hw_stop(hdev); kfree(hid_get_drvdata(hdev)); } -- cgit v0.10.2 From ab3f4980ec62b907e697ff0934a8e1d076a6d46d Mon Sep 17 00:00:00 2001 From: Rafi Rubin Date: Tue, 4 May 2010 14:20:16 -0400 Subject: HID: ntrig: add filtering module parameters Signed-off-by: Rafi Rubin Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index 2c08365..b6b0cae 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -25,11 +25,32 @@ #define NTRIG_DUPLICATE_USAGES 0x001 static unsigned int min_width; +module_param(min_width, uint, 0644); +MODULE_PARM_DESC(min_width, "Minimum touch contact width to accept."); + static unsigned int min_height; +module_param(min_height, uint, 0644); +MODULE_PARM_DESC(min_height, "Minimum touch contact height to accept."); + static unsigned int activate_slack = 1; +module_param(activate_slack, uint, 0644); +MODULE_PARM_DESC(activate_slack, "Number of touch frames to ignore at " + "the start of touch input."); + static unsigned int deactivate_slack = 4; +module_param(deactivate_slack, uint, 0644); +MODULE_PARM_DESC(deactivate_slack, "Number of empty frames to ignore before " + "deactivating touch."); + static unsigned int activation_width = 64; +module_param(activation_width, uint, 0644); +MODULE_PARM_DESC(activation_width, "Width threshold to immediately start " + "processing touch events."); + static unsigned int activation_height = 32; +module_param(activation_height, uint, 0644); +MODULE_PARM_DESC(activation_height, "Height threshold to immediately start " + "processing touch events."); struct ntrig_data { /* Incoming raw values for a single contact */ -- cgit v0.10.2 From 3a370ca1dcf8c80aff7a0a21d6b0f50ca2a151e9 Mon Sep 17 00:00:00 2001 From: Don Prince Date: Wed, 12 May 2010 15:18:59 +0200 Subject: HID: Prodikeys PC-MIDI HID Driver A specialised HID driver for the Creative Prodikeys PC-MIDI USB Keyboard. The Prodikeys PC-MIDI is a multifunction keyboard comprising a qwerty keyboard, multimedia keys and a touch sensitive musical keyboard. The specialised HID driver adds full support for the musical keyboard and extra multimedia keys which are not currently handled by the default HID driver. The specialised HID driver interfaces with ALSA, and presents the midi keyboard as a rawmidi device. Sustain duration, octave shifting and the midi output channel can be read/written form userspace via sysfs. Signed-off-by: Don Prince ALSA parts: Acked-by: Clemens Ladisch Signed-off-by: Jiri Kosina diff --git a/Documentation/ABI/testing/sysfs-driver-hid-prodikeys b/Documentation/ABI/testing/sysfs-driver-hid-prodikeys new file mode 100644 index 0000000..05d988c --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-prodikeys @@ -0,0 +1,29 @@ +What: /sys/bus/hid/drivers/prodikeys/.../channel +Date: April 2010 +KernelVersion: 2.6.34 +Contact: Don Prince +Description: + Allows control (via software) the midi channel to which + that the pc-midi keyboard will output.midi data. + Range: 0..15 + Type: Read/write +What: /sys/bus/hid/drivers/prodikeys/.../sustain +Date: April 2010 +KernelVersion: 2.6.34 +Contact: Don Prince +Description: + Allows control (via software) the sustain duration of a + note held by the pc-midi driver. + 0 means sustain mode is disabled. + Range: 0..5000 (milliseconds) + Type: Read/write +What: /sys/bus/hid/drivers/prodikeys/.../octave +Date: April 2010 +KernelVersion: 2.6.34 +Contact: Don Prince +Description: + Controls the octave shift modifier in the pc-midi driver. + The octave can be shifted via software up/down 2 octaves. + 0 means the no ocatve shift. + Range: -2..2 (minus 2 to plus 2) + Type: Read/Write diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 71d4c07..0c3eee9 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -100,6 +100,22 @@ config HID_CHICONY ---help--- Support for Chicony Tactical pad. +config HID_PRODIKEYS + tristate "Prodikeys PC-MIDI Keyboard support" if EMBEDDED + depends on USB_HID && SND + select SND_RAWMIDI + default !EMBEDDED + ---help--- + Support for Prodikeys PC-MIDI Keyboard device support. + Say Y here to enable support for this device. + - Prodikeys PC-MIDI keyboard. + The Prodikeys PC-MIDI acts as a USB Audio device, with one MIDI + input and one MIDI output. These MIDI jacks appear as + a sound "card" in the ALSA sound system. + Note: if you say N here, this device will still function as a basic + multimedia keyboard, but will lack support for the musical keyboard + and some additional multimedia keys. + config HID_CYPRESS tristate "Cypress" if EMBEDDED depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0b2618f..f637b38 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o obj-$(CONFIG_HID_MOSART) += hid-mosart.o obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o obj-$(CONFIG_HID_ORTEK) += hid-ortek.o +obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o obj-$(CONFIG_HID_QUANTA) += hid-quanta.o obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 143e788..1c57a93 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1293,6 +1293,7 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 09d2764..5013443 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -148,6 +148,9 @@ #define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500 #define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff +#define USB_VENDOR_ID_CREATIVELABS 0x041e +#define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801 + #define USB_VENDOR_ID_CYGNAL 0x10c4 #define USB_DEVICE_ID_CYGNAL_RADIO_SI470X 0x818a diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c new file mode 100644 index 0000000..845f428 --- /dev/null +++ b/drivers/hid/hid-prodikeys.c @@ -0,0 +1,910 @@ +/* + * HID driver for the Prodikeys PC-MIDI Keyboard + * providing midi & extra multimedia keys functionality + * + * Copyright (c) 2009 Don Prince + * + * Controls for Octave Shift Up/Down, Channel, and + * Sustain Duration available via sysfs. + * + */ + +/* + * 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; either version 2 of the License, or (at your option) + * any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "usbhid/usbhid.h" +#include "hid-ids.h" + + +#define pk_debug(format, arg...) \ + pr_debug("hid-prodikeys: " format "\n" , ## arg) +#define pk_error(format, arg...) \ + pr_err("hid-prodikeys: " format "\n" , ## arg) + +struct pcmidi_snd; + +struct pk_device { + unsigned long quirks; + + struct hid_device *hdev; + struct pcmidi_snd *pm; /* pcmidi device context */ +}; + +struct pcmidi_snd; + +struct pcmidi_sustain { + unsigned long in_use; + struct pcmidi_snd *pm; + struct timer_list timer; + unsigned char status; + unsigned char note; + unsigned char velocity; +}; + +#define PCMIDI_SUSTAINED_MAX 32 +struct pcmidi_snd { + struct pk_device *pk; + unsigned short ifnum; + struct hid_report *pcmidi_report6; + struct input_dev *input_ep82; + unsigned short midi_mode; + unsigned short midi_sustain_mode; + unsigned short midi_sustain; + unsigned short midi_channel; + short midi_octave; + struct pcmidi_sustain sustained_notes[PCMIDI_SUSTAINED_MAX]; + unsigned short fn_state; + unsigned short last_key[24]; + spinlock_t rawmidi_in_lock; + struct snd_card *card; + struct snd_rawmidi *rwmidi; + struct snd_rawmidi_substream *in_substream; + struct snd_rawmidi_substream *out_substream; + unsigned long in_triggered; + unsigned long out_active; +}; + +#define PK_QUIRK_NOGET 0x00010000 +#define PCMIDI_MIDDLE_C 60 +#define PCMIDI_CHANNEL_MIN 0 +#define PCMIDI_CHANNEL_MAX 15 +#define PCMIDI_OCTAVE_MIN (-2) +#define PCMIDI_OCTAVE_MAX 2 +#define PCMIDI_SUSTAIN_MIN 0 +#define PCMIDI_SUSTAIN_MAX 5000 + +static const char shortname[] = "PC-MIDI"; +static const char longname[] = "Prodikeys PC-MIDI Keyboard"; + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +module_param_array(id, charp, NULL, 0444); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the PC-MIDI virtual audio driver"); +MODULE_PARM_DESC(id, "ID string for the PC-MIDI virtual audio driver"); +MODULE_PARM_DESC(enable, "Enable for the PC-MIDI virtual audio driver"); + + +/* Output routine for the sysfs channel file */ +static ssize_t show_channel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + + dbg_hid("pcmidi sysfs read channel=%u\n", pk->pm->midi_channel); + + return sprintf(buf, "%u (min:%u, max:%u)\n", pk->pm->midi_channel, + PCMIDI_CHANNEL_MIN, PCMIDI_CHANNEL_MAX); +} + +/* Input routine for the sysfs channel file */ +static ssize_t store_channel(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + + unsigned channel = 0; + + if (sscanf(buf, "%u", &channel) > 0 && channel <= PCMIDI_CHANNEL_MAX) { + dbg_hid("pcmidi sysfs write channel=%u\n", channel); + pk->pm->midi_channel = channel; + return strlen(buf); + } + return -EINVAL; +} + +static DEVICE_ATTR(channel, S_IRUGO | S_IWUGO, show_channel, + store_channel); + +static struct device_attribute *sysfs_device_attr_channel = { + &dev_attr_channel, + }; + +/* Output routine for the sysfs sustain file */ +static ssize_t show_sustain(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + + dbg_hid("pcmidi sysfs read sustain=%u\n", pk->pm->midi_sustain); + + return sprintf(buf, "%u (off:%u, max:%u (ms))\n", pk->pm->midi_sustain, + PCMIDI_SUSTAIN_MIN, PCMIDI_SUSTAIN_MAX); +} + +/* Input routine for the sysfs sustain file */ +static ssize_t store_sustain(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + + unsigned sustain = 0; + + if (sscanf(buf, "%u", &sustain) > 0 && sustain <= PCMIDI_SUSTAIN_MAX) { + dbg_hid("pcmidi sysfs write sustain=%u\n", sustain); + pk->pm->midi_sustain = sustain; + pk->pm->midi_sustain_mode = + (0 == sustain || !pk->pm->midi_mode) ? 0 : 1; + return strlen(buf); + } + return -EINVAL; +} + +static DEVICE_ATTR(sustain, S_IRUGO | S_IWUGO, show_sustain, + store_sustain); + +static struct device_attribute *sysfs_device_attr_sustain = { + &dev_attr_sustain, + }; + +/* Output routine for the sysfs octave file */ +static ssize_t show_octave(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + + dbg_hid("pcmidi sysfs read octave=%d\n", pk->pm->midi_octave); + + return sprintf(buf, "%d (min:%d, max:%d)\n", pk->pm->midi_octave, + PCMIDI_OCTAVE_MIN, PCMIDI_OCTAVE_MAX); +} + +/* Input routine for the sysfs octave file */ +static ssize_t store_octave(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + + int octave = 0; + + if (sscanf(buf, "%d", &octave) > 0 && + octave >= PCMIDI_OCTAVE_MIN && octave <= PCMIDI_OCTAVE_MAX) { + dbg_hid("pcmidi sysfs write octave=%d\n", octave); + pk->pm->midi_octave = octave; + return strlen(buf); + } + return -EINVAL; +} + +static DEVICE_ATTR(octave, S_IRUGO | S_IWUGO, show_octave, + store_octave); + +static struct device_attribute *sysfs_device_attr_octave = { + &dev_attr_octave, + }; + + +static void pcmidi_send_note(struct pcmidi_snd *pm, + unsigned char status, unsigned char note, unsigned char velocity) +{ + unsigned long flags; + unsigned char buffer[3]; + + buffer[0] = status; + buffer[1] = note; + buffer[2] = velocity; + + spin_lock_irqsave(&pm->rawmidi_in_lock, flags); + + if (!pm->in_substream) + goto drop_note; + if (!test_bit(pm->in_substream->number, &pm->in_triggered)) + goto drop_note; + + snd_rawmidi_receive(pm->in_substream, buffer, 3); + +drop_note: + spin_unlock_irqrestore(&pm->rawmidi_in_lock, flags); + + return; +} + +void pcmidi_sustained_note_release(unsigned long data) +{ + struct pcmidi_sustain *pms = (struct pcmidi_sustain *)data; + + pcmidi_send_note(pms->pm, pms->status, pms->note, pms->velocity); + pms->in_use = 0; +} + +void init_sustain_timers(struct pcmidi_snd *pm) +{ + struct pcmidi_sustain *pms; + unsigned i; + + for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) { + pms = &pm->sustained_notes[i]; + pms->in_use = 0; + pms->pm = pm; + setup_timer(&pms->timer, pcmidi_sustained_note_release, + (unsigned long)pms); + } +} + +void stop_sustain_timers(struct pcmidi_snd *pm) +{ + struct pcmidi_sustain *pms; + unsigned i; + + for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) { + pms = &pm->sustained_notes[i]; + pms->in_use = 1; + del_timer_sync(&pms->timer); + } +} + +static int pcmidi_get_output_report(struct pcmidi_snd *pm) +{ + struct hid_device *hdev = pm->pk->hdev; + struct hid_report *report; + + list_for_each_entry(report, + &hdev->report_enum[HID_OUTPUT_REPORT].report_list, list) { + if (!(6 == report->id)) + continue; + + if (report->maxfield < 1) { + dev_err(&hdev->dev, "output report is empty\n"); + break; + } + if (report->field[0]->report_count != 2) { + dev_err(&hdev->dev, "field count too low\n"); + break; + } + pm->pcmidi_report6 = report; + return 0; + } + /* should never get here */ + return -ENODEV; +} + +static void pcmidi_submit_output_report(struct pcmidi_snd *pm, int state) +{ + struct hid_device *hdev = pm->pk->hdev; + struct hid_report *report = pm->pcmidi_report6; + report->field[0]->value[0] = 0x01; + report->field[0]->value[1] = state; + + usbhid_submit_report(hdev, report, USB_DIR_OUT); +} + +static int pcmidi_handle_report1(struct pcmidi_snd *pm, u8 *data) +{ + u32 bit_mask; + + bit_mask = data[1]; + bit_mask = (bit_mask << 8) | data[2]; + bit_mask = (bit_mask << 8) | data[3]; + + dbg_hid("pcmidi mode: %d\n", pm->midi_mode); + + /*KEY_MAIL or octave down*/ + if (pm->midi_mode && bit_mask == 0x004000) { + /* octave down */ + pm->midi_octave--; + if (pm->midi_octave < -2) + pm->midi_octave = -2; + dbg_hid("pcmidi mode: %d octave: %d\n", + pm->midi_mode, pm->midi_octave); + return 1; + } + /*KEY_WWW or sustain*/ + else if (pm->midi_mode && bit_mask == 0x000004) { + /* sustain on/off*/ + pm->midi_sustain_mode ^= 0x1; + return 1; + } + + return 0; /* continue key processing */ +} + +static int pcmidi_handle_report3(struct pcmidi_snd *pm, u8 *data, int size) +{ + struct pcmidi_sustain *pms; + unsigned i, j; + unsigned char status, note, velocity; + + unsigned num_notes = (size-1)/2; + for (j = 0; j < num_notes; j++) { + note = data[j*2+1]; + velocity = data[j*2+2]; + + if (note < 0x81) { /* note on */ + status = 128 + 16 + pm->midi_channel; /* 1001nnnn */ + note = note - 0x54 + PCMIDI_MIDDLE_C + + (pm->midi_octave * 12); + if (0 == velocity) + velocity = 1; /* force note on */ + } else { /* note off */ + status = 128 + pm->midi_channel; /* 1000nnnn */ + note = note - 0x94 + PCMIDI_MIDDLE_C + + (pm->midi_octave*12); + + if (pm->midi_sustain_mode) { + for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) { + pms = &pm->sustained_notes[i]; + if (!pms->in_use) { + pms->status = status; + pms->note = note; + pms->velocity = velocity; + pms->in_use = 1; + + mod_timer(&pms->timer, + jiffies + + msecs_to_jiffies(pm->midi_sustain)); + return 1; + } + } + } + } + pcmidi_send_note(pm, status, note, velocity); + } + + return 1; +} + +static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data) +{ + unsigned key; + u32 bit_mask; + u32 bit_index; + + bit_mask = data[1]; + bit_mask = (bit_mask << 8) | data[2]; + bit_mask = (bit_mask << 8) | data[3]; + + /* break keys */ + for (bit_index = 0; bit_index < 24; bit_index++) { + key = pm->last_key[bit_index]; + if (!((0x01 << bit_index) & bit_mask)) { + input_event(pm->input_ep82, EV_KEY, + pm->last_key[bit_index], 0); + pm->last_key[bit_index] = 0; + } + } + + /* make keys */ + for (bit_index = 0; bit_index < 24; bit_index++) { + key = 0; + switch ((0x01 << bit_index) & bit_mask) { + case 0x000010: /* Fn lock*/ + pm->fn_state ^= 0x000010; + if (pm->fn_state) + pcmidi_submit_output_report(pm, 0xc5); + else + pcmidi_submit_output_report(pm, 0xc6); + continue; + case 0x020000: /* midi launcher..send a key (qwerty) or not? */ + pcmidi_submit_output_report(pm, 0xc1); + pm->midi_mode ^= 0x01; + + dbg_hid("pcmidi mode: %d\n", pm->midi_mode); + continue; + case 0x100000: /* KEY_MESSENGER or octave up */ + dbg_hid("pcmidi mode: %d\n", pm->midi_mode); + if (pm->midi_mode) { + pm->midi_octave++; + if (pm->midi_octave > 2) + pm->midi_octave = 2; + dbg_hid("pcmidi mode: %d octave: %d\n", + pm->midi_mode, pm->midi_octave); + continue; + } else + key = KEY_MESSENGER; + break; + case 0x400000: + key = KEY_CALENDAR; + break; + case 0x080000: + key = KEY_ADDRESSBOOK; + break; + case 0x040000: + key = KEY_DOCUMENTS; + break; + case 0x800000: + key = KEY_WORDPROCESSOR; + break; + case 0x200000: + key = KEY_SPREADSHEET; + break; + case 0x010000: + key = KEY_COFFEE; + break; + case 0x000100: + key = KEY_HELP; + break; + case 0x000200: + key = KEY_SEND; + break; + case 0x000400: + key = KEY_REPLY; + break; + case 0x000800: + key = KEY_FORWARDMAIL; + break; + case 0x001000: + key = KEY_NEW; + break; + case 0x002000: + key = KEY_OPEN; + break; + case 0x004000: + key = KEY_CLOSE; + break; + case 0x008000: + key = KEY_SAVE; + break; + case 0x000001: + key = KEY_UNDO; + break; + case 0x000002: + key = KEY_REDO; + break; + case 0x000004: + key = KEY_SPELLCHECK; + break; + case 0x000008: + key = KEY_PRINT; + break; + } + if (key) { + input_event(pm->input_ep82, EV_KEY, key, 1); + pm->last_key[bit_index] = key; + } + } + + return 1; +} + +int pcmidi_handle_report( + struct pcmidi_snd *pm, unsigned report_id, u8 *data, int size) +{ + int ret = 0; + + switch (report_id) { + case 0x01: /* midi keys (qwerty)*/ + ret = pcmidi_handle_report1(pm, data); + break; + case 0x03: /* midi keyboard (musical)*/ + ret = pcmidi_handle_report3(pm, data, size); + break; + case 0x04: /* multimedia/midi keys (qwerty)*/ + ret = pcmidi_handle_report4(pm, data); + break; + } + return ret; +} + +void pcmidi_setup_extra_keys(struct pcmidi_snd *pm, struct input_dev *input) +{ + /* reassigned functionality for N/A keys + MY PICTURES => KEY_WORDPROCESSOR + MY MUSIC=> KEY_SPREADSHEET + */ + unsigned int keys[] = { + KEY_FN, + KEY_MESSENGER, KEY_CALENDAR, + KEY_ADDRESSBOOK, KEY_DOCUMENTS, + KEY_WORDPROCESSOR, + KEY_SPREADSHEET, + KEY_COFFEE, + KEY_HELP, KEY_SEND, + KEY_REPLY, KEY_FORWARDMAIL, + KEY_NEW, KEY_OPEN, + KEY_CLOSE, KEY_SAVE, + KEY_UNDO, KEY_REDO, + KEY_SPELLCHECK, KEY_PRINT, + 0 + }; + + unsigned int *pkeys = &keys[0]; + unsigned short i; + + if (pm->ifnum != 1) /* only set up ONCE for interace 1 */ + return; + + pm->input_ep82 = input; + + for (i = 0; i < 24; i++) + pm->last_key[i] = 0; + + while (*pkeys != 0) { + set_bit(*pkeys, pm->input_ep82->keybit); + ++pkeys; + } +} + +static int pcmidi_set_operational(struct pcmidi_snd *pm) +{ + if (pm->ifnum != 1) + return 0; /* only set up ONCE for interace 1 */ + + pcmidi_get_output_report(pm); + pcmidi_submit_output_report(pm, 0xc1); + return 0; +} + +static int pcmidi_snd_free(struct snd_device *dev) +{ + return 0; +} + +static int pcmidi_in_open(struct snd_rawmidi_substream *substream) +{ + struct pcmidi_snd *pm = substream->rmidi->private_data; + + dbg_hid("pcmidi in open\n"); + pm->in_substream = substream; + return 0; +} + +static int pcmidi_in_close(struct snd_rawmidi_substream *substream) +{ + dbg_hid("pcmidi in close\n"); + return 0; +} + +static void pcmidi_in_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct pcmidi_snd *pm = substream->rmidi->private_data; + + dbg_hid("pcmidi in trigger %d\n", up); + + pm->in_triggered = up; +} + +static struct snd_rawmidi_ops pcmidi_in_ops = { + .open = pcmidi_in_open, + .close = pcmidi_in_close, + .trigger = pcmidi_in_trigger +}; + +int pcmidi_snd_initialise(struct pcmidi_snd *pm) +{ + static int dev; + struct snd_card *card; + struct snd_rawmidi *rwmidi; + int err; + + static struct snd_device_ops ops = { + .dev_free = pcmidi_snd_free, + }; + + if (pm->ifnum != 1) + return 0; /* only set up midi device ONCE for interace 1 */ + + if (dev >= SNDRV_CARDS) + return -ENODEV; + + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + /* Setup sound card */ + + err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card); + if (err < 0) { + pk_error("failed to create pc-midi sound card\n"); + err = -ENOMEM; + goto fail; + } + pm->card = card; + + /* Setup sound device */ + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pm, &ops); + if (err < 0) { + pk_error("failed to create pc-midi sound device: error %d\n", + err); + goto fail; + } + + strncpy(card->driver, shortname, sizeof(card->driver)); + strncpy(card->shortname, shortname, sizeof(card->shortname)); + strncpy(card->longname, longname, sizeof(card->longname)); + + /* Set up rawmidi */ + err = snd_rawmidi_new(card, card->shortname, 0, + 0, 1, &rwmidi); + if (err < 0) { + pk_error("failed to create pc-midi rawmidi device: error %d\n", + err); + goto fail; + } + pm->rwmidi = rwmidi; + strncpy(rwmidi->name, card->shortname, sizeof(rwmidi->name)); + rwmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT; + rwmidi->private_data = pm; + + snd_rawmidi_set_ops(rwmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &pcmidi_in_ops); + + snd_card_set_dev(card, &pm->pk->hdev->dev); + + /* create sysfs variables */ + err = device_create_file(&pm->pk->hdev->dev, + sysfs_device_attr_channel); + if (err < 0) { + pk_error("failed to create sysfs attribute channel: error %d\n", + err); + goto fail; + } + + err = device_create_file(&pm->pk->hdev->dev, + sysfs_device_attr_sustain); + if (err < 0) { + pk_error("failed to create sysfs attribute sustain: error %d\n", + err); + goto fail_attr_sustain; + } + + err = device_create_file(&pm->pk->hdev->dev, + sysfs_device_attr_octave); + if (err < 0) { + pk_error("failed to create sysfs attribute octave: error %d\n", + err); + goto fail_attr_octave; + } + + spin_lock_init(&pm->rawmidi_in_lock); + + init_sustain_timers(pm); + pcmidi_set_operational(pm); + + /* register it */ + err = snd_card_register(card); + if (err < 0) { + pk_error("failed to register pc-midi sound card: error %d\n", + err); + goto fail_register; + } + + dbg_hid("pcmidi_snd_initialise finished ok\n"); + return 0; + +fail_register: + stop_sustain_timers(pm); + device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_octave); +fail_attr_octave: + device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_sustain); +fail_attr_sustain: + device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_channel); +fail: + if (pm->card) { + snd_card_free(pm->card); + pm->card = NULL; + } + return err; +} + +int pcmidi_snd_terminate(struct pcmidi_snd *pm) +{ + if (pm->card) { + stop_sustain_timers(pm); + + device_remove_file(&pm->pk->hdev->dev, + sysfs_device_attr_channel); + device_remove_file(&pm->pk->hdev->dev, + sysfs_device_attr_sustain); + device_remove_file(&pm->pk->hdev->dev, + sysfs_device_attr_octave); + + snd_card_disconnect(pm->card); + snd_card_free_when_closed(pm->card); + } + + return 0; +} + +/* + * PC-MIDI report descriptor for report id is wrong. + */ +static void pk_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int rsize) +{ + if (rsize == 178 && + rdesc[111] == 0x06 && rdesc[112] == 0x00 && + rdesc[113] == 0xff) { + dev_info(&hdev->dev, "fixing up pc-midi keyboard report " + "descriptor\n"); + + rdesc[144] = 0x18; /* report 4: was 0x10 report count */ + } +} + +static int pk_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + struct pcmidi_snd *pm; + + pm = pk->pm; + + if (HID_UP_MSVENDOR == (usage->hid & HID_USAGE_PAGE) && + 1 == pm->ifnum) { + pcmidi_setup_extra_keys(pm, hi->input); + return 0; + } + + return 0; +} + + +static int pk_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + int ret = 0; + + if (1 == pk->pm->ifnum) { + if (report->id == data[0]) + switch (report->id) { + case 0x01: /* midi keys (qwerty)*/ + case 0x03: /* midi keyboard (musical)*/ + case 0x04: /* extra/midi keys (qwerty)*/ + ret = pcmidi_handle_report(pk->pm, + report->id, data, size); + break; + } + } + + return ret; +} + +static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + unsigned long quirks = id->driver_data; + struct pk_device *pk; + struct pcmidi_snd *pm = NULL; + + pk = kzalloc(sizeof(*pk), GFP_KERNEL); + if (pk == NULL) { + dev_err(&hdev->dev, "prodikeys: can't alloc descriptor\n"); + return -ENOMEM; + } + + pk->hdev = hdev; + + pm = kzalloc(sizeof(*pm), GFP_KERNEL); + if (pm == NULL) { + dev_err(&hdev->dev, + "prodikeys: can't alloc descriptor\n"); + ret = -ENOMEM; + goto err_free; + } + + pm->pk = pk; + pk->pm = pm; + pm->ifnum = ifnum; + + hid_set_drvdata(hdev, pk); + + ret = hid_parse(hdev); + if (ret) { + dev_err(&hdev->dev, "prodikeys: hid parse failed\n"); + goto err_free; + } + + if (quirks & PK_QUIRK_NOGET) { /* hid_parse cleared all the quirks */ + hdev->quirks |= HID_QUIRK_NOGET; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + dev_err(&hdev->dev, "prodikeys: hw start failed\n"); + goto err_free; + } + + ret = pcmidi_snd_initialise(pm); + if (ret < 0) + goto err_stop; + + return 0; +err_stop: + hid_hw_stop(hdev); +err_free: + if (pm != NULL) + kfree(pm); + + kfree(pk); + return ret; +} + +static void pk_remove(struct hid_device *hdev) +{ + struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev); + struct pcmidi_snd *pm; + + pm = pk->pm; + if (pm) { + pcmidi_snd_terminate(pm); + kfree(pm); + } + + hid_hw_stop(hdev); + + kfree(pk); +} + +static const struct hid_device_id pk_devices[] = { + {HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, + USB_DEVICE_ID_PRODIKEYS_PCMIDI), + .driver_data = PK_QUIRK_NOGET}, + { } +}; +MODULE_DEVICE_TABLE(hid, pk_devices); + +static struct hid_driver pk_driver = { + .name = "prodikeys", + .id_table = pk_devices, + .report_fixup = pk_report_fixup, + .input_mapping = pk_input_mapping, + .raw_event = pk_raw_event, + .probe = pk_probe, + .remove = pk_remove, +}; + +static int pk_init(void) +{ + int ret; + + ret = hid_register_driver(&pk_driver); + if (ret) + printk(KERN_ERR "can't register prodikeys driver\n"); + + return ret; +} + +static void pk_exit(void) +{ + hid_unregister_driver(&pk_driver); +} + +module_init(pk_init); +module_exit(pk_exit); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 95736de984dec5b80ea9d6640d4d55ca8ff98db4 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Wed, 12 May 2010 15:27:00 +0200 Subject: HID: make Prodikeys driver standalone config option Analogically for other full-fledged HID drivers, make the Prodikeys driver independent on EMBEDDED. Signed-off-by: Jiri Kosina diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 0c3eee9..26c6f10 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -101,10 +101,9 @@ config HID_CHICONY Support for Chicony Tactical pad. config HID_PRODIKEYS - tristate "Prodikeys PC-MIDI Keyboard support" if EMBEDDED + tristate "Prodikeys PC-MIDI Keyboard support" depends on USB_HID && SND select SND_RAWMIDI - default !EMBEDDED ---help--- Support for Prodikeys PC-MIDI Keyboard device support. Say Y here to enable support for this device. -- cgit v0.10.2 From aeacb6fd30c800e8229905eac7023777a472d772 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 15 May 2010 11:46:36 +0200 Subject: HID: picolcd: Eliminate use after free The skip label frees resp, which has not been allocated at the point of this goto and then does a break, based on the fact that err is non-zero. This is replaced by a break directly. A simplified version of the semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @free@ expression E; position p; @@ kfree@p(E) @@ expression free.E, subE<=free.E, E1; position free.p; @@ kfree@p(E) ... ( subE = E1 | * E ) // Signed-off-by: Julia Lawall Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 95253b3..7aabf65 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -1616,7 +1616,7 @@ static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id, raw_data[len_off] = s > 32 ? 32 : s; if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) { err = -EFAULT; - goto skip; + break; } resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1+raw_data[len_off]); -- cgit v0.10.2 From 00237bc5204c43f67b2e68546012d7bd27efc1b6 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Sat, 8 May 2010 17:20:38 +0200 Subject: HID: roccat: Correctly mark init and exit functions Added the __init and __exit hints for module functions. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 7b11784..a41df9a 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -989,12 +989,12 @@ static struct hid_driver kone_driver = { .raw_event = kone_raw_event }; -static int kone_init(void) +static int __init kone_init(void) { return hid_register_driver(&kone_driver); } -static void kone_exit(void) +static void __exit kone_exit(void) { hid_unregister_driver(&kone_driver); } -- cgit v0.10.2 From 73b3577d5dc80bf5f079ddd5c0449459a1997765 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Wed, 19 May 2010 13:53:22 +0200 Subject: HID: roccat: fix special button support Added new data and changed workaround for abnormal button behaviour according to new gained knowledge about Roccat Kone device. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index a41df9a..5d9ced0 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -928,14 +928,15 @@ static int kone_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; /* - * Firmware 1.38 introduced new behaviour for tilt buttons. - * Pressed tilt button is reported in each movement event. + * Firmware 1.38 introduced new behaviour for tilt and special buttons. + * Pressed button is reported in each movement event. * Workaround sends only one event per press. */ - if (kone->last_tilt_state == event->tilt) - event->tilt = 0; + if (memcmp(&kone->last_mouse_event.tilt, &event->tilt, 5)) + memcpy(&kone->last_mouse_event, event, + sizeof(struct kone_mouse_event)); else - kone->last_tilt_state = event->tilt; + memset(&event->tilt, 0, 5); /* * handle special events and keep actual profile and dpi values diff --git a/drivers/hid/hid-roccat-kone.h b/drivers/hid/hid-roccat-kone.h index ee6898c..1330954 100644 --- a/drivers/hid/hid-roccat-kone.h +++ b/drivers/hid/hid-roccat-kone.h @@ -83,6 +83,17 @@ enum kone_button_info_types { kone_button_info_type_multimedia_volume_down = 0x27 }; +enum kone_button_info_numbers { + kone_button_top = 1, + kone_button_wheel_tilt_left = 2, + kone_button_wheel_tilt_right = 3, + kone_button_forward = 4, + kone_button_backward = 5, + kone_button_middle = 6, + kone_button_plus = 7, + kone_button_minus = 8, +}; + struct kone_light_info { uint8_t number; /* number of light 1-5 */ uint8_t mod; /* 1 = on, 2 = off */ @@ -120,6 +131,7 @@ struct kone_profile { uint8_t light_effect_speed; /* range 0-255 */ struct kone_light_info light_infos[5]; + /* offset is kone_button_info_numbers - 1 */ struct kone_button_info button_infos[8]; uint16_t checksum; /* \brief holds checksum of struct */ @@ -165,7 +177,7 @@ enum kone_mouse_events { /* TODO clarify meaning and occurence of kone_mouse_event_calibration */ kone_mouse_event_calibration = 0xc0, kone_mouse_event_call_overlong_macro = 0xe0, - /* switch events notify if user changed values wiht mousebutton click */ + /* switch events notify if user changed values with mousebutton click */ kone_mouse_event_switch_dpi = 0xf0, kone_mouse_event_switch_profile = 0xf1 }; @@ -188,8 +200,9 @@ struct kone_device { * is no way of getting this information from the device on demand */ int actual_profile, actual_dpi; - /* Used for neutralizing abnormal tilt button behaviour */ - int last_tilt_state; + /* Used for neutralizing abnormal button behaviour */ + struct kone_mouse_event last_mouse_event; + /* * It's unlikely that multiple sysfs attributes are accessed at a time, * so only one mutex is used to secure hardware access and profiles and -- cgit v0.10.2 From 48e70804d37f9c52aab7c4ce7b7ab7bc7b800099 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Tue, 18 May 2010 18:31:04 +0200 Subject: HID: roccat: refactor special event handling As special events are reported along with hid event information all events are now processed further by standard handler. Also cleaned up this code. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 5d9ced0..8f35fe2 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -912,6 +912,24 @@ static void kone_remove(struct hid_device *hdev) hid_hw_stop(hdev); } +/* handle special events and keep actual profile and dpi values up to date */ +static void kone_keep_values_up_to_date(struct kone_device *kone, + struct kone_mouse_event const *event) +{ + switch (event->event) { + case kone_mouse_event_switch_profile: + case kone_mouse_event_osd_profile: + kone->actual_profile = event->value; + kone->actual_dpi = kone->profiles[kone->actual_profile - 1]. + startup_dpi; + break; + case kone_mouse_event_switch_dpi: + case kone_mouse_event_osd_dpi: + kone->actual_dpi = event->value; + break; + } +} + /* * Is called for keyboard- and mousepart. * Only mousepart gets informations about special events in its extended event @@ -938,41 +956,9 @@ static int kone_raw_event(struct hid_device *hdev, struct hid_report *report, else memset(&event->tilt, 0, 5); - /* - * handle special events and keep actual profile and dpi values - * up to date - */ - switch (event->event) { - case kone_mouse_event_osd_dpi: - dev_dbg(&hdev->dev, "osd dpi event. actual dpi %d\n", - event->value); - return 1; /* return 1 if event was handled */ - case kone_mouse_event_switch_dpi: - kone->actual_dpi = event->value; - dev_dbg(&hdev->dev, "switched dpi to %d\n", event->value); - return 1; - case kone_mouse_event_osd_profile: - dev_dbg(&hdev->dev, "osd profile event. actual profile %d\n", - event->value); - return 1; - case kone_mouse_event_switch_profile: - kone->actual_profile = event->value; - kone->actual_dpi = kone->profiles[kone->actual_profile - 1]. - startup_dpi; - dev_dbg(&hdev->dev, "switched profile to %d\n", event->value); - return 1; - case kone_mouse_event_call_overlong_macro: - dev_dbg(&hdev->dev, "overlong macro called, button %d %s/%s\n", - event->macro_key, - kone->profiles[kone->actual_profile - 1]. - button_infos[event->macro_key].macro_set_name, - kone->profiles[kone->actual_profile - 1]. - button_infos[event->macro_key].macro_name - ); - return 1; - } + kone_keep_values_up_to_date(kone, event); - return 0; /* do further processing */ + return 0; /* always do further processing */ } static const struct hid_device_id kone_devices[] = { -- cgit v0.10.2 From 1f749d8d5f92c275e35cdcd1fdcb7c8298157118 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Wed, 12 May 2010 17:43:34 +0200 Subject: HID: roccat: cleanup preprocessor macros Removed useless preprocessor macros and renamed remaining one to be more qualified. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 8f35fe2..66e6940 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -630,7 +630,7 @@ static ssize_t kone_sysfs_set_startup_profile(struct device *dev, static ssize_t kone_sysfs_show_driver_version(struct device *dev, struct device_attribute *attr, char *buf) { - return snprintf(buf, PAGE_SIZE, DRIVER_VERSION "\n"); + return snprintf(buf, PAGE_SIZE, ROCCAT_KONE_DRIVER_VERSION "\n"); } /* @@ -989,6 +989,6 @@ static void __exit kone_exit(void) module_init(kone_init); module_exit(kone_exit); -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE(DRIVER_LICENSE); +MODULE_AUTHOR("Stefan Achatz"); +MODULE_DESCRIPTION("USB Roccat Kone driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-roccat-kone.h b/drivers/hid/hid-roccat-kone.h index 1330954..b413b10 100644 --- a/drivers/hid/hid-roccat-kone.h +++ b/drivers/hid/hid-roccat-kone.h @@ -14,10 +14,7 @@ #include -#define DRIVER_VERSION "v0.3.0" -#define DRIVER_AUTHOR "Stefan Achatz" -#define DRIVER_DESC "USB Roccat Kone driver" -#define DRIVER_LICENSE "GPL v2" +#define ROCCAT_KONE_DRIVER_VERSION "v0.3.1" #pragma pack(push) #pragma pack(1) -- cgit v0.10.2