Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Linus Torvalds [Mon, 1 Oct 2012 16:13:10 +0000 (09:13 -0700)]
Pull HID updates from Jiri Kosina:

 1) Patchset from Henrik Rydberg which substantially reduces irqsoff
    latency for all input devices.  In addition to that, Henrik reworked
    multitouch handling in order to reduce runtime memory consumption.

    This patchset touches code in Input subsystem as well.  All the
    changes have been Acked by Dmitry, and we agreed to do it this way
    due to inter-dependencies between the patchset and subsequent
    changes in HID subsystem.

 2) Rework, clenaups and a lot of fixes to picolcd driver by Bruno
    Prémont.

 3) Core report descriptor handling fix which fixes resume issue on some
    devices, by Kevin Daughtridge

 4) hidraw fixes by Alexey Khoroshilov and Ratan Nalumasu

 5) wiimote driver now supports balance board, by David Herrmann.

 6) Other smaller fixes and new device id additions all over the place.

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (79 commits)
  HID: hidraw: don't deallocate memory when it is in use
  HID: picoLCD: optimize for inactive debugfs
  HID: multitouch: add support for GeneralTouch multi-touchscreen
  HID: Add support for Sony PS3 BD Remote Control
  HID: keep dev_rdesc unmodified and use it for comparisons
  HID: lg4ff: Minor coding style fixes in lg4ff and hid-lg
  HID: hid-lg4ff: Set absolute axes parametes on DFP
  HID: hid-lg4ff: Adjust X axis input value accordingly to selected range.
  HID: hid-lg4ff: Minor code cleanup to improve readability
  HID: ntrig: change default value of logical/physical width/height to 1
  HID: picoLCD: bounds check in dump_buff_as_hex()
  Input: bcm5974 - Convert to MT-B
  Input: bcm5974 - Drop the logical dimensions
  Input: bcm5974 - Preparatory renames
  Input: bcm5974 - only setup button urb for TYPE1 devices
  HID: hid-multitouch: Add Flatfrog support
  HID: hid-multitouch: Fix contact count on 3M panels
  HID: hid-multitouch: Remove the redundant touch state
  HID: hid-multitouch: Simplify setup and frame synchronization
  HID: Allow more fields in the hid report
  ...

71 files changed:
Documentation/ABI/testing/sysfs-driver-wacom
MAINTAINERS
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/hid-a4tech.c
drivers/hid/hid-apple.c
drivers/hid/hid-aureal.c
drivers/hid/hid-belkin.c
drivers/hid/hid-cherry.c
drivers/hid/hid-core.c
drivers/hid/hid-cypress.c
drivers/hid/hid-debug.c
drivers/hid/hid-ezkey.c
drivers/hid/hid-gyration.c
drivers/hid/hid-holtekff.c
drivers/hid/hid-ids.h
drivers/hid/hid-input.c
drivers/hid/hid-lcpower.c
drivers/hid/hid-lenovo-tpkbd.c
drivers/hid/hid-lg.c
drivers/hid/hid-lg.h
drivers/hid/hid-lg4ff.c
drivers/hid/hid-magicmouse.c
drivers/hid/hid-microsoft.c
drivers/hid/hid-monterey.c
drivers/hid/hid-multitouch.c
drivers/hid/hid-ntrig.c
drivers/hid/hid-petalynx.c
drivers/hid/hid-picolcd.c [deleted file]
drivers/hid/hid-picolcd.h [new file with mode: 0644]
drivers/hid/hid-picolcd_backlight.c [new file with mode: 0644]
drivers/hid/hid-picolcd_cir.c [new file with mode: 0644]
drivers/hid/hid-picolcd_core.c [new file with mode: 0644]
drivers/hid/hid-picolcd_debugfs.c [new file with mode: 0644]
drivers/hid/hid-picolcd_fb.c [new file with mode: 0644]
drivers/hid/hid-picolcd_lcd.c [new file with mode: 0644]
drivers/hid/hid-picolcd_leds.c [new file with mode: 0644]
drivers/hid/hid-primax.c
drivers/hid/hid-prodikeys.c
drivers/hid/hid-ps3remote.c [new file with mode: 0644]
drivers/hid/hid-samsung.c
drivers/hid/hid-sony.c
drivers/hid/hid-sunplus.c
drivers/hid/hid-uclogic.c
drivers/hid/hid-wacom.c
drivers/hid/hid-waltop.c
drivers/hid/hid-wiimote-ext.c
drivers/hid/hidraw.c
drivers/hid/usbhid/hid-core.c
drivers/hid/usbhid/hid-quirks.c
drivers/input/evdev.c
drivers/input/input-mt.c
drivers/input/input.c
drivers/input/misc/uinput.c
drivers/input/mouse/alps.c
drivers/input/mouse/bcm5974.c
drivers/input/mouse/elantech.c
drivers/input/mouse/sentelic.c
drivers/input/mouse/synaptics.c
drivers/input/tablet/wacom_wac.c
drivers/input/touchscreen/atmel_mxt_ts.c
drivers/input/touchscreen/cyttsp_core.c
drivers/input/touchscreen/edt-ft5x06.c
drivers/input/touchscreen/egalax_ts.c
drivers/input/touchscreen/ili210x.c
drivers/input/touchscreen/mms114.c
drivers/input/touchscreen/penmount.c
drivers/input/touchscreen/wacom_w8001.c
include/linux/hid.h
include/linux/input.h
include/linux/input/mt.h

index 8d55a83..7fc7810 100644 (file)
@@ -1,3 +1,16 @@
+WWhat:         /sys/class/hidraw/hidraw*/device/oled*_img
+Date:          June 2012
+Contact:       linux-bluetooth@vger.kernel.org
+Description:
+               The /sys/class/hidraw/hidraw*/device/oled*_img files control
+               OLED mocro displays on Intuos4 Wireless tablet. Accepted image
+               has to contain 256 bytes (64x32 px 1 bit colour). The format
+               is the same as PBM image 62x32px without header (64 bits per
+               horizontal line, 32 lines). An example of setting OLED No. 0:
+               dd bs=256 count=1 if=img_file of=[path to oled0_img]/oled0_img
+               The attribute is read only and no local copy of the image is
+               stored.
+
 What:          /sys/class/hidraw/hidraw*/device/speed
 Date:          April 2010
 Kernel Version:        2.6.35
index 9a6c4da..9362f54 100644 (file)
@@ -5322,6 +5322,12 @@ L:       linux-mtd@lists.infradead.org
 S:     Maintained
 F:     drivers/mtd/devices/phram.c
 
+PICOLCD HID DRIVER
+M:     Bruno Prémont <bonbons@linux-vserver.org>
+L:     linux-input@vger.kernel.org
+S:     Maintained
+F:     drivers/hid/hid-picolcd*
+
 PICOXCELL SUPPORT
 M:     Jamie Iles <jamie@jamieiles.com>
 L:     linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
index fbf4950..2af774a 100644 (file)
@@ -307,7 +307,6 @@ config HID_LOGITECH
 config HID_LOGITECH_DJ
        tristate "Logitech Unifying receivers full support"
        depends on HID_LOGITECH
-       default m
        ---help---
        Say Y if you want support for Logitech Unifying receivers and devices.
        Unifying receivers are capable of pairing up to 6 Logitech compliant
@@ -527,6 +526,14 @@ config HID_PICOLCD_LEDS
        ---help---
          Provide access to PicoLCD's GPO pins via leds class.
 
+config HID_PICOLCD_CIR
+       bool "CIR via RC class" if EXPERT
+       default !EXPERT
+       depends on HID_PICOLCD
+       depends on HID_PICOLCD=RC_CORE || RC_CORE=y
+       ---help---
+         Provide access to PicoLCD's CIR interface via remote control (LIRC).
+
 config HID_PRIMAX
        tristate "Primax non-fully HID-compliant devices"
        depends on USB_HID
@@ -534,6 +541,15 @@ config HID_PRIMAX
        Support for Primax devices that are not fully compliant with the
        HID standard.
 
+config HID_PS3REMOTE
+       tristate "Sony PS3 BD Remote Control"
+       depends on BT_HIDP
+       ---help---
+       Support for the Sony PS3 Blue-ray Disk Remote Control and Logitech
+       Harmony Adapter for PS3, which connect over Bluetooth.
+
+       Support for the 6-axis controllers is provided by HID_SONY.
+
 config HID_ROCCAT
        tristate "Roccat device support"
        depends on USB_HID
@@ -561,7 +577,9 @@ config HID_SONY
        tristate "Sony PS3 controller"
        depends on USB_HID
        ---help---
-       Support for Sony PS3 controller.
+       Support for Sony PS3 6-axis controllers.
+
+       Support for the Sony PS3 BD Remote is provided by HID_PS3REMOTE.
 
 config HID_SPEEDLINK
        tristate "Speedlink VAD Cezanne mouse support"
index f975485..5a3690f 100644 (file)
@@ -69,7 +69,28 @@ obj-$(CONFIG_HID_PRODIKEYS)  += hid-prodikeys.o
 obj-$(CONFIG_HID_PANTHERLORD)  += hid-pl.o
 obj-$(CONFIG_HID_PETALYNX)     += hid-petalynx.o
 obj-$(CONFIG_HID_PICOLCD)      += hid-picolcd.o
+hid-picolcd-y                  += hid-picolcd_core.o
+ifdef CONFIG_HID_PICOLCD_FB
+hid-picolcd-y                  += hid-picolcd_fb.o
+endif
+ifdef CONFIG_HID_PICOLCD_BACKLIGHT
+hid-picolcd-y                  += hid-picolcd_backlight.o
+endif
+ifdef CONFIG_HID_PICOLCD_LCD
+hid-picolcd-y                  += hid-picolcd_lcd.o
+endif
+ifdef CONFIG_HID_PICOLCD_LEDS
+hid-picolcd-y                  += hid-picolcd_leds.o
+endif
+ifdef CONFIG_HID_PICOLCD_CIR
+hid-picolcd-y                  += hid-picolcd_cir.o
+endif
+ifdef CONFIG_DEBUG_FS
+hid-picolcd-y                  += hid-picolcd_debugfs.o
+endif
+
 obj-$(CONFIG_HID_PRIMAX)       += hid-primax.o
+obj-$(CONFIG_HID_PS3REMOTE)    += hid-ps3remote.o
 obj-$(CONFIG_HID_ROCCAT)       += hid-roccat.o hid-roccat-common.o \
        hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
        hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o \
index 902d1df..0a23988 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
index 585344b..06ebdbb 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
  */
 
