diff options
Diffstat (limited to 'drivers/usb/input')
-rw-r--r-- | drivers/usb/input/Kconfig | 34 | ||||
-rw-r--r-- | drivers/usb/input/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/input/appletouch.c | 469 | ||||
-rw-r--r-- | drivers/usb/input/hid-core.c | 81 | ||||
-rw-r--r-- | drivers/usb/input/hid-debug.h | 34 | ||||
-rw-r--r-- | drivers/usb/input/hid-input.c | 66 | ||||
-rw-r--r-- | drivers/usb/input/hid.h | 9 | ||||
-rw-r--r-- | drivers/usb/input/hiddev.c | 1 | ||||
-rw-r--r-- | drivers/usb/input/keyspan_remote.c | 5 | ||||
-rw-r--r-- | drivers/usb/input/map_to_7segment.h | 189 | ||||
-rw-r--r-- | drivers/usb/input/yealink.c | 1013 | ||||
-rw-r--r-- | drivers/usb/input/yealink.h | 220 |
12 files changed, 2084 insertions, 39 deletions
diff --git a/drivers/usb/input/Kconfig b/drivers/usb/input/Kconfig index 298e4a2..1e53934 100644 --- a/drivers/usb/input/Kconfig +++ b/drivers/usb/input/Kconfig @@ -230,6 +230,20 @@ config USB_EGALAX To compile this driver as a module, choose M here: the module will be called touchkitusb. +config USB_YEALINK + tristate "Yealink usb-p1k voip phone" + depends on USB && INPUT && EXPERIMENTAL + ---help--- + Say Y here if you want to enable keyboard and LCD functions of the + Yealink usb-p1k usb phones. The audio part is enabled by the generic + usb sound driver, so you might want to enable that as well. + + For information about how to use these additional functions, see + <file:Documentation/input/yealink.txt>. + + To compile this driver as a module, choose M here: the module will be + called yealink. + config USB_XPAD tristate "X-Box gamepad support" depends on USB && INPUT @@ -272,3 +286,23 @@ config USB_KEYSPAN_REMOTE To compile this driver as a module, choose M here: the module will be called keyspan_remote. + +config USB_APPLETOUCH + tristate "Apple USB Touchpad support" + depends on USB && INPUT + ---help--- + Say Y here if you want to use an Apple USB Touchpad. + + These are the touchpads that can be found on post-February 2005 + Apple Powerbooks (prior models have a Synaptics touchpad connected + to the ADB bus). + + This driver provides a basic mouse driver but can be interfaced + with the synaptics X11 driver to provide acceleration and + scrolling in X11. + + For further information, see + <file:Documentation/input/appletouch.txt>. + + To compile this driver as a module, choose M here: the + module will be called appletouch. diff --git a/drivers/usb/input/Makefile b/drivers/usb/input/Makefile index f1547be..5e03b93 100644 --- a/drivers/usb/input/Makefile +++ b/drivers/usb/input/Makefile @@ -39,4 +39,6 @@ obj-$(CONFIG_USB_EGALAX) += touchkitusb.o obj-$(CONFIG_USB_POWERMATE) += powermate.o obj-$(CONFIG_USB_WACOM) += wacom.o obj-$(CONFIG_USB_ACECAD) += acecad.o +obj-$(CONFIG_USB_YEALINK) += yealink.o obj-$(CONFIG_USB_XPAD) += xpad.o +obj-$(CONFIG_USB_APPLETOUCH) += appletouch.o diff --git a/drivers/usb/input/appletouch.c b/drivers/usb/input/appletouch.c new file mode 100644 index 0000000..e03c1c5 --- /dev/null +++ b/drivers/usb/input/appletouch.c @@ -0,0 +1,469 @@ +/* + * Apple USB Touchpad (for post-February 2005 PowerBooks) driver + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net) + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) + * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) + * + * Thanks to Alex Harper <basilisk@foobox.net> for his inputs. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/input.h> +#include <linux/usb_input.h> + +/* Apple has powerbooks which have the keyboard with different Product IDs */ +#define APPLE_VENDOR_ID 0x05AC + +#define ATP_DEVICE(prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_PROTOCOL, \ + .idVendor = APPLE_VENDOR_ID, \ + .idProduct = (prod), \ + .bInterfaceClass = 0x03, \ + .bInterfaceProtocol = 0x02 + +/* table of devices that work with this driver */ +static struct usb_device_id atp_table [] = { + { ATP_DEVICE(0x020E) }, + { ATP_DEVICE(0x020F) }, + { ATP_DEVICE(0x030A) }, + { ATP_DEVICE(0x030B) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE (usb, atp_table); + +/* size of a USB urb transfer */ +#define ATP_DATASIZE 81 + +/* + * number of sensors. Note that only 16 instead of 26 X (horizontal) + * sensors exist on 12" and 15" PowerBooks. All models have 16 Y + * (vertical) sensors. + */ +#define ATP_XSENSORS 26 +#define ATP_YSENSORS 16 + +/* amount of fuzz this touchpad generates */ +#define ATP_FUZZ 16 + +/* maximum pressure this driver will report */ +#define ATP_PRESSURE 300 +/* + * multiplication factor for the X and Y coordinates. + * We try to keep the touchpad aspect ratio while still doing only simple + * arithmetics. + * The factors below give coordinates like: + * 0 <= x < 960 on 12" and 15" Powerbooks + * 0 <= x < 1600 on 17" Powerbooks + * 0 <= y < 646 + */ +#define ATP_XFACT 64 +#define ATP_YFACT 43 + +/* + * Threshold for the touchpad sensors. Any change less than ATP_THRESHOLD is + * ignored. + */ +#define ATP_THRESHOLD 5 + +/* Structure to hold all of our device specific stuff */ +struct atp { + struct usb_device * udev; /* usb device */ + struct urb * urb; /* usb request block */ + signed char * data; /* transferred data */ + int open; /* non-zero if opened */ + struct input_dev input; /* input dev */ + int valid; /* are the sensors valid ? */ + int x_old; /* last reported x/y, */ + int y_old; /* used for smoothing */ + /* current value of the sensors */ + signed char xy_cur[ATP_XSENSORS + ATP_YSENSORS]; + /* last value of the sensors */ + signed char xy_old[ATP_XSENSORS + ATP_YSENSORS]; + /* accumulated sensors */ + int xy_acc[ATP_XSENSORS + ATP_YSENSORS]; +}; + +#define dbg_dump(msg, tab) \ + if (debug > 1) { \ + int i; \ + printk("appletouch: %s %lld", msg, (long long)jiffies); \ + for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) \ + printk(" %02x", tab[i]); \ + printk("\n"); \ + } + +#define dprintk(format, a...) \ + do { \ + if (debug) printk(format, ##a); \ + } while (0) + +MODULE_AUTHOR("Johannes Berg, Stelian Pop, Frank Arnold"); +MODULE_DESCRIPTION("Apple PowerBooks USB touchpad driver"); +MODULE_LICENSE("GPL"); + +static int debug = 1; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Activate debugging output"); + +static int atp_calculate_abs(int *xy_sensors, int nb_sensors, int fact, + int *z, int *fingers) +{ + int i; + /* values to calculate mean */ + int pcum = 0, psum = 0; + + *fingers = 0; + + for (i = 0; i < nb_sensors; i++) { + if (xy_sensors[i] < ATP_THRESHOLD) + continue; + if ((i - 1 < 0) || (xy_sensors[i - 1] < ATP_THRESHOLD)) + (*fingers)++; + pcum += xy_sensors[i] * i; + psum += xy_sensors[i]; + } + + if (psum > 0) { + *z = psum; + return pcum * fact / psum; + } + + return 0; +} + +static inline void atp_report_fingers(struct input_dev *input, int fingers) +{ + input_report_key(input, BTN_TOOL_FINGER, fingers == 1); + input_report_key(input, BTN_TOOL_DOUBLETAP, fingers == 2); + input_report_key(input, BTN_TOOL_TRIPLETAP, fingers > 2); +} + +static void atp_complete(struct urb* urb, struct pt_regs* regs) +{ + int x, y, x_z, y_z, x_f, y_f; + int retval, i; + struct atp *dev = urb->context; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", + __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", + __FUNCTION__, urb->status); + goto exit; + } + + /* drop incomplete datasets */ + if (dev->urb->actual_length != ATP_DATASIZE) { + dprintk("appletouch: incomplete data package.\n"); + goto exit; + } + + /* reorder the sensors values */ + for (i = 0; i < 8; i++) { + /* X values */ + dev->xy_cur[i ] = dev->data[5 * i + 2]; + dev->xy_cur[i + 8] = dev->data[5 * i + 4]; + dev->xy_cur[i + 16] = dev->data[5 * i + 42]; + if (i < 2) + dev->xy_cur[i + 24] = dev->data[5 * i + 44]; + + /* Y values */ + dev->xy_cur[i + 26] = dev->data[5 * i + 1]; + dev->xy_cur[i + 34] = dev->data[5 * i + 3]; + } + + dbg_dump("sample", dev->xy_cur); + + if (!dev->valid) { + /* first sample */ + dev->valid = 1; + dev->x_old = dev->y_old = -1; + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + + /* 17" Powerbooks have 10 extra X sensors */ + for (i = 16; i < ATP_XSENSORS; i++) + if (dev->xy_cur[i]) { + printk("appletouch: 17\" model detected.\n"); + input_set_abs_params(&dev->input, ABS_X, 0, + (ATP_XSENSORS - 1) * + ATP_XFACT - 1, + ATP_FUZZ, 0); + break; + } + + goto exit; + } + + for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) { + /* accumulate the change */ + signed char change = dev->xy_old[i] - dev->xy_cur[i]; + dev->xy_acc[i] -= change; + + /* prevent down drifting */ + if (dev->xy_acc[i] < 0) + dev->xy_acc[i] = 0; + } + + memcpy(dev->xy_old, dev->xy_cur, sizeof(dev->xy_old)); + + dbg_dump("accumulator", dev->xy_acc); + + x = atp_calculate_abs(dev->xy_acc, ATP_XSENSORS, + ATP_XFACT, &x_z, &x_f); + y = atp_calculate_abs(dev->xy_acc + ATP_XSENSORS, ATP_YSENSORS, + ATP_YFACT, &y_z, &y_f); + + if (x && y) { + if (dev->x_old != -1) { + x = (dev->x_old * 3 + x) >> 2; + y = (dev->y_old * 3 + y) >> 2; + dev->x_old = x; + dev->y_old = y; + + if (debug > 1) + printk("appletouch: X: %3d Y: %3d " + "Xz: %3d Yz: %3d\n", + x, y, x_z, y_z); + + input_report_key(&dev->input, BTN_TOUCH, 1); + input_report_abs(&dev->input, ABS_X, x); + input_report_abs(&dev->input, ABS_Y, y); + input_report_abs(&dev->input, ABS_PRESSURE, + min(ATP_PRESSURE, x_z + y_z)); + atp_report_fingers(&dev->input, max(x_f, y_f)); + } + dev->x_old = x; + dev->y_old = y; + } + else if (!x && !y) { + + dev->x_old = dev->y_old = -1; + input_report_key(&dev->input, BTN_TOUCH, 0); + input_report_abs(&dev->input, ABS_PRESSURE, 0); + atp_report_fingers(&dev->input, 0); + + /* reset the accumulator on release */ + memset(dev->xy_acc, 0, sizeof(dev->xy_acc)); + } + + input_report_key(&dev->input, BTN_LEFT, !!dev->data[80]); + + input_sync(&dev->input); + +exit: + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) { + err("%s - usb_submit_urb failed with result %d", + __FUNCTION__, retval); + } +} + +static int atp_open(struct input_dev *input) +{ + struct atp *dev = input->private; + + if (usb_submit_urb(dev->urb, GFP_ATOMIC)) + return -EIO; + + dev->open = 1; + return 0; +} + +static void atp_close(struct input_dev *input) +{ + struct atp *dev = input->private; + + usb_kill_urb(dev->urb); + dev->open = 0; +} + +static int atp_probe(struct usb_interface *iface, const struct usb_device_id *id) +{ + struct atp *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int int_in_endpointAddr = 0; + int i, retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kmalloc(sizeof(struct atp), GFP_KERNEL); + if (dev == NULL) { + err("Out of memory"); + goto err_kmalloc; + } + memset(dev, 0, sizeof(struct atp)); + + dev->udev = interface_to_usbdev(iface); + + /* set up the endpoint information */ + /* use only the first interrupt-in endpoint */ + iface_desc = iface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + if (!int_in_endpointAddr && + (endpoint->bEndpointAddress & USB_DIR_IN) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_INT)) { + /* we found an interrupt in endpoint */ + int_in_endpointAddr = endpoint->bEndpointAddress; + break; + } + } + if (!int_in_endpointAddr) { + retval = -EIO; + err("Could not find int-in endpoint"); + goto err_endpoint; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(iface, dev); + + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb) { + retval = -ENOMEM; + goto err_usballoc; + } + dev->data = usb_buffer_alloc(dev->udev, ATP_DATASIZE, GFP_KERNEL, + &dev->urb->transfer_dma); + if (!dev->data) { + retval = -ENOMEM; + goto err_usbbufalloc; + } + usb_fill_int_urb(dev->urb, dev->udev, + usb_rcvintpipe(dev->udev, int_in_endpointAddr), + dev->data, ATP_DATASIZE, atp_complete, dev, 1); + + init_input_dev(&dev->input); + dev->input.name = "appletouch"; + dev->input.dev = &iface->dev; + dev->input.private = dev; + dev->input.open = atp_open; + dev->input.close = atp_close; + + usb_to_input_id(dev->udev, &dev->input.id); + + set_bit(EV_ABS, dev->input.evbit); + + /* + * 12" and 15" Powerbooks only have 16 x sensors, + * 17" models are detected later. + */ + input_set_abs_params(&dev->input, ABS_X, 0, + (16 - 1) * ATP_XFACT - 1, ATP_FUZZ, 0); + input_set_abs_params(&dev->input, ABS_Y, 0, + (ATP_YSENSORS - 1) * ATP_YFACT - 1, ATP_FUZZ, 0); + input_set_abs_params(&dev->input, ABS_PRESSURE, 0, ATP_PRESSURE, 0, 0); + + set_bit(EV_KEY, dev->input.evbit); + set_bit(BTN_TOUCH, dev->input.keybit); + set_bit(BTN_TOOL_FINGER, dev->input.keybit); + set_bit(BTN_TOOL_DOUBLETAP, dev->input.keybit); + set_bit(BTN_TOOL_TRIPLETAP, dev->input.keybit); + set_bit(BTN_LEFT, dev->input.keybit); + + input_register_device(&dev->input); + + printk(KERN_INFO "input: appletouch connected\n"); + + return 0; + +err_usbbufalloc: + usb_free_urb(dev->urb); +err_usballoc: + usb_set_intfdata(iface, NULL); +err_endpoint: + kfree(dev); +err_kmalloc: + return retval; +} + +static void atp_disconnect(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + + usb_set_intfdata(iface, NULL); + if (dev) { + usb_kill_urb(dev->urb); + input_unregister_device(&dev->input); + usb_free_urb(dev->urb); + usb_buffer_free(dev->udev, ATP_DATASIZE, + dev->data, dev->urb->transfer_dma); + kfree(dev); + } + printk(KERN_INFO "input: appletouch disconnected\n"); +} + +static int atp_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct atp *dev = usb_get_intfdata(iface); + usb_kill_urb(dev->urb); + dev->valid = 0; + return 0; +} + +static int atp_resume(struct usb_interface *iface) +{ + struct atp *dev = usb_get_intfdata(iface); + if (dev->open && usb_submit_urb(dev->urb, GFP_ATOMIC)) + return -EIO; + + return 0; +} + +static struct usb_driver atp_driver = { + .owner = THIS_MODULE, + .name = "appletouch", + .probe = atp_probe, + .disconnect = atp_disconnect, + .suspend = atp_suspend, + .resume = atp_resume, + .id_table = atp_table, +}; + +static int __init atp_init(void) +{ + return usb_register(&atp_driver); +} + +static void __exit atp_exit(void) +{ + usb_deregister(&atp_driver); +} + +module_init(atp_init); +module_exit(atp_exit); diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c index b2cb2b3..a99865c 100644 --- a/drivers/usb/input/hid-core.c +++ b/drivers/usb/input/hid-core.c @@ -2,7 +2,8 @@ * USB HID support for Linux * * Copyright (c) 1999 Andreas Gal - * Copyright (c) 2000-2001 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> + * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc */ /* @@ -38,7 +39,7 @@ * Version Information */ -#define DRIVER_VERSION "v2.01" +#define DRIVER_VERSION "v2.6" #define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik" #define DRIVER_DESC "USB HID core driver" #define DRIVER_LICENSE "GPL" @@ -1058,8 +1059,8 @@ static int hid_submit_ctrl(struct hid_device *hid) if (maxpacket > 0) { padlen = (len + maxpacket - 1) / maxpacket; padlen *= maxpacket; - if (padlen > HID_BUFFER_SIZE) - padlen = HID_BUFFER_SIZE; + if (padlen > hid->bufsize) + padlen = hid->bufsize; } else padlen = 0; hid->urbctrl->transfer_buffer_length = padlen; @@ -1096,6 +1097,7 @@ static void hid_irq_out(struct urb *urb, struct pt_regs *regs) switch (urb->status) { case 0: /* success */ + break; case -ESHUTDOWN: /* unplug */ case -EILSEQ: /* unplug timeout on uhci */ unplug = 1; @@ -1143,6 +1145,7 @@ static void hid_ctrl(struct urb *urb, struct pt_regs *regs) case 0: /* success */ if (hid->ctrl[hid->ctrltail].dir == USB_DIR_IN) hid_input_report(hid->ctrl[hid->ctrltail].report->type, urb, 0, regs); + break; case -ESHUTDOWN: /* unplug */ case -EILSEQ: /* unplug timectrl on uhci */ unplug = 1; @@ -1284,13 +1287,8 @@ void hid_init_reports(struct hid_device *hid) struct hid_report *report; int err, ret; - list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list) { - int size = ((report->size - 1) >> 3) + 1 + hid->report_enum[HID_INPUT_REPORT].numbered; - if (size > HID_BUFFER_SIZE) size = HID_BUFFER_SIZE; - if (size > hid->urbin->transfer_buffer_length) - hid->urbin->transfer_buffer_length = size; + list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list) hid_submit_report(hid, report, USB_DIR_IN); - } list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list) hid_submit_report(hid, report, USB_DIR_IN); @@ -1372,12 +1370,14 @@ void hid_init_reports(struct hid_device *hid) #define USB_VENDOR_ID_A4TECH 0x09da #define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 -#define USB_VENDOR_ID_AASHIMA 0x06D6 +#define USB_VENDOR_ID_AASHIMA 0x06d6 #define USB_DEVICE_ID_AASHIMA_GAMEPAD 0x0025 +#define USB_DEVICE_ID_AASHIMA_PREDATOR 0x0026 #define USB_VENDOR_ID_CYPRESS 0x04b4 #define USB_DEVICE_ID_CYPRESS_MOUSE 0x0001 #define USB_DEVICE_ID_CYPRESS_HIDCOM 0x5500 +#define USB_DEVICE_ID_CYPRESS_ULTRAMOUSE 0x7417 #define USB_VENDOR_ID_BERKSHIRE 0x0c98 #define USB_DEVICE_ID_BERKSHIRE_PCWD 0x1140 @@ -1432,7 +1432,7 @@ void hid_init_reports(struct hid_device *hid) #define USB_DEVICE_ID_VERNIER_CYCLOPS 0x0004 #define USB_VENDOR_ID_LD 0x0f11 -#define USB_DEVICE_ID_CASSY 0x1000 +#define USB_DEVICE_ID_CASSY 0x1000 #define USB_DEVICE_ID_POCKETCASSY 0x1010 #define USB_DEVICE_ID_MOBILECASSY 0x1020 #define USB_DEVICE_ID_JWM 0x1080 @@ -1444,6 +1444,8 @@ void hid_init_reports(struct hid_device *hid) #define USB_DEVICE_ID_NETWORKANALYSER 0x2020 #define USB_DEVICE_ID_POWERCONTROL 0x2030 +#define USB_VENDOR_ID_APPLE 0x05ac +#define USB_DEVICE_ID_APPLE_POWERMOUSE 0x0304 /* * Alphabetically sorted blacklist by quirk type. @@ -1468,6 +1470,7 @@ static struct hid_blacklist { { USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW48, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_HIDCOM, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_ULTRAMOUSE, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5, HID_QUIRK_IGNORE }, @@ -1548,10 +1551,12 @@ static struct hid_blacklist { { USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_USBHUB_KB, HID_QUIRK_NOGET}, { USB_VENDOR_ID_TANGTOP, USB_DEVICE_ID_TANGTOP_USBPS2, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_POWERMOUSE, HID_QUIRK_2WHEEL_POWERMOUSE }, { USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU, HID_QUIRK_2WHEEL_MOUSE_HACK_7 }, { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE, HID_QUIRK_2WHEEL_MOUSE_HACK_5 }, { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, @@ -1564,15 +1569,32 @@ static struct hid_blacklist { { 0, 0 } }; +/* + * Traverse the supplied list of reports and find the longest + */ +static void hid_find_max_report(struct hid_device *hid, unsigned int type, int *max) +{ + struct hid_report *report; + int size; + + list_for_each_entry(report, &hid->report_enum[type].report_list, list) { + size = ((report->size - 1) >> 3) + 1; + if (type == HID_INPUT_REPORT && hid->report_enum[type].numbered) + size++; + if (*max < size) + *max = size; + } +} + static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) { - if (!(hid->inbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->inbuf_dma))) + if (!(hid->inbuf = usb_buffer_alloc(dev, hid->bufsize, SLAB_ATOMIC, &hid->inbuf_dma))) return -1; - if (!(hid->outbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->outbuf_dma))) + if (!(hid->outbuf = usb_buffer_alloc(dev, hid->bufsize, SLAB_ATOMIC, &hid->outbuf_dma))) return -1; if (!(hid->cr = usb_buffer_alloc(dev, sizeof(*(hid->cr)), SLAB_ATOMIC, &hid->cr_dma))) return -1; - if (!(hid->ctrlbuf = usb_buffer_alloc(dev, HID_BUFFER_SIZE, SLAB_ATOMIC, &hid->ctrlbuf_dma))) + if (!(hid->ctrlbuf = usb_buffer_alloc(dev, hid->bufsize, SLAB_ATOMIC, &hid->ctrlbuf_dma))) return -1; return 0; @@ -1581,13 +1603,13 @@ static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid) { if (hid->inbuf) - usb_buffer_free(dev, HID_BUFFER_SIZE, hid->inbuf, hid->inbuf_dma); + usb_buffer_free(dev, hid->bufsize, hid->inbuf, hid->inbuf_dma); if (hid->outbuf) - usb_buffer_free(dev, HID_BUFFER_SIZE, hid->outbuf, hid->outbuf_dma); + usb_buffer_free(dev, hid->bufsize, hid->outbuf, hid->outbuf_dma); if (hid->cr) usb_buffer_free(dev, sizeof(*(hid->cr)), hid->cr, hid->cr_dma); if (hid->ctrlbuf) - usb_buffer_free(dev, HID_BUFFER_SIZE, hid->ctrlbuf, hid->ctrlbuf_dma); + usb_buffer_free(dev, hid->bufsize, hid->ctrlbuf, hid->ctrlbuf_dma); } static struct hid_device *usb_hid_configure(struct usb_interface *intf) @@ -1598,7 +1620,7 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf) struct hid_device *hid; unsigned quirks = 0, rsize = 0; char *buf, *rdesc; - int n; + int n, insize = 0; for (n = 0; hid_blacklist[n].idVendor; n++) if ((hid_blacklist[n].idVendor == le16_to_cpu(dev->descriptor.idVendor)) && @@ -1652,6 +1674,19 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf) kfree(rdesc); hid->quirks = quirks; + hid->bufsize = HID_MIN_BUFFER_SIZE; + hid_find_max_report(hid, HID_INPUT_REPORT, &hid->bufsize); + hid_find_max_report(hid, HID_OUTPUT_REPORT, &hid->bufsize); + hid_find_max_report(hid, HID_FEATURE_REPORT, &hid->bufsize); + + if (hid->bufsize > HID_MAX_BUFFER_SIZE) + hid->bufsize = HID_MAX_BUFFER_SIZE; + + hid_find_max_report(hid, HID_INPUT_REPORT, &insize); + + if (insize > HID_MAX_BUFFER_SIZE) + insize = HID_MAX_BUFFER_SIZE; + if (hid_alloc_buffers(dev, hid)) { hid_free_buffers(dev, hid); goto fail; @@ -1682,10 +1717,10 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf) if (!(hid->urbin = usb_alloc_urb(0, GFP_KERNEL))) goto fail; pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); - usb_fill_int_urb(hid->urbin, dev, pipe, hid->inbuf, 0, + usb_fill_int_urb(hid->urbin, dev, pipe, hid->inbuf, insize, hid_irq_in, hid, interval); hid->urbin->transfer_dma = hid->inbuf_dma; - hid->urbin->transfer_flags |=(URB_NO_TRANSFER_DMA_MAP | URB_ASYNC_UNLINK); + hid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; } else { if (hid->urbout) continue; @@ -1695,7 +1730,7 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf) usb_fill_int_urb(hid->urbout, dev, pipe, hid->outbuf, 0, hid_irq_out, hid, interval); hid->urbout->transfer_dma = hid->outbuf_dma; - hid->urbout->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_ASYNC_UNLINK); + hid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; } } @@ -1747,7 +1782,7 @@ static struct hid_device *usb_hid_configure(struct usb_interface *intf) hid->ctrlbuf, 1, hid_ctrl, hid); hid->urbctrl->setup_dma = hid->cr_dma; hid->urbctrl->transfer_dma = hid->ctrlbuf_dma; - hid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP | URB_ASYNC_UNLINK); + hid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP); return hid; diff --git a/drivers/usb/input/hid-debug.h b/drivers/usb/input/hid-debug.h index 52437e5..ceebab9 100644 --- a/drivers/usb/input/hid-debug.h +++ b/drivers/usb/input/hid-debug.h @@ -85,6 +85,23 @@ static const struct hid_usage_entry hid_usage_table[] = { {0, 0x91, "D-PadDown"}, {0, 0x92, "D-PadRight"}, {0, 0x93, "D-PadLeft"}, + { 2, 0, "Simulation" }, + {0, 0xb0, "Aileron"}, + {0, 0xb1, "AileronTrim"}, + {0, 0xb2, "Anti-Torque"}, + {0, 0xb3, "Autopilot"}, + {0, 0xb4, "Chaff"}, + {0, 0xb5, "Collective"}, + {0, 0xb6, "DiveBrake"}, + {0, 0xb7, "ElectronicCountermeasures"}, + {0, 0xb8, "Elevator"}, + {0, 0xb9, "ElevatorTrim"}, + {0, 0xba, "Rudder"}, + {0, 0xbb, "Throttle"}, + {0, 0xbc, "FlightCommunications"}, + {0, 0xbd, "FlareRelease"}, + {0, 0xbe, "LandingGear"}, + {0, 0xbf, "ToeBrake"}, { 7, 0, "Keyboard" }, { 8, 0, "LED" }, {0, 0x01, "NumLock"}, @@ -92,6 +109,7 @@ static const struct hid_usage_entry hid_usage_table[] = { {0, 0x03, "ScrollLock"}, {0, 0x04, "Compose"}, {0, 0x05, "Kana"}, + {0, 0x4b, "GenericIndicator"}, { 9, 0, "Button" }, { 10, 0, "Ordinal" }, { 12, 0, "Consumer" }, @@ -574,7 +592,8 @@ static char *keys[KEY_MAX + 1] = { [KEY_EXIT] = "Exit", [KEY_MOVE] = "Move", [KEY_EDIT] = "Edit", [KEY_SCROLLUP] = "ScrollUp", [KEY_SCROLLDOWN] = "ScrollDown", [KEY_KPLEFTPAREN] = "KPLeftParenthesis", - [KEY_KPRIGHTPAREN] = "KPRightParenthesis", [KEY_F13] = "F13", + [KEY_KPRIGHTPAREN] = "KPRightParenthesis", [KEY_NEW] = "New", + [KEY_REDO] = "Redo", [KEY_F13] = "F13", [KEY_F14] = "F14", [KEY_F15] = "F15", [KEY_F16] = "F16", [KEY_F17] = "F17", [KEY_F18] = "F18", [KEY_F19] = "F19", @@ -584,15 +603,15 @@ static char *keys[KEY_MAX + 1] = { [KEY_PAUSECD] = "PauseCD", [KEY_PROG3] = "Prog3", [KEY_PROG4] = "Prog4", [KEY_SUSPEND] = "Suspend", [KEY_CLOSE] = "Close", [KEY_PLAY] = "Play", - [KEY_FASTFORWARD] = "Fast Forward", [KEY_BASSBOOST] = "Bass Boost", + [KEY_FASTFORWARD] = "FastForward", [KEY_BASSBOOST] = "BassBoost", [KEY_PRINT] = "Print", [KEY_HP] = "HP", [KEY_CAMERA] = "Camera", [KEY_SOUND] = "Sound", [KEY_QUESTION] = "Question", [KEY_EMAIL] = "Email", [KEY_CHAT] = "Chat", [KEY_SEARCH] = "Search", [KEY_CONNECT] = "Connect", [KEY_FINANCE] = "Finance", [KEY_SPORT] = "Sport", [KEY_SHOP] = "Shop", - [KEY_ALTERASE] = "Alternate Erase", [KEY_CANCEL] = "Cancel", - [KEY_BRIGHTNESSDOWN] = "Brightness down", [KEY_BRIGHTNESSUP] = "Brightness up", + [KEY_ALTERASE] = "AlternateErase", [KEY_CANCEL] = "Cancel", + [KEY_BRIGHTNESSDOWN] = "BrightnessDown", [KEY_BRIGHTNESSUP] = "BrightnessUp", [KEY_MEDIA] = "Media", [KEY_UNKNOWN] = "Unknown", [BTN_0] = "Btn0", [BTN_1] = "Btn1", [BTN_2] = "Btn2", [BTN_3] = "Btn3", @@ -622,8 +641,8 @@ static char *keys[KEY_MAX + 1] = { [BTN_TOOL_AIRBRUSH] = "ToolAirbrush", [BTN_TOOL_FINGER] = "ToolFinger", [BTN_TOOL_MOUSE] = "ToolMouse", [BTN_TOOL_LENS] = "ToolLens", [BTN_TOUCH] = "Touch", [BTN_STYLUS] = "Stylus", - [BTN_STYLUS2] = "Stylus2", [BTN_TOOL_DOUBLETAP] = "Tool Doubletap", - [BTN_TOOL_TRIPLETAP] = "Tool Tripletap", [BTN_GEAR_DOWN] = "WheelBtn", + [BTN_STYLUS2] = "Stylus2", [BTN_TOOL_DOUBLETAP] = "ToolDoubleTap", + [BTN_TOOL_TRIPLETAP] = "ToolTripleTap", [BTN_GEAR_DOWN] = "WheelBtn", [BTN_GEAR_UP] = "Gear up", [KEY_OK] = "Ok", [KEY_SELECT] = "Select", [KEY_GOTO] = "Goto", [KEY_CLEAR] = "Clear", [KEY_POWER2] = "Power2", @@ -659,6 +678,9 @@ static char *keys[KEY_MAX + 1] = { [KEY_TWEN] = "TWEN", [KEY_DEL_EOL] = "DeleteEOL", [KEY_DEL_EOS] = "DeleteEOS", [KEY_INS_LINE] = "InsertLine", [KEY_DEL_LINE] = "DeleteLine", + [KEY_SEND] = "Send", [KEY_REPLY] = "Reply", + [KEY_FORWARDMAIL] = "ForwardMail", [KEY_SAVE] = "Save", + [KEY_DOCUMENTS] = "Documents", }; static char *relatives[REL_MAX + 1] = { diff --git a/drivers/usb/input/hid-input.c b/drivers/usb/input/hid-input.c index 63a4db7..0b64522 100644 --- a/drivers/usb/input/hid-input.c +++ b/drivers/usb/input/hid-input.c @@ -78,8 +78,8 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel { struct input_dev *input = &hidinput->input; struct hid_device *device = hidinput->input.private; - int max, code; - unsigned long *bit; + int max = 0, code; + unsigned long *bit = NULL; field->hidinput = hidinput; @@ -131,6 +131,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel map_key(code); break; + + case HID_UP_SIMULATION: + + switch (usage->hid & 0xffff) { + case 0xba: map_abs(ABS_RUDDER); break; + case 0xbb: map_abs(ABS_THROTTLE); break; + } + break; + case HID_UP_GENDESK: if ((usage->hid & 0xf0) == 0x80) { /* SystemControl */ @@ -238,8 +247,12 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x000: goto ignore; case 0x034: map_key_clear(KEY_SLEEP); break; case 0x036: map_key_clear(BTN_MISC); break; + case 0x045: map_key_clear(KEY_RADIO); break; case 0x08a: map_key_clear(KEY_WWW); break; + case 0x08d: map_key_clear(KEY_PROGRAM); break; case 0x095: map_key_clear(KEY_HELP); break; + case 0x09c: map_key_clear(KEY_CHANNELUP); break; + case 0x09d: map_key_clear(KEY_CHANNELDOWN); break; case 0x0b0: map_key_clear(KEY_PLAY); break; case 0x0b1: map_key_clear(KEY_PAUSE); break; case 0x0b2: map_key_clear(KEY_RECORD); break; @@ -259,6 +272,11 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x18a: map_key_clear(KEY_MAIL); break; case 0x192: map_key_clear(KEY_CALC); break; case 0x194: map_key_clear(KEY_FILE); break; + case 0x1a7: map_key_clear(KEY_DOCUMENTS); break; + case 0x201: map_key_clear(KEY_NEW); break; + case 0x207: map_key_clear(KEY_SAVE); break; + case 0x208: map_key_clear(KEY_PRINT); break; + case 0x209: map_key_clear(KEY_PROPS); break; case 0x21a: map_key_clear(KEY_UNDO); break; case 0x21b: map_key_clear(KEY_COPY); break; case 0x21c: map_key_clear(KEY_CUT); break; @@ -271,7 +289,11 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x227: map_key_clear(KEY_REFRESH); break; case 0x22a: map_key_clear(KEY_BOOKMARKS); break; case 0x238: map_rel(REL_HWHEEL); break; - default: goto unknown; + case 0x279: map_key_clear(KEY_REDO); break; + case 0x289: map_key_clear(KEY_REPLY); break; + case 0x28b: map_key_clear(KEY_FORWARDMAIL); break; + case 0x28c: map_key_clear(KEY_SEND); break; + default: goto ignore; } break; @@ -296,9 +318,42 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel break; case HID_UP_MSVENDOR: - goto ignore; + case HID_UP_CUSTOM: /* Reported on Logitech and Powerbook USB keyboards */ + + set_bit(EV_REP, input->evbit); + switch(usage->hid & HID_USAGE) { + case 0x003: map_key_clear(KEY_FN); break; + default: goto ignore; + } + break; + + case HID_UP_LOGIVENDOR: /* Reported on Logitech Ultra X Media Remote */ + + set_bit(EV_REP, input->evbit); + switch(usage->hid & HID_USAGE) { + case 0x004: map_key_clear(KEY_AGAIN); break; + case 0x00d: map_key_clear(KEY_HOME); break; + case 0x024: map_key_clear(KEY_SHUFFLE); break; + case 0x025: map_key_clear(KEY_TV); break; + case 0x026: map_key_clear(KEY_MENU); break; + case 0x031: map_key_clear(KEY_AUDIO); break; + case 0x032: map_key_clear(KEY_SUBTITLE); break; + case 0x033: map_key_clear(KEY_LAST); break; + case 0x047: map_key_clear(KEY_MP3); break; + case 0x048: map_key_clear(KEY_DVD); break; + case 0x049: map_key_clear(KEY_MEDIA); break; + case 0x04a: map_key_clear(KEY_VIDEO); break; + case 0x04b: map_key_clear(KEY_ANGLE); break; + case 0x04c: map_key_clear(KEY_LANGUAGE); break; + case 0x04d: map_key_clear(KEY_SUBTITLE); break; + case 0x051: map_key_clear(KEY_RED); break; + case 0x052: map_key_clear(KEY_CLOSE); break; + default: goto ignore; + } + break; + case HID_UP_PID: set_bit(EV_FF, input->evbit); @@ -349,6 +404,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel if (usage->code > max) goto ignore; + if (((device->quirks & (HID_QUIRK_2WHEEL_POWERMOUSE)) && (usage->hid == 0x00010032))) + map_rel(REL_HWHEEL); + if ((device->quirks & (HID_QUIRK_2WHEEL_MOUSE_HACK_7 | HID_QUIRK_2WHEEL_MOUSE_HACK_5)) && (usage->type == EV_REL) && (usage->code == REL_WHEEL)) set_bit(REL_HWHEEL, bit); diff --git a/drivers/usb/input/hid.h b/drivers/usb/input/hid.h index c1b6b69..ec2412c 100644 --- a/drivers/usb/input/hid.h +++ b/drivers/usb/input/hid.h @@ -173,6 +173,7 @@ struct hid_item { #define HID_UP_UNDEFINED 0x00000000 #define HID_UP_GENDESK 0x00010000 +#define HID_UP_SIMULATION 0x00020000 #define HID_UP_KEYBOARD 0x00070000 #define HID_UP_LED 0x00080000 #define HID_UP_BUTTON 0x00090000 @@ -182,6 +183,8 @@ struct hid_item { #define HID_UP_PID 0x000f0000 #define HID_UP_HPVENDOR 0xff7f0000 #define HID_UP_MSVENDOR 0xff000000 +#define HID_UP_CUSTOM 0x00ff0000 +#define HID_UP_LOGIVENDOR 0xffbc0000 #define HID_USAGE 0x0000ffff @@ -242,6 +245,7 @@ struct hid_item { #define HID_QUIRK_2WHEEL_MOUSE_HACK_7 0x080 #define HID_QUIRK_2WHEEL_MOUSE_HACK_5 0x100 #define HID_QUIRK_2WHEEL_MOUSE_HACK_ON 0x200 +#define HID_QUIRK_2WHEEL_POWERMOUSE 0x400 /* * This is the global environment of the parser. This information is @@ -348,7 +352,8 @@ struct hid_report_enum { #define HID_REPORT_TYPES 3 -#define HID_BUFFER_SIZE 64 /* use 64 for compatibility with all possible packetlen */ +#define HID_MIN_BUFFER_SIZE 64 /* make sure there is at least a packet size of space */ +#define HID_MAX_BUFFER_SIZE 4096 /* 4kb */ #define HID_CONTROL_FIFO_SIZE 256 /* to init devices with >100 reports */ #define HID_OUTPUT_FIFO_SIZE 64 @@ -386,6 +391,8 @@ struct hid_device { /* device report descriptor */ unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */ + unsigned int bufsize; /* URB buffer size */ + struct urb *urbin; /* Input URB */ char *inbuf; /* Input buffer */ dma_addr_t inbuf_dma; /* Input buffer dma */ diff --git a/drivers/usb/input/hiddev.c b/drivers/usb/input/hiddev.c index 4c13331..d324278 100644 --- a/drivers/usb/input/hiddev.c +++ b/drivers/usb/input/hiddev.c @@ -507,6 +507,7 @@ static int hiddev_ioctl(struct inode *inode, struct file *file, unsigned int cmd return -EINVAL; hid_submit_report(hid, report, USB_DIR_OUT); + hid_wait_io(hid); return 0; diff --git a/drivers/usb/input/keyspan_remote.c b/drivers/usb/input/keyspan_remote.c index 67dc936..99de1b3 100644 --- a/drivers/usb/input/keyspan_remote.c +++ b/drivers/usb/input/keyspan_remote.c @@ -431,11 +431,6 @@ static int keyspan_probe(struct usb_interface *interface, const struct usb_devic struct usb_endpoint_descriptor *endpoint; struct usb_device *udev = usb_get_dev(interface_to_usbdev(interface)); - /* See if the offered device matches what we can accept */ - if ((udev->descriptor.idVendor != USB_KEYSPAN_VENDOR_ID) || - (udev->descriptor.idProduct != USB_KEYSPAN_PRODUCT_UIA11) ) - return -ENODEV; - /* allocate memory for our device state and initialize it */ remote = kmalloc(sizeof(*remote), GFP_KERNEL); if (remote == NULL) { diff --git a/drivers/usb/input/map_to_7segment.h b/drivers/usb/input/map_to_7segment.h new file mode 100644 index 0000000..52ff27f --- /dev/null +++ b/drivers/usb/input/map_to_7segment.h @@ -0,0 +1,189 @@ +/* + * drivers/usb/input/map_to_7segment.h + * + * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MAP_TO_7SEGMENT_H +#define MAP_TO_7SEGMENT_H + +/* This file provides translation primitives and tables for the conversion + * of (ASCII) characters to a 7-segments notation. + * + * The 7 segment's wikipedia notation below is used as standard. + * See: http://en.wikipedia.org/wiki/Seven_segment_display + * + * Notation: +-a-+ + * f b + * +-g-+ + * e c + * +-d-+ + * + * Usage: + * + * Register a map variable, and fill it with a character set: + * static SEG7_DEFAULT_MAP(map_seg7); + * + * + * Then use for conversion: + * seg7 = map_to_seg7(&map_seg7, some_char); + * ... + * + * In device drivers it is recommended, if required, to make the char map + * accessible via the sysfs interface using the following scheme: + * + * static ssize_t show_map(struct device *dev, char *buf) { + * memcpy(buf, &map_seg7, sizeof(map_seg7)); + * return sizeof(map_seg7); + * } + * static ssize_t store_map(struct device *dev, const char *buf, size_t cnt) { + * if(cnt != sizeof(map_seg7)) + * return -EINVAL; + * memcpy(&map_seg7, buf, cnt); + * return cnt; + * } + * static DEVICE_ATTR(map_seg7, PERMS_RW, show_map, store_map); + * + * History: + * 2005-05-31 RFC linux-kernel@vger.kernel.org + */ +#include <linux/errno.h> + + +#define BIT_SEG7_A 0 +#define BIT_SEG7_B 1 +#define BIT_SEG7_C 2 +#define BIT_SEG7_D 3 +#define BIT_SEG7_E 4 +#define BIT_SEG7_F 5 +#define BIT_SEG7_G 6 +#define BIT_SEG7_RESERVED 7 + +struct seg7_conversion_map { + unsigned char table[128]; +}; + +static inline int map_to_seg7(struct seg7_conversion_map *map, int c) +{ + return c & 0x7f ? map->table[c] : -EINVAL; +} + +#define SEG7_CONVERSION_MAP(_name, _map) \ + struct seg7_conversion_map _name = { .table = { _map } } + +/* + * It is recommended to use a facility that allows user space to redefine + * custom character sets for LCD devices. Please use a sysfs interface + * as described above. + */ +#define MAP_TO_SEG7_SYSFS_FILE "map_seg7" + +/******************************************************************************* + * ASCII conversion table + ******************************************************************************/ + +#define _SEG7(l,a,b,c,d,e,f,g) \ + ( a<<BIT_SEG7_A | b<<BIT_SEG7_B | c<<BIT_SEG7_C | d<<BIT_SEG7_D | \ + e<<BIT_SEG7_E | f<<BIT_SEG7_F | g<<BIT_SEG7_G ) + +#define _MAP_0_32_ASCII_SEG7_NON_PRINTABLE \ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + +#define _MAP_33_47_ASCII_SEG7_SYMBOL \ + _SEG7('!',0,0,0,0,1,1,0), _SEG7('"',0,1,0,0,0,1,0), _SEG7('#',0,1,1,0,1,1,0),\ + _SEG7('$',1,0,1,1,0,1,1), _SEG7('%',0,0,1,0,0,1,0), _SEG7('&',1,0,1,1,1,1,1),\ + _SEG7('\'',0,0,0,0,0,1,0),_SEG7('(',1,0,0,1,1,1,0), _SEG7(')',1,1,1,1,0,0,0),\ + _SEG7('*',0,1,1,0,1,1,1), _SEG7('+',0,1,1,0,0,0,1), _SEG7(',',0,0,0,0,1,0,0),\ + _SEG7('-',0,0,0,0,0,0,1), _SEG7('.',0,0,0,0,1,0,0), _SEG7('/',0,1,0,0,1,0,1), + +#define _MAP_48_57_ASCII_SEG7_NUMERIC \ + _SEG7('0',1,1,1,1,1,1,0), _SEG7('1',0,1,1,0,0,0,0), _SEG7('2',1,1,0,1,1,0,1),\ + _SEG7('3',1,1,1,1,0,0,1), _SEG7('4',0,1,1,0,0,1,1), _SEG7('5',1,0,1,1,0,1,1),\ + _SEG7('6',1,0,1,1,1,1,1), _SEG7('7',1,1,1,0,0,0,0), _SEG7('8',1,1,1,1,1,1,1),\ + _SEG7('9',1,1,1,1,0,1,1), + +#define _MAP_58_64_ASCII_SEG7_SYMBOL \ + _SEG7(':',0,0,0,1,0,0,1), _SEG7(';',0,0,0,1,0,0,1), _SEG7('<',1,0,0,0,0,1,1),\ + _SEG7('=',0,0,0,1,0,0,1), _SEG7('>',1,1,0,0,0,0,1), _SEG7('?',1,1,1,0,0,1,0),\ + _SEG7('@',1,1,0,1,1,1,1), + +#define _MAP_65_90_ASCII_SEG7_ALPHA_UPPR \ + _SEG7('A',1,1,1,0,1,1,1), _SEG7('B',1,1,1,1,1,1,1), _SEG7('C',1,0,0,1,1,1,0),\ + _SEG7('D',1,1,1,1,1,1,0), _SEG7('E',1,0,0,1,1,1,1), _SEG7('F',1,0,0,0,1,1,1),\ + _SEG7('G',1,1,1,1,0,1,1), _SEG7('H',0,1,1,0,1,1,1), _SEG7('I',0,1,1,0,0,0,0),\ + _SEG7('J',0,1,1,1,0,0,0), _SEG7('K',0,1,1,0,1,1,1), _SEG7('L',0,0,0,1,1,1,0),\ + _SEG7('M',1,1,1,0,1,1,0), _SEG7('N',1,1,1,0,1,1,0), _SEG7('O',1,1,1,1,1,1,0),\ + _SEG7('P',1,1,0,0,1,1,1), _SEG7('Q',1,1,1,1,1,1,0), _SEG7('R',1,1,1,0,1,1,1),\ + _SEG7('S',1,0,1,1,0,1,1), _SEG7('T',0,0,0,1,1,1,1), _SEG7('U',0,1,1,1,1,1,0),\ + _SEG7('V',0,1,1,1,1,1,0), _SEG7('W',0,1,1,1,1,1,1), _SEG7('X',0,1,1,0,1,1,1),\ + _SEG7('Y',0,1,1,0,0,1,1), _SEG7('Z',1,1,0,1,1,0,1), + +#define _MAP_91_96_ASCII_SEG7_SYMBOL \ + _SEG7('[',1,0,0,1,1,1,0), _SEG7('\\',0,0,1,0,0,1,1),_SEG7(']',1,1,1,1,0,0,0),\ + _SEG7('^',1,1,0,0,0,1,0), _SEG7('_',0,0,0,1,0,0,0), _SEG7('`',0,1,0,0,0,0,0), + +#define _MAP_97_122_ASCII_SEG7_ALPHA_LOWER \ + _SEG7('A',1,1,1,0,1,1,1), _SEG7('b',0,0,1,1,1,1,1), _SEG7('c',0,0,0,1,1,0,1),\ + _SEG7('d',0,1,1,1,1,0,1), _SEG7('E',1,0,0,1,1,1,1), _SEG7('F',1,0,0,0,1,1,1),\ + _SEG7('G',1,1,1,1,0,1,1), _SEG7('h',0,0,1,0,1,1,1), _SEG7('i',0,0,1,0,0,0,0),\ + _SEG7('j',0,0,1,1,0,0,0), _SEG7('k',0,0,1,0,1,1,1), _SEG7('L',0,0,0,1,1,1,0),\ + _SEG7('M',1,1,1,0,1,1,0), _SEG7('n',0,0,1,0,1,0,1), _SEG7('o',0,0,1,1,1,0,1),\ + _SEG7('P',1,1,0,0,1,1,1), _SEG7('q',1,1,1,0,0,1,1), _SEG7('r',0,0,0,0,1,0,1),\ + _SEG7('S',1,0,1,1,0,1,1), _SEG7('T',0,0,0,1,1,1,1), _SEG7('u',0,0,1,1,1,0,0),\ + _SEG7('v',0,0,1,1,1,0,0), _SEG7('W',0,1,1,1,1,1,1), _SEG7('X',0,1,1,0,1,1,1),\ + _SEG7('y',0,1,1,1,0,1,1), _SEG7('Z',1,1,0,1,1,0,1), + +#define _MAP_123_126_ASCII_SEG7_SYMBOL \ + _SEG7('{',1,0,0,1,1,1,0), _SEG7('|',0,0,0,0,1,1,0), _SEG7('}',1,1,1,1,0,0,0),\ + _SEG7('~',1,0,0,0,0,0,0), + +/* Maps */ + +/* This set tries to map as close as possible to the visible characteristics + * of the ASCII symbol, lowercase and uppercase letters may differ in + * presentation on the display. + */ +#define MAP_ASCII7SEG_ALPHANUM \ + _MAP_0_32_ASCII_SEG7_NON_PRINTABLE \ + _MAP_33_47_ASCII_SEG7_SYMBOL \ + _MAP_48_57_ASCII_SEG7_NUMERIC \ + _MAP_58_64_ASCII_SEG7_SYMBOL \ + _MAP_65_90_ASCII_SEG7_ALPHA_UPPR \ + _MAP_91_96_ASCII_SEG7_SYMBOL \ + _MAP_97_122_ASCII_SEG7_ALPHA_LOWER \ + _MAP_123_126_ASCII_SEG7_SYMBOL + +/* This set tries to map as close as possible to the symbolic characteristics + * of the ASCII character for maximum discrimination. + * For now this means all alpha chars are in lower case representations. + * (This for example facilitates the use of hex numbers with uppercase input.) + */ +#define MAP_ASCII7SEG_ALPHANUM_LC \ + _MAP_0_32_ASCII_SEG7_NON_PRINTABLE \ + _MAP_33_47_ASCII_SEG7_SYMBOL \ + _MAP_48_57_ASCII_SEG7_NUMERIC \ + _MAP_58_64_ASCII_SEG7_SYMBOL \ + _MAP_97_122_ASCII_SEG7_ALPHA_LOWER \ + _MAP_91_96_ASCII_SEG7_SYMBOL \ + _MAP_97_122_ASCII_SEG7_ALPHA_LOWER \ + _MAP_123_126_ASCII_SEG7_SYMBOL + +#define SEG7_DEFAULT_MAP(_name) \ + SEG7_CONVERSION_MAP(_name,MAP_ASCII7SEG_ALPHANUM) + +#endif /* MAP_TO_7SEGMENT_H */ + diff --git a/drivers/usb/input/yealink.c b/drivers/usb/input/yealink.c new file mode 100644 index 0000000..58a176e --- /dev/null +++ b/drivers/usb/input/yealink.c @@ -0,0 +1,1013 @@ +/* + * drivers/usb/input/yealink.c + * + * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * Description: + * Driver for the USB-P1K voip usb phone. + * This device is produced by Yealink Network Technology Co Ltd + * but may be branded under several names: + * - Yealink usb-p1k + * - Tiptel 115 + * - ... + * + * This driver is based on: + * - the usbb2k-api http://savannah.nongnu.org/projects/usbb2k-api/ + * - information from http://memeteau.free.fr/usbb2k + * - the xpad-driver drivers/usb/input/xpad.c + * + * Thanks to: + * - Olivier Vandorpe, for providing the usbb2k-api. + * - Martin Diehl, for spotting my memory allocation bug. + * + * History: + * 20050527 henk First version, functional keyboard. Keyboard events + * will pop-up on the ../input/eventX bus. + * 20050531 henk Added led, LCD, dialtone and sysfs interface. + * 20050610 henk Cleanups, make it ready for public consumption. + * 20050630 henk Cleanups, fixes in response to comments. + * 20050701 henk sysfs write serialisation, fix potential unload races + * 20050801 henk Added ringtone, restructure USB + * 20050816 henk Merge 2.6.13-rc6 + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/rwsem.h> +#include <linux/usb.h> + +#include "map_to_7segment.h" +#include "yealink.h" + +#define DRIVER_VERSION "yld-20050816" +#define DRIVER_AUTHOR "Henk Vergonet" +#define DRIVER_DESC "Yealink phone driver" + +#define YEALINK_POLLING_FREQUENCY 10 /* in [Hz] */ + +struct yld_status { + u8 lcd[24]; + u8 led; + u8 dialtone; + u8 ringtone; + u8 keynum; +} __attribute__ ((packed)); + +/* + * Register the LCD segment and icon map + */ +#define _LOC(k,l) { .a = (k), .m = (l) } +#define _SEG(t, a, am, b, bm, c, cm, d, dm, e, em, f, fm, g, gm) \ + { .type = (t), \ + .u = { .s = { _LOC(a, am), _LOC(b, bm), _LOC(c, cm), \ + _LOC(d, dm), _LOC(e, em), _LOC(g, gm), \ + _LOC(f, fm) } } } +#define _PIC(t, h, hm, n) \ + { .type = (t), \ + .u = { .p = { .name = (n), .a = (h), .m = (hm) } } } + +static const struct lcd_segment_map { + char type; + union { + struct pictogram_map { + u8 a,m; + char name[10]; + } p; + struct segment_map { + u8 a,m; + } s[7]; + } u; +} lcdMap[] = { +#include "yealink.h" +}; + +struct yealink_dev { + struct input_dev idev; /* input device */ + struct usb_device *udev; /* usb device */ + + /* irq input channel */ + struct yld_ctl_packet *irq_data; + dma_addr_t irq_dma; + struct urb *urb_irq; + + /* control output channel */ + struct yld_ctl_packet *ctl_data; + dma_addr_t ctl_dma; + struct usb_ctrlrequest *ctl_req; + dma_addr_t ctl_req_dma; + struct urb *urb_ctl; + + char phys[64]; /* physical device path */ + + u8 lcdMap[ARRAY_SIZE(lcdMap)]; /* state of LCD, LED ... */ + int key_code; /* last reported key */ + + int stat_ix; + union { + struct yld_status s; + u8 b[sizeof(struct yld_status)]; + } master, copy; +}; + + +/******************************************************************************* + * Yealink lcd interface + ******************************************************************************/ + +/* + * Register a default 7 segment character set + */ +static SEG7_DEFAULT_MAP(map_seg7); + + /* Display a char, + * char '\9' and '\n' are placeholders and do not overwrite the original text. + * A space will always hide an icon. + */ +static int setChar(struct yealink_dev *yld, int el, int chr) +{ + int i, a, m, val; + + if (el >= ARRAY_SIZE(lcdMap)) + return -EINVAL; + + if (chr == '\t' || chr == '\n') + return 0; + + yld->lcdMap[el] = chr; + + if (lcdMap[el].type == '.') { + a = lcdMap[el].u.p.a; + m = lcdMap[el].u.p.m; + if (chr != ' ') + yld->master.b[a] |= m; + else + yld->master.b[a] &= ~m; + return 0; + } + + val = map_to_seg7(&map_seg7, chr); + for (i = 0; i < ARRAY_SIZE(lcdMap[0].u.s); i++) { + m = lcdMap[el].u.s[i].m; + + if (m == 0) + continue; + + a = lcdMap[el].u.s[i].a; + if (val & 1) + yld->master.b[a] |= m; + else + yld->master.b[a] &= ~m; + val = val >> 1; + } + return 0; +}; + +/******************************************************************************* + * Yealink key interface + ******************************************************************************/ + +/* Map device buttons to internal key events. + * + * USB-P1K button layout: + * + * up + * IN OUT + * down + * + * pickup C hangup + * 1 2 3 + * 4 5 6 + * 7 8 9 + * * 0 # + * + * The "up" and "down" keys, are symbolised by arrows on the button. + * The "pickup" and "hangup" keys are symbolised by a green and red phone + * on the button. + */ +static int map_p1k_to_key(int scancode) +{ + switch(scancode) { /* phone key: */ + case 0x23: return KEY_LEFT; /* IN */ + case 0x33: return KEY_UP; /* up */ + case 0x04: return KEY_RIGHT; /* OUT */ + case 0x24: return KEY_DOWN; /* down */ + case 0x03: return KEY_ENTER; /* pickup */ + case 0x14: return KEY_BACKSPACE; /* C */ + case 0x13: return KEY_ESC; /* hangup */ + case 0x00: return KEY_1; /* 1 */ + case 0x01: return KEY_2; /* 2 */ + case 0x02: return KEY_3; /* 3 */ + case 0x10: return KEY_4; /* 4 */ + case 0x11: return KEY_5; /* 5 */ + case 0x12: return KEY_6; /* 6 */ + case 0x20: return KEY_7; /* 7 */ + case 0x21: return KEY_8; /* 8 */ + case 0x22: return KEY_9; /* 9 */ + case 0x30: return KEY_KPASTERISK; /* * */ + case 0x31: return KEY_0; /* 0 */ + case 0x32: return KEY_LEFTSHIFT | + KEY_3 << 8; /* # */ + } + return -EINVAL; +} + +/* Completes a request by converting the data into events for the + * input subsystem. + * + * The key parameter can be cascaded: key2 << 8 | key1 + */ +static void report_key(struct yealink_dev *yld, int key, struct pt_regs *regs) +{ + struct input_dev *idev = &yld->idev; + + input_regs(idev, regs); + if (yld->key_code >= 0) { + /* old key up */ + input_report_key(idev, yld->key_code & 0xff, 0); + if (yld->key_code >> 8) + input_report_key(idev, yld->key_code >> 8, 0); + } + + yld->key_code = key; + if (key >= 0) { + /* new valid key */ + input_report_key(idev, key & 0xff, 1); + if (key >> 8) + input_report_key(idev, key >> 8, 1); + } + input_sync(idev); +} + +/******************************************************************************* + * Yealink usb communication interface + ******************************************************************************/ + +static int yealink_cmd(struct yealink_dev *yld, struct yld_ctl_packet *p) +{ + u8 *buf = (u8 *)p; + int i; + u8 sum = 0; + + for(i=0; i<USB_PKT_LEN-1; i++) + sum -= buf[i]; + p->sum = sum; + return usb_control_msg(yld->udev, + usb_sndctrlpipe(yld->udev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x200, 3, + p, sizeof(*p), + USB_CTRL_SET_TIMEOUT); +} + +static u8 default_ringtone[] = { + 0xEF, /* volume [0-255] */ + 0xFB, 0x1E, 0x00, 0x0C, /* 1250 [hz], 12/100 [s] */ + 0xFC, 0x18, 0x00, 0x0C, /* 1000 [hz], 12/100 [s] */ + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFF, 0xFF, 0x01, 0x90, /* silent, 400/100 [s] */ + 0x00, 0x00 /* end of sequence */ +}; + +static int yealink_set_ringtone(struct yealink_dev *yld, u8 *buf, size_t size) +{ + struct yld_ctl_packet *p = yld->ctl_data; + int ix, len; + + if (size <= 0) + return -EINVAL; + + /* Set the ringtone volume */ + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_RING_VOLUME; + yld->ctl_data->size = 1; + yld->ctl_data->data[0] = buf[0]; + yealink_cmd(yld, p); + + buf++; + size--; + + p->cmd = CMD_RING_NOTE; + ix = 0; + while (size != ix) { + len = size - ix; + if (len > sizeof(p->data)) + len = sizeof(p->data); + p->size = len; + p->offset = cpu_to_be16(ix); + memcpy(p->data, &buf[ix], len); + yealink_cmd(yld, p); + ix += len; + } + return 0; +} + +/* keep stat_master & stat_copy in sync. + */ +static int yealink_do_idle_tasks(struct yealink_dev *yld) +{ + u8 val; + int i, ix, len; + + ix = yld->stat_ix; + + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_KEYPRESS; + yld->ctl_data->size = 1; + yld->ctl_data->sum = 0xff - CMD_KEYPRESS; + + /* If state update pointer wraps do a KEYPRESS first. */ + if (ix >= sizeof(yld->master)) { + yld->stat_ix = 0; + return 0; + } + + /* find update candidates: copy != master */ + do { + val = yld->master.b[ix]; + if (val != yld->copy.b[ix]) + goto send_update; + } while (++ix < sizeof(yld->master)); + + /* nothing todo, wait a bit and poll for a KEYPRESS */ + yld->stat_ix = 0; + /* TODO how can we wait abit. ?? + * msleep_interruptible(1000 / YEALINK_POLLING_FREQUENCY); + */ + return 0; + +send_update: + + /* Setup an appropriate update request */ + yld->copy.b[ix] = val; + yld->ctl_data->data[0] = val; + + switch(ix) { + case offsetof(struct yld_status, led): + yld->ctl_data->cmd = CMD_LED; + yld->ctl_data->sum = -1 - CMD_LED - val; + break; + case offsetof(struct yld_status, dialtone): + yld->ctl_data->cmd = CMD_DIALTONE; + yld->ctl_data->sum = -1 - CMD_DIALTONE - val; + break; + case offsetof(struct yld_status, ringtone): + yld->ctl_data->cmd = CMD_RINGTONE; + yld->ctl_data->sum = -1 - CMD_RINGTONE - val; + break; + case offsetof(struct yld_status, keynum): + val--; + val &= 0x1f; + yld->ctl_data->cmd = CMD_SCANCODE; + yld->ctl_data->offset = cpu_to_be16(val); + yld->ctl_data->data[0] = 0; + yld->ctl_data->sum = -1 - CMD_SCANCODE - val; + break; + default: + len = sizeof(yld->master.s.lcd) - ix; + if (len > sizeof(yld->ctl_data->data)) + len = sizeof(yld->ctl_data->data); + + /* Combine up to <len> consecutive LCD bytes in a singe request + */ + yld->ctl_data->cmd = CMD_LCD; + yld->ctl_data->offset = cpu_to_be16(ix); + yld->ctl_data->size = len; + yld->ctl_data->sum = -CMD_LCD - ix - val - len; + for(i=1; i<len; i++) { + ix++; + val = yld->master.b[ix]; + yld->copy.b[ix] = val; + yld->ctl_data->data[i] = val; + yld->ctl_data->sum -= val; + } + } + yld->stat_ix = ix + 1; + return 1; +} + +/* Decide on how to handle responses + * + * The state transition diagram is somethhing like: + * + * syncState<--+ + * | | + * | idle + * \|/ | + * init --ok--> waitForKey --ok--> getKey + * ^ ^ | + * | +-------ok-------+ + * error,start + * + */ +static void urb_irq_callback(struct urb *urb, struct pt_regs *regs) +{ + struct yealink_dev *yld = urb->context; + int ret; + + if (urb->status) + err("%s - urb status %d", __FUNCTION__, urb->status); + + switch (yld->irq_data->cmd) { + case CMD_KEYPRESS: + + yld->master.s.keynum = yld->irq_data->data[0]; + break; + + case CMD_SCANCODE: + dbg("get scancode %x", yld->irq_data->data[0]); + + report_key(yld, map_p1k_to_key(yld->irq_data->data[0]), regs); + break; + + default: + err("unexpected response %x", yld->irq_data->cmd); + } + + yealink_do_idle_tasks(yld); + + ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC); + if (ret) + err("%s - usb_submit_urb failed %d", __FUNCTION__, ret); +} + +static void urb_ctl_callback(struct urb *urb, struct pt_regs *regs) +{ + struct yealink_dev *yld = urb->context; + int ret; + + if (urb->status) + err("%s - urb status %d", __FUNCTION__, urb->status); + + switch (yld->ctl_data->cmd) { + case CMD_KEYPRESS: + case CMD_SCANCODE: + /* ask for a response */ + ret = usb_submit_urb(yld->urb_irq, GFP_ATOMIC); + break; + default: + /* send new command */ + yealink_do_idle_tasks(yld); + ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC); + } + + if (ret) + err("%s - usb_submit_urb failed %d", __FUNCTION__, ret); +} + +/******************************************************************************* + * input event interface + ******************************************************************************/ + +/* TODO should we issue a ringtone on a SND_BELL event? +static int input_ev(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + + if (type != EV_SND) + return -EINVAL; + + switch (code) { + case SND_BELL: + case SND_TONE: + break; + default: + return -EINVAL; + } + + return 0; +} +*/ + +static int input_open(struct input_dev *dev) +{ + struct yealink_dev *yld = dev->private; + int i, ret; + + dbg("%s", __FUNCTION__); + + /* force updates to device */ + for (i = 0; i<sizeof(yld->master); i++) + yld->copy.b[i] = ~yld->master.b[i]; + yld->key_code = -1; /* no keys pressed */ + + yealink_set_ringtone(yld, default_ringtone, sizeof(default_ringtone)); + + /* issue INIT */ + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_INIT; + yld->ctl_data->size = 10; + yld->ctl_data->sum = 0x100-CMD_INIT-10; + if ((ret = usb_submit_urb(yld->urb_ctl, GFP_KERNEL)) != 0) { + dbg("%s - usb_submit_urb failed with result %d", + __FUNCTION__, ret); + return ret; + } + return 0; +} + +static void input_close(struct input_dev *dev) +{ + struct yealink_dev *yld = dev->private; + + usb_kill_urb(yld->urb_ctl); + usb_kill_urb(yld->urb_irq); +} + +/******************************************************************************* + * sysfs interface + ******************************************************************************/ + +static DECLARE_RWSEM(sysfs_rwsema); + +/* Interface to the 7-segments translation table aka. char set. + */ +static ssize_t show_map(struct device *dev, struct device_attribute *attr, + char *buf) +{ + memcpy(buf, &map_seg7, sizeof(map_seg7)); + return sizeof(map_seg7); +} + +static ssize_t store_map(struct device *dev, struct device_attribute *attr, + const char *buf, size_t cnt) +{ + if (cnt != sizeof(map_seg7)) + return -EINVAL; + memcpy(&map_seg7, buf, sizeof(map_seg7)); + return sizeof(map_seg7); +} + +/* Interface to the LCD. + */ + +/* Reading /sys/../lineX will return the format string with its settings: + * + * Example: + * cat ./line3 + * 888888888888 + * Linux Rocks! + */ +static ssize_t show_line(struct device *dev, char *buf, int a, int b) +{ + struct yealink_dev *yld; + int i; + + down_read(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_read(&sysfs_rwsema); + return -ENODEV; + } + + for (i = a; i < b; i++) + *buf++ = lcdMap[i].type; + *buf++ = '\n'; + for (i = a; i < b; i++) + *buf++ = yld->lcdMap[i]; + *buf++ = '\n'; + *buf = 0; + + up_read(&sysfs_rwsema); + return 3 + ((b - a) << 1); +} + +static ssize_t show_line1(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE1_OFFSET, LCD_LINE2_OFFSET); +} + +static ssize_t show_line2(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE2_OFFSET, LCD_LINE3_OFFSET); +} + +static ssize_t show_line3(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE3_OFFSET, LCD_LINE4_OFFSET); +} + +/* Writing to /sys/../lineX will set the coresponding LCD line. + * - Excess characters are ignored. + * - If less characters are written than allowed, the remaining digits are + * unchanged. + * - The '\n' or '\t' char is a placeholder, it does not overwrite the + * original content. + */ +static ssize_t store_line(struct device *dev, const char *buf, size_t count, + int el, size_t len) +{ + struct yealink_dev *yld; + int i; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + if (len > count) + len = count; + for (i = 0; i < len; i++) + setChar(yld, el++, buf[i]); + + up_write(&sysfs_rwsema); + return count; +} + +static ssize_t store_line1(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE1_OFFSET, LCD_LINE1_SIZE); +} + +static ssize_t store_line2(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE2_OFFSET, LCD_LINE2_SIZE); +} + +static ssize_t store_line3(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE3_OFFSET, LCD_LINE3_SIZE); +} + +/* Interface to visible and audible "icons", these include: + * pictures on the LCD, the LED, and the dialtone signal. + */ + +/* Get a list of "switchable elements" with their current state. */ +static ssize_t get_icons(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct yealink_dev *yld; + int i, ret = 1; + + down_read(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_read(&sysfs_rwsema); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) { + if (lcdMap[i].type != '.') + continue; + ret += sprintf(&buf[ret], "%s %s\n", + yld->lcdMap[i] == ' ' ? " " : "on", + lcdMap[i].u.p.name); + } + up_read(&sysfs_rwsema); + return ret; +} + +/* Change the visibility of a particular element. */ +static ssize_t set_icon(struct device *dev, const char *buf, size_t count, + int chr) +{ + struct yealink_dev *yld; + int i; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) { + if (lcdMap[i].type != '.') + continue; + if (strncmp(buf, lcdMap[i].u.p.name, count) == 0) { + setChar(yld, i, chr); + break; + } + } + + up_write(&sysfs_rwsema); + return count; +} + +static ssize_t show_icon(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return set_icon(dev, buf, count, buf[0]); +} + +static ssize_t hide_icon(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return set_icon(dev, buf, count, ' '); +} + +/* Upload a ringtone to the device. + */ + +/* Stores raw ringtone data in the phone */ +static ssize_t store_ringtone(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct yealink_dev *yld; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + /* TODO locking with async usb control interface??? */ + yealink_set_ringtone(yld, (char *)buf, count); + up_write(&sysfs_rwsema); + return count; +} + +#define _M444 S_IRUGO +#define _M664 S_IRUGO|S_IWUSR|S_IWGRP +#define _M220 S_IWUSR|S_IWGRP + +static DEVICE_ATTR(map_seg7 , _M664, show_map , store_map ); +static DEVICE_ATTR(line1 , _M664, show_line1 , store_line1 ); +static DEVICE_ATTR(line2 , _M664, show_line2 , store_line2 ); +static DEVICE_ATTR(line3 , _M664, show_line3 , store_line3 ); +static DEVICE_ATTR(get_icons , _M444, get_icons , NULL ); +static DEVICE_ATTR(show_icon , _M220, NULL , show_icon ); +static DEVICE_ATTR(hide_icon , _M220, NULL , hide_icon ); +static DEVICE_ATTR(ringtone , _M220, NULL , store_ringtone); + +static struct attribute *yld_attributes[] = { + &dev_attr_line1.attr, + &dev_attr_line2.attr, + &dev_attr_line3.attr, + &dev_attr_get_icons.attr, + &dev_attr_show_icon.attr, + &dev_attr_hide_icon.attr, + &dev_attr_map_seg7.attr, + &dev_attr_ringtone.attr, + NULL +}; + +static struct attribute_group yld_attr_group = { + .attrs = yld_attributes +}; + +/******************************************************************************* + * Linux interface and usb initialisation + ******************************************************************************/ + +static const struct yld_device { + u16 idVendor; + u16 idProduct; + char *name; +} yld_device[] = { + { 0x6993, 0xb001, "Yealink usb-p1k" }, +}; + +static struct usb_device_id usb_table [] = { + { USB_INTERFACE_INFO(USB_CLASS_HID, 0, 0) }, + { } +}; + +static int usb_cleanup(struct yealink_dev *yld, int err) +{ + if (yld == NULL) + return err; + + if (yld->urb_irq) { + usb_kill_urb(yld->urb_irq); + usb_free_urb(yld->urb_irq); + } + if (yld->urb_ctl) + usb_free_urb(yld->urb_ctl); + if (yld->idev.dev) + input_unregister_device(&yld->idev); + if (yld->ctl_req) + usb_buffer_free(yld->udev, sizeof(*(yld->ctl_req)), + yld->ctl_req, yld->ctl_req_dma); + if (yld->ctl_data) + usb_buffer_free(yld->udev, USB_PKT_LEN, + yld->ctl_data, yld->ctl_dma); + if (yld->irq_data) + usb_buffer_free(yld->udev, USB_PKT_LEN, + yld->irq_data, yld->irq_dma); + kfree(yld); + return err; +} + +static void usb_disconnect(struct usb_interface *intf) +{ + struct yealink_dev *yld; + + down_write(&sysfs_rwsema); + yld = usb_get_intfdata(intf); + sysfs_remove_group(&intf->dev.kobj, &yld_attr_group); + usb_set_intfdata(intf, NULL); + up_write(&sysfs_rwsema); + + usb_cleanup(yld, 0); +} + +static int usb_match(struct usb_device *udev) +{ + int i; + u16 idVendor = le16_to_cpu(udev->descriptor.idVendor); + u16 idProduct = le16_to_cpu(udev->descriptor.idProduct); + + for (i = 0; i < ARRAY_SIZE(yld_device); i++) { + if ((idVendor == yld_device[i].idVendor) && + (idProduct == yld_device[i].idProduct)) + return i; + } + return -ENODEV; +} + +static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev (intf); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct yealink_dev *yld; + char path[64]; + int ret, pipe, i; + + i = usb_match(udev); + if (i < 0) + return -ENODEV; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + if (!(endpoint->bEndpointAddress & 0x80)) + return -EIO; + if ((endpoint->bmAttributes & 3) != 3) + return -EIO; + + if ((yld = kmalloc(sizeof(struct yealink_dev), GFP_KERNEL)) == NULL) + return -ENOMEM; + + memset(yld, 0, sizeof(*yld)); + yld->udev = udev; + + /* allocate usb buffers */ + yld->irq_data = usb_buffer_alloc(udev, USB_PKT_LEN, + SLAB_ATOMIC, &yld->irq_dma); + if (yld->irq_data == NULL) + return usb_cleanup(yld, -ENOMEM); + + yld->ctl_data = usb_buffer_alloc(udev, USB_PKT_LEN, + SLAB_ATOMIC, &yld->ctl_dma); + if (!yld->ctl_data) + return usb_cleanup(yld, -ENOMEM); + + yld->ctl_req = usb_buffer_alloc(udev, sizeof(*(yld->ctl_req)), + SLAB_ATOMIC, &yld->ctl_req_dma); + if (yld->ctl_req == NULL) + return usb_cleanup(yld, -ENOMEM); + + /* allocate urb structures */ + yld->urb_irq = usb_alloc_urb(0, GFP_KERNEL); + if (yld->urb_irq == NULL) + return usb_cleanup(yld, -ENOMEM); + + yld->urb_ctl = usb_alloc_urb(0, GFP_KERNEL); + if (yld->urb_ctl == NULL) + return usb_cleanup(yld, -ENOMEM); + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + ret = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + if (ret != USB_PKT_LEN) + err("invalid payload size %d, expected %d", ret, USB_PKT_LEN); + + /* initialise irq urb */ + usb_fill_int_urb(yld->urb_irq, udev, pipe, yld->irq_data, + USB_PKT_LEN, + urb_irq_callback, + yld, endpoint->bInterval); + yld->urb_irq->transfer_dma = yld->irq_dma; + yld->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + yld->urb_irq->dev = udev; + + /* initialise ctl urb */ + yld->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_OUT; + yld->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION; + yld->ctl_req->wValue = cpu_to_le16(0x200); + yld->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); + yld->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN); + + usb_fill_control_urb(yld->urb_ctl, udev, usb_sndctrlpipe(udev, 0), + (void *)yld->ctl_req, yld->ctl_data, USB_PKT_LEN, + urb_ctl_callback, yld); + yld->urb_ctl->setup_dma = yld->ctl_req_dma; + yld->urb_ctl->transfer_dma = yld->ctl_dma; + yld->urb_ctl->transfer_flags |= URB_NO_SETUP_DMA_MAP | + URB_NO_TRANSFER_DMA_MAP; + yld->urb_ctl->dev = udev; + + /* find out the physical bus location */ + if (usb_make_path(udev, path, sizeof(path)) > 0) + snprintf(yld->phys, sizeof(yld->phys)-1, "%s/input0", path); + + /* register settings for the input device */ + init_input_dev(&yld->idev); + yld->idev.private = yld; + yld->idev.id.bustype = BUS_USB; + yld->idev.id.vendor = le16_to_cpu(udev->descriptor.idVendor); + yld->idev.id.product = le16_to_cpu(udev->descriptor.idProduct); + yld->idev.id.version = le16_to_cpu(udev->descriptor.bcdDevice); + yld->idev.dev = &intf->dev; + yld->idev.name = yld_device[i].name; + yld->idev.phys = yld->phys; + /* yld->idev.event = input_ev; TODO */ + yld->idev.open = input_open; + yld->idev.close = input_close; + + /* register available key events */ + yld->idev.evbit[0] = BIT(EV_KEY); + for (i = 0; i < 256; i++) { + int k = map_p1k_to_key(i); + if (k >= 0) { + set_bit(k & 0xff, yld->idev.keybit); + if (k >> 8) + set_bit(k >> 8, yld->idev.keybit); + } + } + + printk(KERN_INFO "input: %s on %s\n", yld->idev.name, path); + + input_register_device(&yld->idev); + + usb_set_intfdata(intf, yld); + + /* clear visible elements */ + for (i=0; i<ARRAY_SIZE(lcdMap); i++) + setChar(yld, i, ' '); + + /* display driver version on LCD line 3 */ + store_line3(&intf->dev, NULL, + DRIVER_VERSION, sizeof(DRIVER_VERSION)); + + /* Register sysfs hooks (don't care about failure) */ + sysfs_create_group(&intf->dev.kobj, &yld_attr_group); + return 0; +} + +static struct usb_driver yealink_driver = { + .owner = THIS_MODULE, + .name = "yealink", + .probe = usb_probe, + .disconnect = usb_disconnect, + .id_table = usb_table, +}; + +static int __init yealink_dev_init(void) +{ + int ret = usb_register(&yealink_driver); + if (ret == 0) + info(DRIVER_DESC ":" DRIVER_VERSION); + return ret; +} + +static void __exit yealink_dev_exit(void) +{ + usb_deregister(&yealink_driver); +} + +module_init(yealink_dev_init); +module_exit(yealink_dev_exit); + +MODULE_DEVICE_TABLE (usb, usb_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/input/yealink.h b/drivers/usb/input/yealink.h new file mode 100644 index 0000000..48af0be --- /dev/null +++ b/drivers/usb/input/yealink.h @@ -0,0 +1,220 @@ +/* + * drivers/usb/input/yealink.h + * + * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef INPUT_YEALINK_H +#define INPUT_YEALINK_H + +/* Using the control channel on interface 3 various aspects of the phone + * can be controlled like LCD, LED, dialtone and the ringtone. + */ + +struct yld_ctl_packet { + u8 cmd; /* command code, see below */ + u8 size; /* 1-11, size of used data bytes. */ + u16 offset; /* internal packet offset */ + u8 data[11]; + s8 sum; /* negative sum of 15 preceding bytes */ +} __attribute__ ((packed)); + +#define USB_PKT_LEN sizeof(struct yld_ctl_packet) + +/* The following yld_ctl_packet's are available: */ + +/* Init registers + * + * cmd 0x8e + * size 10 + * offset 0 + * data 0,0,0,0.... + */ +#define CMD_INIT 0x8e + +/* Request key scan + * + * cmd 0x80 + * size 1 + * offset 0 + * data[0] on return returns the key number, if it changes there's a new + * key pressed. + */ +#define CMD_KEYPRESS 0x80 + +/* Request scancode + * + * cmd 0x81 + * size 1 + * offset key number [0-1f] + * data[0] on return returns the scancode + */ +#define CMD_SCANCODE 0x81 + +/* Set LCD + * + * cmd 0x04 + * size 1-11 + * offset 0-23 + * data segment bits + */ +#define CMD_LCD 0x04 + +/* Set led + * + * cmd 0x05 + * size 1 + * offset 0 + * data[0] 0 OFF / 1 ON + */ +#define CMD_LED 0x05 + +/* Set ringtone volume + * + * cmd 0x11 + * size 1 + * offset 0 + * data[0] 0-0xff volume + */ +#define CMD_RING_VOLUME 0x11 + +/* Set ringtone notes + * + * cmd 0x02 + * size 1-11 + * offset 0-> + * data binary representation LE16(-freq), LE16(duration) .... + */ +#define CMD_RING_NOTE 0x02 + +/* Sound ringtone via the speaker on the back + * + * cmd 0x03 + * size 1 + * offset 0 + * data[0] 0 OFF / 0x24 ON + */ +#define CMD_RINGTONE 0x03 + +/* Sound dial tone via the ear speaker + * + * cmd 0x09 + * size 1 + * offset 0 + * data[0] 0 OFF / 1 ON + */ +#define CMD_DIALTONE 0x09 + +#endif /* INPUT_YEALINK_H */ + + +#if defined(_SEG) && defined(_PIC) +/* This table maps the LCD segments onto individual bit positions in the + * yld_status struct. + */ + +/* LCD, each segment must be driven seperately. + * + * Layout: + * + * |[] [][] [][] [][] in |[][] + * |[] M [][] D [][] : [][] out |[][] + * store + * + * NEW REP SU MO TU WE TH FR SA + * + * [] [] [] [] [] [] [] [] [] [] [] [] + * [] [] [] [] [] [] [] [] [] [] [] [] + */ + +/* Line 1 + * Format : 18.e8.M8.88...188 + * Icon names : M D : IN OUT STORE + */ +#define LCD_LINE1_OFFSET 0 +#define LCD_LINE1_SIZE 17 + +/* Note: first g then f => ! ! */ +/* _SEG( type a b c d e g f ) */ + _SEG('1', 0,0 , 22,2 , 22,2 , 0,0 , 0,0 , 0,0 , 0,0 ), + _SEG('8', 20,1 , 20,2 , 20,4 , 20,8 , 21,4 , 21,2 , 21,1 ), + _PIC('.', 22,1 , "M" ), + _SEG('e', 18,1 , 18,2 , 18,4 , 18,1 , 19,2 , 18,1 , 19,1 ), + _SEG('8', 16,1 , 16,2 , 16,4 , 16,8 , 17,4 , 17,2 , 17,1 ), + _PIC('.', 15,8 , "D" ), + _SEG('M', 14,1 , 14,2 , 14,4 , 14,1 , 15,4 , 15,2 , 15,1 ), + _SEG('8', 12,1 , 12,2 , 12,4 , 12,8 , 13,4 , 13,2 , 13,1 ), + _PIC('.', 11,8 , ":" ), + _SEG('8', 10,1 , 10,2 , 10,4 , 10,8 , 11,4 , 11,2 , 11,1 ), + _SEG('8', 8,1 , 8,2 , 8,4 , 8,8 , 9,4 , 9,2 , 9,1 ), + _PIC('.', 7,1 , "IN" ), + _PIC('.', 7,2 , "OUT" ), + _PIC('.', 7,4 , "STORE" ), + _SEG('1', 0,0 , 5,1 , 5,1 , 0,0 , 0,0 , 0,0 , 0,0 ), + _SEG('8', 4,1 , 4,2 , 4,4 , 4,8 , 5,8 , 5,4 , 5,2 ), + _SEG('8', 2,1 , 2,2 , 2,4 , 2,8 , 3,4 , 3,2 , 3,1 ), + +/* Line 2 + * Format : ......... + * Pict. name : NEW REP SU MO TU WE TH FR SA + */ +#define LCD_LINE2_OFFSET LCD_LINE1_OFFSET + LCD_LINE1_SIZE +#define LCD_LINE2_SIZE 9 + + _PIC('.', 23,2 , "NEW" ), + _PIC('.', 23,4 , "REP" ), + _PIC('.', 1,8 , "SU" ), + _PIC('.', 1,4 , "MO" ), + _PIC('.', 1,2 , "TU" ), + _PIC('.', 1,1 , "WE" ), + _PIC('.', 0,1 , "TH" ), + _PIC('.', 0,2 , "FR" ), + _PIC('.', 0,4 , "SA" ), + +/* Line 3 + * Format : 888888888888 + */ +#define LCD_LINE3_OFFSET LCD_LINE2_OFFSET + LCD_LINE2_SIZE +#define LCD_LINE3_SIZE 12 + + _SEG('8', 22,16, 22,32, 22,64, 22,128, 23,128, 23,64, 23,32 ), + _SEG('8', 20,16, 20,32, 20,64, 20,128, 21,128, 21,64, 21,32 ), + _SEG('8', 18,16, 18,32, 18,64, 18,128, 19,128, 19,64, 19,32 ), + _SEG('8', 16,16, 16,32, 16,64, 16,128, 17,128, 17,64, 17,32 ), + _SEG('8', 14,16, 14,32, 14,64, 14,128, 15,128, 15,64, 15,32 ), + _SEG('8', 12,16, 12,32, 12,64, 12,128, 13,128, 13,64, 13,32 ), + _SEG('8', 10,16, 10,32, 10,64, 10,128, 11,128, 11,64, 11,32 ), + _SEG('8', 8,16, 8,32, 8,64, 8,128, 9,128, 9,64, 9,32 ), + _SEG('8', 6,16, 6,32, 6,64, 6,128, 7,128, 7,64, 7,32 ), + _SEG('8', 4,16, 4,32, 4,64, 4,128, 5,128, 5,64, 5,32 ), + _SEG('8', 2,16, 2,32, 2,64, 2,128, 3,128, 3,64, 3,32 ), + _SEG('8', 0,16, 0,32, 0,64, 0,128, 1,128, 1,64, 1,32 ), + +/* Line 4 + * + * The LED, DIALTONE and RINGTONE are implemented as icons and use the same + * sysfs interface. + */ +#define LCD_LINE4_OFFSET LCD_LINE3_OFFSET + LCD_LINE3_SIZE + + _PIC('.', offsetof(struct yld_status, led) , 0x01, "LED" ), + _PIC('.', offsetof(struct yld_status, dialtone) , 0x01, "DIALTONE" ), + _PIC('.', offsetof(struct yld_status, ringtone) , 0x24, "RINGTONE" ), + +#undef _SEG +#undef _PIC +#endif /* _SEG && _PIC */ |