LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH] input: driver for USB VoIP phones with CM109 chipset
@ 2008-02-07 18:38 Alfred E. Heggestad
2008-02-07 21:59 ` Dmitry Torokhov
` (2 more replies)
0 siblings, 3 replies; 11+ messages in thread
From: Alfred E. Heggestad @ 2008-02-07 18:38 UTC (permalink / raw)
To: dmitry.torokhov, linux-input; +Cc: linux-kernel
From: Alfred E. Heggestad <aeh@db.org>
This driver adds support for USB VoIP phones using the CM109 chipset,
such as Komunikate KIP-1000 and Genius G-talk. Keypad is scanned and
events are reported to the input subsystem. The buzzer can be activated
by sending SND_TONE or SND_BELL to the input device. The phone keymap
can be selected in run-time by using the "phone" module parameter.
The driver has been tested with linux 2.6.24 on i386, and also tested
to build cleanly on AMD64.
More testing and code review is welcome..
Signed-off-by: Alfred E. Heggestad <aeh@db.org>
---
diff -uprN -X linux-2.6.24/Documentation/dontdiff linux-2.6.24-orig/drivers/input/misc/cm109.c linux-2.6.24/drivers/input/misc/cm109.c
--- linux-2.6.24-orig/drivers/input/misc/cm109.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.24/drivers/input/misc/cm109.c 2008-02-07 19:13:52.000000000 +0100
@@ -0,0 +1,646 @@
+/*
+ * Driver for the VoIP USB phones with CM109 chipsets.
+ *
+ * Copyright (C) 2007 - 2008 Alfred E. Heggestad <aeh@db.org>
+ *
+ * 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.
+ */
+
+/*
+ * Tested devices:
+ * - Komunikate KIP1000
+ * - Genius G-talk
+ * - ...
+ *
+ * This driver is based on the yealink.c driver
+ *
+ * Thanks to:
+ * - Authors of yealink.c
+ * - Thomas Reitmayr
+ * - Oliver Neukum for good review comments
+ * - Shaun Jackman <sjackman@gmail.com> for Genius G-talk keymap
+ *
+ * Todo:
+ * - Read/write EEPROM
+ * - Report input events volume up/down
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/rwsem.h>
+#include <linux/usb/input.h>
+
+/* #define CM109_DEBUG 1 */
+
+#define DRIVER_VERSION "20080205"
+#define DRIVER_AUTHOR "Alfred E. Heggestad"
+#define DRIVER_DESC "CM109 phone driver"
+
+static char *phone = "kip1000";
+module_param(phone, charp, S_IRUSR);
+MODULE_PARM_DESC(phone, "Phone name {kip1000, gtalk}");
+
+
+enum {
+ /* HID Registers */
+ HID_IR0 = 0x00, /* Record/Playback-mute button, Volume up/down */
+ HID_IR1 = 0x01, /* GPI, generic registers or EEPROM_DATA0 */
+ HID_IR2 = 0x02, /* Generic registers or EEPROM_DATA1 */
+ HID_IR3 = 0x03, /* Generic registers or EEPROM_CTRL */
+ HID_OR0 = 0x00, /* Mapping control, buzzer, SPDIF (offset 0x04) */
+ HID_OR1 = 0x01, /* GPO - General Purpose Output */
+ HID_OR2 = 0x02, /* Set GPIO to input/output mode */
+ HID_OR3 = 0x03, /* SPDIF status channel or EEPROM_CTRL */
+
+ /* HID_IR0 */
+ RECORD_MUTE = 1 << 3,
+ PLAYBACK_MUTE = 1 << 2,
+ VOLUME_DOWN = 1 << 1,
+ VOLUME_UP = 1 << 0,
+
+ /* HID_OR0 */
+ /* bits 7-6
+ 0: HID_OR1-2 are used for GPO; HID_OR0, 3 are used for buzzer
+ and SPDIF
+ 1: HID_OR0-3 are used as generic HID registers
+ 2: Values written to HID_OR0-3 are also mapped to MCU_CTRL,
+ EEPROM_DATA0-1, EEPROM_CTRL (see Note)
+ 3: Reserved
+ */
+ HID_OR_GPO_BUZ_SPDIF = 0 << 6,
+ HID_OR_GENERIC_HID_REG = 1 << 6,
+ HID_OR_MAP_MCU_EEPROM = 2 << 6,
+
+ BUZZER_ON = 1 << 5,
+};
+
+/* CM109 protocol packet */
+struct cm109_ctl_packet {
+ u8 byte[4];
+} __attribute__ ((packed));
+
+enum {USB_PKT_LEN = sizeof(struct cm109_ctl_packet)};
+
+/* CM109 device structure */
+struct cm109_dev {
+ struct input_dev *idev; /* input device */
+ struct usb_device *udev; /* usb device */
+
+ /* irq input channel */
+ struct cm109_ctl_packet *irq_data;
+ dma_addr_t irq_dma;
+ struct urb *urb_irq;
+
+ /* control output channel */
+ struct cm109_ctl_packet *ctl_data;
+ dma_addr_t ctl_dma;
+ struct usb_ctrlrequest *ctl_req;
+ dma_addr_t ctl_req_dma;
+ struct urb *urb_ctl;
+
+ spinlock_t submit_lock;
+ int disconnecting;
+
+ char phys[64]; /* physical device path */
+ int key_code; /* last reported key */
+ int keybit; /* 0=new scan 1,2,4,8=scan columns */
+ u8 gpi; /* Cached value of GPI (high nibble) */
+};
+
+/*******************************************************************************
+ * CM109 key interface
+ ******************************************************************************/
+
+
+/* Map device buttons to internal key events.
+ *
+ * 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.
+
+ Komunikate KIP1000 Keyboard Matrix
+
+ -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10)
+ | | | |
+ <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20)
+ | | | |
+ END - 7 -- 8 -- 9 --> GPI pin 6 (0x40)
+ | | | |
+ OK -- * -- 0 -- # --> GPI pin 7 (0x80)
+ | | | |
+
+ /|\ /|\ /|\ /|\
+ | | | |
+GPO
+pin: 3 2 1 0
+ 0x8 0x4 0x2 0x1
+
+ */
+static int keymap_kip1000(int scancode)
+{
+ switch (scancode) { /* phone key: */
+ case 0x82: return KEY_0; /* 0 */
+ case 0x14: return KEY_1; /* 1 */
+ case 0x12: return KEY_2; /* 2 */
+ case 0x11: return KEY_3; /* 3 */
+ case 0x24: return KEY_4; /* 4 */
+ case 0x22: return KEY_5; /* 5 */
+ case 0x21: return KEY_6; /* 6 */
+ case 0x44: return KEY_7; /* 7 */
+ case 0x42: return KEY_8; /* 8 */
+ case 0x41: return KEY_9; /* 9 */
+ case 0x81: return KEY_LEFTSHIFT | KEY_3 << 8; /* # */
+ case 0x84: return KEY_KPASTERISK; /* * */
+ case 0x88: return KEY_ENTER; /* pickup */
+ case 0x48: return KEY_ESC; /* hangup */
+ case 0x28: return KEY_LEFT; /* IN */
+ case 0x18: return KEY_RIGHT; /* OUT */
+ }
+ return -EINVAL;
+}
+
+/*
+ Contributed by Shaun Jackman <sjackman@gmail.com>
+
+ Genius G-Talk keyboard matrix
+ 0 1 2 3
+ 4: 0 4 8 Talk
+ 5: 1 5 9 End
+ 6: 2 6 # Up
+ 7: 3 7 * Down
+*/
+static int keymap_gtalk(int scancode)
+{
+ switch (scancode) {
+ case 0x11: return KEY_0;
+ case 0x21: return KEY_1;
+ case 0x41: return KEY_2;
+ case 0x81: return KEY_3;
+ case 0x12: return KEY_4;
+ case 0x22: return KEY_5;
+ case 0x42: return KEY_6;
+ case 0x82: return KEY_7;
+ case 0x14: return KEY_8;
+ case 0x24: return KEY_9;
+ case 0x44: return KEY_LEFTSHIFT | KEY_3 << 8; /* # */
+ case 0x84: return KEY_KPASTERISK;
+ case 0x18: return KEY_ENTER; /* Talk (green handset) */
+ case 0x28: return KEY_ESC; /* End (red handset) */
+ case 0x48: return KEY_UP; /* Menu up (rocker switch) */
+ case 0x88: return KEY_DOWN; /* Menu down (rocker switch) */
+ }
+ return -EINVAL;
+}
+
+static int (*keymap)(int) = keymap_kip1000;
+
+
+/* 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 cm109_dev *dev, int key)
+{
+ struct input_dev *idev = dev->idev;
+
+ if (dev->key_code >= 0) {
+ /* old key up */
+ input_report_key(idev, dev->key_code & 0xff, 0);
+ if (dev->key_code >> 8)
+ input_report_key(idev, dev->key_code >> 8, 0);
+ }
+
+ dev->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);
+}
+
+/*******************************************************************************
+ * CM109 usb communication interface
+ ******************************************************************************/
+
+
+/*
+ * IRQ handler
+ */
+static void urb_irq_callback(struct urb *urb)
+{
+ struct cm109_dev *dev = urb->context;
+ int ret;
+
+#ifdef CM109_DEBUG
+ dbg("### URB IRQ: [0x%02x 0x%02x 0x%02x 0x%02x] keybit=0x%02x",
+ dev->irq_data->byte[0],
+ dev->irq_data->byte[1],
+ dev->irq_data->byte[2],
+ dev->irq_data->byte[3],
+ dev->keybit);
+#endif
+
+ if (urb->status) {
+ if (-ESHUTDOWN == urb->status)
+ return;
+ err("%s - urb status %d", __FUNCTION__, urb->status);
+ }
+
+ /* Scan key column */
+ if (0xf == dev->keybit) {
+
+ /* Any changes ? */
+ if ((dev->gpi & 0xf0) == (dev->irq_data->byte[HID_IR1] & 0xf0)) {
+ goto out;
+ }
+
+ dev->gpi = dev->irq_data->byte[HID_IR1] & 0xf0;
+
+ dev->keybit = 0x1;
+ } else {
+ report_key(dev, keymap(dev->irq_data->byte[HID_IR1]));
+
+ dev->keybit <<= 1;
+ if (dev->keybit > 0x8)
+ dev->keybit = 0xf;
+ }
+
+ dev->ctl_data->byte[HID_OR1] = dev->keybit;
+ dev->ctl_data->byte[HID_OR2] = dev->keybit;
+
+ out:
+ ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
+ if (ret)
+ err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+}
+
+static void urb_ctl_callback(struct urb *urb)
+{
+ struct cm109_dev *dev = urb->context;
+ int ret = 0;
+
+#ifdef CM109_DEBUG
+ dbg("### URB CTL: [0x%02x 0x%02x 0x%02x 0x%02x]",
+ dev->ctl_data->byte[0],
+ dev->ctl_data->byte[1],
+ dev->ctl_data->byte[2],
+ dev->ctl_data->byte[3]);
+#endif
+
+ if (urb->status)
+ err("%s - urb status %d", __FUNCTION__, urb->status);
+
+ spin_lock(&dev->submit_lock);
+ /* ask for a response */
+ if (!dev->disconnecting)
+ ret = usb_submit_urb(dev->urb_irq, GFP_ATOMIC);
+ spin_unlock(&dev->submit_lock);
+
+ if (ret)
+ err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+}
+
+/*******************************************************************************
+ * input event interface
+ ******************************************************************************/
+
+static DEFINE_SPINLOCK(cm109_buzz_lock);
+
+static int input_open(struct input_dev *idev)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+ int ret;
+
+ dev->key_code = -1; /* no keys pressed */
+ dev->keybit = 0xf;
+
+ /* issue INIT */
+ dev->ctl_data->byte[HID_OR0] = HID_OR_GPO_BUZ_SPDIF;
+ dev->ctl_data->byte[HID_OR1] = dev->keybit;
+ dev->ctl_data->byte[HID_OR2] = dev->keybit;
+ dev->ctl_data->byte[HID_OR3] = 0x00;
+
+ if ((ret = usb_submit_urb(dev->urb_ctl, GFP_KERNEL)) != 0) {
+ err("%s - usb_submit_urb failed with result %d",
+ __FUNCTION__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void input_close(struct input_dev *idev)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+
+ usb_kill_urb(dev->urb_ctl);
+ usb_kill_urb(dev->urb_irq);
+}
+
+static void buzz(struct cm109_dev *dev, int on)
+{
+ int ret;
+
+ if (dev == NULL) {
+ err("buzz: dev is NULL");
+ return;
+ }
+
+ dbg("Buzzer %s", on ? "on" : "off");
+ if (on)
+ dev->ctl_data->byte[HID_OR0] |= BUZZER_ON;
+ else
+ dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON;
+
+ ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
+ if (ret)
+ err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+}
+
+static int input_ev(struct input_dev *idev, unsigned int type,
+ unsigned int code, int value)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+ unsigned long flags;
+
+#ifdef CM109_DEBUG
+ info("input_ev: type=%u code=%u value=%d", type, code, value);
+#endif
+
+ if (type != EV_SND)
+ return -EINVAL;
+
+ switch (code) {
+ case SND_TONE:
+ case SND_BELL:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&cm109_buzz_lock, flags);
+ buzz(dev, value);
+ spin_unlock_irqrestore(&cm109_buzz_lock, flags);
+
+ return 0;
+}
+
+
+/*******************************************************************************
+ * Linux interface and usb initialisation
+ ******************************************************************************/
+
+struct driver_info {
+ char *name;
+};
+
+static const struct driver_info info_cm109 = {
+ .name = "CM109 USB driver",
+};
+
+enum {
+ VENDOR_ID = 0x0d8c, /* C-Media Electronics */
+ PRODUCT_ID_CM109 = 0x000e, /* CM109 defines range 0x0008 - 0x000f */
+};
+
+/* table of devices that work with this driver */
+static const struct usb_device_id usb_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = VENDOR_ID,
+ .idProduct = PRODUCT_ID_CM109,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t) & info_cm109},
+ /* you can add more devices here with product ID 0x0008 - 0x000f */
+ {}
+};
+
+static int usb_cleanup(struct cm109_dev *dev, int err)
+{
+ if (dev == NULL)
+ return err;
+
+ usb_kill_urb(dev->urb_irq); /* parameter validation in core/urb */
+ usb_kill_urb(dev->urb_ctl); /* parameter validation in core/urb */
+
+ if (dev->idev) {
+ if (err)
+ input_free_device(dev->idev);
+ else
+ input_unregister_device(dev->idev);
+ }
+ if (dev->ctl_req)
+ usb_buffer_free(dev->udev, sizeof(*(dev->ctl_req)),
+ dev->ctl_req, dev->ctl_req_dma);
+ if (dev->ctl_data)
+ usb_buffer_free(dev->udev, USB_PKT_LEN,
+ dev->ctl_data, dev->ctl_dma);
+ if (dev->irq_data)
+ usb_buffer_free(dev->udev, USB_PKT_LEN,
+ dev->irq_data, dev->irq_dma);
+
+ usb_free_urb(dev->urb_irq); /* parameter validation in core/urb */
+ usb_free_urb(dev->urb_ctl); /* parameter validation in core/urb */
+ kfree(dev);
+
+ return err;
+}
+
+static void usb_disconnect(struct usb_interface *interface)
+{
+ struct cm109_dev *dev;
+
+ dev = usb_get_intfdata(interface);
+
+ /* Wait for URB idle */
+ spin_lock_irq(&dev->submit_lock);
+ dev->disconnecting = 1;
+ spin_unlock_irq(&dev->submit_lock);
+
+ usb_set_intfdata(interface, NULL);
+
+ usb_cleanup(dev, 0);
+}
+
+static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct driver_info *nfo = (struct driver_info *)id->driver_info;
+ struct usb_host_interface *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct cm109_dev *dev;
+ struct input_dev *input_dev;
+ int ret, pipe, i;
+
+ interface = intf->cur_altsetting;
+ endpoint = &interface->endpoint[0].desc;
+
+ if (!usb_endpoint_is_int_in(endpoint))
+ return -ENODEV;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->submit_lock);
+ dev->disconnecting = 0;
+
+ dev->udev = udev;
+
+ dev->idev = input_dev = input_allocate_device();
+ if (!input_dev)
+ goto err;
+
+ /* allocate usb buffers */
+ dev->irq_data = usb_buffer_alloc(udev, USB_PKT_LEN,
+ GFP_KERNEL, &dev->irq_dma);
+ if (dev->irq_data == NULL)
+ goto err;
+
+ dev->ctl_data = usb_buffer_alloc(udev, USB_PKT_LEN,
+ GFP_KERNEL, &dev->ctl_dma);
+ if (!dev->ctl_data)
+ goto err;
+
+ dev->ctl_req = usb_buffer_alloc(udev, sizeof(*(dev->ctl_req)),
+ GFP_KERNEL, &dev->ctl_req_dma);
+ if (dev->ctl_req == NULL)
+ goto err;
+
+ /* allocate urb structures */
+ dev->urb_irq = usb_alloc_urb(0, GFP_KERNEL);
+ if (dev->urb_irq == NULL)
+ goto err;
+
+ dev->urb_ctl = usb_alloc_urb(0, GFP_KERNEL);
+ if (dev->urb_ctl == NULL)
+ goto err;
+
+ /* 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(dev->urb_irq, udev, pipe, dev->irq_data,
+ USB_PKT_LEN,
+ urb_irq_callback, dev, endpoint->bInterval);
+ dev->urb_irq->transfer_dma = dev->irq_dma;
+ dev->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ dev->urb_irq->dev = udev;
+
+ /* initialise ctl urb */
+ dev->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE |
+ USB_DIR_OUT;
+ dev->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION;
+ dev->ctl_req->wValue = cpu_to_le16(0x200);
+ dev->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
+ dev->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN);
+
+ usb_fill_control_urb(dev->urb_ctl, udev, usb_sndctrlpipe(udev, 0),
+ (void *)dev->ctl_req, dev->ctl_data, USB_PKT_LEN,
+ urb_ctl_callback, dev);
+ dev->urb_ctl->setup_dma = dev->ctl_req_dma;
+ dev->urb_ctl->transfer_dma = dev->ctl_dma;
+ dev->urb_ctl->transfer_flags |= URB_NO_SETUP_DMA_MAP |
+ URB_NO_TRANSFER_DMA_MAP;
+ dev->urb_ctl->dev = udev;
+
+ /* find out the physical bus location */
+ usb_make_path(udev, dev->phys, sizeof(dev->phys));
+ strlcat(dev->phys, "/input0", sizeof(dev->phys));
+
+ /* register settings for the input device */
+ input_dev->name = nfo->name;
+ input_dev->phys = dev->phys;
+ usb_to_input_id(udev, &input_dev->id);
+ input_dev->dev.parent = &intf->dev;
+
+ input_set_drvdata(input_dev, dev);
+ input_dev->open = input_open;
+ input_dev->close = input_close;
+ input_dev->event = input_ev;
+
+ /* register available key events */
+ input_dev->evbit[0] = BIT_MASK(EV_KEY);
+ for (i = 0; i < 256; i++) {
+ int k = keymap(i);
+ if (k >= 0) {
+ set_bit(k & 0xff, input_dev->keybit);
+ if (k >> 8)
+ set_bit(k >> 8, input_dev->keybit);
+ }
+ }
+
+ input_dev->evbit[0] |= BIT_MASK(EV_SND);
+ input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+
+ ret = input_register_device(dev->idev);
+ if (ret)
+ goto err;
+
+ usb_set_intfdata(intf, dev);
+
+ return 0;
+
+ err:
+ return usb_cleanup(dev, -ENOMEM);
+}
+
+static struct usb_driver cm109_driver = {
+ .name = "cm109",
+ .probe = usb_probe,
+ .disconnect = usb_disconnect,
+ .id_table = usb_table,
+};
+
+static int __init cm109_dev_init(void)
+{
+ int ret;
+
+ /* Load the phone keymap */
+ if (0 == strcasecmp(phone, "kip1000")) {
+ keymap = keymap_kip1000;
+ info("Keymap for Komunikate KIP1000 phone loaded");
+ }
+ else if (0 == strcasecmp(phone, "gtalk")) {
+ keymap = keymap_gtalk;
+ info("Keymap for Genius G-talk phone loaded");
+ }
+ else {
+ err("Unsupported phone: %s", phone);
+ return -EINVAL;
+ }
+
+ ret = usb_register(&cm109_driver);
+ if (ret == 0)
+ info(DRIVER_DESC ": " DRIVER_VERSION " (C) " DRIVER_AUTHOR);
+
+ return ret;
+}
+
+static void __exit cm109_dev_exit(void)
+{
+ usb_deregister(&cm109_driver);
+}
+
+module_init(cm109_dev_init);
+module_exit(cm109_dev_exit);
+
+MODULE_DEVICE_TABLE(usb, usb_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff -uprN -X linux-2.6.24/Documentation/dontdiff linux-2.6.24-orig/drivers/input/misc/Kconfig linux-2.6.24/drivers/input/misc/Kconfig
--- linux-2.6.24-orig/drivers/input/misc/Kconfig 2008-01-24 23:58:37.000000000 +0100
+++ linux-2.6.24/drivers/input/misc/Kconfig 2008-02-07 19:14:26.000000000 +0100
@@ -166,6 +166,18 @@ config INPUT_YEALINK
To compile this driver as a module, choose M here: the module will be
called yealink.
+config INPUT_CM109
+ tristate "C-Media CM109 USB I/O Controller"
+ depends on INPUT && EXPERIMENTAL
+ select USB
+ ---help---
+ Say Y here if you want to enable keyboard and buzzer functions of the
+ C-Media CM109 usb phones. The audio part is enabled by the generic
+ usb sound driver, so you might want to enable that as well.
+
+ To compile this driver as a module, choose M here: the module will be
+ called cm109.
+
config INPUT_UINPUT
tristate "User level driver support"
help
diff -uprN -X linux-2.6.24/Documentation/dontdiff linux-2.6.24-orig/drivers/input/misc/Makefile linux-2.6.24/drivers/input/misc/Makefile
--- linux-2.6.24-orig/drivers/input/misc/Makefile 2008-01-24 23:58:37.000000000 +0100
+++ linux-2.6.24/drivers/input/misc/Makefile 2008-02-07 19:14:26.000000000 +0100
@@ -16,5 +16,6 @@ obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+obj-$(CONFIG_INPUT_CM109) += cm109.o
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_UINPUT) += uinput.o
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-02-07 18:38 [PATCH] input: driver for USB VoIP phones with CM109 chipset Alfred E. Heggestad
@ 2008-02-07 21:59 ` Dmitry Torokhov
2008-02-08 21:23 ` Alfred E. Heggestad
2008-06-21 22:23 ` Alfred E. Heggestad
2008-02-09 19:12 ` Pavel Machek
[not found] ` <200802091023.02580.oliver@neukum.org>
2 siblings, 2 replies; 11+ messages in thread
From: Dmitry Torokhov @ 2008-02-07 21:59 UTC (permalink / raw)
To: Alfred E. Heggestad; +Cc: linux-input, linux-kernel
Hi Alfred,
On Thu, Feb 07, 2008 at 07:38:10PM +0100, Alfred E. Heggestad wrote:
> From: Alfred E. Heggestad <aeh@db.org>
>
> This driver adds support for USB VoIP phones using the CM109 chipset,
> such as Komunikate KIP-1000 and Genius G-talk. Keypad is scanned and
> events are reported to the input subsystem. The buzzer can be activated
> by sending SND_TONE or SND_BELL to the input device. The phone keymap
> can be selected in run-time by using the "phone" module parameter.
> The driver has been tested with linux 2.6.24 on i386, and also tested
> to build cleanly on AMD64.
> More testing and code review is welcome..
>
For a long time I was sitting on the patch not sure what to do about
the pound key, but I think we need to allocate separate keycodes for
remote controls and phones that work regardless of users keymap.
Another item is keymap for different devices - the best way to handle
it I think it to implement getkeycodes and setkeycodes methods for
the input device and have alternative keymaps loaded from userspace
instead of adding module parameters.
The rest of the driver looks great and I am sorry I was ignoring it
for so long.
--
Dmitry
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-02-07 21:59 ` Dmitry Torokhov
@ 2008-02-08 21:23 ` Alfred E. Heggestad
2008-06-21 22:23 ` Alfred E. Heggestad
1 sibling, 0 replies; 11+ messages in thread
From: Alfred E. Heggestad @ 2008-02-08 21:23 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel
Dmitry Torokhov wrote:
> Hi Alfred,
>
> On Thu, Feb 07, 2008 at 07:38:10PM +0100, Alfred E. Heggestad wrote:
>> From: Alfred E. Heggestad <aeh@db.org>
>>
>> This driver adds support for USB VoIP phones using the CM109 chipset,
>> such as Komunikate KIP-1000 and Genius G-talk. Keypad is scanned and
>> events are reported to the input subsystem. The buzzer can be activated
>> by sending SND_TONE or SND_BELL to the input device. The phone keymap
>> can be selected in run-time by using the "phone" module parameter.
>> The driver has been tested with linux 2.6.24 on i386, and also tested
>> to build cleanly on AMD64.
>> More testing and code review is welcome..
>>
>
> For a long time I was sitting on the patch not sure what to do about
> the pound key, but I think we need to allocate separate keycodes for
> remote controls and phones that work regardless of users keymap.
>
ok, let us define a new KEY_KPPOUND in linux/input.h
this also means that any application wanting to use this key must
be updated to read this new keycode.
do you want me to add a new value for KEY_KPPOUND in linux/input.h
and include it in the next patch, or can you add that value to the
tree first?
> Another item is keymap for different devices - the best way to handle
> it I think it to implement getkeycodes and setkeycodes methods for
> the input device and have alternative keymaps loaded from userspace
> instead of adding module parameters.
>
I agree - I dont want to upgrade the driver code whenever there is a
new phone with different key-mapping.
regarding get/set-keycodes, please let me know how I should
proceed on this one..
> The rest of the driver looks great and I am sorry I was ignoring it
> for so long.
>
no worries, I am happy to fix the remaining issues and submit new
versions of the patch..
/alfred
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-02-07 18:38 [PATCH] input: driver for USB VoIP phones with CM109 chipset Alfred E. Heggestad
2008-02-07 21:59 ` Dmitry Torokhov
@ 2008-02-09 19:12 ` Pavel Machek
2008-03-03 22:07 ` Alfred E. Heggestad
[not found] ` <200802091023.02580.oliver@neukum.org>
2 siblings, 1 reply; 11+ messages in thread
From: Pavel Machek @ 2008-02-09 19:12 UTC (permalink / raw)
To: Alfred E. Heggestad; +Cc: dmitry.torokhov, linux-input, linux-kernel
Hi!
> + Komunikate KIP1000 Keyboard Matrix
> +
> + -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10)
> + | | | |
> + <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20)
> + | | | |
> + END - 7 -- 8 -- 9 --> GPI pin 6 (0x40)
> + | | | |
> + OK -- * -- 0 -- # --> GPI pin 7 (0x80)
> + | | | |
> +
> + /|\ /|\ /|\ /|\
> + | | | |
> +GPO
> +pin: 3 2 1 0
> + 0x8 0x4 0x2 0x1
> +
> + */
> +static int keymap_kip1000(int scancode)
> +{
> + switch (scancode) { /* phone
> key: */
> + case 0x82: return KEY_0; /* 0
> */
> + case 0x14: return KEY_1; /* 1
> */
> + case 0x12: return KEY_2; /* 2
> */
> + case 0x11: return KEY_3; /* 3
> */
> + case 0x24: return KEY_4; /* 4
> */
> + case 0x22: return KEY_5; /* 5
> */
> + case 0x21: return KEY_6; /* 6
> */
> + case 0x44: return KEY_7; /* 7
> */
> + case 0x42: return KEY_8; /* 8
> */
> + case 0x41: return KEY_9; /* 9
> */
> + case 0x81: return KEY_LEFTSHIFT | KEY_3 << 8; /*
> # */
That's a very dirty hack, right?
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-02-09 19:12 ` Pavel Machek
@ 2008-03-03 22:07 ` Alfred E. Heggestad
2008-03-03 22:13 ` Pavel Machek
0 siblings, 1 reply; 11+ messages in thread
From: Alfred E. Heggestad @ 2008-03-03 22:07 UTC (permalink / raw)
To: Pavel Machek; +Cc: dmitry.torokhov, linux-input, linux-kernel
Pavel Machek wrote:
> Hi!
>
>> + Komunikate KIP1000 Keyboard Matrix
>> +
>> + -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10)
>> + | | | |
>> + <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20)
>> + | | | |
>> + END - 7 -- 8 -- 9 --> GPI pin 6 (0x40)
>> + | | | |
>> + OK -- * -- 0 -- # --> GPI pin 7 (0x80)
>> + | | | |
>> +
>> + /|\ /|\ /|\ /|\
>> + | | | |
>> +GPO
>> +pin: 3 2 1 0
>> + 0x8 0x4 0x2 0x1
>> +
>> + */
>> +static int keymap_kip1000(int scancode)
>> +{
>> + switch (scancode) { /* phone
>> key: */
>> + case 0x82: return KEY_0; /* 0
>> */
>> + case 0x14: return KEY_1; /* 1
>> */
>> + case 0x12: return KEY_2; /* 2
>> */
>> + case 0x11: return KEY_3; /* 3
>> */
>> + case 0x24: return KEY_4; /* 4
>> */
>> + case 0x22: return KEY_5; /* 5
>> */
>> + case 0x21: return KEY_6; /* 6
>> */
>> + case 0x44: return KEY_7; /* 7
>> */
>> + case 0x42: return KEY_8; /* 8
>> */
>> + case 0x41: return KEY_9; /* 9
>> */
>> + case 0x81: return KEY_LEFTSHIFT | KEY_3 << 8; /*
>> # */
>
> That's a very dirty hack, right?
>
yeah, that is a dirty hack which we should avoid by defining
a new key called e.g. KEY_KPPOUND for the '#'-key.
the CM109 driver is based on the Yealink driver
(drivers/input/misc/yealink.c) so the same hack exists there..
/alfred
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
[not found] ` <200802091023.02580.oliver@neukum.org>
@ 2008-03-03 22:10 ` Alfred E. Heggestad
0 siblings, 0 replies; 11+ messages in thread
From: Alfred E. Heggestad @ 2008-03-03 22:10 UTC (permalink / raw)
To: Oliver Neukum; +Cc: dmitry.torokhov, linux-input, linux-kernel
Oliver Neukum wrote:
> Am Donnerstag, 7. Februar 2008 19:38:10 schrieb Alfred E. Heggestad:
>> +static void buzz(struct cm109_dev *dev, int on)
>> +{
>> + int ret;
>> +
>> + if (dev == NULL) {
>> + err("buzz: dev is NULL");
>> + return;
>> + }
>> +
>> + dbg("Buzzer %s", on ? "on" : "off");
>> + if (on)
>> + dev->ctl_data->byte[HID_OR0] |= BUZZER_ON;
>> + else
>> + dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON;
>> +
>> + ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
>> + if (ret)
>> + err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
>> +}
>> +
>> +static int input_ev(struct input_dev *idev, unsigned int type,
>> + unsigned int code, int value)
>> +{
>> + struct cm109_dev *dev = input_get_drvdata(idev);
>> + unsigned long flags;
>> +
>> +#ifdef CM109_DEBUG
>> + info("input_ev: type=%u code=%u value=%d", type, code, value);
>> +#endif
>> +
>> + if (type != EV_SND)
>> + return -EINVAL;
>> +
>> + switch (code) {
>> + case SND_TONE:
>> + case SND_BELL:
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + spin_lock_irqsave(&cm109_buzz_lock, flags);
>> + buzz(dev, value);
>> + spin_unlock_irqrestore(&cm109_buzz_lock, flags);
>> +
>> + return 0;
>> +}
>
> What makes you sure you finished the URB to switch the buzzer on before
> you try to switch it off again? It seems that you might be attempting to
> manipulate a live URB there if you hit the window.
>
thanks for the hint. could you let me know how I can fix this properly,
or point me to some USB driver code that implements this correctly.. ?
/alfred
> Regards
> Oliver
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-03-03 22:07 ` Alfred E. Heggestad
@ 2008-03-03 22:13 ` Pavel Machek
0 siblings, 0 replies; 11+ messages in thread
From: Pavel Machek @ 2008-03-03 22:13 UTC (permalink / raw)
To: Alfred E. Heggestad; +Cc: dmitry.torokhov, linux-input, linux-kernel
On Mon 2008-03-03 23:07:02, Alfred E. Heggestad wrote:
> Pavel Machek wrote:
>> Hi!
>>
>>> + Komunikate KIP1000 Keyboard Matrix
>>> +
>>> + -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10)
>>> + | | | |
>>> + <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20)
>>> + | | | |
>>> + END - 7 -- 8 -- 9 --> GPI pin 6 (0x40)
>>> + | | | |
>>> + OK -- * -- 0 -- # --> GPI pin 7 (0x80)
>>> + | | | |
>>> +
>>> + /|\ /|\ /|\ /|\
>>> + | | | |
>>> +GPO
>>> +pin: 3 2 1 0
>>> + 0x8 0x4 0x2 0x1
>>> +
>>> + */
>>> +static int keymap_kip1000(int scancode)
>>> +{
>>> + switch (scancode) { /* phone key: */
>>> + case 0x82: return KEY_0; /* 0 */
>>> + case 0x14: return KEY_1; /* 1 */
>>> + case 0x12: return KEY_2; /* 2 */
>>> + case 0x11: return KEY_3; /* 3 */
>>> + case 0x24: return KEY_4; /* 4 */
>>> + case 0x22: return KEY_5; /* 5 */
>>> + case 0x21: return KEY_6; /* 6 */
>>> + case 0x44: return KEY_7; /* 7 */
>>> + case 0x42: return KEY_8; /* 8 */
>>> + case 0x41: return KEY_9; /* 9 */
>>> + case 0x81: return KEY_LEFTSHIFT | KEY_3 << 8; /* # */
>>
>> That's a very dirty hack, right?
>>
>
> yeah, that is a dirty hack which we should avoid by defining
> a new key called e.g. KEY_KPPOUND for the '#'-key.
Yes please. Phones are pretty common these days.
> the CM109 driver is based on the Yealink driver
> (drivers/input/misc/yealink.c) so the same hack exists there..
Fix it too ;-).
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-02-07 21:59 ` Dmitry Torokhov
2008-02-08 21:23 ` Alfred E. Heggestad
@ 2008-06-21 22:23 ` Alfred E. Heggestad
2008-06-24 4:59 ` Dmitry Torokhov
1 sibling, 1 reply; 11+ messages in thread
From: Alfred E. Heggestad @ 2008-06-21 22:23 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input, linux-kernel
Hi Dmitry
Dmitry Torokhov wrote:
> Hi Alfred,
>
> On Thu, Feb 07, 2008 at 07:38:10PM +0100, Alfred E. Heggestad wrote:
>> From: Alfred E. Heggestad <aeh@db.org>
>>
>> This driver adds support for USB VoIP phones using the CM109 chipset,
>> such as Komunikate KIP-1000 and Genius G-talk. Keypad is scanned and
>> events are reported to the input subsystem. The buzzer can be activated
>> by sending SND_TONE or SND_BELL to the input device. The phone keymap
>> can be selected in run-time by using the "phone" module parameter.
>> The driver has been tested with linux 2.6.24 on i386, and also tested
>> to build cleanly on AMD64.
>> More testing and code review is welcome..
>>
>
> For a long time I was sitting on the patch not sure what to do about
> the pound key, but I think we need to allocate separate keycodes for
> remote controls and phones that work regardless of users keymap.
>
do you think we could add a new KEY_KPPOUND to include/linux/input.h ?
I just had a quick look in latest git, but could not find anything..
do you want me to define a key and make a patch for you?
BTW, for now I have defined a work-around in my cm109 driver:
#ifndef KEY_KPPOUND
#define KEY_KPPOUND (KEY_LEFTSHIFT | KEY_3 << 8)
#endif
that hack was "copied" from drivers/input/misc/yealink.c
case 0x32: return KEY_LEFTSHIFT |
KEY_3 << 8; /* # */
/alfred
<snip>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-06-21 22:23 ` Alfred E. Heggestad
@ 2008-06-24 4:59 ` Dmitry Torokhov
2008-06-25 20:07 ` Alfred E. Heggestad
0 siblings, 1 reply; 11+ messages in thread
From: Dmitry Torokhov @ 2008-06-24 4:59 UTC (permalink / raw)
To: Alfred E. Heggestad; +Cc: linux-input, linux-kernel
Hi Alfred,
On Sun, Jun 22, 2008 at 12:23:33AM +0200, Alfred E. Heggestad wrote:
> Hi Dmitry
>
> Dmitry Torokhov wrote:
>> Hi Alfred,
>>
>> On Thu, Feb 07, 2008 at 07:38:10PM +0100, Alfred E. Heggestad wrote:
>>> From: Alfred E. Heggestad <aeh@db.org>
>>>
>>> This driver adds support for USB VoIP phones using the CM109 chipset,
>>> such as Komunikate KIP-1000 and Genius G-talk. Keypad is scanned and
>>> events are reported to the input subsystem. The buzzer can be activated
>>> by sending SND_TONE or SND_BELL to the input device. The phone keymap
>>> can be selected in run-time by using the "phone" module parameter.
>>> The driver has been tested with linux 2.6.24 on i386, and also tested
>>> to build cleanly on AMD64.
>>> More testing and code review is welcome..
>>>
>>
>> For a long time I was sitting on the patch not sure what to do about
>> the pound key, but I think we need to allocate separate keycodes for
>> remote controls and phones that work regardless of users keymap.
>>
>
> do you think we could add a new KEY_KPPOUND to include/linux/input.h ?
>
> I just had a quick look in latest git, but could not find anything..
> do you want me to define a key and make a patch for you?
>
I was pretty sure I have commited that patch a few weeks ago but I was
wrong. Anyway, please take a look at the master branch of my input tree
on git.kernel.org, it has a few new keycode definitions I woudl like us
to start using.
> BTW, for now I have defined a work-around in my cm109 driver:
>
> #ifndef KEY_KPPOUND
> #define KEY_KPPOUND (KEY_LEFTSHIFT | KEY_3 << 8)
> #endif
>
> that hack was "copied" from drivers/input/misc/yealink.c
>
> case 0x32: return KEY_LEFTSHIFT |
> KEY_3 << 8; /* # */
>
Yeah, well, sometimes we let suboptimal solutions sneak in... our fault.
--
Dmitry
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-06-24 4:59 ` Dmitry Torokhov
@ 2008-06-25 20:07 ` Alfred E. Heggestad
2008-06-26 10:31 ` Oliver Neukum
0 siblings, 1 reply; 11+ messages in thread
From: Alfred E. Heggestad @ 2008-06-25 20:07 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel
Hi,
see inline.
Dmitry Torokhov wrote:
> Hi Alfred,
>
> On Sun, Jun 22, 2008 at 12:23:33AM +0200, Alfred E. Heggestad wrote:
>> Hi Dmitry
>>
>> Dmitry Torokhov wrote:
>>> Hi Alfred,
>>>
>>> On Thu, Feb 07, 2008 at 07:38:10PM +0100, Alfred E. Heggestad wrote:
>>>> From: Alfred E. Heggestad <aeh@db.org>
>>>>
>>>> This driver adds support for USB VoIP phones using the CM109 chipset,
>>>> such as Komunikate KIP-1000 and Genius G-talk. Keypad is scanned and
>>>> events are reported to the input subsystem. The buzzer can be activated
>>>> by sending SND_TONE or SND_BELL to the input device. The phone keymap
>>>> can be selected in run-time by using the "phone" module parameter.
>>>> The driver has been tested with linux 2.6.24 on i386, and also tested
>>>> to build cleanly on AMD64.
>>>> More testing and code review is welcome..
>>>>
>>> For a long time I was sitting on the patch not sure what to do about
>>> the pound key, but I think we need to allocate separate keycodes for
>>> remote controls and phones that work regardless of users keymap.
>>>
>> do you think we could add a new KEY_KPPOUND to include/linux/input.h ?
>>
>> I just had a quick look in latest git, but could not find anything..
>> do you want me to define a key and make a patch for you?
>>
> I was pretty sure I have commited that patch a few weeks ago but I was
> wrong. Anyway, please take a look at the master branch of my input tree
> on git.kernel.org, it has a few new keycode definitions I woudl like us
> to start using.
>
many thanks, I am now using the new KEY_NUMERIC_POUND value
include/linux/input.h -- new patch inlined below for review.
one remaining issue which should be fixed before mainline
inclusion, is using proper keymaps. currently there are 3
phone models supported by the driver, and they all have
different keymaps, and must be selected when the module
is loaded. is it possible to use getkeycode/setkeycode
on struct input_dev for that purpose? do you know how/where
the various keymap files will be stored/packaged in
userspace? if you know any other driver doing similar
stuff, please let me know ..
/alfred
>> BTW, for now I have defined a work-around in my cm109 driver:
>>
>> #ifndef KEY_KPPOUND
>> #define KEY_KPPOUND (KEY_LEFTSHIFT | KEY_3 << 8)
>> #endif
>>
>> that hack was "copied" from drivers/input/misc/yealink.c
>>
>> case 0x32: return KEY_LEFTSHIFT |
>> KEY_3 << 8; /* # */
>>
>
> Yeah, well, sometimes we let suboptimal solutions sneak in... our fault.
>
diff -uprN -X linux-2.6.25/Documentation/dontdiff linux-2.6.25-orig/drivers/input/misc/cm109.c linux-2.6.25/drivers/input/misc/cm109.c
--- linux-2.6.25-orig/drivers/input/misc/cm109.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.25/drivers/input/misc/cm109.c 2008-06-25 21:41:30.000000000 +0200
@@ -0,0 +1,703 @@
+/*
+ * Driver for the VoIP USB phones with CM109 chipsets.
+ *
+ * Copyright (C) 2007 - 2008 Alfred E. Heggestad <aeh@db.org>
+ *
+ * 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.
+ */
+
+/*
+ * Tested devices:
+ * - Komunikate KIP1000
+ * - Genius G-talk
+ * - Allied-Telesis Corega USBPH01
+ * - ...
+ *
+ * This driver is based on the yealink.c driver
+ *
+ * Thanks to:
+ * - Authors of yealink.c
+ * - Thomas Reitmayr
+ * - Oliver Neukum for good review comments
+ * - Shaun Jackman <sjackman@gmail.com> for Genius G-talk keymap
+ *
+ * Todo:
+ * - Fix bug with buzz and urb_ctl_callback:usb_submit_urb() -EINVAL
+ * - Fix KEY_KPPOUND
+ * - Read/write EEPROM
+ * - Report input events volume up/down
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/rwsem.h>
+#include <linux/usb/input.h>
+
+#define CM109_DEBUG 0
+
+#define DRIVER_VERSION "20080625"
+#define DRIVER_AUTHOR "Alfred E. Heggestad"
+#define DRIVER_DESC "CM109 phone driver"
+
+static char *phone = "kip1000";
+module_param(phone, charp, S_IRUSR);
+MODULE_PARM_DESC(phone, "Phone name {kip1000, gtalk, usbph01}");
+
+
+enum {
+ /* HID Registers */
+ HID_IR0 = 0x00, /* Record/Playback-mute button, Volume up/down */
+ HID_IR1 = 0x01, /* GPI, generic registers or EEPROM_DATA0 */
+ HID_IR2 = 0x02, /* Generic registers or EEPROM_DATA1 */
+ HID_IR3 = 0x03, /* Generic registers or EEPROM_CTRL */
+ HID_OR0 = 0x00, /* Mapping control, buzzer, SPDIF (offset 0x04) */
+ HID_OR1 = 0x01, /* GPO - General Purpose Output */
+ HID_OR2 = 0x02, /* Set GPIO to input/output mode */
+ HID_OR3 = 0x03, /* SPDIF status channel or EEPROM_CTRL */
+
+ /* HID_IR0 */
+ RECORD_MUTE = 1 << 3,
+ PLAYBACK_MUTE = 1 << 2,
+ VOLUME_DOWN = 1 << 1,
+ VOLUME_UP = 1 << 0,
+
+ /* HID_OR0 */
+ /* bits 7-6
+ 0: HID_OR1-2 are used for GPO; HID_OR0, 3 are used for buzzer
+ and SPDIF
+ 1: HID_OR0-3 are used as generic HID registers
+ 2: Values written to HID_OR0-3 are also mapped to MCU_CTRL,
+ EEPROM_DATA0-1, EEPROM_CTRL (see Note)
+ 3: Reserved
+ */
+ HID_OR_GPO_BUZ_SPDIF = 0 << 6,
+ HID_OR_GENERIC_HID_REG = 1 << 6,
+ HID_OR_MAP_MCU_EEPROM = 2 << 6,
+
+ BUZZER_ON = 1 << 5,
+};
+
+/* CM109 protocol packet */
+struct cm109_ctl_packet {
+ u8 byte[4];
+} __attribute__ ((packed));
+
+enum {USB_PKT_LEN = sizeof(struct cm109_ctl_packet)};
+
+/* CM109 device structure */
+struct cm109_dev {
+ struct input_dev *idev; /* input device */
+ struct usb_device *udev; /* usb device */
+
+ /* irq input channel */
+ struct cm109_ctl_packet *irq_data;
+ dma_addr_t irq_dma;
+ struct urb *urb_irq;
+
+ /* control output channel */
+ struct cm109_ctl_packet *ctl_data;
+ dma_addr_t ctl_dma;
+ struct usb_ctrlrequest *ctl_req;
+ dma_addr_t ctl_req_dma;
+ struct urb *urb_ctl;
+
+ spinlock_t submit_lock;
+ int disconnecting;
+
+ char phys[64]; /* physical device path */
+ int key_code; /* last reported key */
+ int keybit; /* 0=new scan 1,2,4,8=scan columns */
+ u8 gpi; /* Cached value of GPI (high nibble) */
+};
+
+/******************************************************************************
+ * CM109 key interface
+ *****************************************************************************/
+
+/* TODO: remove this code when KEY_NUMERIC_POUND is defined in linux/input.h
+ *
+ * if KEY_NUMERIC_POUND is not defined, we define our own version which
+ * is a rather dirty hack.
+ */
+#ifndef KEY_NUMERIC_POUND
+#warning "using dirty hack for pound key"
+#define KEY_NUMERIC_POUND (KEY_LEFTSHIFT | KEY_3 << 8)
+#endif
+
+
+/* Map device buttons to internal key events.
+ *
+ * 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.
+
+ Komunikate KIP1000 Keyboard Matrix
+
+ -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10)
+ | | | |
+ <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20)
+ | | | |
+ END - 7 -- 8 -- 9 --> GPI pin 6 (0x40)
+ | | | |
+ OK -- * -- 0 -- # --> GPI pin 7 (0x80)
+ | | | |
+
+ /|\ /|\ /|\ /|\
+ | | | |
+GPO
+pin: 3 2 1 0
+ 0x8 0x4 0x2 0x1
+
+ */
+static int keymap_kip1000(int scancode)
+{
+ switch (scancode) { /* phone key: */
+ case 0x82: return KEY_0; /* 0 */
+ case 0x14: return KEY_1; /* 1 */
+ case 0x12: return KEY_2; /* 2 */
+ case 0x11: return KEY_3; /* 3 */
+ case 0x24: return KEY_4; /* 4 */
+ case 0x22: return KEY_5; /* 5 */
+ case 0x21: return KEY_6; /* 6 */
+ case 0x44: return KEY_7; /* 7 */
+ case 0x42: return KEY_8; /* 8 */
+ case 0x41: return KEY_9; /* 9 */
+ case 0x81: return KEY_NUMERIC_POUND; /* # */
+ case 0x84: return KEY_KPASTERISK; /* * */
+ case 0x88: return KEY_ENTER; /* pickup */
+ case 0x48: return KEY_ESC; /* hangup */
+ case 0x28: return KEY_LEFT; /* IN */
+ case 0x18: return KEY_RIGHT; /* OUT */
+ }
+ return -EINVAL;
+}
+
+/*
+ Contributed by Shaun Jackman <sjackman@gmail.com>
+
+ Genius G-Talk keyboard matrix
+ 0 1 2 3
+ 4: 0 4 8 Talk
+ 5: 1 5 9 End
+ 6: 2 6 # Up
+ 7: 3 7 * Down
+*/
+static int keymap_gtalk(int scancode)
+{
+ switch (scancode) {
+ case 0x11: return KEY_0;
+ case 0x21: return KEY_1;
+ case 0x41: return KEY_2;
+ case 0x81: return KEY_3;
+ case 0x12: return KEY_4;
+ case 0x22: return KEY_5;
+ case 0x42: return KEY_6;
+ case 0x82: return KEY_7;
+ case 0x14: return KEY_8;
+ case 0x24: return KEY_9;
+ case 0x44: return KEY_NUMERIC_POUND; /* # */
+ case 0x84: return KEY_KPASTERISK;
+ case 0x18: return KEY_ENTER; /* Talk (green handset) */
+ case 0x28: return KEY_ESC; /* End (red handset) */
+ case 0x48: return KEY_UP; /* Menu up (rocker switch) */
+ case 0x88: return KEY_DOWN; /* Menu down (rocker switch) */
+ }
+ return -EINVAL;
+}
+
+/*
+ * Keymap for Allied-Telesis Corega USBPH01
+ * http://www.alliedtelesis-corega.com/2/1344/1437/1360/chprd.html
+ *
+ * Contributed by july@nat.bg
+ */
+static int keymap_usbph01(int scancode)
+{
+ switch (scancode) {
+ case 0x11: return KEY_0; /* 0 */
+ case 0x21: return KEY_1; /* 1 */
+ case 0x41: return KEY_2; /* 2 */
+ case 0x81: return KEY_3; /* 3 */
+ case 0x12: return KEY_4; /* 4 */
+ case 0x22: return KEY_5; /* 5 */
+ case 0x42: return KEY_6; /* 6 */
+ case 0x82: return KEY_7; /* 7 */
+ case 0x14: return KEY_8; /* 8 */
+ case 0x24: return KEY_9; /* 9 */
+ case 0x44: return KEY_NUMERIC_POUND; /* # */
+ case 0x84: return KEY_KPASTERISK; /* * */
+ case 0x18: return KEY_ENTER; /* pickup */
+ case 0x28: return KEY_ESC; /* hangup */
+ case 0x48: return KEY_LEFT; /* IN */
+ case 0x88: return KEY_RIGHT; /* OUT */
+ }
+ return -EINVAL;
+}
+
+static int (*keymap)(int) = keymap_kip1000;
+
+
+/* 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 cm109_dev *dev, int key)
+{
+ struct input_dev *idev = dev->idev;
+
+ if (dev->key_code >= 0) {
+ /* old key up */
+ input_report_key(idev, dev->key_code & 0xff, 0);
+ if (dev->key_code >> 8)
+ input_report_key(idev, dev->key_code >> 8, 0);
+ }
+
+ dev->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);
+}
+
+/******************************************************************************
+ * CM109 usb communication interface
+ *****************************************************************************/
+
+
+/*
+ * IRQ handler
+ */
+static void urb_irq_callback(struct urb *urb)
+{
+ struct cm109_dev *dev = urb->context;
+ int ret;
+
+#if CM109_DEBUG
+ info("### URB IRQ: [0x%02x 0x%02x 0x%02x 0x%02x] keybit=0x%02x",
+ dev->irq_data->byte[0],
+ dev->irq_data->byte[1],
+ dev->irq_data->byte[2],
+ dev->irq_data->byte[3],
+ dev->keybit);
+#endif
+
+ if (urb->status) {
+ if (-ESHUTDOWN == urb->status)
+ return;
+ err("%s - urb status %d", __FUNCTION__, urb->status);
+ }
+
+ /* Scan key column */
+ if (0xf == dev->keybit) {
+
+ /* Any changes ? */
+ if ((dev->gpi & 0xf0) == (dev->irq_data->byte[HID_IR1] & 0xf0)) {
+ goto out;
+ }
+
+ dev->gpi = dev->irq_data->byte[HID_IR1] & 0xf0;
+
+ dev->keybit = 0x1;
+ } else {
+ report_key(dev, keymap(dev->irq_data->byte[HID_IR1]));
+
+ dev->keybit <<= 1;
+ if (dev->keybit > 0x8)
+ dev->keybit = 0xf;
+ }
+
+ dev->ctl_data->byte[HID_OR1] = dev->keybit;
+ dev->ctl_data->byte[HID_OR2] = dev->keybit;
+
+ out:
+ ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
+ if (ret)
+ err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+}
+
+static void urb_ctl_callback(struct urb *urb)
+{
+ struct cm109_dev *dev = urb->context;
+ int ret = 0;
+
+#if CM109_DEBUG
+ info("### URB CTL: [0x%02x 0x%02x 0x%02x 0x%02x]",
+ dev->ctl_data->byte[0],
+ dev->ctl_data->byte[1],
+ dev->ctl_data->byte[2],
+ dev->ctl_data->byte[3]);
+#endif
+
+ if (urb->status)
+ err("%s - urb status %d", __FUNCTION__, urb->status);
+
+ spin_lock(&dev->submit_lock);
+ /* ask for a response */
+ if (!dev->disconnecting)
+ ret = usb_submit_urb(dev->urb_irq, GFP_ATOMIC);
+ spin_unlock(&dev->submit_lock);
+
+ if (ret)
+ err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+}
+
+/******************************************************************************
+ * input event interface
+ *****************************************************************************/
+
+static DEFINE_SPINLOCK(cm109_buzz_lock);
+
+static int input_open(struct input_dev *idev)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+ int ret;
+
+ dev->key_code = -1; /* no keys pressed */
+ dev->keybit = 0xf;
+
+ /* issue INIT */
+ dev->ctl_data->byte[HID_OR0] = HID_OR_GPO_BUZ_SPDIF;
+ dev->ctl_data->byte[HID_OR1] = dev->keybit;
+ dev->ctl_data->byte[HID_OR2] = dev->keybit;
+ dev->ctl_data->byte[HID_OR3] = 0x00;
+
+ if ((ret = usb_submit_urb(dev->urb_ctl, GFP_KERNEL)) != 0) {
+ err("%s - usb_submit_urb failed with result %d",
+ __FUNCTION__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void input_close(struct input_dev *idev)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+
+ usb_kill_urb(dev->urb_ctl);
+ usb_kill_urb(dev->urb_irq);
+}
+
+static void buzz(struct cm109_dev *dev, int on)
+{
+ int ret;
+
+ if (dev == NULL) {
+ err("buzz: dev is NULL");
+ return;
+ }
+
+ dbg("Buzzer %s", on ? "on" : "off");
+ if (on)
+ dev->ctl_data->byte[HID_OR0] |= BUZZER_ON;
+ else
+ dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON;
+
+ ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
+ if (ret)
+ err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+}
+
+static int input_ev(struct input_dev *idev, unsigned int type,
+ unsigned int code, int value)
+{
+ struct cm109_dev *dev = input_get_drvdata(idev);
+ unsigned long flags;
+
+#if CM109_DEBUG
+ info("input_ev: type=%u code=%u value=%d", type, code, value);
+#endif
+
+ if (type != EV_SND)
+ return -EINVAL;
+
+ switch (code) {
+ case SND_TONE:
+ case SND_BELL:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&cm109_buzz_lock, flags);
+ buzz(dev, value);
+ spin_unlock_irqrestore(&cm109_buzz_lock, flags);
+
+ return 0;
+}
+
+
+/******************************************************************************
+ * Linux interface and usb initialisation
+ *****************************************************************************/
+
+struct driver_info {
+ char *name;
+};
+
+static const struct driver_info info_cm109 = {
+ .name = "CM109 USB driver",
+};
+
+enum {
+ VENDOR_ID = 0x0d8c, /* C-Media Electronics */
+ PRODUCT_ID_CM109 = 0x000e, /* CM109 defines range 0x0008 - 0x000f */
+};
+
+/* table of devices that work with this driver */
+static const struct usb_device_id usb_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = VENDOR_ID,
+ .idProduct = PRODUCT_ID_CM109,
+ .bInterfaceClass = USB_CLASS_HID,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t) & info_cm109},
+ /* you can add more devices here with product ID 0x0008 - 0x000f */
+ {}
+};
+
+static int usb_cleanup(struct cm109_dev *dev, int err)
+{
+ if (dev == NULL)
+ return err;
+
+ usb_kill_urb(dev->urb_irq); /* parameter validation in core/urb */
+ usb_kill_urb(dev->urb_ctl); /* parameter validation in core/urb */
+
+ if (dev->idev) {
+ if (err)
+ input_free_device(dev->idev);
+ else
+ input_unregister_device(dev->idev);
+ }
+ if (dev->ctl_req)
+ usb_buffer_free(dev->udev, sizeof(*(dev->ctl_req)),
+ dev->ctl_req, dev->ctl_req_dma);
+ if (dev->ctl_data)
+ usb_buffer_free(dev->udev, USB_PKT_LEN,
+ dev->ctl_data, dev->ctl_dma);
+ if (dev->irq_data)
+ usb_buffer_free(dev->udev, USB_PKT_LEN,
+ dev->irq_data, dev->irq_dma);
+
+ usb_free_urb(dev->urb_irq); /* parameter validation in core/urb */
+ usb_free_urb(dev->urb_ctl); /* parameter validation in core/urb */
+ kfree(dev);
+
+ return err;
+}
+
+static void usb_disconnect(struct usb_interface *interface)
+{
+ struct cm109_dev *dev;
+
+ dev = usb_get_intfdata(interface);
+
+ /* Wait for URB idle */
+ spin_lock_irq(&dev->submit_lock);
+ dev->disconnecting = 1;
+ spin_unlock_irq(&dev->submit_lock);
+
+ usb_set_intfdata(interface, NULL);
+
+ usb_cleanup(dev, 0);
+}
+
+static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct driver_info *nfo = (struct driver_info *)id->driver_info;
+ struct usb_host_interface *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct cm109_dev *dev;
+ struct input_dev *input_dev;
+ int ret, pipe, i;
+
+ interface = intf->cur_altsetting;
+ endpoint = &interface->endpoint[0].desc;
+
+ if (!usb_endpoint_is_int_in(endpoint))
+ return -ENODEV;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->submit_lock);
+ dev->disconnecting = 0;
+
+ dev->udev = udev;
+
+ dev->idev = input_dev = input_allocate_device();
+ if (!input_dev)
+ goto err;
+
+ /* allocate usb buffers */
+ dev->irq_data = usb_buffer_alloc(udev, USB_PKT_LEN,
+ GFP_KERNEL, &dev->irq_dma);
+ if (dev->irq_data == NULL)
+ goto err;
+
+ dev->ctl_data = usb_buffer_alloc(udev, USB_PKT_LEN,
+ GFP_KERNEL, &dev->ctl_dma);
+ if (!dev->ctl_data)
+ goto err;
+
+ dev->ctl_req = usb_buffer_alloc(udev, sizeof(*(dev->ctl_req)),
+ GFP_KERNEL, &dev->ctl_req_dma);
+ if (dev->ctl_req == NULL)
+ goto err;
+
+ /* allocate urb structures */
+ dev->urb_irq = usb_alloc_urb(0, GFP_KERNEL);
+ if (dev->urb_irq == NULL)
+ goto err;
+
+ dev->urb_ctl = usb_alloc_urb(0, GFP_KERNEL);
+ if (dev->urb_ctl == NULL)
+ goto err;
+
+ /* 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(dev->urb_irq, udev, pipe, dev->irq_data,
+ USB_PKT_LEN,
+ urb_irq_callback, dev, endpoint->bInterval);
+ dev->urb_irq->transfer_dma = dev->irq_dma;
+ dev->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ dev->urb_irq->dev = udev;
+
+ /* initialise ctl urb */
+ dev->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE |
+ USB_DIR_OUT;
+ dev->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION;
+ dev->ctl_req->wValue = cpu_to_le16(0x200);
+ dev->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
+ dev->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN);
+
+ usb_fill_control_urb(dev->urb_ctl, udev, usb_sndctrlpipe(udev, 0),
+ (void *)dev->ctl_req, dev->ctl_data, USB_PKT_LEN,
+ urb_ctl_callback, dev);
+ dev->urb_ctl->setup_dma = dev->ctl_req_dma;
+ dev->urb_ctl->transfer_dma = dev->ctl_dma;
+ dev->urb_ctl->transfer_flags |= URB_NO_SETUP_DMA_MAP |
+ URB_NO_TRANSFER_DMA_MAP;
+ dev->urb_ctl->dev = udev;
+
+ /* find out the physical bus location */
+ usb_make_path(udev, dev->phys, sizeof(dev->phys));
+ strlcat(dev->phys, "/input0", sizeof(dev->phys));
+
+ /* register settings for the input device */
+ input_dev->name = nfo->name;
+ input_dev->phys = dev->phys;
+ usb_to_input_id(udev, &input_dev->id);
+ input_dev->dev.parent = &intf->dev;
+
+ input_set_drvdata(input_dev, dev);
+ input_dev->open = input_open;
+ input_dev->close = input_close;
+ input_dev->event = input_ev;
+
+ /* register available key events */
+ input_dev->evbit[0] = BIT_MASK(EV_KEY);
+ for (i = 0; i < 256; i++) {
+ int k = keymap(i);
+ if (k >= 0) {
+ set_bit(k & 0xff, input_dev->keybit);
+ if (k >> 8)
+ set_bit(k >> 8, input_dev->keybit);
+ }
+ }
+
+ input_dev->evbit[0] |= BIT_MASK(EV_SND);
+ input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+
+ ret = input_register_device(dev->idev);
+ if (ret)
+ goto err;
+
+ usb_set_intfdata(intf, dev);
+
+ return 0;
+
+ err:
+ return usb_cleanup(dev, -ENOMEM);
+}
+
+static struct usb_driver cm109_driver = {
+ .name = "cm109",
+ .probe = usb_probe,
+ .disconnect = usb_disconnect,
+ .id_table = usb_table,
+};
+
+static int __init select_keymap(void)
+{
+ /* Load the phone keymap */
+ if (0 == strcasecmp(phone, "kip1000")) {
+ keymap = keymap_kip1000;
+ info("Keymap for Komunikate KIP1000 phone loaded");
+ }
+ else if (0 == strcasecmp(phone, "gtalk")) {
+ keymap = keymap_gtalk;
+ info("Keymap for Genius G-talk phone loaded");
+ }
+ else if (0 == strcasecmp(phone, "usbph01")) {
+ keymap = keymap_usbph01;
+ info("Keymap for Allied-Telesis Corega USBPH01 phone loaded");
+ }
+ else {
+ err("Unsupported phone: %s", phone);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __init cm109_dev_init(void)
+{
+ int err;
+
+ err = select_keymap();
+ if (err)
+ return err;
+
+ err = usb_register(&cm109_driver);
+ if (err)
+ return err;
+
+ info(DRIVER_DESC ": " DRIVER_VERSION " (C) " DRIVER_AUTHOR);
+
+ return err;
+}
+
+static void __exit cm109_dev_exit(void)
+{
+ usb_deregister(&cm109_driver);
+}
+
+module_init(cm109_dev_init);
+module_exit(cm109_dev_exit);
+
+MODULE_DEVICE_TABLE(usb, usb_table);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff -uprN -X linux-2.6.25/Documentation/dontdiff linux-2.6.25-orig/drivers/input/misc/Kconfig linux-2.6.25/drivers/input/misc/Kconfig
--- linux-2.6.25-orig/drivers/input/misc/Kconfig 2008-04-17 04:49:44.000000000 +0200
+++ linux-2.6.25/drivers/input/misc/Kconfig 2008-06-21 23:11:48.000000000 +0200
@@ -180,6 +180,18 @@ config INPUT_YEALINK
To compile this driver as a module, choose M here: the module will be
called yealink.
+config INPUT_CM109
+ tristate "C-Media CM109 USB I/O Controller"
+ depends on INPUT && EXPERIMENTAL
+ select USB
+ ---help---
+ Say Y here if you want to enable keyboard and buzzer functions of the
+ C-Media CM109 usb phones. The audio part is enabled by the generic
+ usb sound driver, so you might want to enable that as well.
+
+ To compile this driver as a module, choose M here: the module will be
+ called cm109.
+
config INPUT_UINPUT
tristate "User level driver support"
help
diff -uprN -X linux-2.6.25/Documentation/dontdiff linux-2.6.25-orig/drivers/input/misc/Makefile linux-2.6.25/drivers/input/misc/Makefile
--- linux-2.6.25-orig/drivers/input/misc/Makefile 2008-04-17 04:49:44.000000000 +0200
+++ linux-2.6.25/drivers/input/misc/Makefile 2008-06-21 23:11:48.000000000 +0200
@@ -16,6 +16,7 @@ obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+obj-$(CONFIG_INPUT_CM109) += cm109.o
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_UINPUT) += uinput.o
obj-$(CONFIG_INPUT_APANEL) += apanel.o
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] input: driver for USB VoIP phones with CM109 chipset
2008-06-25 20:07 ` Alfred E. Heggestad
@ 2008-06-26 10:31 ` Oliver Neukum
0 siblings, 0 replies; 11+ messages in thread
From: Oliver Neukum @ 2008-06-26 10:31 UTC (permalink / raw)
To: Alfred E. Heggestad; +Cc: Dmitry Torokhov, linux-input, linux-kernel
Am Mittwoch 25 Juni 2008 22:07:34 schrieb Alfred E. Heggestad:
Very well
- urb->status is about to go away and replaced by a parameter
- reliably kill two URBs submitting each other cannot be done with
usb_kill_urb() alone
- you close the device but leave the buzzer on
- input_ev() races with disconnect
- no support for suspend/resume
- no support for pre/post_reset
Could you test this additional patch?
Regards
Oliver
--- linux-2.6.26-sierra/drivers/input/misc/cm109.alt.c 2008-06-26 08:15:16.000000000 +0200
+++ linux-2.6.26-sierra/drivers/input/misc/cm109.c 2008-06-26 12:02:54.000000000 +0200
@@ -93,6 +93,7 @@ enum {USB_PKT_LEN = sizeof(struct cm109_
struct cm109_dev {
struct input_dev *idev; /* input device */
struct usb_device *udev; /* usb device */
+ struct usb_interface *intf;
/* irq input channel */
struct cm109_ctl_packet *irq_data;
@@ -107,7 +108,12 @@ struct cm109_dev {
struct urb *urb_ctl;
spinlock_t submit_lock;
- int disconnecting;
+ char disconnecting:1;
+ char shutting_down:1;
+ char buzz_state:1;
+ char open:1;
+ char resetting:1;
+ wait_queue_head_t wait;
char phys[64]; /* physical device path */
int key_code; /* last reported key */
@@ -115,6 +121,9 @@ struct cm109_dev {
u8 gpi; /* Cached value of GPI (high nibble) */
};
+static DEFINE_MUTEX(close_disc_mutex);
+static int buzz(struct cm109_dev *dev, int on);
+
/******************************************************************************
* CM109 key interface
*****************************************************************************/
@@ -279,7 +288,7 @@ static void report_key(struct cm109_dev
static void urb_irq_callback(struct urb *urb)
{
struct cm109_dev *dev = urb->context;
- int ret;
+ int ret, status = urb->status;
#if CM109_DEBUG
info("### URB IRQ: [0x%02x 0x%02x 0x%02x 0x%02x] keybit=0x%02x",
@@ -290,10 +299,10 @@ static void urb_irq_callback(struct urb
dev->keybit);
#endif
- if (urb->status) {
- if (-ESHUTDOWN == urb->status)
+ if (status) {
+ if (-ESHUTDOWN == status)
return;
- err("%s - urb status %d", __FUNCTION__, urb->status);
+ err("%s - urb status %d", __func__, status);
}
/* Scan key column */
@@ -319,15 +328,19 @@ static void urb_irq_callback(struct urb
dev->ctl_data->byte[HID_OR2] = dev->keybit;
out:
- ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
- if (ret)
- err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+ spin_lock(&dev->submit_lock);
+ if (!dev->shutting_down) {
+ ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
+ if (ret)
+ err("%s - usb_submit_urb failed %d", __func__, ret);
+ }
+ spin_unlock(&dev->submit_lock);
}
static void urb_ctl_callback(struct urb *urb)
{
struct cm109_dev *dev = urb->context;
- int ret = 0;
+ int ret = 0, status = urb->status;
#if CM109_DEBUG
info("### URB CTL: [0x%02x 0x%02x 0x%02x 0x%02x]",
@@ -337,24 +350,37 @@ static void urb_ctl_callback(struct urb
dev->ctl_data->byte[3]);
#endif
- if (urb->status)
- err("%s - urb status %d", __FUNCTION__, urb->status);
+ if (status)
+ err("%s - urb status %d", __func__, status);
spin_lock(&dev->submit_lock);
/* ask for a response */
- if (!dev->disconnecting)
+ if (!dev->shutting_down)
ret = usb_submit_urb(dev->urb_irq, GFP_ATOMIC);
spin_unlock(&dev->submit_lock);
if (ret)
err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+ wake_up(&dev->wait);
}
/******************************************************************************
* input event interface
*****************************************************************************/
-static DEFINE_SPINLOCK(cm109_buzz_lock);
+static void stop_traffic(struct cm109_dev *dev)
+{
+ spin_lock_irq(&dev->submit_lock);
+ dev->shutting_down = 1;
+ spin_unlock_irq(&dev->submit_lock);
+
+ usb_kill_urb(dev->urb_ctl);
+ usb_kill_urb(dev->urb_irq);
+
+ spin_lock_irq(&dev->submit_lock);
+ dev->shutting_down = 0;
+ spin_unlock_irq(&dev->submit_lock);
+}
static int input_open(struct input_dev *idev)
{
@@ -370,41 +396,81 @@ static int input_open(struct input_dev *
dev->ctl_data->byte[HID_OR2] = dev->keybit;
dev->ctl_data->byte[HID_OR3] = 0x00;
+ ret = usb_autopm_get_interface(dev->intf);
+ if (ret < 0) {
+ err("%s - cannot autoresume, result %d",
+ __func__, ret);
+ return ret;
+ }
+
+ mutex_lock(&close_disc_mutex);
if ((ret = usb_submit_urb(dev->urb_ctl, GFP_KERNEL)) != 0) {
err("%s - usb_submit_urb failed with result %d",
- __FUNCTION__, ret);
+ __func__, ret);
+ usb_autopm_put_interface(dev->intf);
+ mutex_unlock(&close_disc_mutex);
return ret;
}
+ dev->open = 1;
+ mutex_unlock(&close_disc_mutex);
+
return 0;
}
static void input_close(struct input_dev *idev)
{
struct cm109_dev *dev = input_get_drvdata(idev);
+ int traffic = 0;
+ int r;
- usb_kill_urb(dev->urb_ctl);
- usb_kill_urb(dev->urb_irq);
+ stop_traffic(dev);
+ dev->open = 0;
+ spin_lock_irq(&dev->submit_lock);
+ if (!dev->disconnecting && dev->buzz_state) {
+ r = buzz(dev, 0);
+ spin_unlock_irq(&dev->submit_lock);
+ if (!r) {
+ wait_event(dev->wait, !dev->buzz_state);
+ traffic = 1;
+ }
+ } else {
+ spin_unlock_irq(&dev->submit_lock);
+ }
+ if (traffic)
+ stop_traffic(dev);
+
+ mutex_lock(&close_disc_mutex);
+ if (!dev->disconnecting)
+ usb_autopm_put_interface(dev->intf);
+ mutex_unlock(&close_disc_mutex);
}
-static void buzz(struct cm109_dev *dev, int on)
+static int buzz(struct cm109_dev *dev, int on)
{
- int ret;
+ int ret = 0;
if (dev == NULL) {
err("buzz: dev is NULL");
- return;
+ return -EINVAL;
}
dbg("Buzzer %s", on ? "on" : "off");
+ if (dev->resetting)
+ goto skip_io;
if (on)
dev->ctl_data->byte[HID_OR0] |= BUZZER_ON;
else
dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON;
ret = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC);
- if (ret)
- err("%s - usb_submit_urb failed %d", __FUNCTION__, ret);
+ if (ret) {
+ err("%s - usb_submit_urb failed %d", __func__, ret);
+ } else {
+skip_io:
+ dev->buzz_state = on ? 1 : 0;
+ }
+ return ret;
}
static int input_ev(struct input_dev *idev, unsigned int type,
@@ -412,6 +478,7 @@ static int input_ev(struct input_dev *id
{
struct cm109_dev *dev = input_get_drvdata(idev);
unsigned long flags;
+ int rv = 0;
#if CM109_DEBUG
info("input_ev: type=%u code=%u value=%d", type, code, value);
@@ -428,11 +495,12 @@ static int input_ev(struct input_dev *id
return -EINVAL;
}
- spin_lock_irqsave(&cm109_buzz_lock, flags);
- buzz(dev, value);
- spin_unlock_irqrestore(&cm109_buzz_lock, flags);
+ spin_lock_irqsave(&dev->submit_lock, flags);
+ if (!dev->disconnecting)
+ rv = buzz(dev, value);
+ spin_unlock_irqrestore(&dev->submit_lock, flags);
- return 0;
+ return rv;
}
@@ -468,13 +536,12 @@ static const struct usb_device_id usb_ta
{}
};
-static int usb_cleanup(struct cm109_dev *dev, int err)
+static void usb_cleanup(struct cm109_dev *dev, int err)
{
if (dev == NULL)
- return err;
+ return;
- usb_kill_urb(dev->urb_irq); /* parameter validation in core/urb */
- usb_kill_urb(dev->urb_ctl); /* parameter validation in core/urb */
+ stop_traffic(dev);
if (dev->idev) {
if (err)
@@ -495,8 +562,6 @@ static int usb_cleanup(struct cm109_dev
usb_free_urb(dev->urb_irq); /* parameter validation in core/urb */
usb_free_urb(dev->urb_ctl); /* parameter validation in core/urb */
kfree(dev);
-
- return err;
}
static void usb_disconnect(struct usb_interface *interface)
@@ -506,9 +571,12 @@ static void usb_disconnect(struct usb_in
dev = usb_get_intfdata(interface);
/* Wait for URB idle */
+
+ mutex_lock(&close_disc_mutex);
spin_lock_irq(&dev->submit_lock);
dev->disconnecting = 1;
spin_unlock_irq(&dev->submit_lock);
+ mutex_unlock(&close_disc_mutex);
usb_set_intfdata(interface, NULL);
@@ -536,9 +604,9 @@ static int usb_probe(struct usb_interfac
return -ENOMEM;
spin_lock_init(&dev->submit_lock);
- dev->disconnecting = 0;
dev->udev = udev;
+ dev->intf = intf;
dev->idev = input_dev = input_allocate_device();
if (!input_dev)
@@ -638,14 +706,81 @@ static int usb_probe(struct usb_interfac
return 0;
err:
- return usb_cleanup(dev, -ENOMEM);
+ usb_cleanup(dev, 1);
+ return -ENOMEM;
+}
+
+static int restore_state(struct cm109_dev *dev)
+{
+ int rv;
+
+ spin_lock_irq(&dev->submit_lock);
+ /* if not open, just restore buzz, else submit urb */
+ dev->shutting_down = dev->open;
+ spin_unlock_irq(&dev->submit_lock);
+ rv = buzz(dev, dev->buzz_state);
+ spin_lock_irq(&dev->submit_lock);
+ dev->shutting_down = 0;
+ spin_unlock_irq(&dev->submit_lock);
+
+ return rv;
+}
+
+static int usb_resume(struct usb_interface *intf)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+ int rv;
+
+ rv = restore_state(dev);
+
+ return rv;
+}
+
+static int usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+
+ stop_traffic(dev);
+ return 0;
+}
+
+static int usb_pre_reset(struct usb_interface *intf)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+
+ mutex_lock(&close_disc_mutex);
+ spin_lock_irq(&dev->submit_lock);
+ dev->resetting = 1;
+ spin_unlock_irq(&dev->submit_lock);
+ stop_traffic(dev);
+
+ return 0;
+}
+
+static int usb_post_reset(struct usb_interface *intf)
+{
+ struct cm109_dev *dev = usb_get_intfdata(intf);
+ int rv;
+
+ spin_lock_irq(&dev->submit_lock);
+ dev->resetting = 0;
+ spin_unlock_irq(&dev->submit_lock);
+ rv = restore_state(dev);
+ mutex_unlock(&close_disc_mutex);
+ return rv;
}
static struct usb_driver cm109_driver = {
- .name = "cm109",
- .probe = usb_probe,
- .disconnect = usb_disconnect,
- .id_table = usb_table,
+ .name = "cm109",
+ .probe = usb_probe,
+ .disconnect = usb_disconnect,
+ .resume = usb_resume,
+ .reset_resume = usb_resume,
+ .suspend = usb_suspend,
+ .pre_reset = usb_pre_reset,
+ .post_reset = usb_post_reset,
+ .id_table = usb_table,
+ .supports_autosuspend = 1,
};
static int __init select_keymap(void)
@@ -685,7 +820,7 @@ static int __init cm109_dev_init(void)
info(DRIVER_DESC ": " DRIVER_VERSION " (C) " DRIVER_AUTHOR);
- return err;
+ return 0;
}
static void __exit cm109_dev_exit(void)
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2008-06-26 10:31 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-02-07 18:38 [PATCH] input: driver for USB VoIP phones with CM109 chipset Alfred E. Heggestad
2008-02-07 21:59 ` Dmitry Torokhov
2008-02-08 21:23 ` Alfred E. Heggestad
2008-06-21 22:23 ` Alfred E. Heggestad
2008-06-24 4:59 ` Dmitry Torokhov
2008-06-25 20:07 ` Alfred E. Heggestad
2008-06-26 10:31 ` Oliver Neukum
2008-02-09 19:12 ` Pavel Machek
2008-03-03 22:07 ` Alfred E. Heggestad
2008-03-03 22:13 ` Pavel Machek
[not found] ` <200802091023.02580.oliver@neukum.org>
2008-03-03 22:10 ` Alfred E. Heggestad
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).