index ba64b04..7968187 100644 (file)
@@ -9,7 +9,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 #include <linux/device.h>
index a1a765a..a1a5a12 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
index 888ece6..af034d3 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
index 8bcd168..2cd6880 100644 (file)
@@ -126,7 +126,7 @@ static int open_collection(struct hid_parser *parser, unsigned type)
 
        if (parser->collection_stack_ptr == HID_COLLECTION_STACK_SIZE) {
                hid_err(parser->device, "collection stack overflow\n");
-               return -1;
+               return -EINVAL;
        }
 
        if (parser->device->maxcollection == parser->device->collection_size) {
@@ -134,7 +134,7 @@ static int open_collection(struct hid_parser *parser, unsigned type)
                                parser->device->collection_size * 2, GFP_KERNEL);
                if (collection == NULL) {
                        hid_err(parser->device, "failed to reallocate collection array\n");
-                       return -1;
+                       return -ENOMEM;
                }
                memcpy(collection, parser->device->collection,
                        sizeof(struct hid_collection) *
@@ -170,7 +170,7 @@ static int close_collection(struct hid_parser *parser)
 {
        if (!parser->collection_stack_ptr) {
                hid_err(parser->device, "collection stack underflow\n");
-               return -1;
+               return -EINVAL;
        }
        parser->collection_stack_ptr--;
        return 0;
@@ -374,7 +374,7 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
 
        case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:
                parser->global.report_size = item_udata(item);
-               if (parser->global.report_size > 96) {
+               if (parser->global.report_size > 128) {
                        hid_err(parser->device, "invalid report_size %d\n",
                                        parser->global.report_size);
                        return -1;
@@ -757,6 +757,7 @@ int hid_open_report(struct hid_device *device)
        struct hid_item item;
        unsigned int size;
        __u8 *start;
+       __u8 *buf;
        __u8 *end;
        int ret;
        static int (*dispatch_type[])(struct hid_parser *parser,
@@ -775,12 +776,21 @@ int hid_open_report(struct hid_device *device)
                return -ENODEV;
        size = device->dev_rsize;
 
+       buf = kmemdup(start, size, GFP_KERNEL);
+       if (buf == NULL)
+               return -ENOMEM;
+
        if (device->driver->report_fixup)
-               start = device->driver->report_fixup(device, start, &size);
+               start = device->driver->report_fixup(device, buf, &size);
+       else
+               start = buf;
 
-       device->rdesc = kmemdup(start, size, GFP_KERNEL);
-       if (device->rdesc == NULL)
+       start = kmemdup(start, size, GFP_KERNEL);
+       kfree(buf);
+       if (start == NULL)
                return -ENOMEM;
+
+       device->rdesc = start;
        device->rsize = size;
 
        parser = vzalloc(sizeof(struct hid_parser));
@@ -1448,7 +1458,14 @@ void hid_disconnect(struct hid_device *hdev)
 }
 EXPORT_SYMBOL_GPL(hid_disconnect);
 
-/* a list of devices for which there is a specialized driver on HID bus */
+/*
+ * A list of devices for which there is a specialized driver on HID bus.
+ *
+ * Please note that for multitouch devices (driven by hid-multitouch driver),
+ * there is a proper autodetection and autoloading in place (based on presence
+ * of HID_DG_CONTACTID), so those devices don't need to be added to this list,
+ * as we are doing the right thing in hid_scan_usage().
+ */
 static const struct hid_device_id hid_have_special_driver[] = {
        { 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) },
@@ -1566,6 +1583,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER) },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI) },
@@ -1627,6 +1645,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_WKB2000) },
        { HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
        { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
+#if IS_ENABLED(CONFIG_HID_ROCCAT)
        { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) },
        { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) },
        { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) },
@@ -1635,10 +1654,12 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) },
        { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS) },
        { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
+#endif
        { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) },
        { 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_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) },
        { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
        { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
@@ -1663,6 +1684,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) },
        { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) },
        { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
        { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) },
        { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SUPER_JOY_BOX_3) },
        { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD) },
index 9e43aac..3e159a5 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
index 01dd9a7..933fff0 100644 (file)
@@ -911,15 +911,21 @@ static void hid_dump_input_mapping(struct hid_device *hid, struct seq_file *f)
 
 }
 
-
 static int hid_debug_rdesc_show(struct seq_file *f, void *p)
 {
        struct hid_device *hdev = f->private;
+       const __u8 *rdesc = hdev->rdesc;
+       unsigned rsize = hdev->rsize;
        int i;
 
+       if (!rdesc) {
+               rdesc = hdev->dev_rdesc;
+               rsize = hdev->dev_rsize;
+       }
+
        /* dump HID report descriptor */
-       for (i = 0; i < hdev->rsize; i++)
-               seq_printf(f, "%02x ", hdev->rdesc[i]);
+       for (i = 0; i < rsize; i++)
+               seq_printf(f, "%02x ", rdesc[i]);
        seq_printf(f, "\n\n");
 
        /* dump parsed data and input mappings */
index ca1163e..6540af2 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
index e88b951..4442c30 100644 (file)
@@ -4,7 +4,6 @@
  *  Copyright (c) 1999 Andreas Gal
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  *  Copyright (c) 2006-2008 Jiri Kosina
  */
index 4e75421..ff295e6 100644 (file)
@@ -100,8 +100,7 @@ static void holtekff_send(struct holtekff_device *holtekff,
                holtekff->field->value[i] = data[i];
        }
 
-       dbg_hid("sending %02x %02x %02x %02x %02x %02x %02x\n", data[0],
-               data[1], data[2], data[3], data[4], data[5], data[6]);
+       dbg_hid("sending %*ph\n", 7, data);
 
        usbhid_submit_report(hid, holtekff->field->report, USB_DIR_OUT);
 }
index 1dcb76f..ca4d83e 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  */
 
 /*
 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72FA      0x72fa
 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7302      0x7302
 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349      0x7349
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7      0x73f7
 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001      0xa001
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224      0x7224
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72D0      0x72d0
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72C4      0x72c4
 
 #define USB_VENDOR_ID_ELECOM           0x056e
 #define USB_DEVICE_ID_ELECOM_BM084     0x0061
 #define USB_VENDOR_ID_EMS              0x2006
 #define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118
 
+#define USB_VENDOR_ID_FLATFROG         0x25b5
+#define USB_DEVICE_ID_MULTITOUCH_3200  0x0002
+
 #define USB_VENDOR_ID_ESSENTIAL_REALITY        0x0d7f
 #define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100
 
 #define USB_VENDOR_ID_EZKEY            0x0518
 #define USB_DEVICE_ID_BTC_8193         0x0002
 
+#define USB_VENDOR_ID_FREESCALE                0x15A2
+#define USB_DEVICE_ID_FREESCALE_MX28   0x004F
+
 #define USB_VENDOR_ID_FRUCTEL  0x25B6
 #define USB_DEVICE_ID_GAMETEL_MT_MODE  0x0002
 
 
 #define USB_VENDOR_ID_GENERAL_TOUCH    0x0dfc
 #define USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS 0x0003
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PWT_TENFINGERS 0x0100
 
 #define USB_VENDOR_ID_GLAB             0x06c2
 #define USB_DEVICE_ID_4_PHIDGETSERVO_30        0x0038
 #define USB_DEVICE_ID_LOGITECH_RECEIVER        0xc101
 #define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST  0xc110
 #define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f
+#define USB_DEVICE_ID_LOGITECH_HARMONY_PS3 0x0306
 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD  0xc20a
 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD       0xc211
 #define USB_DEVICE_ID_LOGITECH_EXTREME_3D      0xc215
 #define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH             0x3000
 #define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001                0x3001
 #define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008                0x3008
-#define USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN  0x3001
 
 #define USB_VENDOR_ID_ROCCAT           0x1e7d
 #define USB_DEVICE_ID_ROCCAT_ARVO      0x30d4
 
 #define USB_VENDOR_ID_SONY                     0x054c
 #define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE      0x024b
+#define USB_DEVICE_ID_SONY_PS3_BDREMOTE                0x0306
 #define USB_DEVICE_ID_SONY_PS3_CONTROLLER      0x0268
 #define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER       0x042f
 
 #define USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U   0x0005
 #define USB_DEVICE_ID_UCLOGIC_TABLET_WP1062    0x0064
 #define USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850  0x0522
+#define USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60    0x0781
 
 #define USB_VENDOR_ID_UNITEC   0x227d
 #define USB_DEVICE_ID_UNITEC_USB_TOUCH_0709    0x0709
index 811bfad..d917c0d 100644 (file)
@@ -1154,6 +1154,7 @@ static void report_features(struct hid_device *hid)
 
 int hidinput_connect(struct hid_device *hid, unsigned int force)
 {
+       struct hid_driver *drv = hid->driver;
        struct hid_report *report;
        struct hid_input *hidinput = NULL;
        struct input_dev *input_dev;
@@ -1228,6 +1229,8 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
                                 * UGCI) cram a lot of unrelated inputs into the
                                 * same interface. */
                                hidinput->report = report;
+                               if (drv->input_configured)
+                                       drv->input_configured(hid, hidinput);
                                if (input_register_device(hidinput->input))
                                        goto out_cleanup;
                                hidinput = NULL;
@@ -1235,8 +1238,12 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
                }
        }
 
-       if (hidinput && input_register_device(hidinput->input))
-               goto out_cleanup;
+       if (hidinput) {
+               if (drv->input_configured)
+                       drv->input_configured(hid, hidinput);
+               if (input_register_device(hidinput->input))
+                       goto out_cleanup;
+       }
 
        return 0;
 
index c4fe9bd..22bc14a 100644 (file)
@@ -24,7 +24,7 @@ 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) {
index 60c4e1e..cea016e 100644 (file)
@@ -56,9 +56,8 @@ static int tpkbd_input_mapping(struct hid_device *hdev,
 static int tpkbd_features_set(struct hid_device *hdev)
 {
        struct hid_report *report;
-       struct tpkbd_data_pointer *data_pointer;
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
        report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
 
        report->field[0]->value[0]  = data_pointer->press_to_select   ? 0x01 : 0x02;
@@ -77,14 +76,8 @@ static ssize_t pointer_press_to_select_show(struct device *dev,
                struct device_attribute *attr,
                char *buf)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
-
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
        return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
 }
@@ -94,16 +87,10 @@ static ssize_t pointer_press_to_select_store(struct device *dev,
                const char *buf,
                size_t count)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        int value;
 
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (kstrtoint(buf, 10, &value))
                return -EINVAL;
        if (value < 0 || value > 1)
@@ -119,14 +106,8 @@ static ssize_t pointer_dragging_show(struct device *dev,
                struct device_attribute *attr,
                char *buf)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
-
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
        return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
 }
@@ -136,16 +117,10 @@ static ssize_t pointer_dragging_store(struct device *dev,
                const char *buf,
                size_t count)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        int value;
 
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (kstrtoint(buf, 10, &value))
                return -EINVAL;
        if (value < 0 || value > 1)
@@ -161,14 +136,8 @@ static ssize_t pointer_release_to_select_show(struct device *dev,
                struct device_attribute *attr,
                char *buf)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
-
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
        return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
 }
@@ -178,16 +147,10 @@ static ssize_t pointer_release_to_select_store(struct device *dev,
                const char *buf,
                size_t count)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        int value;
 
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (kstrtoint(buf, 10, &value))
                return -EINVAL;
        if (value < 0 || value > 1)
@@ -203,14 +166,8 @@ static ssize_t pointer_select_right_show(struct device *dev,
                struct device_attribute *attr,
                char *buf)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
-
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
        return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
 }
@@ -220,16 +177,10 @@ static ssize_t pointer_select_right_store(struct device *dev,
                const char *buf,
                size_t count)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        int value;
 
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (kstrtoint(buf, 10, &value))
                return -EINVAL;
        if (value < 0 || value > 1)
@@ -245,14 +196,8 @@ static ssize_t pointer_sensitivity_show(struct device *dev,
                struct device_attribute *attr,
                char *buf)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
-
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
        return snprintf(buf, PAGE_SIZE, "%u\n",
                data_pointer->sensitivity);
@@ -263,16 +208,10 @@ static ssize_t pointer_sensitivity_store(struct device *dev,
                const char *buf,
                size_t count)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        int value;
 
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
                return -EINVAL;
 
@@ -286,14 +225,10 @@ static ssize_t pointer_press_speed_show(struct device *dev,
                struct device_attribute *attr,
                char *buf)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+       data_pointer = hid_get_drvdata(hdev);
 
        return snprintf(buf, PAGE_SIZE, "%u\n",
                data_pointer->press_speed);
@@ -304,16 +239,10 @@ static ssize_t pointer_press_speed_store(struct device *dev,
                const char *buf,
                size_t count)
 {
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        int value;
 
-       hdev = container_of(dev, struct hid_device, dev);
-       if (hdev == NULL)
-               return -ENODEV;
-
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
                return -EINVAL;
 
@@ -370,15 +299,11 @@ static const struct attribute_group tpkbd_attr_group_pointer = {
 static enum led_brightness tpkbd_led_brightness_get(
                        struct led_classdev *led_cdev)
 {
-       struct device *dev;
-       struct hid_device *hdev;
-       struct tpkbd_data_pointer *data_pointer;
+       struct device *dev = led_cdev->dev->parent;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        int led_nr = 0;
 
-       dev = led_cdev->dev->parent;
-       hdev = container_of(dev, struct hid_device, dev);
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (led_cdev == &data_pointer->led_micmute)
                led_nr = 1;
 
@@ -390,16 +315,12 @@ static enum led_brightness tpkbd_led_brightness_get(
 static void tpkbd_led_brightness_set(struct led_classdev *led_cdev,
                        enum led_brightness value)
 {
-       struct device *dev;
-       struct hid_device *hdev;
+       struct device *dev = led_cdev->dev->parent;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
        struct hid_report *report;
-       struct tpkbd_data_pointer *data_pointer;
        int led_nr = 0;
 
-       dev = led_cdev->dev->parent;
-       hdev = container_of(dev, struct hid_device, dev);
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        if (led_cdev == &data_pointer->led_micmute)
                led_nr = 1;
 
@@ -508,13 +429,11 @@ err_free:
 
 static void tpkbd_remove_tp(struct hid_device *hdev)
 {
-       struct tpkbd_data_pointer *data_pointer;
+       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
 
        sysfs_remove_group(&hdev->dev.kobj,
                        &tpkbd_attr_group_pointer);
 
-       data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
-
        led_classdev_unregister(&data_pointer->led_micmute);
        led_classdev_unregister(&data_pointer->led_mute);
 
index fc37ed6..a2f8e88 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  *  Copyright (c) 2010 Hendrik Iben
  */
@@ -109,7 +108,7 @@ static __u8 dfp_rdesc_fixed[] = {
 static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                unsigned int *rsize)
 {
-       struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hdev);
+       struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
 
        if ((drv_data->quirks & LG_RDESC) && *rsize >= 90 && rdesc[83] == 0x26 &&
                        rdesc[84] == 0x8c && rdesc[85] == 0x02) {
@@ -278,7 +277,7 @@ static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                  0,  0,  0,  0,  0,183,184,185,186,187,
                188,189,190,191,192,193,194,  0,  0,  0
        };
-       struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hdev);
+       struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
        unsigned int hid = usage->hid;
 
        if (hdev->product == USB_DEVICE_ID_LOGITECH_RECEIVER &&
@@ -319,7 +318,7 @@ static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
                struct hid_field *field, struct hid_usage *usage,
                unsigned long **bit, int *max)
 {
-       struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hdev);
+       struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
 
        if ((drv_data->quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY &&
                        (field->flags & HID_MAIN_ITEM_RELATIVE))
@@ -335,13 +334,16 @@ static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
 static int lg_event(struct hid_device *hdev, struct hid_field *field,
                struct hid_usage *usage, __s32 value)
 {
-       struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hdev);
+       struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
 
        if ((drv_data->quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) {
                input_event(field->hidinput->input, usage->type, usage->code,
                                -value);
                return 1;
        }
+       if (drv_data->quirks & LG_FF4) {
+               return lg4ff_adjust_input_event(hdev, field, usage, value, drv_data);
+       }
 
        return 0;
 }
@@ -358,7 +360,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
                return -ENOMEM;
        }
        drv_data->quirks = id->driver_data;
-       
+
        hid_set_drvdata(hdev, (void *)drv_data);
 
        if (drv_data->quirks & LG_NOGET)
@@ -380,7 +382,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
        }
 
        /* Setup wireless link with Logitech Wii wheel */
-       if(hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {
+       if (hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {
                unsigned char buf[] = { 0x00, 0xAF,  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
 
                ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
@@ -416,7 +418,7 @@ err_free:
 
 static void lg_remove(struct hid_device *hdev)
 {
-       struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hdev);
+       struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
        if (drv_data->quirks & LG_FF4)
                lg4ff_deinit(hdev);
 
@@ -476,7 +478,7 @@ static const struct hid_device_id lg_devices[] = {
                .driver_data = LG_NOGET | LG_FF4 },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
                .driver_data = LG_FF4 },
-       { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG ),
+       { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG),
                .driver_data = LG_FF },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2),
                .driver_data = LG_FF2 },
index d64cf8d..142ce3f 100644 (file)
@@ -25,9 +25,13 @@ static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
 #endif
 
 #ifdef CONFIG_LOGIWHEELS_FF
+int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+                            struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data);
 int lg4ff_init(struct hid_device *hdev);
 int lg4ff_deinit(struct hid_device *hdev);
 #else
+static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+                                          struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data) { return 0; }
 static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
 static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
 #endif
index f3390ee..d7947c7 100644 (file)
 #define G27_REV_MAJ 0x12
 #define G27_REV_MIN 0x38
 
+#define DFP_X_MIN 0
+#define DFP_X_MAX 16383
+#define DFP_PEDAL_MIN 0
+#define DFP_PEDAL_MAX 255
+
 #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
 
 static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
@@ -53,6 +58,7 @@ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *at
 static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store);
 
 struct lg4ff_device_entry {
+       __u32 product_id;
        __u16 range;
        __u16 min_range;
        __u16 max_range;
@@ -129,26 +135,77 @@ static const struct lg4ff_usb_revision lg4ff_revs[] = {
        {G27_REV_MAJ,  G27_REV_MIN,  &native_g27},      /* G27 */
 };
 
+/* Recalculates X axis value accordingly to currently selected range */
+static __s32 lg4ff_adjust_dfp_x_axis(__s32 value, __u16 range)
+{
+       __u16 max_range;
+       __s32 new_value;
+
+       if (range == 900)
+               return value;
+       else if (range == 200)
+               return value;
+       else if (range < 200)
+               max_range = 200;
+       else
+               max_range = 900;
+
+       new_value = 8192 + mult_frac(value - 8192, max_range, range);
+       if (new_value < 0)
+               return 0;
+       else if (new_value > 16383)
+               return 16383;
+       else
+               return new_value;
+}
+
+int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+                            struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data)
+{
+       struct lg4ff_device_entry *entry = drv_data->device_props;
+       __s32 new_value = 0;
+
+       if (!entry) {
+               hid_err(hid, "Device properties not found");
+               return 0;
+       }
+
+       switch (entry->product_id) {
+       case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+               switch (usage->code) {
+               case ABS_X:
+                       new_value = lg4ff_adjust_dfp_x_axis(value, entry->range);
+                       input_event(field->hidinput->input, usage->type, usage->code, new_value);
+                       return 1;
+               default:
+                       return 0;
+               }
+       default:
+               return 0;
+       }
+}
+
 static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
 {
        struct hid_device *hid = input_get_drvdata(dev);
        struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
        struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+       __s32 *value = report->field[0]->value;
        int x;
 
-#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
+#define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
 
        switch (effect->type) {
        case FF_CONSTANT:
                x = effect->u.ramp.start_level + 0x80;  /* 0x80 is no force */
                CLAMP(x);
-               report->field[0]->value[0] = 0x11;      /* Slot 1 */
-               report->field[0]->value[1] = 0x08;
-               report->field[0]->value[2] = x;
-               report->field[0]->value[3] = 0x80;
-               report->field[0]->value[4] = 0x00;
-               report->field[0]->value[5] = 0x00;
-               report->field[0]->value[6] = 0x00;
+               value[0] = 0x11;        /* Slot 1 */
+               value[1] = 0x08;
+               value[2] = x;
+               value[3] = 0x80;
+               value[4] = 0x00;
+               value[5] = 0x00;
+               value[6] = 0x00;
 
                usbhid_submit_report(hid, report, USB_DIR_OUT);
                break;
@@ -163,14 +220,15 @@ static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitud
        struct hid_device *hid = input_get_drvdata(dev);
        struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
        struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+       __s32 *value = report->field[0]->value;
 
-       report->field[0]->value[0] = 0xfe;
-       report->field[0]->value[1] = 0x0d;
-       report->field[0]->value[2] = magnitude >> 13;
-       report->field[0]->value[3] = magnitude >> 13;
-       report->field[0]->value[4] = magnitude >> 8;
-       report->field[0]->value[5] = 0x00;
-       report->field[0]->value[6] = 0x00;
+       value[0] = 0xfe;
+       value[1] = 0x0d;
+       value[2] = magnitude >> 13;
+       value[3] = magnitude >> 13;
+       value[4] = magnitude >> 8;
+       value[5] = 0x00;
+       value[6] = 0x00;
 
        usbhid_submit_report(hid, report, USB_DIR_OUT);
 }
@@ -181,16 +239,16 @@ static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude)
        struct hid_device *hid = input_get_drvdata(dev);
        struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
        struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+       __s32 *value = report->field[0]->value;
        magnitude = magnitude * 90 / 65535;
-       
 
-       report->field[0]->value[0] = 0xfe;
-       report->field[0]->value[1] = 0x03;
-       report->field[0]->value[2] = magnitude >> 14;
-       report->field[0]->value[3] = magnitude >> 14;
-       report->field[0]->value[4] = magnitude;
-       report->field[0]->value[5] = 0x00;
-       report->field[0]->value[6] = 0x00;
+       value[0] = 0xfe;
+       value[1] = 0x03;
+       value[2] = magnitude >> 14;
+       value[3] = magnitude >> 14;
+       value[4] = magnitude;
+       value[5] = 0x00;
+       value[6] = 0x00;
 
        usbhid_submit_report(hid, report, USB_DIR_OUT);
 }
@@ -200,15 +258,17 @@ static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range)
 {
        struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
        struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+       __s32 *value = report->field[0]->value;
+
        dbg_hid("G25/G27/DFGT: setting range to %u\n", range);
 
-       report->field[0]->value[0] = 0xf8;
-       report->field[0]->value[1] = 0x81;
-       report->field[0]->value[2] = range & 0x00ff;
-       report->field[0]->value[3] = (range & 0xff00) >> 8;
-       report->field[0]->value[4] = 0x00;
-       report->field[0]->value[5] = 0x00;
-       report->field[0]->value[6] = 0x00;
+       value[0] = 0xf8;
+       value[1] = 0x81;
+       value[2] = range & 0x00ff;
+       value[3] = (range & 0xff00) >> 8;
+       value[4] = 0x00;
+       value[5] = 0x00;
+       value[6] = 0x00;
 
        usbhid_submit_report(hid, report, USB_DIR_OUT);
 }
@@ -219,16 +279,18 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
        struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
        struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
        int start_left, start_right, full_range;
+       __s32 *value = report->field[0]->value;
+
        dbg_hid("Driving Force Pro: setting range to %u\n", range);
 
        /* Prepare "coarse" limit command */
