blob: d400afdaf2b32f37bf8b289a6c1ac35ce1ee973a [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Linus Torvalds1da177e2005-04-16 15:20:36 -07002 * Copyright (c) 1999-2001 Vojtech Pavlik
3 *
4 * USB HIDBP Keyboard support
5 */
6
7/*
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -050010 * the Free Software Foundation; either version 2 of the License, or
Linus Torvalds1da177e2005-04-16 15:20:36 -070011 * (at your option) any later version.
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -050012 *
Linus Torvalds1da177e2005-04-16 15:20:36 -070013 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -050017 *
Linus Torvalds1da177e2005-04-16 15:20:36 -070018 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -050021 *
Linus Torvalds1da177e2005-04-16 15:20:36 -070022 * Should you need to contact me, the author, you can do so either by
23 * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
24 * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
25 */
26
27#include <linux/kernel.h>
28#include <linux/slab.h>
29#include <linux/module.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070030#include <linux/init.h>
David Brownellae0dadc2006-06-13 10:04:34 -070031#include <linux/usb/input.h>
Michael Opdenacker4ef2e232007-02-21 22:51:25 +010032#include <linux/hid.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070033
34/*
35 * Version Information
36 */
37#define DRIVER_VERSION ""
38#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>"
39#define DRIVER_DESC "USB HID Boot Protocol keyboard driver"
40#define DRIVER_LICENSE "GPL"
41
42MODULE_AUTHOR(DRIVER_AUTHOR);
43MODULE_DESCRIPTION(DRIVER_DESC);
44MODULE_LICENSE(DRIVER_LICENSE);
45
Ming Leia44ebcc2008-06-08 16:15:16 +080046static const unsigned char usb_kbd_keycode[256] = {
Linus Torvalds1da177e2005-04-16 15:20:36 -070047 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
48 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3,
49 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26,
50 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
51 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
52 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
53 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
54 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
55 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0,
56 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
58 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
59 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
60 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
61 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
62 150,158,159,128,136,177,178,176,142,152,173,140
63};
64
65struct usb_kbd {
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -050066 struct input_dev *dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -070067 struct usb_device *usbdev;
68 unsigned char old[8];
69 struct urb *irq, *led;
70 unsigned char newleds;
71 char name[128];
72 char phys[64];
Linus Torvalds1da177e2005-04-16 15:20:36 -070073
74 unsigned char *new;
75 struct usb_ctrlrequest *cr;
76 unsigned char *leds;
77 dma_addr_t cr_dma;
78 dma_addr_t new_dma;
79 dma_addr_t leds_dma;
80};
81
David Howells7d12e782006-10-05 14:55:46 +010082static void usb_kbd_irq(struct urb *urb)
Linus Torvalds1da177e2005-04-16 15:20:36 -070083{
84 struct usb_kbd *kbd = urb->context;
85 int i;
86
87 switch (urb->status) {
88 case 0: /* success */
89 break;
90 case -ECONNRESET: /* unlink */
91 case -ENOENT:
92 case -ESHUTDOWN:
93 return;
94 /* -EPIPE: should clear the halt */
95 default: /* error */
96 goto resubmit;
97 }
98
Linus Torvalds1da177e2005-04-16 15:20:36 -070099 for (i = 0; i < 8; i++)
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500100 input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700101
102 for (i = 2; i < 8; i++) {
103
104 if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
105 if (usb_kbd_keycode[kbd->old[i]])
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500106 input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700107 else
Greg Kroah-Hartmanddbe3242008-10-12 00:14:23 +0200108 dev_info(&urb->dev->dev,
109 "Unknown key (scancode %#x) released.\n", kbd->old[i]);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700110 }
111
112 if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
113 if (usb_kbd_keycode[kbd->new[i]])
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500114 input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115 else
Greg Kroah-Hartmanddbe3242008-10-12 00:14:23 +0200116 dev_info(&urb->dev->dev,
117 "Unknown key (scancode %#x) released.\n", kbd->new[i]);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700118 }
119 }
120
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500121 input_sync(kbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700122
123 memcpy(kbd->old, kbd->new, 8);
124
125resubmit:
Christoph Lameter54e6ecb2006-12-06 20:33:16 -0800126 i = usb_submit_urb (urb, GFP_ATOMIC);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127 if (i)
Jiri Kosina58037eb2007-05-30 15:07:13 +0200128 err_hid ("can't resubmit intr, %s-%s/input0, status %d",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700129 kbd->usbdev->bus->bus_name,
130 kbd->usbdev->devpath, i);
131}
132
Adrian Bunkbe5e33832005-04-22 15:07:00 -0700133static int usb_kbd_event(struct input_dev *dev, unsigned int type,
134 unsigned int code, int value)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700135{
Dmitry Torokhove0712982007-05-09 10:17:31 +0200136 struct usb_kbd *kbd = input_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700137
138 if (type != EV_LED)
139 return -1;
140
Linus Torvalds1da177e2005-04-16 15:20:36 -0700141 kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
142 (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) |
143 (!!test_bit(LED_NUML, dev->led));
144
145 if (kbd->led->status == -EINPROGRESS)
146 return 0;
147
148 if (*(kbd->leds) == kbd->newleds)
149 return 0;
150
151 *(kbd->leds) = kbd->newleds;
152 kbd->led->dev = kbd->usbdev;
153 if (usb_submit_urb(kbd->led, GFP_ATOMIC))
Jiri Kosina58037eb2007-05-30 15:07:13 +0200154 err_hid("usb_submit_urb(leds) failed");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700155
156 return 0;
157}
158
David Howells7d12e782006-10-05 14:55:46 +0100159static void usb_kbd_led(struct urb *urb)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700160{
161 struct usb_kbd *kbd = urb->context;
162
163 if (urb->status)
164 warn("led urb status %d received", urb->status);
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -0500165
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166 if (*(kbd->leds) == kbd->newleds)
167 return;
168
169 *(kbd->leds) = kbd->newleds;
170 kbd->led->dev = kbd->usbdev;
171 if (usb_submit_urb(kbd->led, GFP_ATOMIC))
Jiri Kosina58037eb2007-05-30 15:07:13 +0200172 err_hid("usb_submit_urb(leds) failed");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700173}
174
175static int usb_kbd_open(struct input_dev *dev)
176{
Dmitry Torokhove0712982007-05-09 10:17:31 +0200177 struct usb_kbd *kbd = input_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700178
Linus Torvalds1da177e2005-04-16 15:20:36 -0700179 kbd->irq->dev = kbd->usbdev;
Dmitry Torokhov65cde542005-05-29 02:29:38 -0500180 if (usb_submit_urb(kbd->irq, GFP_KERNEL))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700181 return -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700182
183 return 0;
184}
185
186static void usb_kbd_close(struct input_dev *dev)
187{
Dmitry Torokhove0712982007-05-09 10:17:31 +0200188 struct usb_kbd *kbd = input_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700189
Dmitry Torokhov65cde542005-05-29 02:29:38 -0500190 usb_kill_urb(kbd->irq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191}
192
193static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)
194{
195 if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))
196 return -1;
197 if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))
198 return -1;
Christoph Lameter54e6ecb2006-12-06 20:33:16 -0800199 if (!(kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700200 return -1;
Christoph Lameter54e6ecb2006-12-06 20:33:16 -0800201 if (!(kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700202 return -1;
Christoph Lameter54e6ecb2006-12-06 20:33:16 -0800203 if (!(kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700204 return -1;
205
206 return 0;
207}
208
209static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd)
210{
Mariusz Kozlowski4ba0b2e2006-11-08 15:35:58 +0100211 usb_free_urb(kbd->irq);
212 usb_free_urb(kbd->led);
Dmitry Torokhov6675c5b2007-05-03 01:04:52 -0400213 usb_buffer_free(dev, 8, kbd->new, kbd->new_dma);
214 usb_buffer_free(dev, sizeof(struct usb_ctrlrequest), kbd->cr, kbd->cr_dma);
215 usb_buffer_free(dev, 1, kbd->leds, kbd->leds_dma);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700216}
217
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -0500218static int usb_kbd_probe(struct usb_interface *iface,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700219 const struct usb_device_id *id)
220{
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500221 struct usb_device *dev = interface_to_usbdev(iface);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700222 struct usb_host_interface *interface;
223 struct usb_endpoint_descriptor *endpoint;
224 struct usb_kbd *kbd;
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500225 struct input_dev *input_dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226 int i, pipe, maxp;
Dmitry Torokhov5d6341c2007-04-04 10:40:57 +0200227 int error = -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700228
229 interface = iface->cur_altsetting;
230
231 if (interface->desc.bNumEndpoints != 1)
232 return -ENODEV;
233
234 endpoint = &interface->endpoint[0].desc;
Luiz Fernando N. Capitulinoa20c3142006-10-26 13:02:59 -0300235 if (!usb_endpoint_is_int_in(endpoint))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700236 return -ENODEV;
237
238 pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
239 maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
240
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500241 kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);
242 input_dev = input_allocate_device();
243 if (!kbd || !input_dev)
244 goto fail1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700245
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500246 if (usb_kbd_alloc_mem(dev, kbd))
247 goto fail2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700248
249 kbd->usbdev = dev;
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500250 kbd->dev = input_dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700251
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500252 if (dev->manufacturer)
253 strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));
254
255 if (dev->product) {
256 if (dev->manufacturer)
257 strlcat(kbd->name, " ", sizeof(kbd->name));
258 strlcat(kbd->name, dev->product, sizeof(kbd->name));
259 }
260
261 if (!strlen(kbd->name))
262 snprintf(kbd->name, sizeof(kbd->name),
263 "USB HIDBP Keyboard %04x:%04x",
264 le16_to_cpu(dev->descriptor.idVendor),
265 le16_to_cpu(dev->descriptor.idProduct));
266
267 usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
268 strlcpy(kbd->phys, "/input0", sizeof(kbd->phys));
269
270 input_dev->name = kbd->name;
271 input_dev->phys = kbd->phys;
272 usb_to_input_id(dev, &input_dev->id);
Dmitry Torokhove0712982007-05-09 10:17:31 +0200273 input_dev->dev.parent = &iface->dev;
274
275 input_set_drvdata(input_dev, kbd);
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500276
Jiri Slaby7b19ada2007-10-18 23:40:32 -0700277 input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
278 BIT_MASK(EV_REP);
279 input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
280 BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |
281 BIT_MASK(LED_KANA);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700282
283 for (i = 0; i < 255; i++)
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500284 set_bit(usb_kbd_keycode[i], input_dev->keybit);
285 clear_bit(0, input_dev->keybit);
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -0500286
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500287 input_dev->event = usb_kbd_event;
288 input_dev->open = usb_kbd_open;
289 input_dev->close = usb_kbd_close;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700290
291 usb_fill_int_urb(kbd->irq, dev, pipe,
292 kbd->new, (maxp > 8 ? 8 : maxp),
293 usb_kbd_irq, kbd, endpoint->bInterval);
294 kbd->irq->transfer_dma = kbd->new_dma;
295 kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
296
297 kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
298 kbd->cr->bRequest = 0x09;
299 kbd->cr->wValue = cpu_to_le16(0x200);
300 kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
301 kbd->cr->wLength = cpu_to_le16(1);
302
Linus Torvalds1da177e2005-04-16 15:20:36 -0700303 usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),
304 (void *) kbd->cr, kbd->leds, 1,
305 usb_kbd_led, kbd);
306 kbd->led->setup_dma = kbd->cr_dma;
307 kbd->led->transfer_dma = kbd->leds_dma;
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500308 kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700309
Dmitry Torokhov5d6341c2007-04-04 10:40:57 +0200310 error = input_register_device(kbd->dev);
311 if (error)
312 goto fail2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700313
314 usb_set_intfdata(iface, kbd);
315 return 0;
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500316
Dmitry Torokhov5d6341c2007-04-04 10:40:57 +0200317fail2:
318 usb_kbd_free_mem(dev, kbd);
319fail1:
320 input_free_device(input_dev);
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500321 kfree(kbd);
Dmitry Torokhov5d6341c2007-04-04 10:40:57 +0200322 return error;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700323}
324
325static void usb_kbd_disconnect(struct usb_interface *intf)
326{
327 struct usb_kbd *kbd = usb_get_intfdata (intf);
Dmitry Torokhov05f091ab2005-05-29 02:29:01 -0500328
Linus Torvalds1da177e2005-04-16 15:20:36 -0700329 usb_set_intfdata(intf, NULL);
330 if (kbd) {
331 usb_kill_urb(kbd->irq);
Dmitry Torokhovc5b7c7c2005-09-15 02:01:47 -0500332 input_unregister_device(kbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700333 usb_kbd_free_mem(interface_to_usbdev(intf), kbd);
334 kfree(kbd);
335 }
336}
337
338static struct usb_device_id usb_kbd_id_table [] = {
Michael Opdenacker4ef2e232007-02-21 22:51:25 +0100339 { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
340 USB_INTERFACE_PROTOCOL_KEYBOARD) },
Linus Torvalds1da177e2005-04-16 15:20:36 -0700341 { } /* Terminating entry */
342};
343
344MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);
345
346static struct usb_driver usb_kbd_driver = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700347 .name = "usbkbd",
348 .probe = usb_kbd_probe,
349 .disconnect = usb_kbd_disconnect,
350 .id_table = usb_kbd_id_table,
351};
352
353static int __init usb_kbd_init(void)
354{
355 int result = usb_register(&usb_kbd_driver);
356 if (result == 0)
Greg Kroah-Hartmanddbe3242008-10-12 00:14:23 +0200357 printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
358 DRIVER_DESC "\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700359 return result;
360}
361
362static void __exit usb_kbd_exit(void)
363{
364 usb_deregister(&usb_kbd_driver);
365}
366
367module_init(usb_kbd_init);
368module_exit(usb_kbd_exit);