mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-30 00:21:06 +00:00
709ea543b9
At present stdio device functions do not get any clue as to which stdio device is being acted on. Some implementations go to great lengths to work around this, such as defining a whole separate set of functions for each possible device. For driver model we need to associate a stdio_dev with a device. It doesn't seem possible to continue with this work-around approach. Instead, add a stdio_dev pointer to each of the stdio member functions. Note: The serial drivers have the same problem, but it is not strictly necessary to fix that to get driver model running. Also, if we convert serial over to driver model the problem will go away. Code size increases by 244 bytes for Thumb2 and 428 for PowerPC. 22: stdio: Pass device pointer to stdio methods arm: (for 2/2 boards) all +244.0 bss -4.0 text +248.0 powerpc: (for 1/1 boards) all +428.0 text +428.0 Signed-off-by: Simon Glass <sjg@chromium.org> Acked-by: Marek Vasut <marex@denx.de> Reviewed-by: Marek Vasut <marex@denx.de>
565 lines
14 KiB
C
565 lines
14 KiB
C
/*
|
|
* (C) Copyright 2001
|
|
* Denis Peter, MPL AG Switzerland
|
|
*
|
|
* Part of this source has been derived from the Linux USB
|
|
* project.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
#include <common.h>
|
|
#include <malloc.h>
|
|
#include <stdio_dev.h>
|
|
#include <asm/byteorder.h>
|
|
|
|
#include <usb.h>
|
|
|
|
/*
|
|
* If overwrite_console returns 1, the stdin, stderr and stdout
|
|
* are switched to the serial port, else the settings in the
|
|
* environment are used
|
|
*/
|
|
#ifdef CONFIG_SYS_CONSOLE_OVERWRITE_ROUTINE
|
|
extern int overwrite_console(void);
|
|
#else
|
|
int overwrite_console(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* Keyboard sampling rate */
|
|
#define REPEAT_RATE (40 / 4) /* 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
|
|
|
|
/* Device name */
|
|
#define DEVNAME "usbkbd"
|
|
|
|
/* 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 {
|
|
uint32_t repeat_delay;
|
|
|
|
uint32_t usb_in_pointer;
|
|
uint32_t usb_out_pointer;
|
|
uint8_t usb_kbd_buffer[USB_KBD_BUFFER_LEN];
|
|
|
|
uint8_t *new;
|
|
uint8_t old[USB_KBD_BOOT_REPORT_SIZE];
|
|
|
|
uint8_t flags;
|
|
};
|
|
|
|
extern int __maybe_unused net_busy_flag;
|
|
|
|
/* The period of time between two calls of usb_kbd_testc(). */
|
|
static unsigned long __maybe_unused kbd_testc_tms;
|
|
|
|
/* Generic keyboard event polling. */
|
|
void usb_kbd_generic_poll(void)
|
|
{
|
|
struct stdio_dev *dev;
|
|
struct usb_device *usb_kbd_dev;
|
|
struct usb_kbd_pdata *data;
|
|
struct usb_interface *iface;
|
|
struct usb_endpoint_descriptor *ep;
|
|
int pipe;
|
|
int maxp;
|
|
|
|
/* Get the pointer to USB Keyboard device pointer */
|
|
dev = stdio_get_by_name(DEVNAME);
|
|
usb_kbd_dev = (struct usb_device *)dev->priv;
|
|
data = usb_kbd_dev->privptr;
|
|
iface = &usb_kbd_dev->config.if_desc[0];
|
|
ep = &iface->ep_desc[0];
|
|
pipe = usb_rcvintpipe(usb_kbd_dev, ep->bEndpointAddress);
|
|
|
|
/* Submit a interrupt transfer request */
|
|
maxp = usb_maxpacket(usb_kbd_dev, pipe);
|
|
usb_submit_int_msg(usb_kbd_dev, pipe, data->new,
|
|
min(maxp, USB_KBD_BOOT_REPORT_SIZE),
|
|
ep->bInterval);
|
|
}
|
|
|
|
/* Puts character in the queue and sets up the in and out pointer. */
|
|
static void usb_kbd_put_queue(struct usb_kbd_pdata *data, char c)
|
|
{
|
|
if (data->usb_in_pointer == USB_KBD_BUFFER_LEN - 1) {
|
|
/* Check for buffer full. */
|
|
if (data->usb_out_pointer == 0)
|
|
return;
|
|
|
|
data->usb_in_pointer = 0;
|
|
} else {
|
|
/* Check for buffer full. */
|
|
if (data->usb_in_pointer == data->usb_out_pointer - 1)
|
|
return;
|
|
|
|
data->usb_in_pointer++;
|
|
}
|
|
|
|
data->usb_kbd_buffer[data->usb_in_pointer] = c;
|
|
}
|
|
|
|
/*
|
|
* Set the LEDs. Since this is used in the irq routine, the control job is
|
|
* issued with a timeout of 0. This means, that the job is queued without
|
|
* waiting for job completion.
|
|
*/
|
|
static void usb_kbd_setled(struct usb_device *dev)
|
|
{
|
|
struct usb_interface *iface = &dev->config.if_desc[0];
|
|
struct usb_kbd_pdata *data = dev->privptr;
|
|
uint32_t leds = data->flags & USB_KBD_LEDMASK;
|
|
|
|
usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
|
|
USB_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
|
0x200, iface->desc.bInterfaceNumber, (void *)&leds, 1, 0);
|
|
}
|
|
|
|
#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)
|
|
{
|
|
uint8_t 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) {
|
|
debug("%c", keycode);
|
|
usb_kbd_put_queue(data, keycode);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t usb_kbd_service_key(struct usb_device *dev, int i, int up)
|
|
{
|
|
uint32_t res = 0;
|
|
struct usb_kbd_pdata *data = dev->privptr;
|
|
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;
|
|
}
|
|
|
|
/* Interrupt service routine */
|
|
static int usb_kbd_irq_worker(struct usb_device *dev)
|
|
{
|
|
struct usb_kbd_pdata *data = dev->privptr;
|
|
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(dev, i, 0);
|
|
res |= usb_kbd_service_key(dev, 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(dev);
|
|
|
|
memcpy(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Keyboard interrupt handler */
|
|
static int usb_kbd_irq(struct usb_device *dev)
|
|
{
|
|
if ((dev->irq_status != 0) ||
|
|
(dev->irq_act_len != USB_KBD_BOOT_REPORT_SIZE)) {
|
|
debug("USB KBD: Error %lX, len %d\n",
|
|
dev->irq_status, dev->irq_act_len);
|
|
return 1;
|
|
}
|
|
|
|
return usb_kbd_irq_worker(dev);
|
|
}
|
|
|
|
/* Interrupt polling */
|
|
static inline void usb_kbd_poll_for_event(struct usb_device *dev)
|
|
{
|
|
#if defined(CONFIG_SYS_USB_EVENT_POLL)
|
|
struct usb_interface *iface;
|
|
struct usb_endpoint_descriptor *ep;
|
|
struct usb_kbd_pdata *data;
|
|
int pipe;
|
|
int maxp;
|
|
|
|
/* Get the pointer to USB Keyboard device pointer */
|
|
data = dev->privptr;
|
|
iface = &dev->config.if_desc[0];
|
|
ep = &iface->ep_desc[0];
|
|
pipe = usb_rcvintpipe(dev, ep->bEndpointAddress);
|
|
|
|
/* Submit a interrupt transfer request */
|
|
maxp = usb_maxpacket(dev, pipe);
|
|
usb_submit_int_msg(dev, pipe, &data->new[0],
|
|
min(maxp, USB_KBD_BOOT_REPORT_SIZE),
|
|
ep->bInterval);
|
|
|
|
usb_kbd_irq_worker(dev);
|
|
#elif defined(CONFIG_SYS_USB_EVENT_POLL_VIA_CONTROL_EP)
|
|
struct usb_interface *iface;
|
|
struct usb_kbd_pdata *data = dev->privptr;
|
|
iface = &dev->config.if_desc[0];
|
|
usb_get_report(dev, iface->desc.bInterfaceNumber,
|
|
1, 0, data->new, USB_KBD_BOOT_REPORT_SIZE);
|
|
if (memcmp(data->old, data->new, USB_KBD_BOOT_REPORT_SIZE))
|
|
usb_kbd_irq_worker(dev);
|
|
#endif
|
|
}
|
|
|
|
/* test if a character is in the queue */
|
|
static int usb_kbd_testc(struct stdio_dev *sdev)
|
|
{
|
|
struct stdio_dev *dev;
|
|
struct usb_device *usb_kbd_dev;
|
|
struct usb_kbd_pdata *data;
|
|
|
|
#ifdef CONFIG_CMD_NET
|
|
/*
|
|
* If net_busy_flag is 1, NET transfer is running,
|
|
* then we check key-pressed every second (first check may be
|
|
* less than 1 second) to improve TFTP booting performance.
|
|
*/
|
|
if (net_busy_flag && (get_timer(kbd_testc_tms) < CONFIG_SYS_HZ))
|
|
return 0;
|
|
kbd_testc_tms = get_timer(0);
|
|
#endif
|
|
dev = stdio_get_by_name(DEVNAME);
|
|
usb_kbd_dev = (struct usb_device *)dev->priv;
|
|
data = usb_kbd_dev->privptr;
|
|
|
|
usb_kbd_poll_for_event(usb_kbd_dev);
|
|
|
|
return !(data->usb_in_pointer == data->usb_out_pointer);
|
|
}
|
|
|
|
/* gets the character from the queue */
|
|
static int usb_kbd_getc(struct stdio_dev *sdev)
|
|
{
|
|
struct stdio_dev *dev;
|
|
struct usb_device *usb_kbd_dev;
|
|
struct usb_kbd_pdata *data;
|
|
|
|
dev = stdio_get_by_name(DEVNAME);
|
|
usb_kbd_dev = (struct usb_device *)dev->priv;
|
|
data = usb_kbd_dev->privptr;
|
|
|
|
while (data->usb_in_pointer == data->usb_out_pointer)
|
|
usb_kbd_poll_for_event(usb_kbd_dev);
|
|
|
|
if (data->usb_out_pointer == USB_KBD_BUFFER_LEN - 1)
|
|
data->usb_out_pointer = 0;
|
|
else
|
|
data->usb_out_pointer++;
|
|
|
|
return data->usb_kbd_buffer[data->usb_out_pointer];
|
|
}
|
|
|
|
/* probes the USB device dev for keyboard type. */
|
|
static int usb_kbd_probe(struct usb_device *dev, unsigned int ifnum)
|
|
{
|
|
struct usb_interface *iface;
|
|
struct usb_endpoint_descriptor *ep;
|
|
struct usb_kbd_pdata *data;
|
|
int pipe, maxp;
|
|
|
|
if (dev->descriptor.bNumConfigurations != 1)
|
|
return 0;
|
|
|
|
iface = &dev->config.if_desc[ifnum];
|
|
|
|
if (iface->desc.bInterfaceClass != 3)
|
|
return 0;
|
|
|
|
if (iface->desc.bInterfaceSubClass != 1)
|
|
return 0;
|
|
|
|
if (iface->desc.bInterfaceProtocol != 1)
|
|
return 0;
|
|
|
|
if (iface->desc.bNumEndpoints != 1)
|
|
return 0;
|
|
|
|
ep = &iface->ep_desc[0];
|
|
|
|
/* Check if endpoint 1 is interrupt endpoint */
|
|
if (!(ep->bEndpointAddress & 0x80))
|
|
return 0;
|
|
|
|
if ((ep->bmAttributes & 3) != 3)
|
|
return 0;
|
|
|
|
debug("USB KBD: found set protocol...\n");
|
|
|
|
data = malloc(sizeof(struct usb_kbd_pdata));
|
|
if (!data) {
|
|
printf("USB KBD: Error allocating private data\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Clear private data */
|
|
memset(data, 0, sizeof(struct usb_kbd_pdata));
|
|
|
|
/* allocate input buffer aligned and sized to USB DMA alignment */
|
|
data->new = memalign(USB_DMA_MINALIGN,
|
|
roundup(USB_KBD_BOOT_REPORT_SIZE, USB_DMA_MINALIGN));
|
|
|
|
/* Insert private data into USB device structure */
|
|
dev->privptr = data;
|
|
|
|
/* Set IRQ handler */
|
|
dev->irq_handle = usb_kbd_irq;
|
|
|
|
pipe = usb_rcvintpipe(dev, ep->bEndpointAddress);
|
|
maxp = usb_maxpacket(dev, pipe);
|
|
|
|
/* We found a USB Keyboard, install it. */
|
|
usb_set_protocol(dev, iface->desc.bInterfaceNumber, 0);
|
|
|
|
debug("USB KBD: found set idle...\n");
|
|
usb_set_idle(dev, iface->desc.bInterfaceNumber, REPEAT_RATE, 0);
|
|
|
|
debug("USB KBD: enable interrupt pipe...\n");
|
|
if (usb_submit_int_msg(dev, pipe, data->new,
|
|
min(maxp, USB_KBD_BOOT_REPORT_SIZE),
|
|
ep->bInterval) < 0) {
|
|
printf("Failed to get keyboard state from device %04x:%04x\n",
|
|
dev->descriptor.idVendor, dev->descriptor.idProduct);
|
|
/* Abort, we don't want to use that non-functional keyboard. */
|
|
return 0;
|
|
}
|
|
|
|
/* Success. */
|
|
return 1;
|
|
}
|
|
|
|
/* Search for keyboard and register it if found. */
|
|
int drv_usb_kbd_init(void)
|
|
{
|
|
struct stdio_dev usb_kbd_dev, *old_dev;
|
|
struct usb_device *dev;
|
|
char *stdinname = getenv("stdin");
|
|
int error, i;
|
|
|
|
/* Scan all USB Devices */
|
|
for (i = 0; i < USB_MAX_DEVICE; i++) {
|
|
/* Get USB device. */
|
|
dev = usb_get_dev_index(i);
|
|
if (!dev)
|
|
return -1;
|
|
|
|
if (dev->devnum == -1)
|
|
continue;
|
|
|
|
/* Try probing the keyboard */
|
|
if (usb_kbd_probe(dev, 0) != 1)
|
|
continue;
|
|
|
|
/* We found a keyboard, check if it is already registered. */
|
|
debug("USB KBD: found set up device.\n");
|
|
old_dev = stdio_get_by_name(DEVNAME);
|
|
if (old_dev) {
|
|
/* Already registered, just return ok. */
|
|
debug("USB KBD: is already registered.\n");
|
|
usb_kbd_deregister();
|
|
return 1;
|
|
}
|
|
|
|
/* Register the keyboard */
|
|
debug("USB KBD: register.\n");
|
|
memset(&usb_kbd_dev, 0, sizeof(struct stdio_dev));
|
|
strcpy(usb_kbd_dev.name, DEVNAME);
|
|
usb_kbd_dev.flags = DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;
|
|
usb_kbd_dev.getc = usb_kbd_getc;
|
|
usb_kbd_dev.tstc = usb_kbd_testc;
|
|
usb_kbd_dev.priv = (void *)dev;
|
|
error = stdio_register(&usb_kbd_dev);
|
|
if (error)
|
|
return error;
|
|
|
|
#ifdef CONFIG_CONSOLE_MUX
|
|
error = iomux_doenv(stdin, stdinname);
|
|
if (error)
|
|
return error;
|
|
#else
|
|
/* Check if this is the standard input device. */
|
|
if (strcmp(stdinname, DEVNAME))
|
|
return 1;
|
|
|
|
/* Reassign the console */
|
|
if (overwrite_console())
|
|
return 1;
|
|
|
|
error = console_assign(stdin, DEVNAME);
|
|
if (error)
|
|
return error;
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* No USB Keyboard found */
|
|
return -1;
|
|
}
|
|
|
|
/* Deregister the keyboard. */
|
|
int usb_kbd_deregister(void)
|
|
{
|
|
#ifdef CONFIG_SYS_STDIO_DEREGISTER
|
|
return stdio_deregister(DEVNAME);
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|