-       report->field[0]->value[0] = 0xf8;
-       report->field[0]->value[1] = 0x00;      /* Set later */
-       report->field[0]->value[2] = 0x00;
-       report->field[0]->value[3] = 0x00;
-       report->field[0]->value[4] = 0x00;
-       report->field[0]->value[5] = 0x00;
-       report->field[0]->value[6] = 0x00;
+       value[0] = 0xf8;
+       value[1] = 0x00;        /* Set later */
+       value[2] = 0x00;
+       value[3] = 0x00;
+       value[4] = 0x00;
+       value[5] = 0x00;
+       value[6] = 0x00;
 
        if (range > 200) {
                report->field[0]->value[1] = 0x03;
@@ -240,13 +302,13 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
        usbhid_submit_report(hid, report, USB_DIR_OUT);
 
        /* Prepare "fine" limit command */
-       report->field[0]->value[0] = 0x81;
-       report->field[0]->value[1] = 0x0b;
-       report->field[0]->value[2] = 0x00;
-       report->field[0]->value[3] = 0x00;
-       report->field[0]->value[4] = 0x00;
-       report->field[0]->value[5] = 0x00;
-       report->field[0]->value[6] = 0x00;
+       value[0] = 0x81;
+       value[1] = 0x0b;
+       value[2] = 0x00;
+       value[3] = 0x00;
+       value[4] = 0x00;
+       value[5] = 0x00;
+       value[6] = 0x00;
 
        if (range == 200 || range == 900) {     /* Do not apply any fine limit */
                usbhid_submit_report(hid, report, USB_DIR_OUT);
@@ -257,11 +319,11 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
        start_left = (((full_range - range + 1) * 2047) / full_range);
        start_right = 0xfff - start_left;
 
-       report->field[0]->value[2] = start_left >> 4;
-       report->field[0]->value[3] = start_right >> 4;
-       report->field[0]->value[4] = 0xff;
-       report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
-       report->field[0]->value[6] = 0xff;
+       value[2] = start_left >> 4;
+       value[3] = start_right >> 4;
+       value[4] = 0xff;
+       value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
+       value[6] = 0xff;
 
        usbhid_submit_report(hid, report, USB_DIR_OUT);
 }
@@ -344,14 +406,15 @@ static void lg4ff_set_leds(struct hid_device *hid, __u8 leds)
 {
        struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
        struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
-
-       report->field[0]->value[0] = 0xf8;
-       report->field[0]->value[1] = 0x12;
-       report->field[0]->value[2] = leds;
-       report->field[0]->value[3] = 0x00;
-       report->field[0]->value[4] = 0x00;
-       report->field[0]->value[5] = 0x00;
-       report->field[0]->value[6] = 0x00;
+       __s32 *value = report->field[0]->value;
+
+       value[0] = 0xf8;
+       value[1] = 0x12;
+       value[2] = leds;
+       value[3] = 0x00;
+       value[4] = 0x00;
+       value[5] = 0x00;
+       value[6] = 0x00;
        usbhid_submit_report(hid, report, USB_DIR_OUT);
 }
 
@@ -360,7 +423,7 @@ static void lg4ff_led_set_brightness(struct led_classdev *led_cdev,
 {
        struct device *dev = led_cdev->dev->parent;
        struct hid_device *hid = container_of(dev, struct hid_device, dev);
-       struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hid);
+       struct lg_drv_data *drv_data = hid_get_drvdata(hid);
        struct lg4ff_device_entry *entry;
        int i, state = 0;
 
@@ -395,7 +458,7 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde
 {
        struct device *dev = led_cdev->dev->parent;
        struct hid_device *hid = container_of(dev, struct hid_device, dev);
-       struct lg_drv_data *drv_data = (struct lg_drv_data *)hid_get_drvdata(hid);
+       struct lg_drv_data *drv_data = hid_get_drvdata(hid);
        struct lg4ff_device_entry *entry;
        int i, value = 0;
 
@@ -501,7 +564,7 @@ int lg4ff_init(struct hid_device *hid)
        /* Check if autocentering is available and
         * set the centering force to zero by default */
        if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
-               if(rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN)  /* Formula Force EX expects different autocentering command */
+               if (rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */
                        dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex;
                else
                        dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
@@ -524,6 +587,7 @@ int lg4ff_init(struct hid_device *hid)
        }
        drv_data->device_props = entry;
 
+       entry->product_id = lg4ff_devices[i].product_id;
        entry->min_range = lg4ff_devices[i].min_range;
        entry->max_range = lg4ff_devices[i].max_range;
        entry->set_range = lg4ff_devices[i].set_range;
@@ -534,6 +598,18 @@ int lg4ff_init(struct hid_device *hid)
                return error;
        dbg_hid("sysfs interface created\n");
 
+       /* Set default axes parameters */
+       switch (lg4ff_devices[i].product_id) {
+       case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+               dbg_hid("Setting axes parameters for Driving Force Pro\n");
+               input_set_abs_params(dev, ABS_X, DFP_X_MIN, DFP_X_MAX, 0, 0);
+               input_set_abs_params(dev, ABS_Y, DFP_PEDAL_MIN, DFP_PEDAL_MAX, 0, 0);
+               input_set_abs_params(dev, ABS_RZ, DFP_PEDAL_MIN, DFP_PEDAL_MAX, 0, 0);
+               break;
+       default:
+               break;
+       }
+
        /* Set the maximum range to start with */
        entry->range = entry->max_range;
        if (entry->set_range != NULL)
@@ -594,6 +670,8 @@ out:
        return 0;
 }
 
+
+
 int lg4ff_deinit(struct hid_device *hid)
 {
        struct lg4ff_device_entry *entry;
index 7364726..25ddf3e 100644 (file)
@@ -392,7 +392,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 
        __set_bit(EV_ABS, input->evbit);
 
-       error = input_mt_init_slots(input, 16);
+       error = input_mt_init_slots(input, 16, 0);
        if (error)
                return error;
        input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
index e5c699b..3acdcfc 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
index dedf757..cd3643e 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
index 59c8b5c..3eb02b9 100644 (file)
@@ -51,12 +51,12 @@ MODULE_LICENSE("GPL");
 #define MT_QUIRK_VALID_IS_INRANGE      (1 << 5)
 #define MT_QUIRK_VALID_IS_CONFIDENCE   (1 << 6)
 #define MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE   (1 << 8)
+#define MT_QUIRK_NO_AREA               (1 << 9)
 
 struct mt_slot {
        __s32 x, y, p, w, h;
        __s32 contactid;        /* the device ContactID assigned to this slot */
        bool touch_state;       /* is the touch valid? */
-       bool seen_in_this_frame;/* has this slot been updated */
 };
 
 struct mt_class {
@@ -92,8 +92,9 @@ struct mt_device {
        __u8 touches_by_report; /* how many touches are present in one report:
                                * 1 means we should use a serial protocol
                                * > 1 means hybrid (multitouch) protocol */
+       bool serial_maybe;      /* need to check for serial protocol */
        bool curvalid;          /* is the current contact valid? */
-       struct mt_slot *slots;
+       unsigned mt_flags;      /* flags to pass to input-mt */
 };
 
 /* classes of device behavior */
@@ -115,6 +116,9 @@ struct mt_device {
 #define MT_CLS_EGALAX_SERIAL                   0x0104
 #define MT_CLS_TOPSEED                         0x0105
 #define MT_CLS_PANASONIC                       0x0106
+#define MT_CLS_FLATFROG                                0x0107
+#define MT_CLS_GENERALTOUCH_TWOFINGERS         0x0108
+#define MT_CLS_GENERALTOUCH_PWT_TENFINGERS     0x0109
 
 #define MT_DEFAULT_MAXCONTACT  10
 
@@ -134,25 +138,6 @@ static int cypress_compute_slot(struct mt_device *td)
                return -1;
 }
 
-static int find_slot_from_contactid(struct mt_device *td)
-{
-       int i;
-       for (i = 0; i < td->maxcontacts; ++i) {
-               if (td->slots[i].contactid == td->curdata.contactid &&
-                       td->slots[i].touch_state)
-                       return i;
-       }
-       for (i = 0; i < td->maxcontacts; ++i) {
-               if (!td->slots[i].seen_in_this_frame &&
-                       !td->slots[i].touch_state)
-                       return i;
-       }
-       /* should not occurs. If this happens that means
-        * that the device sent more touches that it says
-        * in the report descriptor. It is ignored then. */
-       return -1;
-}
-
 static struct mt_class mt_classes[] = {
        { .name = MT_CLS_DEFAULT,
                .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP },
@@ -190,7 +175,9 @@ static struct mt_class mt_classes[] = {
                        MT_QUIRK_SLOT_IS_CONTACTID,
                .sn_move = 2048,
                .sn_width = 128,
-               .sn_height = 128 },
+               .sn_height = 128,
+               .maxcontacts = 60,
+       },
        { .name = MT_CLS_CYPRESS,
                .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
                        MT_QUIRK_CYPRESS,
@@ -215,7 +202,24 @@ static struct mt_class mt_classes[] = {
        { .name = MT_CLS_PANASONIC,
                .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP,
                .maxcontacts = 4 },
+       { .name = MT_CLS_GENERALTOUCH_TWOFINGERS,
+               .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
+                       MT_QUIRK_VALID_IS_INRANGE |
+                       MT_QUIRK_SLOT_IS_CONTACTNUMBER,
+               .maxcontacts = 2
+       },
+       { .name = MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+               .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
+                       MT_QUIRK_SLOT_IS_CONTACTNUMBER,
+               .maxcontacts = 10
+       },
 
+       { .name = MT_CLS_FLATFROG,
+               .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
+                       MT_QUIRK_NO_AREA,
+               .sn_move = 2048,
+               .maxcontacts = 40,
+       },
        { }
 };
 
@@ -319,24 +323,16 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
        * We need to ignore fields that belong to other collections
        * such as Mouse that might have the same GenericDesktop usages. */
        if (field->application == HID_DG_TOUCHSCREEN)
-               set_bit(INPUT_PROP_DIRECT, hi->input->propbit);
+               td->mt_flags |= INPUT_MT_DIRECT;
        else if (field->application != HID_DG_TOUCHPAD)
                return 0;
 
-       /* In case of an indirect device (touchpad), we need to add
-        * specific BTN_TOOL_* to be handled by the synaptics xorg
-        * driver.
-        * We also consider that touchscreens providing buttons are touchpads.
+       /*
+        * Model touchscreens providing buttons as touchpads.
         */
        if (field->application == HID_DG_TOUCHPAD ||
-           (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON ||
-           cls->is_indirect) {
-               set_bit(INPUT_PROP_POINTER, hi->input->propbit);
-               set_bit(BTN_TOOL_FINGER, hi->input->keybit);
-               set_bit(BTN_TOOL_DOUBLETAP, hi->input->keybit);
-               set_bit(BTN_TOOL_TRIPLETAP, hi->input->keybit);
-               set_bit(BTN_TOOL_QUADTAP, hi->input->keybit);
-       }
+           (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)
+               td->mt_flags |= INPUT_MT_POINTER;
 
        /* eGalax devices provide a Digitizer.Stylus input which overrides
         * the correct Digitizers.Finger X/Y ranges.
@@ -353,8 +349,6 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                                        EV_ABS, ABS_MT_POSITION_X);
                        set_abs(hi->input, ABS_MT_POSITION_X, field,
                                cls->sn_move);
-                       /* touchscreen emulation */
-                       set_abs(hi->input, ABS_X, field, cls->sn_move);
                        mt_store_field(usage, td, hi);
                        td->last_field_index = field->index;
                        return 1;
@@ -363,8 +357,6 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                                        EV_ABS, ABS_MT_POSITION_Y);
                        set_abs(hi->input, ABS_MT_POSITION_Y, field,
                                cls->sn_move);
-                       /* touchscreen emulation */
-                       set_abs(hi->input, ABS_Y, field, cls->sn_move);
                        mt_store_field(usage, td, hi);
                        td->last_field_index = field->index;
                        return 1;
@@ -388,9 +380,6 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                        td->last_field_index = field->index;
                        return 1;
                case HID_DG_CONTACTID:
-                       if (!td->maxcontacts)
-                               td->maxcontacts = MT_DEFAULT_MAXCONTACT;
-                       input_mt_init_slots(hi->input, td->maxcontacts);
                        mt_store_field(usage, td, hi);
                        td->last_field_index = field->index;
                        td->touches_by_report++;
@@ -398,18 +387,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                case HID_DG_WIDTH:
                        hid_map_usage(hi, usage, bit, max,
                                        EV_ABS, ABS_MT_TOUCH_MAJOR);
-                       set_abs(hi->input, ABS_MT_TOUCH_MAJOR, field,
-                               cls->sn_width);
+                       if (!(cls->quirks & MT_QUIRK_NO_AREA))
+                               set_abs(hi->input, ABS_MT_TOUCH_MAJOR, field,
+                                       cls->sn_width);
                        mt_store_field(usage, td, hi);
                        td->last_field_index = field->index;
                        return 1;
                case HID_DG_HEIGHT:
                        hid_map_usage(hi, usage, bit, max,
                                        EV_ABS, ABS_MT_TOUCH_MINOR);
-                       set_abs(hi->input, ABS_MT_TOUCH_MINOR, field,
-                               cls->sn_height);
-                       input_set_abs_params(hi->input,
+                       if (!(cls->quirks & MT_QUIRK_NO_AREA)) {
+                               set_abs(hi->input, ABS_MT_TOUCH_MINOR, field,
+                                       cls->sn_height);
+                               input_set_abs_params(hi->input,
                                        ABS_MT_ORIENTATION, 0, 1, 0, 0);
+                       }
                        mt_store_field(usage, td, hi);
                        td->last_field_index = field->index;
                        return 1;
@@ -418,9 +410,6 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                                        EV_ABS, ABS_MT_PRESSURE);
                        set_abs(hi->input, ABS_MT_PRESSURE, field,
                                cls->sn_pressure);
-                       /* touchscreen emulation */
-                       set_abs(hi->input, ABS_PRESSURE, field,
-                               cls->sn_pressure);
                        mt_store_field(usage, td, hi);
                        td->last_field_index = field->index;
                        return 1;
@@ -464,7 +453,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi,
        return -1;
 }
 
-static int mt_compute_slot(struct mt_device *td)
+static int mt_compute_slot(struct mt_device *td, struct input_dev *input)
 {
        __s32 quirks = td->mtclass.quirks;
 
@@ -480,42 +469,23 @@ static int mt_compute_slot(struct mt_device *td)
        if (quirks & MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE)
                return td->curdata.contactid - 1;
 
-       return find_slot_from_contactid(td);
+       return input_mt_get_slot_by_key(input, td->curdata.contactid);
 }
 
 /*
  * this function is called when a whole contact has been processed,
  * so that it can assign it to a slot and store the data there
  */
-static void mt_complete_slot(struct mt_device *td)
+static void mt_complete_slot(struct mt_device *td, struct input_dev *input)
 {
-       td->curdata.seen_in_this_frame = true;
        if (td->curvalid) {
-               int slotnum = mt_compute_slot(td);
-
-               if (slotnum >= 0 && slotnum < td->maxcontacts)
-                       td->slots[slotnum] = td->curdata;
-       }
-       td->num_received++;
-}
+               int slotnum = mt_compute_slot(td, input);
+               struct mt_slot *s = &td->curdata;
 
+               if (slotnum < 0 || slotnum >= td->maxcontacts)
+                       return;
 
-/*
- * this function is called when a whole packet has been received and processed,
- * so that it can decide what to send to the input layer.
- */
-static void mt_emit_event(struct mt_device *td, struct input_dev *input)
-{
-       int i;
-
-       for (i = 0; i < td->maxcontacts; ++i) {
-               struct mt_slot *s = &(td->slots[i]);
-               if ((td->mtclass.quirks & MT_QUIRK_NOT_SEEN_MEANS_UP) &&
-                       !s->seen_in_this_frame) {
-                       s->touch_state = false;
-               }
-
-               input_mt_slot(input, i);
+               input_mt_slot(input, slotnum);
                input_mt_report_slot_state(input, MT_TOOL_FINGER,
                        s->touch_state);
                if (s->touch_state) {
@@ -532,24 +502,29 @@ static void mt_emit_event(struct mt_device *td, struct input_dev *input)
                        input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
                        input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
                }
-               s->seen_in_this_frame = false;
-
        }
 
-       input_mt_report_pointer_emulation(input, true);
+       td->num_received++;
+}
+
+/*
+ * this function is called when a whole packet has been received and processed,
+ * so that it can decide what to send to the input layer.
+ */
+static void mt_sync_frame(struct mt_device *td, struct input_dev *input)
+{
+       input_mt_sync_frame(input);
        input_sync(input);
        td->num_received = 0;
 }
 
-
-
 static int mt_event(struct hid_device *hid, struct hid_field *field,
                                struct hid_usage *usage, __s32 value)
 {
        struct mt_device *td = hid_get_drvdata(hid);
        __s32 quirks = td->mtclass.quirks;
 
-       if (hid->claimed & HID_CLAIMED_INPUT && td->slots) {
+       if (hid->claimed & HID_CLAIMED_INPUT) {
                switch (usage->hid) {
                case HID_DG_INRANGE:
                        if (quirks & MT_QUIRK_ALWAYS_VALID)
@@ -602,11 +577,11 @@ static int mt_event(struct hid_device *hid, struct hid_field *field,
                }
 
                if (usage->hid == td->last_slot_field)
-                       mt_complete_slot(td);
+                       mt_complete_slot(td, field->hidinput->input);
 
                if (field->index == td->last_field_index
                        && td->num_received >= td->num_expected)
-                       mt_emit_event(td, field->hidinput->input);
+                       mt_sync_frame(td, field->hidinput->input);
 
        }
 
@@ -685,18 +660,45 @@ static void mt_post_parse(struct mt_device *td)
        }
 }
 
+static void mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
+
+{
+       struct mt_device *td = hid_get_drvdata(hdev);
+       struct mt_class *cls = &td->mtclass;
+       struct input_dev *input = hi->input;
+
+       /* Only initialize slots for MT input devices */
+       if (!test_bit(ABS_MT_POSITION_X, input->absbit))
+               return;
+
+       if (!td->maxcontacts)
+               td->maxcontacts = MT_DEFAULT_MAXCONTACT;
+
+       mt_post_parse(td);
+       if (td->serial_maybe)
+               mt_post_parse_default_settings(td);
+
+       if (cls->is_indirect)
+               td->mt_flags |= INPUT_MT_POINTER;
+
+       if (cls->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
+               td->mt_flags |= INPUT_MT_DROP_UNUSED;
+
+       input_mt_init_slots(input, td->maxcontacts, td->mt_flags);
+
+       td->mt_flags = 0;
+}
+
 static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
 {
        int ret, i;
        struct mt_device *td;
        struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */
 
-       if (id) {
-               for (i = 0; mt_classes[i].name ; i++) {
-                       if (id->driver_data == mt_classes[i].name) {
-                               mtclass = &(mt_classes[i]);
-                               break;
-                       }
+       for (i = 0; mt_classes[i].name ; i++) {
+               if (id->driver_data == mt_classes[i].name) {
+                       mtclass = &(mt_classes[i]);
+                       break;
                }
        }
 
@@ -722,6 +724,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
                goto fail;
        }
 
+       if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID)
+               td->serial_maybe = true;
+
        ret = hid_parse(hdev);
        if (ret != 0)
                goto fail;
@@ -730,20 +735,6 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
        if (ret)
                goto fail;
 
-       mt_post_parse(td);
-
-       if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID)
-               mt_post_parse_default_settings(td);
-
-       td->slots = kzalloc(td->maxcontacts * sizeof(struct mt_slot),
-                               GFP_KERNEL);
-       if (!td->slots) {
-               dev_err(&hdev->dev, "cannot allocate multitouch slots\n");
-               hid_hw_stop(hdev);
-               ret = -ENOMEM;
-               goto fail;
-       }
-
        ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group);
 
        mt_set_maxcontacts(hdev);
@@ -767,6 +758,32 @@ static int mt_reset_resume(struct hid_device *hdev)
        mt_set_input_mode(hdev);
        return 0;
 }
+
+static int mt_resume(struct hid_device *hdev)
+{
+       struct usb_interface *intf;
+       struct usb_host_interface *interface;
+       struct usb_device *dev;
+
+       if (hdev->bus != BUS_USB)
+               return 0;
+
+       intf = to_usb_interface(hdev->dev.parent);
+       interface = intf->cur_altsetting;
+       dev = hid_to_usb_dev(hdev);
+
+       /* Some Elan legacy devices require SET_IDLE to be set on resume.
+        * It should be safe to send it to other devices too.
+        * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */
+
+       usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+                       HID_REQ_SET_IDLE,
+                       USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       0, interface->desc.bInterfaceNumber,
+                       NULL, 0, USB_CTRL_SET_TIMEOUT);
+
+       return 0;
+}
 #endif
 
 static void mt_remove(struct hid_device *hdev)
@@ -774,7 +791,6 @@ static void mt_remove(struct hid_device *hdev)
        struct mt_device *td = hid_get_drvdata(hdev);
        sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group);
        hid_hw_stop(hdev);
-       kfree(td->slots);
        kfree(td);
        hid_set_drvdata(hdev, NULL);
 }
@@ -885,17 +901,37 @@ static const struct hid_device_id mt_devices[] = {
                        USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349) },
        { .driver_data = MT_CLS_EGALAX_SERIAL,
                MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+                       USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7) },
