diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-05-21 10:51:03 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-05-21 10:51:03 -0700 |
commit | 8b108c609adefd98577c35f0a41497a610041a6c (patch) | |
tree | f1552fdc5bf0ebcc484a88f01cd3864113adf25c /drivers | |
parent | 7ce1418f95e918cfc5ad36e3ec3431145c768cd0 (diff) | |
parent | 73d5e8f77e88a4d3a154dfdbb4ed2cf461b7bf21 (diff) | |
download | linux-8b108c609adefd98577c35f0a41497a610041a6c.tar.bz2 |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (59 commits)
HID: fix up 'EMBEDDED' mess in Kconfig
HID: roccat: cleanup preprocessor macros
HID: roccat: refactor special event handling
HID: roccat: fix special button support
HID: roccat: Correctly mark init and exit functions
HID: hidraw: Use Interrupt Endpoint for OUT Transfers if Available
HID: hid-samsung: remove redundant key mappings
HID: add omitted hid-zydacron.c file
HID: hid-samsung: add support for Creative Desktop Wireless 6000
HID: picolcd: Eliminate use after free
HID: Zydacron Remote Control driver
HID: Use kmemdup
HID: magicmouse: fix input registration
HID: make Prodikeys driver standalone config option
HID: Prodikeys PC-MIDI HID Driver
HID: hidraw: fix indentation
HID: ntrig: add filtering module parameters
HID: ntrig: add sysfs access to filter parameters
HID: ntrig: add sensitivity and responsiveness support
HID: add multi-input quirk for eGalax Touchcontroller
...
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hid/Kconfig | 151 | ||||
-rw-r--r-- | drivers/hid/Makefile | 6 | ||||
-rw-r--r-- | drivers/hid/hid-3m-pct.c | 31 | ||||
-rw-r--r-- | drivers/hid/hid-cando.c | 272 | ||||
-rw-r--r-- | drivers/hid/hid-core.c | 53 | ||||
-rw-r--r-- | drivers/hid/hid-egalax.c | 281 | ||||
-rw-r--r-- | drivers/hid/hid-ids.h | 25 | ||||
-rw-r--r-- | drivers/hid/hid-lg.c | 9 | ||||
-rw-r--r-- | drivers/hid/hid-magicmouse.c | 5 | ||||
-rw-r--r-- | drivers/hid/hid-ntrig.c | 526 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd.c | 2631 | ||||
-rw-r--r-- | drivers/hid/hid-prodikeys.c | 910 | ||||
-rw-r--r-- | drivers/hid/hid-roccat-kone.c | 994 | ||||
-rw-r--r-- | drivers/hid/hid-roccat-kone.h | 224 | ||||
-rw-r--r-- | drivers/hid/hid-samsung.c | 95 | ||||
-rw-r--r-- | drivers/hid/hid-topseed.c | 38 | ||||
-rw-r--r-- | drivers/hid/hid-wacom.c | 229 | ||||
-rw-r--r-- | drivers/hid/hid-zydacron.c | 237 | ||||
-rw-r--r-- | drivers/hid/hidraw.c | 50 | ||||
-rw-r--r-- | drivers/hid/usbhid/hid-core.c | 73 | ||||
-rw-r--r-- | drivers/hid/usbhid/hid-quirks.c | 1 | ||||
-rw-r--r-- | drivers/hid/usbhid/hiddev.c | 19 | ||||
-rw-r--r-- | drivers/hid/usbhid/usbkbd.c | 1 |
23 files changed, 6704 insertions, 157 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 71d4c0703629..76ba59b9fea1 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -86,6 +86,12 @@ config HID_BELKIN ---help--- Support for Belkin Flip KVM and Wireless keyboard. +config HID_CANDO + tristate "Cando dual touch panel" + depends on USB_HID + ---help--- + Support for Cando dual touch panel. + config HID_CHERRY tristate "Cherry" if EMBEDDED depends on USB_HID @@ -100,6 +106,21 @@ config HID_CHICONY ---help--- Support for Chicony Tactical pad. +config HID_PRODIKEYS + tristate "Prodikeys PC-MIDI Keyboard support" + depends on USB_HID && SND + select SND_RAWMIDI + ---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 @@ -108,9 +129,8 @@ config HID_CYPRESS Support for cypress mouse and barcode readers. config HID_DRAGONRISE - tristate "DragonRise Inc. support" if EMBEDDED + tristate "DragonRise Inc. support" depends on USB_HID - default !EMBEDDED ---help--- Say Y here if you have DragonRise Inc.game controllers. @@ -122,6 +142,12 @@ config DRAGONRISE_FF Say Y here if you want to enable force feedback support for DragonRise Inc. game controllers. +config HID_EGALAX + tristate "eGalax multi-touch panel" + depends on USB_HID + ---help--- + Support for the eGalax dual-touch panel. + config HID_EZKEY tristate "Ezkey" if EMBEDDED depends on USB_HID @@ -137,16 +163,14 @@ config HID_KYE Support for Kye/Genius Ergo Mouse. config HID_GYRATION - tristate "Gyration" if EMBEDDED + tristate "Gyration" depends on USB_HID - default !EMBEDDED ---help--- Support for Gyration remote control. config HID_TWINHAN - tristate "Twinhan" if EMBEDDED + tristate "Twinhan" depends on USB_HID - default !EMBEDDED ---help--- Support for Twinhan IR remote control. @@ -233,16 +257,14 @@ config HID_NTRIG Support for N-Trig touch screen. config HID_ORTEK - tristate "Ortek" if EMBEDDED + tristate "Ortek" depends on USB_HID - default !EMBEDDED ---help--- Support for Ortek WKB-2000 wireless keyboard + mouse trackpad. config HID_PANTHERLORD - tristate "Pantherlord support" if EMBEDDED + tristate "Pantherlord support" depends on USB_HID - default !EMBEDDED ---help--- Say Y here if you have a PantherLord/GreenAsia based game controller or adapter. @@ -256,29 +278,90 @@ config PANTHERLORD_FF or adapter and want to enable force feedback support for it. config HID_PETALYNX - tristate "Petalynx" if EMBEDDED + tristate "Petalynx" depends on USB_HID - default !EMBEDDED ---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 + - 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 ---help--- Support for Quanta Optical Touch dual-touch panels. +config HID_ROCCAT_KONE + tristate "Roccat Kone Mouse support" + depends on USB_HID + ---help--- + Support for Roccat Kone mouse. + config HID_SAMSUNG - tristate "Samsung" if EMBEDDED + tristate "Samsung" depends on USB_HID - default !EMBEDDED ---help--- - Support for Samsung InfraRed remote control. + Support for Samsung InfraRed remote control or keyboards. config HID_SONY - tristate "Sony" if EMBEDDED + tristate "Sony" depends on USB_HID - default !EMBEDDED ---help--- Support for Sony PS3 controller. @@ -289,16 +372,14 @@ config HID_STANTUM Support for Stantum multitouch panel. config HID_SUNPLUS - tristate "Sunplus" if EMBEDDED + tristate "Sunplus" depends on USB_HID - default !EMBEDDED ---help--- Support for Sunplus wireless desktop. config HID_GREENASIA - tristate "GreenAsia (Product ID 0x12) support" if EMBEDDED + tristate "GreenAsia (Product ID 0x12) support" depends on USB_HID - default !EMBEDDED ---help--- Say Y here if you have a GreenAsia (Product ID 0x12) based game controller or adapter. @@ -313,9 +394,8 @@ config GREENASIA_FF and want to enable force feedback support for it. config HID_SMARTJOYPLUS - tristate "SmartJoy PLUS PS2/USB adapter support" if EMBEDDED + tristate "SmartJoy PLUS PS2/USB adapter support" depends on USB_HID - default !EMBEDDED ---help--- Support for SmartJoy PLUS PS2/USB adapter. @@ -328,16 +408,14 @@ config SMARTJOYPLUS_FF enable force feedback support for it. config HID_TOPSEED - tristate "TopSeed Cyberlink remote control support" if EMBEDDED + tristate "TopSeed Cyberlink remote control support" depends on USB_HID - default !EMBEDDED ---help--- - Say Y if you have a TopSeed Cyberlink remote control. + Say Y if you have a TopSeed Cyberlink or BTC Emprex remote control. config HID_THRUSTMASTER - tristate "ThrustMaster devices support" if EMBEDDED + tristate "ThrustMaster devices support" depends on USB_HID - default !EMBEDDED ---help--- Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or a THRUSTMASTER Ferrari GT Rumble Wheel. @@ -357,10 +435,17 @@ config HID_WACOM ---help--- Support for Wacom Graphire Bluetooth tablet. +config HID_WACOM_POWER_SUPPLY + bool "Wacom Bluetooth devices power supply status support" + depends on HID_WACOM + select POWER_SUPPLY + ---help--- + Say Y here if you want to enable power supply status monitoring for + Wacom Bluetooth devices. + config HID_ZEROPLUS - tristate "Zeroplus based game controller support" if EMBEDDED + tristate "Zeroplus based game controller support" depends on USB_HID - default !EMBEDDED ---help--- Say Y here if you have a Zeroplus based game controller. @@ -372,6 +457,12 @@ config ZEROPLUS_FF Say Y here if you have a Zeroplus based game controller and want to have force feedback support for it. +config HID_ZYDACRON + tristate "Zydacron remote control support" + depends on USB_HID + ---help--- + Support for Zydacron remote control. + endmenu endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0b2618f092ca..22e47eaeea32 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -26,10 +26,12 @@ obj-$(CONFIG_HID_3M_PCT) += hid-3m-pct.o obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o obj-$(CONFIG_HID_APPLE) += hid-apple.o obj-$(CONFIG_HID_BELKIN) += hid-belkin.o +obj-$(CONFIG_HID_CANDO) += hid-cando.o obj-$(CONFIG_HID_CHERRY) += hid-cherry.o obj-$(CONFIG_HID_CHICONY) += hid-chicony.o obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o obj-$(CONFIG_HID_DRAGONRISE) += hid-drff.o +obj-$(CONFIG_HID_EGALAX) += hid-egalax.o obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o obj-$(CONFIG_HID_GYRATION) += hid-gyration.o obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o @@ -41,9 +43,12 @@ 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 +obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.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 @@ -54,6 +59,7 @@ obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o +obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o obj-$(CONFIG_HID_WACOM) += hid-wacom.o obj-$(CONFIG_USB_HID) += usbhid/ diff --git a/drivers/hid/hid-3m-pct.c b/drivers/hid/hid-3m-pct.c index c31e0be8ccea..2a0d56b7a02b 100644 --- a/drivers/hid/hid-3m-pct.c +++ b/drivers/hid/hid-3m-pct.c @@ -1,7 +1,7 @@ /* * HID driver for 3M PCT multitouch panels * - * Copyright (c) 2009 Stephane Chatty <chatty@enac.fr> + * Copyright (c) 2009-2010 Stephane Chatty <chatty@enac.fr> * */ @@ -25,7 +25,7 @@ MODULE_LICENSE("GPL"); #include "hid-ids.h" struct mmm_finger { - __s32 x, y; + __s32 x, y, w, h; __u8 rank; bool touch, valid; }; @@ -82,7 +82,18 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, /* touchscreen emulation */ hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH); return 1; + case HID_DG_WIDTH: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_TOUCH_MAJOR); + return 1; + case HID_DG_HEIGHT: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_TOUCH_MINOR); + input_set_abs_params(hi->input, ABS_MT_ORIENTATION, + 1, 1, 0, 0); + return 1; case HID_DG_CONTACTID: + field->logical_maximum = 59; hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_TRACKING_ID); return 1; @@ -128,9 +139,15 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) /* this finger is just placeholder data, ignore */ } else if (f->touch) { /* this finger is on the screen */ + int wide = (f->w > f->h); input_event(input, EV_ABS, ABS_MT_TRACKING_ID, i); input_event(input, EV_ABS, ABS_MT_POSITION_X, f->x); input_event(input, EV_ABS, ABS_MT_POSITION_Y, f->y); + input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide); + input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, + wide ? f->w : f->h); + input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, + wide ? f->h : f->w); input_mt_sync(input); /* * touchscreen emulation: maintain the age rank @@ -197,6 +214,14 @@ static int mmm_event(struct hid_device *hid, struct hid_field *field, case HID_DG_CONFIDENCE: md->valid = value; break; + case HID_DG_WIDTH: + if (md->valid) + md->f[md->curid].w = value; + break; + case HID_DG_HEIGHT: + if (md->valid) + md->f[md->curid].h = value; + break; case HID_DG_CONTACTID: if (md->valid) { md->curid = value; @@ -255,6 +280,7 @@ static void mmm_remove(struct hid_device *hdev) static const struct hid_device_id mmm_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) }, + { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M2256) }, { } }; MODULE_DEVICE_TABLE(hid, mmm_devices); @@ -287,5 +313,4 @@ static void __exit mmm_exit(void) module_init(mmm_init); module_exit(mmm_exit); -MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-cando.c b/drivers/hid/hid-cando.c new file mode 100644 index 000000000000..4267a6fdc277 --- /dev/null +++ b/drivers/hid/hid-cando.c @@ -0,0 +1,272 @@ +/* + * HID driver for Cando dual-touch panels + * + * Copyright (c) 2010 Stephane Chatty <chatty@enac.fr> + * + */ + +/* + * 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 <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/slab.h> + +MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>"); +MODULE_DESCRIPTION("Cando dual-touch panel"); +MODULE_LICENSE("GPL"); + +#include "hid-ids.h" + +struct cando_data { + __u16 x, y; + __u8 id; + __s8 oldest; /* id of the oldest finger in previous frame */ + bool valid; /* valid finger data, or just placeholder? */ + bool first; /* is this the first finger in this frame? */ + __s8 firstid; /* id of the first finger in the frame */ + __u16 firstx, firsty; /* (x, y) of the first finger in the frame */ +}; + +static int cando_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + switch (usage->hid & HID_USAGE_PAGE) { + + case HID_UP_GENDESK: + switch (usage->hid) { + case HID_GD_X: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_POSITION_X); + /* touchscreen emulation */ + input_set_abs_params(hi->input, ABS_X, + field->logical_minimum, + field->logical_maximum, 0, 0); + return 1; + case HID_GD_Y: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_POSITION_Y); + /* touchscreen emulation */ + input_set_abs_params(hi->input, ABS_Y, + field->logical_minimum, + field->logical_maximum, 0, 0); + return 1; + } + return 0; + + case HID_UP_DIGITIZER: + switch (usage->hid) { + case HID_DG_TIPSWITCH: + case HID_DG_CONTACTMAX: + return -1; + case HID_DG_INRANGE: + /* touchscreen emulation */ + hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH); + return 1; + case HID_DG_CONTACTID: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_TRACKING_ID); + return 1; + } + return 0; + } + + return 0; +} + +static int cando_input_mapped(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if (usage->type == EV_KEY || usage->type == EV_ABS) + clear_bit(usage->code, *bit); + + return 0; +} + +/* + * this function is called when a whole finger has been parsed, + * so that it can decide what to send to the input layer. + */ +static void cando_filter_event(struct cando_data *td, struct input_dev *input) +{ + td->first = !td->first; /* touchscreen emulation */ + + if (!td->valid) { + /* + * touchscreen emulation: if this is the second finger and + * the first was valid, the first was the oldest; if the + * first was not valid and there was a valid finger in the + * previous frame, this is a release. + */ + if (td->first) { + td->firstid = -1; + } else if (td->firstid >= 0) { + input_event(input, EV_ABS, ABS_X, td->firstx); + input_event(input, EV_ABS, ABS_Y, td->firsty); + td->oldest = td->firstid; + } else if (td->oldest >= 0) { + input_event(input, EV_KEY, BTN_TOUCH, 0); + td->oldest = -1; + } + + return; + } + + input_event(input, EV_ABS, ABS_MT_TRACKING_ID, td->id); + input_event(input, EV_ABS, ABS_MT_POSITION_X, td->x); + input_event(input, EV_ABS, ABS_MT_POSITION_Y, td->y); + + input_mt_sync(input); + + /* + * touchscreen emulation: if there was no touching finger previously, + * emit touch event + */ + if (td->oldest < 0) { + input_event(input, EV_KEY, BTN_TOUCH, 1); + td->oldest = td->id; + } + + /* + * touchscreen emulation: if this is the first finger, wait for the + * second; the oldest is then the second if it was the oldest already + * or if there was no first, the first otherwise. + */ + if (td->first) { + td->firstx = td->x; + td->firsty = td->y; + td->firstid = td->id; + } else { + int x, y, oldest; + if (td->id == td->oldest || td->firstid < 0) { + x = td->x; + y = td->y; + oldest = td->id; + } else { + x = td->firstx; + y = td->firsty; + oldest = td->firstid; + } + input_event(input, EV_ABS, ABS_X, x); + input_event(input, EV_ABS, ABS_Y, y); + td->oldest = oldest; + } +} + + +static int cando_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct cando_data *td = hid_get_drvdata(hid); + + if (hid->claimed & HID_CLAIMED_INPUT) { + struct input_dev *input = field->hidinput->input; + + switch (usage->hid) { + case HID_DG_INRANGE: + td->valid = value; + break; + case HID_DG_CONTACTID: + td->id = value; + break; + case HID_GD_X: + td->x = value; + break; + case HID_GD_Y: + td->y = value; + cando_filter_event(td, input); + break; + case HID_DG_TIPSWITCH: + /* avoid interference from generic hidinput handling */ + break; + + default: + /* fallback to the generic hidinput handling */ + return 0; + } + } + + /* we have handled the hidinput part, now remains hiddev */ + if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event) + hid->hiddev_hid_event(hid, field, usage, value); + + return 1; +} + +static int cando_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct cando_data *td; + + td = kmalloc(sizeof(struct cando_data), GFP_KERNEL); + if (!td) { + dev_err(&hdev->dev, "cannot allocate Cando Touch data\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, td); + td->first = false; + td->oldest = -1; + td->valid = false; + + ret = hid_parse(hdev); + if (!ret) + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + + if (ret) + kfree(td); + + return ret; +} + +static void cando_remove(struct hid_device *hdev) +{ + hid_hw_stop(hdev); + kfree(hid_get_drvdata(hdev)); + hid_set_drvdata(hdev, NULL); +} + +static const struct hid_device_id cando_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, + USB_DEVICE_ID_CANDO_MULTI_TOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, + USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6) }, + { } +}; +MODULE_DEVICE_TABLE(hid, cando_devices); + +static const struct hid_usage_id cando_grabbed_usages[] = { + { HID_ANY_ID, HID_ANY_ID, HID_ANY_ID }, + { HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1} +}; + +static struct hid_driver cando_driver = { + .name = "cando-touch", + .id_table = cando_devices, + .probe = cando_probe, + .remove = cando_remove, + .input_mapping = cando_input_mapping, + .input_mapped = cando_input_mapped, + .usage_table = cando_grabbed_usages, + .event = cando_event, +}; + +static int __init cando_init(void) +{ + return hid_register_driver(&cando_driver); +} + +static void __exit cando_exit(void) +{ + hid_unregister_driver(&cando_driver); +} + +module_init(cando_init); +module_exit(cando_exit); + diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 143e788b729b..e10e314d38cc 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -653,10 +653,9 @@ int hid_parse_report(struct hid_device *device, __u8 *start, if (device->driver->report_fixup) device->driver->report_fixup(device, start, size); - device->rdesc = kmalloc(size, GFP_KERNEL); + device->rdesc = kmemdup(start, size, GFP_KERNEL); if (device->rdesc == NULL) return -ENOMEM; - memcpy(device->rdesc, start, size); device->rsize = size; parser = vmalloc(sizeof(struct hid_parser)); @@ -940,13 +939,8 @@ static void hid_output_field(struct hid_field *field, __u8 *data) unsigned count = field->report_count; unsigned offset = field->report_offset; unsigned size = field->report_size; - unsigned bitsused = offset + count * size; unsigned n; - /* make sure the unused bits in the last byte are zeros */ - if (count > 0 && size > 0 && (bitsused % 8) != 0) - data[(bitsused-1)/8] &= (1 << (bitsused % 8)) - 1; - for (n = 0; n < count; n++) { if (field->logical_minimum < 0) /* signed values */ implement(data, offset + n * size, size, s32ton(field->value[n], size)); @@ -966,6 +960,7 @@ void hid_output_report(struct hid_report *report, __u8 *data) if (report->id > 0) *data++ = report->id; + memset(data, 0, ((report->size - 1) >> 3) + 1); for (n = 0; n < report->maxfield; n++) hid_output_field(report->field[n], data); } @@ -1086,35 +1081,28 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC); - if (!buf) { - report = hid_get_report(report_enum, data); + if (!buf) goto nomem; - } - - snprintf(buf, HID_DEBUG_BUFSIZE - 1, - "\nreport (size %u) (%snumbered)\n", size, report_enum->numbered ? "" : "un"); - hid_debug_event(hid, buf); - - report = hid_get_report(report_enum, data); - if (!report) { - kfree(buf); - return -1; - } /* dump the report */ snprintf(buf, HID_DEBUG_BUFSIZE - 1, - "report %d (size %u) = ", report->id, size); + "\nreport (size %u) (%snumbered) = ", size, report_enum->numbered ? "" : "un"); hid_debug_event(hid, buf); + for (i = 0; i < size; i++) { snprintf(buf, HID_DEBUG_BUFSIZE - 1, " %02x", data[i]); hid_debug_event(hid, buf); } hid_debug_event(hid, "\n"); - kfree(buf); nomem: + report = hid_get_report(report_enum, data); + + if (!report) + return -1; + if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) { ret = hdrv->raw_event(hid, report, data, size); if (ret != 0) @@ -1167,6 +1155,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) unsigned int i; int len; + if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE) + connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV); if (hdev->bus != BUS_USB) connect_mask &= ~HID_CONNECT_HIDDEV; if (hid_hiddev(hdev)) @@ -1246,6 +1236,7 @@ EXPORT_SYMBOL_GPL(hid_disconnect); /* a list of devices for which there is a specialized driver on HID bus */ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) }, + { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M2256) }, { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) }, { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL) }, @@ -1290,14 +1281,19 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) }, + { HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6) }, { 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) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193) }, { HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR) }, { HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR) }, @@ -1331,6 +1327,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) }, @@ -1342,7 +1340,9 @@ 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_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) }, { 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) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, @@ -1359,8 +1359,10 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH) }, { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) }, { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT) }, { } @@ -1757,7 +1759,7 @@ int hid_add_device(struct hid_device *hdev) /* we need to kill them here, otherwise they will stay allocated to * wait for coming driver */ - if (hid_ignore(hdev)) + if (!(hdev->quirks & HID_QUIRK_NO_IGNORE) && hid_ignore(hdev)) return -ENODEV; /* XXX hack, any other cleaner solution after the driver core @@ -1765,11 +1767,12 @@ int hid_add_device(struct hid_device *hdev) dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus, hdev->vendor, hdev->product, atomic_inc_return(&id)); + hid_debug_register(hdev, dev_name(&hdev->dev)); ret = device_add(&hdev->dev); if (!ret) hdev->status |= HID_STAT_ADDED; - - hid_debug_register(hdev, dev_name(&hdev->dev)); + else + hid_debug_unregister(hdev); return ret; } diff --git a/drivers/hid/hid-egalax.c b/drivers/hid/hid-egalax.c new file mode 100644 index 000000000000..f44bdc084cb2 --- /dev/null +++ b/drivers/hid/hid-egalax.c @@ -0,0 +1,281 @@ +/* + * HID driver for eGalax dual-touch panels + * + * Copyright (c) 2010 Stephane Chatty <chatty@enac.fr> + * + */ + +/* + * 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 <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include "usbhid/usbhid.h" + +MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>"); +MODULE_DESCRIPTION("eGalax dual-touch panel"); +MODULE_LICENSE("GPL"); + +#include "hid-ids.h" + +struct egalax_data { + __u16 x, y, z; + __u8 id; + bool first; /* is this the first finger in the frame? */ + bool valid; /* valid finger data, or just placeholder? */ + bool activity; /* at least one active finger previously? */ + __u16 lastx, lasty; /* latest valid (x, y) in the frame */ +}; + +static int egalax_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + switch (usage->hid & HID_USAGE_PAGE) { + + case HID_UP_GENDESK: + switch (usage->hid) { + case HID_GD_X: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_POSITION_X); + /* touchscreen emulation */ + input_set_abs_params(hi->input, ABS_X, + field->logical_minimum, + field->logical_maximum, 0, 0); + return 1; + case HID_GD_Y: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_POSITION_Y); + /* touchscreen emulation */ + input_set_abs_params(hi->input, ABS_Y, + field->logical_minimum, + field->logical_maximum, 0, 0); + return 1; + } + return 0; + + case HID_UP_DIGITIZER: + switch (usage->hid) { + case HID_DG_TIPSWITCH: + /* touchscreen emulation */ + hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH); + return 1; + case HID_DG_INRANGE: + case HID_DG_CONFIDENCE: + case HID_DG_CONTACTCOUNT: + case HID_DG_CONTACTMAX: + return -1; + case HID_DG_CONTACTID: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_TRACKING_ID); + return 1; + case HID_DG_TIPPRESSURE: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_PRESSURE); + return 1; + } + return 0; + } + + /* ignore others (from other reports we won't get anyway) */ + return -1; +} + +static int egalax_input_mapped(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if (usage->type == EV_KEY || usage->type == EV_ABS) + clear_bit(usage->code, *bit); + + return 0; +} + +/* + * this function is called when a whole finger has been parsed, + * so that it can decide what to send to the input layer. + */ +static void egalax_filter_event(struct egalax_data *td, struct input_dev *input) +{ + td->first = !td->first; /* touchscreen emulation */ + + if (td->valid) { + /* emit multitouch events */ + input_event(input, EV_ABS, ABS_MT_TRACKING_ID, td->id); + input_event(input, EV_ABS, ABS_MT_POSITION_X, td->x); + input_event(input, EV_ABS, ABS_MT_POSITION_Y, td->y); + input_event(input, EV_ABS, ABS_MT_PRESSURE, td->z); + + input_mt_sync(input); + + /* + * touchscreen emulation: store (x, y) as + * the last valid values in this frame + */ + td->lastx = td->x; + td->lasty = td->y; + } + + /* + * touchscreen emulation: if this is the second finger and at least + * one in this frame is valid, the latest valid in the frame is + * the oldest on the panel, the one we want for single touch + */ + if (!td->first && td->activity) { + input_event(input, EV_ABS, ABS_X, td->lastx); + input_event(input, EV_ABS, ABS_Y, td->lasty); + } + + if (!td->valid) { + /* + * touchscreen emulation: if the first finger is invalid + * and there previously was finger activity, this is a release + */ + if (td->first && td->activity) { + input_event(input, EV_KEY, BTN_TOUCH, 0); + td->activity = false; + } + return; + } + + + /* touchscreen emulation: if no previous activity, emit touch event */ + if (!td->activity) { + input_event(input, EV_KEY, BTN_TOUCH, 1); + td->activity = true; + } +} + + +static int egalax_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct egalax_data *td = hid_get_drvdata(hid); + + if (hid->claimed & HID_CLAIMED_INPUT) { + struct input_dev *input = field->hidinput->input; + + switch (usage->hid) { + case HID_DG_INRANGE: + case HID_DG_CONFIDENCE: + /* avoid interference from generic hidinput handling */ + break; + case HID_DG_TIPSWITCH: + td->valid = value; + break; + case HID_DG_TIPPRESSURE: + td->z = value; + break; + case HID_DG_CONTACTID: + td->id = value; + break; + case HID_GD_X: + td->x = value; + break; + case HID_GD_Y: + td->y = value; + /* this is the last field in a finger */ + egalax_filter_event(td, input); + break; + case HID_DG_CONTACTCOUNT: + /* touch emulation: this is the last field in a frame */ + td->first = false; + break; + + default: + /* fallback to the generic hidinput handling */ + return 0; + } + } + + /* we have handled the hidinput part, now remains hiddev */ + if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event) + hid->hiddev_hid_event(hid, field, usage, value); + + return 1; +} + +static int egalax_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct egalax_data *td; + struct hid_report *report; + + td = kmalloc(sizeof(struct egalax_data), GFP_KERNEL); + if (!td) { + dev_err(&hdev->dev, "cannot allocate eGalax data\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, td); + + ret = hid_parse(hdev); + if (ret) + goto end; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + goto end; + + report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[5]; + if (report) { + report->field[0]->value[0] = 2; + usbhid_submit_report(hdev, report, USB_DIR_OUT); + } + +end: + if (ret) + kfree(td); + + return ret; +} + +static void egalax_remove(struct hid_device *hdev) +{ + hid_hw_stop(hdev); + kfree(hid_get_drvdata(hdev)); + hid_set_drvdata(hdev, NULL); +} + +static const struct hid_device_id egalax_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH) }, + { } +}; +MODULE_DEVICE_TABLE(hid, egalax_devices); + +static const struct hid_usage_id egalax_grabbed_usages[] = { + { HID_ANY_ID, HID_ANY_ID, HID_ANY_ID }, + { HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1} +}; + +static struct hid_driver egalax_driver = { + .name = "egalax-touch", + .id_table = egalax_devices, + .probe = egalax_probe, + .remove = egalax_remove, + .input_mapping = egalax_input_mapping, + .input_mapped = egalax_input_mapped, + .usage_table = egalax_grabbed_usages, + .event = egalax_event, +}; + +static int __init egalax_init(void) +{ + return hid_register_driver(&egalax_driver); +} + +static void __exit egalax_exit(void) +{ + hid_unregister_driver(&egalax_driver); +} + +module_init(egalax_init); +module_exit(egalax_exit); + diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 09d27649a0f7..9776896cc4fc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -20,6 +20,7 @@ #define USB_VENDOR_ID_3M 0x0596 #define USB_DEVICE_ID_3M1968 0x0500 +#define USB_DEVICE_ID_3M2256 0x0502 #define USB_VENDOR_ID_A4TECH 0x09da #define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 @@ -123,6 +124,13 @@ #define USB_VENDOR_ID_BERKSHIRE 0x0c98 #define USB_DEVICE_ID_BERKSHIRE_PCWD 0x1140 +#define USB_VENDOR_ID_BTC 0x046e +#define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578 + +#define USB_VENDOR_ID_CANDO 0x2087 +#define USB_DEVICE_ID_CANDO_MULTI_TOUCH 0x0a01 +#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6 0x0b03 + #define USB_VENDOR_ID_CH 0x068e #define USB_DEVICE_ID_CH_PRO_PEDALS 0x00f2 #define USB_DEVICE_ID_CH_COMBATSTICK 0x00f4 @@ -148,6 +156,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 @@ -171,6 +182,10 @@ #define USB_VENDOR_ID_DRAGONRISE 0x0079 +#define USB_VENDOR_ID_DWAV 0x0eef +#define USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER 0x0001 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH 0x480d + #define USB_VENDOR_ID_ELO 0x04E7 #define USB_DEVICE_ID_ELO_TS2700 0x0020 @@ -342,6 +357,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 @@ -400,6 +417,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 @@ -409,6 +429,7 @@ #define USB_VENDOR_ID_SAMSUNG 0x0419 #define USB_DEVICE_ID_SAMSUNG_IR_REMOTE 0x0001 +#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE 0x0600 #define USB_VENDOR_ID_SONY 0x054c #define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b @@ -457,6 +478,7 @@ #define USB_VENDOR_ID_WACOM 0x056a #define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81 +#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0xbd #define USB_VENDOR_ID_WISEGROUP 0x0925 #define USB_DEVICE_ID_SMARTJOY_PLUS 0x0005 @@ -475,6 +497,9 @@ #define USB_VENDOR_ID_ZEROPLUS 0x0c12 +#define USB_VENDOR_ID_ZYDACRON 0x13EC +#define USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL 0x0006 + #define USB_VENDOR_ID_KYE 0x0458 #define USB_DEVICE_ID_KYE_ERGO_525V 0x0087 #define USB_DEVICE_ID_KYE_GPEN_560 0x5003 diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 3677c9037a11..f6433d8050a9 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -126,6 +126,9 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, case 0x1004: lg_map_key_clear(KEY_VIDEO); break; case 0x1005: lg_map_key_clear(KEY_AUDIO); break; case 0x100a: lg_map_key_clear(KEY_DOCUMENTS); break; + /* The following two entries are Playlist 1 and 2 on the MX3200 */ + case 0x100f: lg_map_key_clear(KEY_FN_1); break; + case 0x1010: lg_map_key_clear(KEY_FN_2); break; case 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG); break; case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break; case 0x1013: lg_map_key_clear(KEY_CAMERA); break; @@ -137,6 +140,7 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, case 0x1019: lg_map_key_clear(KEY_PROG1); break; case 0x101a: lg_map_key_clear(KEY_PROG2); break; case 0x101b: lg_map_key_clear(KEY_PROG3); break; + case 0x101c: lg_map_key_clear(KEY_CYCLEWINDOWS); break; case 0x101f: lg_map_key_clear(KEY_ZOOMIN); break; case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break; case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break; @@ -147,6 +151,11 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break; case 0x102a: lg_map_key_clear(KEY_BACK); break; case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS); break; + case 0x102d: lg_map_key_clear(KEY_WWW); break; + /* The following two are 'Start/answer call' and 'End/reject call' + on the MX3200 */ + case 0x1031: lg_map_key_clear(KEY_OK); break; + case 0x1032: lg_map_key_clear(KEY_CANCEL); break; case 0x1041: lg_map_key_clear(KEY_BATTERY); break; case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break; case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break; diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 0d471fc2ab82..f10d56a15f21 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -354,12 +354,15 @@ static int magicmouse_probe(struct hid_device *hdev, goto err_free; } - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_HIDINPUT); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { dev_err(&hdev->dev, "magicmouse hw start failed\n"); goto err_free; } + /* we are handling the input ourselves */ + hidinput_disconnect(hdev); + report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID); if (!report) { dev_err(&hdev->dev, "unable to register touch report\n"); diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index 4777bbfa1cc2..b6b0caeeac58 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -24,6 +24,34 @@ #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 */ __u16 x, y, w, h; @@ -37,6 +65,309 @@ 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; +}; + + +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 }; /* @@ -49,6 +380,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 +395,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 +417,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 +564,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 +638,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 +666,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 +757,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 +787,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); @@ -344,6 +848,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: @@ -353,6 +859,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)); } diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c new file mode 100644 index 000000000000..7aabf65c48ef --- /dev/null +++ b/drivers/hid/hid-picolcd.c @@ -0,0 +1,2631 @@ +/*************************************************************************** + * Copyright (C) 2010 by Bruno Prémont <bonbons@linux-vserver.org> * + * * + * Based on Logitech G13 driver (v0.4) * + * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * + * * + * 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 <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include <linux/hid.h> +#include <linux/hid-debug.h> +#include <linux/input.h> +#include "hid-ids.h" +#include "usbhid/usbhid.h" +#include <linux/usb.h> + +#include <linux/fb.h> +#include <linux/vmalloc.h> +#include <linux/backlight.h> +#include <linux/lcd.h> + +#include <linux/leds.h> + +#include <linux/seq_file.h> +#include <linux/debugfs.h> + +#include <linux/completion.h> +#include <linux/uaccess.h> + +#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] */ + +#ifdef CONFIG_HID_PICOLCD_FB +/* 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_HID_PICOLCD_FB */ + +/* 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 + struct dentry *debug_reset; + struct dentry *debug_eeprom; + struct dentry *debug_flash; + struct mutex mutex_flash; + int addr_sz; +#endif + u8 version[2]; + unsigned short opmode_delay; + /* input stuff */ + u8 pressed_keys[2]; + struct input_dev *input_keys; + struct input_dev *input_cir; + unsigned short keycode[PICOLCD_KEYS]; + +#ifdef CONFIG_HID_PICOLCD_FB + /* 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_HID_PICOLCD_FB */ +#ifdef CONFIG_HID_PICOLCD_LCD + struct lcd_device *lcd; + u8 lcd_contrast; +#endif /* CONFIG_HID_PICOLCD_LCD */ +#ifdef CONFIG_HID_PICOLCD_BACKLIGHT + struct backlight_device *backlight; + u8 lcd_brightness; + u8 lcd_power; +#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ +#ifdef CONFIG_HID_PICOLCD_LEDS + /* LED stuff */ + u8 led_state; + struct led_classdev *led[8]; +#endif /* CONFIG_HID_PICOLCD_LEDS */ + + /* Housekeeping stuff */ + spinlock_t lock; + struct mutex mutex; + struct picolcd_pending *pending; + int status; +#define PICOLCD_BOOTLOADER 1 +#define PICOLCD_FAILED 2 +#define PICOLCD_READY_FB 4 +}; + + +/* 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; +} + +#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) +{ + 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); +} + +#define picolcd_fbinfo(d) ((d)->fb_info) +#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 inline void picolcd_exit_framebuffer(struct picolcd_data *data) +{ +} +#define picolcd_fbinfo(d) NULL +#endif /* CONFIG_HID_PICOLCD_FB */ + +#ifdef CONFIG_HID_PICOLCD_BACKLIGHT +/* + * 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); +} + +#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) +{ + return 0; +} +static inline void picolcd_exit_backlight(struct picolcd_data *data) +{ +} +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 +/* + * 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_HID_PICOLCD_LCD */ + +#ifdef CONFIG_HID_PICOLCD_LEDS +/** + * 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 inline void picolcd_exit_leds(struct picolcd_data *data) +{ +} +static inline int picolcd_leds_set(struct picolcd_data *data) +{ + return 0; +} +#endif /* CONFIG_HID_PICOLCD_LEDS */ + +/* + * 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) { + 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[1], verinfo->raw_data[0]); + } else { + dev_info(&hdev->dev, "PicoLCD, firmware version %d.%d\n", + verinfo->raw_data[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; + int error; + + 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); + + error = picolcd_check_version(hdev); + if (error) + return error; + + picolcd_resume_lcd(data); + picolcd_resume_backlight(data); +#ifdef CONFIG_HID_PICOLCD_FB + if (data->fb_info) + schedule_delayed_work(&data->fb_info->deferred_work, 0); +#endif /* CONFIG_HID_PICOLCD_FB */ + + picolcd_leds_set(data); + return 0; +} + +/* + * 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 = data->opmode_delay; + 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[cnt-1] == '\n' || buf[cnt-1] == '\r')) + cnt--; + if (cnt != 0) + return -EINVAL; + + 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); + +/* + * 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 +/* + * 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; + break; + } + 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[] = { + "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", + "<unknown>", 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", + "<unknown>", report->id, size-1); + hid_debug_event(hdev, buff); + break; + } + 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 +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 */ + +/* + * 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; +} + +#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) +{ + 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) +{ + 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; + + /* Set up the framebuffer device */ + error = picolcd_init_framebuffer(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) + goto err; + + /* Setup the LED class devices */ + error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev)); + if (error) + goto err; + + 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); + picolcd_exit_backlight(data); + picolcd_exit_lcd(data); + picolcd_exit_framebuffer(data); + picolcd_exit_cir(data); + picolcd_exit_keys(data); + return error; +} + +static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data) +{ + 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)); + + 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; +} + +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; + data->opmode_delay = 5000; + 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_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_sysfs2; + + dbg_hid(PICOLCD_NAME " activated and initialized\n"); + return 0; + +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: + 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); + + 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); + + /* 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 LED */ + picolcd_exit_leds(data); + /* Clean up the framebuffer */ + picolcd_exit_backlight(data); + picolcd_exit_lcd(data); + picolcd_exit_framebuffer(data); + /* 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, +#ifdef CONFIG_PM + .suspend = picolcd_suspend, + .resume = picolcd_resume, + .reset_resume = picolcd_reset_resume, +#endif +}; + +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"); diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c new file mode 100644 index 000000000000..845f428b8090 --- /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 <dhprince.devel@yahoo.co.uk> + * + * 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 <linux/device.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/hid.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> +#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"); diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c new file mode 100644 index 000000000000..66e694054ba2 --- /dev/null +++ b/drivers/hid/hid-roccat-kone.c @@ -0,0 +1,994 @@ +/* + * Roccat Kone driver for Linux + * + * Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * 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 <linux/device.h> +#include <linux/input.h> +#include <linux/hid.h> +#include <linux/usb.h> +#include <linux/module.h> +#include <linux/slab.h> +#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, ROCCAT_KONE_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); +} + +/* 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 + * 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 and special buttons. + * Pressed button is reported in each movement event. + * Workaround sends only one event per press. + */ + if (memcmp(&kone->last_mouse_event.tilt, &event->tilt, 5)) + memcpy(&kone->last_mouse_event, event, + sizeof(struct kone_mouse_event)); + else + memset(&event->tilt, 0, 5); + + kone_keep_values_up_to_date(kone, event); + + return 0; /* always 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 __init kone_init(void) +{ + return hid_register_driver(&kone_driver); +} + +static void __exit kone_exit(void) +{ + hid_unregister_driver(&kone_driver); +} + +module_init(kone_init); +module_exit(kone_exit); + +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 new file mode 100644 index 000000000000..b413b10a7f8a --- /dev/null +++ b/drivers/hid/hid-roccat-kone.h @@ -0,0 +1,224 @@ +#ifndef __HID_ROCCAT_KONE_H +#define __HID_ROCCAT_KONE_H + +/* + * Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * 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 <linux/types.h> + +#define ROCCAT_KONE_DRIVER_VERSION "v0.3.1" + +#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 +}; + +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 */ + 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]; + /* offset is kone_button_info_numbers - 1 */ + 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 with 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 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 + * 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 diff --git a/drivers/hid/hid-samsung.c b/drivers/hid/hid-samsung.c index 510dd1340597..bda0fd60c98d 100644 --- a/drivers/hid/hid-samsung.c +++ b/drivers/hid/hid-samsung.c @@ -7,6 +7,18 @@ * Copyright (c) 2006-2007 Jiri Kosina * Copyright (c) 2007 Paul Walmsley * Copyright (c) 2008 Jiri Slaby + * Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk> + * + * + * This driver supports several HID devices: + * + * [0419:0001] Samsung IrDA remote controller (reports as Cypress USB Mouse). + * various hid report fixups for different variants. + * + * [0419:0600] Creative Desktop Wireless 6000 keyboard/mouse combo + * several key mappings used from the consumer usage page + * deviate from the USB HUT 1.12 standard. + * */ /* @@ -17,14 +29,13 @@ */ #include <linux/device.h> +#include <linux/usb.h> #include <linux/hid.h> #include <linux/module.h> #include "hid-ids.h" /* - * Samsung IrDA remote controller (reports as Cypress USB Mouse). - * * There are several variants for 0419:0001: * * 1. 184 byte report descriptor @@ -43,21 +54,21 @@ * 4. 171 byte report descriptor * Report #3 has an array field with logical range 0..1 instead of 1..3. */ -static inline void samsung_dev_trace(struct hid_device *hdev, +static inline void samsung_irda_dev_trace(struct hid_device *hdev, unsigned int rsize) { dev_info(&hdev->dev, "fixing up Samsung IrDA %d byte report " "descriptor\n", rsize); } -static void samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc, +static void samsung_irda_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int rsize) { if (rsize == 184 && rdesc[175] == 0x25 && rdesc[176] == 0x40 && rdesc[177] == 0x75 && rdesc[178] == 0x30 && rdesc[179] == 0x95 && rdesc[180] == 0x01 && rdesc[182] == 0x40) { - samsung_dev_trace(hdev, 184); + samsung_irda_dev_trace(hdev, 184); rdesc[176] = 0xff; rdesc[178] = 0x08; rdesc[180] = 0x06; @@ -65,24 +76,80 @@ static void samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc, } else if (rsize == 203 && rdesc[192] == 0x15 && rdesc[193] == 0x0 && rdesc[194] == 0x25 && rdesc[195] == 0x12) { - samsung_dev_trace(hdev, 203); + samsung_irda_dev_trace(hdev, 203); rdesc[193] = 0x1; rdesc[195] = 0xf; } else if (rsize == 135 && rdesc[124] == 0x15 && rdesc[125] == 0x0 && rdesc[126] == 0x25 && rdesc[127] == 0x11) { - samsung_dev_trace(hdev, 135); + samsung_irda_dev_trace(hdev, 135); rdesc[125] = 0x1; rdesc[127] = 0xe; } else if (rsize == 171 && rdesc[160] == 0x15 && rdesc[161] == 0x0 && rdesc[162] == 0x25 && rdesc[163] == 0x01) { - samsung_dev_trace(hdev, 171); + samsung_irda_dev_trace(hdev, 171); rdesc[161] = 0x1; rdesc[163] = 0x3; } } +#define samsung_kbd_mouse_map_key_clear(c) \ + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) + +static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + + if (1 != ifnum || HID_UP_CONSUMER != (usage->hid & HID_USAGE_PAGE)) + return 0; + + dbg_hid("samsung wireless keyboard/mouse input mapping event [0x%x]\n", + usage->hid & HID_USAGE); + + switch (usage->hid & HID_USAGE) { + /* report 2 */ + case 0x183: samsung_kbd_mouse_map_key_clear(KEY_MEDIA); break; + case 0x195: samsung_kbd_mouse_map_key_clear(KEY_EMAIL); break; + case 0x196: samsung_kbd_mouse_map_key_clear(KEY_CALC); break; + case 0x197: samsung_kbd_mouse_map_key_clear(KEY_COMPUTER); break; + case 0x22b: samsung_kbd_mouse_map_key_clear(KEY_SEARCH); break; + case 0x22c: samsung_kbd_mouse_map_key_clear(KEY_WWW); break; + case 0x22d: samsung_kbd_mouse_map_key_clear(KEY_BACK); break; + case 0x22e: samsung_kbd_mouse_map_key_clear(KEY_FORWARD); break; + case 0x22f: samsung_kbd_mouse_map_key_clear(KEY_FAVORITES); break; + case 0x230: samsung_kbd_mouse_map_key_clear(KEY_REFRESH); break; + case 0x231: samsung_kbd_mouse_map_key_clear(KEY_STOP); break; + default: + return 0; + } + + return 1; +} + +static void samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int rsize) +{ + if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product) + samsung_irda_report_fixup(hdev, rdesc, rsize); +} + +static int samsung_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + int ret = 0; + + if (USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE == hdev->product) + ret = samsung_kbd_mouse_input_mapping(hdev, + hi, field, usage, bit, max); + + return ret; +} + static int samsung_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -95,10 +162,12 @@ static int samsung_probe(struct hid_device *hdev, goto err_free; } - if (hdev->rsize == 184) { - /* disable hidinput, force hiddev */ - cmask = (cmask & ~HID_CONNECT_HIDINPUT) | - HID_CONNECT_HIDDEV_FORCE; + if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product) { + if (hdev->rsize == 184) { + /* disable hidinput, force hiddev */ + cmask = (cmask & ~HID_CONNECT_HIDINPUT) | + HID_CONNECT_HIDDEV_FORCE; + } } ret = hid_hw_start(hdev, cmask); @@ -114,6 +183,7 @@ err_free: static const struct hid_device_id samsung_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) }, { } }; MODULE_DEVICE_TABLE(hid, samsung_devices); @@ -122,6 +192,7 @@ static struct hid_driver samsung_driver = { .name = "samsung", .id_table = samsung_devices, .report_fixup = samsung_report_fixup, + .input_mapping = samsung_input_mapping, .probe = samsung_probe, }; diff --git a/drivers/hid/hid-topseed.c b/drivers/hid/hid-topseed.c index 6925eda1081a..2eebdcc57bcf 100644 --- a/drivers/hid/hid-topseed.c +++ b/drivers/hid/hid-topseed.c @@ -3,6 +3,9 @@ * * Copyright (c) 2008 Lev Babiev * based on hid-cherry driver + * + * Modified to also support BTC "Emprex 3009URF III Vista MCE Remote" by + * Wayne Thomas 2010. */ /* @@ -24,23 +27,29 @@ static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { - if ((usage->hid & HID_USAGE_PAGE) != 0x0ffbc0000) + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR) return 0; switch (usage->hid & HID_USAGE) { - case 0x00d: ts_map_key_clear(KEY_HOME); break; - case 0x024: ts_map_key_clear(KEY_MENU); break; - case 0x025: ts_map_key_clear(KEY_TV); break; - case 0x048: ts_map_key_clear(KEY_RED); break; - case 0x047: ts_map_key_clear(KEY_GREEN); break; - case 0x049: ts_map_key_clear(KEY_YELLOW); break; - case 0x04a: ts_map_key_clear(KEY_BLUE); break; - case 0x04b: ts_map_key_clear(KEY_ANGLE); break; - case 0x04c: ts_map_key_clear(KEY_LANGUAGE); break; - case 0x04d: ts_map_key_clear(KEY_SUBTITLE); break; - case 0x031: ts_map_key_clear(KEY_AUDIO); break; - case 0x032: ts_map_key_clear(KEY_TEXT); break; - case 0x033: ts_map_key_clear(KEY_CHANNEL); break; + case 0x00d: ts_map_key_clear(KEY_MEDIA); break; + case 0x024: ts_map_key_clear(KEY_MENU); break; + case 0x025: ts_map_key_clear(KEY_TV); break; + case 0x031: ts_map_key_clear(KEY_AUDIO); break; + case 0x032: ts_map_key_clear(KEY_TEXT); break; + case 0x033: ts_map_key_clear(KEY_CHANNEL); break; + case 0x047: ts_map_key_clear(KEY_MP3); break; + case 0x048: ts_map_key_clear(KEY_TV2); break; + case 0x049: ts_map_key_clear(KEY_CAMERA); break; + case 0x04a: ts_map_key_clear(KEY_VIDEO); break; + case 0x04b: ts_map_key_clear(KEY_ANGLE); break; + case 0x04c: ts_map_key_clear(KEY_LANGUAGE); break; + case 0x04d: ts_map_key_clear(KEY_SUBTITLE); break; + case 0x050: ts_map_key_clear(KEY_RADIO); break; + case 0x05a: ts_map_key_clear(KEY_TEXT); break; + case 0x05b: ts_map_key_clear(KEY_RED); break; + case 0x05c: ts_map_key_clear(KEY_GREEN); break; + case 0x05d: ts_map_key_clear(KEY_YELLOW); break; + case 0x05e: ts_map_key_clear(KEY_BLUE); break; default: return 0; } @@ -50,6 +59,7 @@ static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi, static const struct hid_device_id ts_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE) }, { } }; MODULE_DEVICE_TABLE(hid, ts_devices); diff --git a/drivers/hid/hid-wacom.c b/drivers/hid/hid-wacom.c index f947d8337e21..1e051f1171e4 100644 --- a/drivers/hid/hid-wacom.c +++ b/drivers/hid/hid-wacom.c @@ -22,14 +22,159 @@ #include <linux/hid.h> #include <linux/module.h> #include <linux/slab.h> +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY +#include <linux/power_supply.h> +#endif #include "hid-ids.h" struct wacom_data { __u16 tool; unsigned char butstate; + unsigned char high_speed; +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + int battery_capacity; + struct power_supply battery; + struct power_supply ac; +#endif }; +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY +/*percent of battery capacity, 0 means AC online*/ +static unsigned short batcap[8] = { 1, 15, 25, 35, 50, 70, 100, 0 }; + +static enum power_supply_property wacom_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY +}; + +static enum power_supply_property wacom_ac_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE +}; + +static int wacom_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wacom_data *wdata = container_of(psy, + struct wacom_data, battery); + int power_state = batcap[wdata->battery_capacity]; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CAPACITY: + /* show 100% battery capacity when charging */ + if (power_state == 0) + val->intval = 100; + else + val->intval = power_state; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int wacom_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wacom_data *wdata = container_of(psy, struct wacom_data, ac); + int power_state = batcap[wdata->battery_capacity]; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + /* fall through */ + case POWER_SUPPLY_PROP_ONLINE: + if (power_state == 0) + val->intval = 1; + else + val->intval = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +#endif + +static void wacom_poke(struct hid_device *hdev, u8 speed) +{ + struct wacom_data *wdata = hid_get_drvdata(hdev); + int limit, ret; + char rep_data[2]; + + rep_data[0] = 0x03 ; rep_data[1] = 0x00; + limit = 3; + do { + ret = hdev->hid_output_raw_report(hdev, rep_data, 2, + HID_FEATURE_REPORT); + } while (ret < 0 && limit-- > 0); + + if (ret >= 0) { + if (speed == 0) + rep_data[0] = 0x05; + else + rep_data[0] = 0x06; + + rep_data[1] = 0x00; + limit = 3; + do { + ret = hdev->hid_output_raw_report(hdev, rep_data, 2, + HID_FEATURE_REPORT); + } while (ret < 0 && limit-- > 0); + + if (ret >= 0) { + wdata->high_speed = speed; + return; + } + } + + /* + * Note that if the raw queries fail, it's not a hard failure and it + * is safe to continue + */ + dev_warn(&hdev->dev, "failed to poke device, command %d, err %d\n", + rep_data[0], ret); + return; +} + +static ssize_t wacom_show_speed(struct device *dev, + struct device_attribute + *attr, char *buf) +{ + struct wacom_data *wdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%i\n", wdata->high_speed); +} + +static ssize_t wacom_store_speed(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + int new_speed; + + if (sscanf(buf, "%1d", &new_speed ) != 1) + return -EINVAL; + + if (new_speed == 0 || new_speed == 1) { + wacom_poke(hdev, new_speed); + return strnlen(buf, PAGE_SIZE); + } else + return -EINVAL; +} + +static DEVICE_ATTR(speed, S_IRUGO | S_IWUGO, + wacom_show_speed, wacom_store_speed); + static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *raw_data, int size) { @@ -148,6 +293,12 @@ static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report, input_sync(input); } +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + /* Store current battery capacity */ + rw = (data[7] >> 2 & 0x07); + if (rw != wdata->battery_capacity) + wdata->battery_capacity = rw; +#endif return 1; } @@ -157,9 +308,7 @@ static int wacom_probe(struct hid_device *hdev, struct hid_input *hidinput; struct input_dev *input; struct wacom_data *wdata; - char rep_data[2]; int ret; - int limit; wdata = kzalloc(sizeof(*wdata), GFP_KERNEL); if (wdata == NULL) { @@ -182,31 +331,53 @@ static int wacom_probe(struct hid_device *hdev, goto err_free; } - /* - * Note that if the raw queries fail, it's not a hard failure and it - * is safe to continue - */ + ret = device_create_file(&hdev->dev, &dev_attr_speed); + if (ret) + dev_warn(&hdev->dev, + "can't create sysfs speed attribute err: %d\n", ret); - /* Set Wacom mode2 */ - rep_data[0] = 0x03; rep_data[1] = 0x00; - limit = 3; - do { - ret = hdev->hid_output_raw_report(hdev, rep_data, 2, - HID_FEATURE_REPORT); - } while (ret < 0 && limit-- > 0); - if (ret < 0) - dev_warn(&hdev->dev, "failed to poke device #1, %d\n", ret); + /* Set Wacom mode 2 with high reporting speed */ + wacom_poke(hdev, 1); - /* 0x06 - high reporting speed, 0x05 - low speed */ - rep_data[0] = 0x06; rep_data[1] = 0x00; - limit = 3; - do { - ret = hdev->hid_output_raw_report(hdev, rep_data, 2, - HID_FEATURE_REPORT); - } while (ret < 0 && limit-- > 0); - if (ret < 0) - dev_warn(&hdev->dev, "failed to poke device #2, %d\n", ret); +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + wdata->battery.properties = wacom_battery_props; + wdata->battery.num_properties = ARRAY_SIZE(wacom_battery_props); + wdata->battery.get_property = wacom_battery_get_property; + wdata->battery.name = "wacom_battery"; + wdata->battery.type = POWER_SUPPLY_TYPE_BATTERY; + wdata->battery.use_for_apm = 0; + ret = power_supply_register(&hdev->dev, &wdata->battery); + if (ret) { + dev_warn(&hdev->dev, + "can't create sysfs battery attribute, err: %d\n", ret); + /* + * battery attribute is not critical for the tablet, but if it + * failed then there is no need to create ac attribute + */ + goto move_on; + } + + wdata->ac.properties = wacom_ac_props; + wdata->ac.num_properties = ARRAY_SIZE(wacom_ac_props); + wdata->ac.get_property = wacom_ac_get_property; + wdata->ac.name = "wacom_ac"; + wdata->ac.type = POWER_SUPPLY_TYPE_MAINS; + wdata->ac.use_for_apm = 0; + + ret = power_supply_register(&hdev->dev, &wdata->ac); + if (ret) { + dev_warn(&hdev->dev, + "can't create ac battery attribute, err: %d\n", ret); + /* + * ac attribute is not critical for the tablet, but if it + * failed then we don't want to battery attribute to exist + */ + power_supply_unregister(&wdata->battery); + } + +move_on: +#endif hidinput = list_entry(hdev->inputs.next, struct hid_input, list); input = hidinput->input; @@ -251,13 +422,21 @@ err_free: static void wacom_remove(struct hid_device *hdev) { +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + struct wacom_data *wdata = hid_get_drvdata(hdev); +#endif hid_hw_stop(hdev); + +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + power_supply_unregister(&wdata->battery); + power_supply_unregister(&wdata->ac); +#endif kfree(hid_get_drvdata(hdev)); } static const struct hid_device_id wacom_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) }, - + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH) }, { } }; MODULE_DEVICE_TABLE(hid, wacom_devices); diff --git a/drivers/hid/hid-zydacron.c b/drivers/hid/hid-zydacron.c new file mode 100644 index 000000000000..9e8d35a203e4 --- /dev/null +++ b/drivers/hid/hid-zydacron.c @@ -0,0 +1,237 @@ +/* +* HID driver for zydacron remote control +* +* Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk> +*/ + +/* +* 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 <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> + +#include "hid-ids.h" + +struct zc_device { + struct input_dev *input_ep81; + unsigned short last_key[4]; +}; + + +/* +* Zydacron remote control has an invalid HID report descriptor, +* that needs fixing before we can parse it. +*/ +static void zc_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int rsize) +{ + if (rsize >= 253 && + rdesc[0x96] == 0xbc && rdesc[0x97] == 0xff && + rdesc[0xca] == 0xbc && rdesc[0xcb] == 0xff && + rdesc[0xe1] == 0xbc && rdesc[0xe2] == 0xff) { + dev_info(&hdev->dev, + "fixing up zydacron remote control report " + "descriptor\n"); + rdesc[0x96] = rdesc[0xca] = rdesc[0xe1] = 0x0c; + rdesc[0x97] = rdesc[0xcb] = rdesc[0xe2] = 0x00; + } +} + +#define zc_map_key_clear(c) \ + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) + +static int zc_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + int i; + struct zc_device *zc = hid_get_drvdata(hdev); + zc->input_ep81 = hi->input; + + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER) + return 0; + + dbg_hid("zynacron input mapping event [0x%x]\n", + usage->hid & HID_USAGE); + + switch (usage->hid & HID_USAGE) { + /* report 2 */ + case 0x10: + zc_map_key_clear(KEY_MODE); + break; + case 0x30: + zc_map_key_clear(KEY_SCREEN); + break; + case 0x70: + zc_map_key_clear(KEY_INFO); + break; + /* report 3 */ + case 0x04: + zc_map_key_clear(KEY_RADIO); + break; + /* report 4 */ + case 0x0d: + zc_map_key_clear(KEY_PVR); + break; + case 0x25: + zc_map_key_clear(KEY_TV); + break; + case 0x47: + zc_map_key_clear(KEY_AUDIO); + break; + case 0x49: + zc_map_key_clear(KEY_AUX); + break; + case 0x4a: + zc_map_key_clear(KEY_VIDEO); + break; + case 0x48: + zc_map_key_clear(KEY_DVD); + break; + case 0x24: + zc_map_key_clear(KEY_MENU); + break; + case 0x32: + zc_map_key_clear(KEY_TEXT); + break; + default: + return 0; + } + + for (i = 0; i < 4; i++) + zc->last_key[i] = 0; + + return 1; +} + +static int zc_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct zc_device *zc = hid_get_drvdata(hdev); + int ret = 0; + unsigned key; + unsigned short index; + + if (report->id == data[0]) { + + /* break keys */ + for (index = 0; index < 4; index++) { + key = zc->last_key[index]; + if (key) { + input_event(zc->input_ep81, EV_KEY, key, 0); + zc->last_key[index] = 0; + } + } + + key = 0; + switch (report->id) { + case 0x02: + case 0x03: + switch (data[1]) { + case 0x10: + key = KEY_MODE; + index = 0; + break; + case 0x30: + key = KEY_SCREEN; + index = 1; + break; + case 0x70: + key = KEY_INFO; + index = 2; + break; + case 0x04: + key = KEY_RADIO; + index = 3; + break; + } + + if (key) { + input_event(zc->input_ep81, EV_KEY, key, 1); + zc->last_key[index] = key; + } + + ret = 1; + break; + } + } + + return ret; +} + +static int zc_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct zc_device *zc; + + zc = kzalloc(sizeof(*zc), GFP_KERNEL); + if (zc == NULL) { + dev_err(&hdev->dev, "zydacron: can't alloc descriptor\n"); + return -ENOMEM; + } + + hid_set_drvdata(hdev, zc); + + ret = hid_parse(hdev); + if (ret) { + dev_err(&hdev->dev, "zydacron: parse failed\n"); + goto err_free; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + dev_err(&hdev->dev, "zydacron: hw start failed\n"); + goto err_free; + } + + return 0; +err_free: + kfree(zc); + + return ret; +} + +static void zc_remove(struct hid_device *hdev) +{ + struct zc_device *zc = hid_get_drvdata(hdev); + + hid_hw_stop(hdev); + + if (NULL != zc) + kfree(zc); +} + +static const struct hid_device_id zc_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) }, + { } +}; +MODULE_DEVICE_TABLE(hid, zc_devices); + +static struct hid_driver zc_driver = { + .name = "zydacron", + .id_table = zc_devices, + .report_fixup = zc_report_fixup, + .input_mapping = zc_input_mapping, + .raw_event = zc_raw_event, + .probe = zc_probe, + .remove = zc_remove, +}; + +static int __init zc_init(void) +{ + return hid_register_driver(&zc_driver); +} + +static void __exit zc_exit(void) +{ + hid_unregister_driver(&zc_driver); +} + +module_init(zc_init); +module_exit(zc_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index 6eadf1a9b3cc..3ccd47850677 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -106,38 +106,48 @@ out: static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { unsigned int minor = iminor(file->f_path.dentry->d_inode); - /* FIXME: What stops hidraw_table going NULL */ - struct hid_device *dev = hidraw_table[minor]->hid; + struct hid_device *dev; __u8 *buf; int ret = 0; - if (!dev->hid_output_raw_report) - return -ENODEV; + mutex_lock(&minors_lock); + dev = hidraw_table[minor]->hid; + + if (!dev->hid_output_raw_report) { + ret = -ENODEV; + goto out; + } if (count > HID_MAX_BUFFER_SIZE) { printk(KERN_WARNING "hidraw: pid %d passed too large report\n", task_pid_nr(current)); - return -EINVAL; + ret = -EINVAL; + goto out; } if (count < 2) { printk(KERN_WARNING "hidraw: pid %d passed too short report\n", task_pid_nr(current)); - return -EINVAL; + ret = -EINVAL; + goto out; } buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); - if (!buf) - return -ENOMEM; + if (!buf) { + ret = -ENOMEM; + goto out; + } if (copy_from_user(buf, buffer, count)) { ret = -EFAULT; - goto out; + goto out_free; } ret = dev->hid_output_raw_report(dev, buf, count, HID_OUTPUT_REPORT); -out: +out_free: kfree(buf); +out: + mutex_unlock(&minors_lock); return ret; } @@ -165,11 +175,8 @@ static int hidraw_open(struct inode *inode, struct file *file) goto out; } - lock_kernel(); mutex_lock(&minors_lock); if (!hidraw_table[minor]) { - printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", - minor); kfree(list); err = -ENODEV; goto out_unlock; @@ -197,7 +204,6 @@ static int hidraw_open(struct inode *inode, struct file *file) out_unlock: mutex_unlock(&minors_lock); - unlock_kernel(); out: return err; @@ -209,11 +215,8 @@ static int hidraw_release(struct inode * inode, struct file * file) struct hidraw *dev; struct hidraw_list *list = file->private_data; - if (!hidraw_table[minor]) { - printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", - minor); + if (!hidraw_table[minor]) return -ENODEV; - } list_del(&list->node); dev = hidraw_table[minor]; @@ -238,11 +241,12 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, struct inode *inode = file->f_path.dentry->d_inode; unsigned int minor = iminor(inode); long ret = 0; - /* FIXME: What stops hidraw_table going NULL */ - struct hidraw *dev = hidraw_table[minor]; + struct hidraw *dev; void __user *user_arg = (void __user*) arg; - lock_kernel(); + mutex_lock(&minors_lock); + dev = hidraw_table[minor]; + switch (cmd) { case HIDIOCGRDESCSIZE: if (put_user(dev->hid->rsize, (int __user *)arg)) @@ -311,11 +315,11 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, -EFAULT : len; break; } - } + } ret = -ENOTTY; } - unlock_kernel(); + mutex_unlock(&minors_lock); return ret; } diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index df619756b7ae..1ebd3244eb85 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) { @@ -806,16 +807,36 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co struct usb_host_interface *interface = intf->cur_altsetting; int ret; - ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - HID_REQ_SET_REPORT, - USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, - ((report_type + 1) << 8) | *buf, - interface->desc.bInterfaceNumber, buf + 1, count - 1, - USB_CTRL_SET_TIMEOUT); - - /* count also the report id */ - if (ret > 0) - ret++; + if (usbhid->urbout) { + int actual_length; + int skipped_report_id = 0; + if (buf[0] == 0x0) { + /* Don't send the Report ID */ + buf++; + count--; + skipped_report_id = 1; + } + ret = usb_interrupt_msg(dev, usbhid->urbout->pipe, + buf, count, &actual_length, + USB_CTRL_SET_TIMEOUT); + /* return the number of bytes transferred */ + if (ret == 0) { + ret = actual_length; + /* count also the report id */ + if (skipped_report_id) + ret++; + } + } else { + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ((report_type + 1) << 8) | *buf, + interface->desc.bInterfaceNumber, buf + 1, count - 1, + USB_CTRL_SET_TIMEOUT); + /* count also the report id */ + if (ret > 0) + ret++; + } return ret; } @@ -1017,12 +1038,15 @@ static int usbhid_start(struct hid_device *hid) /* Some keyboards don't work until their LEDs have been set. * Since BIOSes do set the LEDs, it must be safe for any device * that supports the keyboard boot protocol. + * In addition, enable remote wakeup by default for all keyboard + * devices supporting the boot protocol. */ if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT && interface->desc.bInterfaceProtocol == - USB_INTERFACE_PROTOCOL_KEYBOARD) + USB_INTERFACE_PROTOCOL_KEYBOARD) { usbhid_set_leds(hid); - + device_set_wakeup_enable(&dev->dev, 1); + } return 0; fail: @@ -1131,6 +1155,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * hid->vendor = le16_to_cpu(dev->descriptor.idVendor); hid->product = le16_to_cpu(dev->descriptor.idProduct); hid->name[0] = 0; + hid->quirks = usbhid_lookup_quirk(hid->vendor, hid->product); if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) hid->type = HID_TYPE_USBMOUSE; @@ -1287,6 +1312,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) { set_bit(HID_REPORTED_IDLE, &usbhid->iofl); spin_unlock_irq(&usbhid->lock); + if (hid->driver && hid->driver->suspend) { + status = hid->driver->suspend(hid, message); + if (status < 0) + return status; + } } else { usbhid_mark_busy(usbhid); spin_unlock_irq(&usbhid->lock); @@ -1294,6 +1324,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) } } else { + if (hid->driver && hid->driver->suspend) { + status = hid->driver->suspend(hid, message); + if (status < 0) + return status; + } spin_lock_irq(&usbhid->lock); set_bit(HID_REPORTED_IDLE, &usbhid->iofl); spin_unlock_irq(&usbhid->lock); @@ -1348,6 +1383,11 @@ static int hid_resume(struct usb_interface *intf) hid_io_error(hid); usbhid_restart_queues(usbhid); + if (status >= 0 && hid->driver && hid->driver->resume) { + int ret = hid->driver->resume(hid); + if (ret < 0) + status = ret; + } dev_dbg(&intf->dev, "resume status %d\n", status); return 0; } @@ -1356,9 +1396,16 @@ static int hid_reset_resume(struct usb_interface *intf) { struct hid_device *hid = usb_get_intfdata(intf); struct usbhid_device *usbhid = hid->driver_data; + int status; clear_bit(HID_REPORTED_IDLE, &usbhid->iofl); - return hid_post_reset(intf); + status = hid_post_reset(intf); + if (status >= 0 && hid->driver && hid->driver->reset_resume) { + int ret = hid->driver->reset_resume(hid); + if (ret < 0) + status = ret; + } + return status; } #endif /* CONFIG_PM */ diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index 1152f9b5fd44..5ff8d327f33a 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -33,6 +33,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 433602aed468..c24d2fa3e3b6 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -267,6 +267,7 @@ static int hiddev_open(struct inode *inode, struct file *file) struct hiddev_list *list; int res, i; + /* See comment in hiddev_connect() for BKL explanation */ lock_kernel(); i = iminor(inode) - HIDDEV_MINOR_BASE; @@ -894,8 +895,22 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) hiddev->hid = hid; hiddev->exist = 1; - /* when lock_kernel() usage is fixed in usb_open(), - * we could also fix it here */ + /* + * BKL here is used to avoid race after usb_register_dev(). + * Once the device node has been created, open() could happen on it. + * The code below will then fail, as hiddev_table hasn't been + * updated. + * + * The obvious fix -- introducing mutex to guard hiddev_table[] + * doesn't work, as usb_open() and usb_register_dev() both take + * minor_rwsem, thus we'll have ABBA deadlock. + * + * Before BKL pushdown, usb_open() had been acquiring it in right + * order, so _open() was safe to use it to protect from this race. + * Now the order is different, but AB-BA deadlock still doesn't occur + * as BKL is dropped on schedule() (i.e. while sleeping on + * minor_rwsem). Fugly. + */ lock_kernel(); retval = usb_register_dev(usbhid->intf, &hiddev_class); if (retval) { diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index bb14c8270af3..a948605564fb 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -311,6 +311,7 @@ static int usb_kbd_probe(struct usb_interface *iface, goto fail2; usb_set_intfdata(iface, kbd); + device_set_wakeup_enable(&dev->dev, 1); return 0; fail2: |