From 30ad6273adad76017ddad737d45f8931cac554ad Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 29 Jan 2020 09:20:19 +0200 Subject: usb: dwc3: trace: print enqueue/dequeue pointers too By printing enqueue/dequeue pointers, we can make sure that our TRB handling is correct. We've had a recent situation where we were not always dequeueing all TRBs in an SG list and this helped figure out what the problem was. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/trace.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 9edff17111f7..3054b89512ff 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -227,6 +227,8 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __field(u32, size) __field(u32, ctrl) __field(u32, type) + __field(u32, enqueue) + __field(u32, dequeue) ), TP_fast_assign( __assign_str(name, dep->name); @@ -236,9 +238,12 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __entry->size = trb->size; __entry->ctrl = trb->ctrl; __entry->type = usb_endpoint_type(dep->endpoint.desc); + __entry->enqueue = dep->trb_enqueue; + __entry->dequeue = dep->trb_dequeue; ), - TP_printk("%s: trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", - __get_str(name), __entry->trb, __entry->bph, __entry->bpl, + TP_printk("%s: trb %p (E%d:D%d) buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", + __get_str(name), __entry->trb, __entry->enqueue, + __entry->dequeue, __entry->bph, __entry->bpl, ({char *s; int pcm = ((__entry->size >> 24) & 3) + 1; switch (__entry->type) { -- cgit v1.2.3 From 48ba02b2e2b1a1c80718e93fefe99c8319597c4a Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 21 Jan 2020 21:04:16 -0600 Subject: usb: gadget: add udc driver for max3420 The MAX3420 is USB2.0 only, UDC-over-SPI controller. This driver also supports the peripheral mode of MAX3421. Signed-off-by: Jassi Brar Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/Kconfig | 10 + drivers/usb/gadget/udc/Makefile | 1 + drivers/usb/gadget/udc/max3420_udc.c | 1331 ++++++++++++++++++++++++++++++++++ 3 files changed, 1342 insertions(+) create mode 100644 drivers/usb/gadget/udc/max3420_udc.c (limited to 'drivers/usb') 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/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 + * (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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 1e355f21d3fb96cc630dbb31e119f868c43119c4 Mon Sep 17 00:00:00 2001 From: Hanjie Lin Date: Mon, 20 Jan 2020 10:58:05 +0800 Subject: usb: dwc3: Add Amlogic A1 DWC3 glue Adds support for Amlogic A1 USB Control Glue HW. The Amlogic A1 SoC Family embeds 1 USB Controllers: - a DWC3 IP configured as Host for USB2 and USB3 A glue connects the controllers to the USB2 PHY of A1 SoC. Signed-off-by: Yue Wang Signed-off-by: Hanjie Lin Reviewed-by: Martin Blumenstingl Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-meson-g12a.c | 172 +++++++++++++++++++++++++------------ 1 file changed, 116 insertions(+), 56 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c index 8a3ec1a951fe..70d24b98fcad 100644 --- a/drivers/usb/dwc3/dwc3-meson-g12a.c +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -107,10 +107,37 @@ static const char *phy_names[PHY_COUNT] = { "usb2-phy0", "usb2-phy1", "usb3-phy0", }; +static struct clk_bulk_data meson_g12a_clocks[] = { + { .id = NULL }, +}; + +static struct clk_bulk_data meson_a1_clocks[] = { + { .id = "usb_ctrl" }, + { .id = "usb_bus" }, + { .id = "xtal_usb_ctrl" }, +}; + +struct dwc3_meson_g12a_drvdata { + bool otg_switch_supported; + struct clk_bulk_data *clks; + int num_clks; +}; + +static struct dwc3_meson_g12a_drvdata g12a_drvdata = { + .otg_switch_supported = true, + .clks = meson_g12a_clocks, + .num_clks = ARRAY_SIZE(meson_g12a_clocks), +}; + +static struct dwc3_meson_g12a_drvdata a1_drvdata = { + .otg_switch_supported = false, + .clks = meson_a1_clocks, + .num_clks = ARRAY_SIZE(meson_a1_clocks), +}; + struct dwc3_meson_g12a { struct device *dev; struct regmap *regmap; - struct clk *clk; struct reset_control *reset; struct phy *phys[PHY_COUNT]; enum usb_dr_mode otg_mode; @@ -120,6 +147,7 @@ struct dwc3_meson_g12a { struct regulator *vbus; struct usb_role_switch_desc switch_desc; struct usb_role_switch *role_switch; + const struct dwc3_meson_g12a_drvdata *drvdata; }; static void dwc3_meson_g12a_usb2_set_mode(struct dwc3_meson_g12a *priv, @@ -151,7 +179,7 @@ static int dwc3_meson_g12a_usb2_init(struct dwc3_meson_g12a *priv) U2P_R0_POWER_ON_RESET, U2P_R0_POWER_ON_RESET); - if (i == USB2_OTG_PHY) { + if (priv->drvdata->otg_switch_supported && i == USB2_OTG_PHY) { regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i), U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS, @@ -295,7 +323,7 @@ static int dwc3_meson_g12a_otg_mode_set(struct dwc3_meson_g12a *priv, { int ret; - if (!priv->phys[USB2_OTG_PHY]) + if (!priv->drvdata->otg_switch_supported || !priv->phys[USB2_OTG_PHY]) return -EINVAL; if (mode == PHY_MODE_USB_HOST) @@ -380,14 +408,60 @@ static struct device *dwc3_meson_g12_find_child(struct device *dev, return &pdev->dev; } +static int dwc3_meson_g12a_otg_init(struct platform_device *pdev, + struct dwc3_meson_g12a *priv) +{ + enum phy_mode otg_id; + int ret, irq; + struct device *dev = &pdev->dev; + + if (!priv->drvdata->otg_switch_supported) + return 0; + + if (priv->otg_mode == USB_DR_MODE_OTG) { + /* Ack irq before registering */ + regmap_update_bits(priv->regmap, USB_R5, + USB_R5_ID_DIG_IRQ, 0); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + dwc3_meson_g12a_irq_thread, + IRQF_ONESHOT, pdev->name, priv); + if (ret) + return ret; + } + + /* Setup OTG mode corresponding to the ID pin */ + if (priv->otg_mode == USB_DR_MODE_OTG) { + otg_id = dwc3_meson_g12a_get_id(priv); + if (otg_id != priv->otg_phy_mode) { + if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) + dev_warn(dev, "Failed to switch OTG mode\n"); + } + } + + /* Setup role switcher */ + priv->switch_desc.usb2_port = dwc3_meson_g12_find_child(dev, + "snps,dwc3"); + priv->switch_desc.udc = dwc3_meson_g12_find_child(dev, "snps,dwc2"); + priv->switch_desc.allow_userspace_control = true; + priv->switch_desc.set = dwc3_meson_g12a_role_set; + priv->switch_desc.get = dwc3_meson_g12a_role_get; + + priv->role_switch = usb_role_switch_register(dev, &priv->switch_desc); + if (IS_ERR(priv->role_switch)) + dev_warn(dev, "Unable to register Role Switch\n"); + + return ret; +} + static int dwc3_meson_g12a_probe(struct platform_device *pdev) { struct dwc3_meson_g12a *priv; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; void __iomem *base; - enum phy_mode otg_id; - int ret, i, irq; + int ret, i; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -409,17 +483,18 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) priv->vbus = NULL; } - priv->clk = devm_clk_get(dev, NULL); - if (IS_ERR(priv->clk)) - return PTR_ERR(priv->clk); + priv->drvdata = of_device_get_match_data(&pdev->dev); - ret = clk_prepare_enable(priv->clk); + ret = devm_clk_bulk_get(dev, + priv->drvdata->num_clks, + priv->drvdata->clks); if (ret) return ret; - devm_add_action_or_reset(dev, - (void(*)(void *))clk_disable_unprepare, - priv->clk); + ret = clk_bulk_prepare_enable(priv->drvdata->num_clks, + priv->drvdata->clks); + if (ret) + return ret; platform_set_drvdata(pdev, priv); priv->dev = dev; @@ -433,41 +508,28 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) ret = reset_control_reset(priv->reset); if (ret) - return ret; + goto err_disable_clks; ret = dwc3_meson_g12a_get_phys(priv); if (ret) - return ret; + goto err_disable_clks; if (priv->vbus) { ret = regulator_enable(priv->vbus); if (ret) - return ret; + goto err_disable_clks; } /* Get dr_mode */ priv->otg_mode = usb_get_dr_mode(dev); - if (priv->otg_mode == USB_DR_MODE_OTG) { - /* Ack irq before registering */ - regmap_update_bits(priv->regmap, USB_R5, - USB_R5_ID_DIG_IRQ, 0); - - irq = platform_get_irq(pdev, 0); - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - dwc3_meson_g12a_irq_thread, - IRQF_ONESHOT, pdev->name, priv); - if (ret) - return ret; - } - dwc3_meson_g12a_usb_init(priv); /* Init PHYs */ for (i = 0 ; i < PHY_COUNT ; ++i) { ret = phy_init(priv->phys[i]); if (ret) - return ret; + goto err_disable_clks; } /* Set PHY Power */ @@ -478,31 +540,12 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) } ret = of_platform_populate(np, NULL, NULL, dev); - if (ret) { - clk_disable_unprepare(priv->clk); + if (ret) goto err_phys_power; - } - - /* Setup OTG mode corresponding to the ID pin */ - if (priv->otg_mode == USB_DR_MODE_OTG) { - otg_id = dwc3_meson_g12a_get_id(priv); - if (otg_id != priv->otg_phy_mode) { - if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) - dev_warn(dev, "Failed to switch OTG mode\n"); - } - } - /* Setup role switcher */ - priv->switch_desc.usb2_port = dwc3_meson_g12_find_child(dev, - "snps,dwc3"); - priv->switch_desc.udc = dwc3_meson_g12_find_child(dev, "snps,dwc2"); - priv->switch_desc.allow_userspace_control = true; - priv->switch_desc.set = dwc3_meson_g12a_role_set; - priv->switch_desc.get = dwc3_meson_g12a_role_get; - - priv->role_switch = usb_role_switch_register(dev, &priv->switch_desc); - if (IS_ERR(priv->role_switch)) - dev_warn(dev, "Unable to register Role Switch\n"); + ret = dwc3_meson_g12a_otg_init(pdev, priv); + if (ret) + goto err_phys_power; pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -518,6 +561,10 @@ err_phys_exit: for (i = 0 ; i < PHY_COUNT ; ++i) phy_exit(priv->phys[i]); +err_disable_clks: + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); + return ret; } @@ -527,7 +574,8 @@ static int dwc3_meson_g12a_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; int i; - usb_role_switch_unregister(priv->role_switch); + if (priv->drvdata->otg_switch_supported) + usb_role_switch_unregister(priv->role_switch); of_platform_depopulate(dev); @@ -540,6 +588,9 @@ static int dwc3_meson_g12a_remove(struct platform_device *pdev) pm_runtime_put_noidle(dev); pm_runtime_set_suspended(dev); + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); + return 0; } @@ -547,7 +598,8 @@ static int __maybe_unused dwc3_meson_g12a_runtime_suspend(struct device *dev) { struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); - clk_disable(priv->clk); + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); return 0; } @@ -556,7 +608,8 @@ static int __maybe_unused dwc3_meson_g12a_runtime_resume(struct device *dev) { struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); - return clk_enable(priv->clk); + return clk_bulk_prepare_enable(priv->drvdata->num_clks, + priv->drvdata->clks); } static int __maybe_unused dwc3_meson_g12a_suspend(struct device *dev) @@ -619,7 +672,14 @@ static const struct dev_pm_ops dwc3_meson_g12a_dev_pm_ops = { }; static const struct of_device_id dwc3_meson_g12a_match[] = { - { .compatible = "amlogic,meson-g12a-usb-ctrl" }, + { + .compatible = "amlogic,meson-g12a-usb-ctrl", + .data = &g12a_drvdata, + }, + { + .compatible = "amlogic,meson-a1-usb-ctrl", + .data = &a1_drvdata, + }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, dwc3_meson_g12a_match); -- cgit v1.2.3 From a415083a11cc76f85322406fb91e2eb917c6cef9 Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Fri, 24 Jan 2020 09:41:31 +0100 Subject: usb: dwc2: add support for STM32MP15 SoCs USB OTG HS and FS This patch introduces a new parameter to activate external ID pin and valid vbus level detection, required on STM32MP15 SoC to support dual role, either in HS or FS. The STM32MP15 SoC uses the GGPIO register to enable the level detection. The level detector requires to be powered. Also adds the params structures for STM32MP15 OTG HS and STM32MP1 OTG FS. Acked-by: Minas Harutyunyan Signed-off-by: Amelie Delaunay Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.h | 8 ++++ drivers/usb/dwc2/hw.h | 8 ++++ drivers/usb/dwc2/params.c | 33 ++++++++++++++++ drivers/usb/dwc2/platform.c | 94 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 141 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 968e03b89d04..99b0bdfe0012 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -411,6 +411,10 @@ enum dwc2_ep0_state { * register. * 0 - Deactivate the transceiver (default) * 1 - Activate the transceiver + * @activate_stm_id_vb_detection: Activate external ID pin and Vbus level + * detection using GGPIO register. + * 0 - Deactivate the external level detection (default) + * 1 - Activate the external level detection * @g_dma: Enables gadget dma usage (default: autodetect). * @g_dma_desc: Enables gadget descriptor DMA (default: autodetect). * @g_rx_fifo_size: The periodic rx fifo size for the device, in @@ -481,6 +485,7 @@ struct dwc2_core_params { bool service_interval; u8 hird_threshold; bool activate_stm_fs_transceiver; + bool activate_stm_id_vb_detection; bool ipg_isoc_en; u16 max_packet_count; u32 max_transfer_size; @@ -874,6 +879,8 @@ struct dwc2_hregs_backup { * removed once all SoCs support usb transceiver. * @supplies: Definition of USB power supplies * @vbus_supply: Regulator supplying vbus. + * @usb33d: Optional 3.3v regulator used on some stm32 devices to + * supply ID and VBUS detection hardware. * @lock: Spinlock that protects all the driver data structures * @priv: Stores a pointer to the struct usb_hcd * @queuing_high_bandwidth: True if multiple packets of a high-bandwidth @@ -1061,6 +1068,7 @@ struct dwc2_hsotg { struct dwc2_hsotg_plat *plat; struct regulator_bulk_data supplies[DWC2_NUM_SUPPLIES]; struct regulator *vbus_supply; + struct regulator *usb33d; spinlock_t lock; void *priv; diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 510e87ec0be8..c4027bbcedec 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -54,6 +54,12 @@ #define GOTGCTL_HSTSETHNPEN BIT(10) #define GOTGCTL_HNPREQ BIT(9) #define GOTGCTL_HSTNEGSCS BIT(8) +#define GOTGCTL_BVALOVAL BIT(7) +#define GOTGCTL_BVALOEN BIT(6) +#define GOTGCTL_AVALOVAL BIT(5) +#define GOTGCTL_AVALOEN BIT(4) +#define GOTGCTL_VBVALOVAL BIT(3) +#define GOTGCTL_VBVALOEN BIT(2) #define GOTGCTL_SESREQ BIT(1) #define GOTGCTL_SESREQSCS BIT(0) @@ -227,6 +233,8 @@ #define GPVNDCTL HSOTG_REG(0x0034) #define GGPIO HSOTG_REG(0x0038) #define GGPIO_STM32_OTG_GCCFG_PWRDWN BIT(16) +#define GGPIO_STM32_OTG_GCCFG_VBDEN BIT(21) +#define GGPIO_STM32_OTG_GCCFG_IDEN BIT(22) #define GUID HSOTG_REG(0x003c) #define GSNPSID HSOTG_REG(0x0040) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 31e090ac9f1e..8ccc83f7eb3f 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -163,6 +163,35 @@ static void dwc2_set_stm32f7_hsotg_params(struct dwc2_hsotg *hsotg) p->host_perio_tx_fifo_size = 256; } +static void dwc2_set_stm32mp15_fsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; + p->speed = DWC2_SPEED_PARAM_FULL; + p->host_rx_fifo_size = 128; + p->host_nperio_tx_fifo_size = 96; + p->host_perio_tx_fifo_size = 96; + p->max_packet_count = 256; + p->phy_type = DWC2_PHY_TYPE_PARAM_FS; + p->i2c_enable = false; + p->activate_stm_fs_transceiver = true; + p->activate_stm_id_vb_detection = true; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; + p->activate_stm_id_vb_detection = true; + p->host_rx_fifo_size = 440; + p->host_nperio_tx_fifo_size = 256; + p->host_perio_tx_fifo_size = 256; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params }, { .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params }, @@ -186,6 +215,10 @@ const struct of_device_id dwc2_of_match_table[] = { { .compatible = "st,stm32f4x9-hsotg" }, { .compatible = "st,stm32f7-hsotg", .data = dwc2_set_stm32f7_hsotg_params }, + { .compatible = "st,stm32mp15-fsotg", + .data = dwc2_set_stm32mp15_fsotg_params }, + { .compatible = "st,stm32mp15-hsotg", + .data = dwc2_set_stm32mp15_hsotg_params }, {}, }; MODULE_DEVICE_TABLE(of, dwc2_of_match_table); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 3c6ce09a6db5..8368d6d66d64 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -312,6 +312,9 @@ static int dwc2_driver_remove(struct platform_device *dev) if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); + if (hsotg->ll_hw_enabled) dwc2_lowlevel_hw_disable(hsotg); @@ -464,10 +467,35 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) goto error; + if (hsotg->params.activate_stm_id_vb_detection) { + u32 ggpio; + + hsotg->usb33d = devm_regulator_get(hsotg->dev, "usb33d"); + if (IS_ERR(hsotg->usb33d)) { + retval = PTR_ERR(hsotg->usb33d); + if (retval != -EPROBE_DEFER) + dev_err(hsotg->dev, + "failed to request usb33d supply: %d\n", + retval); + goto error; + } + retval = regulator_enable(hsotg->usb33d); + if (retval) { + dev_err(hsotg->dev, + "failed to enable usb33d supply: %d\n", retval); + goto error; + } + + ggpio = dwc2_readl(hsotg, GGPIO); + ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(hsotg, ggpio, GGPIO); + } + if (hsotg->dr_mode != USB_DR_MODE_HOST) { retval = dwc2_gadget_init(hsotg); if (retval) - goto error; + goto error_init; hsotg->gadget_enabled = 1; } @@ -493,7 +521,7 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) { if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); - goto error; + goto error_init; } hsotg->hcd_enabled = 1; } @@ -509,6 +537,9 @@ static int dwc2_driver_probe(struct platform_device *dev) return 0; +error_init: + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); error: dwc2_lowlevel_hw_disable(hsotg); return retval; @@ -523,6 +554,37 @@ static int __maybe_unused dwc2_suspend(struct device *dev) if (is_device_mode) dwc2_hsotg_suspend(dwc2); + if (dwc2->params.activate_stm_id_vb_detection) { + unsigned long flags; + u32 ggpio, gotgctl; + + /* + * Need to force the mode to the current mode to avoid Mode + * Mismatch Interrupt when ID detection will be disabled. + */ + dwc2_force_mode(dwc2, !is_device_mode); + + spin_lock_irqsave(&dwc2->lock, flags); + gotgctl = dwc2_readl(dwc2, GOTGCTL); + /* bypass debounce filter, enable overrides */ + gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN; + /* Force A / B session if needed */ + if (gotgctl & GOTGCTL_ASESVLD) + gotgctl |= GOTGCTL_AVALOVAL; + if (gotgctl & GOTGCTL_BSESVLD) + gotgctl |= GOTGCTL_BVALOVAL; + dwc2_writel(dwc2, gotgctl, GOTGCTL); + spin_unlock_irqrestore(&dwc2->lock, flags); + + ggpio = dwc2_readl(dwc2, GGPIO); + ggpio &= ~GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio &= ~GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(dwc2, ggpio, GGPIO); + + regulator_disable(dwc2->usb33d); + } + if (dwc2->ll_hw_enabled && (is_device_mode || dwc2_host_can_poweroff_phy(dwc2))) { ret = __dwc2_lowlevel_hw_disable(dwc2); @@ -544,6 +606,34 @@ static int __maybe_unused dwc2_resume(struct device *dev) } dwc2->phy_off_for_suspend = false; + if (dwc2->params.activate_stm_id_vb_detection) { + unsigned long flags; + u32 ggpio, gotgctl; + + ret = regulator_enable(dwc2->usb33d); + if (ret) + return ret; + + ggpio = dwc2_readl(dwc2, GGPIO); + ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(dwc2, ggpio, GGPIO); + + /* ID/VBUS detection startup time */ + usleep_range(5000, 7000); + + spin_lock_irqsave(&dwc2->lock, flags); + gotgctl = dwc2_readl(dwc2, GOTGCTL); + gotgctl &= ~GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl &= ~(GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | + GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL); + dwc2_writel(dwc2, gotgctl, GOTGCTL); + spin_unlock_irqrestore(&dwc2->lock, flags); + } + + /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ + dwc2_force_dr_mode(dwc2); + if (dwc2_is_device_mode(dwc2)) ret = dwc2_hsotg_resume(dwc2); -- cgit v1.2.3 From 49f1997ad2e2e2d05917146e94dcb0ad600b866a Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Thu, 16 Jan 2020 22:14:33 +0800 Subject: usb: gadget: xudc: Remove redundant platform_get_irq error message platform_get_irq() will call dev_err() itself on failure, so there is no need for the driver to also do this. This is detected by coccinelle. Acked-by: Thierry Reding Reviewed-by: Nagarjuna Kristam Signed-off-by: YueHaibing Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/tegra-xudc.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 634c2c19a176..fc1eafc60cd7 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -3492,11 +3492,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); -- cgit v1.2.3 From 09890fb1073bb503dc9995cec319bfde63b15a27 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 8 Feb 2020 16:31:32 +0000 Subject: usb: gadget: remove redundant assignment to variable status Variable status is being assigned with a value that is never read, it is assigned a new value immediately afterwards. The assignment is redundant and can be removed. Addresses-Coverity: ("Unused value") Signed-off-by: Colin Ian King Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_uac1_legacy.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index 6677ae932de0..349deae7cabd 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -752,8 +752,6 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio->out_ep = ep; audio->out_ep->desc = &as_out_ep_desc; - status = -ENOMEM; - /* copy descriptors, and track endpoint copies */ status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, NULL); -- cgit v1.2.3 From e6c597a643a673e58b06b49b91b4261fcded5d94 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 8 Feb 2020 16:18:02 +0000 Subject: usb: cdns3: remove redundant assignment to pointer trb Pointer trb being assigned with a value that is never read, it is assigned a new value later on. The assignment is redundant and can be removed. Addresses-Coverity: ("Unused value") Signed-off-by: Colin Ian King Signed-off-by: Felipe Balbi --- drivers/usb/cdns3/gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c index 3574dbb09366..372460ea4df9 100644 --- a/drivers/usb/cdns3/gadget.c +++ b/drivers/usb/cdns3/gadget.c @@ -1380,7 +1380,7 @@ static bool cdns3_request_handled(struct cdns3_endpoint *priv_ep, struct cdns3_request *priv_req) { struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct cdns3_trb *trb = priv_req->trb; + struct cdns3_trb *trb; int current_index = 0; int handled = 0; int doorbell; -- cgit v1.2.3 From 238d760216541d345ee930e76c781f5cd1d22fba Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 10 Feb 2020 15:58:17 -0700 Subject: usb: dwc3: meson-g12a: Don't use ret uninitialized in dwc3_meson_g12a_otg_init Clang warns: ../drivers/usb/dwc3/dwc3-meson-g12a.c:421:6: warning: variable 'ret' is used uninitialized whenever 'if' condition is false [-Wsometimes-uninitialized] if (priv->otg_mode == USB_DR_MODE_OTG) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ../drivers/usb/dwc3/dwc3-meson-g12a.c:455:9: note: uninitialized use occurs here return ret; ^~~ ../drivers/usb/dwc3/dwc3-meson-g12a.c:421:2: note: remove the 'if' if its condition is always true if (priv->otg_mode == USB_DR_MODE_OTG) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ../drivers/usb/dwc3/dwc3-meson-g12a.c:415:9: note: initialize the variable 'ret' to silence this warning int ret, irq; ^ = 0 1 warning generated. It is not wrong, ret is only used when that if statement is true. Just directly return 0 at the end to avoid this. Fixes: 729149c53f04 ("usb: dwc3: Add Amlogic A1 DWC3 glue") Reported-by: kbuild test robot Link: https://groups.google.com/d/msg/clang-built-linux/w5iBENco_m4/PPuXreAxBQAJ Link: https://github.com/ClangBuiltLinux/linux/issues/869 Signed-off-by: Nathan Chancellor Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-meson-g12a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c index 70d24b98fcad..902553f39889 100644 --- a/drivers/usb/dwc3/dwc3-meson-g12a.c +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -452,7 +452,7 @@ static int dwc3_meson_g12a_otg_init(struct platform_device *pdev, if (IS_ERR(priv->role_switch)) dev_warn(dev, "Unable to register Role Switch\n"); - return ret; + return 0; } static int dwc3_meson_g12a_probe(struct platform_device *pdev) -- cgit v1.2.3 From 07f6842341abe978e6375078f84506ec3280ece5 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Tue, 10 Mar 2020 19:48:53 +0000 Subject: usb: dwc3: exynos: Add support for Exynos5422 suspend clk Exynos5422 DWC3 module support two clk USBD300 and SCLK_USBD300 so add missing code to enable/disable code and suspend clk, for this add a new compatible samsung,exynos5420-dwusb3 to help configure dwc3 code and dwc3 suspend clock. Suspend clock controls the PHY power change from P0 to P1/P2/P3 during U0 to U1/U2/U3 transition. Signed-off-by: Anand Moon Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-exynos.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 90bb022737da..48b68b6f0dc8 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -162,6 +162,12 @@ static const struct dwc3_exynos_driverdata exynos5250_drvdata = { .suspend_clk_idx = -1, }; +static const struct dwc3_exynos_driverdata exynos5420_drvdata = { + .clk_names = { "usbdrd30", "usbdrd30_susp_clk"}, + .num_clks = 2, + .suspend_clk_idx = 1, +}; + static const struct dwc3_exynos_driverdata exynos5433_drvdata = { .clk_names = { "aclk", "susp_clk", "pipe_pclk", "phyclk" }, .num_clks = 4, @@ -178,6 +184,9 @@ static const struct of_device_id exynos_dwc3_match[] = { { .compatible = "samsung,exynos5250-dwusb3", .data = &exynos5250_drvdata, + }, { + .compatible = "samsung,exynos5420-dwusb3", + .data = &exynos5420_drvdata, }, { .compatible = "samsung,exynos5433-dwusb3", .data = &exynos5433_drvdata, -- cgit v1.2.3 From 11c39809070fc0eb3aacb0d9ee71cd3678144f66 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 18 Feb 2020 17:12:18 +0200 Subject: usb: dwc3: Add ACPI support for xHCI ports The ACPI companion of the adapter has to be set for xHCI controller code to read and attach the ports described in the ACPI table. Use ACPI_COMPANION_SET macro to set this. Signed-off-by: Andy Shevchenko Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/host.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index fa252870c926..95a90ea08975 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -7,6 +7,7 @@ * Authors: Felipe Balbi , */ +#include #include #include "core.h" @@ -75,6 +76,7 @@ int dwc3_host_init(struct dwc3 *dwc) } xhci->dev.parent = dwc->dev; + ACPI_COMPANION_SET(&xhci->dev, ACPI_COMPANION(dwc->dev)); dwc->xhci = xhci; -- cgit v1.2.3 From a33e5d639c9b2ad980d3e458ca92af36cfcf3a9b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 18 Feb 2020 17:12:19 +0200 Subject: usb: dwc3: Remove kernel doc annotation where it's not needed The main comment in the file mistakenly marked with kernel doc annotation which makes the parser unhappy: .../dwc3/host.c:16: warning: Function parameter or member 'dwc' not described in 'dwc3_host_get_irq' Drop kernel doc annotation from host.c module. Signed-off-by: Andy Shevchenko Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 95a90ea08975..86dbd012b984 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -/** +/* * host.c - DesignWare USB3 DRD Controller Host Glue * * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com -- cgit v1.2.3 From a114c4ca64bd522aec1790c7e5c60c882f699d8f Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 5 Mar 2020 13:23:49 -0800 Subject: usb: dwc3: gadget: Don't clear flags before transfer ended We track END_TRANSFER command completion. Don't clear transfer started/ended flag prematurely. Otherwise, we'd run into the problem with restarting transfer before END_TRANSFER command finishes. Fixes: 6d8a019614f3 ("usb: dwc3: gadget: check for Missed Isoc from event status") Signed-off-by: Thinh Nguyen Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 1e00bf2d65a2..b032e62fff0f 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2570,10 +2570,8 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep, dwc3_gadget_ep_cleanup_completed_requests(dep, event, status); - if (stop) { + if (stop) dwc3_stop_active_transfer(dep, true, true); - dep->flags = DWC3_EP_ENABLED; - } /* * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround. -- cgit v1.2.3 From 2dedea035ae82c5af0595637a6eda4655532b21e Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 5 Mar 2020 13:24:01 -0800 Subject: usb: dwc3: gadget: Wrap around when skip TRBs When skipping TRBs, we need to account for wrapping around the ring buffer and not modifying some invalid TRBs. Without this fix, dwc3 won't be able to check for available TRBs. Cc: stable Fixes: 7746a8dfb3f9 ("usb: dwc3: gadget: extract dwc3_gadget_ep_skip_trbs()") Signed-off-by: Thinh Nguyen Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index b032e62fff0f..4d3c79d90a6e 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1521,7 +1521,7 @@ static void dwc3_gadget_ep_skip_trbs(struct dwc3_ep *dep, struct dwc3_request *r for (i = 0; i < req->num_trbs; i++) { struct dwc3_trb *trb; - trb = req->trb + i; + trb = &dep->trb_pool[dep->trb_dequeue]; trb->ctrl &= ~DWC3_TRB_CTRL_HWO; dwc3_ep_inc_deq(dep); } -- cgit v1.2.3 From 7ba6b09fda5e0cb741ee56f3264665e0edc64822 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Fri, 21 Feb 2020 10:15:31 +0100 Subject: usb: dwc3: core: add support for disabling SS instances in park mode In certain circumstances, the XHCI SuperSpeed instance in park mode can fail to recover, thus on Amlogic G12A/G12B/SM1 SoCs when there is high load on the single XHCI SuperSpeed instance, the controller can crash like: xhci-hcd xhci-hcd.0.auto: xHCI host not responding to stop endpoint command. xhci-hcd xhci-hcd.0.auto: Host halt failed, -110 xhci-hcd xhci-hcd.0.auto: xHCI host controller not responding, assume dead xhci-hcd xhci-hcd.0.auto: xHCI host not responding to stop endpoint command. hub 2-1.1:1.0: hub_ext_port_status failed (err = -22) xhci-hcd xhci-hcd.0.auto: HC died; cleaning up usb 2-1.1-port1: cannot reset (err = -22) Setting the PARKMODE_DISABLE_SS bit in the DWC3_USB3_GUCTL1 mitigates the issue. The bit is described as : "When this bit is set to '1' all SS bus instances in park mode are disabled" Synopsys explains: The GUCTL1.PARKMODE_DISABLE_SS is only available in dwc_usb3 controller running in host mode. This should not be set for other IPs. This can be disabled by default based on IP, but I recommend to have a property to enable this feature for devices that need this. CC: Dongjin Kim Cc: Jianxin Pan Cc: Thinh Nguyen Cc: Jun Li Reported-by: Tim Signed-off-by: Neil Armstrong Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 5 +++++ drivers/usb/dwc3/core.h | 4 ++++ 2 files changed, 9 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 1d85c42b9c67..43bd5b1ea9e2 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1029,6 +1029,9 @@ static int dwc3_core_init(struct dwc3 *dwc) if (dwc->dis_tx_ipgap_linecheck_quirk) reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; + if (dwc->parkmode_disable_ss_quirk) + reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS; + dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); } @@ -1342,6 +1345,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) "snps,dis-del-phy-power-chg-quirk"); dwc->dis_tx_ipgap_linecheck_quirk = device_property_read_bool(dev, "snps,dis-tx-ipgap-linecheck-quirk"); + dwc->parkmode_disable_ss_quirk = device_property_read_bool(dev, + "snps,parkmode-disable-ss-quirk"); dwc->tx_de_emphasis_quirk = device_property_read_bool(dev, "snps,tx_de_emphasis_quirk"); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 77c4a9abe365..3ecc69c5b150 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -249,6 +249,7 @@ #define DWC3_GUCTL_HSTINAUTORETRY BIT(14) /* Global User Control 1 Register */ +#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17) #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) @@ -1024,6 +1025,8 @@ struct dwc3_scratchpad_array { * change quirk. * @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate * check during HS transmit. + * @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed + * instances in park mode. * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk * @tx_de_emphasis: Tx de-emphasis value * 0 - -6dB de-emphasis @@ -1215,6 +1218,7 @@ struct dwc3 { unsigned dis_u2_freeclk_exists_quirk:1; unsigned dis_del_phy_power_chg_quirk:1; unsigned dis_tx_ipgap_linecheck_quirk:1; + unsigned parkmode_disable_ss_quirk:1; unsigned tx_de_emphasis_quirk:1; unsigned tx_de_emphasis:2; -- cgit v1.2.3 From 8a0a137997448690a642e2325fe73242788b26fe Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Tue, 25 Feb 2020 17:52:59 +0000 Subject: usb: dwc3: Registering a role switch in the DRD code. The Type-C drivers use USB role switch API to inform the system about the negotiated data role, so registering a role switch in the DRD code in order to support platforms with USB Type-C connectors. Cc: Greg Kroah-Hartman Cc: Rob Herring Cc: Mark Rutland CC: ShuFan Lee Cc: Heikki Krogerus Cc: Suzuki K Poulose Cc: Chunfeng Yun Cc: Yu Chen Cc: Felipe Balbi Cc: Hans de Goede Cc: Andy Shevchenko Cc: Jun Li Cc: Valentin Schneider Cc: Guillaume Gardet Cc: Jack Pham Cc: Bryan O'Donoghue Cc: linux-usb@vger.kernel.org Cc: devicetree@vger.kernel.org Suggested-by: Heikki Krogerus Tested-by: Bryan O'Donoghue Signed-off-by: Yu Chen Signed-off-by: John Stultz Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 3 ++ drivers/usb/dwc3/drd.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 3ecc69c5b150..1e9513cc32f3 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -954,6 +955,7 @@ struct dwc3_scratchpad_array { * @hsphy_mode: UTMI phy mode, one of following: * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW + * @role_sw: usb_role_switch handle * @usb2_phy: pointer to USB2 PHY * @usb3_phy: pointer to USB3 PHY * @usb2_generic_phy: pointer to USB2 PHY @@ -1089,6 +1091,7 @@ struct dwc3 { struct extcon_dev *edev; struct notifier_block edev_nb; enum usb_phy_interface hsphy_mode; + struct usb_role_switch *role_sw; u32 fladj; u32 irq_gadget; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index c946d64142ad..331c6e997f0c 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -476,6 +476,73 @@ static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) return edev; } +#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH) +#define ROLE_SWITCH 1 +static int dwc3_usb_role_switch_set(struct device *dev, enum usb_role role) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + u32 mode; + + switch (role) { + case USB_ROLE_HOST: + mode = DWC3_GCTL_PRTCAP_HOST; + break; + case USB_ROLE_DEVICE: + mode = DWC3_GCTL_PRTCAP_DEVICE; + break; + default: + mode = DWC3_GCTL_PRTCAP_DEVICE; + break; + } + + dwc3_set_mode(dwc, mode); + return 0; +} + +static enum usb_role dwc3_usb_role_switch_get(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + enum usb_role role; + + spin_lock_irqsave(&dwc->lock, flags); + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + role = USB_ROLE_HOST; + break; + case DWC3_GCTL_PRTCAP_DEVICE: + role = USB_ROLE_DEVICE; + break; + case DWC3_GCTL_PRTCAP_OTG: + role = dwc->current_otg_role; + break; + default: + role = USB_ROLE_DEVICE; + break; + } + spin_unlock_irqrestore(&dwc->lock, flags); + return role; +} + +static int dwc3_setup_role_switch(struct dwc3 *dwc) +{ + struct usb_role_switch_desc dwc3_role_switch = {NULL}; + + dwc3_role_switch.fwnode = dev_fwnode(dwc->dev); + dwc3_role_switch.set = dwc3_usb_role_switch_set; + dwc3_role_switch.get = dwc3_usb_role_switch_get; + dwc->role_sw = usb_role_switch_register(dwc->dev, &dwc3_role_switch); + if (IS_ERR(dwc->role_sw)) + return PTR_ERR(dwc->role_sw); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + return 0; +} +#else +#define ROLE_SWITCH 0 +#define dwc3_setup_role_switch(x) 0 +#endif + int dwc3_drd_init(struct dwc3 *dwc) { int ret, irq; @@ -484,7 +551,12 @@ int dwc3_drd_init(struct dwc3 *dwc) if (IS_ERR(dwc->edev)) return PTR_ERR(dwc->edev); - if (dwc->edev) { + if (ROLE_SWITCH && + device_property_read_bool(dwc->dev, "usb-role-switch")) { + ret = dwc3_setup_role_switch(dwc); + if (ret < 0) + return ret; + } else if (dwc->edev) { dwc->edev_nb.notifier_call = dwc3_drd_notifier; ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); @@ -531,6 +603,9 @@ void dwc3_drd_exit(struct dwc3 *dwc) { unsigned long flags; + if (dwc->role_sw) + usb_role_switch_unregister(dwc->role_sw); + if (dwc->edev) extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); -- cgit v1.2.3 From 98ed256a4dbad8f20226a4f414c1a60a4a558aec Mon Sep 17 00:00:00 2001 From: John Stultz Date: Tue, 25 Feb 2020 17:53:01 +0000 Subject: usb: dwc3: Add support for role-switch-default-mode binding Support the new role-switch-default-mode binding for configuring the default role the controller assumes as when the usb role is USB_ROLE_NONE This patch was split out from a larger patch originally by Yu Chen Cc: Greg Kroah-Hartman Cc: Rob Herring Cc: Mark Rutland CC: ShuFan Lee Cc: Heikki Krogerus Cc: Suzuki K Poulose Cc: Chunfeng Yun Cc: Yu Chen Cc: Felipe Balbi Cc: Hans de Goede Cc: Andy Shevchenko Cc: Jun Li Cc: Valentin Schneider Cc: Guillaume Gardet Cc: Bryan O'Donoghue Cc: Jack Pham Cc: linux-usb@vger.kernel.org Cc: devicetree@vger.kernel.org Tested-by: Bryan O'Donoghue Signed-off-by: John Stultz Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 3 +++ drivers/usb/dwc3/drd.c | 25 ++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 1e9513cc32f3..6846eb0cba13 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -956,6 +956,8 @@ struct dwc3_scratchpad_array { * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW * @role_sw: usb_role_switch handle + * @role_switch_default_mode: default operation mode of controller while + * usb role is USB_ROLE_NONE. * @usb2_phy: pointer to USB2 PHY * @usb3_phy: pointer to USB3 PHY * @usb2_generic_phy: pointer to USB2 PHY @@ -1092,6 +1094,7 @@ struct dwc3 { struct notifier_block edev_nb; enum usb_phy_interface hsphy_mode; struct usb_role_switch *role_sw; + enum usb_dr_mode role_switch_default_mode; u32 fladj; u32 irq_gadget; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 331c6e997f0c..db68d48c2267 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -491,7 +491,10 @@ static int dwc3_usb_role_switch_set(struct device *dev, enum usb_role role) mode = DWC3_GCTL_PRTCAP_DEVICE; break; default: - mode = DWC3_GCTL_PRTCAP_DEVICE; + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) + mode = DWC3_GCTL_PRTCAP_HOST; + else + mode = DWC3_GCTL_PRTCAP_DEVICE; break; } @@ -517,7 +520,10 @@ static enum usb_role dwc3_usb_role_switch_get(struct device *dev) role = dwc->current_otg_role; break; default: - role = USB_ROLE_DEVICE; + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) + role = USB_ROLE_HOST; + else + role = USB_ROLE_DEVICE; break; } spin_unlock_irqrestore(&dwc->lock, flags); @@ -527,6 +533,19 @@ static enum usb_role dwc3_usb_role_switch_get(struct device *dev) static int dwc3_setup_role_switch(struct dwc3 *dwc) { struct usb_role_switch_desc dwc3_role_switch = {NULL}; + const char *str; + u32 mode; + int ret; + + ret = device_property_read_string(dwc->dev, "role-switch-default-mode", + &str); + if (ret >= 0 && !strncmp(str, "host", strlen("host"))) { + dwc->role_switch_default_mode = USB_DR_MODE_HOST; + mode = DWC3_GCTL_PRTCAP_HOST; + } else { + dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL; + mode = DWC3_GCTL_PRTCAP_DEVICE; + } dwc3_role_switch.fwnode = dev_fwnode(dwc->dev); dwc3_role_switch.set = dwc3_usb_role_switch_set; @@ -535,7 +554,7 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc) if (IS_ERR(dwc->role_sw)) return PTR_ERR(dwc->role_sw); - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + dwc3_set_mode(dwc, mode); return 0; } #else -- cgit v1.2.3 From 0d3a97083e0c6cb70d8e02db342a36bb695d43ea Mon Sep 17 00:00:00 2001 From: John Stultz Date: Tue, 25 Feb 2020 17:53:03 +0000 Subject: usb: dwc3: Rework clock initialization to be more flexible The dwc3 core binding specifies three clocks: ref, bus_early, and suspend which are all controlled in the driver together. However some variants of the hardware my not have all three clks, or some may have more. Usually this was handled by using the dwc3-of-simple glue driver, but that resulted in a proliferation of bindings for for every variant, when the only difference was the clocks and resets lists. So this patch reworks the reading of the clks from the dts to use devm_clk_bulk_get_all() will will fetch all the clocks specified in the dts together. This patch was recommended by Rob Herring as an alternative to creating multiple bindings for each variant of hardware. Cc: Greg Kroah-Hartman Cc: Rob Herring Cc: Mark Rutland CC: ShuFan Lee Cc: Heikki Krogerus Cc: Suzuki K Poulose Cc: Chunfeng Yun Cc: Yu Chen Cc: Felipe Balbi Cc: Hans de Goede Cc: Andy Shevchenko Cc: Jun Li Cc: Valentin Schneider Cc: Guillaume Gardet Cc: Jack Pham Cc: linux-usb@vger.kernel.org Cc: devicetree@vger.kernel.org Suggested-by: Rob Herring Signed-off-by: John Stultz Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 43bd5b1ea9e2..874bc49d417f 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -289,12 +289,6 @@ done: return 0; } -static const struct clk_bulk_data dwc3_core_clks[] = { - { .id = "ref" }, - { .id = "bus_early" }, - { .id = "suspend" }, -}; - /* * dwc3_frame_length_adjustment - Adjusts frame length if required * @dwc3: Pointer to our controller context structure @@ -1446,11 +1440,6 @@ static int dwc3_probe(struct platform_device *pdev) if (!dwc) return -ENOMEM; - dwc->clks = devm_kmemdup(dev, dwc3_core_clks, sizeof(dwc3_core_clks), - GFP_KERNEL); - if (!dwc->clks) - return -ENOMEM; - dwc->dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1486,17 +1475,18 @@ static int dwc3_probe(struct platform_device *pdev) return PTR_ERR(dwc->reset); if (dev->of_node) { - dwc->num_clks = ARRAY_SIZE(dwc3_core_clks); - - ret = devm_clk_bulk_get(dev, dwc->num_clks, dwc->clks); + ret = devm_clk_bulk_get_all(dev, &dwc->clks); if (ret == -EPROBE_DEFER) return ret; /* * Clocks are optional, but new DT platforms should support all * clocks as required by the DT-binding. */ - if (ret) + if (ret < 0) dwc->num_clks = 0; + else + dwc->num_clks = ret; + } ret = reset_control_deassert(dwc->reset); -- cgit v1.2.3 From 4a1d042af4209544dba2196c2e92803907c3db88 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Tue, 25 Feb 2020 17:53:04 +0000 Subject: usb: dwc3: Rework resets initialization to be more flexible The dwc3 core binding specifies one reset. However some variants of the hardware may have more. Previously this was handled by using the dwc3-of-simple glue driver, but that resulted in a proliferation of bindings for for every variant, when the only difference was the clocks and resets lists. So this patch reworks the reading of the resets to fetch all the resets specified in the dts together. This patch was recommended by Rob Herring as an alternative to creating multiple bindings for each variant of hardware. Cc: Greg Kroah-Hartman Cc: Rob Herring Cc: Mark Rutland CC: ShuFan Lee Cc: Heikki Krogerus Cc: Suzuki K Poulose Cc: Chunfeng Yun Cc: Yu Chen Cc: Felipe Balbi Cc: Hans de Goede Cc: Andy Shevchenko Cc: Jun Li Cc: Valentin Schneider Cc: Guillaume Gardet Cc: Jack Pham Cc: linux-usb@vger.kernel.org Cc: devicetree@vger.kernel.org Suggested-by: Rob Herring Signed-off-by: John Stultz Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 874bc49d417f..e6879a38b5a8 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1470,7 +1470,7 @@ static int dwc3_probe(struct platform_device *pdev) dwc3_get_properties(dwc); - dwc->reset = devm_reset_control_get_optional_shared(dev, NULL); + dwc->reset = devm_reset_control_array_get(dev, true, true); if (IS_ERR(dwc->reset)) return PTR_ERR(dwc->reset); -- cgit v1.2.3 From 0227cc84c44417a29c8102e41db8ec2c11ebc6b2 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Thu, 20 Feb 2020 00:20:04 +0800 Subject: usb: dwc3: core: don't do suspend for device mode if already suspended If dwc->dev in device mode already runtime suspended, don't do it again for system suspend. Signed-off-by: Li Jun Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index e6879a38b5a8..edc17155cb2b 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1632,6 +1632,8 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: + if (pm_runtime_suspended(dwc->dev)) + break; spin_lock_irqsave(&dwc->lock, flags); dwc3_gadget_suspend(dwc); spin_unlock_irqrestore(&dwc->lock, flags); -- cgit v1.2.3 From 201c26c08db4bafde361ace54d74eac5d69fb66a Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 12 Feb 2020 11:18:53 +0100 Subject: usb: dwc3: qcom: Replace by The DWC3 USB driver is not a clock provider, and just needs to call of_clk_get_parent_count(). Hence it can include instead of . Reviewed-by: Stephen Boyd Signed-off-by: Geert Uytterhoeven Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-qcom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 261af9e38ddd..1dfd024cd06b 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include -- cgit v1.2.3 From 1a0808cb9e417170ed6ab97254cf319dc3e3c310 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Tue, 4 Feb 2020 15:29:33 +0000 Subject: usb: dwc2: Implement set_selfpowered() dwc2 always reports as self-powered in response to a device status request. Implement the set_selfpowered() operations so that the gadget can report as bus-powered when appropriate. This is modelled on the dwc3 implementation. Acked-by: Minas Harutyunyan Signed-off-by: John Keeping Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/gadget.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 92ed32ec1607..12b98b466287 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1646,7 +1646,8 @@ static int dwc2_hsotg_process_req_status(struct dwc2_hsotg *hsotg, switch (ctrl->bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: - status = 1 << USB_DEVICE_SELF_POWERED; + status = hsotg->gadget.is_selfpowered << + USB_DEVICE_SELF_POWERED; status |= hsotg->remote_wakeup_allowed << USB_DEVICE_REMOTE_WAKEUP; reply = cpu_to_le16(status); @@ -4527,6 +4528,26 @@ static int dwc2_hsotg_gadget_getframe(struct usb_gadget *gadget) return dwc2_hsotg_read_frameno(to_hsotg(gadget)); } +/** + * dwc2_hsotg_set_selfpowered - set if device is self/bus powered + * @gadget: The usb gadget state + * @is_selfpowered: Whether the device is self-powered + * + * Set if the device is self or bus powered. + */ +static int dwc2_hsotg_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + gadget->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} + /** * dwc2_hsotg_pullup - connect/disconnect the USB PHY * @gadget: The usb gadget state @@ -4618,6 +4639,7 @@ static int dwc2_hsotg_vbus_draw(struct usb_gadget *gadget, unsigned int mA) static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = { .get_frame = dwc2_hsotg_gadget_getframe, + .set_selfpowered = dwc2_hsotg_set_selfpowered, .udc_start = dwc2_hsotg_udc_start, .udc_stop = dwc2_hsotg_udc_stop, .pullup = dwc2_hsotg_pullup, -- cgit v1.2.3 From f2c2e717642c66f7fe7e5dd69b2e8ff5849f4d10 Mon Sep 17 00:00:00 2001 From: Andrey Konovalov Date: Mon, 24 Feb 2020 17:13:03 +0100 Subject: usb: gadget: add raw-gadget interface 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. Enabled with CONFIG_USB_RAW_GADGET. Raw Gadget is currently a strictly debugging feature and shouldn't be used in production. Raw Gadget is similar to GadgetFS, but provides a more low-level and direct access to the USB Gadget layer for the userspace. The key differences are: 1. Every USB request is passed to the userspace to get a response, while GadgetFS responds to some USB requests internally based on the provided descriptors. However note, that the UDC driver might respond to some requests on its own and never forward them to the Gadget layer. 2. GadgetFS performs some sanity checks on the provided USB descriptors, while Raw Gadget allows you to provide arbitrary data as responses to USB requests. 3. Raw Gadget provides a way to select a UDC device/driver to bind to, while GadgetFS currently binds to the first available UDC. 4. Raw Gadget uses predictable endpoint names (handles) across different UDCs (as long as UDCs have enough endpoints of each required transfer type). 5. Raw Gadget has ioctl-based interface instead of a filesystem-based one. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Andrey Konovalov Signed-off-by: Felipe Balbi --- Documentation/usb/index.rst | 1 + Documentation/usb/raw-gadget.rst | 61 ++ drivers/usb/gadget/legacy/Kconfig | 11 + drivers/usb/gadget/legacy/Makefile | 1 + drivers/usb/gadget/legacy/raw_gadget.c | 1078 ++++++++++++++++++++++++++++++++ include/uapi/linux/usb/raw_gadget.h | 167 +++++ 6 files changed, 1319 insertions(+) create mode 100644 Documentation/usb/raw-gadget.rst create mode 100644 drivers/usb/gadget/legacy/raw_gadget.c create mode 100644 include/uapi/linux/usb/raw_gadget.h (limited to 'drivers/usb') diff --git a/Documentation/usb/index.rst b/Documentation/usb/index.rst index 36b6ebd9a9d9..b656c9be23ed 100644 --- a/Documentation/usb/index.rst +++ b/Documentation/usb/index.rst @@ -22,6 +22,7 @@ USB support misc_usbsevseg mtouchusb ohci + raw-gadget usbip_protocol usbmon usb-serial diff --git a/Documentation/usb/raw-gadget.rst b/Documentation/usb/raw-gadget.rst new file mode 100644 index 000000000000..9e78cb858f86 --- /dev/null +++ b/Documentation/usb/raw-gadget.rst @@ -0,0 +1,61 @@ +============== +USB Raw Gadget +============== + +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. Enabled with CONFIG_USB_RAW_GADGET. Raw Gadget is +currently a strictly debugging feature and shouldn't be used in +production, use GadgetFS instead. + +Comparison to GadgetFS +~~~~~~~~~~~~~~~~~~~~~~ + +Raw Gadget is similar to GadgetFS, but provides a more low-level and +direct access to the USB Gadget layer for the userspace. The key +differences are: + +1. Every USB request is passed to the userspace to get a response, while + GadgetFS responds to some USB requests internally based on the provided + descriptors. However note, that the UDC driver might respond to some + requests on its own and never forward them to the Gadget layer. + +2. GadgetFS performs some sanity checks on the provided USB descriptors, + while Raw Gadget allows you to provide arbitrary data as responses to + USB requests. + +3. Raw Gadget provides a way to select a UDC device/driver to bind to, + while GadgetFS currently binds to the first available UDC. + +4. Raw Gadget uses predictable endpoint names (handles) across different + UDCs (as long as UDCs have enough endpoints of each required transfer + type). + +5. Raw Gadget has ioctl-based interface instead of a filesystem-based one. + +Userspace interface +~~~~~~~~~~~~~~~~~~~ + +To create a Raw Gadget instance open /dev/raw-gadget. Multiple raw-gadget +instances (bound to different UDCs) can be used at the same time. The +interaction with the opened file happens through the ioctl() calls, see +comments in include/uapi/linux/usb/raw_gadget.h for details. + +The typical usage of Raw Gadget looks like: + +1. Open Raw Gadget instance via /dev/raw-gadget. +2. Initialize the instance via USB_RAW_IOCTL_INIT. +3. Launch the instance with USB_RAW_IOCTL_RUN. +4. In a loop issue USB_RAW_IOCTL_EVENT_FETCH calls to receive events from + Raw Gadget and react to those depending on what kind of USB device + needs to be emulated. + +Potential future improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Implement ioctl's for setting/clearing halt status on endpoints. + +- Reporting more events (suspend, resume, etc.) through + USB_RAW_IOCTL_EVENT_FETCH. + +- Support O_NONBLOCK I/O. 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#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/include/uapi/linux/usb/raw_gadget.h b/include/uapi/linux/usb/raw_gadget.h new file mode 100644 index 000000000000..00cbded71061 --- /dev/null +++ b/include/uapi/linux/usb/raw_gadget.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * USB Raw Gadget driver. + * + * See Documentation/usb/raw-gadget.rst for more details. + */ + +#ifndef _UAPI__LINUX_USB_RAW_GADGET_H +#define _UAPI__LINUX_USB_RAW_GADGET_H + +#include +#include +#include + +/* Maximum length of driver_name/device_name in the usb_raw_init struct. */ +#define UDC_NAME_LENGTH_MAX 128 + +/* + * struct usb_raw_init - argument for USB_RAW_IOCTL_INIT ioctl. + * @speed: The speed of the emulated USB device, takes the same values as + * the usb_device_speed enum: USB_SPEED_FULL, USB_SPEED_HIGH, etc. + * @driver_name: The name of the UDC driver. + * @device_name: The name of a UDC instance. + * + * The last two fields identify a UDC the gadget driver should bind to. + * For example, Dummy UDC has "dummy_udc" as its driver_name and "dummy_udc.N" + * as its device_name, where N in the index of the Dummy UDC instance. + * At the same time the dwc2 driver that is used on Raspberry Pi Zero, has + * "20980000.usb" as both driver_name and device_name. + */ +struct usb_raw_init { + __u8 driver_name[UDC_NAME_LENGTH_MAX]; + __u8 device_name[UDC_NAME_LENGTH_MAX]; + __u8 speed; +}; + +/* The type of event fetched with the USB_RAW_IOCTL_EVENT_FETCH ioctl. */ +enum usb_raw_event_type { + USB_RAW_EVENT_INVALID = 0, + + /* This event is queued when the driver has bound to a UDC. */ + USB_RAW_EVENT_CONNECT = 1, + + /* This event is queued when a new control request arrived to ep0. */ + USB_RAW_EVENT_CONTROL = 2, + + /* The list might grow in the future. */ +}; + +/* + * struct usb_raw_event - argument for USB_RAW_IOCTL_EVENT_FETCH ioctl. + * @type: The type of the fetched event. + * @length: Length of the data buffer. Updated by the driver and set to the + * actual length of the fetched event data. + * @data: A buffer to store the fetched event data. + * + * Currently the fetched data buffer is empty for USB_RAW_EVENT_CONNECT, + * and contains struct usb_ctrlrequest for USB_RAW_EVENT_CONTROL. + */ +struct usb_raw_event { + __u32 type; + __u32 length; + __u8 data[0]; +}; + +#define USB_RAW_IO_FLAGS_ZERO 0x0001 +#define USB_RAW_IO_FLAGS_MASK 0x0001 + +static int usb_raw_io_flags_valid(__u16 flags) +{ + return (flags & ~USB_RAW_IO_FLAGS_MASK) == 0; +} + +static int usb_raw_io_flags_zero(__u16 flags) +{ + return (flags & USB_RAW_IO_FLAGS_ZERO); +} + +/* + * struct usb_raw_ep_io - argument for USB_RAW_IOCTL_EP0/EP_WRITE/READ ioctls. + * @ep: Endpoint handle as returned by USB_RAW_IOCTL_EP_ENABLE for + * USB_RAW_IOCTL_EP_WRITE/READ. Ignored for USB_RAW_IOCTL_EP0_WRITE/READ. + * @flags: When USB_RAW_IO_FLAGS_ZERO is specified, the zero flag is set on + * the submitted USB request, see include/linux/usb/gadget.h for details. + * @length: Length of data. + * @data: Data to send for USB_RAW_IOCTL_EP0/EP_WRITE. Buffer to store received + * data for USB_RAW_IOCTL_EP0/EP_READ. + */ +struct usb_raw_ep_io { + __u16 ep; + __u16 flags; + __u32 length; + __u8 data[0]; +}; + +/* + * Initializes a Raw Gadget instance. + * Accepts a pointer to the usb_raw_init struct as an argument. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init) + +/* + * Instructs Raw Gadget to bind to a UDC and start emulating a USB device. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_RUN _IO('U', 1) + +/* + * A blocking ioctl that waits for an event and returns fetched event data to + * the user. + * Accepts a pointer to the usb_raw_event struct. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event) + +/* + * Queues an IN (OUT for READ) urb as a response to the last control request + * received on endpoint 0, provided that was an IN (OUT for READ) request and + * waits until the urb is completed. Copies received data to user for READ. + * Accepts a pointer to the usb_raw_ep_io struct as an argument. + * Returns length of trasferred data on success or negative error code on + * failure. + */ +#define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io) +#define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io) + +/* + * Finds an endpoint that supports the transfer type specified in the + * descriptor and enables it. + * Accepts a pointer to the usb_endpoint_descriptor struct as an argument. + * Returns enabled endpoint handle on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor) + +/* Disables specified endpoint. + * Accepts endpoint handle as an argument. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_EP_DISABLE _IOW('U', 6, __u32) + +/* + * Queues an IN (OUT for READ) urb as a response to the last control request + * received on endpoint usb_raw_ep_io.ep, provided that was an IN (OUT for READ) + * request and waits until the urb is completed. Copies received data to user + * for READ. + * Accepts a pointer to the usb_raw_ep_io struct as an argument. + * Returns length of trasferred data on success or negative error code on + * failure. + */ +#define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io) +#define USB_RAW_IOCTL_EP_READ _IOWR('U', 8, struct usb_raw_ep_io) + +/* + * Switches the gadget into the configured state. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_CONFIGURE _IO('U', 9) + +/* + * Constrains UDC VBUS power usage. + * Accepts current limit in 2 mA units as an argument. + * Returns 0 on success or negative error code on failure. + */ +#define USB_RAW_IOCTL_VBUS_DRAW _IOW('U', 10, __u32) + +#endif /* _UAPI__LINUX_USB_RAW_GADGET_H */ -- cgit v1.2.3 From 8e11a977c8eb02aa06604b11cfb4e43eb8287537 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 28 Feb 2020 10:25:57 +0100 Subject: usb: dwc2: Silence warning about supplies during deferred probe Don't confuse user with meaningless warning about the failure in getting supplies in case of deferred probe. Acked-by: Minas Harutyunyan Reviewed-by: Krzysztof Kozlowski Signed-off-by: Marek Szyprowski Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/platform.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 8368d6d66d64..8ea4a24637fa 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -285,7 +285,9 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) ret = devm_regulator_bulk_get(hsotg->dev, ARRAY_SIZE(hsotg->supplies), hsotg->supplies); if (ret) { - dev_err(hsotg->dev, "failed to request supplies: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(hsotg->dev, "failed to request supplies: %d\n", + ret); return ret; } return 0; -- cgit v1.2.3 From f48f7f9216cec92d7281fb23bd3874cf82338451 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 28 Feb 2020 10:28:08 +0100 Subject: udc: s3c-hsudc: Silence warning about supplies during deferred probe Don't confuse user with meaningless warning about the failure in getting supplies in case of deferred probe. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Marek Szyprowski Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/s3c-hsudc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/s3c-hsudc.c b/drivers/usb/gadget/udc/s3c-hsudc.c index 21252fbc0319..aaca1b0a2f59 100644 --- a/drivers/usb/gadget/udc/s3c-hsudc.c +++ b/drivers/usb/gadget/udc/s3c-hsudc.c @@ -1285,7 +1285,8 @@ static int s3c_hsudc_probe(struct platform_device *pdev) ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hsudc->supplies), hsudc->supplies); if (ret != 0) { - dev_err(dev, "failed to request supplies: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request supplies: %d\n", ret); goto err_supplies; } -- cgit v1.2.3 From eeead847487f726fa177d0f4060c4f0816ad9cd9 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Fri, 14 Feb 2020 15:24:46 +0100 Subject: usb: gadget: amd5536udc: fix spelling mistake "reserverd" -> "reserved" The variable is named reserved, the comment should say so. Signed-off-by: Alexandre Belloni Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/amd5536udc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h index dfdef6a28904..0262383f8c79 100644 --- a/drivers/usb/gadget/udc/amd5536udc.h +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -440,7 +440,7 @@ struct udc_ep_regs { /* endpoint data descriptor pointer */ u32 desptr; - /* reserverd */ + /* reserved */ u32 reserved; /* write/read confirmation */ -- cgit v1.2.3 From 5e5caf4fa8d3039140b4548b6ab23dd17fce9b2c Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Mon, 3 Feb 2020 18:05:32 -0800 Subject: usb: gadget: composite: Inform controller driver of self-powered Different configuration/condition may draw different power. Inform the controller driver of the change so it can respond properly (e.g. GET_STATUS request). This fixes an issue with setting MaxPower from configfs. The composite driver doesn't check this value when setting self-powered. Cc: stable@vger.kernel.org Fixes: 88af8bbe4ef7 ("usb: gadget: the start of the configfs interface") Signed-off-by: Thinh Nguyen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/composite.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers/usb') 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); } -- cgit v1.2.3 From eaea6efe1da90cfaf70a71715a2f71816e97432c Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 11 Feb 2020 17:23:03 -0600 Subject: usb: gadget: f_phonet: Replace zero-length array with flexible-array member The current codebase makes use of the zero-length array language extension to the C90 standard, but the preferred mechanism to declare variable-length types such as these ones is a flexible array member[1][2], introduced in C99: struct foo { int stuff; struct boo array[]; }; By making use of the mechanism above, we will get a compiler warning in case the flexible array does not occur last in the structure, which will help us prevent some kind of undefined behavior bugs from being inadvertenly introduced[3] to the codebase from now on. This issue was found with the help of Coccinelle. [1] https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html [2] https://github.com/KSPP/linux/issues/21 [3] commit 76497732932f ("cxgb3/l2t: Fix undefined behaviour") Signed-off-by: Gustavo A. R. Silva Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_phonet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/f_phonet.c b/drivers/usb/gadget/function/f_phonet.c index 8b72b192c747..d7f6cc51b7ec 100644 --- a/drivers/usb/gadget/function/f_phonet.c +++ b/drivers/usb/gadget/function/f_phonet.c @@ -48,7 +48,7 @@ struct f_phonet { struct usb_ep *in_ep, *out_ep; struct usb_request *in_req; - struct usb_request *out_reqv[0]; + struct usb_request *out_reqv[]; }; static int phonet_rxq_size = 17; -- cgit v1.2.3 From 6dbf05fcb68870546d5059ac95747d21ffbf807d Mon Sep 17 00:00:00 2001 From: Tao Ren Date: Mon, 2 Mar 2020 22:23:30 -0800 Subject: usb: gadget: aspeed: support per-vhub usb descriptors This patch store vhub's standard usb descriptors in struct "ast_vhub" so it's more convenient to customize descriptors and potentially support multiple vhub instances in the future. Signed-off-by: Tao Ren Acked-by: Benjamin Herrenschmidt Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/aspeed-vhub/hub.c | 43 ++++++++++++++++++++++--------- drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 15 +++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 19b3517e04c0..9c3027306b15 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 @@ -834,9 +832,30 @@ 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)); + + /* 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..191f9fae7420 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 +#include + /***************************** * * * VHUB register definitions * @@ -373,6 +376,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; @@ -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 */ -- cgit v1.2.3 From 487bc82801ab056e084d3005185ba39264d40e7c Mon Sep 17 00:00:00 2001 From: Tao Ren Date: Mon, 2 Mar 2020 22:23:31 -0800 Subject: usb: gadget: aspeed: read vhub properties from device tree The patch introduces 2 DT properties ("aspeed,vhub-downstream-ports" and "aspeed,vhub-generic-endpoints") which replaces hardcoded port/endpoint number. It is to make it more convenient to add support for newer vhub revisions with different number of ports and endpoints. Signed-off-by: Tao Ren Reviewed-by: Joel Stanley Acked-by: Benjamin Herrenschmidt Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/aspeed-vhub/core.c | 68 +++++++++++++++++++------------ drivers/usb/gadget/udc/aspeed-vhub/dev.c | 30 ++++++++++---- drivers/usb/gadget/udc/aspeed-vhub/epn.c | 4 +- drivers/usb/gadget/udc/aspeed-vhub/hub.c | 15 +++---- drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 28 ++++++------- 5 files changed, 88 insertions(+), 57 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 90b134d5dca9..f8ab8e012f34 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; 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) @@ -546,14 +547,25 @@ 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 9c3027306b15..6e565c3dbb5b 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -502,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)) @@ -585,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]; @@ -628,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]; @@ -674,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--; @@ -755,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)) @@ -778,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)) @@ -812,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 */ @@ -845,6 +845,7 @@ static void ast_vhub_init_desc(struct ast_vhub *vhub) /* 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, diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index 191f9fae7420..fac79ef6d669 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -79,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) @@ -213,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 */ @@ -315,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 */ @@ -345,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; @@ -361,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) @@ -402,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; -- cgit v1.2.3 From b9a57990f9d71bc00b4603b7122a107810b74cbc Mon Sep 17 00:00:00 2001 From: Tao Ren Date: Mon, 2 Mar 2020 22:23:32 -0800 Subject: usb: gadget: aspeed: add ast2600 vhub support Add AST2600 support in aspeed-vhub driver. There are 3 major differences between AST2500 and AST2600 vhub: - AST2600 supports 7 downstream ports while AST2500 supports 5. - AST2600 supports 21 generic endpoints while AST2500 supports 15. - EP0 data buffer's 8-byte DMA alignment restriction is removed from AST2600. Signed-off-by: Tao Ren Reviewed-by: Andrew Jeffery Acked-by: Benjamin Herrenschmidt Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/aspeed-vhub/Kconfig | 4 ++-- drivers/usb/gadget/udc/aspeed-vhub/core.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/usb') 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 f8ab8e012f34..f8d35dd60c34 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -423,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); -- cgit v1.2.3