+       { .driver_data = MT_CLS_EGALAX_SERIAL,
+               MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
                        USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001) },
+       { .driver_data = MT_CLS_EGALAX,
+               HID_USB_DEVICE(USB_VENDOR_ID_DWAV,
+                       USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224) },
+       { .driver_data = MT_CLS_EGALAX,
+               HID_USB_DEVICE(USB_VENDOR_ID_DWAV,
+                       USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72D0) },
+       { .driver_data = MT_CLS_EGALAX,
+               HID_USB_DEVICE(USB_VENDOR_ID_DWAV,
+                       USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72C4) },
 
        /* Elo TouchSystems IntelliTouch Plus panel */
        { .driver_data = MT_CLS_DUAL_NSMU_CONTACTID,
                MT_USB_DEVICE(USB_VENDOR_ID_ELO,
                        USB_DEVICE_ID_ELO_TS2515) },
 
+       /* Flatfrog Panels */
+       { .driver_data = MT_CLS_FLATFROG,
+               MT_USB_DEVICE(USB_VENDOR_ID_FLATFROG,
+                       USB_DEVICE_ID_MULTITOUCH_3200) },
+
        /* GeneralTouch panel */
-       { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER,
+       { .driver_data = MT_CLS_GENERALTOUCH_TWOFINGERS,
                MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
                        USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS) },
