* [RFC v4 0/2] WIP: add usb keyboard driver @ 2015-09-17 11:18 Peter Mamonov 2015-09-17 11:18 ` [PATCH 1/2] usb: ehci-hcd: port periodic transactions implementation from the u-boot Peter Mamonov ` (2 more replies) 0 siblings, 3 replies; 5+ messages in thread From: Peter Mamonov @ 2015-09-17 11:18 UTC (permalink / raw) To: barebox Changes since v3: - issue Set_Idle(duration=0) usb request if polling via control EP Peter Mamonov (2): usb: ehci-hcd: port periodic transactions implementation input: port usb keyboard driver from the u-boot _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/2] usb: ehci-hcd: port periodic transactions implementation from the u-boot 2015-09-17 11:18 [RFC v4 0/2] WIP: add usb keyboard driver Peter Mamonov @ 2015-09-17 11:18 ` Peter Mamonov 2015-09-17 11:18 ` [PATCH 2/2] input: port usb keyboard driver " Peter Mamonov 2015-09-21 6:18 ` [RFC v4 0/2] WIP: add usb keyboard driver Sascha Hauer 2 siblings, 0 replies; 5+ messages in thread From: Peter Mamonov @ 2015-09-17 11:18 UTC (permalink / raw) To: barebox; +Cc: Peter Mamonov Signed-off-by: Peter Mamonov <pmamonov@gmail.com> --- drivers/usb/host/ehci-hcd.c | 400 +++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/ehci.h | 15 +- 2 files changed, 413 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 551d1a0..9553fdf 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -48,6 +48,18 @@ struct ehci_priv { int (*init)(void *drvdata); int (*post_init)(void *drvdata); void *drvdata; + int periodic_schedules; + struct QH *periodic_queue; + uint32_t *periodic_list; +}; + +struct int_queue { + int elementsize; + unsigned long pipe; + struct QH *first; + struct QH *current; + struct QH *last; + struct qTD *tds; }; #define to_ehci(ptr) container_of(ptr, struct ehci_priv, host) @@ -768,6 +780,8 @@ static int ehci_init(struct usb_host *host) uint32_t reg; uint32_t cmd; int ret = 0; + struct QH *periodic; + int i; ehci_halt(ehci); @@ -798,6 +812,44 @@ static int ehci_init(struct usb_host *host) ehci_writel(&ehci->hcor->or_asynclistaddr, ba); } + /* + * Set up periodic list + * Step 1: Parent QH for all periodic transfers. + */ + ehci->periodic_schedules = 0; + periodic = ehci->periodic_queue; + memset(periodic, 0, sizeof(*periodic)); + periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE); + periodic->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + periodic->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + + /* + * Step 2: Setup frame-list: Every microframe, USB tries the same list. + * In particular, device specifications on polling frequency + * are disregarded. Keyboards seem to send NAK/NYet reliably + * when polled with an empty buffer. + * + * Split Transactions will be spread across microframes using + * S-mask and C-mask. + */ + if (ehci->periodic_list == NULL) + /* + * FIXME: this memory chunk have to be 4k aligned AND + * reside in coherent memory. Current implementation of + * dma_alloc_coherent() allocates PAGE_SIZE aligned memory chunks. + * PAGE_SIZE less then 4k will break this code. + */ + ehci->periodic_list = dma_alloc_coherent(1024 * 4, + DMA_ADDRESS_BROKEN); + for (i = 0; i < 1024; i++) { + ehci->periodic_list[i] = cpu_to_hc32((unsigned long)periodic + | QH_LINK_TYPE_QH); + } + + /* Set periodic list base address */ + ehci_writel(&ehci->hcor->or_periodiclistbase, + (unsigned long)ehci->periodic_list); + reg = ehci_readl(&ehci->hccr->cr_hcsparams); descriptor.hub.bNbrPorts = HCS_N_PORTS(reg); @@ -869,15 +921,359 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, } static int +disable_periodic(struct ehci_priv *ehci) +{ + uint32_t cmd; + struct ehci_hcor *hcor = ehci->hcor; + int ret; + + cmd = ehci_readl(&hcor->or_usbcmd); + cmd &= ~CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + ret = handshake((uint32_t *)&hcor->or_usbsts, + STS_PSS, 0, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when disabling periodic list\n"); + return -ETIMEDOUT; + } + return 0; +} + +#define NEXT_QH(qh) (struct QH *)((0xa0000000 | (unsigned long)hc32_to_cpu((qh)->qh_link)) & ~0x1f) + +static int +enable_periodic(struct ehci_priv *ehci) +{ + uint32_t cmd; + struct ehci_hcor *hcor = ehci->hcor; + int ret; + uint64_t start; + + cmd = ehci_readl(&hcor->or_usbcmd); + cmd |= CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + ret = handshake((uint32_t *)&hcor->or_usbsts, + STS_PSS, STS_PSS, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when enabling periodic list\n"); + return -ETIMEDOUT; + } + + start = get_time_ns(); + while(!is_timeout_non_interruptible(start, 1 * MSECOND)); + + return 0; +} + +static inline u8 ehci_encode_speed(enum usb_device_speed speed) +{ + #define QH_HIGH_SPEED 2 + #define QH_FULL_SPEED 0 + #define QH_LOW_SPEED 1 + if (speed == USB_SPEED_HIGH) + return QH_HIGH_SPEED; + if (speed == USB_SPEED_LOW) + return QH_LOW_SPEED; + return QH_FULL_SPEED; +} + +static void ehci_update_endpt2_dev_n_port(struct usb_device *udev, + struct QH *qh) +{ + struct usb_device *ttdev; + int parent_devnum; + + if (udev->speed != USB_SPEED_LOW && udev->speed != USB_SPEED_FULL) + return; + + /* + * For full / low speed devices we need to get the devnum and portnr of + * the tt, so of the first upstream usb-2 hub, there may be usb-1 hubs + * in the tree before that one! + */ + + ttdev = udev; + while (ttdev->parent && ttdev->parent->speed != USB_SPEED_HIGH) + ttdev = ttdev->parent; + if (!ttdev->parent) + return; + parent_devnum = ttdev->parent->devnum; + + qh->qh_endpt2 |= cpu_to_hc32(QH_ENDPT2_PORTNUM(ttdev->portnr) | + QH_ENDPT2_HUBADDR(parent_devnum)); +} + +static struct int_queue *ehci_create_int_queue(struct usb_device *dev, + unsigned long pipe, int queuesize, int elementsize, + void *buffer, int interval) +{ + struct usb_host *host = dev->host; + struct ehci_priv *ehci = to_ehci(host); + struct int_queue *result = NULL; + uint32_t i, toggle; + struct QH *list = ehci->periodic_queue; + + /* + * Interrupt transfers requiring several transactions are not supported + * because bInterval is ignored. + * + * Also, ehci_submit_async() relies on wMaxPacketSize being a power of 2 + * <= PKT_ALIGN if several qTDs are required, while the USB + * specification does not constrain this for interrupt transfers. That + * means that ehci_submit_async() would support interrupt transfers + * requiring several transactions only as long as the transfer size does + * not require more than a single qTD. + */ + if (elementsize > usb_maxpacket(dev, pipe)) { + dev_err(&dev->dev, + "%s: xfers requiring several transactions are not supported.\n", + __func__); + return NULL; + } + + debug("Enter create_int_queue\n"); + if (usb_pipetype(pipe) != PIPE_INTERRUPT) { + dev_dbg(&dev->dev, + "non-interrupt pipe (type=%lu)", + usb_pipetype(pipe)); + return NULL; + } + + /* limit to 4 full pages worth of data - + * we can safely fit them in a single TD, + * no matter the alignment + */ + if (elementsize >= 16384) { + dev_dbg(&dev->dev, + "too large elements for interrupt transfers\n"); + return NULL; + } + + result = xzalloc(sizeof(*result)); + result->elementsize = elementsize; + result->pipe = pipe; + result->first = dma_alloc_coherent(sizeof(struct QH) * queuesize, + DMA_ADDRESS_BROKEN); + result->current = result->first; + result->last = result->first + queuesize - 1; + result->tds = dma_alloc_coherent(sizeof(struct qTD) * queuesize, + DMA_ADDRESS_BROKEN); + memset(result->first, 0, sizeof(struct QH) * queuesize); + memset(result->tds, 0, sizeof(struct qTD) * queuesize); + + toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); + + for (i = 0; i < queuesize; i++) { + struct QH *qh = result->first + i; + struct qTD *td = result->tds + i; + void **buf = &qh->buffer; + + qh->qh_link = cpu_to_hc32((unsigned long)(qh+1) | QH_LINK_TYPE_QH); + if (i == queuesize - 1) + qh->qh_link = cpu_to_hc32(QH_LINK_TERMINATE); + + qh->qt_next = cpu_to_hc32((unsigned long)td); + qh->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + qh->qh_endpt1 = + cpu_to_hc32((0 << 28) | /* No NAK reload (ehci 4.9) */ + (usb_maxpacket(dev, pipe) << 16) | /* MPS */ + (1 << 14) | + QH_ENDPT1_EPS(ehci_encode_speed(dev->speed)) | + (usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */ + (usb_pipedevice(pipe) << 0)); + qh->qh_endpt2 = cpu_to_hc32((1 << 30) | /* 1 Tx per mframe */ + (1 << 0)); /* S-mask: microframe 0 */ + if (dev->speed == USB_SPEED_LOW || + dev->speed == USB_SPEED_FULL) { + /* C-mask: microframes 2-4 */ + qh->qh_endpt2 |= cpu_to_hc32((0x1c << 8)); + } + ehci_update_endpt2_dev_n_port(dev, qh); + + td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + dev_dbg(&dev->dev, + "communication direction is '%s'\n", + usb_pipein(pipe) ? "in" : "out"); + td->qt_token = cpu_to_hc32( + QT_TOKEN_DT(toggle) | + (elementsize << 16) | + ((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */ + 0x80); /* active */ + td->qt_buffer[0] = + cpu_to_hc32((unsigned long)buffer + i * elementsize); + td->qt_buffer[1] = + cpu_to_hc32((td->qt_buffer[0] + 0x1000) & ~0xfff); + td->qt_buffer[2] = + cpu_to_hc32((td->qt_buffer[0] + 0x2000) & ~0xfff); + td->qt_buffer[3] = + cpu_to_hc32((td->qt_buffer[0] + 0x3000) & ~0xfff); + td->qt_buffer[4] = + cpu_to_hc32((td->qt_buffer[0] + 0x4000) & ~0xfff); + + *buf = buffer + i * elementsize; + toggle ^= 1; + } + + if (ehci->periodic_schedules > 0) { + if (disable_periodic(ehci) < 0) { + dev_err(&dev->dev, + "FATAL: periodic should never fail, but did"); + goto fail3; + } + } + + /* hook up to periodic list */ + result->last->qh_link = list->qh_link; + list->qh_link = cpu_to_hc32((unsigned long)result->first | QH_LINK_TYPE_QH); + + if (enable_periodic(ehci) < 0) { + dev_err(&dev->dev, + "FATAL: periodic should never fail, but did"); + goto fail3; + } + ehci->periodic_schedules++; + + dev_dbg(&dev->dev, "Exit create_int_queue\n"); + return result; +fail3: + dma_free_coherent(result->tds, 0, sizeof(struct qTD) * queuesize); + dma_free_coherent(result->first, 0, sizeof(struct QH) * queuesize); + free(result); + return NULL; +} + +static void *ehci_poll_int_queue(struct usb_device *dev, + struct int_queue *queue) +{ + struct QH *cur = queue->current; + struct qTD *cur_td; + uint32_t token, toggle; + unsigned long pipe = queue->pipe; + + /* depleted queue */ + if (cur == NULL) { + dev_dbg(&dev->dev, "Exit poll_int_queue with completed queue\n"); + return NULL; + } + /* still active */ + cur_td = &queue->tds[queue->current - queue->first]; + token = hc32_to_cpu(cur_td->qt_token); + if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE) { + dev_dbg(&dev->dev, + "Exit poll_int_queue with no completed intr transfer. token is %x\n", + token); + return NULL; + } + + toggle = QT_TOKEN_GET_DT(token); + usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), toggle); + + if (!(cur->qh_link & QH_LINK_TERMINATE)) + queue->current++; + else + queue->current = NULL; + + dev_dbg(&dev->dev, + "Exit poll_int_queue with completed intr transfer. token is %x at %p (first at %p)\n", + token, cur, queue->first); + return cur->buffer; +} + +static int ehci_destroy_int_queue(struct usb_device *dev, + struct int_queue *queue) +{ + int result = -EINVAL; + struct usb_host *host = dev->host; + struct ehci_priv *ehci = to_ehci(host); + struct QH *cur = ehci->periodic_queue; + uint64_t start; + + if (disable_periodic(ehci) < 0) { + dev_err(&dev->dev, + "FATAL: periodic should never fail, but did\n"); + goto out; + } + ehci->periodic_schedules--; + + start = get_time_ns(); + while (!(cur->qh_link & cpu_to_hc32(QH_LINK_TERMINATE))) { + dev_dbg(&dev->dev, + "considering %p, with qh_link %x\n", + cur, cur->qh_link); + if (NEXT_QH(cur) == queue->first) { + dev_dbg(&dev->dev, + "found candidate. removing from chain\n"); + cur->qh_link = queue->last->qh_link; + result = 0; + break; + } + cur = NEXT_QH(cur); + if (is_timeout_non_interruptible(start, 500 * MSECOND)) { + dev_err(&dev->dev, + "Timeout destroying interrupt endpoint queue\n"); + result = -ETIMEDOUT; + goto out; + } + } + + if (ehci->periodic_schedules > 0) { + result = enable_periodic(ehci); + if (result < 0) + dev_err(&dev->dev, + "FATAL: periodic should never fail, but did"); + } + +out: + free(queue->tds); + free(queue->first); + free(queue); + + return result; +} + +static int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, int length, int interval) { struct usb_host *host = dev->host; struct ehci_priv *ehci = to_ehci(host); + struct int_queue *queue; + uint64_t start; + void *backbuffer; + int result = 0, ret; dev_dbg(ehci->dev, "dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d", dev, pipe, buffer, length, interval); - return -1; + + queue = ehci_create_int_queue(dev, pipe, 1, length, buffer, interval); + if (!queue) + return -EINVAL; + + start = get_time_ns(); + while ((backbuffer = ehci_poll_int_queue(dev, queue)) == NULL) + if (is_timeout_non_interruptible(start, + USB_CNTL_TIMEOUT * MSECOND)) { + dev_err(&dev->dev, + "Timeout poll on interrupt endpoint\n"); + result = -ETIMEDOUT; + break; + } + + if (backbuffer != buffer) { + dev_err(&dev->dev, + "got wrong buffer back (%p instead of %p)\n", + backbuffer, buffer); + return -EINVAL; + } + + ret = ehci_destroy_int_queue(dev, queue); + if (ret < 0) + return ret; + + return result; } static int ehci_detect(struct device_d *dev) @@ -912,6 +1308,8 @@ int ehci_register(struct device_d *dev, struct ehci_data *data) ehci->qh_list = dma_alloc_coherent(sizeof(struct QH) * NUM_TD, DMA_ADDRESS_BROKEN); + ehci->periodic_queue = dma_alloc_coherent(sizeof(struct QH), + DMA_ADDRESS_BROKEN); ehci->td = dma_alloc_coherent(sizeof(struct qTD) * NUM_TD, DMA_ADDRESS_BROKEN); diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index d71d056..39de763 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -20,6 +20,15 @@ #include <io.h> +#define QH_ENDPT1_EPS(x) (((x) & 0x3) << 12) /* Endpoint Speed */ +#define QH_ENDPT2_PORTNUM(x) (((x) & 0x7f) << 23) /* Port Number */ +#define QH_ENDPT2_HUBADDR(x) (((x) & 0x7f) << 16) /* Hub Address */ + +#define QT_TOKEN_DT(x) (((x) & 0x1) << 31) /* Data Toggle */ +#define QT_TOKEN_GET_STATUS(x) (((x) >> 0) & 0xff) +#define QT_TOKEN_STATUS_ACTIVE 0x80 +#define QT_TOKEN_GET_DT(x) (((x) >> 31) & 0x1) + #if !defined(CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS) #define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS 16 #endif @@ -51,6 +60,7 @@ struct ehci_hcor { #define CMD_RUN (1 << 0) /* start/stop HC */ uint32_t or_usbsts; #define STD_ASS (1 << 15) +#define STS_PSS (1 << 14) #define STS_HALT (1 << 12) uint32_t or_usbintr; uint32_t or_frindex; @@ -148,7 +158,10 @@ struct QH { * Add dummy fill value to make the size of this struct * aligned to 32 bytes */ - uint8_t fill[16]; + union { + uint8_t fill[16]; + void* buffer; + }; }; #endif /* USB_EHCI_H */ -- 2.1.4 _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/2] input: port usb keyboard driver from the u-boot 2015-09-17 11:18 [RFC v4 0/2] WIP: add usb keyboard driver Peter Mamonov 2015-09-17 11:18 ` [PATCH 1/2] usb: ehci-hcd: port periodic transactions implementation from the u-boot Peter Mamonov @ 2015-09-17 11:18 ` Peter Mamonov 2015-09-21 6:18 ` [RFC v4 0/2] WIP: add usb keyboard driver Sascha Hauer 2 siblings, 0 replies; 5+ messages in thread From: Peter Mamonov @ 2015-09-17 11:18 UTC (permalink / raw) To: barebox; +Cc: Peter Mamonov Signed-off-by: Peter Mamonov <pmamonov@gmail.com> --- drivers/input/Kconfig | 7 + drivers/input/Makefile | 1 + drivers/input/usb_kbd.c | 416 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 424 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..db1ba90 --- /dev/null +++ b/drivers/input/usb_kbd.c @@ -0,0 +1,416 @@ +/* + * USB keyboard driver for barebox + * + * (C) Copyright 2001 Denis Peter, MPL AG Switzerland + * (C) Copyright 2015 Peter Mamonov + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#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; + +struct usb_kbd_pdata { + uint64_t last_report; + uint64_t last_poll; + uint8_t *new; + 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; + unsigned long intpipe; + int intpktsize; + int intinterval; + struct usb_endpoint_descriptor *ep; + int (*do_poll)(struct usb_kbd_pdata *); +}; + +static int usb_kbd_int_poll(struct usb_kbd_pdata *data) +{ + return usb_submit_int_msg(data->usbdev, data->intpipe, data->new, + data->intpktsize, data->intinterval); +} + +static int usb_kbd_cnt_poll(struct usb_kbd_pdata *data) +{ + struct usb_interface *iface = &data->usbdev->config.interface[0]; + + return usb_get_report(data->usbdev, iface->desc.bInterfaceNumber, + 1, 0, data->new, USB_KBD_BOOT_REPORT_SIZE); +} + +#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; + int diff, tout; + + if (data->lock) + return; + data->lock = 1; + + if (0 > data->do_poll(data)) { + /* exit and lock forever */ + dev_err(&usbdev->dev, + "usb_submit_int_msg() failed. Keyboard disconnect?\n"); + return; + } + diff = memcmp(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE); + tout = get_time_ns() > data->last_report + REPEAT_RATE * MSECOND; + if (diff || tout) { + data->last_report = get_time_ns(); + if (diff) { + pr_debug("%s: old report: %016llx\n", + __func__, + *((volatile uint64_t *)data->old)); + pr_debug("%s: new report: %016llx\n\n", + __func__, + *((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"); + dev_dbg(&usbdev->dev, "Debug enabled\n"); + ret = usb_set_protocol(usbdev, iface->desc.bInterfaceNumber, 0); + if (ret < 0) + return ret; + + ret = usb_set_idle(usbdev, iface->desc.bInterfaceNumber, REPEAT_RATE / 4, 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->new = dma_alloc_coherent(USB_KBD_BOOT_REPORT_SIZE, NULL); + + data->usbdev = usbdev; + data->last_report = get_time_ns(); + + data->ep = &iface->ep_desc[0]; + data->intpipe = usb_rcvintpipe(usbdev, data->ep->bEndpointAddress); + data->intpktsize = min(usb_maxpacket(usbdev, data->intpipe), + USB_KBD_BOOT_REPORT_SIZE); + data->intinterval = data->ep->bInterval; + /* test polling via interrupt endpoint */ + data->do_poll = usb_kbd_int_poll; + ret = data->do_poll(data); + if (ret < 0) { + /* fall back to polling via control enpoint */ + data->do_poll = usb_kbd_cnt_poll; + usb_set_idle(usbdev, + iface->desc.bInterfaceNumber, 0, 0); + ret = data->do_poll(data); + if (ret < 0) { + /* no luck */ + kfifo_free(data->recv_fifo); + dma_free_coherent(data->new, 0, + USB_KBD_BOOT_REPORT_SIZE); + free(data); + return ret; + } else + dev_dbg(&usbdev->dev, "poll keyboard via cont ep\n"); + } else + dev_dbg(&usbdev->dev, "poll keyboard via int ep\n"); + + 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); + dma_free_coherent(data->new, 0, USB_KBD_BOOT_REPORT_SIZE); + 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 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [RFC v4 0/2] WIP: add usb keyboard driver 2015-09-17 11:18 [RFC v4 0/2] WIP: add usb keyboard driver Peter Mamonov 2015-09-17 11:18 ` [PATCH 1/2] usb: ehci-hcd: port periodic transactions implementation from the u-boot Peter Mamonov 2015-09-17 11:18 ` [PATCH 2/2] input: port usb keyboard driver " Peter Mamonov @ 2015-09-21 6:18 ` Sascha Hauer 2 siblings, 0 replies; 5+ messages in thread From: Sascha Hauer @ 2015-09-21 6:18 UTC (permalink / raw) To: Peter Mamonov; +Cc: barebox Hi Peter, On Thu, Sep 17, 2015 at 02:18:32PM +0300, Peter Mamonov wrote: > Changes since v3: > - issue Set_Idle(duration=0) usb request if polling via control EP > > Peter Mamonov (2): > usb: ehci-hcd: port periodic transactions implementation > input: port usb keyboard driver from the u-boot > The code looks ok now. I tested the first version with interrupt transfer support and this one didn't work for me, maybe because of missing cache flushes or similar. I want to give it a test and if necessary make it work on ARM if necessary before applying, but haven't found the time to do so yet. Consider them applied, but I'll need a few more days to test them. Sascha -- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 | _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox ^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC v3 0/2] WIP: add usb keyboard driver @ 2015-09-16 13:13 Peter Mamonov 2015-09-16 13:13 ` [PATCH 2/2] input: port usb keyboard driver from the u-boot Peter Mamonov 0 siblings, 1 reply; 5+ messages in thread From: Peter Mamonov @ 2015-09-16 13:13 UTC (permalink / raw) To: barebox Changes since v2: ehci-hcd: * no need for non-interruptible mdelay() * use dev_dbg(), dev_err() functions for messages output * remove excessive checks of pointers * use dma_alloc_coherent for memory allocation * use somewhat meaningful return values instead of -1 * remove too optimistic comments from the code usb_kbd: * allocate usb_kbd_pdata.new using dma_alloc_coherent() * fall back to polling via control EP if interrupt polling fails Peter Mamonov (2): usb: ehci-hcd: port periodic transactions implementation input: port usb keyboard driver from the u-boot _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/2] input: port usb keyboard driver from the u-boot 2015-09-16 13:13 [RFC v3 " Peter Mamonov @ 2015-09-16 13:13 ` Peter Mamonov 0 siblings, 0 replies; 5+ messages in thread From: Peter Mamonov @ 2015-09-16 13:13 UTC (permalink / raw) To: barebox; +Cc: Peter Mamonov Signed-off-by: Peter Mamonov <pmamonov@gmail.com> --- drivers/input/Kconfig | 7 + drivers/input/Makefile | 1 + drivers/input/usb_kbd.c | 417 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 425 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..e6e7745 --- /dev/null +++ b/drivers/input/usb_kbd.c @@ -0,0 +1,417 @@ +/* + * USB keyboard driver for barebox + * + * (C) Copyright 2001 Denis Peter, MPL AG Switzerland + * (C) Copyright 2015 Peter Mamonov + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#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; + +struct usb_kbd_pdata { + uint64_t last_report; + uint64_t last_poll; + uint8_t *new; + 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; + unsigned long intpipe; + int intpktsize; + int intinterval; + struct usb_endpoint_descriptor *ep; + int (*do_poll)(struct usb_kbd_pdata *); +}; + +static int usb_kbd_int_poll(struct usb_kbd_pdata *data) +{ + return usb_submit_int_msg(data->usbdev, data->intpipe, data->new, + data->intpktsize, data->intinterval); +} + +static int usb_kbd_cnt_poll(struct usb_kbd_pdata *data) +{ + struct usb_interface *iface = &data->usbdev->config.interface[0]; + + return usb_get_report(data->usbdev, iface->desc.bInterfaceNumber, + 1, 0, data->new, USB_KBD_BOOT_REPORT_SIZE); +} + +#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; + int diff, tout; + + if (data->lock) + return; + data->lock = 1; + + if (0 > data->do_poll(data)) { + /* exit and lock forever */ + dev_err(&usbdev->dev, + "usb_submit_int_msg() failed. Keyboard disconnect?\n"); + return; + } + diff = memcmp(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE); + tout = get_time_ns() > data->last_report + REPEAT_RATE * MSECOND; + if (diff || tout) { + data->last_report = get_time_ns(); + if (diff) { + pr_debug("%s: old report: %016llx\n", + __func__, + *((volatile uint64_t *)data->old)); + pr_debug("%s: new report: %016llx\n\n", + __func__, + *((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"); + dev_dbg(&usbdev->dev, "Debug enabled\n"); + ret = usb_set_protocol(usbdev, iface->desc.bInterfaceNumber, 0); + if (ret < 0) + return ret; + + ret = usb_set_idle(usbdev, iface->desc.bInterfaceNumber, REPEAT_RATE / 4, 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->new = dma_alloc_coherent(USB_KBD_BOOT_REPORT_SIZE, NULL); + + data->usbdev = usbdev; + data->last_report = get_time_ns(); + + data->ep = &iface->ep_desc[0]; + data->intpipe = usb_rcvintpipe(usbdev, data->ep->bEndpointAddress); + data->intpktsize = min(usb_maxpacket(usbdev, data->intpipe), + USB_KBD_BOOT_REPORT_SIZE); + data->intinterval = data->ep->bInterval; + /* test polling via interrupt endpoint */ + data->do_poll = usb_kbd_int_poll; + ret = data->do_poll(data); + if (ret < 0) { + /* fall back to polling via control enpoint */ + data->do_poll = usb_kbd_cnt_poll; + usb_set_idle(usbdev, + iface->desc.bInterfaceNumber, + REPEAT_RATE / 4, 0); + ret = data->do_poll(data); + if (ret < 0) { + /* no luck */ + kfifo_free(data->recv_fifo); + dma_free_coherent(data->new, 0, + USB_KBD_BOOT_REPORT_SIZE); + free(data); + return ret; + } else + dev_dbg(&usbdev->dev, "poll keyboard via cont ep\n"); + } else + dev_dbg(&usbdev->dev, "poll keyboard via int ep\n"); + + 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); + dma_free_coherent(data->new, 0, USB_KBD_BOOT_REPORT_SIZE); + 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 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2015-09-21 6:19 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2015-09-17 11:18 [RFC v4 0/2] WIP: add usb keyboard driver Peter Mamonov 2015-09-17 11:18 ` [PATCH 1/2] usb: ehci-hcd: port periodic transactions implementation from the u-boot Peter Mamonov 2015-09-17 11:18 ` [PATCH 2/2] input: port usb keyboard driver " Peter Mamonov 2015-09-21 6:18 ` [RFC v4 0/2] WIP: add usb keyboard driver Sascha Hauer -- strict thread matches above, loose matches on Subject: below -- 2015-09-16 13:13 [RFC v3 " Peter Mamonov 2015-09-16 13:13 ` [PATCH 2/2] input: port usb keyboard driver from the u-boot Peter Mamonov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox