
From: Simon Glass <sjg@chromium.org> Add a basic mouse driver for USB mice. It only handles very basic mice so assumes that the reports are in the basic format described by the USB HID specification 1.11. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/input/Kconfig | 8 + drivers/input/Makefile | 1 + drivers/input/usb_mouse.c | 325 ++++++++++++++++++++++++++++++++++++++ include/usb.h | 1 + 4 files changed, 335 insertions(+) create mode 100644 drivers/input/usb_mouse.c diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 6bfee40ccac..bcab0408d70 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -109,3 +109,11 @@ config MOUSE graphics boot menus and the like. The driver can provide mouse events based on user interaction and these can be used to control U-Boot's operation. + +config USB_MOUSE + bool "USB mouse support" + help + This enables using a USB mouse to control a feature in U-Boot, + typically a boot menu. The driver uses the USB boot interface of + the mouse and attempts to auto-configure itself for normal + operation. diff --git a/drivers/input/Makefile b/drivers/input/Makefile index a4938d19903..f1c136b214c 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -18,3 +18,4 @@ endif obj-$(CONFIG_MOUSE) += mouse-uclass.o obj-$(CONFIG_SANDBOX) += sandbox_mouse.o +obj-$(CONFIG_USB_MOUSE) += usb_mouse.o diff --git a/drivers/input/usb_mouse.c b/drivers/input/usb_mouse.c new file mode 100644 index 00000000000..b5b5ba2f894 --- /dev/null +++ b/drivers/input/usb_mouse.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB mouse driver (parts taken from usb_kbd.c) + * + * (C) Copyright 2001 + * Denis Peter, MPL AG Switzerland + * + * Part of this source has been derived from the Linux USB + * project. + * + * Copyright 2020 Google LLC + */ + +#define LOG_CATEGORY UCLASS_MOUSE + +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <mouse.h> +#include <time.h> +#include <usb.h> + +enum { + RPT_BUTTON, + RPT_XREL, + RPT_YREL, + RPT_SCROLLY, +}; + +struct usb_mouse_priv { + ulong intpipe; + int intpktsize; + int intinterval; + ulong last_report; + struct int_queue *intq; + u32 repeat_delay; + int xrel; + int yrel; + int x; + int y; + int buttons; + int old_buttons; + int yscroll; + /* + * TODO(sjg@chromium.org): Use an array instead, with the + * DM_FLAG_ALLOC_PRIV_DMA flag + */ + s8 *buf; + u8 flags; +}; + +/* Interrupt service routine */ +static int usb_mouse_irq_worker(struct udevice *dev) +{ + struct usb_mouse_priv *priv = dev_get_priv(dev); + s8 *buf = priv->buf; + + priv->buttons = buf[RPT_BUTTON]; + priv->xrel = buf[RPT_XREL]; + if (priv->xrel < -127) + priv->xrel = 0; + priv->yrel = buf[RPT_YREL]; + if (priv->yrel < -127) + priv->yrel = 0; + priv->yscroll = buf[RPT_SCROLLY]; + + return 1; +} + +/* Mouse interrupt handler */ +static int usb_mouse_irq(struct usb_device *udev) +{ + struct udevice *dev = udev->dev; + + if (udev->irq_status || udev->irq_act_len != USB_MOUSE_BOOT_REPORT_SIZE) { + log_warning("Error %lx, len %d\n", udev->irq_status, + udev->irq_act_len); + return 1; + } + + return usb_mouse_irq_worker(dev); +} + +/* Interrupt polling */ +static void usb_mouse_poll_for_event(struct udevice *dev) +{ + struct usb_device *udev = dev_get_parent_priv(dev); + struct usb_mouse_priv *priv = dev_get_priv(dev); + int ret; + + if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL)) { + /* Submit an interrupt transfer request */ + if (usb_int_msg(udev, priv->intpipe, priv->buf, + priv->intpktsize, priv->intinterval, true) >= 0) + usb_mouse_irq_worker(dev); + } else if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_CONTROL_EP) || + IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_INT_QUEUE)) { + bool got_report = false; + + if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_CONTROL_EP)) { + struct usb_interface *iface; + + iface = &udev->config.if_desc[0]; + ret = usb_get_report(udev, iface->desc.bInterfaceNumber, + 1, 0, priv->buf, + USB_MOUSE_BOOT_REPORT_SIZE); + printf("control ret=%d\b", ret); + } else { + if (poll_int_queue(udev, priv->intq)) { + usb_mouse_irq_worker(dev); + /* We've consumed all queued int packets, create new */ + destroy_int_queue(udev, priv->intq); + priv->intq = create_int_queue(udev, + priv->intpipe, 1, + USB_MOUSE_BOOT_REPORT_SIZE, priv->buf, + priv->intinterval); + got_report = true; + } + } + if (got_report) + priv->last_report = get_timer(0); + } +} + +static int usb_mouse_get_event(struct udevice *dev, struct mouse_event *event) +{ + struct usb_mouse_priv *priv = dev_get_priv(dev); + + if (priv->buttons != priv->old_buttons) { + struct mouse_button *but = &event->button; + u8 diff; + int i; + + event->type = MOUSE_EV_BUTTON; + diff = priv->buttons ^ priv->old_buttons; + log_debug("buttons=%d, old=%d, diff=%d\n", priv->buttons, + priv->old_buttons, diff); + for (i = 0; i < 3; i++) { + u8 mask = 1 << i; + + if (diff && mask) { + but->button = i; + but->press_state = priv->buttons & mask; + but->clicks = 1; + but->x = priv->x; + but->y = priv->y; + priv->old_buttons ^= mask; + break; + } + } + log_debug(" end: buttons=%d, old=%d, diff=%d\n", priv->buttons, + priv->old_buttons, diff); + } else if (priv->xrel || priv->yrel) { + struct mouse_motion *motion = &event->motion; + + priv->x += priv->xrel; + priv->x = max(priv->x, 0); + priv->x = min(priv->x, 0xffff); + + priv->y += priv->yrel; + priv->y = max(priv->y, 0); + priv->y = min(priv->y, 0xffff); + + event->type = MOUSE_EV_MOTION; + motion->state = priv->buttons; + motion->x = priv->x; + motion->y = priv->y; + motion->xrel = priv->xrel; + motion->yrel = priv->yrel; + priv->xrel = 0; + priv->yrel = 0; + } else { + usb_mouse_poll_for_event(dev); + return -EAGAIN; + } + + return 0; +} + +static int check_mouse(struct usb_device *udev, int ifnum) +{ + struct usb_endpoint_descriptor *ep; + struct usb_interface *iface; + + if (udev->descriptor.bNumConfigurations != 1) + return log_msg_ret("numcfg", -EINVAL); + + iface = &udev->config.if_desc[ifnum]; + + if (iface->desc.bInterfaceClass != USB_CLASS_HID) + return log_msg_ret("if class", -EINVAL); + + if (iface->desc.bInterfaceSubClass != USB_SUB_HID_BOOT) + return log_msg_ret("if subclass", -EINVAL); + + if (iface->desc.bInterfaceProtocol != USB_PROT_HID_MOUSE) + return log_msg_ret("if protocol", -EINVAL); + + if (iface->desc.bNumEndpoints != 1) + return log_msg_ret("num endpoints", -EINVAL); + + ep = &iface->ep_desc[0]; + + /* Check if endpoint 1 is interrupt endpoint */ + if (!(ep->bEndpointAddress & 0x80)) + return log_msg_ret("ep not irq", -EINVAL); + + if ((ep->bmAttributes & 3) != 3) + return log_msg_ret("ep attr", -EINVAL); + + return 0; +} + +/* probes the USB device dev for mouse type */ +static int usb_mouse_probe(struct udevice *dev) +{ + struct usb_device *udev = dev_get_parent_priv(dev); + struct usb_mouse_priv *priv = dev_get_priv(dev); + struct usb_endpoint_descriptor *ep; + struct usb_interface *iface; + const int ifnum = 0; + int ret; + + ret = check_mouse(udev, ifnum); + if (ret) { + log_warning("Mouse detect fail (err=%d)\n", ret); + return log_msg_ret("probe", ret); + } + log_debug("USB mouse: found set protocol...\n"); + + /* allocate input buffer aligned and sized to USB DMA alignment */ + priv->buf = memalign(USB_DMA_MINALIGN, + roundup(USB_MOUSE_BOOT_REPORT_SIZE, USB_DMA_MINALIGN)); + + /* Insert private data into USB device structure */ + udev->privptr = priv; + + /* Set IRQ handler */ + udev->irq_handle = usb_mouse_irq; + + iface = &udev->config.if_desc[ifnum]; + ep = &iface->ep_desc[0]; + priv->intpipe = usb_rcvintpipe(udev, ep->bEndpointAddress); + priv->intpktsize = min(usb_maxpacket(udev, priv->intpipe), + USB_MOUSE_BOOT_REPORT_SIZE); + priv->intinterval = ep->bInterval; + priv->last_report = -1; + + /* We found a USB Keyboard, install it. */ + usb_set_protocol(udev, iface->desc.bInterfaceNumber, 0); + + log_debug("Found set idle...\n"); + usb_set_idle(udev, iface->desc.bInterfaceNumber, 0, 0); + + log_debug("Enable interrupt pipe...\n"); + if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_INT_QUEUE)) { + priv->intq = create_int_queue(udev, priv->intpipe, 1, + USB_MOUSE_BOOT_REPORT_SIZE, + priv->buf, priv->intinterval); + printf("priv->intq %p\n", priv->intq); + ret = priv->intq ? 0 : -EBUSY; + } else if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_CONTROL_EP)) { + ret = usb_get_report(udev, iface->desc.bInterfaceNumber, 1, 0, + priv->buf, USB_MOUSE_BOOT_REPORT_SIZE); + } else { + ret = usb_int_msg(udev, priv->intpipe, priv->buf, + priv->intpktsize, priv->intinterval, false); + } + if (ret < 0) { + log_warning("Failed to get mouse state from device %04x:%04x (err=%d)\n", + udev->descriptor.idVendor, + udev->descriptor.idProduct, ret); + /* Abort, we don't want to use that non-functional keyboard */ + return ret; + } + log_info("USB mouse OK\n"); + + /* Success */ + return 0; +} + +static int usb_mouse_remove(struct udevice *dev) +{ + struct usb_device *udev = dev_get_parent_priv(dev); + struct usb_mouse_priv *priv = dev_get_priv(dev); + + if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_INT_QUEUE)) + destroy_int_queue(udev, priv->intq); + free(priv->buf); + + return 0; +} + +const struct mouse_ops usb_mouse_ops = { + .get_event = usb_mouse_get_event, +}; + +static const struct udevice_id usb_mouse_ids[] = { + { .compatible = "usb-mouse" }, + { } +}; + +U_BOOT_DRIVER(usb_mouse) = { + .name = "usb_mouse", + .id = UCLASS_MOUSE, + .of_match = usb_mouse_ids, + .ops = &usb_mouse_ops, + .probe = usb_mouse_probe, + .remove = usb_mouse_remove, + .priv_auto = sizeof(struct usb_mouse_priv), +}; + +static const struct usb_device_id mouse_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = USB_SUB_HID_BOOT, + .bInterfaceProtocol = USB_PROT_HID_MOUSE, + }, + { } /* Terminating entry */ +}; + +U_BOOT_USB_DEVICE(usb_mouse, mouse_id_table); diff --git a/include/usb.h b/include/usb.h index be37ed272e1..7e05ef26d6b 100644 --- a/include/usb.h +++ b/include/usb.h @@ -255,6 +255,7 @@ int usb_host_eth_scan(int mode); * Appendix B of HID Device Class Definition 1.11 */ #define USB_KBD_BOOT_REPORT_SIZE 8 +#define USB_MOUSE_BOOT_REPORT_SIZE 8 /* * usb_init() - initialize the USB Controllers -- 2.43.0