diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-03-16 08:22:49 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-03-16 08:22:49 +0100 |
commit | a8ab3e76297ea85d92f4ee0833bd469816a13ccf (patch) | |
tree | 0bb5724a3d050a5c84203fc38b23447166035b9e /drivers/usb/gadget | |
parent | xhci-pci: Allow host runtime PM as default for Intel Tiger Lake xHCI (diff) | |
parent | dt-bindings: usb: add documentation for aspeed usb-vhub (diff) | |
download | linux-a8ab3e76297ea85d92f4ee0833bd469816a13ccf.tar.xz linux-a8ab3e76297ea85d92f4ee0833bd469816a13ccf.zip |
Merge tag 'usb-for-v5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next
Felipe writes:
USB: changes for v5.7 merge window
Lots of changes on dwc3 this time, most of them from Thinh fixing a
bunch of really old mishaps on the driver.
DWC2 got support for STM32MP15 and a couple RockChip SoCs while DWC3
learned about Amlogic A1 family.
Apart from these, we have a few spelling fixes and other minor
non-critical fixes all over the place.
Signed-off-by: Felipe Balbi <balbi@kernel.org>
* tag 'usb-for-v5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb: (41 commits)
dt-bindings: usb: add documentation for aspeed usb-vhub
ARM: dts: aspeed-g4: add vhub port and endpoint properties
ARM: dts: aspeed-g5: add vhub port and endpoint properties
ARM: dts: aspeed-g6: add usb functions
usb: gadget: aspeed: add ast2600 vhub support
usb: gadget: aspeed: read vhub properties from device tree
usb: gadget: aspeed: support per-vhub usb descriptors
usb: gadget: f_phonet: Replace zero-length array with flexible-array member
usb: gadget: composite: Inform controller driver of self-powered
usb: gadget: amd5536udc: fix spelling mistake "reserverd" -> "reserved"
udc: s3c-hsudc: Silence warning about supplies during deferred probe
usb: dwc2: Silence warning about supplies during deferred probe
dt-bindings: usb: dwc2: add compatible property for rk3368 usb
dt-bindings: usb: dwc2: add compatible property for rk3328 usb
usb: gadget: add raw-gadget interface
usb: dwc2: Implement set_selfpowered()
usb: dwc3: qcom: Replace <linux/clk-provider.h> by <linux/of_clk.h>
usb: dwc3: core: don't do suspend for device mode if already suspended
usb: dwc3: Rework resets initialization to be more flexible
usb: dwc3: Rework clock initialization to be more flexible
...
Diffstat (limited to 'drivers/usb/gadget')
-rw-r--r-- | drivers/usb/gadget/composite.c | 9 | ||||
-rw-r--r-- | drivers/usb/gadget/legacy/Kconfig | 11 | ||||
-rw-r--r-- | drivers/usb/gadget/legacy/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/gadget/legacy/raw_gadget.c | 1078 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/Kconfig | 4 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/core.c | 71 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/dev.c | 30 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/epn.c | 4 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/hub.c | 58 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 43 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/max3420_udc.c | 1331 | ||||
-rw-r--r-- | drivers/usb/gadget/udc/tegra-xudc.c | 5 |
14 files changed, 2581 insertions, 75 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 223f72d4d9ed..cb4950cf1cdc 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -861,6 +861,11 @@ static int set_config(struct usb_composite_dev *cdev, else power = min(power, 900U); done: + if (power <= USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_set_selfpowered(gadget); + else + usb_gadget_clear_selfpowered(gadget); + usb_gadget_vbus_draw(gadget, power); if (result >= 0 && cdev->delayed_status) result = USB_GADGET_DELAYED_STATUS; @@ -2279,6 +2284,7 @@ void composite_suspend(struct usb_gadget *gadget) cdev->suspended = 1; + usb_gadget_set_selfpowered(gadget); usb_gadget_vbus_draw(gadget, 2); } @@ -2307,6 +2313,9 @@ void composite_resume(struct usb_gadget *gadget) else maxpower = min(maxpower, 900U); + if (maxpower > USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_clear_selfpowered(gadget); + usb_gadget_vbus_draw(gadget, maxpower); } diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 6e7e1a9202e6..a7064e21d9f2 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -516,4 +516,15 @@ config USB_G_WEBCAM Say "y" to link the driver statically, or "m" to build a dynamically linked module called "g_webcam". +config USB_RAW_GADGET + tristate "USB Raw Gadget" + help + USB Raw Gadget is a kernel module that provides a userspace interface + for the USB Gadget subsystem. Essentially it allows to emulate USB + devices from userspace. See Documentation/usb/raw-gadget.rst for + details. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "raw_gadget". + endchoice diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile index abd0c3e66a05..4d864bf82799 100644 --- a/drivers/usb/gadget/legacy/Makefile +++ b/drivers/usb/gadget/legacy/Makefile @@ -43,3 +43,4 @@ obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o +obj-$(CONFIG_USB_RAW_GADGET) += raw_gadget.o diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c new file mode 100644 index 000000000000..76406343fbe5 --- /dev/null +++ b/drivers/usb/gadget/legacy/raw_gadget.c @@ -0,0 +1,1078 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Raw Gadget driver. + * See Documentation/usb/raw-gadget.rst for more details. + * + * Andrey Konovalov <andreyknvl@gmail.com> + */ + +#include <linux/compiler.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/kref.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/semaphore.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/wait.h> + +#include <linux/usb.h> +#include <linux/usb/ch9.h> +#include <linux/usb/ch11.h> +#include <linux/usb/gadget.h> + +#include <uapi/linux/usb/raw_gadget.h> + +#define DRIVER_DESC "USB Raw Gadget" +#define DRIVER_NAME "raw-gadget" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Andrey Konovalov"); +MODULE_LICENSE("GPL"); + +/*----------------------------------------------------------------------*/ + +#define RAW_EVENT_QUEUE_SIZE 16 + +struct raw_event_queue { + /* See the comment in raw_event_queue_fetch() for locking details. */ + spinlock_t lock; + struct semaphore sema; + struct usb_raw_event *events[RAW_EVENT_QUEUE_SIZE]; + int size; +}; + +static void raw_event_queue_init(struct raw_event_queue *queue) +{ + spin_lock_init(&queue->lock); + sema_init(&queue->sema, 0); + queue->size = 0; +} + +static int raw_event_queue_add(struct raw_event_queue *queue, + enum usb_raw_event_type type, size_t length, const void *data) +{ + unsigned long flags; + struct usb_raw_event *event; + + spin_lock_irqsave(&queue->lock, flags); + if (WARN_ON(queue->size >= RAW_EVENT_QUEUE_SIZE)) { + spin_unlock_irqrestore(&queue->lock, flags); + return -ENOMEM; + } + event = kmalloc(sizeof(*event) + length, GFP_ATOMIC); + if (!event) { + spin_unlock_irqrestore(&queue->lock, flags); + return -ENOMEM; + } + event->type = type; + event->length = length; + if (event->length) + memcpy(&event->data[0], data, length); + queue->events[queue->size] = event; + queue->size++; + up(&queue->sema); + spin_unlock_irqrestore(&queue->lock, flags); + return 0; +} + +static struct usb_raw_event *raw_event_queue_fetch( + struct raw_event_queue *queue) +{ + unsigned long flags; + struct usb_raw_event *event; + + /* + * This function can be called concurrently. We first check that + * there's at least one event queued by decrementing the semaphore, + * and then take the lock to protect queue struct fields. + */ + if (down_interruptible(&queue->sema)) + return NULL; + spin_lock_irqsave(&queue->lock, flags); + if (WARN_ON(!queue->size)) + return NULL; + event = queue->events[0]; + queue->size--; + memmove(&queue->events[0], &queue->events[1], + queue->size * sizeof(queue->events[0])); + spin_unlock_irqrestore(&queue->lock, flags); + return event; +} + +static void raw_event_queue_destroy(struct raw_event_queue *queue) +{ + int i; + + for (i = 0; i < queue->size; i++) + kfree(queue->events[i]); + queue->size = 0; +} + +/*----------------------------------------------------------------------*/ + +struct raw_dev; + +#define USB_RAW_MAX_ENDPOINTS 32 + +enum ep_state { + STATE_EP_DISABLED, + STATE_EP_ENABLED, +}; + +struct raw_ep { + struct raw_dev *dev; + enum ep_state state; + struct usb_ep *ep; + struct usb_request *req; + bool urb_queued; + bool disabling; + ssize_t status; +}; + +enum dev_state { + STATE_DEV_INVALID = 0, + STATE_DEV_OPENED, + STATE_DEV_INITIALIZED, + STATE_DEV_RUNNING, + STATE_DEV_CLOSED, + STATE_DEV_FAILED +}; + +struct raw_dev { + struct kref count; + spinlock_t lock; + + const char *udc_name; + struct usb_gadget_driver driver; + + /* Reference to misc device: */ + struct device *dev; + + /* Protected by lock: */ + enum dev_state state; + bool gadget_registered; + struct usb_gadget *gadget; + struct usb_request *req; + bool ep0_in_pending; + bool ep0_out_pending; + bool ep0_urb_queued; + ssize_t ep0_status; + struct raw_ep eps[USB_RAW_MAX_ENDPOINTS]; + + struct completion ep0_done; + struct raw_event_queue queue; +}; + +static struct raw_dev *dev_new(void) +{ + struct raw_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + /* Matches kref_put() in raw_release(). */ + kref_init(&dev->count); + spin_lock_init(&dev->lock); + init_completion(&dev->ep0_done); + raw_event_queue_init(&dev->queue); + return dev; +} + +static void dev_free(struct kref *kref) +{ + struct raw_dev *dev = container_of(kref, struct raw_dev, count); + int i; + + kfree(dev->udc_name); + kfree(dev->driver.udc_name); + if (dev->req) { + if (dev->ep0_urb_queued) + usb_ep_dequeue(dev->gadget->ep0, dev->req); + usb_ep_free_request(dev->gadget->ep0, dev->req); + } + raw_event_queue_destroy(&dev->queue); + for (i = 0; i < USB_RAW_MAX_ENDPOINTS; i++) { + if (dev->eps[i].state != STATE_EP_ENABLED) + continue; + usb_ep_disable(dev->eps[i].ep); + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); + kfree(dev->eps[i].ep->desc); + dev->eps[i].state = STATE_EP_DISABLED; + } + kfree(dev); +} + +/*----------------------------------------------------------------------*/ + +static int raw_queue_event(struct raw_dev *dev, + enum usb_raw_event_type type, size_t length, const void *data) +{ + int ret = 0; + unsigned long flags; + + ret = raw_event_queue_add(&dev->queue, type, length, data); + if (ret < 0) { + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + spin_unlock_irqrestore(&dev->lock, flags); + } + return ret; +} + +static void gadget_ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct raw_dev *dev = req->context; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (req->status) + dev->ep0_status = req->status; + else + dev->ep0_status = req->actual; + if (dev->ep0_in_pending) + dev->ep0_in_pending = false; + else + dev->ep0_out_pending = false; + spin_unlock_irqrestore(&dev->lock, flags); + + complete(&dev->ep0_done); +} + +static int gadget_bind(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + int ret = 0; + struct raw_dev *dev = container_of(driver, struct raw_dev, driver); + struct usb_request *req; + unsigned long flags; + + if (strcmp(gadget->name, dev->udc_name) != 0) + return -ENODEV; + + set_gadget_data(gadget, dev); + req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!req) { + dev_err(&gadget->dev, "usb_ep_alloc_request failed\n"); + set_gadget_data(gadget, NULL); + return -ENOMEM; + } + + spin_lock_irqsave(&dev->lock, flags); + dev->req = req; + dev->req->context = dev; + dev->req->complete = gadget_ep0_complete; + dev->gadget = gadget; + spin_unlock_irqrestore(&dev->lock, flags); + + /* Matches kref_put() in gadget_unbind(). */ + kref_get(&dev->count); + + ret = raw_queue_event(dev, USB_RAW_EVENT_CONNECT, 0, NULL); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue event\n"); + + return ret; +} + +static void gadget_unbind(struct usb_gadget *gadget) +{ + struct raw_dev *dev = get_gadget_data(gadget); + + set_gadget_data(gadget, NULL); + /* Matches kref_get() in gadget_bind(). */ + kref_put(&dev->count, dev_free); +} + +static int gadget_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + int ret = 0; + struct raw_dev *dev = get_gadget_data(gadget); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_err(&gadget->dev, "ignoring, device is not running\n"); + ret = -ENODEV; + goto out_unlock; + } + if (dev->ep0_in_pending || dev->ep0_out_pending) { + dev_dbg(&gadget->dev, "stalling, request already pending\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((ctrl->bRequestType & USB_DIR_IN) && ctrl->wLength) + dev->ep0_in_pending = true; + else + dev->ep0_out_pending = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = raw_queue_event(dev, USB_RAW_EVENT_CONTROL, sizeof(*ctrl), ctrl); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue event\n"); + goto out; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); +out: + return ret; +} + +/* These are currently unused but present in case UDC driver requires them. */ +static void gadget_disconnect(struct usb_gadget *gadget) { } +static void gadget_suspend(struct usb_gadget *gadget) { } +static void gadget_resume(struct usb_gadget *gadget) { } +static void gadget_reset(struct usb_gadget *gadget) { } + +/*----------------------------------------------------------------------*/ + +static struct miscdevice raw_misc_device; + +static int raw_open(struct inode *inode, struct file *fd) +{ + struct raw_dev *dev; + + /* Nonblocking I/O is not supported yet. */ + if (fd->f_flags & O_NONBLOCK) + return -EINVAL; + + dev = dev_new(); + if (!dev) + return -ENOMEM; + fd->private_data = dev; + dev->state = STATE_DEV_OPENED; + dev->dev = raw_misc_device.this_device; + return 0; +} + +static int raw_release(struct inode *inode, struct file *fd) +{ + int ret = 0; + struct raw_dev *dev = fd->private_data; + unsigned long flags; + bool unregister = false; + + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_CLOSED; + if (!dev->gadget) { + spin_unlock_irqrestore(&dev->lock, flags); + goto out_put; + } + if (dev->gadget_registered) + unregister = true; + dev->gadget_registered = false; + spin_unlock_irqrestore(&dev->lock, flags); + + if (unregister) { + ret = usb_gadget_unregister_driver(&dev->driver); + if (ret != 0) + dev_err(dev->dev, + "usb_gadget_unregister_driver() failed with %d\n", + ret); + /* Matches kref_get() in raw_ioctl_run(). */ + kref_put(&dev->count, dev_free); + } + +out_put: + /* Matches dev_new() in raw_open(). */ + kref_put(&dev->count, dev_free); + return ret; +} + +/*----------------------------------------------------------------------*/ + +static int raw_ioctl_init(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + struct usb_raw_init arg; + char *udc_driver_name; + char *udc_device_name; + unsigned long flags; + + ret = copy_from_user(&arg, (void __user *)value, sizeof(arg)); + if (ret) + return ret; + + switch (arg.speed) { + case USB_SPEED_UNKNOWN: + arg.speed = USB_SPEED_HIGH; + break; + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + break; + default: + return -EINVAL; + } + + udc_driver_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); + if (!udc_driver_name) + return -ENOMEM; + ret = strscpy(udc_driver_name, &arg.driver_name[0], + UDC_NAME_LENGTH_MAX); + if (ret < 0) { + kfree(udc_driver_name); + return ret; + } + ret = 0; + + udc_device_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); + if (!udc_device_name) { + kfree(udc_driver_name); + return -ENOMEM; + } + ret = strscpy(udc_device_name, &arg.device_name[0], + UDC_NAME_LENGTH_MAX); + if (ret < 0) { + kfree(udc_driver_name); + kfree(udc_device_name); + return ret; + } + ret = 0; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_OPENED) { + dev_dbg(dev->dev, "fail, device is not opened\n"); + kfree(udc_driver_name); + kfree(udc_device_name); + ret = -EINVAL; + goto out_unlock; + } + dev->udc_name = udc_driver_name; + + dev->driver.function = DRIVER_DESC; + dev->driver.max_speed = arg.speed; + dev->driver.setup = gadget_setup; + dev->driver.disconnect = gadget_disconnect; + dev->driver.bind = gadget_bind; + dev->driver.unbind = gadget_unbind; + dev->driver.suspend = gadget_suspend; + dev->driver.resume = gadget_resume; + dev->driver.reset = gadget_reset; + dev->driver.driver.name = DRIVER_NAME; + dev->driver.udc_name = udc_device_name; + dev->driver.match_existing_only = 1; + + dev->state = STATE_DEV_INITIALIZED; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_run(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + if (value) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_INITIALIZED) { + dev_dbg(dev->dev, "fail, device is not initialized\n"); + ret = -EINVAL; + goto out_unlock; + } + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_gadget_probe_driver(&dev->driver); + + spin_lock_irqsave(&dev->lock, flags); + if (ret) { + dev_err(dev->dev, + "fail, usb_gadget_probe_driver returned %d\n", ret); + dev->state = STATE_DEV_FAILED; + goto out_unlock; + } + dev->gadget_registered = true; + dev->state = STATE_DEV_RUNNING; + /* Matches kref_put() in raw_release(). */ + kref_get(&dev->count); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_event_fetch(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + struct usb_raw_event arg; + unsigned long flags; + struct usb_raw_event *event; + uint32_t length; + + ret = copy_from_user(&arg, (void __user *)value, sizeof(arg)); + if (ret) + return ret; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&dev->lock, flags); + + event = raw_event_queue_fetch(&dev->queue); + if (!event) { + dev_dbg(&dev->gadget->dev, "event fetching interrupted\n"); + return -EINTR; + } + length = min(arg.length, event->length); + ret = copy_to_user((void __user *)value, event, + sizeof(*event) + length); + return ret; +} + +static void *raw_alloc_io_data(struct usb_raw_ep_io *io, void __user *ptr, + bool get_from_user) +{ + int ret; + void *data; + + ret = copy_from_user(io, ptr, sizeof(*io)); + if (ret) + return ERR_PTR(ret); + if (io->ep >= USB_RAW_MAX_ENDPOINTS) + return ERR_PTR(-EINVAL); + if (!usb_raw_io_flags_valid(io->flags)) + return ERR_PTR(-EINVAL); + if (io->length > PAGE_SIZE) + return ERR_PTR(-EINVAL); + if (get_from_user) + data = memdup_user(ptr + sizeof(*io), io->length); + else { + data = kmalloc(io->length, GFP_KERNEL); + if (!data) + data = ERR_PTR(-ENOMEM); + } + return data; +} + +static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io, + void *data, bool in) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->ep0_urb_queued) { + dev_dbg(&dev->gadget->dev, "fail, urb already queued\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((in && !dev->ep0_in_pending) || + (!in && !dev->ep0_out_pending)) { + dev_dbg(&dev->gadget->dev, "fail, wrong direction\n"); + ret = -EBUSY; + goto out_unlock; + } + if (WARN_ON(in && dev->ep0_out_pending)) { + ret = -ENODEV; + dev->state = STATE_DEV_FAILED; + goto out_done; + } + if (WARN_ON(!in && dev->ep0_in_pending)) { + ret = -ENODEV; + dev->state = STATE_DEV_FAILED; + goto out_done; + } + + dev->req->buf = data; + dev->req->length = io->length; + dev->req->zero = usb_raw_io_flags_zero(io->flags); + dev->ep0_urb_queued = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(dev->gadget->ep0, dev->req, GFP_KERNEL); + if (ret) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_queue returned %d\n", ret); + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + goto out_done; + } + + ret = wait_for_completion_interruptible(&dev->ep0_done); + if (ret) { + dev_dbg(&dev->gadget->dev, "wait interrupted\n"); + usb_ep_dequeue(dev->gadget->ep0, dev->req); + wait_for_completion(&dev->ep0_done); + spin_lock_irqsave(&dev->lock, flags); + goto out_done; + } + + spin_lock_irqsave(&dev->lock, flags); + ret = dev->ep0_status; + +out_done: + dev->ep0_urb_queued = false; +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep0_write(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + void *data; + struct usb_raw_ep_io io; + + data = raw_alloc_io_data(&io, (void __user *)value, true); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep0_io(dev, &io, data, true); + kfree(data); + return ret; +} + +static int raw_ioctl_ep0_read(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + void *data; + struct usb_raw_ep_io io; + unsigned int length; + + data = raw_alloc_io_data(&io, (void __user *)value, false); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep0_io(dev, &io, data, false); + if (ret < 0) { + kfree(data); + return ret; + } + length = min(io.length, (unsigned int)ret); + ret = copy_to_user((void __user *)(value + sizeof(io)), data, length); + kfree(data); + return ret; +} + +static bool check_ep_caps(struct usb_ep *ep, + struct usb_endpoint_descriptor *desc) +{ + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_ISOC: + if (!ep->caps.type_iso) + return false; + break; + case USB_ENDPOINT_XFER_BULK: + if (!ep->caps.type_bulk) + return false; + break; + case USB_ENDPOINT_XFER_INT: + if (!ep->caps.type_int) + return false; + break; + default: + return false; + } + + if (usb_endpoint_dir_in(desc) && !ep->caps.dir_in) + return false; + if (usb_endpoint_dir_out(desc) && !ep->caps.dir_out) + return false; + + return true; +} + +static int raw_ioctl_ep_enable(struct raw_dev *dev, unsigned long value) +{ + int ret = 0, i; + unsigned long flags; + struct usb_endpoint_descriptor *desc; + struct usb_ep *ep = NULL; + + desc = memdup_user((void __user *)value, sizeof(*desc)); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + /* + * Endpoints with a maxpacket length of 0 can cause crashes in UDC + * drivers. + */ + if (usb_endpoint_maxp(desc) == 0) { + dev_dbg(dev->dev, "fail, bad endpoint maxpacket\n"); + kfree(desc); + return -EINVAL; + } + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_free; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_free; + } + + for (i = 0; i < USB_RAW_MAX_ENDPOINTS; i++) { + if (dev->eps[i].state == STATE_EP_ENABLED) + continue; + break; + } + if (i == USB_RAW_MAX_ENDPOINTS) { + dev_dbg(&dev->gadget->dev, + "fail, no device endpoints available\n"); + ret = -EBUSY; + goto out_free; + } + + gadget_for_each_ep(ep, dev->gadget) { + if (ep->enabled) + continue; + if (!check_ep_caps(ep, desc)) + continue; + ep->desc = desc; + ret = usb_ep_enable(ep); + if (ret < 0) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_enable returned %d\n", ret); + goto out_free; + } + dev->eps[i].req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!dev->eps[i].req) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_alloc_request failed\n"); + usb_ep_disable(ep); + ret = -ENOMEM; + goto out_free; + } + dev->eps[i].ep = ep; + dev->eps[i].state = STATE_EP_ENABLED; + ep->driver_data = &dev->eps[i]; + ret = i; + goto out_unlock; + } + + dev_dbg(&dev->gadget->dev, "fail, no gadget endpoints available\n"); + ret = -EBUSY; + +out_free: + kfree(desc); +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_disable(struct raw_dev *dev, unsigned long value) +{ + int ret = 0, i = value; + unsigned long flags; + const void *desc; + + if (i < 0 || i >= USB_RAW_MAX_ENDPOINTS) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->eps[i].state != STATE_EP_ENABLED) { + dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].disabling) { + dev_dbg(&dev->gadget->dev, + "fail, disable already in progress\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].urb_queued) { + dev_dbg(&dev->gadget->dev, + "fail, waiting for urb completion\n"); + ret = -EINVAL; + goto out_unlock; + } + dev->eps[i].disabling = true; + spin_unlock_irqrestore(&dev->lock, flags); + + usb_ep_disable(dev->eps[i].ep); + + spin_lock_irqsave(&dev->lock, flags); + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); + desc = dev->eps[i].ep->desc; + dev->eps[i].ep = NULL; + dev->eps[i].state = STATE_EP_DISABLED; + kfree(desc); + dev->eps[i].disabling = false; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static void gadget_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct raw_ep *r_ep = (struct raw_ep *)ep->driver_data; + struct raw_dev *dev = r_ep->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (req->status) + r_ep->status = req->status; + else + r_ep->status = req->actual; + spin_unlock_irqrestore(&dev->lock, flags); + + complete((struct completion *)req->context); +} + +static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io, + void *data, bool in) +{ + int ret = 0; + unsigned long flags; + struct raw_ep *ep = &dev->eps[io->ep]; + DECLARE_COMPLETION_ONSTACK(done); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->state != STATE_EP_ENABLED) { + dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->disabling) { + dev_dbg(&dev->gadget->dev, + "fail, endpoint is already being disabled\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->urb_queued) { + dev_dbg(&dev->gadget->dev, "fail, urb already queued\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((in && !ep->ep->caps.dir_in) || (!in && ep->ep->caps.dir_in)) { + dev_dbg(&dev->gadget->dev, "fail, wrong direction\n"); + ret = -EINVAL; + goto out_unlock; + } + + ep->dev = dev; + ep->req->context = &done; + ep->req->complete = gadget_ep_complete; + ep->req->buf = data; + ep->req->length = io->length; + ep->req->zero = usb_raw_io_flags_zero(io->flags); + ep->urb_queued = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(ep->ep, ep->req, GFP_KERNEL); + if (ret) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_queue returned %d\n", ret); + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + goto out_done; + } + + ret = wait_for_completion_interruptible(&done); + if (ret) { + dev_dbg(&dev->gadget->dev, "wait interrupted\n"); + usb_ep_dequeue(ep->ep, ep->req); + wait_for_completion(&done); + spin_lock_irqsave(&dev->lock, flags); + goto out_done; + } + + spin_lock_irqsave(&dev->lock, flags); + ret = ep->status; + +out_done: + ep->urb_queued = false; +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_write(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + char *data; + struct usb_raw_ep_io io; + + data = raw_alloc_io_data(&io, (void __user *)value, true); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep_io(dev, &io, data, true); + kfree(data); + return ret; +} + +static int raw_ioctl_ep_read(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + char *data; + struct usb_raw_ep_io io; + unsigned int length; + + data = raw_alloc_io_data(&io, (void __user *)value, false); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep_io(dev, &io, data, false); + if (ret < 0) { + kfree(data); + return ret; + } + length = min(io.length, (unsigned int)ret); + ret = copy_to_user((void __user *)(value + sizeof(io)), data, length); + kfree(data); + return ret; +} + +static int raw_ioctl_configure(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + if (value) + return -EINVAL; + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + usb_gadget_set_state(dev->gadget, USB_STATE_CONFIGURED); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_vbus_draw(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + usb_gadget_vbus_draw(dev->gadget, 2 * value); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static long raw_ioctl(struct file *fd, unsigned int cmd, unsigned long value) +{ + struct raw_dev *dev = fd->private_data; + int ret = 0; + + if (!dev) + return -EBUSY; + + switch (cmd) { + case USB_RAW_IOCTL_INIT: + ret = raw_ioctl_init(dev, value); + break; + case USB_RAW_IOCTL_RUN: + ret = raw_ioctl_run(dev, value); + break; + case USB_RAW_IOCTL_EVENT_FETCH: + ret = raw_ioctl_event_fetch(dev, value); + break; + case USB_RAW_IOCTL_EP0_WRITE: + ret = raw_ioctl_ep0_write(dev, value); + break; + case USB_RAW_IOCTL_EP0_READ: + ret = raw_ioctl_ep0_read(dev, value); + break; + case USB_RAW_IOCTL_EP_ENABLE: + ret = raw_ioctl_ep_enable(dev, value); + break; + case USB_RAW_IOCTL_EP_DISABLE: + ret = raw_ioctl_ep_disable(dev, value); + break; + case USB_RAW_IOCTL_EP_WRITE: + ret = raw_ioctl_ep_write(dev, value); + break; + case USB_RAW_IOCTL_EP_READ: + ret = raw_ioctl_ep_read(dev, value); + break; + case USB_RAW_IOCTL_CONFIGURE: + ret = raw_ioctl_configure(dev, value); + break; + case USB_RAW_IOCTL_VBUS_DRAW: + ret = raw_ioctl_vbus_draw(dev, value); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*----------------------------------------------------------------------*/ + +static const struct file_operations raw_fops = { + .open = raw_open, + .unlocked_ioctl = raw_ioctl, + .compat_ioctl = raw_ioctl, + .release = raw_release, + .llseek = no_llseek, +}; + +static struct miscdevice raw_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRIVER_NAME, + .fops = &raw_fops, +}; + +module_misc_device(raw_misc_device); diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 797d6ace8994..1ff82285ac7a 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -441,6 +441,16 @@ config USB_GADGET_XILINX dynamically linked module called "udc-xilinx" and force all gadget drivers to also be dynamically linked. +config USB_MAX3420_UDC + tristate "MAX3420 (USB-over-SPI) support" + depends on SPI + help + The Maxim MAX3420 chip supports USB2.0 full-speed peripheral mode. + The MAX3420 is run by SPI interface, and hence the dependency. + + To compile this driver as a module, choose M here: the module will + be called max3420_udc + config USB_TEGRA_XUDC tristate "NVIDIA Tegra Superspeed USB 3.0 Device Controller" depends on ARCH_TEGRA || COMPILE_TEST diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index f6777e654a8e..f5a7ce28aecd 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/ obj-$(CONFIG_USB_BDC_UDC) += bdc/ +obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o diff --git a/drivers/usb/gadget/udc/aspeed-vhub/Kconfig b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig index 83ba8a2eb6af..605500b19cf3 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/Kconfig +++ b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig @@ -4,5 +4,5 @@ config USB_ASPEED_VHUB depends on ARCH_ASPEED || COMPILE_TEST depends on USB_LIBCOMPOSITE help - USB peripheral controller for the Aspeed AST2500 family - SoCs supporting the "vHub" functionality and USB2.0 + USB peripheral controller for the Aspeed AST2400, AST2500 and + AST2600 family SoCs supporting the "vHub" functionality and USB2.0 diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 90b134d5dca9..f8d35dd60c34 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -99,7 +99,7 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) { struct ast_vhub *vhub = data; irqreturn_t iret = IRQ_NONE; - u32 istat; + u32 i, istat; /* Stale interrupt while tearing down */ if (!vhub->ep0_bufs) @@ -121,10 +121,10 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) /* Handle generic EPs first */ if (istat & VHUB_IRQ_EP_POOL_ACK_STALL) { - u32 i, ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR); + u32 ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR); writel(ep_acks, vhub->regs + AST_VHUB_EP_ACK_ISR); - for (i = 0; ep_acks && i < AST_VHUB_NUM_GEN_EPs; i++) { + for (i = 0; ep_acks && i < vhub->max_epns; i++) { u32 mask = VHUB_EP_IRQ(i); if (ep_acks & mask) { ast_vhub_epn_ack_irq(&vhub->epns[i]); @@ -134,21 +134,11 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) } /* Handle device interrupts */ - if (istat & (VHUB_IRQ_DEVICE1 | - VHUB_IRQ_DEVICE2 | - VHUB_IRQ_DEVICE3 | - VHUB_IRQ_DEVICE4 | - VHUB_IRQ_DEVICE5)) { - if (istat & VHUB_IRQ_DEVICE1) - ast_vhub_dev_irq(&vhub->ports[0].dev); - if (istat & VHUB_IRQ_DEVICE2) - ast_vhub_dev_irq(&vhub->ports[1].dev); - if (istat & VHUB_IRQ_DEVICE3) - ast_vhub_dev_irq(&vhub->ports[2].dev); - if (istat & VHUB_IRQ_DEVICE4) - ast_vhub_dev_irq(&vhub->ports[3].dev); - if (istat & VHUB_IRQ_DEVICE5) - ast_vhub_dev_irq(&vhub->ports[4].dev); + for (i = 0; i < vhub->max_ports; i++) { + u32 dev_mask = VHUB_IRQ_DEVICE1 << i; + + if (istat & dev_mask) + ast_vhub_dev_irq(&vhub->ports[i].dev); } /* Handle top-level vHub EP0 interrupts */ @@ -182,7 +172,7 @@ static irqreturn_t ast_vhub_irq(int irq, void *data) void ast_vhub_init_hw(struct ast_vhub *vhub) { - u32 ctrl; + u32 ctrl, port_mask, epn_mask; UDCDBG(vhub,"(Re)Starting HW ...\n"); @@ -222,15 +212,20 @@ void ast_vhub_init_hw(struct ast_vhub *vhub) } /* Reset all devices */ - writel(VHUB_SW_RESET_ALL, vhub->regs + AST_VHUB_SW_RESET); + port_mask = GENMASK(vhub->max_ports, 1); + writel(VHUB_SW_RESET_ROOT_HUB | + VHUB_SW_RESET_DMA_CONTROLLER | + VHUB_SW_RESET_EP_POOL | + port_mask, vhub->regs + AST_VHUB_SW_RESET); udelay(1); writel(0, vhub->regs + AST_VHUB_SW_RESET); /* Disable and cleanup EP ACK/NACK interrupts */ + epn_mask = GENMASK(vhub->max_epns - 1, 0); writel(0, vhub->regs + AST_VHUB_EP_ACK_IER); writel(0, vhub->regs + AST_VHUB_EP_NACK_IER); - writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_ACK_ISR); - writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_NACK_ISR); + writel(epn_mask, vhub->regs + AST_VHUB_EP_ACK_ISR); + writel(epn_mask, vhub->regs + AST_VHUB_EP_NACK_ISR); /* Default settings for EP0, enable HW hub EP1 */ writel(0, vhub->regs + AST_VHUB_EP0_CTRL); @@ -273,7 +268,7 @@ static int ast_vhub_remove(struct platform_device *pdev) return 0; /* Remove devices */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) + for (i = 0; i < vhub->max_ports; i++) ast_vhub_del_dev(&vhub->ports[i].dev); spin_lock_irqsave(&vhub->lock, flags); @@ -295,7 +290,7 @@ static int ast_vhub_remove(struct platform_device *pdev) if (vhub->ep0_bufs) dma_free_coherent(&pdev->dev, AST_VHUB_EP0_MAX_PACKET * - (AST_VHUB_NUM_PORTS + 1), + (vhub->max_ports + 1), vhub->ep0_bufs, vhub->ep0_bufs_dma); vhub->ep0_bufs = NULL; @@ -309,11 +304,32 @@ static int ast_vhub_probe(struct platform_device *pdev) struct ast_vhub *vhub; struct resource *res; int i, rc = 0; + const struct device_node *np = pdev->dev.of_node; vhub = devm_kzalloc(&pdev->dev, sizeof(*vhub), GFP_KERNEL); if (!vhub) return -ENOMEM; + rc = of_property_read_u32(np, "aspeed,vhub-downstream-ports", + &vhub->max_ports); + if (rc < 0) + vhub->max_ports = AST_VHUB_NUM_PORTS; + + vhub->ports = devm_kcalloc(&pdev->dev, vhub->max_ports, + sizeof(*vhub->ports), GFP_KERNEL); + if (!vhub->ports) + return -ENOMEM; + + rc = of_property_read_u32(np, "aspeed,vhub-generic-endpoints", + &vhub->max_epns); + if (rc < 0) + vhub->max_epns = AST_VHUB_NUM_GEN_EPs; + + vhub->epns = devm_kcalloc(&pdev->dev, vhub->max_epns, + sizeof(*vhub->epns), GFP_KERNEL); + if (!vhub->epns) + return -ENOMEM; + spin_lock_init(&vhub->lock); vhub->pdev = pdev; @@ -366,7 +382,7 @@ static int ast_vhub_probe(struct platform_device *pdev) */ vhub->ep0_bufs = dma_alloc_coherent(&pdev->dev, AST_VHUB_EP0_MAX_PACKET * - (AST_VHUB_NUM_PORTS + 1), + (vhub->max_ports + 1), &vhub->ep0_bufs_dma, GFP_KERNEL); if (!vhub->ep0_bufs) { dev_err(&pdev->dev, "Failed to allocate EP0 DMA buffers\n"); @@ -380,7 +396,7 @@ static int ast_vhub_probe(struct platform_device *pdev) ast_vhub_init_ep0(vhub, &vhub->ep0, NULL); /* Init devices */ - for (i = 0; i < AST_VHUB_NUM_PORTS && rc == 0; i++) + for (i = 0; i < vhub->max_ports && rc == 0; i++) rc = ast_vhub_init_dev(vhub, i); if (rc) goto err; @@ -407,6 +423,9 @@ static const struct of_device_id ast_vhub_dt_ids[] = { { .compatible = "aspeed,ast2500-usb-vhub", }, + { + .compatible = "aspeed,ast2600-usb-vhub", + }, { } }; MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index 4008e7a51188..d268306a7bfe 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -77,7 +77,7 @@ static void ast_vhub_dev_enable(struct ast_vhub_dev *d) writel(d->ep0.buf_dma, d->regs + AST_VHUB_DEV_EP0_DATA); /* Clear stall on all EPs */ - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) { + for (i = 0; i < d->max_epns; i++) { struct ast_vhub_ep *ep = d->epns[i]; if (ep && (ep->epn.stalled || ep->epn.wedged)) { @@ -137,7 +137,7 @@ static int ast_vhub_ep_feature(struct ast_vhub_dev *d, is_set ? "SET" : "CLEAR", ep_num, wValue); if (ep_num == 0) return std_req_complete; - if (ep_num >= AST_VHUB_NUM_GEN_EPs || !d->epns[ep_num - 1]) + if (ep_num >= d->max_epns || !d->epns[ep_num - 1]) return std_req_stall; if (wValue != USB_ENDPOINT_HALT) return std_req_driver; @@ -181,7 +181,7 @@ static int ast_vhub_ep_status(struct ast_vhub_dev *d, DDBG(d, "GET_STATUS(ep%d)\n", ep_num); - if (ep_num >= AST_VHUB_NUM_GEN_EPs) + if (ep_num >= d->max_epns) return std_req_stall; if (ep_num != 0) { ep = d->epns[ep_num - 1]; @@ -299,7 +299,7 @@ static void ast_vhub_dev_nuke(struct ast_vhub_dev *d) { unsigned int i; - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) { + for (i = 0; i < d->max_epns; i++) { if (!d->epns[i]) continue; ast_vhub_nuke(d->epns[i], -ESHUTDOWN); @@ -416,10 +416,10 @@ static struct usb_ep *ast_vhub_udc_match_ep(struct usb_gadget *gadget, * that will allow the generic code to use our * assigned address. */ - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) + for (i = 0; i < d->max_epns; i++) if (d->epns[i] == NULL) break; - if (i >= AST_VHUB_NUM_GEN_EPs) + if (i >= d->max_epns) return NULL; addr = i + 1; @@ -526,6 +526,7 @@ void ast_vhub_del_dev(struct ast_vhub_dev *d) usb_del_gadget_udc(&d->gadget); device_unregister(d->port_dev); + kfree(d->epns); } static void ast_vhub_dev_release(struct device *dev) @@ -547,13 +548,24 @@ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx) ast_vhub_init_ep0(vhub, &d->ep0, d); /* + * A USB device can have up to 30 endpoints besides control + * endpoint 0. + */ + d->max_epns = min_t(u32, vhub->max_epns, 30); + d->epns = kcalloc(d->max_epns, sizeof(*d->epns), GFP_KERNEL); + if (!d->epns) + return -ENOMEM; + + /* * The UDC core really needs us to have separate and uniquely * named "parent" devices for each port so we create a sub device * here for that purpose */ d->port_dev = kzalloc(sizeof(struct device), GFP_KERNEL); - if (!d->port_dev) - return -ENOMEM; + if (!d->port_dev) { + rc = -ENOMEM; + goto fail_alloc; + } device_initialize(d->port_dev); d->port_dev->release = ast_vhub_dev_release; d->port_dev->parent = parent; @@ -584,6 +596,8 @@ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx) device_del(d->port_dev); fail_add: put_device(d->port_dev); + fail_alloc: + kfree(d->epns); return rc; } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c index 7475c74aa5c5..0bd6b20435b8 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/epn.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c @@ -800,10 +800,10 @@ struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr) /* Find a free one (no device) */ spin_lock_irqsave(&vhub->lock, flags); - for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) + for (i = 0; i < vhub->max_epns; i++) if (vhub->epns[i].dev == NULL) break; - if (i >= AST_VHUB_NUM_GEN_EPs) { + if (i >= vhub->max_epns) { spin_unlock_irqrestore(&vhub->lock, flags); return NULL; } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 19b3517e04c0..6e565c3dbb5b 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -93,11 +93,7 @@ static void ast_vhub_patch_dev_desc_usb1(struct usb_device_descriptor *desc) USB_DT_INTERFACE_SIZE + \ USB_DT_ENDPOINT_SIZE) -static const struct ast_vhub_full_cdesc { - struct usb_config_descriptor cfg; - struct usb_interface_descriptor intf; - struct usb_endpoint_descriptor ep; -} __attribute__ ((packed)) ast_vhub_conf_desc = { +static const struct ast_vhub_full_cdesc ast_vhub_conf_desc = { .cfg = { .bLength = USB_DT_CONFIG_SIZE, .bDescriptorType = USB_DT_CONFIG, @@ -266,6 +262,7 @@ static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, u8 desc_type, u16 len) { size_t dsize; + struct ast_vhub *vhub = ep->vhub; EPDBG(ep, "GET_DESCRIPTOR(type:%d)\n", desc_type); @@ -281,20 +278,20 @@ static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, switch(desc_type) { case USB_DT_DEVICE: dsize = USB_DT_DEVICE_SIZE; - memcpy(ep->buf, &ast_vhub_dev_desc, dsize); - BUILD_BUG_ON(dsize > sizeof(ast_vhub_dev_desc)); + memcpy(ep->buf, &vhub->vhub_dev_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_dev_desc)); BUILD_BUG_ON(USB_DT_DEVICE_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; case USB_DT_CONFIG: dsize = AST_VHUB_CONF_DESC_SIZE; - memcpy(ep->buf, &ast_vhub_conf_desc, dsize); - BUILD_BUG_ON(dsize > sizeof(ast_vhub_conf_desc)); + memcpy(ep->buf, &vhub->vhub_conf_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_conf_desc)); BUILD_BUG_ON(AST_VHUB_CONF_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; case USB_DT_HUB: dsize = AST_VHUB_HUB_DESC_SIZE; - memcpy(ep->buf, &ast_vhub_hub_desc, dsize); - BUILD_BUG_ON(dsize > sizeof(ast_vhub_hub_desc)); + memcpy(ep->buf, &vhub->vhub_hub_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_hub_desc)); BUILD_BUG_ON(AST_VHUB_HUB_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; default: @@ -317,7 +314,8 @@ static int ast_vhub_rep_string(struct ast_vhub_ep *ep, u8 string_id, u16 lang_id, u16 len) { - int rc = usb_gadget_get_string (&ast_vhub_strings, string_id, ep->buf); + int rc = usb_gadget_get_string(&ep->vhub->vhub_str_desc, + string_id, ep->buf); /* * This should never happen unless we put too big strings in @@ -504,7 +502,7 @@ static void ast_vhub_wake_work(struct work_struct *work) * we let the normal host wake path deal with it later. */ spin_lock_irqsave(&vhub->lock, flags); - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; if (!(p->status & USB_PORT_STAT_SUSPEND)) @@ -587,7 +585,7 @@ static enum std_req_rc ast_vhub_set_port_feature(struct ast_vhub_ep *ep, struct ast_vhub *vhub = ep->vhub; struct ast_vhub_port *p; - if (port == 0 || port > AST_VHUB_NUM_PORTS) + if (port == 0 || port > vhub->max_ports) return std_req_stall; port--; p = &vhub->ports[port]; @@ -630,7 +628,7 @@ static enum std_req_rc ast_vhub_clr_port_feature(struct ast_vhub_ep *ep, struct ast_vhub *vhub = ep->vhub; struct ast_vhub_port *p; - if (port == 0 || port > AST_VHUB_NUM_PORTS) + if (port == 0 || port > vhub->max_ports) return std_req_stall; port--; p = &vhub->ports[port]; @@ -676,7 +674,7 @@ static enum std_req_rc ast_vhub_get_port_stat(struct ast_vhub_ep *ep, struct ast_vhub *vhub = ep->vhub; u16 stat, chg; - if (port == 0 || port > AST_VHUB_NUM_PORTS) + if (port == 0 || port > vhub->max_ports) return std_req_stall; port--; @@ -757,7 +755,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub) * Forward to unsuspended ports without changing * their connection status. */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; if (!(p->status & USB_PORT_STAT_SUSPEND)) @@ -780,7 +778,7 @@ void ast_vhub_hub_resume(struct ast_vhub *vhub) * Forward to unsuspended ports without changing * their connection status. */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; if (!(p->status & USB_PORT_STAT_SUSPEND)) @@ -814,7 +812,7 @@ void ast_vhub_hub_reset(struct ast_vhub *vhub) * Clear all port status, disable gadgets and "suspend" * them. They will be woken up by a port reset. */ - for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + for (i = 0; i < vhub->max_ports; i++) { struct ast_vhub_port *p = &vhub->ports[i]; /* Only keep the connected flag */ @@ -834,9 +832,31 @@ void ast_vhub_hub_reset(struct ast_vhub *vhub) writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG); } +static void ast_vhub_init_desc(struct ast_vhub *vhub) +{ + /* Initialize vhub Device Descriptor. */ + memcpy(&vhub->vhub_dev_desc, &ast_vhub_dev_desc, + sizeof(vhub->vhub_dev_desc)); + + /* Initialize vhub Configuration Descriptor. */ + memcpy(&vhub->vhub_conf_desc, &ast_vhub_conf_desc, + sizeof(vhub->vhub_conf_desc)); + + /* Initialize vhub Hub Descriptor. */ + memcpy(&vhub->vhub_hub_desc, &ast_vhub_hub_desc, + sizeof(vhub->vhub_hub_desc)); + vhub->vhub_hub_desc.bNbrPorts = vhub->max_ports; + + /* Initialize vhub String Descriptors. */ + memcpy(&vhub->vhub_str_desc, &ast_vhub_strings, + sizeof(vhub->vhub_str_desc)); +} + void ast_vhub_init_hub(struct ast_vhub *vhub) { vhub->speed = USB_SPEED_UNKNOWN; INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + + ast_vhub_init_desc(vhub); } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index 761919e220d3..fac79ef6d669 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -2,6 +2,9 @@ #ifndef __ASPEED_VHUB_H #define __ASPEED_VHUB_H +#include <linux/usb.h> +#include <linux/usb/ch11.h> + /***************************** * * * VHUB register definitions * @@ -76,17 +79,9 @@ #define VHUB_SW_RESET_DEVICE2 (1 << 2) #define VHUB_SW_RESET_DEVICE1 (1 << 1) #define VHUB_SW_RESET_ROOT_HUB (1 << 0) -#define VHUB_SW_RESET_ALL (VHUB_SW_RESET_EP_POOL | \ - VHUB_SW_RESET_DMA_CONTROLLER | \ - VHUB_SW_RESET_DEVICE5 | \ - VHUB_SW_RESET_DEVICE4 | \ - VHUB_SW_RESET_DEVICE3 | \ - VHUB_SW_RESET_DEVICE2 | \ - VHUB_SW_RESET_DEVICE1 | \ - VHUB_SW_RESET_ROOT_HUB) + /* EP ACK/NACK IRQ masks */ #define VHUB_EP_IRQ(n) (1 << (n)) -#define VHUB_EP_IRQ_ALL 0x7fff /* 15 EPs */ /* USB status reg */ #define VHUB_USBSTS_HISPEED (1 << 27) @@ -210,6 +205,11 @@ * * ****************************************/ +/* + * AST_VHUB_NUM_GEN_EPs and AST_VHUB_NUM_PORTS are kept to avoid breaking + * existing AST2400/AST2500 platforms. AST2600 and future vhub revisions + * should define number of downstream ports and endpoints in device tree. + */ #define AST_VHUB_NUM_GEN_EPs 15 /* Generic non-0 EPs */ #define AST_VHUB_NUM_PORTS 5 /* vHub ports */ #define AST_VHUB_EP0_MAX_PACKET 64 /* EP0's max packet size */ @@ -312,7 +312,7 @@ struct ast_vhub_ep { /* Registers */ void __iomem *regs; - /* Index in global pool (0..14) */ + /* Index in global pool (zero-based) */ unsigned int g_idx; /* DMA Descriptors */ @@ -342,7 +342,7 @@ struct ast_vhub_dev { struct ast_vhub *vhub; void __iomem *regs; - /* Device index (0...4) and name string */ + /* Device index (zero-based) and name string */ unsigned int index; const char *name; @@ -358,7 +358,8 @@ struct ast_vhub_dev { /* Endpoint structures */ struct ast_vhub_ep ep0; - struct ast_vhub_ep *epns[AST_VHUB_NUM_GEN_EPs]; + struct ast_vhub_ep **epns; + u32 max_epns; }; #define to_ast_dev(__g) container_of(__g, struct ast_vhub_dev, gadget) @@ -373,6 +374,12 @@ struct ast_vhub_port { struct ast_vhub_dev dev; }; +struct ast_vhub_full_cdesc { + struct usb_config_descriptor cfg; + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor ep; +} __packed; + /* Global vhub structure */ struct ast_vhub { struct platform_device *pdev; @@ -393,10 +400,12 @@ struct ast_vhub { bool ep1_stalled : 1; /* Per-port info */ - struct ast_vhub_port ports[AST_VHUB_NUM_PORTS]; + struct ast_vhub_port *ports; + u32 max_ports; /* Generic EP data structures */ - struct ast_vhub_ep epns[AST_VHUB_NUM_GEN_EPs]; + struct ast_vhub_ep *epns; + u32 max_epns; /* Upstream bus is suspended ? */ bool suspended : 1; @@ -409,6 +418,12 @@ struct ast_vhub { /* Upstream bus speed captured at bus reset */ unsigned int speed; + + /* Standard USB Descriptors of the vhub. */ + struct usb_device_descriptor vhub_dev_desc; + struct ast_vhub_full_cdesc vhub_conf_desc; + struct usb_hub_descriptor vhub_hub_desc; + struct usb_gadget_strings vhub_str_desc; }; /* Standard request handlers result codes */ diff --git a/drivers/usb/gadget/udc/max3420_udc.c b/drivers/usb/gadget/udc/max3420_udc.c new file mode 100644 index 000000000000..8fbc083b6732 --- /dev/null +++ b/drivers/usb/gadget/udc/max3420_udc.c @@ -0,0 +1,1331 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MAX3420 Device Controller driver for USB. + * + * Author: Jaswinder Singh Brar <jaswinder.singh@linaro.org> + * (C) Copyright 2019-2020 Linaro Ltd + * + * Based on: + * o MAX3420E datasheet + * http://datasheets.maximintegrated.com/en/ds/MAX3420E.pdf + * o MAX342{0,1}E Programming Guides + * https://pdfserv.maximintegrated.com/en/an/AN3598.pdf + * https://pdfserv.maximintegrated.com/en/an/AN3785.pdf + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/bitfield.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/prefetch.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/spi/spi.h> +#include <linux/gpio/consumer.h> + +#define MAX3420_MAX_EPS 4 +#define MAX3420_EP_MAX_PACKET 64 /* Same for all Endpoints */ +#define MAX3420_EPNAME_SIZE 16 /* Buffer size for endpoint name */ + +#define MAX3420_ACKSTAT BIT(0) + +#define MAX3420_SPI_DIR_RD 0 /* read register from MAX3420 */ +#define MAX3420_SPI_DIR_WR 1 /* write register to MAX3420 */ + +/* SPI commands: */ +#define MAX3420_SPI_DIR_SHIFT 1 +#define MAX3420_SPI_REG_SHIFT 3 + +#define MAX3420_REG_EP0FIFO 0 +#define MAX3420_REG_EP1FIFO 1 +#define MAX3420_REG_EP2FIFO 2 +#define MAX3420_REG_EP3FIFO 3 +#define MAX3420_REG_SUDFIFO 4 +#define MAX3420_REG_EP0BC 5 +#define MAX3420_REG_EP1BC 6 +#define MAX3420_REG_EP2BC 7 +#define MAX3420_REG_EP3BC 8 + +#define MAX3420_REG_EPSTALLS 9 + #define ACKSTAT BIT(6) + #define STLSTAT BIT(5) + #define STLEP3IN BIT(4) + #define STLEP2IN BIT(3) + #define STLEP1OUT BIT(2) + #define STLEP0OUT BIT(1) + #define STLEP0IN BIT(0) + +#define MAX3420_REG_CLRTOGS 10 + #define EP3DISAB BIT(7) + #define EP2DISAB BIT(6) + #define EP1DISAB BIT(5) + #define CTGEP3IN BIT(4) + #define CTGEP2IN BIT(3) + #define CTGEP1OUT BIT(2) + +#define MAX3420_REG_EPIRQ 11 +#define MAX3420_REG_EPIEN 12 + #define SUDAVIRQ BIT(5) + #define IN3BAVIRQ BIT(4) + #define IN2BAVIRQ BIT(3) + #define OUT1DAVIRQ BIT(2) + #define OUT0DAVIRQ BIT(1) + #define IN0BAVIRQ BIT(0) + +#define MAX3420_REG_USBIRQ 13 +#define MAX3420_REG_USBIEN 14 + #define OSCOKIRQ BIT(0) + #define RWUDNIRQ BIT(1) + #define BUSACTIRQ BIT(2) + #define URESIRQ BIT(3) + #define SUSPIRQ BIT(4) + #define NOVBUSIRQ BIT(5) + #define VBUSIRQ BIT(6) + #define URESDNIRQ BIT(7) + +#define MAX3420_REG_USBCTL 15 + #define HOSCSTEN BIT(7) + #define VBGATE BIT(6) + #define CHIPRES BIT(5) + #define PWRDOWN BIT(4) + #define CONNECT BIT(3) + #define SIGRWU BIT(2) + +#define MAX3420_REG_CPUCTL 16 + #define IE BIT(0) + +#define MAX3420_REG_PINCTL 17 + #define EP3INAK BIT(7) + #define EP2INAK BIT(6) + #define EP0INAK BIT(5) + #define FDUPSPI BIT(4) + #define INTLEVEL BIT(3) + #define POSINT BIT(2) + #define GPXB BIT(1) + #define GPXA BIT(0) + +#define MAX3420_REG_REVISION 18 + +#define MAX3420_REG_FNADDR 19 + #define FNADDR_MASK 0x7f + +#define MAX3420_REG_IOPINS 20 +#define MAX3420_REG_IOPINS2 21 +#define MAX3420_REG_GPINIRQ 22 +#define MAX3420_REG_GPINIEN 23 +#define MAX3420_REG_GPINPOL 24 +#define MAX3420_REG_HIRQ 25 +#define MAX3420_REG_HIEN 26 +#define MAX3420_REG_MODE 27 +#define MAX3420_REG_PERADDR 28 +#define MAX3420_REG_HCTL 29 +#define MAX3420_REG_HXFR 30 +#define MAX3420_REG_HRSL 31 + +#define ENABLE_IRQ BIT(0) +#define IOPIN_UPDATE BIT(1) +#define REMOTE_WAKEUP BIT(2) +#define CONNECT_HOST GENMASK(4, 3) +#define HCONNECT (1 << 3) +#define HDISCONNECT (3 << 3) +#define UDC_START GENMASK(6, 5) +#define START (1 << 5) +#define STOP (3 << 5) +#define ENABLE_EP GENMASK(8, 7) +#define ENABLE (1 << 7) +#define DISABLE (3 << 7) +#define STALL_EP GENMASK(10, 9) +#define STALL (1 << 9) +#define UNSTALL (3 << 9) + +#define MAX3420_CMD(c) FIELD_PREP(GENMASK(7, 3), c) +#define MAX3420_SPI_CMD_RD(c) (MAX3420_CMD(c) | (0 << 1)) +#define MAX3420_SPI_CMD_WR(c) (MAX3420_CMD(c) | (1 << 1)) + +struct max3420_req { + struct usb_request usb_req; + struct list_head queue; + struct max3420_ep *ep; +}; + +struct max3420_ep { + struct usb_ep ep_usb; + struct max3420_udc *udc; + struct list_head queue; + char name[MAX3420_EPNAME_SIZE]; + unsigned int maxpacket; + spinlock_t lock; + int halted; + u32 todo; + int id; +}; + +struct max3420_udc { + struct usb_gadget gadget; + struct max3420_ep ep[MAX3420_MAX_EPS]; + struct usb_gadget_driver *driver; + struct task_struct *thread_task; + int remote_wkp, is_selfpowered; + bool vbus_active, softconnect; + struct usb_ctrlrequest setup; + struct mutex spi_bus_mutex; + struct max3420_req ep0req; + struct spi_device *spi; + struct device *dev; + spinlock_t lock; + bool suspended; + u8 ep0buf[64]; + u32 todo; +}; + +#define to_max3420_req(r) container_of((r), struct max3420_req, usb_req) +#define to_max3420_ep(e) container_of((e), struct max3420_ep, ep_usb) +#define to_udc(g) container_of((g), struct max3420_udc, gadget) + +#define DRIVER_DESC "MAX3420 USB Device-Mode Driver" +static const char driver_name[] = "max3420-udc"; + +/* Control endpoint configuration.*/ +static const struct usb_endpoint_descriptor ep0_desc = { + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(MAX3420_EP_MAX_PACKET), +}; + +static void spi_ack_ctrl(struct max3420_udc *udc) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[1]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_ACKSTAT; + transfer.tx_buf = txdata; + transfer.len = 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int actstat) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[2], rxdata[2]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_SPI_CMD_RD(reg) | (actstat ? MAX3420_ACKSTAT : 0); + transfer.tx_buf = txdata; + transfer.rx_buf = rxdata; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); + + return rxdata[1]; +} + +static u8 spi_rd8(struct max3420_udc *udc, u8 reg) +{ + return spi_rd8_ack(udc, reg, 0); +} + +static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val, int actstat) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[2]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_SPI_CMD_WR(reg) | (actstat ? MAX3420_ACKSTAT : 0); + txdata[1] = val; + + transfer.tx_buf = txdata; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val) +{ + spi_wr8_ack(udc, reg, val, 0); +} + +static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 local_buf[MAX3420_EP_MAX_PACKET + 1] = {}; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + local_buf[0] = MAX3420_SPI_CMD_RD(reg); + transfer.tx_buf = &local_buf[0]; + transfer.rx_buf = &local_buf[0]; + transfer.len = len + 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); + + memcpy(buf, &local_buf[1], len); +} + +static void spi_wr_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 local_buf[MAX3420_EP_MAX_PACKET + 1] = {}; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + local_buf[0] = MAX3420_SPI_CMD_WR(reg); + memcpy(&local_buf[1], buf, len); + + transfer.tx_buf = local_buf; + transfer.len = len + 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static int spi_max3420_enable(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + u8 epdis, epien; + int todo; + + spin_lock_irqsave(&ep->lock, flags); + todo = ep->todo & ENABLE_EP; + ep->todo &= ~ENABLE_EP; + spin_unlock_irqrestore(&ep->lock, flags); + + if (!todo || ep->id == 0) + return false; + + epien = spi_rd8(udc, MAX3420_REG_EPIEN); + epdis = spi_rd8(udc, MAX3420_REG_CLRTOGS); + + if (todo == ENABLE) { + epdis &= ~BIT(ep->id + 4); + epien |= BIT(ep->id + 1); + } else { + epdis |= BIT(ep->id + 4); + epien &= ~BIT(ep->id + 1); + } + + spi_wr8(udc, MAX3420_REG_CLRTOGS, epdis); + spi_wr8(udc, MAX3420_REG_EPIEN, epien); + + return true; +} + +static int spi_max3420_stall(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + u8 epstalls; + int todo; + + spin_lock_irqsave(&ep->lock, flags); + todo = ep->todo & STALL_EP; + ep->todo &= ~STALL_EP; + spin_unlock_irqrestore(&ep->lock, flags); + + if (!todo || ep->id == 0) + return false; + + epstalls = spi_rd8(udc, MAX3420_REG_EPSTALLS); + if (todo == STALL) { + ep->halted = 1; + epstalls |= BIT(ep->id + 1); + } else { + u8 clrtogs; + + ep->halted = 0; + epstalls &= ~BIT(ep->id + 1); + clrtogs = spi_rd8(udc, MAX3420_REG_CLRTOGS); + clrtogs |= BIT(ep->id + 1); + spi_wr8(udc, MAX3420_REG_CLRTOGS, clrtogs); + } + spi_wr8(udc, MAX3420_REG_EPSTALLS, epstalls | ACKSTAT); + + return true; +} + +static int spi_max3420_rwkup(struct max3420_udc *udc) +{ + unsigned long flags; + int wake_remote; + u8 usbctl; + + spin_lock_irqsave(&udc->lock, flags); + wake_remote = udc->todo & REMOTE_WAKEUP; + udc->todo &= ~REMOTE_WAKEUP; + spin_unlock_irqrestore(&udc->lock, flags); + + if (!wake_remote || !udc->suspended) + return false; + + /* Set Remote-WkUp Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl |= SIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + msleep_interruptible(5); + + /* Clear Remote-WkUp Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl &= ~SIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + udc->suspended = false; + + return true; +} + +static void max3420_nuke(struct max3420_ep *ep, int status); +static void __max3420_stop(struct max3420_udc *udc) +{ + u8 val; + int i; + + /* clear all pending requests */ + for (i = 1; i < MAX3420_MAX_EPS; i++) + max3420_nuke(&udc->ep[i], -ECONNRESET); + + /* Disable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, 0); + + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= PWRDOWN; + if (udc->is_selfpowered) + val &= ~HOSCSTEN; + else + val |= HOSCSTEN; + spi_wr8(udc, MAX3420_REG_USBCTL, val); +} + +static void __max3420_start(struct max3420_udc *udc) +{ + u8 val; + + /* Need this delay if bus-powered, + * but even for self-powered it helps stability + */ + msleep_interruptible(250); + + /* configure SPI */ + spi_wr8(udc, MAX3420_REG_PINCTL, FDUPSPI); + + /* Chip Reset */ + spi_wr8(udc, MAX3420_REG_USBCTL, CHIPRES); + msleep_interruptible(5); + spi_wr8(udc, MAX3420_REG_USBCTL, 0); + + /* Poll for OSC to stabilize */ + while (1) { + val = spi_rd8(udc, MAX3420_REG_USBIRQ); + if (val & OSCOKIRQ) + break; + cond_resched(); + } + + /* Enable PULL-UP only when Vbus detected */ + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= VBGATE | CONNECT; + spi_wr8(udc, MAX3420_REG_USBCTL, val); + + val = URESDNIRQ | URESIRQ; + if (udc->is_selfpowered) + val |= NOVBUSIRQ; + spi_wr8(udc, MAX3420_REG_USBIEN, val); + + /* Enable only EP0 interrupts */ + val = IN0BAVIRQ | OUT0DAVIRQ | SUDAVIRQ; + spi_wr8(udc, MAX3420_REG_EPIEN, val); + + /* Enable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, IE); +} + +static int max3420_start(struct max3420_udc *udc) +{ + unsigned long flags; + int todo; + + spin_lock_irqsave(&udc->lock, flags); + todo = udc->todo & UDC_START; + udc->todo &= ~UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (!todo) + return false; + + if (udc->vbus_active && udc->softconnect) + __max3420_start(udc); + else + __max3420_stop(udc); + + return true; +} + +static irqreturn_t max3420_vbus_handler(int irq, void *dev_id) +{ + struct max3420_udc *udc = dev_id; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* its a vbus change interrupt */ + udc->vbus_active = !udc->vbus_active; + udc->todo |= UDC_START; + usb_udc_vbus_handler(&udc->gadget, udc->vbus_active); + usb_gadget_set_state(&udc->gadget, udc->vbus_active + ? USB_STATE_POWERED : USB_STATE_NOTATTACHED); + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return IRQ_HANDLED; +} + +static irqreturn_t max3420_irq_handler(int irq, void *dev_id) +{ + struct max3420_udc *udc = dev_id; + struct spi_device *spi = udc->spi; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + if ((udc->todo & ENABLE_IRQ) == 0) { + disable_irq_nosync(spi->irq); + udc->todo |= ENABLE_IRQ; + } + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return IRQ_HANDLED; +} + +static void max3420_getstatus(struct max3420_udc *udc) +{ + struct max3420_ep *ep; + u16 status = 0; + + switch (udc->setup.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = udc->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED; + status |= (udc->remote_wkp << USB_DEVICE_REMOTE_WAKEUP); + break; + case USB_RECIP_INTERFACE: + if (udc->driver->setup(&udc->gadget, &udc->setup) < 0) + goto stall; + break; + case USB_RECIP_ENDPOINT: + ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK]; + if (udc->setup.wIndex & USB_DIR_IN) { + if (!ep->ep_usb.caps.dir_in) + goto stall; + } else { + if (!ep->ep_usb.caps.dir_out) + goto stall; + } + if (ep->halted) + status = 1 << USB_ENDPOINT_HALT; + break; + default: + goto stall; + } + + status = cpu_to_le16(status); + spi_wr_buf(udc, MAX3420_REG_EP0FIFO, &status, 2); + spi_wr8_ack(udc, MAX3420_REG_EP0BC, 2, 1); + return; +stall: + dev_err(udc->dev, "Can't respond to getstatus request\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, STLEP0IN | STLEP0OUT | STLSTAT); +} + +static void max3420_set_clear_feature(struct max3420_udc *udc) +{ + struct max3420_ep *ep; + int set = udc->setup.bRequest == USB_REQ_SET_FEATURE; + unsigned long flags; + int id; + + switch (udc->setup.bRequestType) { + case USB_RECIP_DEVICE: + if (udc->setup.wValue != USB_DEVICE_REMOTE_WAKEUP) + break; + + if (udc->setup.bRequest == USB_REQ_SET_FEATURE) + udc->remote_wkp = 1; + else + udc->remote_wkp = 0; + + return spi_ack_ctrl(udc); + + case USB_RECIP_ENDPOINT: + if (udc->setup.wValue != USB_ENDPOINT_HALT) + break; + + id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[id]; + + spin_lock_irqsave(&ep->lock, flags); + ep->todo &= ~STALL_EP; + if (set) + ep->todo |= STALL; + else + ep->todo |= UNSTALL; + spin_unlock_irqrestore(&ep->lock, flags); + + spi_max3420_stall(ep); + return; + default: + break; + } + + dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, STLEP0IN | STLEP0OUT | STLSTAT); +} + +static void max3420_handle_setup(struct max3420_udc *udc) +{ + struct usb_ctrlrequest setup; + u8 addr; + + spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8); + + udc->setup = setup; + udc->setup.wValue = cpu_to_le16(setup.wValue); + udc->setup.wIndex = cpu_to_le16(setup.wIndex); + udc->setup.wLength = cpu_to_le16(setup.wLength); + + switch (udc->setup.bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase form udc */ + if ((udc->setup.bRequestType & + (USB_DIR_IN | USB_TYPE_MASK)) != + (USB_DIR_IN | USB_TYPE_STANDARD)) { + break; + } + return max3420_getstatus(udc); + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (udc->setup.bRequestType != (USB_DIR_OUT | + USB_TYPE_STANDARD | USB_RECIP_DEVICE)) { + break; + } + addr = spi_rd8_ack(udc, MAX3420_REG_FNADDR, 1); + dev_dbg(udc->dev, "Assigned Address=%d\n", udc->setup.wValue); + return; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Requests with no data phase, status phase from udc */ + if ((udc->setup.bRequestType & USB_TYPE_MASK) + != USB_TYPE_STANDARD) + break; + return max3420_set_clear_feature(udc); + default: + break; + } + + if (udc->driver->setup(&udc->gadget, &setup) < 0) { + /* Stall EP0 */ + spi_wr8(udc, MAX3420_REG_EPSTALLS, + STLEP0IN | STLEP0OUT | STLSTAT); + } +} + +static void max3420_req_done(struct max3420_req *req, int status) +{ + struct max3420_ep *ep = req->ep; + struct max3420_udc *udc = ep->udc; + + if (req->usb_req.status == -EINPROGRESS) + req->usb_req.status = status; + else + status = req->usb_req.status; + + if (status && status != -ESHUTDOWN) + dev_err(udc->dev, "%s done %p, status %d\n", + ep->ep_usb.name, req, status); + + if (req->usb_req.complete) + req->usb_req.complete(&ep->ep_usb, &req->usb_req); +} + +static int max3420_do_data(struct max3420_udc *udc, int ep_id, int in) +{ + struct max3420_ep *ep = &udc->ep[ep_id]; + struct max3420_req *req; + int done, length, psz; + void *buf; + + if (list_empty(&ep->queue)) + return false; + + req = list_first_entry(&ep->queue, struct max3420_req, queue); + buf = req->usb_req.buf + req->usb_req.actual; + + psz = ep->ep_usb.maxpacket; + length = req->usb_req.length - req->usb_req.actual; + length = min(length, psz); + + if (length == 0) { + done = 1; + goto xfer_done; + } + + done = 0; + if (in) { + prefetch(buf); + spi_wr_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + spi_wr8(udc, MAX3420_REG_EP0BC + ep_id, length); + if (length < psz) + done = 1; + } else { + psz = spi_rd8(udc, MAX3420_REG_EP0BC + ep_id); + length = min(length, psz); + prefetchw(buf); + spi_rd_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + if (length < ep->ep_usb.maxpacket) + done = 1; + } + + req->usb_req.actual += length; + + if (req->usb_req.actual == req->usb_req.length) + done = 1; + +xfer_done: + if (done) { + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + list_del_init(&req->queue); + spin_unlock_irqrestore(&ep->lock, flags); + + if (ep_id == 0) + spi_ack_ctrl(udc); + + max3420_req_done(req, 0); + } + + return true; +} + +static int max3420_handle_irqs(struct max3420_udc *udc) +{ + u8 epien, epirq, usbirq, usbien, reg[4]; + bool ret = false; + + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 4); + epirq = reg[0]; + epien = reg[1]; + usbirq = reg[2]; + usbien = reg[3]; + + usbirq &= usbien; + epirq &= epien; + + if (epirq & SUDAVIRQ) { + spi_wr8(udc, MAX3420_REG_EPIRQ, SUDAVIRQ); + max3420_handle_setup(udc); + return true; + } + + if (usbirq & VBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, VBUSIRQ); + dev_dbg(udc->dev, "Cable plugged in\n"); + return true; + } + + if (usbirq & NOVBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, NOVBUSIRQ); + dev_dbg(udc->dev, "Cable pulled out\n"); + return true; + } + + if (usbirq & URESIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, URESIRQ); + dev_dbg(udc->dev, "USB Reset - Start\n"); + return true; + } + + if (usbirq & URESDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, URESDNIRQ); + dev_dbg(udc->dev, "USB Reset - END\n"); + spi_wr8(udc, MAX3420_REG_USBIEN, URESDNIRQ | URESIRQ); + spi_wr8(udc, MAX3420_REG_EPIEN, SUDAVIRQ | IN0BAVIRQ + | OUT0DAVIRQ); + return true; + } + + if (usbirq & SUSPIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, SUSPIRQ); + dev_dbg(udc->dev, "USB Suspend - Enter\n"); + udc->suspended = true; + return true; + } + + if (usbirq & BUSACTIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, BUSACTIRQ); + dev_dbg(udc->dev, "USB Suspend - Exit\n"); + udc->suspended = false; + return true; + } + + if (usbirq & RWUDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, RWUDNIRQ); + dev_dbg(udc->dev, "Asked Host to wakeup\n"); + return true; + } + + if (usbirq & OSCOKIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, OSCOKIRQ); + dev_dbg(udc->dev, "Osc stabilized, start work\n"); + return true; + } + + if (epirq & OUT0DAVIRQ && max3420_do_data(udc, 0, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, OUT0DAVIRQ, 1); + ret = true; + } + + if (epirq & IN0BAVIRQ && max3420_do_data(udc, 0, 1)) + ret = true; + + if (epirq & OUT1DAVIRQ && max3420_do_data(udc, 1, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, OUT1DAVIRQ, 1); + ret = true; + } + + if (epirq & IN2BAVIRQ && max3420_do_data(udc, 2, 1)) + ret = true; + + if (epirq & IN3BAVIRQ && max3420_do_data(udc, 3, 1)) + ret = true; + + return ret; +} + +static int max3420_thread(void *dev_id) +{ + struct max3420_udc *udc = dev_id; + struct spi_device *spi = udc->spi; + int i, loop_again = 1; + unsigned long flags; + + while (!kthread_should_stop()) { + if (!loop_again) { + ktime_t kt = ns_to_ktime(1000 * 1000 * 250); /* 250ms */ + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&udc->lock, flags); + if (udc->todo & ENABLE_IRQ) { + enable_irq(spi->irq); + udc->todo &= ~ENABLE_IRQ; + } + spin_unlock_irqrestore(&udc->lock, flags); + + schedule_hrtimeout(&kt, HRTIMER_MODE_REL); + } + loop_again = 0; + + mutex_lock(&udc->spi_bus_mutex); + + /* If bus-vbus_active and disconnected */ + if (!udc->vbus_active || !udc->softconnect) + goto loop; + + if (max3420_start(udc)) { + loop_again = 1; + goto loop; + } + + if (max3420_handle_irqs(udc)) { + loop_again = 1; + goto loop; + } + + if (spi_max3420_rwkup(udc)) { + loop_again = 1; + goto loop; + } + + max3420_do_data(udc, 0, 1); /* get done with the EP0 ZLP */ + + for (i = 1; i < MAX3420_MAX_EPS; i++) { + struct max3420_ep *ep = &udc->ep[i]; + + if (spi_max3420_enable(ep)) + loop_again = 1; + if (spi_max3420_stall(ep)) + loop_again = 1; + } +loop: + mutex_unlock(&udc->spi_bus_mutex); + } + + set_current_state(TASK_RUNNING); + dev_info(udc->dev, "SPI thread exiting"); + return 0; +} + +static int max3420_ep_set_halt(struct usb_ep *_ep, int stall) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + ep->todo &= ~STALL_EP; + if (stall) + ep->todo |= STALL; + else + ep->todo |= UNSTALL; + + spin_unlock_irqrestore(&ep->lock, flags); + + wake_up_process(udc->thread_task); + + dev_dbg(udc->dev, "%sStall %s\n", stall ? "" : "Un", ep->name); + return 0; +} + +static int __max3420_ep_enable(struct max3420_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + unsigned int maxp = usb_endpoint_maxp(desc); + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + ep->ep_usb.desc = desc; + ep->ep_usb.maxpacket = maxp; + + ep->todo &= ~ENABLE_EP; + ep->todo |= ENABLE; + spin_unlock_irqrestore(&ep->lock, flags); + + return 0; +} + +static int max3420_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + + __max3420_ep_enable(ep, desc); + + wake_up_process(udc->thread_task); + + return 0; +} + +static void max3420_nuke(struct max3420_ep *ep, int status) +{ + struct max3420_req *req, *r; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + list_for_each_entry_safe(req, r, &ep->queue, queue) { + list_del_init(&req->queue); + + spin_unlock_irqrestore(&ep->lock, flags); + max3420_req_done(req, status); + spin_lock_irqsave(&ep->lock, flags); + } + + spin_unlock_irqrestore(&ep->lock, flags); +} + +static void __max3420_ep_disable(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + ep->ep_usb.desc = NULL; + + ep->todo &= ~ENABLE_EP; + ep->todo |= DISABLE; + + spin_unlock_irqrestore(&ep->lock, flags); + + dev_dbg(udc->dev, "Disabled %s\n", ep->name); +} + +static int max3420_ep_disable(struct usb_ep *_ep) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + + max3420_nuke(ep, -ESHUTDOWN); + + __max3420_ep_disable(ep); + + wake_up_process(udc->thread_task); + + return 0; +} + +static struct usb_request *max3420_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_req *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + req->ep = ep; + + return &req->usb_req; +} + +static void max3420_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + kfree(to_max3420_req(_req)); +} + +static int max3420_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t ignored) +{ + struct max3420_req *req = to_max3420_req(_req); + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&ep->lock, flags); + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&ep->lock, flags); + + wake_up_process(udc->thread_task); + return 0; +} + +static int max3420_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct max3420_req *t, *req = to_max3420_req(_req); + struct max3420_ep *ep = to_max3420_ep(_ep); + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + /* Pluck the descriptor from queue */ + list_for_each_entry(t, &ep->queue, queue) + if (t == req) { + list_del_init(&req->queue); + break; + } + + spin_unlock_irqrestore(&ep->lock, flags); + + if (t == req) + max3420_req_done(req, -ECONNRESET); + + return 0; +} + +static const struct usb_ep_ops max3420_ep_ops = { + .enable = max3420_ep_enable, + .disable = max3420_ep_disable, + .alloc_request = max3420_alloc_request, + .free_request = max3420_free_request, + .queue = max3420_ep_queue, + .dequeue = max3420_ep_dequeue, + .set_halt = max3420_ep_set_halt, +}; + +static int max3420_wakeup(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + + /* Only if wakeup allowed by host */ + if (udc->remote_wkp) { + udc->todo |= REMOTE_WAKEUP; + ret = 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + return ret; +} + +static int max3420_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* hook up the driver */ + driver->driver.bus = NULL; + udc->driver = driver; + udc->gadget.speed = USB_SPEED_FULL; + + udc->gadget.is_selfpowered = udc->is_selfpowered; + udc->remote_wkp = 0; + udc->softconnect = true; + udc->todo |= UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return 0; +} + +static int max3420_udc_stop(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->is_selfpowered = udc->gadget.is_selfpowered; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver = NULL; + udc->softconnect = false; + udc->todo |= UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task && + udc->thread_task->state != TASK_RUNNING) + wake_up_process(udc->thread_task); + + return 0; +} + +static const struct usb_gadget_ops max3420_udc_ops = { + .udc_start = max3420_udc_start, + .udc_stop = max3420_udc_stop, + .wakeup = max3420_wakeup, +}; + +static void max3420_eps_init(struct max3420_udc *udc) +{ + int idx; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (idx = 0; idx < MAX3420_MAX_EPS; idx++) { + struct max3420_ep *ep = &udc->ep[idx]; + + spin_lock_init(&ep->lock); + INIT_LIST_HEAD(&ep->queue); + + ep->udc = udc; + ep->id = idx; + ep->halted = 0; + ep->maxpacket = 0; + ep->ep_usb.name = ep->name; + ep->ep_usb.ops = &max3420_ep_ops; + usb_ep_set_maxpacket_limit(&ep->ep_usb, MAX3420_EP_MAX_PACKET); + + if (idx == 0) { /* For EP0 */ + ep->ep_usb.desc = &ep0_desc; + ep->ep_usb.maxpacket = usb_endpoint_maxp(&ep0_desc); + ep->ep_usb.caps.type_control = true; + ep->ep_usb.caps.dir_in = true; + ep->ep_usb.caps.dir_out = true; + snprintf(ep->name, MAX3420_EPNAME_SIZE, "ep0"); + continue; + } + + if (idx == 1) { /* EP1 is OUT */ + ep->ep_usb.caps.dir_in = false; + ep->ep_usb.caps.dir_out = true; + snprintf(ep->name, MAX3420_EPNAME_SIZE, "ep1-bulk-out"); + } else { /* EP2 & EP3 are IN */ + ep->ep_usb.caps.dir_in = true; + ep->ep_usb.caps.dir_out = false; + snprintf(ep->name, MAX3420_EPNAME_SIZE, + "ep%d-bulk-in", idx); + } + ep->ep_usb.caps.type_iso = false; + ep->ep_usb.caps.type_int = false; + ep->ep_usb.caps.type_bulk = true; + + list_add_tail(&ep->ep_usb.ep_list, + &udc->gadget.ep_list); + } +} + +static int max3420_probe(struct spi_device *spi) +{ + struct max3420_udc *udc; + int err, irq; + u8 reg[8]; + + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + dev_err(&spi->dev, "UDC needs full duplex to work\n"); + return -EINVAL; + } + + spi->mode = SPI_MODE_3; + spi->bits_per_word = 8; + + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "Unable to setup SPI bus\n"); + return -EFAULT; + } + + udc = devm_kzalloc(&spi->dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + udc->spi = spi; + + udc->remote_wkp = 0; + + /* Setup gadget structure */ + udc->gadget.ops = &max3420_udc_ops; + udc->gadget.max_speed = USB_SPEED_FULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.ep0 = &udc->ep[0].ep_usb; + udc->gadget.name = driver_name; + + spin_lock_init(&udc->lock); + mutex_init(&udc->spi_bus_mutex); + + udc->ep0req.ep = &udc->ep[0]; + udc->ep0req.usb_req.buf = udc->ep0buf; + INIT_LIST_HEAD(&udc->ep0req.queue); + + /* setup Endpoints */ + max3420_eps_init(udc); + + /* configure SPI */ + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8); + spi_wr8(udc, MAX3420_REG_PINCTL, FDUPSPI); + + err = usb_add_gadget_udc(&spi->dev, &udc->gadget); + if (err) + return err; + + udc->dev = &udc->gadget.dev; + + spi_set_drvdata(spi, udc); + + irq = of_irq_get_byname(spi->dev.of_node, "udc"); + err = devm_request_irq(&spi->dev, irq, max3420_irq_handler, 0, + "max3420", udc); + if (err < 0) + return err; + + udc->thread_task = kthread_create(max3420_thread, udc, + "max3420-thread"); + if (IS_ERR(udc->thread_task)) + return PTR_ERR(udc->thread_task); + + irq = of_irq_get_byname(spi->dev.of_node, "vbus"); + if (irq <= 0) { /* no vbus irq implies self-powered design */ + udc->is_selfpowered = 1; + udc->vbus_active = true; + udc->todo |= UDC_START; + usb_udc_vbus_handler(&udc->gadget, udc->vbus_active); + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + max3420_start(udc); + } else { + udc->is_selfpowered = 0; + /* Detect current vbus status */ + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8); + if (reg[7] != 0xff) + udc->vbus_active = true; + + err = devm_request_irq(&spi->dev, irq, + max3420_vbus_handler, 0, "vbus", udc); + if (err < 0) + return err; + } + + return 0; +} + +static int max3420_remove(struct spi_device *spi) +{ + struct max3420_udc *udc = spi_get_drvdata(spi); + unsigned long flags; + + usb_del_gadget_udc(&udc->gadget); + + spin_lock_irqsave(&udc->lock, flags); + + kthread_stop(udc->thread_task); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct of_device_id max3420_udc_of_match[] = { + { .compatible = "maxim,max3420-udc"}, + { .compatible = "maxim,max3421-udc"}, + {}, +}; +MODULE_DEVICE_TABLE(of, max3420_udc_of_match); + +static struct spi_driver max3420_driver = { + .driver = { + .name = "max3420-udc", + .of_match_table = of_match_ptr(max3420_udc_of_match), + }, + .probe = max3420_probe, + .remove = max3420_remove, +}; + +module_spi_driver(max3420_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Jassi Brar <jaswinder.singh@linaro.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index b9df6369d56d..09eb64e9889a 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -3493,11 +3493,8 @@ static int tegra_xudc_probe(struct platform_device *pdev) } xudc->irq = platform_get_irq(pdev, 0); - if (xudc->irq < 0) { - dev_err(xudc->dev, "failed to get IRQ: %d\n", - xudc->irq); + if (xudc->irq < 0) return xudc->irq; - } err = devm_request_irq(&pdev->dev, xudc->irq, tegra_xudc_irq, 0, dev_name(&pdev->dev), xudc); |