From: Peter Mamonov <pmamonov@gmail.com>
To: barebox@lists.infradead.org
Cc: Peter Mamonov <pmamonov@gmail.com>
Subject: [RFC] WIP: add usb keyboard driver
Date: Wed, 9 Sep 2015 19:36:52 +0300 [thread overview]
Message-ID: <1441816612-5471-1-git-send-email-pmamonov@gmail.com> (raw)
The driver doesn't work with some "multimedia" keyboards.
This driver contains code ported from u-boot.
Signed-off-by: Peter Mamonov <pmamonov@gmail.com>
---
drivers/input/Kconfig | 7 +
drivers/input/Makefile | 1 +
drivers/input/usb_kbd.c | 340 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 348 insertions(+)
create mode 100644 drivers/input/usb_kbd.c
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index b4e86fd..24a5d10 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -46,4 +46,11 @@ config KEYBOARD_TWL6030
help
Say Y here if you want to use TWL6030 power button as a key.
+config KEYBOARD_USB
+ bool "USB keyboard"
+ depends on USB_HOST
+ select POLLER
+ help
+ This driver implements support for usb keyboard.
+
endmenu
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 2143336..40b898c 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -1,3 +1,4 @@
+obj-$(CONFIG_KEYBOARD_USB) += usb_kbd.o
obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
obj-$(CONFIG_KEYBOARD_TWL6030) += twl6030_pwrbtn.o
obj-$(CONFIG_KEYBOARD_IMX_KEYPAD) += imx_keypad.o
diff --git a/drivers/input/usb_kbd.c b/drivers/input/usb_kbd.c
new file mode 100644
index 0000000..0ded81e
--- /dev/null
+++ b/drivers/input/usb_kbd.c
@@ -0,0 +1,340 @@
+#include <common.h>
+#include <init.h>
+#include <clock.h>
+#include <poller.h>
+#include <usb/usb.h>
+#include <string.h>
+#include <dma.h>
+#include <kfifo.h>
+
+#define USB_KBD_FIFO_SIZE 50
+
+#define REPEAT_RATE 40 /* 40msec -> 25cps */
+#define REPEAT_DELAY 10 /* 10 x REPEAT_RATE = 400msec */
+
+#define NUM_LOCK 0x53
+#define CAPS_LOCK 0x39
+#define SCROLL_LOCK 0x47
+
+/* Modifier bits */
+#define LEFT_CNTR (1 << 0)
+#define LEFT_SHIFT (1 << 1)
+#define LEFT_ALT (1 << 2)
+#define LEFT_GUI (1 << 3)
+#define RIGHT_CNTR (1 << 4)
+#define RIGHT_SHIFT (1 << 5)
+#define RIGHT_ALT (1 << 6)
+#define RIGHT_GUI (1 << 7)
+
+/* Size of the keyboard buffer */
+#define USB_KBD_BUFFER_LEN 0x20
+
+/* Keyboard maps */
+static const unsigned char usb_kbd_numkey[] = {
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '\r', 0x1b, '\b', '\t', ' ', '-', '=', '[', ']',
+ '\\', '#', ';', '\'', '`', ',', '.', '/'
+};
+static const unsigned char usb_kbd_numkey_shifted[] = {
+ '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+ '\r', 0x1b, '\b', '\t', ' ', '_', '+', '{', '}',
+ '|', '~', ':', '"', '~', '<', '>', '?'
+};
+
+static const unsigned char usb_kbd_num_keypad[] = {
+ '/', '*', '-', '+', '\r',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '.', 0, 0, 0, '='
+};
+
+/*
+ * map arrow keys to ^F/^B ^N/^P, can't really use the proper
+ * ANSI sequence for arrow keys because the queuing code breaks
+ * when a single keypress expands to 3 queue elements
+ */
+static const unsigned char usb_kbd_arrow[] = {
+ 0x6, 0x2, 0xe, 0x10
+};
+
+/*
+ * NOTE: It's important for the NUM, CAPS, SCROLL-lock bits to be in this
+ * order. See usb_kbd_setled() function!
+ */
+#define USB_KBD_NUMLOCK (1 << 0)
+#define USB_KBD_CAPSLOCK (1 << 1)
+#define USB_KBD_SCROLLLOCK (1 << 2)
+#define USB_KBD_CTRL (1 << 3)
+
+#define USB_KBD_LEDMASK \
+ (USB_KBD_NUMLOCK | USB_KBD_CAPSLOCK | USB_KBD_SCROLLLOCK)
+
+/*
+ * USB Keyboard reports are 8 bytes in boot protocol.
+ * Appendix B of HID Device Class Definition 1.11
+ */
+#define USB_KBD_BOOT_REPORT_SIZE 8
+
+struct usb_kbd_pdata {
+ uint64_t last_report;
+ uint8_t new[USB_KBD_BOOT_REPORT_SIZE];
+ uint8_t old[USB_KBD_BOOT_REPORT_SIZE];
+ uint32_t repeat_delay;
+ uint8_t flags;
+ struct poller_struct poller;
+ struct usb_device *usbdev;
+ struct console_device cdev;
+ struct kfifo *recv_fifo;
+ int lock;
+};
+
+#define CAPITAL_MASK 0x20
+/* Translate the scancode in ASCII */
+static int usb_kbd_translate(struct usb_kbd_pdata *data, unsigned char scancode,
+ unsigned char modifier, int pressed)
+{
+ int keycode = 0;
+
+ /* Key released */
+ if (pressed == 0) {
+ data->repeat_delay = 0;
+ return 0;
+ }
+
+ if (pressed == 2) {
+ data->repeat_delay++;
+ if (data->repeat_delay < REPEAT_DELAY)
+ return 0;
+
+ data->repeat_delay = REPEAT_DELAY;
+ }
+
+ /* Alphanumeric values */
+ if ((scancode > 3) && (scancode <= 0x1d)) {
+ keycode = scancode - 4 + 'a';
+
+ if (data->flags & USB_KBD_CAPSLOCK)
+ keycode &= ~CAPITAL_MASK;
+
+ if (modifier & (LEFT_SHIFT | RIGHT_SHIFT)) {
+ /* Handle CAPSLock + Shift pressed simultaneously */
+ if (keycode & CAPITAL_MASK)
+ keycode &= ~CAPITAL_MASK;
+ else
+ keycode |= CAPITAL_MASK;
+ }
+ }
+
+ if ((scancode > 0x1d) && (scancode < 0x3a)) {
+ /* Shift pressed */
+ if (modifier & (LEFT_SHIFT | RIGHT_SHIFT))
+ keycode = usb_kbd_numkey_shifted[scancode - 0x1e];
+ else
+ keycode = usb_kbd_numkey[scancode - 0x1e];
+ }
+
+ /* Arrow keys */
+ if ((scancode >= 0x4f) && (scancode <= 0x52))
+ keycode = usb_kbd_arrow[scancode - 0x4f];
+
+ /* Numeric keypad */
+ if ((scancode >= 0x54) && (scancode <= 0x67))
+ keycode = usb_kbd_num_keypad[scancode - 0x54];
+
+ if (data->flags & USB_KBD_CTRL)
+ keycode = scancode - 0x3;
+
+ if (pressed == 1) {
+ if (scancode == NUM_LOCK) {
+ data->flags ^= USB_KBD_NUMLOCK;
+ return 1;
+ }
+
+ if (scancode == CAPS_LOCK) {
+ data->flags ^= USB_KBD_CAPSLOCK;
+ return 1;
+ }
+ if (scancode == SCROLL_LOCK) {
+ data->flags ^= USB_KBD_SCROLLLOCK;
+ return 1;
+ }
+ }
+
+ /* Report keycode if any */
+ if (keycode) {
+ pr_debug("%s: key pressed: '%c'\n", __FUNCTION__, keycode);
+ kfifo_put(data->recv_fifo, (u_char*)&keycode, sizeof(keycode));
+ }
+
+ return 0;
+}
+
+static uint32_t usb_kbd_service_key(struct usb_kbd_pdata *data, int i, int up)
+{
+ uint32_t res = 0;
+ uint8_t *new;
+ uint8_t *old;
+
+ if (up) {
+ new = data->old;
+ old = data->new;
+ } else {
+ new = data->new;
+ old = data->old;
+ }
+
+ if ((old[i] > 3) &&
+ (memscan(new + 2, old[i], USB_KBD_BOOT_REPORT_SIZE - 2) ==
+ new + USB_KBD_BOOT_REPORT_SIZE)) {
+ res |= usb_kbd_translate(data, old[i], data->new[0], up);
+ }
+
+ return res;
+}
+
+static void usb_kbd_setled(struct usb_kbd_pdata *data)
+{
+ struct usb_device *usbdev = data->usbdev;
+ struct usb_interface *iface = &usbdev->config.interface[0];
+ uint8_t leds = (uint8_t)(data->flags & USB_KBD_LEDMASK);
+
+ usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+ USB_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x200, iface->desc.bInterfaceNumber, &leds, 1, USB_CNTL_TIMEOUT);
+}
+
+
+static int usb_kbd_process(struct usb_kbd_pdata *data)
+{
+ int i, res = 0;
+
+ /* No combo key pressed */
+ if (data->new[0] == 0x00)
+ data->flags &= ~USB_KBD_CTRL;
+ /* Left or Right Ctrl pressed */
+ else if ((data->new[0] == LEFT_CNTR) || (data->new[0] == RIGHT_CNTR))
+ data->flags |= USB_KBD_CTRL;
+
+ for (i = 2; i < USB_KBD_BOOT_REPORT_SIZE; i++) {
+ res |= usb_kbd_service_key(data, i, 0);
+ res |= usb_kbd_service_key(data, i, 1);
+ }
+
+ /* Key is still pressed */
+ if ((data->new[2] > 3) && (data->old[2] == data->new[2]))
+ res |= usb_kbd_translate(data, data->new[2], data->new[0], 2);
+
+ if (res == 1)
+ usb_kbd_setled(data);
+
+ return 1;
+}
+
+static void usb_kbd_poll(struct poller_struct *poller)
+{
+ struct usb_kbd_pdata *data = container_of(poller, struct usb_kbd_pdata, poller);
+ struct usb_device *usbdev = data->usbdev;
+ struct usb_interface *iface = &usbdev->config.interface[0];
+
+ if (data->lock)
+ return;
+ data->lock = 1;
+
+ usb_get_report(usbdev, iface->desc.bInterfaceNumber,
+ 1, 0, data->new, USB_KBD_BOOT_REPORT_SIZE);
+ if (memcmp(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE) ||
+ get_time_ns() > data->last_report + REPEAT_RATE * MSECOND) {
+ data->last_report = get_time_ns();
+ pr_debug("%s: old report: %016llx\n",
+ __FUNCTION__,
+ *((volatile uint64_t *)data->old));
+ pr_debug("%s: new report: %016llx\n\n",
+ __FUNCTION__,
+ *((volatile uint64_t *)data->new));
+ usb_kbd_process(data);
+ memcpy(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE);
+ }
+
+ data->lock = 0;
+}
+
+static int usb_kbd_getc(struct console_device *cdev)
+{
+ int code = 0;
+ struct usb_kbd_pdata *data = container_of(cdev, struct usb_kbd_pdata, cdev);
+
+ kfifo_get(data->recv_fifo, (u_char*)&code, sizeof(int));
+ return code;
+}
+
+static int usb_kbd_tstc(struct console_device *cdev)
+{
+ struct usb_kbd_pdata *data = container_of(cdev, struct usb_kbd_pdata, cdev);
+
+ return (kfifo_len(data->recv_fifo) == 0) ? 0 : 1;
+}
+
+static int usb_kbd_probe(struct usb_device *usbdev,
+ const struct usb_device_id *id)
+{
+ int ret;
+ struct usb_interface *iface = &usbdev->config.interface[0];
+ struct usb_kbd_pdata *data;
+ struct console_device *cdev;
+
+ dev_info(&usbdev->dev, "USB keyboard found\n");
+
+ ret = usb_set_protocol(usbdev, iface->desc.bInterfaceNumber, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_set_idle(usbdev, iface->desc.bInterfaceNumber, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ data = xzalloc(sizeof(struct usb_kbd_pdata));
+ usbdev->drv_data = data;
+ data->recv_fifo = kfifo_alloc(USB_KBD_FIFO_SIZE);
+
+ data->usbdev = usbdev;
+ data->last_report = get_time_ns();
+
+ cdev = &data->cdev;
+ usbdev->dev.type_data = cdev;
+ cdev->dev = &usbdev->dev;
+ cdev->tstc = usb_kbd_tstc;
+ cdev->getc = usb_kbd_getc;
+
+ console_register(cdev);
+ console_set_active(cdev, CONSOLE_STDIN);
+
+ data->poller.func = usb_kbd_poll;
+ return poller_register(&data->poller);
+}
+
+static void usb_kbd_disconnect(struct usb_device *usbdev)
+{
+ struct usb_kbd_pdata *data = usbdev->drv_data;
+
+ poller_unregister(&data->poller);
+ console_unregister(&data->cdev);
+ kfifo_free(data->recv_fifo);
+ free(data);
+}
+
+static struct usb_device_id usb_kbd_usb_ids[] = {
+ { USB_INTERFACE_INFO(3, 1, 1) }, // usb keyboard
+ { }
+};
+
+static struct usb_driver usb_kbd_driver = {
+ .name = "usb-keyboard",
+ .id_table = usb_kbd_usb_ids,
+ .probe = usb_kbd_probe,
+ .disconnect = usb_kbd_disconnect,
+};
+
+static int __init usb_kbd_init(void)
+{
+ return usb_driver_register(&usb_kbd_driver);
+}
+device_initcall(usb_kbd_init);
--
2.1.4
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
next reply other threads:[~2015-09-09 16:35 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-09-09 16:36 Peter Mamonov [this message]
2015-09-10 7:38 ` [RFC] WIP: add usb keyboard driver Sascha Hauer
2015-09-10 15:51 ` Peter Mamonov
2015-09-10 17:21 ` Sascha Hauer
2015-09-11 14:56 ` Peter Mamonov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1441816612-5471-1-git-send-email-pmamonov@gmail.com \
--to=pmamonov@gmail.com \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.