+       { .driver_data = MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+               MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+                       USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PWT_TENFINGERS) },
 
        /* Gametel game controller */
        { .driver_data = MT_CLS_DEFAULT,
@@ -1087,11 +1123,13 @@ static struct hid_driver mt_driver = {
        .remove = mt_remove,
        .input_mapping = mt_input_mapping,
        .input_mapped = mt_input_mapped,
+       .input_configured = mt_input_configured,
        .feature_mapping = mt_feature_mapping,
        .usage_table = mt_grabbed_usages,
        .event = mt_event,
 #ifdef CONFIG_PM
        .reset_resume = mt_reset_resume,
+       .resume = mt_resume,
 #endif
 };
 
index 9fae2eb..86a969f 100644 (file)
@@ -882,10 +882,10 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
        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;
+       nd->sensor_logical_width = 1;
+       nd->sensor_logical_height = 1;
+       nd->sensor_physical_width = 1;
+       nd->sensor_physical_height = 1;
 
        hid_set_drvdata(hdev, nd);
 
index f1ea3ff..4c521de 100644 (file)
@@ -5,7 +5,6 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
  *  Copyright (c) 2006-2007 Jiri Kosina
- *  Copyright (c) 2007 Paul Walmsley
  *  Copyright (c) 2008 Jiri Slaby
  */
 
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
deleted file mode 100644 (file)
index 27c8ebd..0000000
+++ /dev/null
@@ -1,2748 +0,0 @@
-/***************************************************************************
- *   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>
-#include <linux/module.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,
-       .red            = {
-               .offset = 0,
-               .length = 1,
-               .msb_right = 0,
-       },
-       .green          = {
-               .offset = 0,
-               .length = 1,
-               .msb_right = 0,
-       },
-       .blue           = {
-               .offset = 0,
-               .length = 1,
-               .msb_right = 0,
-       },
-       .transp         = {
-               .offset = 0,
-               .length = 0,
-               .msb_right = 0,
-       },
-};
-#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_force;
-       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;
-       }
-       hid_warn(hdev, "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] >> (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, 0, PICOLCDFB_SIZE);
-                       memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp);
-               }
-               data->fb_force = 1;
-       }
-
-       /* 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;
-
-       if (!data)
-               return;
-
-       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) ||
-                               data->fb_force) {
-                               n += 2;
-                               if (!data->fb_info->par)
-                                       return; /* device lost! */
-                               if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
-                                       usbhid_wait_io(data->hdev);
-                                       n = 0;
-                               }
-                               picolcd_fb_send_tile(data->hdev, chip, tile);
-                       }
-       data->fb_force = false;
-       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;
-       u32 *ref_cnt = info->pseudo_palette;
-       int may_release;
-
-       info->par = NULL;
-       if (data)
-               data->fb_info = NULL;
-       fb_deferred_io_cleanup(info);
-
-       ref_cnt--;
-       mutex_lock(&info->lock);
-       (*ref_cnt)--;
-       may_release = !*ref_cnt;
-       mutex_unlock(&info->lock);
-       if (may_release) {
-               vfree((u8 *)info->fix.smem_start);
-               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;
-               var->red.length     = 8;
-               var->green.length   = 8;
-               var->blue.length    = 8;
-       } else {
-               var->bits_per_pixel = 1;
-               var->red.length     = 1;
-               var->green.length   = 1;
-               var->blue.length    = 1;
-       }
-       return 0;
-}
-
-static int picolcd_set_par(struct fb_info *info)
-{
-       struct picolcd_data *data = info->par;
-       u8 *tmp_fb, *o_fb;
-       if (!data)
-               return -ENODEV;
-       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;
-       tmp_fb = kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL);
-       if (!tmp_fb)
-               return -ENOMEM;
-
-       /* 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;
-                       }
-                       tmp_fb[i] = p;
-               }
-               memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
-               info->fix.visual = FB_VISUAL_MONO01;
-               info->fix.line_length = PICOLCDFB_WIDTH / 8;
-       } else {
-               int i;
-               memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
-               for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
-                       o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
-               info->fix.visual = FB_VISUAL_DIRECTCOLOR;
-               info->fix.line_length = PICOLCDFB_WIDTH;
-       }
-
-       kfree(tmp_fb);
-       data->fb_bpp      = info->var.bits_per_pixel;
-       return 0;
-}
-
-/* Do refcounting on our FB and cleanup per worker if FB is
- * closed after unplug of our device
- * (fb_release holds info->lock and still touches info after
- *  we return so we can't release it immediately.
- */
-struct picolcd_fb_cleanup_item {
-       struct fb_info *info;
-       struct picolcd_fb_cleanup_item *next;
-};
-static struct picolcd_fb_cleanup_item *fb_pending;
-static DEFINE_SPINLOCK(fb_pending_lock);
-
-static void picolcd_fb_do_cleanup(struct work_struct *data)
-{
-       struct picolcd_fb_cleanup_item *item;
-       unsigned long flags;
-
-       do {
-               spin_lock_irqsave(&fb_pending_lock, flags);
-               item = fb_pending;
-               fb_pending = item ? item->next : NULL;
-               spin_unlock_irqrestore(&fb_pending_lock, flags);
-
-               if (item) {
-                       u8 *fb = (u8 *)item->info->fix.smem_start;
-                       /* make sure we do not race against fb core when
-                        * releasing */
-                       mutex_lock(&item->info->lock);
-                       mutex_unlock(&item->info->lock);
-                       framebuffer_release(item->info);
-                       vfree(fb);
-               }
-       } while (item);
-}
-
-static DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup);
-
-static int picolcd_fb_open(struct fb_info *info, int u)
-{
-       u32 *ref_cnt = info->pseudo_palette;
-       ref_cnt--;
-
-       (*ref_cnt)++;
-       return 0;
-}
-
-static int picolcd_fb_release(struct fb_info *info, int u)
-{
-       u32 *ref_cnt = info->pseudo_palette;
-       ref_cnt--;
-
-       (*ref_cnt)++;
-       if (!*ref_cnt) {
-               unsigned long flags;
-               struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt;
-               item--;
-               spin_lock_irqsave(&fb_pending_lock, flags);
-               item->next = fb_pending;
-               fb_pending = item;
-               spin_unlock_irqrestore(&fb_pending_lock, flags);
-               schedule_work(&picolcd_fb_cleanup);
-       }
-       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_open      = picolcd_fb_open,
-       .fb_release   = picolcd_fb_release,
-       .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 i, error = -ENOMEM;
-       u8 *fb_vbitmap = NULL;
-       u8 *fb_bitmap  = NULL;
-       u32 *palette;
-
-       fb_bitmap = vmalloc(PICOLCDFB_SIZE*8);
-       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;
-       /* The extra memory is:
-        * - struct picolcd_fb_cleanup_item
-        * - u32 for ref_count
-        * - 256*u32 for pseudo_palette
-        */
-       info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev);
-       if (info == NULL) {
-               dev_err(dev, "failed to allocate a framebuffer\n");
-               goto err_nomem;
-       }
-
-       palette  = info->par + sizeof(struct picolcd_fb_cleanup_item);
-       *palette = 1;
-       palette++;
-       for (i = 0; i < 256; i++)
-               palette[i] = i > 0 && i < 16 ? 0xff : 0;
-       info->pseudo_palette = palette;
-       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*8;
-       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;
-       }
-       fb_deferred_io_init(info);
-       data->fb_info    = info;
-       error = register_framebuffer(info);
-       if (error) {
-               dev_err(dev, "failed to register framebuffer\n");
-               goto err_sysfs;
-       }
-       /* schedule first output of framebuffer */
-       data->fb_force = 1;
-       schedule_delayed_work(&info->deferred_work, 0);
-       return 0;
-
-err_sysfs:
-       fb_deferred_io_cleanup(info);
-       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;
-
-       if (!info)
-               return;
-
-       info->par = NULL;
-       device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
-       unregister_framebuffer(info);
-       data->fb_vbitmap = NULL;
-       data->fb_bitmap  = NULL;
-       data->fb_bpp     = 0;
-       data->fb_info    = NULL;
-       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.type = BACKLIGHT_RAW;
-       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) {
-               hid_err(hdev, "no version response from PicoLCD\n");
-               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) {
-                       hid_info(hdev, "PicoLCD, bootloader version %d.%d\n",
-                                verinfo->raw_data[1], verinfo->raw_data[0]);
-               } else {
-                       hid_info(hdev, "PicoLCD, firmware version %d.%d\n",
-                                verinfo->raw_data[1], verinfo->raw_data[0]);
-               }
-       } else {
-               hid_err(hdev, "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 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] = min((size_t)20, s);
-       if (*off + raw_data[2] > 0xff)
-               raw_data[2] = 0x100 - *off;
-
-       if (copy_from_user(raw_data+3, u, min((u8)20, 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     = simple_open,
-       .read     = picolcd_debug_eeprom_read,
-       .write    = picolcd_debug_eeprom_write,
-       .llseek   = generic_file_llseek,
-};
-
-/*
- * The "flash" file
- */
-/* 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 preceded 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 happen without special restriction
- */
-static const struct file_operations picolcd_debug_flash_fops = {
-       .owner    = THIS_MODULE,
-       .open     = simple_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 (list_empty(&hdev->debug_list))
-               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)) {
-               kfree(buff);
-               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)
-               hid_warn(hdev, "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 (PMSG_IS_AUTO(message))
-               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) {
-               hid_err(hdev, "unsupported KEY_STATE report\n");
-               return -EINVAL;
-       }
-
-       idev = input_allocate_device();
-       if (idev == NULL) {
-               hid_err(hdev, "failed to allocate input device\n");
-               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) {
-               hid_err(hdev, "error registering the input device\n");
-               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)
-               hid_info(hdev, "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)
-               hid_info(hdev, "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) {
-               hid_err(hdev, "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) {
-               hid_err(hdev, "device report parse failed\n");
-               goto err_cleanup_data;
-       }
-
-       error = hid_hw_start(hdev, 0);
-       if (error) {
-               hid_err(hdev, "hardware start failed\n");
-               goto err_cleanup_data;
-       }
-
-       error = hid_hw_open(hdev);
-       if (error) {
-               hid_err(hdev, "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) {
-               hid_err(hdev, "failed to create sysfs attributes\n");
-               goto err_cleanup_hid_ll;
-       }
-
-       error = device_create_file(&hdev->dev, &dev_attr_operation_mode);
-       if (error) {
-               hid_err(hdev, "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:
-       hid_hw_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);
-#ifdef CONFIG_HID_PICOLCD_FB
-       /* short-circuit FB as early as possible in order to
-        * avoid long delays if we host console.
-        */
-       if (data->fb_info)
-               data->fb_info->par = NULL;
-#endif
-
-       picolcd_exit_devfs(data);
-       device_remove_file(&hdev->dev, &dev_attr_operation_mode);
-       device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay);
-       hid_hw_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);
-#ifdef CONFIG_HID_PICOLCD_FB
-       flush_work_sync(&picolcd_fb_cleanup);
-       WARN_ON(fb_pending);
-#endif
-}
-
-module_init(picolcd_init);
-module_exit(picolcd_exit);
-MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-picolcd.h b/drivers/hid/hid-picolcd.h
new file mode 100644 (file)
index 0000000..020cef6
--- /dev/null
@@ -0,0 +1,309 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 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/>.  *
+ ***************************************************************************/
+
+#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]         */
+
+/* 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];
+};
+
+
+#define PICOLCD_KEYS 17
+
+/* 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;
+#ifdef CONFIG_HID_PICOLCD_CIR
+       struct rc_dev *rc_dev;
+#endif
+       unsigned short keycode[PICOLCD_KEYS];
+
+#ifdef CONFIG_HID_PICOLCD_FB
+       /* Framebuffer stuff */
+       struct fb_info *fb_info;
+#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_CIR_SHUN 4
+};
+
+#ifdef CONFIG_HID_PICOLCD_FB
+struct picolcd_fb_data {
+       /* Framebuffer stuff */
+       spinlock_t lock;
+       struct picolcd_data *picolcd;
+       u8 update_rate;
+       u8 bpp;
+       u8 force;
+       u8 ready;
+       u8 *vbitmap;            /* local copy of what was sent to PicoLCD */
+       u8 *bitmap;             /* framebuffer */
+};
+#endif /* CONFIG_HID_PICOLCD_FB */
+
+/* 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)
+
+struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir);
+
+#ifdef CONFIG_DEBUG_FS
+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)
+
+void picolcd_debug_raw_event(struct picolcd_data *data,
+               struct hid_device *hdev, struct hid_report *report,
+               u8 *raw_data, int size);
+
+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);
+
+void picolcd_exit_devfs(struct picolcd_data *data);
+#else
+static inline void picolcd_debug_out_report(struct picolcd_data *data,
+               struct hid_device *hdev, struct hid_report *report)
+{
+}
+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 */
+
+
+#ifdef CONFIG_HID_PICOLCD_FB
+int picolcd_fb_reset(struct picolcd_data *data, int clear);
+
+int picolcd_init_framebuffer(struct picolcd_data *data);
+
+void picolcd_exit_framebuffer(struct picolcd_data *data);
+
+void picolcd_fb_refresh(struct picolcd_data *data);
+#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)
+{
+}
+static inline void picolcd_fb_refresh(struct picolcd_data *data)
+{
+}
+#define picolcd_fbinfo(d) NULL
+#endif /* CONFIG_HID_PICOLCD_FB */
+
+
+#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
+int picolcd_init_backlight(struct picolcd_data *data,
+               struct hid_report *report);
+
+void picolcd_exit_backlight(struct picolcd_data *data);
+
+int picolcd_resume_backlight(struct picolcd_data *data);
+
+void picolcd_suspend_backlight(struct picolcd_data *data);
+#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
+int picolcd_init_lcd(struct picolcd_data *data,
+               struct hid_report *report);
+
+void picolcd_exit_lcd(struct picolcd_data *data);
+
+int picolcd_resume_lcd(struct picolcd_data *data);
+#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
+int picolcd_init_leds(struct picolcd_data *data,
+               struct hid_report *report);
+
+void picolcd_exit_leds(struct picolcd_data *data);
+
+void picolcd_leds_set(struct picolcd_data *data);
+#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 void picolcd_leds_set(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_HID_PICOLCD_LEDS */
+
+
+#ifdef CONFIG_HID_PICOLCD_CIR
+int picolcd_raw_cir(struct picolcd_data *data,
+               struct hid_report *report, u8 *raw_data, int size);
+
+int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report);
+
+void picolcd_exit_cir(struct picolcd_data *data);
+#else
+static inline int picolcd_raw_cir(struct picolcd_data *data,
+               struct hid_report *report, u8 *raw_data, int size)
+{
+       return 1;
+}
+static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report)
+{
+       return 0;
+}
+static inline void picolcd_exit_cir(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_HID_PICOLCD_LIRC */
+
+int picolcd_reset(struct hid_device *hdev);
+struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
+                       int report_id, const u8 *raw_data, int size);
diff --git a/drivers/hid/hid-picolcd_backlight.c b/drivers/hid/hid-picolcd_backlight.c
new file mode 100644 (file)
index 0000000..b91f309
--- /dev/null
@@ -0,0 +1,122 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 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 "usbhid/usbhid.h"
+#include <linux/usb.h>
+
+#include <linux/fb.h>
+#include <linux/backlight.h>
+
+#include "hid-picolcd.h"
+
+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);
+       if (!(data->status & PICOLCD_FAILED))
+               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,
+};
+
+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.type = BACKLIGHT_RAW;
+       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;
+}
+
+void picolcd_exit_backlight(struct picolcd_data *data)
+{
+       struct backlight_device *bdev = data->backlight;
+
+       data->backlight = NULL;
+       if (bdev)
+               backlight_device_unregister(bdev);
+}
+
+int picolcd_resume_backlight(struct picolcd_data *data)
+{
+       if (!data->backlight)
+               return 0;
+       return picolcd_set_brightness(data->backlight);
+}
+
+#ifdef CONFIG_PM
+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 */
+
diff --git a/drivers/hid/hid-picolcd_cir.c b/drivers/hid/hid-picolcd_cir.c
new file mode 100644 (file)
index 0000000..13ca919
--- /dev/null
@@ -0,0 +1,152 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 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>
+#include <linux/module.h>
+#include <media/rc-core.h>
+
+#include "hid-picolcd.h"
+
+
+int picolcd_raw_cir(struct picolcd_data *data,
+               struct hid_report *report, u8 *raw_data, int size)
+{
+       unsigned long flags;
+       int i, w, sz;
+       DEFINE_IR_RAW_EVENT(rawir);
+
+       /* ignore if rc_dev is NULL or status is shunned */
+       spin_lock_irqsave(&data->lock, flags);
+       if (!data->rc_dev || (data->status & PICOLCD_CIR_SHUN)) {
+               spin_unlock_irqrestore(&data->lock, flags);
+               return 1;
+       }
+       spin_unlock_irqrestore(&data->lock, flags);
+
+       /* PicoLCD USB packets contain 16-bit intervals in network order,
+        * with value negated for pulse. Intervals are in microseconds.
+        *
+        * Note: some userspace LIRC code for PicoLCD says negated values
+        * for space - is it a matter of IR chip? (pulse for my TSOP2236)
+        *
+        * In addition, the first interval seems to be around 15000 + base
+        * interval for non-first report of IR data - thus the quirk below
+        * to get RC_CODE to understand Sony and JVC remotes I have at hand
+        */
+       sz = size > 0 ? min((int)raw_data[0], size-1) : 0;
+       for (i = 0; i+1 < sz; i += 2) {
+               init_ir_raw_event(&rawir);
+               w = (raw_data[i] << 8) | (raw_data[i+1]);
+               rawir.pulse = !!(w & 0x8000);
+               rawir.duration = US_TO_NS(rawir.pulse ? (65536 - w) : w);
+               /* Quirk!! - see above */
+               if (i == 0 && rawir.duration > 15000000)
+                       rawir.duration -= 15000000;
+               ir_raw_event_store(data->rc_dev, &rawir);
+       }
+       ir_raw_event_handle(data->rc_dev);
+
+       return 1;
+}
+
+static int picolcd_cir_open(struct rc_dev *dev)
+{
+       struct picolcd_data *data = dev->priv;
+       unsigned long flags;
+
+       spin_lock_irqsave(&data->lock, flags);
+       data->status &= ~PICOLCD_CIR_SHUN;
+       spin_unlock_irqrestore(&data->lock, flags);
+       return 0;
+}
+
+static void picolcd_cir_close(struct rc_dev *dev)
+{
+       struct picolcd_data *data = dev->priv;
+       unsigned long flags;
+
+       spin_lock_irqsave(&data->lock, flags);
+       data->status |= PICOLCD_CIR_SHUN;
+       spin_unlock_irqrestore(&data->lock, flags);
+}
+
+/* initialize CIR input device */
+int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report)
+{
+       struct rc_dev *rdev;
+       int ret = 0;
+
+       rdev = rc_allocate_device();
+       if (!rdev)
+               return -ENOMEM;
+
+       rdev->priv             = data;
+       rdev->driver_type      = RC_DRIVER_IR_RAW;
+       rdev->allowed_protos   = RC_TYPE_ALL;
+       rdev->open             = picolcd_cir_open;
+       rdev->close            = picolcd_cir_close;
+       rdev->input_name       = data->hdev->name;
+       rdev->input_phys       = data->hdev->phys;
+       rdev->input_id.bustype = data->hdev->bus;
+       rdev->input_id.vendor  = data->hdev->vendor;
+       rdev->input_id.product = data->hdev->product;
+       rdev->input_id.version = data->hdev->version;
+       rdev->dev.parent       = &data->hdev->dev;
+       rdev->driver_name      = PICOLCD_NAME;
+       rdev->map_name         = RC_MAP_RC6_MCE;
+       rdev->timeout          = MS_TO_NS(100);
+       rdev->rx_resolution    = US_TO_NS(1);
+
+       ret = rc_register_device(rdev);
+       if (ret)
+               goto err;
+       data->rc_dev = rdev;
+       return 0;
+
+err:
+       rc_free_device(rdev);
+       return ret;
+}
+
+void picolcd_exit_cir(struct picolcd_data *data)
+{
+       struct rc_dev *rdev = data->rc_dev;
+
+       data->rc_dev = NULL;
+       rc_unregister_device(rdev);
+}
+
diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c
new file mode 100644 (file)
index 0000000..86df26e
--- /dev/null
@@ -0,0 +1,689 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 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/completion.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+
+#include "hid-picolcd.h"
+
+
+/* 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[PICOLCD_KEYS] = {
+       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 */
+};
+
+
+/* Find a given report */
+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;
+       }
+       hid_warn(hdev, "No report with id 0x%x found\n", id);
+       return NULL;
+}
+
+/* Submit a report and wait for a reply from device - if device fades away
+ * or does not respond in time, return NULL */
+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++;
+               }
+       if (data->status & PICOLCD_FAILED) {
+               kfree(work);
+               work = NULL;
+       } else {
+               data->pending = work;
+               usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
+               spin_unlock_irqrestore(&data->lock, flags);
+               wait_for_completion_interruptible_timeout(&work->ready, HZ*2);
+               spin_lock_irqsave(&data->lock, flags);
+               data->pending = NULL;
+       }
+       spin_unlock_irqrestore(&data->lock, flags);
+       mutex_unlock(&data->mutex);
+       return work;
+}
+
+/*
+ * input class device
+ */
+static int picolcd_raw_keypad(struct picolcd_data *data,
+               struct hid_report *report, u8 *raw_data, int size)
+{
+       /*
+        * Keypad event
+        * First and second data bytes list currently pressed keys,
+        * 0x00 means no key and at most 2 keys may be pressed at same time
+        */
+       int i, j;
+
+       /* determine newly pressed keys */
+       for (i = 0; i < size; i++) {
+               unsigned int key_code;
+               if (raw_data[i] == 0)
+                       continue;
+               for (j = 0; j < sizeof(data->pressed_keys); j++)
+                       if (data->pressed_keys[j] == raw_data[i])
+                               goto key_already_down;
+               for (j = 0; j < sizeof(data->pressed_keys); j++)
+                       if (data->pressed_keys[j] == 0) {
+                               data->pressed_keys[j] = raw_data[i];
+                               break;
+                       }
+               input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]);
+               if (raw_data[i] < PICOLCD_KEYS)
+                       key_code = data->keycode[raw_data[i]];
+               else
+                       key_code = KEY_UNKNOWN;
+               if (key_code != KEY_UNKNOWN) {
+                       dbg_hid(PICOLCD_NAME " got key press for %u:%d",
+                                       raw_data[i], key_code);
+                       input_report_key(data->input_keys, key_code, 1);
+               }
+               input_sync(data->input_keys);
+key_already_down:
+               continue;
+       }
+
+       /* determine newly released keys */
+       for (j = 0; j < sizeof(data->pressed_keys); j++) {
+               unsigned int key_code;
+               if (data->pressed_keys[j] == 0)
+                       continue;
+               for (i = 0; i < size; i++)
+                       if (data->pressed_keys[j] == raw_data[i])
+                               goto key_still_down;
+               input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]);
+               if (data->pressed_keys[j] < PICOLCD_KEYS)
+                       key_code = data->keycode[data->pressed_keys[j]];
+               else
+                       key_code = KEY_UNKNOWN;
+               if (key_code != KEY_UNKNOWN) {
+                       dbg_hid(PICOLCD_NAME " got key release for %u:%d",
+                                       data->pressed_keys[j], key_code);
+                       input_report_key(data->input_keys, key_code, 0);
+               }
+               input_sync(data->input_keys);
+               data->pressed_keys[j] = 0;
+key_still_down:
+               continue;
+       }
+       return 1;
+}
+
+static int picolcd_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) {
+               hid_err(hdev, "no version response from PicoLCD\n");
+               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) {
+                       hid_info(hdev, "PicoLCD, bootloader version %d.%d\n",
+                                verinfo->raw_data[1], verinfo->raw_data[0]);
+               } else {
+                       hid_info(hdev, "PicoLCD, firmware version %d.%d\n",
+                                verinfo->raw_data[1], verinfo->raw_data[0]);
+               }
+       } else {
+               hid_err(hdev, "confused, got unexpected version response from PicoLCD\n");
+               ret = -EINVAL;
+       }
+       kfree(verinfo);
+       return ret;
+}
+
+/*
+ * Reset our device and wait for answer to VERSION request
+ */
+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);
+       if (data->status & PICOLCD_FAILED) {
+               spin_unlock_irqrestore(&data->lock, flags);
+               return -ENODEV;
+       }
+       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);
+       picolcd_fb_refresh(data);
+       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);
+
+/*
+ * 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) {
+               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 (PMSG_IS_AUTO(message))
+               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) {
+               hid_err(hdev, "unsupported KEY_STATE report\n");
+               return -EINVAL;
+       }
+
+       idev = input_allocate_device();
+       if (idev == NULL) {
+               hid_err(hdev, "failed to allocate input device\n");
+               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;
+       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) {
+               hid_err(hdev, "error registering the input device\n");
+               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);
+}
+
+static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
+{
+       int error;
+
+       /* 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)
+{
+       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) {
+               hid_err(hdev, "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) {
+               hid_err(hdev, "device report parse failed\n");
+               goto err_cleanup_data;
+       }
+
+       error = hid_hw_start(hdev, 0);
+       if (error) {
+               hid_err(hdev, "hardware start failed\n");
+               goto err_cleanup_data;
+       }
+
+       error = hid_hw_open(hdev);
+       if (error) {
+               hid_err(hdev, "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) {
+               hid_err(hdev, "failed to create sysfs attributes\n");
+               goto err_cleanup_hid_ll;
+       }
+
+       error = device_create_file(&hdev->dev, &dev_attr_operation_mode);
+       if (error) {
+               hid_err(hdev, "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:
+       hid_hw_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);
+       hid_hw_close(hdev);
+       hid_hw_stop(hdev);
+
+       /* 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);
+
+       hid_set_drvdata(hdev, NULL);
+       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-picolcd_debugfs.c b/drivers/hid/hid-picolcd_debugfs.c
new file mode 100644 (file)
index 0000000..4809aa1
--- /dev/null
@@ -0,0 +1,899 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 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 "usbhid/usbhid.h"
+#include <linux/usb.h>
+
+#include <linux/fb.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#include "hid-picolcd.h"
+
+
+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 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] = min_t(size_t, 20, s);
+       if (*off + raw_data[2] > 0xff)
+               raw_data[2] = 0x100 - *off;
+
+       if (copy_from_user(raw_data+3, u, min((u8)20, 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     = simple_open,
+       .read     = picolcd_debug_eeprom_read,
+       .write    = picolcd_debug_eeprom_write,
+       .llseek   = generic_file_llseek,
+};
+
+/*
+ * The "flash" file
+ */
+/* 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 preceded 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 happen without special restriction
+ */
+static const struct file_operations picolcd_debug_flash_fops = {
+       .owner    = THIS_MODULE,
+       .open     = simple_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 * const 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 + 4 < dst_sz; i++) {
+               dst[j++] = hex_asc[(data[i] >> 4) & 0x0f];
+               dst[j++] = hex_asc[data[i] & 0x0f];
+               dst[j++] = ' ';
+       }
+       dst[j]   = '\0';
+       if (j > 0)
+               dst[j-1] = '\n';
+       if (i < data_len && j > 2)
+               dst[j-2] = dst[j-3] = '.';
+}
+
+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 (list_empty(&hdev->debug_list))
+               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)) {
+               kfree(buff);
+               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);
+}
+
+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 (list_empty(&hdev->debug_list))
+               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]);
+                       hid_debug_event(hdev, buff);
+                       dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[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);
+}
+
+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)
+               hid_warn(hdev, "Unexpected FLASH access reports, please submit rdesc for review\n");
+}
+
+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);
+}
+
diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c
new file mode 100644 (file)
index 0000000..0008a51
--- /dev/null
@@ -0,0 +1,615 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 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/vmalloc.h>
+#include "usbhid/usbhid.h"
+#include <linux/usb.h>
+
+#include <linux/fb.h>
+#include <linux/module.h>
+
+#include "hid-picolcd.h"
+
+/* 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,
+       .red            = {
+               .offset = 0,
+               .length = 1,
+               .msb_right = 0,
+       },
+       .green          = {
+               .offset = 0,
+               .length = 1,
+               .msb_right = 0,
+       },
+       .blue           = {
+               .offset = 0,
+               .length = 1,
+               .msb_right = 0,
+       },
+       .transp         = {
+               .offset = 0,
+               .length = 0,
+               .msb_right = 0,
+       },
+};
+
+/* Send a given tile to PicoLCD */
+static int picolcd_fb_send_tile(struct picolcd_data *data, u8 *vbitmap,
+               int chip, int tile)
+{
+       struct hid_report *report1, *report2;
+       unsigned long flags;
+       u8 *tdata;
+       int i;
+
+       report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, data->hdev);
+       if (!report1 || report1->maxfield != 1)
+               return -ENODEV;
+       report2 = picolcd_out_report(REPORT_LCD_DATA, data->hdev);
+       if (!report2 || report2->maxfield != 1)
+               return -ENODEV;
+
+       spin_lock_irqsave(&data->lock, flags);
+       if ((data->status & PICOLCD_FAILED)) {
+               spin_unlock_irqrestore(&data->lock, flags);
+               return -ENODEV;
+       }
+       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 = 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] >> (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;
+}
+
+void picolcd_fb_refresh(struct picolcd_data *data)
+{
+       if (data->fb_info)
+               schedule_delayed_work(&data->fb_info->deferred_work, 0);
+}
+
+/* Reconfigure LCD display */
+int picolcd_fb_reset(struct picolcd_data *data, int clear)
+{
+       struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev);
+       struct picolcd_fb_data *fbdata = data->fb_info->par;
+       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);
+       }
+       spin_unlock_irqrestore(&data->lock, flags);
+
+       if (clear) {
+               memset(fbdata->vbitmap, 0, PICOLCDFB_SIZE);
+               memset(fbdata->bitmap, 0, PICOLCDFB_SIZE*fbdata->bpp);
+       }
+       fbdata->force = 1;
+
+       /* schedule first output of framebuffer */
+       if (fbdata->ready)
+               schedule_delayed_work(&data->fb_info->deferred_work, 0);
+       else
+               fbdata->ready = 1;
+
+       return 0;
+}
+
+/* Update fb_vbitmap from the screen_base and send changed tiles to device */
+static void picolcd_fb_update(struct fb_info *info)
+{
+       int chip, tile, n;
+       unsigned long flags;
+       struct picolcd_fb_data *fbdata = info->par;
+       struct picolcd_data *data;
+
+       mutex_lock(&info->lock);
+
+       spin_lock_irqsave(&fbdata->lock, flags);
+       if (!fbdata->ready && fbdata->picolcd)
+               picolcd_fb_reset(fbdata->picolcd, 0);
+       spin_unlock_irqrestore(&fbdata->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 (!fbdata->force && !picolcd_fb_update_tile(
+                                       fbdata->vbitmap, fbdata->bitmap,
+                                       fbdata->bpp, chip, tile))
+                               continue;
+                       n += 2;
+                       if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
+                               spin_lock_irqsave(&fbdata->lock, flags);
+                               data = fbdata->picolcd;
+                               spin_unlock_irqrestore(&fbdata->lock, flags);
+                               mutex_unlock(&info->lock);
+                               if (!data)
+                                       return;
+                               usbhid_wait_io(data->hdev);
+                               mutex_lock(&info->lock);
+                               n = 0;
+                       }
+                       spin_lock_irqsave(&fbdata->lock, flags);
+                       data = fbdata->picolcd;
+                       spin_unlock_irqrestore(&fbdata->lock, flags);
+                       if (!data || picolcd_fb_send_tile(data,
+                                       fbdata->vbitmap, chip, tile))
+                               goto out;
+               }
+       fbdata->force = false;
+       if (n) {
+               spin_lock_irqsave(&fbdata->lock, flags);
+               data = fbdata->picolcd;
+               spin_unlock_irqrestore(&fbdata->lock, flags);
+               mutex_unlock(&info->lock);
+               if (data)
+                       usbhid_wait_io(data->hdev);
+               return;
+       }
+out:
+       mutex_unlock(&info->lock);
+}
+
+/* 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)
+{
+       /* 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_fb_data *fbdata = info->par;
+
+       /* make sure no work is deferred */
+       fb_deferred_io_cleanup(info);
+
+       /* No thridparty should ever unregister our framebuffer! */
+       WARN_ON(fbdata->picolcd != NULL);
+
+       vfree((u8 *)info->fix.smem_start);
+       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;
+               var->red.length     = 8;
+               var->green.length   = 8;
+               var->blue.length    = 8;
+       } else {
+               var->bits_per_pixel = 1;
+               var->red.length     = 1;
+               var->green.length   = 1;
+               var->blue.length    = 1;
+       }
+       return 0;
+}
+
+static int picolcd_set_par(struct fb_info *info)
+{
+       struct picolcd_fb_data *fbdata = info->par;
+       u8 *tmp_fb, *o_fb;
+       if (info->var.bits_per_pixel == fbdata->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   = fbdata->bitmap;
+       tmp_fb = kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL);
+       if (!tmp_fb)
+               return -ENOMEM;
+
+       /* 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;
+                       }
+                       tmp_fb[i] = p;
+               }
+               memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
+               info->fix.visual = FB_VISUAL_MONO01;
+               info->fix.line_length = PICOLCDFB_WIDTH / 8;
+       } else {
+               int i;
+               memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
+               for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
+                       o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
+               info->fix.visual = FB_VISUAL_DIRECTCOLOR;
+               info->fix.line_length = PICOLCDFB_WIDTH;
+       }
+
+       kfree(tmp_fb);
+       fbdata->bpp = info->var.bits_per_pixel;
+       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);
+}
+
+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);
+       struct picolcd_fb_data *fbdata = data->fb_info->par;
+       unsigned i, fb_update_rate = fbdata->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);
+       struct picolcd_fb_data *fbdata = data->fb_info->par;
+       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;
+
+       fbdata->update_rate = u;
+       data->fb_info->fbdefio->delay = HZ / fbdata->update_rate;
+       return count;
+}
+
+static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show,
+               picolcd_fb_update_rate_store);
+
+/* initialize Framebuffer device */
+int picolcd_init_framebuffer(struct picolcd_data *data)
+{
+       struct device *dev = &data->hdev->dev;
+       struct fb_info *info = NULL;
+       struct picolcd_fb_data *fbdata = NULL;
+       int i, error = -ENOMEM;
+       u32 *palette;
+
+       /* The extra memory is:
+        * - 256*u32 for pseudo_palette
+        * - struct fb_deferred_io
+        */
+       info = framebuffer_alloc(256 * sizeof(u32) +
+                       sizeof(struct fb_deferred_io) +
+                       sizeof(struct picolcd_fb_data) +
+                       PICOLCDFB_SIZE, dev);
+       if (info == NULL) {
+               dev_err(dev, "failed to allocate a framebuffer\n");
+               goto err_nomem;
+       }
+
+       info->fbdefio = info->par;
+       *info->fbdefio = picolcd_fb_defio;
+       info->par += sizeof(struct fb_deferred_io);
+       palette = info->par;
+       info->par += 256 * sizeof(u32);
+       for (i = 0; i < 256; i++)
+               palette[i] = i > 0 && i < 16 ? 0xff : 0;
+       info->pseudo_palette = palette;
+       info->fbops = &picolcdfb_ops;
+       info->var = picolcdfb_var;
+       info->fix = picolcdfb_fix;
+       info->fix.smem_len   = PICOLCDFB_SIZE*8;
+       info->flags = FBINFO_FLAG_DEFAULT;
+
+       fbdata = info->par;
+       spin_lock_init(&fbdata->lock);
+       fbdata->picolcd = data;
+       fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
+       fbdata->bpp     = picolcdfb_var.bits_per_pixel;
+       fbdata->force   = 1;
+       fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data);
+       fbdata->bitmap  = vmalloc(PICOLCDFB_SIZE*8);
+       if (fbdata->bitmap == NULL) {
+               dev_err(dev, "can't get a free page for framebuffer\n");
+               goto err_nomem;
+       }
+       info->screen_base = (char __force __iomem *)fbdata->bitmap;
+       info->fix.smem_start = (unsigned long)fbdata->bitmap;
+       memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
+       data->fb_info = info;
+
+       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;
+       }
+
+       fb_deferred_io_init(info);
+       error = register_framebuffer(info);
+       if (error) {
+               dev_err(dev, "failed to register framebuffer\n");
+               goto err_sysfs;
+       }
+       return 0;
+
+err_sysfs:
+       device_remove_file(dev, &dev_attr_fb_update_rate);
+       fb_deferred_io_cleanup(info);
+err_cleanup:
+       data->fb_info    = NULL;
+
+err_nomem:
+