From ebfb319bb601e501f77809a83b0b69b529c22a8d Mon Sep 17 00:00:00 2001 From: Konstantin Shkolnyy Date: Wed, 28 Oct 2015 16:02:03 -0500 Subject: USB: cp210x: flush device queues at close Flush all device queues at close in order to work around a cp2108 Tx queue bug. Occasionally, writing data and immediately closing the port makes cp2108 stop responding. The device has to be unplugged to clear the error. The failure is induced by shutting down the device while its Tx queue still has unsent data. This condition is avoided by issuing PURGE command from the close() callback. This change is applied to all cp210x devices. Clearing internal queues on close is generally good. Signed-off-by: Konstantin Shkolnyy [johan: amend commit message ] Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index eac7ccaa3c85..8ba1005f6a53 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -300,6 +300,14 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CONTROL_WRITE_DTR 0x0100 #define CONTROL_WRITE_RTS 0x0200 +/* + * CP210X_PURGE - 16 bits passed in wValue of USB request. + * SiLabs app note AN571 gives a strange description of the 4 bits: + * bit 0 or bit 2 clears the transmit queue and 1 or 3 receive. + * writing 1 to all, however, purges cp2108 well enough to avoid the hang. + */ +#define PURGE_ALL 0x000f + /* * cp210x_get_config * Reads from the CP210x configuration registers @@ -475,7 +483,14 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) static void cp210x_close(struct usb_serial_port *port) { + unsigned int purge_ctl; + usb_serial_generic_close(port); + + /* Clear both queues; cp2108 needs this to avoid an occasional hang */ + purge_ctl = PURGE_ALL; + cp210x_set_config(port, CP210X_PURGE, &purge_ctl, 2); + cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE); } -- cgit v1.2.3 From e2ae67a3b55188b0342522d8139acf013feb2a69 Mon Sep 17 00:00:00 2001 From: Konstantin Shkolnyy Date: Wed, 28 Oct 2015 16:02:34 -0500 Subject: USB: cp210x: relocate private data from USB interface to port This change is preparation for implementing a cp2108 bug workaround. The workaround requires storing some private data. Right now the data is attached to the USB interface and allocated in the attach() callback. The bug detection requires USB I/O which is done easier from port_probe() callback rather than attach(). Since the USB access functions take port as a parameter, and since the private data is used exclusively by these functions, it can be allocated in port_probe(). Also, all cp210x devices have exactly 1 port per USB iterface, so moving private data from the USB interface to port is trivial. Signed-off-by: Konstantin Shkolnyy Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 8ba1005f6a53..352fe63fb056 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -43,8 +43,8 @@ static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int); static int cp210x_tiocmset_port(struct usb_serial_port *port, unsigned int, unsigned int); static void cp210x_break_ctl(struct tty_struct *, int); -static int cp210x_startup(struct usb_serial *); -static void cp210x_release(struct usb_serial *); +static int cp210x_port_probe(struct usb_serial_port *); +static int cp210x_port_remove(struct usb_serial_port *); static void cp210x_dtr_rts(struct usb_serial_port *p, int on); static const struct usb_device_id id_table[] = { @@ -197,7 +197,7 @@ static const struct usb_device_id id_table[] = { MODULE_DEVICE_TABLE(usb, id_table); -struct cp210x_serial_private { +struct cp210x_port_private { __u8 bInterfaceNumber; }; @@ -216,8 +216,8 @@ static struct usb_serial_driver cp210x_device = { .set_termios = cp210x_set_termios, .tiocmget = cp210x_tiocmget, .tiocmset = cp210x_tiocmset, - .attach = cp210x_startup, - .release = cp210x_release, + .port_probe = cp210x_port_probe, + .port_remove = cp210x_port_remove, .dtr_rts = cp210x_dtr_rts }; @@ -319,7 +319,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request, unsigned int *data, int size) { struct usb_serial *serial = port->serial; - struct cp210x_serial_private *spriv = usb_get_serial_data(serial); + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); __le32 *buf; int result, i, length; @@ -333,7 +333,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request, /* Issue the request, attempting to read 'size' bytes */ result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), request, REQTYPE_INTERFACE_TO_HOST, 0x0000, - spriv->bInterfaceNumber, buf, size, + port_priv->bInterfaceNumber, buf, size, USB_CTRL_GET_TIMEOUT); /* Convert data into an array of integers */ @@ -364,7 +364,7 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request, unsigned int *data, int size) { struct usb_serial *serial = port->serial; - struct cp210x_serial_private *spriv = usb_get_serial_data(serial); + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); __le32 *buf; int result, i, length; @@ -383,13 +383,13 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request, result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), request, REQTYPE_HOST_TO_INTERFACE, 0x0000, - spriv->bInterfaceNumber, buf, size, + port_priv->bInterfaceNumber, buf, size, USB_CTRL_SET_TIMEOUT); } else { result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), request, REQTYPE_HOST_TO_INTERFACE, data[0], - spriv->bInterfaceNumber, NULL, 0, + port_priv->bInterfaceNumber, NULL, 0, USB_CTRL_SET_TIMEOUT); } @@ -878,29 +878,32 @@ static void cp210x_break_ctl(struct tty_struct *tty, int break_state) cp210x_set_config(port, CP210X_SET_BREAK, &state, 2); } -static int cp210x_startup(struct usb_serial *serial) +static int cp210x_port_probe(struct usb_serial_port *port) { + struct usb_serial *serial = port->serial; struct usb_host_interface *cur_altsetting; - struct cp210x_serial_private *spriv; + struct cp210x_port_private *port_priv; - spriv = kzalloc(sizeof(*spriv), GFP_KERNEL); - if (!spriv) + port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL); + if (!port_priv) return -ENOMEM; cur_altsetting = serial->interface->cur_altsetting; - spriv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber; + port_priv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber; - usb_set_serial_data(serial, spriv); + usb_set_serial_port_data(port, port_priv); return 0; } -static void cp210x_release(struct usb_serial *serial) +static int cp210x_port_remove(struct usb_serial_port *port) { - struct cp210x_serial_private *spriv; + struct cp210x_port_private *port_priv; + + port_priv = usb_get_serial_port_data(port); + kfree(port_priv); - spriv = usb_get_serial_data(serial); - kfree(spriv); + return 0; } module_usb_serial_driver(serial_drivers, id_table); -- cgit v1.2.3 From d0bf1ff0ae322aca59b00b9a2ad121d35a77e78f Mon Sep 17 00:00:00 2001 From: Konstantin Shkolnyy Date: Wed, 28 Oct 2015 16:02:54 -0500 Subject: USB: cp210x: work around cp2108 GET_LINE_CTL bug Add helper to access line-control register in order to work around a cp2108 GET_LINE_CTL bug. cp2108 GET_LINE_CTL returns the 16-bit value with the 2 bytes swapped. However, SET_LINE_CTL functions properly. When the driver tries to modify the register, it reads it, modifies some bits and writes back. Because the read bytes were swapped, this often results in an invalid value to be written. In turn, this causes cp2108 respond with a stall. The stall sometimes doesn't clear properly and cp2108 starts responding to following valid commands also with stalls, effectively failing. Signed-off-by: Konstantin Shkolnyy [johan: amend commit message, modify probe error handling ] Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 70 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 352fe63fb056..9e5d5b016633 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -199,6 +199,7 @@ MODULE_DEVICE_TABLE(usb, id_table); struct cp210x_port_private { __u8 bInterfaceNumber; + bool has_swapped_line_ctl; }; static struct usb_serial_driver cp210x_device = { @@ -418,6 +419,60 @@ static inline int cp210x_set_config_single(struct usb_serial_port *port, return cp210x_set_config(port, request, &data, 2); } +/* + * Detect CP2108 GET_LINE_CTL bug and activate workaround. + * Write a known good value 0x800, read it back. + * If it comes back swapped the bug is detected. + * Preserve the original register value. + */ +static int cp210x_detect_swapped_line_ctl(struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + unsigned int line_ctl_save; + unsigned int line_ctl_test; + int err; + + err = cp210x_get_config(port, CP210X_GET_LINE_CTL, &line_ctl_save, 2); + if (err) + return err; + + line_ctl_test = 0x800; + err = cp210x_set_config(port, CP210X_SET_LINE_CTL, &line_ctl_test, 2); + if (err) + return err; + + err = cp210x_get_config(port, CP210X_GET_LINE_CTL, &line_ctl_test, 2); + if (err) + return err; + + if (line_ctl_test == 8) { + port_priv->has_swapped_line_ctl = true; + line_ctl_save = swab16((u16)line_ctl_save); + } + + return cp210x_set_config(port, CP210X_SET_LINE_CTL, &line_ctl_save, 2); +} + +/* + * Must always be called instead of cp210x_get_config(CP210X_GET_LINE_CTL) + * to workaround cp2108 bug and get correct value. + */ +static int cp210x_get_line_ctl(struct usb_serial_port *port, unsigned int *ctl) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int err; + + err = cp210x_get_config(port, CP210X_GET_LINE_CTL, ctl, 2); + if (err) + return err; + + /* Workaround swapped bytes in 16-bit value from CP210X_GET_LINE_CTL */ + if (port_priv->has_swapped_line_ctl) + *ctl = swab16((u16)(*ctl)); + + return 0; +} + /* * cp210x_quantise_baudrate * Quantises the baud rate as per AN205 Table 1 @@ -535,7 +590,7 @@ static void cp210x_get_termios_port(struct usb_serial_port *port, cflag = *cflagp; - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); cflag &= ~CSIZE; switch (bits & BITS_DATA_MASK) { case BITS_DATA_5: @@ -703,7 +758,7 @@ static void cp210x_set_termios(struct tty_struct *tty, /* If the number of data bits is to be updated */ if ((cflag & CSIZE) != (old_cflag & CSIZE)) { - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); bits &= ~BITS_DATA_MASK; switch (cflag & CSIZE) { case CS5: @@ -737,7 +792,7 @@ static void cp210x_set_termios(struct tty_struct *tty, if ((cflag & (PARENB|PARODD|CMSPAR)) != (old_cflag & (PARENB|PARODD|CMSPAR))) { - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); bits &= ~BITS_PARITY_MASK; if (cflag & PARENB) { if (cflag & CMSPAR) { @@ -763,7 +818,7 @@ static void cp210x_set_termios(struct tty_struct *tty, } if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { - cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2); + cp210x_get_line_ctl(port, &bits); bits &= ~BITS_STOP_MASK; if (cflag & CSTOPB) { bits |= BITS_STOP_2; @@ -883,6 +938,7 @@ static int cp210x_port_probe(struct usb_serial_port *port) struct usb_serial *serial = port->serial; struct usb_host_interface *cur_altsetting; struct cp210x_port_private *port_priv; + int ret; port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL); if (!port_priv) @@ -893,6 +949,12 @@ static int cp210x_port_probe(struct usb_serial_port *port) usb_set_serial_port_data(port, port_priv); + ret = cp210x_detect_swapped_line_ctl(port); + if (ret) { + kfree(port_priv); + return ret; + } + return 0; } -- cgit v1.2.3 From 6f923a0134d9868a71db19cca1fae551a27b46f5 Mon Sep 17 00:00:00 2001 From: Konstantin Shkolnyy Date: Tue, 24 Nov 2015 16:28:41 -0600 Subject: USB: cp210x: add tx_empty() Added tx_empty callback needed for generic wait-until-sent support. Without this function, when the port is closed usbserial can't know that there are still data in the chip's transmit FIFO. The chip gets disabled and untransmitted data lost. When the actual byte count is reported by tx-empty the close can be delayed until all data are sent. Signed-off-by: Konstantin Shkolnyy [johan: modify tx_empty error handling ] Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 9e5d5b016633..084033a568c2 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -38,6 +38,7 @@ static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *, struct ktermios *); static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *, struct ktermios*); +static bool cp210x_tx_empty(struct usb_serial_port *port); static int cp210x_tiocmget(struct tty_struct *); static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int); static int cp210x_tiocmset_port(struct usb_serial_port *port, @@ -215,6 +216,7 @@ static struct usb_serial_driver cp210x_device = { .close = cp210x_close, .break_ctl = cp210x_break_ctl, .set_termios = cp210x_set_termios, + .tx_empty = cp210x_tx_empty, .tiocmget = cp210x_tiocmget, .tiocmset = cp210x_tiocmset, .port_probe = cp210x_port_probe, @@ -301,6 +303,17 @@ static struct usb_serial_driver * const serial_drivers[] = { #define CONTROL_WRITE_DTR 0x0100 #define CONTROL_WRITE_RTS 0x0200 +/* CP210X_GET_COMM_STATUS returns these 0x13 bytes */ +struct cp210x_comm_status { + __le32 ulErrors; + __le32 ulHoldReasons; + __le32 ulAmountInInQueue; + __le32 ulAmountInOutQueue; + u8 bEofReceived; + u8 bWaitForImmediate; + u8 bReserved; +} __packed; + /* * CP210X_PURGE - 16 bits passed in wValue of USB request. * SiLabs app note AN571 gives a strange description of the 4 bits: @@ -549,6 +562,51 @@ static void cp210x_close(struct usb_serial_port *port) cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE); } +/* + * Read how many bytes are waiting in the TX queue. + */ +static int cp210x_get_tx_queue_byte_count(struct usb_serial_port *port, + u32 *count) +{ + struct usb_serial *serial = port->serial; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + struct cp210x_comm_status *sts; + int result; + + sts = kmalloc(sizeof(*sts), GFP_KERNEL); + if (!sts) + return -ENOMEM; + + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + CP210X_GET_COMM_STATUS, REQTYPE_INTERFACE_TO_HOST, + 0, port_priv->bInterfaceNumber, sts, sizeof(*sts), + USB_CTRL_GET_TIMEOUT); + if (result == sizeof(*sts)) { + *count = le32_to_cpu(sts->ulAmountInOutQueue); + result = 0; + } else { + dev_err(&port->dev, "failed to get comm status: %d\n", result); + if (result >= 0) + result = -EPROTO; + } + + kfree(sts); + + return result; +} + +static bool cp210x_tx_empty(struct usb_serial_port *port) +{ + int err; + u32 count; + + err = cp210x_get_tx_queue_byte_count(port, &count); + if (err) + return true; + + return !count; +} + /* * cp210x_get_termios * Reads the baud rate, data bits, parity, stop bits and flow control mode -- cgit v1.2.3 From 0290cc9f044a4d24d9a64e81761ac84498dc9d73 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 20 Nov 2015 13:53:22 -0500 Subject: USB: limit usbfs snooping of URB contents The usbfs_snoop facility can be very useful for debugging problems involving usbfs. However, it always prints out the entire contents of every URB. When dealing with large quantities of data, this can be less than helpful. This patch ameliorates the situation by adding a module parameter to usbcore for controlling the maximum number of bytes to print when snooping an URB. This makes debugging much easier. For backward compatibility, the default value is set unreasonably high. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- Documentation/kernel-parameters.txt | 4 ++++ drivers/usb/core/devio.c | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 742f69d18fc8..e6b6e056cc11 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -3874,6 +3874,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted. usbcore.usbfs_snoop= [USB] Set to log all usbfs traffic (default 0 = off). + usbcore.usbfs_snoop_max= + [USB] Maximum number of bytes to snoop in each URB + (default = 65536). + usbcore.blinkenlights= [USB] Set to cycle leds on hubs (default 0 = off). diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 38ae877c46e3..3d41faf6e607 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -100,6 +100,11 @@ static bool usbfs_snoop; module_param(usbfs_snoop, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(usbfs_snoop, "true to log all usbfs traffic"); +static unsigned usbfs_snoop_max = 65536; +module_param(usbfs_snoop_max, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(usbfs_snoop_max, + "maximum number of bytes to print while snooping"); + #define snoop(dev, format, arg...) \ do { \ if (usbfs_snoop) \ @@ -392,6 +397,7 @@ static void snoop_urb(struct usb_device *udev, ep, t, d, length, timeout_or_status); } + data_len = min(data_len, usbfs_snoop_max); if (data && data_len > 0) { print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_NONE, 32, 1, data, data_len, 1); @@ -402,7 +408,8 @@ static void snoop_urb_data(struct urb *urb, unsigned len) { int i, size; - if (!usbfs_snoop) + len = min(len, usbfs_snoop_max); + if (!usbfs_snoop || len == 0) return; if (urb->num_sgs == 0) { -- cgit v1.2.3 From a016a816bb96088ce4cd0ec890e256e4a63dfb47 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 20 Nov 2015 13:53:35 -0500 Subject: USB: add usbfs snooping for REAP and DISCARD This patch improves the usbfs_snoop debugging facility by adding messages for a couple of significant events which, up to now, have not been logged. The events are reaping and discarding (i.e., cancelling) an URB. The debugging messages include the userspace address of the URB being reaped or discarded. The reaping messages have to be added in four places, in order to handle blocking and non-blocking reaps in both normal and 32-bit compatibility mode. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devio.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 3d41faf6e607..e9f0de3e06db 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1716,8 +1716,12 @@ static struct async *reap_as(struct usb_dev_state *ps) static int proc_reapurb(struct usb_dev_state *ps, void __user *arg) { struct async *as = reap_as(ps); + if (as) { - int retval = processcompl(as, (void __user * __user *)arg); + int retval; + + snoop(&ps->dev->dev, "reap %p\n", as->userurb); + retval = processcompl(as, (void __user * __user *)arg); free_async(as); return retval; } @@ -1733,6 +1737,7 @@ static int proc_reapurbnonblock(struct usb_dev_state *ps, void __user *arg) as = async_getcompleted(ps); if (as) { + snoop(&ps->dev->dev, "reap %p\n", as->userurb); retval = processcompl(as, (void __user * __user *)arg); free_async(as); } else { @@ -1859,8 +1864,12 @@ static int processcompl_compat(struct async *as, void __user * __user *arg) static int proc_reapurb_compat(struct usb_dev_state *ps, void __user *arg) { struct async *as = reap_as(ps); + if (as) { - int retval = processcompl_compat(as, (void __user * __user *)arg); + int retval; + + snoop(&ps->dev->dev, "reap %p\n", as->userurb); + retval = processcompl_compat(as, (void __user * __user *)arg); free_async(as); return retval; } @@ -1876,6 +1885,7 @@ static int proc_reapurbnonblock_compat(struct usb_dev_state *ps, void __user *ar as = async_getcompleted(ps); if (as) { + snoop(&ps->dev->dev, "reap %p\n", as->userurb); retval = processcompl_compat(as, (void __user * __user *)arg); free_async(as); } else { @@ -2280,7 +2290,7 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, #endif case USBDEVFS_DISCARDURB: - snoop(&dev->dev, "%s: DISCARDURB\n", __func__); + snoop(&dev->dev, "%s: DISCARDURB %p\n", __func__, p); ret = proc_unlinkurb(ps, p); break; -- cgit v1.2.3 From 8ee10d6292cab0e425f93dbfa1f0e805c449dfc2 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 20 Nov 2015 13:53:45 -0500 Subject: USB: EHCI: enhance "async" debugfs output This patch enhances the "async" debugfs file in ehci-hcd by printing out several additional fields in the hardware-accessible data structures. These fields are important for determining the hardware's view of the async schedule, in particular, the addresses of the current and next qTDs for each QH along with the start address of each qTD's data buffer. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-dbg.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index b26b96e25a13..b7d623f1523c 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -436,7 +436,8 @@ static void qh_lines ( scratch = hc32_to_cpup(ehci, &hw->hw_info1); hw_curr = (mark == '*') ? hc32_to_cpup(ehci, &hw->hw_current) : 0; temp = scnprintf (next, size, - "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", + "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)" + " [cur %08x next %08x buf[0] %08x]", qh, scratch & 0x007f, speed_char (scratch), (scratch >> 8) & 0x000f, @@ -444,7 +445,10 @@ static void qh_lines ( hc32_to_cpup(ehci, &hw->hw_token), mark, (cpu_to_hc32(ehci, QTD_TOGGLE) & hw->hw_token) ? "data1" : "data0", - (hc32_to_cpup(ehci, &hw->hw_alt_next) >> 1) & 0x0f); + (hc32_to_cpup(ehci, &hw->hw_alt_next) >> 1) & 0x0f, + hc32_to_cpup(ehci, &hw->hw_current), + hc32_to_cpup(ehci, &hw->hw_qtd_next), + hc32_to_cpup(ehci, &hw->hw_buf[0])); size -= temp; next += temp; @@ -464,7 +468,8 @@ static void qh_lines ( mark = '/'; } temp = snprintf (next, size, - "\n\t%p%c%s len=%d %08x urb %p", + "\n\t%p%c%s len=%d %08x urb %p" + " [td %08x buf[0] %08x]", td, mark, ({ char *tmp; switch ((scratch>>8)&0x03) { case 0: tmp = "out"; break; @@ -474,7 +479,9 @@ static void qh_lines ( } tmp;}), (scratch >> 16) & 0x7fff, scratch, - td->urb); + td->urb, + (u32) td->qtd_dma, + hc32_to_cpup(ehci, &td->hw_buf[0])); if (size < temp) temp = size; size -= temp; -- cgit v1.2.3 From fc0855f2747a0e21d86b7e63c50bf234fa766184 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 20 Nov 2015 13:53:58 -0500 Subject: USB: EHCI: warn on unexpectedly active QH This patch adds a new warning message to ehci-hcd. The warning is triggered whenever the driver finds that the hardware has set the Active bit in a QH at a time when the driver expects the QH to be completely idle. Such bugs have been observed by users in the past, and since they can lead to serious problems (such as inability to unlink an URB that never completes), it would be good to know about them when they occur. This won't fix these bugs; that's a bigger job for a later patch. But success isn't guaranteed, since this depends on aspects of the hardware which are not documented in the EHCI spec or for which the spec's recommendations are clearly unworkable. It therefore seems worthwhile to check for these bugs proactively. Signed-off-by: Alan Stern Reported-by: Michael Reutman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-q.c | 9 +++++++-- drivers/usb/host/ehci.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 54f5332f814d..aad0777240d3 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -132,10 +132,14 @@ qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh) * qtd is updated in qh_completions(). Update the QH * overlay here. */ - if (qh->hw->hw_token & ACTIVE_BIT(ehci)) + if (qh->hw->hw_token & ACTIVE_BIT(ehci)) { qh->hw->hw_qtd_next = qtd->hw_next; - else + if (qh->should_be_inactive) + ehci_warn(ehci, "qh %p should be inactive!\n", qh); + } else { qh_update(ehci, qh, qtd); + } + qh->should_be_inactive = 0; } /*-------------------------------------------------------------------------*/ @@ -438,6 +442,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) (hw->hw_token & ACTIVE_BIT(ehci))) { token = hc32_to_cpu(ehci, hw->hw_token); hw->hw_token &= ~ACTIVE_BIT(ehci); + qh->should_be_inactive = 1; /* An unlink may leave an incomplete * async transaction in the TT buffer. diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 46f62e41bcde..ec61aedb0067 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -439,6 +439,7 @@ struct ehci_qh { unsigned dequeue_during_giveback:1; unsigned exception:1; /* got a fault, or an unlink was requested */ + unsigned should_be_inactive:1; }; /*-------------------------------------------------------------------------*/ -- cgit v1.2.3 From 5e6389fda02d6a22800bacfb2850366bfa18291b Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Tue, 24 Nov 2015 13:09:46 +0200 Subject: xhci: use the correct define to indicate port status suspend change. use the variables defined for populating the port status and port chage bits retuend by GetPortStatus request intead of the hub class feature selectors. The defines for hub class feature selectors are used for other purposes, they work as port status and feature selectors are in the same order, and set the same bits, but it makes the code very hard to follow Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 0230965fb78c..0863d8a61f81 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -820,7 +820,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, xhci_hub_report_usb2_link_state(&status, raw_port_status); } if (bus_state->port_c_suspend & (1 << wIndex)) - status |= 1 << USB_PORT_FEAT_C_SUSPEND; + status |= USB_PORT_STAT_C_SUSPEND << 16; return status; } -- cgit v1.2.3 From 32479d4b929714b7891efea3783ac4d378e30c59 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 24 Nov 2015 13:09:47 +0200 Subject: usb: host: xhci: cleanup hcd private size This patch cleanups the hcd private size to suitable size. The previous code has "sizeof(struct xhci_hcd *)" in xhci_hc_driver as hcd_priv_size and sizeof(struct xhci_hcd) in xhci_plat_overrides or xhci_pci_overrides as extra_priv_size. However, the xhci driver uses a "sizeof(struct xhcd_hcd)" memory space in each hcd (main_hcd and shared_hcd) actually. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 1 - drivers/usb/host/xhci-plat.c | 1 - drivers/usb/host/xhci.c | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 17f6897acde2..b51ac631c240 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -53,7 +53,6 @@ static struct hc_driver __read_mostly xhci_pci_hc_driver; static int xhci_pci_setup(struct usb_hcd *hcd); static const struct xhci_driver_overrides xhci_pci_overrides __initconst = { - .extra_priv_size = sizeof(struct xhci_hcd), .reset = xhci_pci_setup, }; diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 05647e6753cd..4699c1e223d5 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -31,7 +31,6 @@ static int xhci_plat_setup(struct usb_hcd *hcd); static int xhci_plat_start(struct usb_hcd *hcd); static const struct xhci_driver_overrides xhci_plat_overrides __initconst = { - .extra_priv_size = sizeof(struct xhci_hcd), .reset = xhci_plat_setup, .start = xhci_plat_start, }; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index dfa44d3e8eee..ac25e9037e88 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -4952,7 +4952,7 @@ EXPORT_SYMBOL_GPL(xhci_gen_setup); static const struct hc_driver xhci_hc_driver = { .description = "xhci-hcd", .product_desc = "xHCI Host Controller", - .hcd_priv_size = sizeof(struct xhci_hcd *), + .hcd_priv_size = sizeof(struct xhci_hcd), /* * generic hardware linkage -- cgit v1.2.3 From 79a17ddf89173cf3749656e5d3f87d33c196492b Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 24 Nov 2015 13:09:48 +0200 Subject: usb: host: xhci: add a platform-private field This patch adds an xhci->priv field for private use by XHCI platform drivers. Until now none of the platform drivers has used this private space. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 0b9451250e33..8b4629650328 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1656,6 +1656,9 @@ struct xhci_hcd { u32 port_status_u0; /* Compliance Mode Timer Triggered every 2 seconds */ #define COMP_MODE_RCVRY_MSECS 2000 + + /* platform-specific data -- must come last */ + unsigned long priv[0] __aligned(sizeof(s64)); }; /* Platform specific overrides to generic XHCI hc_driver ops */ -- cgit v1.2.3 From 4efb2f69411456d35051e9047c15157c9a5ba217 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 24 Nov 2015 13:09:49 +0200 Subject: usb: host: xhci-plat: add struct xhci_plat_priv This patch adds struct xhci_plat_priv to simplify the code to match platform specific variables. For now, this patch adds a member "type" in the structure. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 73 +++++++++++++++++++++++++++++--------------- drivers/usb/host/xhci-plat.h | 37 ++++++++++++++++++++++ 2 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 drivers/usb/host/xhci-plat.h (limited to 'drivers') diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 4699c1e223d5..083e77bc80ae 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -22,6 +22,7 @@ #include #include "xhci.h" +#include "xhci-plat.h" #include "xhci-mvebu.h" #include "xhci-rcar.h" @@ -31,6 +32,7 @@ static int xhci_plat_setup(struct usb_hcd *hcd); static int xhci_plat_start(struct usb_hcd *hcd); static const struct xhci_driver_overrides xhci_plat_overrides __initconst = { + .extra_priv_size = sizeof(struct xhci_plat_priv), .reset = xhci_plat_setup, .start = xhci_plat_start, }; @@ -48,11 +50,9 @@ static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) /* called during probe() after chip reset completes */ static int xhci_plat_setup(struct usb_hcd *hcd) { - struct device_node *of_node = hcd->self.controller->of_node; int ret; - if (of_device_is_compatible(of_node, "renesas,xhci-r8a7790") || - of_device_is_compatible(of_node, "renesas,xhci-r8a7791")) { + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2)) { ret = xhci_rcar_init_quirk(hcd); if (ret) return ret; @@ -63,19 +63,49 @@ static int xhci_plat_setup(struct usb_hcd *hcd) static int xhci_plat_start(struct usb_hcd *hcd) { - struct device_node *of_node = hcd->self.controller->of_node; - - if (of_device_is_compatible(of_node, "renesas,xhci-r8a7790") || - of_device_is_compatible(of_node, "renesas,xhci-r8a7791")) + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2)) xhci_rcar_start(hcd); return xhci_run(hcd); } +#ifdef CONFIG_OF +static const struct xhci_plat_priv xhci_plat_marvell_armada = { + .type = XHCI_PLAT_TYPE_MARVELL_ARMADA, +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { + .type = XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2, +}; + +static const struct of_device_id usb_xhci_of_match[] = { + { + .compatible = "generic-xhci", + }, { + .compatible = "xhci-platform", + }, { + .compatible = "marvell,armada-375-xhci", + .data = &xhci_plat_marvell_armada, + }, { + .compatible = "marvell,armada-380-xhci", + .data = &xhci_plat_marvell_armada, + }, { + .compatible = "renesas,xhci-r8a7790", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7791", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + }, +}; +MODULE_DEVICE_TABLE(of, usb_xhci_of_match); +#endif + static int xhci_plat_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct usb_xhci_pdata *pdata = dev_get_platdata(&pdev->dev); + const struct of_device_id *match; const struct hc_driver *driver; struct xhci_hcd *xhci; struct resource *res; @@ -133,10 +163,17 @@ static int xhci_plat_probe(struct platform_device *pdev) goto put_hcd; } - if (of_device_is_compatible(pdev->dev.of_node, - "marvell,armada-375-xhci") || - of_device_is_compatible(pdev->dev.of_node, - "marvell,armada-380-xhci")) { + xhci = hcd_to_xhci(hcd); + match = of_match_node(usb_xhci_of_match, node); + if (match) { + const struct xhci_plat_priv *priv_match = match->data; + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + /* Just copy data for now */ + *priv = *priv_match; + } + + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_MARVELL_ARMADA)) { ret = xhci_mvebu_mbus_init_quirk(pdev); if (ret) goto disable_clk; @@ -144,7 +181,6 @@ static int xhci_plat_probe(struct platform_device *pdev) device_wakeup_enable(hcd->self.controller); - xhci = hcd_to_xhci(hcd); xhci->clk = clk; xhci->main_hcd = hcd; xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev, @@ -255,19 +291,6 @@ static const struct dev_pm_ops xhci_plat_pm_ops = { #define DEV_PM_OPS NULL #endif /* CONFIG_PM */ -#ifdef CONFIG_OF -static const struct of_device_id usb_xhci_of_match[] = { - { .compatible = "generic-xhci" }, - { .compatible = "xhci-platform" }, - { .compatible = "marvell,armada-375-xhci"}, - { .compatible = "marvell,armada-380-xhci"}, - { .compatible = "renesas,xhci-r8a7790"}, - { .compatible = "renesas,xhci-r8a7791"}, - { }, -}; -MODULE_DEVICE_TABLE(of, usb_xhci_of_match); -#endif - static const struct acpi_device_id usb_xhci_acpi_match[] = { /* XHCI-compliant USB Controller */ { "PNP0D10", }, diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h new file mode 100644 index 000000000000..4479869bd006 --- /dev/null +++ b/drivers/usb/host/xhci-plat.h @@ -0,0 +1,37 @@ +/* + * xhci-plat.h - xHCI host controller driver platform Bus Glue. + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef _XHCI_PLAT_H +#define _XHCI_PLAT_H + +#include "xhci.h" /* for hcd_to_xhci() */ + +enum xhci_plat_type { + XHCI_PLAT_TYPE_MARVELL_ARMADA, + XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2, +}; + +struct xhci_plat_priv { + enum xhci_plat_type type; +}; + +#define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) + +static inline bool xhci_plat_type_is(struct usb_hcd *hcd, + enum xhci_plat_type type) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (priv && priv->type == type) + return true; + else + return false; +} +#endif /* _XHCI_PLAT_H */ -- cgit v1.2.3 From e93272fe3c1b7a03865e567f64403c6b578486cf Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 24 Nov 2015 13:09:50 +0200 Subject: usb: host: xhci-plat: add firmware_name in xhci_plat_priv This patch adds a member "firmware_name" in struct xhci_plat_priv to simplify the code to match specific firmware name. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.h | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index 4479869bd006..20b32a749398 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -20,6 +20,7 @@ enum xhci_plat_type { struct xhci_plat_priv { enum xhci_plat_type type; + const char *firmware_name; }; #define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) -- cgit v1.2.3 From 9bf9d9d6008c7541a5c0bcfe61a0e0504c6f2bb9 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 24 Nov 2015 13:09:51 +0200 Subject: usb: host: xhci-rcar: Change code for new SoCs This patch changes code to ease the addition of next generation SoCs. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 1 + drivers/usb/host/xhci-rcar.c | 37 +++++++++++++++++++++++-------------- drivers/usb/host/xhci-rcar.h | 2 ++ 3 files changed, 26 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 083e77bc80ae..a471f12ffa46 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -76,6 +76,7 @@ static const struct xhci_plat_priv xhci_plat_marvell_armada = { static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { .type = XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2, + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V1, }; static const struct of_device_id usb_xhci_of_match[] = { diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index ff0d1b44ea58..010a24fdaa59 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -14,10 +14,10 @@ #include #include "xhci.h" +#include "xhci-plat.h" #include "xhci-rcar.h" -#define FIRMWARE_NAME "r8a779x_usb3_v1.dlmem" -MODULE_FIRMWARE(FIRMWARE_NAME); +MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1); /*** Register Offset ***/ #define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ @@ -56,6 +56,19 @@ MODULE_FIRMWARE(FIRMWARE_NAME); #define RCAR_USB3_RX_POL_VAL BIT(21) #define RCAR_USB3_TX_POL_VAL BIT(4) +static void xhci_rcar_start_gen2(struct usb_hcd *hcd) +{ + /* LCLK Select */ + writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); + /* USB3.0 Configuration */ + writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); + writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); + writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); + /* USB3.0 Polarity */ + writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); + writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); +} + void xhci_rcar_start(struct usb_hcd *hcd) { u32 temp; @@ -65,27 +78,23 @@ void xhci_rcar_start(struct usb_hcd *hcd) temp = readl(hcd->regs + RCAR_USB3_INT_ENA); temp |= RCAR_USB3_INT_ENA_VAL; writel(temp, hcd->regs + RCAR_USB3_INT_ENA); - /* LCLK Select */ - writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); - /* USB3.0 Configuration */ - writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); - writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); - writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); - /* USB3.0 Polarity */ - writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); - writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2)) + xhci_rcar_start_gen2(hcd); } } -static int xhci_rcar_download_firmware(struct device *dev, void __iomem *regs) +static int xhci_rcar_download_firmware(struct usb_hcd *hcd) { + struct device *dev = hcd->self.controller; + void __iomem *regs = hcd->regs; + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); const struct firmware *fw; int retval, index, j, time; int timeout = 10000; u32 data, val, temp; /* request R-Car USB3.0 firmware */ - retval = request_firmware(&fw, FIRMWARE_NAME, dev); + retval = request_firmware(&fw, priv->firmware_name, dev); if (retval) return retval; @@ -144,5 +153,5 @@ int xhci_rcar_init_quirk(struct usb_hcd *hcd) if (!hcd->regs) return 0; - return xhci_rcar_download_firmware(hcd->self.controller, hcd->regs); + return xhci_rcar_download_firmware(hcd); } diff --git a/drivers/usb/host/xhci-rcar.h b/drivers/usb/host/xhci-rcar.h index 58501256715d..ba3ad31e702a 100644 --- a/drivers/usb/host/xhci-rcar.h +++ b/drivers/usb/host/xhci-rcar.h @@ -11,6 +11,8 @@ #ifndef _XHCI_RCAR_H #define _XHCI_RCAR_H +#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" + #if IS_ENABLED(CONFIG_USB_XHCI_RCAR) void xhci_rcar_start(struct usb_hcd *hcd); int xhci_rcar_init_quirk(struct usb_hcd *hcd); -- cgit v1.2.3 From 82487b714858d99feff600a096177675481a4935 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 24 Nov 2015 13:09:52 +0200 Subject: usb: host: xhci-plat: add support for the R-Car M2-N xHCI controller This patch adds support for R-Car M2-N (r8a7793) xHCI controller. This SoC is compatible with R-Car H2 (r8a7790) and R-Car M2-W (r8a7791). Signed-off-by: Yoshihiro Shimoda Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-xhci.txt | 4 ++-- drivers/usb/host/xhci-plat.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index 86f67f0886bc..106299ad3807 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -3,8 +3,8 @@ USB xHCI controllers Required properties: - compatible: should be one of "generic-xhci", "marvell,armada-375-xhci", "marvell,armada-380-xhci", - "renesas,xhci-r8a7790", "renesas,xhci-r8a7791" (deprecated: - "xhci-platform"). + "renesas,xhci-r8a7790", "renesas,xhci-r8a7791", "renesas,xhci-r8a7793" + (deprecated: "xhci-platform"). - reg: should contain address and length of the standard XHCI register set for the device. - interrupts: one XHCI interrupt should be described here. diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index a471f12ffa46..0113e79c74cb 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -96,6 +96,9 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "renesas,xhci-r8a7791", .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7793", + .data = &xhci_plat_renesas_rcar_gen2, }, { }, }; -- cgit v1.2.3 From 526a240f6145fa54658a4d56327f3e053ac73c48 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 24 Nov 2015 13:09:53 +0200 Subject: usb: host: xhci-plat: add support for the R-Car H3 xHCI controllers The R-Car H3 has two xHCI controllers. This SoC is compatible with R-Car Gen2 SoCs, however this SoC doesn't need some specific registers setting, and need a new firmware. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-xhci.txt | 4 ++-- drivers/usb/host/xhci-plat.c | 14 ++++++++++++-- drivers/usb/host/xhci-plat.h | 1 + drivers/usb/host/xhci-rcar.c | 7 +++++++ drivers/usb/host/xhci-rcar.h | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index 106299ad3807..082573289f1e 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -3,8 +3,8 @@ USB xHCI controllers Required properties: - compatible: should be one of "generic-xhci", "marvell,armada-375-xhci", "marvell,armada-380-xhci", - "renesas,xhci-r8a7790", "renesas,xhci-r8a7791", "renesas,xhci-r8a7793" - (deprecated: "xhci-platform"). + "renesas,xhci-r8a7790", "renesas,xhci-r8a7791", "renesas,xhci-r8a7793", + "renesas,xhci-r8a7795" (deprecated: "xhci-platform"). - reg: should contain address and length of the standard XHCI register set for the device. - interrupts: one XHCI interrupt should be described here. diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 0113e79c74cb..770b6b088797 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -52,7 +52,8 @@ static int xhci_plat_setup(struct usb_hcd *hcd) { int ret; - if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2)) { + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2) || + xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3)) { ret = xhci_rcar_init_quirk(hcd); if (ret) return ret; @@ -63,7 +64,8 @@ static int xhci_plat_setup(struct usb_hcd *hcd) static int xhci_plat_start(struct usb_hcd *hcd) { - if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2)) + if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2) || + xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3)) xhci_rcar_start(hcd); return xhci_run(hcd); @@ -79,6 +81,11 @@ static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V1, }; +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { + .type = XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3, + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V2, +}; + static const struct of_device_id usb_xhci_of_match[] = { { .compatible = "generic-xhci", @@ -99,6 +106,9 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "renesas,xhci-r8a7793", .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7795", + .data = &xhci_plat_renesas_rcar_gen3, }, { }, }; diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index 20b32a749398..5a2e2e3936c4 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -16,6 +16,7 @@ enum xhci_plat_type { XHCI_PLAT_TYPE_MARVELL_ARMADA, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2, + XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3, }; struct xhci_plat_priv { diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index 010a24fdaa59..623100e9385e 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -17,7 +17,14 @@ #include "xhci-plat.h" #include "xhci-rcar.h" +/* +* - The V2 firmware is possible to use on R-Car Gen2. However, the V2 causes +* performance degradation. So, this driver continues to use the V1 if R-Car +* Gen2. +* - The V1 firmware is impossible to use on R-Car Gen3. +*/ MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1); +MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V2); /*** Register Offset ***/ #define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ diff --git a/drivers/usb/host/xhci-rcar.h b/drivers/usb/host/xhci-rcar.h index ba3ad31e702a..2941a25cfe98 100644 --- a/drivers/usb/host/xhci-rcar.h +++ b/drivers/usb/host/xhci-rcar.h @@ -12,6 +12,7 @@ #define _XHCI_RCAR_H #define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" +#define XHCI_RCAR_FIRMWARE_NAME_V2 "r8a779x_usb3_v2.dlmem" #if IS_ENABLED(CONFIG_USB_XHCI_RCAR) void xhci_rcar_start(struct usb_hcd *hcd); -- cgit v1.2.3 From 0cbd4b34cda9dfd36b6c26b692dee181e0100b67 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 24 Nov 2015 13:09:55 +0200 Subject: xhci: mediatek: support MTK xHCI host controller There some vendor quirks for MTK xhci host controller: 1. It defines some extra SW scheduling parameters for HW to minimize the scheduling effort for synchronous and interrupt endpoints. The parameters are put into reseved DWs of slot context and endpoint context. 2. Its IMODI unit for Interrupter Moderation register is 8 times as much as that defined in xHCI spec. 3. Its TDS in Normal TRB defines a number of packets that remains to be transferred for a TD after processing all Max packets in all previous TRBs. Signed-off-by: Chunfeng Yun Tested-by: Daniel Thompson Reviewed-by: Daniel Thompson Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 9 + drivers/usb/host/Makefile | 4 + drivers/usb/host/xhci-mtk-sch.c | 415 ++++++++++++++++++++++ drivers/usb/host/xhci-mtk.c | 763 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-mtk.h | 162 +++++++++ drivers/usb/host/xhci-ring.c | 16 +- drivers/usb/host/xhci.c | 19 +- drivers/usb/host/xhci.h | 1 + 8 files changed, 1383 insertions(+), 6 deletions(-) create mode 100644 drivers/usb/host/xhci-mtk-sch.c create mode 100644 drivers/usb/host/xhci-mtk.c create mode 100644 drivers/usb/host/xhci-mtk.h (limited to 'drivers') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 3bb08870148f..daa563ff1fa0 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -41,6 +41,15 @@ config USB_XHCI_PLATFORM If unsure, say N. +config USB_XHCI_MTK + tristate "xHCI support for Mediatek MT65xx" + select MFD_SYSCON + depends on ARCH_MEDIATEK || COMPILE_TEST + ---help--- + Say 'Y' to enable the support for the xHCI host controller + found in Mediatek MT65xx SoCs. + If unsure, say N. + config USB_XHCI_MVEBU tristate "xHCI support for Marvell Armada 375/38x" select USB_XHCI_PLATFORM diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index e7558abc994d..65a06b4382bf 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -13,6 +13,9 @@ fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o xhci-hcd-y := xhci.o xhci-mem.o xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o xhci-hcd-y += xhci-trace.o +ifneq ($(CONFIG_USB_XHCI_MTK), ) + xhci-hcd-y += xhci-mtk-sch.o +endif xhci-plat-hcd-y := xhci-plat.o ifneq ($(CONFIG_USB_XHCI_MVEBU), ) @@ -64,6 +67,7 @@ obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o +obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c new file mode 100644 index 000000000000..c30de7c39f44 --- /dev/null +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Zhigang.Wei + * Chunfeng.Yun + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include "xhci.h" +#include "xhci-mtk.h" + +#define SS_BW_BOUNDARY 51000 +/* table 5-5. High-speed Isoc Transaction Limits in usb_20 spec */ +#define HS_BW_BOUNDARY 6144 +/* usb2 spec section11.18.1: at most 188 FS bytes per microframe */ +#define FS_PAYLOAD_MAX 188 + +/* mtk scheduler bitmasks */ +#define EP_BPKTS(p) ((p) & 0x3f) +#define EP_BCSCOUNT(p) (((p) & 0x7) << 8) +#define EP_BBM(p) ((p) << 11) +#define EP_BOFFSET(p) ((p) & 0x3fff) +#define EP_BREPEAT(p) (((p) & 0x7fff) << 16) + +static int is_fs_or_ls(enum usb_device_speed speed) +{ + return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW; +} + +/* +* get the index of bandwidth domains array which @ep belongs to. +* +* the bandwidth domain array is saved to @sch_array of struct xhci_hcd_mtk, +* each HS root port is treated as a single bandwidth domain, +* but each SS root port is treated as two bandwidth domains, one for IN eps, +* one for OUT eps. +* @real_port value is defined as follow according to xHCI spec: +* 1 for SSport0, ..., N+1 for SSportN, N+2 for HSport0, N+3 for HSport1, etc +* so the bandwidth domain array is organized as follow for simplification: +* SSport0-OUT, SSport0-IN, ..., SSportX-OUT, SSportX-IN, HSport0, ..., HSportY +*/ +static int get_bw_index(struct xhci_hcd *xhci, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_virt_device *virt_dev; + int bw_index; + + virt_dev = xhci->devs[udev->slot_id]; + + if (udev->speed == USB_SPEED_SUPER) { + if (usb_endpoint_dir_out(&ep->desc)) + bw_index = (virt_dev->real_port - 1) * 2; + else + bw_index = (virt_dev->real_port - 1) * 2 + 1; + } else { + /* add one more for each SS port */ + bw_index = virt_dev->real_port + xhci->num_usb3_ports - 1; + } + + return bw_index; +} + +static void setup_sch_info(struct usb_device *udev, + struct xhci_ep_ctx *ep_ctx, struct mu3h_sch_ep_info *sch_ep) +{ + u32 ep_type; + u32 ep_interval; + u32 max_packet_size; + u32 max_burst; + u32 mult; + u32 esit_pkts; + + ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2)); + ep_interval = CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info)); + max_packet_size = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); + max_burst = CTX_TO_MAX_BURST(le32_to_cpu(ep_ctx->ep_info2)); + mult = CTX_TO_EP_MULT(le32_to_cpu(ep_ctx->ep_info)); + + sch_ep->esit = 1 << ep_interval; + sch_ep->offset = 0; + sch_ep->burst_mode = 0; + + if (udev->speed == USB_SPEED_HIGH) { + sch_ep->cs_count = 0; + + /* + * usb_20 spec section5.9 + * a single microframe is enough for HS synchromous endpoints + * in a interval + */ + sch_ep->num_budget_microframes = 1; + sch_ep->repeat = 0; + + /* + * xHCI spec section6.2.3.4 + * @max_burst is the number of additional transactions + * opportunities per microframe + */ + sch_ep->pkts = max_burst + 1; + sch_ep->bw_cost_per_microframe = max_packet_size * sch_ep->pkts; + } else if (udev->speed == USB_SPEED_SUPER) { + /* usb3_r1 spec section4.4.7 & 4.4.8 */ + sch_ep->cs_count = 0; + esit_pkts = (mult + 1) * (max_burst + 1); + if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { + sch_ep->pkts = esit_pkts; + sch_ep->num_budget_microframes = 1; + sch_ep->repeat = 0; + } + + if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) { + if (esit_pkts <= sch_ep->esit) + sch_ep->pkts = 1; + else + sch_ep->pkts = roundup_pow_of_two(esit_pkts) + / sch_ep->esit; + + sch_ep->num_budget_microframes = + DIV_ROUND_UP(esit_pkts, sch_ep->pkts); + + if (sch_ep->num_budget_microframes > 1) + sch_ep->repeat = 1; + else + sch_ep->repeat = 0; + } + sch_ep->bw_cost_per_microframe = max_packet_size * sch_ep->pkts; + } else if (is_fs_or_ls(udev->speed)) { + + /* + * usb_20 spec section11.18.4 + * assume worst cases + */ + sch_ep->repeat = 0; + sch_ep->pkts = 1; /* at most one packet for each microframe */ + if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { + sch_ep->cs_count = 3; /* at most need 3 CS*/ + /* one for SS and one for budgeted transaction */ + sch_ep->num_budget_microframes = sch_ep->cs_count + 2; + sch_ep->bw_cost_per_microframe = max_packet_size; + } + if (ep_type == ISOC_OUT_EP) { + + /* + * the best case FS budget assumes that 188 FS bytes + * occur in each microframe + */ + sch_ep->num_budget_microframes = DIV_ROUND_UP( + max_packet_size, FS_PAYLOAD_MAX); + sch_ep->bw_cost_per_microframe = FS_PAYLOAD_MAX; + sch_ep->cs_count = sch_ep->num_budget_microframes; + } + if (ep_type == ISOC_IN_EP) { + /* at most need additional two CS. */ + sch_ep->cs_count = DIV_ROUND_UP( + max_packet_size, FS_PAYLOAD_MAX) + 2; + sch_ep->num_budget_microframes = sch_ep->cs_count + 2; + sch_ep->bw_cost_per_microframe = FS_PAYLOAD_MAX; + } + } +} + +/* Get maximum bandwidth when we schedule at offset slot. */ +static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep, u32 offset) +{ + u32 num_esit; + u32 max_bw = 0; + int i; + int j; + + num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; + for (i = 0; i < num_esit; i++) { + u32 base = offset + i * sch_ep->esit; + + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + if (sch_bw->bus_bw[base + j] > max_bw) + max_bw = sch_bw->bus_bw[base + j]; + } + } + return max_bw; +} + +static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep, int bw_cost) +{ + u32 num_esit; + u32 base; + int i; + int j; + + num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; + for (i = 0; i < num_esit; i++) { + base = sch_ep->offset + i * sch_ep->esit; + for (j = 0; j < sch_ep->num_budget_microframes; j++) + sch_bw->bus_bw[base + j] += bw_cost; + } +} + +static int check_sch_bw(struct usb_device *udev, + struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep) +{ + u32 offset; + u32 esit; + u32 num_budget_microframes; + u32 min_bw; + u32 min_index; + u32 worst_bw; + u32 bw_boundary; + + if (sch_ep->esit > XHCI_MTK_MAX_ESIT) + sch_ep->esit = XHCI_MTK_MAX_ESIT; + + esit = sch_ep->esit; + num_budget_microframes = sch_ep->num_budget_microframes; + + /* + * Search through all possible schedule microframes. + * and find a microframe where its worst bandwidth is minimum. + */ + min_bw = ~0; + min_index = 0; + for (offset = 0; offset < esit; offset++) { + if ((offset + num_budget_microframes) > sch_ep->esit) + break; + + /* + * usb_20 spec section11.18: + * must never schedule Start-Split in Y6 + */ + if (is_fs_or_ls(udev->speed) && (offset % 8 == 6)) + continue; + + worst_bw = get_max_bw(sch_bw, sch_ep, offset); + if (min_bw > worst_bw) { + min_bw = worst_bw; + min_index = offset; + } + if (min_bw == 0) + break; + } + sch_ep->offset = min_index; + + bw_boundary = (udev->speed == USB_SPEED_SUPER) + ? SS_BW_BOUNDARY : HS_BW_BOUNDARY; + + /* check bandwidth */ + if (min_bw + sch_ep->bw_cost_per_microframe > bw_boundary) + return -ERANGE; + + /* update bus bandwidth info */ + update_bus_bw(sch_bw, sch_ep, sch_ep->bw_cost_per_microframe); + + return 0; +} + +static bool need_bw_sch(struct usb_host_endpoint *ep, + enum usb_device_speed speed, int has_tt) +{ + /* only for periodic endpoints */ + if (usb_endpoint_xfer_control(&ep->desc) + || usb_endpoint_xfer_bulk(&ep->desc)) + return false; + + /* + * for LS & FS periodic endpoints which its device don't attach + * to TT are also ignored, root-hub will schedule them directly + */ + if (is_fs_or_ls(speed) && !has_tt) + return false; + + return true; +} + +int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) +{ + struct mu3h_sch_bw_info *sch_array; + int num_usb_bus; + int i; + + /* ss IN and OUT are separated */ + num_usb_bus = mtk->num_u3_ports * 2 + mtk->num_u2_ports; + + sch_array = kcalloc(num_usb_bus, sizeof(*sch_array), GFP_KERNEL); + if (sch_array == NULL) + return -ENOMEM; + + for (i = 0; i < num_usb_bus; i++) + INIT_LIST_HEAD(&sch_array[i].bw_ep_list); + + mtk->sch_array = sch_array; + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_mtk_sch_init); + +void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk) +{ + kfree(mtk->sch_array); +} +EXPORT_SYMBOL_GPL(xhci_mtk_sch_exit); + +int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci; + struct xhci_ep_ctx *ep_ctx; + struct xhci_slot_ctx *slot_ctx; + struct xhci_virt_device *virt_dev; + struct mu3h_sch_bw_info *sch_bw; + struct mu3h_sch_ep_info *sch_ep; + struct mu3h_sch_bw_info *sch_array; + unsigned int ep_index; + int bw_index; + int ret = 0; + + xhci = hcd_to_xhci(hcd); + virt_dev = xhci->devs[udev->slot_id]; + ep_index = xhci_get_endpoint_index(&ep->desc); + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + sch_array = mtk->sch_array; + + xhci_dbg(xhci, "%s() type:%d, speed:%d, mpkt:%d, dir:%d, ep:%p\n", + __func__, usb_endpoint_type(&ep->desc), udev->speed, + GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc)), + usb_endpoint_dir_in(&ep->desc), ep); + + if (!need_bw_sch(ep, udev->speed, slot_ctx->tt_info & TT_SLOT)) + return 0; + + bw_index = get_bw_index(xhci, udev, ep); + sch_bw = &sch_array[bw_index]; + + sch_ep = kzalloc(sizeof(struct mu3h_sch_ep_info), GFP_NOIO); + if (!sch_ep) + return -ENOMEM; + + setup_sch_info(udev, ep_ctx, sch_ep); + + ret = check_sch_bw(udev, sch_bw, sch_ep); + if (ret) { + xhci_err(xhci, "Not enough bandwidth!\n"); + kfree(sch_ep); + return -ENOSPC; + } + + list_add_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list); + sch_ep->ep = ep; + + ep_ctx->reserved[0] |= cpu_to_le32(EP_BPKTS(sch_ep->pkts) + | EP_BCSCOUNT(sch_ep->cs_count) | EP_BBM(sch_ep->burst_mode)); + ep_ctx->reserved[1] |= cpu_to_le32(EP_BOFFSET(sch_ep->offset) + | EP_BREPEAT(sch_ep->repeat)); + + xhci_dbg(xhci, " PKTS:%x, CSCOUNT:%x, BM:%x, OFFSET:%x, REPEAT:%x\n", + sch_ep->pkts, sch_ep->cs_count, sch_ep->burst_mode, + sch_ep->offset, sch_ep->repeat); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_mtk_add_ep_quirk); + +void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci; + struct xhci_slot_ctx *slot_ctx; + struct xhci_virt_device *virt_dev; + struct mu3h_sch_bw_info *sch_array; + struct mu3h_sch_bw_info *sch_bw; + struct mu3h_sch_ep_info *sch_ep; + int bw_index; + + xhci = hcd_to_xhci(hcd); + virt_dev = xhci->devs[udev->slot_id]; + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + sch_array = mtk->sch_array; + + xhci_dbg(xhci, "%s() type:%d, speed:%d, mpks:%d, dir:%d, ep:%p\n", + __func__, usb_endpoint_type(&ep->desc), udev->speed, + GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc)), + usb_endpoint_dir_in(&ep->desc), ep); + + if (!need_bw_sch(ep, udev->speed, slot_ctx->tt_info & TT_SLOT)) + return; + + bw_index = get_bw_index(xhci, udev, ep); + sch_bw = &sch_array[bw_index]; + + list_for_each_entry(sch_ep, &sch_bw->bw_ep_list, endpoint) { + if (sch_ep->ep == ep) { + update_bus_bw(sch_bw, sch_ep, + -sch_ep->bw_cost_per_microframe); + list_del(&sch_ep->endpoint); + kfree(sch_ep); + break; + } + } +} +EXPORT_SYMBOL_GPL(xhci_mtk_drop_ep_quirk); diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c new file mode 100644 index 000000000000..c9ab6a44c34a --- /dev/null +++ b/drivers/usb/host/xhci-mtk.c @@ -0,0 +1,763 @@ +/* + * MediaTek xHCI Host Controller Driver + * + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Chunfeng Yun + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-mtk.h" + +/* ip_pw_ctrl0 register */ +#define CTRL0_IP_SW_RST BIT(0) + +/* ip_pw_ctrl1 register */ +#define CTRL1_IP_HOST_PDN BIT(0) + +/* ip_pw_ctrl2 register */ +#define CTRL2_IP_DEV_PDN BIT(0) + +/* ip_pw_sts1 register */ +#define STS1_IP_SLEEP_STS BIT(30) +#define STS1_XHCI_RST BIT(11) +#define STS1_SYS125_RST BIT(10) +#define STS1_REF_RST BIT(8) +#define STS1_SYSPLL_STABLE BIT(0) + +/* ip_xhci_cap register */ +#define CAP_U3_PORT_NUM(p) ((p) & 0xff) +#define CAP_U2_PORT_NUM(p) (((p) >> 8) & 0xff) + +/* u3_ctrl_p register */ +#define CTRL_U3_PORT_HOST_SEL BIT(2) +#define CTRL_U3_PORT_PDN BIT(1) +#define CTRL_U3_PORT_DIS BIT(0) + +/* u2_ctrl_p register */ +#define CTRL_U2_PORT_HOST_SEL BIT(2) +#define CTRL_U2_PORT_PDN BIT(1) +#define CTRL_U2_PORT_DIS BIT(0) + +/* u2_phy_pll register */ +#define CTRL_U2_FORCE_PLL_STB BIT(28) + +#define PERI_WK_CTRL0 0x400 +#define UWK_CTR0_0P_LS_PE BIT(8) /* posedge */ +#define UWK_CTR0_0P_LS_NE BIT(7) /* negedge for 0p linestate*/ +#define UWK_CTL1_1P_LS_C(x) (((x) & 0xf) << 1) +#define UWK_CTL1_1P_LS_E BIT(0) + +#define PERI_WK_CTRL1 0x404 +#define UWK_CTL1_IS_C(x) (((x) & 0xf) << 26) +#define UWK_CTL1_IS_E BIT(25) +#define UWK_CTL1_0P_LS_C(x) (((x) & 0xf) << 21) +#define UWK_CTL1_0P_LS_E BIT(20) +#define UWK_CTL1_IDDIG_C(x) (((x) & 0xf) << 11) /* cycle debounce */ +#define UWK_CTL1_IDDIG_E BIT(10) /* enable debounce */ +#define UWK_CTL1_IDDIG_P BIT(9) /* polarity */ +#define UWK_CTL1_0P_LS_P BIT(7) +#define UWK_CTL1_IS_P BIT(6) /* polarity for ip sleep */ + +enum ssusb_wakeup_src { + SSUSB_WK_IP_SLEEP = 1, + SSUSB_WK_LINE_STATE = 2, +}; + +static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value, check_val; + int ret; + int i; + + /* power on host ip */ + value = readl(&ippc->ip_pw_ctr1); + value &= ~CTRL1_IP_HOST_PDN; + writel(value, &ippc->ip_pw_ctr1); + + /* power on and enable all u3 ports */ + for (i = 0; i < mtk->num_u3_ports; i++) { + value = readl(&ippc->u3_ctrl_p[i]); + value &= ~(CTRL_U3_PORT_PDN | CTRL_U3_PORT_DIS); + value |= CTRL_U3_PORT_HOST_SEL; + writel(value, &ippc->u3_ctrl_p[i]); + } + + /* power on and enable all u2 ports */ + for (i = 0; i < mtk->num_u2_ports; i++) { + value = readl(&ippc->u2_ctrl_p[i]); + value &= ~(CTRL_U2_PORT_PDN | CTRL_U2_PORT_DIS); + value |= CTRL_U2_PORT_HOST_SEL; + writel(value, &ippc->u2_ctrl_p[i]); + } + + /* + * wait for clocks to be stable, and clock domains reset to + * be inactive after power on and enable ports + */ + check_val = STS1_SYSPLL_STABLE | STS1_REF_RST | + STS1_SYS125_RST | STS1_XHCI_RST; + + ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, + (check_val == (value & check_val)), 100, 20000); + if (ret) { + dev_err(mtk->dev, "clocks are not stable (0x%x)\n", value); + return ret; + } + + return 0; +} + +static int xhci_mtk_host_disable(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value; + int ret; + int i; + + /* power down all u3 ports */ + for (i = 0; i < mtk->num_u3_ports; i++) { + value = readl(&ippc->u3_ctrl_p[i]); + value |= CTRL_U3_PORT_PDN; + writel(value, &ippc->u3_ctrl_p[i]); + } + + /* power down all u2 ports */ + for (i = 0; i < mtk->num_u2_ports; i++) { + value = readl(&ippc->u2_ctrl_p[i]); + value |= CTRL_U2_PORT_PDN; + writel(value, &ippc->u2_ctrl_p[i]); + } + + /* power down host ip */ + value = readl(&ippc->ip_pw_ctr1); + value |= CTRL1_IP_HOST_PDN; + writel(value, &ippc->ip_pw_ctr1); + + /* wait for host ip to sleep */ + ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, + (value & STS1_IP_SLEEP_STS), 100, 100000); + if (ret) { + dev_err(mtk->dev, "ip sleep failed!!!\n"); + return ret; + } + return 0; +} + +static int xhci_mtk_ssusb_config(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value; + + /* reset whole ip */ + value = readl(&ippc->ip_pw_ctr0); + value |= CTRL0_IP_SW_RST; + writel(value, &ippc->ip_pw_ctr0); + udelay(1); + value = readl(&ippc->ip_pw_ctr0); + value &= ~CTRL0_IP_SW_RST; + writel(value, &ippc->ip_pw_ctr0); + + /* + * device ip is default power-on in fact + * power down device ip, otherwise ip-sleep will fail + */ + value = readl(&ippc->ip_pw_ctr2); + value |= CTRL2_IP_DEV_PDN; + writel(value, &ippc->ip_pw_ctr2); + + value = readl(&ippc->ip_xhci_cap); + mtk->num_u3_ports = CAP_U3_PORT_NUM(value); + mtk->num_u2_ports = CAP_U2_PORT_NUM(value); + dev_dbg(mtk->dev, "%s u2p:%d, u3p:%d\n", __func__, + mtk->num_u2_ports, mtk->num_u3_ports); + + return xhci_mtk_host_enable(mtk); +} + +static int xhci_mtk_clks_enable(struct xhci_hcd_mtk *mtk) +{ + int ret; + + ret = clk_prepare_enable(mtk->sys_clk); + if (ret) { + dev_err(mtk->dev, "failed to enable sys_clk\n"); + goto sys_clk_err; + } + + if (mtk->wakeup_src) { + ret = clk_prepare_enable(mtk->wk_deb_p0); + if (ret) { + dev_err(mtk->dev, "failed to enable wk_deb_p0\n"); + goto usb_p0_err; + } + + ret = clk_prepare_enable(mtk->wk_deb_p1); + if (ret) { + dev_err(mtk->dev, "failed to enable wk_deb_p1\n"); + goto usb_p1_err; + } + } + return 0; + +usb_p1_err: + clk_disable_unprepare(mtk->wk_deb_p0); +usb_p0_err: + clk_disable_unprepare(mtk->sys_clk); +sys_clk_err: + return -EINVAL; +} + +static void xhci_mtk_clks_disable(struct xhci_hcd_mtk *mtk) +{ + if (mtk->wakeup_src) { + clk_disable_unprepare(mtk->wk_deb_p1); + clk_disable_unprepare(mtk->wk_deb_p0); + } + clk_disable_unprepare(mtk->sys_clk); +} + +/* only clocks can be turn off for ip-sleep wakeup mode */ +static void usb_wakeup_ip_sleep_en(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + struct regmap *pericfg = mtk->pericfg; + + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_IS_P; + tmp &= ~(UWK_CTL1_IS_C(0xf)); + tmp |= UWK_CTL1_IS_C(0x8); + regmap_write(pericfg, PERI_WK_CTRL1, tmp); + regmap_write(pericfg, PERI_WK_CTRL1, tmp | UWK_CTL1_IS_E); + + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + dev_dbg(mtk->dev, "%s(): WK_CTRL1[P6,E25,C26:29]=%#x\n", + __func__, tmp); +} + +static void usb_wakeup_ip_sleep_dis(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + + regmap_read(mtk->pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_IS_E; + regmap_write(mtk->pericfg, PERI_WK_CTRL1, tmp); +} + +/* +* for line-state wakeup mode, phy's power should not power-down +* and only support cable plug in/out +*/ +static void usb_wakeup_line_state_en(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + struct regmap *pericfg = mtk->pericfg; + + /* line-state of u2-port0 */ + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_0P_LS_P; + tmp &= ~(UWK_CTL1_0P_LS_C(0xf)); + tmp |= UWK_CTL1_0P_LS_C(0x8); + regmap_write(pericfg, PERI_WK_CTRL1, tmp); + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + regmap_write(pericfg, PERI_WK_CTRL1, tmp | UWK_CTL1_0P_LS_E); + + /* line-state of u2-port1 */ + regmap_read(pericfg, PERI_WK_CTRL0, &tmp); + tmp &= ~(UWK_CTL1_1P_LS_C(0xf)); + tmp |= UWK_CTL1_1P_LS_C(0x8); + regmap_write(pericfg, PERI_WK_CTRL0, tmp); + regmap_write(pericfg, PERI_WK_CTRL0, tmp | UWK_CTL1_1P_LS_E); +} + +static void usb_wakeup_line_state_dis(struct xhci_hcd_mtk *mtk) +{ + u32 tmp; + struct regmap *pericfg = mtk->pericfg; + + /* line-state of u2-port0 */ + regmap_read(pericfg, PERI_WK_CTRL1, &tmp); + tmp &= ~UWK_CTL1_0P_LS_E; + regmap_write(pericfg, PERI_WK_CTRL1, tmp); + + /* line-state of u2-port1 */ + regmap_read(pericfg, PERI_WK_CTRL0, &tmp); + tmp &= ~UWK_CTL1_1P_LS_E; + regmap_write(pericfg, PERI_WK_CTRL0, tmp); +} + +static void usb_wakeup_enable(struct xhci_hcd_mtk *mtk) +{ + if (mtk->wakeup_src == SSUSB_WK_IP_SLEEP) + usb_wakeup_ip_sleep_en(mtk); + else if (mtk->wakeup_src == SSUSB_WK_LINE_STATE) + usb_wakeup_line_state_en(mtk); +} + +static void usb_wakeup_disable(struct xhci_hcd_mtk *mtk) +{ + if (mtk->wakeup_src == SSUSB_WK_IP_SLEEP) + usb_wakeup_ip_sleep_dis(mtk); + else if (mtk->wakeup_src == SSUSB_WK_LINE_STATE) + usb_wakeup_line_state_dis(mtk); +} + +static int usb_wakeup_of_property_parse(struct xhci_hcd_mtk *mtk, + struct device_node *dn) +{ + struct device *dev = mtk->dev; + + /* + * wakeup function is optional, so it is not an error if this property + * does not exist, and in such case, no need to get relative + * properties anymore. + */ + of_property_read_u32(dn, "mediatek,wakeup-src", &mtk->wakeup_src); + if (!mtk->wakeup_src) + return 0; + + mtk->wk_deb_p0 = devm_clk_get(dev, "wakeup_deb_p0"); + if (IS_ERR(mtk->wk_deb_p0)) { + dev_err(dev, "fail to get wakeup_deb_p0\n"); + return PTR_ERR(mtk->wk_deb_p0); + } + + mtk->wk_deb_p1 = devm_clk_get(dev, "wakeup_deb_p1"); + if (IS_ERR(mtk->wk_deb_p1)) { + dev_err(dev, "fail to get wakeup_deb_p1\n"); + return PTR_ERR(mtk->wk_deb_p1); + } + + mtk->pericfg = syscon_regmap_lookup_by_phandle(dn, + "mediatek,syscon-wakeup"); + if (IS_ERR(mtk->pericfg)) { + dev_err(dev, "fail to get pericfg regs\n"); + return PTR_ERR(mtk->pericfg); + } + + return 0; +} + +static int xhci_mtk_setup(struct usb_hcd *hcd); +static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = { + .extra_priv_size = sizeof(struct xhci_hcd), + .reset = xhci_mtk_setup, +}; + +static struct hc_driver __read_mostly xhci_mtk_hc_driver; + +static int xhci_mtk_phy_init(struct xhci_hcd_mtk *mtk) +{ + int i; + int ret; + + for (i = 0; i < mtk->num_phys; i++) { + ret = phy_init(mtk->phys[i]); + if (ret) + goto exit_phy; + } + return 0; + +exit_phy: + for (; i > 0; i--) + phy_exit(mtk->phys[i - 1]); + + return ret; +} + +static int xhci_mtk_phy_exit(struct xhci_hcd_mtk *mtk) +{ + int i; + + for (i = 0; i < mtk->num_phys; i++) + phy_exit(mtk->phys[i]); + + return 0; +} + +static int xhci_mtk_phy_power_on(struct xhci_hcd_mtk *mtk) +{ + int i; + int ret; + + for (i = 0; i < mtk->num_phys; i++) { + ret = phy_power_on(mtk->phys[i]); + if (ret) + goto power_off_phy; + } + return 0; + +power_off_phy: + for (; i > 0; i--) + phy_power_off(mtk->phys[i - 1]); + + return ret; +} + +static void xhci_mtk_phy_power_off(struct xhci_hcd_mtk *mtk) +{ + unsigned int i; + + for (i = 0; i < mtk->num_phys; i++) + phy_power_off(mtk->phys[i]); +} + +static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk) +{ + int ret; + + ret = regulator_enable(mtk->vbus); + if (ret) { + dev_err(mtk->dev, "failed to enable vbus\n"); + return ret; + } + + ret = regulator_enable(mtk->vusb33); + if (ret) { + dev_err(mtk->dev, "failed to enable vusb33\n"); + regulator_disable(mtk->vbus); + return ret; + } + return 0; +} + +static void xhci_mtk_ldos_disable(struct xhci_hcd_mtk *mtk) +{ + regulator_disable(mtk->vbus); + regulator_disable(mtk->vusb33); +} + +static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + + /* + * As of now platform drivers don't provide MSI support so we ensure + * here that the generic code does not try to make a pci_dev from our + * dev struct in order to setup MSI + */ + xhci->quirks |= XHCI_PLAT; + xhci->quirks |= XHCI_MTK_HOST; + /* + * MTK host controller gives a spurious successful event after a + * short transfer. Ignore it. + */ + xhci->quirks |= XHCI_SPURIOUS_SUCCESS; + if (mtk->lpm_support) + xhci->quirks |= XHCI_LPM_SUPPORT; +} + +/* called during probe() after chip reset completes */ +static int xhci_mtk_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + int ret; + + if (usb_hcd_is_primary_hcd(hcd)) { + ret = xhci_mtk_ssusb_config(mtk); + if (ret) + return ret; + ret = xhci_mtk_sch_init(mtk); + if (ret) + return ret; + } + + return xhci_gen_setup(hcd, xhci_mtk_quirks); +} + +static int xhci_mtk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct xhci_hcd_mtk *mtk; + const struct hc_driver *driver; + struct xhci_hcd *xhci; + struct resource *res; + struct usb_hcd *hcd; + struct phy *phy; + int phy_num; + int ret = -ENODEV; + int irq; + + if (usb_disabled()) + return -ENODEV; + + driver = &xhci_mtk_hc_driver; + mtk = devm_kzalloc(dev, sizeof(*mtk), GFP_KERNEL); + if (!mtk) + return -ENOMEM; + + mtk->dev = dev; + mtk->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(mtk->vbus)) { + dev_err(dev, "fail to get vbus\n"); + return PTR_ERR(mtk->vbus); + } + + mtk->vusb33 = devm_regulator_get(dev, "vusb33"); + if (IS_ERR(mtk->vusb33)) { + dev_err(dev, "fail to get vusb33\n"); + return PTR_ERR(mtk->vusb33); + } + + mtk->sys_clk = devm_clk_get(dev, "sys_ck"); + if (IS_ERR(mtk->sys_clk)) { + dev_err(dev, "fail to get sys_ck\n"); + return PTR_ERR(mtk->sys_clk); + } + + mtk->lpm_support = of_property_read_bool(node, "usb3-lpm-capable"); + + ret = usb_wakeup_of_property_parse(mtk, node); + if (ret) + return ret; + + mtk->num_phys = of_count_phandle_with_args(node, + "phys", "#phy-cells"); + if (mtk->num_phys > 0) { + mtk->phys = devm_kcalloc(dev, mtk->num_phys, + sizeof(*mtk->phys), GFP_KERNEL); + if (!mtk->phys) + return -ENOMEM; + } else { + mtk->num_phys = 0; + } + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + device_enable_async_suspend(dev); + + ret = xhci_mtk_ldos_enable(mtk); + if (ret) + goto disable_pm; + + ret = xhci_mtk_clks_enable(mtk); + if (ret) + goto disable_ldos; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto disable_clk; + + /* Initialize dma_mask and coherent_dma_mask to 32-bits */ + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + if (ret) + goto disable_clk; + + if (!dev->dma_mask) + dev->dma_mask = &dev->coherent_dma_mask; + else + dma_set_mask(dev, DMA_BIT_MASK(32)); + + hcd = usb_create_hcd(driver, dev, dev_name(dev)); + if (!hcd) { + ret = -ENOMEM; + goto disable_clk; + } + + /* + * USB 2.0 roothub is stored in the platform_device. + * Swap it with mtk HCD. + */ + mtk->hcd = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, mtk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto put_usb2_hcd; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mtk->ippc_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(mtk->ippc_regs)) { + ret = PTR_ERR(mtk->ippc_regs); + goto put_usb2_hcd; + } + + for (phy_num = 0; phy_num < mtk->num_phys; phy_num++) { + phy = devm_of_phy_get_by_index(dev, node, phy_num); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + goto put_usb2_hcd; + } + mtk->phys[phy_num] = phy; + } + + ret = xhci_mtk_phy_init(mtk); + if (ret) + goto put_usb2_hcd; + + ret = xhci_mtk_phy_power_on(mtk); + if (ret) + goto exit_phys; + + device_init_wakeup(dev, true); + + xhci = hcd_to_xhci(hcd); + xhci->main_hcd = hcd; + xhci->shared_hcd = usb_create_shared_hcd(driver, dev, + dev_name(dev), hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto power_off_phys; + } + + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) + goto put_usb3_hcd; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); + if (ret) + goto dealloc_usb2_hcd; + + return 0; + +dealloc_usb2_hcd: + usb_remove_hcd(hcd); + +put_usb3_hcd: + xhci_mtk_sch_exit(mtk); + usb_put_hcd(xhci->shared_hcd); + +power_off_phys: + xhci_mtk_phy_power_off(mtk); + device_init_wakeup(dev, false); + +exit_phys: + xhci_mtk_phy_exit(mtk); + +put_usb2_hcd: + usb_put_hcd(hcd); + +disable_clk: + xhci_mtk_clks_disable(mtk); + +disable_ldos: + xhci_mtk_ldos_disable(mtk); + +disable_pm: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return ret; +} + +static int xhci_mtk_remove(struct platform_device *dev) +{ + struct xhci_hcd_mtk *mtk = platform_get_drvdata(dev); + struct usb_hcd *hcd = mtk->hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + usb_remove_hcd(xhci->shared_hcd); + xhci_mtk_phy_power_off(mtk); + xhci_mtk_phy_exit(mtk); + device_init_wakeup(&dev->dev, false); + + usb_remove_hcd(hcd); + usb_put_hcd(xhci->shared_hcd); + usb_put_hcd(hcd); + xhci_mtk_sch_exit(mtk); + xhci_mtk_clks_disable(mtk); + xhci_mtk_ldos_disable(mtk); + pm_runtime_put_sync(&dev->dev); + pm_runtime_disable(&dev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int xhci_mtk_suspend(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + + xhci_mtk_host_disable(mtk); + xhci_mtk_phy_power_off(mtk); + xhci_mtk_clks_disable(mtk); + usb_wakeup_enable(mtk); + return 0; +} + +static int xhci_mtk_resume(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + + usb_wakeup_disable(mtk); + xhci_mtk_clks_enable(mtk); + xhci_mtk_phy_power_on(mtk); + xhci_mtk_host_enable(mtk); + return 0; +} + +static const struct dev_pm_ops xhci_mtk_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xhci_mtk_suspend, xhci_mtk_resume) +}; +#define DEV_PM_OPS (&xhci_mtk_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_OF +static const struct of_device_id mtk_xhci_of_match[] = { + { .compatible = "mediatek,mt8173-xhci"}, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_xhci_of_match); +#endif + +static struct platform_driver mtk_xhci_driver = { + .probe = xhci_mtk_probe, + .remove = xhci_mtk_remove, + .driver = { + .name = "xhci-mtk", + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(mtk_xhci_of_match), + }, +}; +MODULE_ALIAS("platform:xhci-mtk"); + +static int __init xhci_mtk_init(void) +{ + xhci_init_driver(&xhci_mtk_hc_driver, &xhci_mtk_overrides); + return platform_driver_register(&mtk_xhci_driver); +} +module_init(xhci_mtk_init); + +static void __exit xhci_mtk_exit(void) +{ + platform_driver_unregister(&mtk_xhci_driver); +} +module_exit(xhci_mtk_exit); + +MODULE_AUTHOR("Chunfeng Yun "); +MODULE_DESCRIPTION("MediaTek xHCI Host Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h new file mode 100644 index 000000000000..7da677c79ea8 --- /dev/null +++ b/drivers/usb/host/xhci-mtk.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Zhigang.Wei + * Chunfeng.Yun + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _XHCI_MTK_H_ +#define _XHCI_MTK_H_ + +#include "xhci.h" + +/** + * To simplify scheduler algorithm, set a upper limit for ESIT, + * if a synchromous ep's ESIT is larger than @XHCI_MTK_MAX_ESIT, + * round down to the limit value, that means allocating more + * bandwidth to it. + */ +#define XHCI_MTK_MAX_ESIT 64 + +/** + * struct mu3h_sch_bw_info: schedule information for bandwidth domain + * + * @bus_bw: array to keep track of bandwidth already used at each uframes + * @bw_ep_list: eps in the bandwidth domain + * + * treat a HS root port as a bandwidth domain, but treat a SS root port as + * two bandwidth domains, one for IN eps and another for OUT eps. + */ +struct mu3h_sch_bw_info { + u32 bus_bw[XHCI_MTK_MAX_ESIT]; + struct list_head bw_ep_list; +}; + +/** + * struct mu3h_sch_ep_info: schedule information for endpoint + * + * @esit: unit is 125us, equal to 2 << Interval field in ep-context + * @num_budget_microframes: number of continuous uframes + * (@repeat==1) scheduled within the interval + * @bw_cost_per_microframe: bandwidth cost per microframe + * @endpoint: linked into bandwidth domain which it belongs to + * @ep: address of usb_host_endpoint struct + * @offset: which uframe of the interval that transfer should be + * scheduled first time within the interval + * @repeat: the time gap between two uframes that transfers are + * scheduled within a interval. in the simple algorithm, only + * assign 0 or 1 to it; 0 means using only one uframe in a + * interval, and 1 means using @num_budget_microframes + * continuous uframes + * @pkts: number of packets to be transferred in the scheduled uframes + * @cs_count: number of CS that host will trigger + * @burst_mode: burst mode for scheduling. 0: normal burst mode, + * distribute the bMaxBurst+1 packets for a single burst + * according to @pkts and @repeat, repeate the burst multiple + * times; 1: distribute the (bMaxBurst+1)*(Mult+1) packets + * according to @pkts and @repeat. normal mode is used by + * default + */ +struct mu3h_sch_ep_info { + u32 esit; + u32 num_budget_microframes; + u32 bw_cost_per_microframe; + struct list_head endpoint; + void *ep; + /* + * mtk xHCI scheduling information put into reserved DWs + * in ep context + */ + u32 offset; + u32 repeat; + u32 pkts; + u32 cs_count; + u32 burst_mode; +}; + +#define MU3C_U3_PORT_MAX 4 +#define MU3C_U2_PORT_MAX 5 + +/** + * struct mu3c_ippc_regs: MTK ssusb ip port control registers + * @ip_pw_ctr0~3: ip power and clock control registers + * @ip_pw_sts1~2: ip power and clock status registers + * @ip_xhci_cap: ip xHCI capability register + * @u3_ctrl_p[x]: ip usb3 port x control register, only low 4bytes are used + * @u2_ctrl_p[x]: ip usb2 port x control register, only low 4bytes are used + * @u2_phy_pll: usb2 phy pll control register + */ +struct mu3c_ippc_regs { + __le32 ip_pw_ctr0; + __le32 ip_pw_ctr1; + __le32 ip_pw_ctr2; + __le32 ip_pw_ctr3; + __le32 ip_pw_sts1; + __le32 ip_pw_sts2; + __le32 reserved0[3]; + __le32 ip_xhci_cap; + __le32 reserved1[2]; + __le64 u3_ctrl_p[MU3C_U3_PORT_MAX]; + __le64 u2_ctrl_p[MU3C_U2_PORT_MAX]; + __le32 reserved2; + __le32 u2_phy_pll; + __le32 reserved3[33]; /* 0x80 ~ 0xff */ +}; + +struct xhci_hcd_mtk { + struct device *dev; + struct usb_hcd *hcd; + struct mu3h_sch_bw_info *sch_array; + struct mu3c_ippc_regs __iomem *ippc_regs; + int num_u2_ports; + int num_u3_ports; + struct regulator *vusb33; + struct regulator *vbus; + struct clk *sys_clk; /* sys and mac clock */ + struct clk *wk_deb_p0; /* port0's wakeup debounce clock */ + struct clk *wk_deb_p1; + struct regmap *pericfg; + struct phy **phys; + int num_phys; + int wakeup_src; + bool lpm_support; +}; + +static inline struct xhci_hcd_mtk *hcd_to_mtk(struct usb_hcd *hcd) +{ + return dev_get_drvdata(hcd->self.controller); +} + +#if IS_ENABLED(CONFIG_USB_XHCI_MTK) +int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk); +void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk); +int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); +void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); + +#else +static inline int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, + struct usb_device *udev, struct usb_host_endpoint *ep) +{ + return 0; +} + +static inline void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, + struct usb_device *udev, struct usb_host_endpoint *ep) +{ +} + +#endif + +#endif /* _XHCI_MTK_H_ */ diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 6c5e8133cf87..8fab75863f1a 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -68,6 +68,7 @@ #include #include "xhci.h" #include "xhci-trace.h" +#include "xhci-mtk.h" /* * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA @@ -3074,17 +3075,22 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred, { u32 maxp, total_packet_count; - if (xhci->hci_version < 0x100) + /* MTK xHCI is mostly 0.97 but contains some features from 1.0 */ + if (xhci->hci_version < 0x100 && !(xhci->quirks & XHCI_MTK_HOST)) return ((td_total_len - transferred) >> 10); - maxp = GET_MAX_PACKET(usb_endpoint_maxp(&urb->ep->desc)); - total_packet_count = DIV_ROUND_UP(td_total_len, maxp); - /* One TRB with a zero-length data packet. */ if (num_trbs_left == 0 || (transferred == 0 && trb_buff_len == 0) || trb_buff_len == td_total_len) return 0; + /* for MTK xHCI, TD size doesn't include this TRB */ + if (xhci->quirks & XHCI_MTK_HOST) + trb_buff_len = 0; + + maxp = GET_MAX_PACKET(usb_endpoint_maxp(&urb->ep->desc)); + total_packet_count = DIV_ROUND_UP(td_total_len, maxp); + /* Queueing functions don't count the current TRB into transferred */ return (total_packet_count - ((transferred + trb_buff_len) / maxp)); } @@ -3472,7 +3478,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, field |= 0x1; /* xHCI 1.0/1.1 6.4.1.2.1: Transfer Type field */ - if (xhci->hci_version >= 0x100) { + if ((xhci->hci_version >= 0x100) || (xhci->quirks & XHCI_MTK_HOST)) { if (urb->transfer_buffer_length > 0) { if (setup->bRequestType & USB_DIR_IN) field |= TRB_TX_TYPE(TRB_DATA_IN); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index ac25e9037e88..887f308376f6 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -31,6 +31,7 @@ #include "xhci.h" #include "xhci-trace.h" +#include "xhci-mtk.h" #define DRIVER_AUTHOR "Sarah Sharp" #define DRIVER_DESC "'eXtensible' Host Controller (xHC) Driver" @@ -634,7 +635,11 @@ int xhci_run(struct usb_hcd *hcd) "// Set the interrupt modulation register"); temp = readl(&xhci->ir_set->irq_control); temp &= ~ER_IRQ_INTERVAL_MASK; - temp |= (u32) 160; + /* + * the increment interval is 8 times as much as that defined + * in xHCI spec on MTK's controller + */ + temp |= (u32) ((xhci->quirks & XHCI_MTK_HOST) ? 20 : 160); writel(temp, &xhci->ir_set->irq_control); /* Set the HCD state before we enable the irqs */ @@ -1698,6 +1703,9 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, xhci_endpoint_zero(xhci, xhci->devs[udev->slot_id], ep); + if (xhci->quirks & XHCI_MTK_HOST) + xhci_mtk_drop_ep_quirk(hcd, udev, ep); + xhci_dbg(xhci, "drop ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x\n", (unsigned int) ep->desc.bEndpointAddress, udev->slot_id, @@ -1793,6 +1801,15 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, return -ENOMEM; } + if (xhci->quirks & XHCI_MTK_HOST) { + ret = xhci_mtk_add_ep_quirk(hcd, udev, ep); + if (ret < 0) { + xhci_free_or_cache_endpoint_ring(xhci, + virt_dev, ep_index); + return ret; + } + } + ctrl_ctx->add_flags |= cpu_to_le32(added_ctxs); new_add_flags = le32_to_cpu(ctrl_ctx->add_flags); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 8b4629650328..9be7348872ba 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1630,6 +1630,7 @@ struct xhci_hcd { /* For controllers with a broken beyond repair streams implementation */ #define XHCI_BROKEN_STREAMS (1 << 19) #define XHCI_PME_STUCK_QUIRK (1 << 20) +#define XHCI_MTK_HOST (1 << 21) unsigned int num_active_eps; unsigned int limit_active_eps; /* There are two roothubs to keep track of bus suspend info for */ -- cgit v1.2.3 From a5da9568c58023dff5e79a5eb52b164e69890707 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Tue, 24 Nov 2015 13:09:57 +0200 Subject: xhci: use debug level when printing out interval rounding messages Don't use dev_warn() for intened behaviour, use dev_dbg() Rounding down the interval to the nearest power of 2 is required by xhci specs. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index c48cbe731356..536d00f21eed 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1245,7 +1245,7 @@ static unsigned int xhci_microframes_to_exponent(struct usb_device *udev, interval = fls(desc_interval) - 1; interval = clamp_val(interval, min_exponent, max_exponent); if ((1 << interval) != desc_interval) - dev_warn(&udev->dev, + dev_dbg(&udev->dev, "ep %#x - rounding interval to %d microframes, ep desc says %d microframes\n", ep->desc.bEndpointAddress, 1 << interval, -- cgit v1.2.3 From d5ddcdf4d672fddeb947ab144eb355c917b431c3 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Tue, 24 Nov 2015 13:09:58 +0200 Subject: xhci: rework xhci extended capability list parsing functions Replace the existing two extended capability parsing helper functions with one called xhci_find_next_ext_cap(). The extended capabilities are read both in pci-quirks before xhci driver is loaded, and inside the xhci driver when adding ports. The existing helpers did not suit well for these cases and a lot of custom parsing code was needed. The new helper function simplifies these two cases a lot. The motivation for this rework was that code to support xhci debug capability needed to parse extended capabilities, and it included yet another capability parsing helper specific for its needs. With this solution it debug capability code can use this new helper as well Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/pci-quirks.c | 25 +++++------- drivers/usb/host/xhci-ext-caps.h | 83 ++++++++++++++-------------------------- drivers/usb/host/xhci-mem.c | 73 +++++++++++++---------------------- 3 files changed, 64 insertions(+), 117 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index f9400564cb72..26cb8c861e6e 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -984,24 +984,17 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev) * Find the Legacy Support Capability register - * this is optional for xHCI host controllers. */ - ext_cap_offset = xhci_find_next_cap_offset(base, XHCI_HCC_PARAMS_OFFSET); - do { - if ((ext_cap_offset + sizeof(val)) > len) { - /* We're reading garbage from the controller */ - dev_warn(&pdev->dev, - "xHCI controller failing to respond"); - return; - } + ext_cap_offset = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_LEGACY); - if (!ext_cap_offset) - /* We've reached the end of the extended capabilities */ - goto hc_init; + if (!ext_cap_offset) + goto hc_init; - val = readl(base + ext_cap_offset); - if (XHCI_EXT_CAPS_ID(val) == XHCI_EXT_CAPS_LEGACY) - break; - ext_cap_offset = xhci_find_next_cap_offset(base, ext_cap_offset); - } while (1); + if ((ext_cap_offset + sizeof(val)) > len) { + /* We're reading garbage from the controller */ + dev_warn(&pdev->dev, "xHCI controller failing to respond"); + return; + } + val = readl(base + ext_cap_offset); /* If the BIOS owns the HC, signal that the OS wants it, and wait */ if (val & XHCI_HC_BIOS_OWNED) { diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index 9fe3225e6c61..04ce6b156b35 100644 --- a/drivers/usb/host/xhci-ext-caps.h +++ b/drivers/usb/host/xhci-ext-caps.h @@ -90,67 +90,40 @@ #include -/** - * Return the next extended capability pointer register. - * - * @base PCI register base address. - * - * @ext_offset Offset of the 32-bit register that contains the extended - * capabilites pointer. If searching for the first extended capability, pass - * in XHCI_HCC_PARAMS_OFFSET. If searching for the next extended capability, - * pass in the offset of the current extended capability register. - * - * Returns 0 if there is no next extended capability register or returns the register offset - * from the PCI registers base address. - */ -static inline int xhci_find_next_cap_offset(void __iomem *base, int ext_offset) -{ - u32 next; - - next = readl(base + ext_offset); - - if (ext_offset == XHCI_HCC_PARAMS_OFFSET) { - /* Find the first extended capability */ - next = XHCI_HCC_EXT_CAPS(next); - ext_offset = 0; - } else { - /* Find the next extended capability */ - next = XHCI_EXT_CAPS_NEXT(next); - } - - if (!next) - return 0; - /* - * Address calculation from offset of extended capabilities - * (or HCCPARAMS) register - see section 5.3.6 and section 7. - */ - return ext_offset + (next << 2); -} - /** * Find the offset of the extended capabilities with capability ID id. * - * @base PCI MMIO registers base address. - * @ext_offset Offset from base of the first extended capability to look at, - * or the address of HCCPARAMS. - * @id Extended capability ID to search for. + * @base PCI MMIO registers base address. + * @start address at which to start looking, (0 or HCC_PARAMS to start at + * beginning of list) + * @id Extended capability ID to search for. * - * This uses an arbitrary limit of XHCI_MAX_EXT_CAPS extended capabilities - * to make sure that the list doesn't contain a loop. + * Returns the offset of the next matching extended capability structure. + * Some capabilities can occur several times, e.g., the XHCI_EXT_CAPS_PROTOCOL, + * and this provides a way to find them all. */ -static inline int xhci_find_ext_cap_by_id(void __iomem *base, int ext_offset, int id) + +static inline int xhci_find_next_ext_cap(void __iomem *base, u32 start, int id) { u32 val; - int limit = XHCI_MAX_EXT_CAPS; - - while (ext_offset && limit > 0) { - val = readl(base + ext_offset); - if (XHCI_EXT_CAPS_ID(val) == id) - break; - ext_offset = xhci_find_next_cap_offset(base, ext_offset); - limit--; - } - if (limit > 0) - return ext_offset; + u32 next; + u32 offset; + + offset = start; + if (!start || start == XHCI_HCC_PARAMS_OFFSET) { + val = readl(base + XHCI_HCC_PARAMS_OFFSET); + offset = XHCI_HCC_EXT_CAPS(val) << 2; + if (!offset) + return 0; + }; + do { + val = readl(base + offset); + if (XHCI_EXT_CAPS_ID(val) == id && offset != start) + return offset; + + next = XHCI_EXT_CAPS_NEXT(val); + offset += next << 2; + } while (next); + return 0; } diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 536d00f21eed..dc7f915d9a13 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2064,17 +2064,19 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) } static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, - __le32 __iomem *addr, u8 major_revision, int max_caps) + __le32 __iomem *addr, int max_caps) { u32 temp, port_offset, port_count; int i; + u8 major_revision; struct xhci_hub *rhub; temp = readl(addr); + major_revision = XHCI_EXT_PORT_MAJOR(temp); - if (XHCI_EXT_PORT_MAJOR(temp) == 0x03) { + if (major_revision == 0x03) { rhub = &xhci->usb3_rhub; - } else if (XHCI_EXT_PORT_MAJOR(temp) <= 0x02) { + } else if (major_revision <= 0x02) { rhub = &xhci->usb2_rhub; } else { xhci_warn(xhci, "Ignoring unknown port speed, " @@ -2190,19 +2192,12 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, */ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) { - __le32 __iomem *addr, *tmp_addr; - u32 offset, tmp_offset; + void __iomem *base; + u32 offset; unsigned int num_ports; int i, j, port_index; int cap_count = 0; - - addr = &xhci->cap_regs->hcc_params; - offset = XHCI_HCC_EXT_CAPS(readl(addr)); - if (offset == 0) { - xhci_err(xhci, "No Extended Capability registers, " - "unable to set up roothub.\n"); - return -ENODEV; - } + u32 cap_start; num_ports = HCS_MAX_PORTS(xhci->hcs_params1); xhci->port_array = kzalloc(sizeof(*xhci->port_array)*num_ports, flags); @@ -2220,48 +2215,34 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) for (j = 0; j < XHCI_MAX_INTERVAL; j++) INIT_LIST_HEAD(&bw_table->interval_bw[j].endpoints); } + base = &xhci->cap_regs->hc_capbase; - /* - * For whatever reason, the first capability offset is from the - * capability register base, not from the HCCPARAMS register. - * See section 5.3.6 for offset calculation. - */ - addr = &xhci->cap_regs->hc_capbase + offset; - - tmp_addr = addr; - tmp_offset = offset; + cap_start = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_PROTOCOL); + if (!cap_start) { + xhci_err(xhci, "No Extended Capability registers, unable to set up roothub\n"); + return -ENODEV; + } + offset = cap_start; /* count extended protocol capability entries for later caching */ - do { - u32 cap_id; - cap_id = readl(tmp_addr); - if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) - cap_count++; - tmp_offset = XHCI_EXT_CAPS_NEXT(cap_id); - tmp_addr += tmp_offset; - } while (tmp_offset); + while (offset) { + cap_count++; + offset = xhci_find_next_ext_cap(base, offset, + XHCI_EXT_CAPS_PROTOCOL); + } xhci->ext_caps = kzalloc(sizeof(*xhci->ext_caps) * cap_count, flags); if (!xhci->ext_caps) return -ENOMEM; - while (1) { - u32 cap_id; - - cap_id = readl(addr); - if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) - xhci_add_in_port(xhci, num_ports, addr, - (u8) XHCI_EXT_PORT_MAJOR(cap_id), - cap_count); - offset = XHCI_EXT_CAPS_NEXT(cap_id); - if (!offset || (xhci->num_usb2_ports + xhci->num_usb3_ports) - == num_ports) + offset = cap_start; + + while (offset) { + xhci_add_in_port(xhci, num_ports, base + offset, cap_count); + if (xhci->num_usb2_ports + xhci->num_usb3_ports == num_ports) break; - /* - * Once you're into the Extended Capabilities, the offset is - * always relative to the register holding the offset. - */ - addr += offset; + offset = xhci_find_next_ext_cap(base, offset, + XHCI_EXT_CAPS_PROTOCOL); } if (xhci->num_usb2_ports == 0 && xhci->num_usb3_ports == 0) { -- cgit v1.2.3 From ced08b020e6ce0fa693e4d1d8c6088dbc0a36c64 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Mon, 26 Oct 2015 04:06:29 +0100 Subject: uwb: uwbd() is not freezable kthread uwbd() calls try_to_freeze(), but the thread doesn't mark itself freezable through set_freezable(), so the try_to_freeze() call is useless. Signed-off-by: Jiri Kosina Signed-off-by: Greg Kroah-Hartman --- drivers/uwb/uwbd.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/uwb/uwbd.c b/drivers/uwb/uwbd.c index bdcb13cc1d54..01c20a260a8b 100644 --- a/drivers/uwb/uwbd.c +++ b/drivers/uwb/uwbd.c @@ -279,7 +279,6 @@ static int uwbd(void *param) HZ); if (should_stop) break; - try_to_freeze(); spin_lock_irqsave(&rc->uwbd.event_list_lock, flags); if (!list_empty(&rc->uwbd.event_list)) { -- cgit v1.2.3 From 84c1eeb02353ffcafe039e892410cad835334ba9 Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Wed, 28 Oct 2015 12:44:35 +0530 Subject: usb : replace dma_pool_alloc and memset with dma_pool_zalloc replace dma_pool_alloc and memset with a single call to dma_pool_zalloc Signed-off-by: Saurabh Sengar Acked-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/udc.c | 3 +-- drivers/usb/gadget/udc/gr_udc.c | 3 +-- drivers/usb/host/uhci-q.c | 3 +-- drivers/usb/host/whci/qset.c | 3 +-- drivers/usb/host/xhci-mem.c | 6 ++---- 5 files changed, 6 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 391a1225b0ba..b292b454c77b 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -349,14 +349,13 @@ static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, if (node == NULL) return -ENOMEM; - node->ptr = dma_pool_alloc(hwep->td_pool, GFP_ATOMIC, + node->ptr = dma_pool_zalloc(hwep->td_pool, GFP_ATOMIC, &node->dma); if (node->ptr == NULL) { kfree(node); return -ENOMEM; } - memset(node->ptr, 0, sizeof(struct ci_hw_td)); node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES)); node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES); node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE); diff --git a/drivers/usb/gadget/udc/gr_udc.c b/drivers/usb/gadget/udc/gr_udc.c index b9429bc42511..39b7136d31d9 100644 --- a/drivers/usb/gadget/udc/gr_udc.c +++ b/drivers/usb/gadget/udc/gr_udc.c @@ -253,13 +253,12 @@ static struct gr_dma_desc *gr_alloc_dma_desc(struct gr_ep *ep, gfp_t gfp_flags) dma_addr_t paddr; struct gr_dma_desc *dma_desc; - dma_desc = dma_pool_alloc(ep->dev->desc_pool, gfp_flags, &paddr); + dma_desc = dma_pool_zalloc(ep->dev->desc_pool, gfp_flags, &paddr); if (!dma_desc) { dev_err(ep->dev->dev, "Could not allocate from DMA pool\n"); return NULL; } - memset(dma_desc, 0, sizeof(*dma_desc)); dma_desc->paddr = paddr; return dma_desc; diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c index da6f56d996ce..c17ea1589b83 100644 --- a/drivers/usb/host/uhci-q.c +++ b/drivers/usb/host/uhci-q.c @@ -248,11 +248,10 @@ static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci, dma_addr_t dma_handle; struct uhci_qh *qh; - qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle); + qh = dma_pool_zalloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle); if (!qh) return NULL; - memset(qh, 0, sizeof(*qh)); qh->dma_handle = dma_handle; qh->element = UHCI_PTR_TERM(uhci); diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c index dc31c425ce01..329747398f9a 100644 --- a/drivers/usb/host/whci/qset.c +++ b/drivers/usb/host/whci/qset.c @@ -30,10 +30,9 @@ struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags) struct whc_qset *qset; dma_addr_t dma; - qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma); + qset = dma_pool_zalloc(whc->qset_pool, mem_flags, &dma); if (qset == NULL) return NULL; - memset(qset, 0, sizeof(struct whc_qset)); qset->qset_dma = dma; qset->whc = whc; diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index dc7f915d9a13..5cd080e0a685 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -47,13 +47,12 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, if (!seg) return NULL; - seg->trbs = dma_pool_alloc(xhci->segment_pool, flags, &dma); + seg->trbs = dma_pool_zalloc(xhci->segment_pool, flags, &dma); if (!seg->trbs) { kfree(seg); return NULL; } - memset(seg->trbs, 0, TRB_SEGMENT_SIZE); /* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */ if (cycle_state == 0) { for (i = 0; i < TRBS_PER_SEGMENT; i++) @@ -517,12 +516,11 @@ static struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci if (type == XHCI_CTX_TYPE_INPUT) ctx->size += CTX_SIZE(xhci->hcc_params); - ctx->bytes = dma_pool_alloc(xhci->device_pool, flags, &ctx->dma); + ctx->bytes = dma_pool_zalloc(xhci->device_pool, flags, &ctx->dma); if (!ctx->bytes) { kfree(ctx); return NULL; } - memset(ctx->bytes, 0, ctx->size); return ctx; } -- cgit v1.2.3 From a7a19d7ab6f7b683e08ef25fd0fd673d5b09776e Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Mon, 16 Nov 2015 19:01:44 +0100 Subject: USB-EHCI: Delete unnecessary checks before the function call "dma_pool_destroy" The dma_pool_destroy() function tests whether its argument is NULL and then returns immediately. Thus the test around the calls is not needed. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-mem.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index b6205fac3567..4de43011df23 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -128,21 +128,13 @@ static void ehci_mem_cleanup (struct ehci_hcd *ehci) ehci->dummy = NULL; /* DMA consistent memory and pools */ - if (ehci->qtd_pool) - dma_pool_destroy (ehci->qtd_pool); + dma_pool_destroy(ehci->qtd_pool); ehci->qtd_pool = NULL; - - if (ehci->qh_pool) { - dma_pool_destroy (ehci->qh_pool); - ehci->qh_pool = NULL; - } - - if (ehci->itd_pool) - dma_pool_destroy (ehci->itd_pool); + dma_pool_destroy(ehci->qh_pool); + ehci->qh_pool = NULL; + dma_pool_destroy(ehci->itd_pool); ehci->itd_pool = NULL; - - if (ehci->sitd_pool) - dma_pool_destroy (ehci->sitd_pool); + dma_pool_destroy(ehci->sitd_pool); ehci->sitd_pool = NULL; if (ehci->periodic) -- cgit v1.2.3 From 764331940bed55a18132ac46bca5a4216090a06b Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 5 Nov 2015 16:20:36 +0100 Subject: uas: use the BIT() macro Use this macro to make the driver more readable. Signed-off-by: Oliver Neukum Reviewed-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/uas.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index e69151664436..e0947e3f1ea5 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -49,18 +49,18 @@ struct uas_dev_info { }; enum { - SUBMIT_STATUS_URB = (1 << 1), - ALLOC_DATA_IN_URB = (1 << 2), - SUBMIT_DATA_IN_URB = (1 << 3), - ALLOC_DATA_OUT_URB = (1 << 4), - SUBMIT_DATA_OUT_URB = (1 << 5), - ALLOC_CMD_URB = (1 << 6), - SUBMIT_CMD_URB = (1 << 7), - COMMAND_INFLIGHT = (1 << 8), - DATA_IN_URB_INFLIGHT = (1 << 9), - DATA_OUT_URB_INFLIGHT = (1 << 10), - COMMAND_ABORTED = (1 << 11), - IS_IN_WORK_LIST = (1 << 12), + SUBMIT_STATUS_URB = BIT(1), + ALLOC_DATA_IN_URB = BIT(2), + SUBMIT_DATA_IN_URB = BIT(3), + ALLOC_DATA_OUT_URB = BIT(4), + SUBMIT_DATA_OUT_URB = BIT(5), + ALLOC_CMD_URB = BIT(6), + SUBMIT_CMD_URB = BIT(7), + COMMAND_INFLIGHT = BIT(8), + DATA_IN_URB_INFLIGHT = BIT(9), + DATA_OUT_URB_INFLIGHT = BIT(10), + COMMAND_ABORTED = BIT(11), + IS_IN_WORK_LIST = BIT(12), }; /* Overrides scsi_pointer */ -- cgit v1.2.3 From b36d83913ae55c7619ac80185fb707bc30fd41c5 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Tue, 3 Nov 2015 16:43:17 +0100 Subject: uas: no gfp argument to uas_submit_urbs() This function must be called with a spinlock held. Memory can be allocated only with GFP_ATOMIC. Passing a gfp_t argument is a waste. Signed-off-by: Oliver Neukum Reviewed-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/uas.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index e0947e3f1ea5..60a1bfc80434 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -74,7 +74,7 @@ struct uas_cmd_info { /* I hate forward declarations, but I actually have a loop */ static int uas_submit_urbs(struct scsi_cmnd *cmnd, - struct uas_dev_info *devinfo, gfp_t gfp); + struct uas_dev_info *devinfo); static void uas_do_work(struct work_struct *work); static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); static void uas_free_streams(struct uas_dev_info *devinfo); @@ -105,7 +105,7 @@ static void uas_do_work(struct work_struct *work) if (!(cmdinfo->state & IS_IN_WORK_LIST)) continue; - err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + err = uas_submit_urbs(cmnd, cmnd->device->hostdata); if (!err) cmdinfo->state &= ~IS_IN_WORK_LIST; else @@ -240,7 +240,7 @@ static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, int err; cmdinfo->state |= direction | SUBMIT_STATUS_URB; - err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + err = uas_submit_urbs(cmnd, cmnd->device->hostdata); if (err) { uas_add_work(cmdinfo); } @@ -512,7 +512,7 @@ static struct urb *uas_submit_sense_urb(struct scsi_cmnd *cmnd, gfp_t gfp) } static int uas_submit_urbs(struct scsi_cmnd *cmnd, - struct uas_dev_info *devinfo, gfp_t gfp) + struct uas_dev_info *devinfo) { struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; struct urb *urb; @@ -520,14 +520,14 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, lockdep_assert_held(&devinfo->lock); if (cmdinfo->state & SUBMIT_STATUS_URB) { - urb = uas_submit_sense_urb(cmnd, gfp); + urb = uas_submit_sense_urb(cmnd, GFP_ATOMIC); if (!urb) return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~SUBMIT_STATUS_URB; } if (cmdinfo->state & ALLOC_DATA_IN_URB) { - cmdinfo->data_in_urb = uas_alloc_data_urb(devinfo, gfp, + cmdinfo->data_in_urb = uas_alloc_data_urb(devinfo, GFP_ATOMIC, cmnd, DMA_FROM_DEVICE); if (!cmdinfo->data_in_urb) return SCSI_MLQUEUE_DEVICE_BUSY; @@ -536,7 +536,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_DATA_IN_URB) { usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); - err = usb_submit_urb(cmdinfo->data_in_urb, gfp); + err = usb_submit_urb(cmdinfo->data_in_urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(cmdinfo->data_in_urb); uas_log_cmd_state(cmnd, "data in submit err", err); @@ -547,7 +547,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & ALLOC_DATA_OUT_URB) { - cmdinfo->data_out_urb = uas_alloc_data_urb(devinfo, gfp, + cmdinfo->data_out_urb = uas_alloc_data_urb(devinfo, GFP_ATOMIC, cmnd, DMA_TO_DEVICE); if (!cmdinfo->data_out_urb) return SCSI_MLQUEUE_DEVICE_BUSY; @@ -556,7 +556,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); - err = usb_submit_urb(cmdinfo->data_out_urb, gfp); + err = usb_submit_urb(cmdinfo->data_out_urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(cmdinfo->data_out_urb); uas_log_cmd_state(cmnd, "data out submit err", err); @@ -567,7 +567,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & ALLOC_CMD_URB) { - cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd); + cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, GFP_ATOMIC, cmnd); if (!cmdinfo->cmd_urb) return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~ALLOC_CMD_URB; @@ -575,7 +575,7 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, if (cmdinfo->state & SUBMIT_CMD_URB) { usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); - err = usb_submit_urb(cmdinfo->cmd_urb, gfp); + err = usb_submit_urb(cmdinfo->cmd_urb, GFP_ATOMIC); if (err) { usb_unanchor_urb(cmdinfo->cmd_urb); uas_log_cmd_state(cmnd, "cmd submit err", err); @@ -653,7 +653,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, if (!devinfo->use_streams) cmdinfo->state &= ~(SUBMIT_DATA_IN_URB | SUBMIT_DATA_OUT_URB); - err = uas_submit_urbs(cmnd, devinfo, GFP_ATOMIC); + err = uas_submit_urbs(cmnd, devinfo); if (err) { /* If we did nothing, give up now */ if (cmdinfo->state & SUBMIT_STATUS_URB) { -- cgit v1.2.3 From 144e38c6bebe41da8dc0e611f70fee53ce22604f Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Mon, 16 Nov 2015 18:18:33 +0530 Subject: usb: host: ohci-pxa27x: use of_property_read_bool() for checking if a property is present or not, of_property_read_bool is more appropriate than of_get_property() Signed-off-by: Saurabh Sengar Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-pxa27x.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index ba1bec7db026..e8c006e7a960 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -365,19 +365,19 @@ static int ohci_pxa_of_init(struct platform_device *pdev) if (!pdata) return -ENOMEM; - if (of_get_property(np, "marvell,enable-port1", NULL)) + if (of_property_read_bool(np, "marvell,enable-port1")) pdata->flags |= ENABLE_PORT1; - if (of_get_property(np, "marvell,enable-port2", NULL)) + if (of_property_read_bool(np, "marvell,enable-port2")) pdata->flags |= ENABLE_PORT2; - if (of_get_property(np, "marvell,enable-port3", NULL)) + if (of_property_read_bool(np, "marvell,enable-port3")) pdata->flags |= ENABLE_PORT3; - if (of_get_property(np, "marvell,port-sense-low", NULL)) + if (of_property_read_bool(np, "marvell,port-sense-low")) pdata->flags |= POWER_SENSE_LOW; - if (of_get_property(np, "marvell,power-control-low", NULL)) + if (of_property_read_bool(np, "marvell,power-control-low")) pdata->flags |= POWER_CONTROL_LOW; - if (of_get_property(np, "marvell,no-oc-protection", NULL)) + if (of_property_read_bool(np, "marvell,no-oc-protection")) pdata->flags |= NO_OC_PROTECTION; - if (of_get_property(np, "marvell,oc-mode-perport", NULL)) + if (of_property_read_bool(np, "marvell,oc-mode-perport")) pdata->flags |= OC_MODE_PERPORT; if (!of_property_read_u32(np, "marvell,power-on-delay", &tmp)) pdata->power_on_delay = tmp; -- cgit v1.2.3 From c7c7806700e15ca9c48d880f8c65071f041cd890 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 18 Nov 2015 17:40:23 +0800 Subject: usb: misc: usbtest: improve the description for error message Now the function of complicated_callback is not only used for iso transfer, improve the error message to reflect it. Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbtest.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 637f3f7cfce8..c1bd1eb548bb 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -1849,7 +1849,7 @@ static void complicated_callback(struct urb *urb) goto done; default: dev_err(&ctx->dev->intf->dev, - "iso resubmit err %d\n", + "resubmit err %d\n", status); /* FALLTHROUGH */ case -ENODEV: /* disconnected */ @@ -1863,7 +1863,7 @@ static void complicated_callback(struct urb *urb) if (ctx->pending == 0) { if (ctx->errors) dev_err(&ctx->dev->intf->dev, - "iso test, %lu errors out of %lu\n", + "during the test, %lu errors out of %lu\n", ctx->errors, ctx->packet_count); complete(&ctx->done); } -- cgit v1.2.3 From 6fb8ac81cb3125aafc7136f2ef0145da792bab94 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 28 Nov 2015 16:07:10 +0100 Subject: USB: constify usb_mon_operations structure The usb_mon_operations structure is never modified, so declare it as const. Done with the help of Coccinelle. Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 4 ++-- drivers/usb/mon/mon_main.c | 2 +- include/linux/usb/hcd.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 1c102d60cd9f..df0e3b92533a 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -3000,7 +3000,7 @@ EXPORT_SYMBOL_GPL(usb_hcd_platform_shutdown); #if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) -struct usb_mon_operations *mon_ops; +const struct usb_mon_operations *mon_ops; /* * The registration is unlocked. @@ -3010,7 +3010,7 @@ struct usb_mon_operations *mon_ops; * symbols from usbcore, usbcore gets referenced and cannot be unloaded first. */ -int usb_mon_register (struct usb_mon_operations *ops) +int usb_mon_register(const struct usb_mon_operations *ops) { if (mon_ops) diff --git a/drivers/usb/mon/mon_main.c b/drivers/usb/mon/mon_main.c index f7c292f4891e..fec3f1128fdc 100644 --- a/drivers/usb/mon/mon_main.c +++ b/drivers/usb/mon/mon_main.c @@ -241,7 +241,7 @@ static struct notifier_block mon_nb = { /* * Ops */ -static struct usb_mon_operations mon_ops_0 = { +static const struct usb_mon_operations mon_ops_0 = { .urb_submit = mon_submit, .urb_submit_error = mon_submit_error, .urb_complete = mon_complete, diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index f89c24bd53a4..4dcf8446dbcd 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -660,7 +660,7 @@ struct usb_mon_operations { /* void (*urb_unlink)(struct usb_bus *bus, struct urb *urb); */ }; -extern struct usb_mon_operations *mon_ops; +extern const struct usb_mon_operations *mon_ops; static inline void usbmon_urb_submit(struct usb_bus *bus, struct urb *urb) { @@ -682,7 +682,7 @@ static inline void usbmon_urb_complete(struct usb_bus *bus, struct urb *urb, (*mon_ops->urb_complete)(bus, urb, status); } -int usb_mon_register(struct usb_mon_operations *ops); +int usb_mon_register(const struct usb_mon_operations *ops); void usb_mon_deregister(void); #else -- cgit v1.2.3 From 9faae5a37b266afca6914163316856c5ed4ec366 Mon Sep 17 00:00:00 2001 From: RafaÅ‚ MiÅ‚ecki Date: Sun, 1 Nov 2015 10:04:41 +0100 Subject: USB: bcma: switch to GPIO descriptor for power control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far we were using simple (legacy) GPIO functions & some poor logic to control power. It got many drawbacks: we were ignoring OF flags (GPIO_ACTIVE_LOW), we were not setting direction to output and we were assuming gpio_request success all the time. Fix it by switching to gpiod functions and adding appropriate checks. Signed-off-by: RafaÅ‚ MiÅ‚ecki Acked-by: Hauke Mehrtens Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/bcma-hcd.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/bcma-hcd.c b/drivers/usb/host/bcma-hcd.c index 5398e3d42822..291aaa2baed8 100644 --- a/drivers/usb/host/bcma-hcd.c +++ b/drivers/usb/host/bcma-hcd.c @@ -21,6 +21,7 @@ */ #include #include +#include #include #include #include @@ -36,6 +37,7 @@ MODULE_LICENSE("GPL"); struct bcma_hcd_device { struct platform_device *ehci_dev; struct platform_device *ohci_dev; + struct gpio_desc *gpio_desc; }; /* Wait for bitmask in a register to get set or cleared. @@ -228,19 +230,12 @@ static void bcma_hcd_init_chip_arm(struct bcma_device *dev) static void bcma_hci_platform_power_gpio(struct bcma_device *dev, bool val) { - int gpio; + struct bcma_hcd_device *usb_dev = bcma_get_drvdata(dev); - gpio = of_get_named_gpio(dev->dev.of_node, "vcc-gpio", 0); - if (!gpio_is_valid(gpio)) + if (IS_ERR_OR_NULL(usb_dev->gpio_desc)) return; - if (val) { - gpio_request(gpio, "bcma-hcd-gpio"); - gpio_set_value(gpio, 1); - } else { - gpio_set_value(gpio, 0); - gpio_free(gpio); - } + gpiod_set_value(usb_dev->gpio_desc, val); } static const struct usb_ehci_pdata ehci_pdata = { @@ -314,7 +309,11 @@ static int bcma_hcd_probe(struct bcma_device *dev) if (!usb_dev) return -ENOMEM; - bcma_hci_platform_power_gpio(dev, true); + if (dev->dev.of_node) + usb_dev->gpio_desc = devm_get_gpiod_from_child(&dev->dev, "vcc", + &dev->dev.of_node->fwnode); + if (!IS_ERR_OR_NULL(usb_dev->gpio_desc)) + gpiod_direction_output(usb_dev->gpio_desc, 1); switch (dev->id.id) { case BCMA_CORE_NS_USB20: -- cgit v1.2.3 From ec4dca8bdfb650fd698401e26f586683ec69a942 Mon Sep 17 00:00:00 2001 From: Tina Ruchandani Date: Thu, 29 Oct 2015 22:58:28 -0700 Subject: USB: usbmon: Remove timeval usage for timestamp struct timeval' uses 32-bits for its seconds field and will overflow in the year 2038 and beyond. This patch replaces the usage of 'struct timeval' in mon_get_timestamp() with timespec64 which uses a 64-bit seconds field and is y2038-safe. mon_get_timestamp() truncates the timestamp at 4096 seconds, so the correctness of the code is not affected. This patch is part of a larger attempt to remove instances of struct timeval and other 32-bit timekeeping (time_t, struct timespec) from the kernel. Signed-off-by: Tina Ruchandani Suggested-by: Arnd Bergmann Reviewed-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mon/mon_text.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mon/mon_text.c b/drivers/usb/mon/mon_text.c index ad408251d955..98e4f63e6823 100644 --- a/drivers/usb/mon/mon_text.c +++ b/drivers/usb/mon/mon_text.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -176,12 +177,12 @@ static inline char mon_text_get_data(struct mon_event_text *ep, struct urb *urb, static inline unsigned int mon_get_timestamp(void) { - struct timeval tval; + struct timespec64 now; unsigned int stamp; - do_gettimeofday(&tval); - stamp = tval.tv_sec & 0xFFF; /* 2^32 = 4294967296. Limit to 4096s. */ - stamp = stamp * 1000000 + tval.tv_usec; + ktime_get_ts64(&now); + stamp = now.tv_sec & 0xFFF; /* 2^32 = 4294967296. Limit to 4096s. */ + stamp = stamp * USEC_PER_SEC + now.tv_nsec / NSEC_PER_USEC; return stamp; } -- cgit v1.2.3 From 18fc4ebdc7057478497682047db5acfb25b35c49 Mon Sep 17 00:00:00 2001 From: Deepa Dinamani Date: Wed, 4 Nov 2015 15:29:11 -0800 Subject: usb: misc: usbtest: Remove timeval usage timeval is deprecated and not y2038 safe. Its size also changes according to 32 bit/ 64 bit compilation. Replace it with 32 and 64 bit versions of its individual fields, giving two ioctls with different code values. The two ioctls are necessary to maintain the 32 bit and 64 bit userspace compatibility with a 64/32 bit kernel. Change unsigned to __u32 types for a definitive userspace interface. This is in accordance with the psABI that the unsigned type is always 32 bits. Also use motonic timer instead of real time to ensure positive delta values. Refactor usbtest_ioctl for readability to isolate the handling of the testing timing measurement. The official testusb userspace tool can be changed in a separate patch to reflect the __u32 changes as well. It can use the usbtest_param_32 struct, since 32 bit seconds is long enough for test durations. Signed-off-by: Deepa Dinamani Reviewed-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbtest.c | 229 +++++++++++++++++++++++++++++---------------- 1 file changed, 147 insertions(+), 82 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index c1bd1eb548bb..92fdb6e9faff 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -22,18 +22,42 @@ static void complicated_callback(struct urb *urb); /*-------------------------------------------------------------------------*/ /* FIXME make these public somewhere; usbdevfs.h? */ -struct usbtest_param { + +/* Parameter for usbtest driver. */ +struct usbtest_param_32 { /* inputs */ - unsigned test_num; /* 0..(TEST_CASES-1) */ - unsigned iterations; - unsigned length; - unsigned vary; - unsigned sglen; + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; /* outputs */ - struct timeval duration; + __s32 duration_sec; + __s32 duration_usec; }; -#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param) + +/* + * Compat parameter to the usbtest driver. + * This supports older user space binaries compiled with 64 bit compiler. + */ +struct usbtest_param_64 { + /* inputs */ + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; + + /* outputs */ + __s64 duration_sec; + __s64 duration_usec; +}; + +/* IOCTL interface to the driver. */ +#define USBTEST_REQUEST_32 _IOWR('U', 100, struct usbtest_param_32) +/* COMPAT IOCTL interface to the driver. */ +#define USBTEST_REQUEST_64 _IOWR('U', 100, struct usbtest_param_64) /*-------------------------------------------------------------------------*/ @@ -1030,7 +1054,7 @@ struct ctrl_ctx { unsigned pending; int status; struct urb **urb; - struct usbtest_param *param; + struct usbtest_param_32 *param; int last; }; @@ -1155,7 +1179,7 @@ error: } static int -test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param *param) +test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param) { struct usb_device *udev = testdev_to_usbdev(dev); struct urb **urb; @@ -1930,7 +1954,7 @@ static struct urb *iso_alloc_urb( } static int -test_queue(struct usbtest_dev *dev, struct usbtest_param *param, +test_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param, int pipe, struct usb_endpoint_descriptor *desc, unsigned offset) { struct transfer_context context; @@ -2049,81 +2073,20 @@ static int test_unaligned_bulk( return retval; } -/*-------------------------------------------------------------------------*/ - -/* We only have this one interface to user space, through usbfs. - * User mode code can scan usbfs to find N different devices (maybe on - * different busses) to use when testing, and allocate one thread per - * test. So discovery is simplified, and we have no device naming issues. - * - * Don't use these only as stress/load tests. Use them along with with - * other USB bus activity: plugging, unplugging, mousing, mp3 playback, - * video capture, and so on. Run different tests at different times, in - * different sequences. Nothing here should interact with other devices, - * except indirectly by consuming USB bandwidth and CPU resources for test - * threads and request completion. But the only way to know that for sure - * is to test when HC queues are in use by many devices. - * - * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), - * it locks out usbcore in certain code paths. Notably, if you disconnect - * the device-under-test, hub_wq will wait block forever waiting for the - * ioctl to complete ... so that usb_disconnect() can abort the pending - * urbs and then call usbtest_disconnect(). To abort a test, you're best - * off just killing the userspace task and waiting for it to exit. - */ - +/* Run tests. */ static int -usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +usbtest_do_ioctl(struct usb_interface *intf, struct usbtest_param_32 *param) { struct usbtest_dev *dev = usb_get_intfdata(intf); struct usb_device *udev = testdev_to_usbdev(dev); - struct usbtest_param *param = buf; - int retval = -EOPNOTSUPP; struct urb *urb; struct scatterlist *sg; struct usb_sg_request req; - struct timeval start; unsigned i; - - /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ - - pattern = mod_pattern; - - if (code != USBTEST_REQUEST) - return -EOPNOTSUPP; + int retval = -EOPNOTSUPP; if (param->iterations <= 0) return -EINVAL; - - if (param->sglen > MAX_SGLEN) - return -EINVAL; - - if (mutex_lock_interruptible(&dev->lock)) - return -ERESTARTSYS; - - /* FIXME: What if a system sleep starts while a test is running? */ - - /* some devices, like ez-usb default devices, need a non-default - * altsetting to have any active endpoints. some tests change - * altsettings; force a default so most tests don't need to check. - */ - if (dev->info->alt >= 0) { - int res; - - if (intf->altsetting->desc.bInterfaceNumber) { - mutex_unlock(&dev->lock); - return -ENODEV; - } - res = set_altsetting(dev, dev->info->alt); - if (res) { - dev_err(&intf->dev, - "set altsetting to %d failed, %d\n", - dev->info->alt, res); - mutex_unlock(&dev->lock); - return res; - } - } - /* * Just a bunch of test cases that every HCD is expected to handle. * @@ -2133,7 +2096,6 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) * FIXME add more tests! cancel requests, verify the data, control * queueing, concurrent read+write threads, and so on. */ - do_gettimeofday(&start); switch (param->test_num) { case 0: @@ -2548,13 +2510,116 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) dev->in_pipe, NULL, 0); break; } - do_gettimeofday(¶m->duration); - param->duration.tv_sec -= start.tv_sec; - param->duration.tv_usec -= start.tv_usec; - if (param->duration.tv_usec < 0) { - param->duration.tv_usec += 1000 * 1000; - param->duration.tv_sec -= 1; + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* We only have this one interface to user space, through usbfs. + * User mode code can scan usbfs to find N different devices (maybe on + * different busses) to use when testing, and allocate one thread per + * test. So discovery is simplified, and we have no device naming issues. + * + * Don't use these only as stress/load tests. Use them along with with + * other USB bus activity: plugging, unplugging, mousing, mp3 playback, + * video capture, and so on. Run different tests at different times, in + * different sequences. Nothing here should interact with other devices, + * except indirectly by consuming USB bandwidth and CPU resources for test + * threads and request completion. But the only way to know that for sure + * is to test when HC queues are in use by many devices. + * + * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), + * it locks out usbcore in certain code paths. Notably, if you disconnect + * the device-under-test, hub_wq will wait block forever waiting for the + * ioctl to complete ... so that usb_disconnect() can abort the pending + * urbs and then call usbtest_disconnect(). To abort a test, you're best + * off just killing the userspace task and waiting for it to exit. + */ + +static int +usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +{ + + struct usbtest_dev *dev = usb_get_intfdata(intf); + struct usbtest_param_64 *param_64 = buf; + struct usbtest_param_32 temp; + struct usbtest_param_32 *param_32 = buf; + struct timespec64 start; + struct timespec64 end; + struct timespec64 duration; + int retval = -EOPNOTSUPP; + + /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ + + pattern = mod_pattern; + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + + /* FIXME: What if a system sleep starts while a test is running? */ + + /* some devices, like ez-usb default devices, need a non-default + * altsetting to have any active endpoints. some tests change + * altsettings; force a default so most tests don't need to check. + */ + if (dev->info->alt >= 0) { + if (intf->altsetting->desc.bInterfaceNumber) { + retval = -ENODEV; + goto free_mutex; + } + retval = set_altsetting(dev, dev->info->alt); + if (retval) { + dev_err(&intf->dev, + "set altsetting to %d failed, %d\n", + dev->info->alt, retval); + goto free_mutex; + } + } + + switch (code) { + case USBTEST_REQUEST_64: + temp.test_num = param_64->test_num; + temp.iterations = param_64->iterations; + temp.length = param_64->length; + temp.sglen = param_64->sglen; + temp.vary = param_64->vary; + param_32 = &temp; + break; + + case USBTEST_REQUEST_32: + break; + + default: + retval = -EOPNOTSUPP; + goto free_mutex; + } + + ktime_get_ts64(&start); + + retval = usbtest_do_ioctl(intf, param_32); + if (retval) + goto free_mutex; + + ktime_get_ts64(&end); + + duration = timespec64_sub(end, start); + + temp.duration_sec = duration.tv_sec; + temp.duration_usec = duration.tv_nsec/NSEC_PER_USEC; + + switch (code) { + case USBTEST_REQUEST_32: + param_32->duration_sec = temp.duration_sec; + param_32->duration_usec = temp.duration_usec; + break; + + case USBTEST_REQUEST_64: + param_64->duration_sec = temp.duration_sec; + param_64->duration_usec = temp.duration_usec; + break; } + +free_mutex: mutex_unlock(&dev->lock); return retval; } -- cgit v1.2.3 From 8ab0f723af247af8dd92196c152c8f1da0254b2f Mon Sep 17 00:00:00 2001 From: Tina Ruchandani Date: Thu, 29 Oct 2015 22:44:31 -0700 Subject: USB: usbmon: Use 64bit timestamp for mon_bin_hdr struct mon_bin_hdr allows for a 64-bit seconds timestamp. The code currently uses 'struct timeval' to populate the timestamp in mon_bin_hdr, which has a 32-bit seconds field and will overflow in year 2038 and beyond. This patch replaces 'struct timeval' with 'struct timespec64' which is y2038 safe. This patch is part of a larger attempt to remove instances of struct timeval and other 32-bit timekeeping (time_t, struct timespec) from the kernel. Signed-off-by: Tina Ruchandani Reviewed-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mon/mon_bin.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index 3598f1a62673..1a874a1f3890 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -92,8 +93,8 @@ struct mon_bin_hdr { unsigned short busnum; /* Bus number */ char flag_setup; char flag_data; - s64 ts_sec; /* gettimeofday */ - s32 ts_usec; /* gettimeofday */ + s64 ts_sec; /* getnstimeofday64 */ + s32 ts_usec; /* getnstimeofday64 */ int status; unsigned int len_urb; /* Length of data (submitted or actual) */ unsigned int len_cap; /* Delivered length */ @@ -483,7 +484,7 @@ static void mon_bin_event(struct mon_reader_bin *rp, struct urb *urb, char ev_type, int status) { const struct usb_endpoint_descriptor *epd = &urb->ep->desc; - struct timeval ts; + struct timespec64 ts; unsigned long flags; unsigned int urb_length; unsigned int offset; @@ -494,7 +495,7 @@ static void mon_bin_event(struct mon_reader_bin *rp, struct urb *urb, struct mon_bin_hdr *ep; char data_tag = 0; - do_gettimeofday(&ts); + getnstimeofday64(&ts); spin_lock_irqsave(&rp->b_lock, flags); @@ -568,7 +569,7 @@ static void mon_bin_event(struct mon_reader_bin *rp, struct urb *urb, ep->busnum = urb->dev->bus->busnum; ep->id = (unsigned long) urb; ep->ts_sec = ts.tv_sec; - ep->ts_usec = ts.tv_usec; + ep->ts_usec = ts.tv_nsec / NSEC_PER_USEC; ep->status = status; ep->len_urb = urb_length; ep->len_cap = length + lendesc; @@ -629,12 +630,12 @@ static void mon_bin_complete(void *data, struct urb *urb, int status) static void mon_bin_error(void *data, struct urb *urb, int error) { struct mon_reader_bin *rp = data; - struct timeval ts; + struct timespec64 ts; unsigned long flags; unsigned int offset; struct mon_bin_hdr *ep; - do_gettimeofday(&ts); + getnstimeofday64(&ts); spin_lock_irqsave(&rp->b_lock, flags); @@ -656,7 +657,7 @@ static void mon_bin_error(void *data, struct urb *urb, int error) ep->busnum = urb->dev->bus->busnum; ep->id = (unsigned long) urb; ep->ts_sec = ts.tv_sec; - ep->ts_usec = ts.tv_usec; + ep->ts_usec = ts.tv_nsec / NSEC_PER_USEC; ep->status = error; ep->flag_setup = '-'; -- cgit v1.2.3 From 5ce7d27d5c4dd40d7b147f7a4c2f7308508208a7 Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 6 Nov 2015 00:04:06 -0600 Subject: usb: chipidea: msm: Use posted data writes on AHB This patch sets the AHBMODE to allow for posted data writes. This results in higher performance. Signed-off-by: Andy Gross Acked-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_msm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c index d79ecc08a1be..3889809fd0c4 100644 --- a/drivers/usb/chipidea/ci_hdrc_msm.c +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -25,7 +25,8 @@ static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) case CI_HDRC_CONTROLLER_RESET_EVENT: dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); writel(0, USB_AHBBURST); - writel(0, USB_AHBMODE); + /* use AHB transactor, allow posted data writes */ + writel(0x8, USB_AHBMODE); usb_phy_init(ci->usb_phy); break; case CI_HDRC_CONTROLLER_STOPPED_EVENT: -- cgit v1.2.3 From c56a2b2bc661fe089fde9beffeadcb0ecb5e934b Mon Sep 17 00:00:00 2001 From: Andy Gross Date: Fri, 6 Nov 2015 00:04:07 -0600 Subject: usb: host: ehci-msm: Use posted data writes on AHB This patch sets the AHBMODE to allow for posted data writes. This results in higher performance. Signed-off-by: Andy Gross Tested-by: Georgi Djakov Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-msm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c index c4f84c81de01..c23e2858c815 100644 --- a/drivers/usb/host/ehci-msm.c +++ b/drivers/usb/host/ehci-msm.c @@ -57,8 +57,8 @@ static int ehci_msm_reset(struct usb_hcd *hcd) /* bursts of unspecified length. */ writel(0, USB_AHBBURST); - /* Use the AHB transactor */ - writel(0, USB_AHBMODE); + /* Use the AHB transactor, allow posted data writes */ + writel(0x8, USB_AHBMODE); /* Disable streaming mode and select host mode */ writel(0x13, USB_USBMODE); -- cgit v1.2.3 From bf5ce5bf3cc7136fd7fe5e8999a580bc93a9c8f6 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Sat, 14 Nov 2015 16:26:32 +0800 Subject: usb: core: lpm: fix usb3_hardware_lpm sysfs node Commit 655fe4effe0f ("usbcore: add sysfs support to xHCI usb3 hardware LPM") introduced usb3_hardware_lpm sysfs node. This doesn't show the correct status of USB3 U1 and U2 LPM status. This patch fixes this by replacing usb3_hardware_lpm with two nodes, usb3_hardware_lpm_u1 (for U1) and usb3_hardware_lpm_u2 (for U2), and recording the U1/U2 LPM status in right places. This patch should be back-ported to kernels as old as 4.3, that contains Commit 655fe4effe0f ("usbcore: add sysfs support to xHCI usb3 hardware LPM"). Cc: stable@vger.kernel.org Signed-off-by: Lu Baolu Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 16 ++++++++------ Documentation/usb/power-management.txt | 11 +++++----- drivers/usb/core/hub.c | 39 +++++++++++++++++++++++++-------- drivers/usb/core/sysfs.c | 31 +++++++++++++++++++++----- include/linux/usb.h | 4 ++++ 5 files changed, 75 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 3a4abfc44f5e..136ba17d2da0 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -134,19 +134,21 @@ Description: enabled for the device. Developer can write y/Y/1 or n/N/0 to the file to enable/disable the feature. -What: /sys/bus/usb/devices/.../power/usb3_hardware_lpm -Date: June 2015 +What: /sys/bus/usb/devices/.../power/usb3_hardware_lpm_u1 + /sys/bus/usb/devices/.../power/usb3_hardware_lpm_u2 +Date: November 2015 Contact: Kevin Strasser + Lu Baolu Description: If CONFIG_PM is set and a USB 3.0 lpm-capable device is plugged in to a xHCI host which supports link PM, it will check if U1 and U2 exit latencies have been set in the BOS descriptor; if - the check is is passed and the host supports USB3 hardware LPM, + the check is passed and the host supports USB3 hardware LPM, USB3 hardware LPM will be enabled for the device and the USB - device directory will contain a file named - power/usb3_hardware_lpm. The file holds a string value (enable - or disable) indicating whether or not USB3 hardware LPM is - enabled for the device. + device directory will contain two files named + power/usb3_hardware_lpm_u1 and power/usb3_hardware_lpm_u2. These + files hold a string value (enable or disable) indicating whether + or not USB3 hardware LPM U1 or U2 is enabled for the device. What: /sys/bus/usb/devices/.../removable Date: February 2012 diff --git a/Documentation/usb/power-management.txt b/Documentation/usb/power-management.txt index 4a15c90bc11d..0a94ffe17ab6 100644 --- a/Documentation/usb/power-management.txt +++ b/Documentation/usb/power-management.txt @@ -537,17 +537,18 @@ relevant attribute files are usb2_hardware_lpm and usb3_hardware_lpm. can write y/Y/1 or n/N/0 to the file to enable/disable USB2 hardware LPM manually. This is for test purpose mainly. - power/usb3_hardware_lpm + power/usb3_hardware_lpm_u1 + power/usb3_hardware_lpm_u2 When a USB 3.0 lpm-capable device is plugged in to a xHCI host which supports link PM, it will check if U1 and U2 exit latencies have been set in the BOS descriptor; if the check is is passed and the host supports USB3 hardware LPM, USB3 hardware LPM will be - enabled for the device and this file will be created. - The file holds a string value (enable or disable) - indicating whether or not USB3 hardware LPM is - enabled for the device. + enabled for the device and these files will be created. + The files hold a string value (enable or disable) + indicating whether or not USB3 hardware LPM U1 or U2 + is enabled for the device. USB Port Power Control ---------------------- diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index bdeadc112d29..59f998e60030 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3875,17 +3875,30 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, return; } - if (usb_set_lpm_timeout(udev, state, timeout)) + if (usb_set_lpm_timeout(udev, state, timeout)) { /* If we can't set the parent hub U1/U2 timeout, * device-initiated LPM won't be allowed either, so let the xHCI * host know that this link state won't be enabled. */ hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state); + } else { + /* Only a configured device will accept the Set Feature + * U1/U2_ENABLE + */ + if (udev->actconfig) + usb_set_device_initiated_lpm(udev, state, true); - /* Only a configured device will accept the Set Feature U1/U2_ENABLE */ - else if (udev->actconfig) - usb_set_device_initiated_lpm(udev, state, true); - + /* As soon as usb_set_lpm_timeout(timeout) returns 0, the + * hub-initiated LPM is enabled. Thus, LPM is enabled no + * matter the result of usb_set_device_initiated_lpm(). + * The only difference is whether device is able to initiate + * LPM. + */ + if (state == USB3_LPM_U1) + udev->usb3_lpm_u1_enabled = 1; + else if (state == USB3_LPM_U2) + udev->usb3_lpm_u2_enabled = 1; + } } /* @@ -3925,6 +3938,18 @@ static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev, dev_warn(&udev->dev, "Could not disable xHCI %s timeout, " "bus schedule bandwidth may be impacted.\n", usb3_lpm_names[state]); + + /* As soon as usb_set_lpm_timeout(0) return 0, hub initiated LPM + * is disabled. Hub will disallows link to enter U1/U2 as well, + * even device is initiating LPM. Hence LPM is disabled if hub LPM + * timeout set to 0, no matter device-initiated LPM is disabled or + * not. + */ + if (state == USB3_LPM_U1) + udev->usb3_lpm_u1_enabled = 0; + else if (state == USB3_LPM_U2) + udev->usb3_lpm_u2_enabled = 0; + return 0; } @@ -3959,8 +3984,6 @@ int usb_disable_lpm(struct usb_device *udev) if (usb_disable_link_state(hcd, udev, USB3_LPM_U2)) goto enable_lpm; - udev->usb3_lpm_enabled = 0; - return 0; enable_lpm: @@ -4018,8 +4041,6 @@ void usb_enable_lpm(struct usb_device *udev) usb_enable_link_state(hcd, udev, USB3_LPM_U1); usb_enable_link_state(hcd, udev, USB3_LPM_U2); - - udev->usb3_lpm_enabled = 1; } EXPORT_SYMBOL_GPL(usb_enable_lpm); diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index d9ec2de6c4cf..65b6e6b84043 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -531,7 +531,7 @@ static ssize_t usb2_lpm_besl_store(struct device *dev, } static DEVICE_ATTR_RW(usb2_lpm_besl); -static ssize_t usb3_hardware_lpm_show(struct device *dev, +static ssize_t usb3_hardware_lpm_u1_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_device *udev = to_usb_device(dev); @@ -539,7 +539,7 @@ static ssize_t usb3_hardware_lpm_show(struct device *dev, usb_lock_device(udev); - if (udev->usb3_lpm_enabled) + if (udev->usb3_lpm_u1_enabled) p = "enabled"; else p = "disabled"; @@ -548,7 +548,26 @@ static ssize_t usb3_hardware_lpm_show(struct device *dev, return sprintf(buf, "%s\n", p); } -static DEVICE_ATTR_RO(usb3_hardware_lpm); +static DEVICE_ATTR_RO(usb3_hardware_lpm_u1); + +static ssize_t usb3_hardware_lpm_u2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + const char *p; + + usb_lock_device(udev); + + if (udev->usb3_lpm_u2_enabled) + p = "enabled"; + else + p = "disabled"; + + usb_unlock_device(udev); + + return sprintf(buf, "%s\n", p); +} +static DEVICE_ATTR_RO(usb3_hardware_lpm_u2); static struct attribute *usb2_hardware_lpm_attr[] = { &dev_attr_usb2_hardware_lpm.attr, @@ -562,7 +581,8 @@ static struct attribute_group usb2_hardware_lpm_attr_group = { }; static struct attribute *usb3_hardware_lpm_attr[] = { - &dev_attr_usb3_hardware_lpm.attr, + &dev_attr_usb3_hardware_lpm_u1.attr, + &dev_attr_usb3_hardware_lpm_u2.attr, NULL, }; static struct attribute_group usb3_hardware_lpm_attr_group = { @@ -592,7 +612,8 @@ static int add_power_attributes(struct device *dev) if (udev->usb2_hw_lpm_capable == 1) rc = sysfs_merge_group(&dev->kobj, &usb2_hardware_lpm_attr_group); - if (udev->lpm_capable == 1) + if (udev->speed == USB_SPEED_SUPER && + udev->lpm_capable == 1) rc = sysfs_merge_group(&dev->kobj, &usb3_hardware_lpm_attr_group); } diff --git a/include/linux/usb.h b/include/linux/usb.h index b9a28074210f..b79925dd2b41 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -511,6 +511,8 @@ struct usb3_lpm_parameters { * @usb2_hw_lpm_enabled: USB2 hardware LPM is enabled * @usb2_hw_lpm_allowed: Userspace allows USB 2.0 LPM to be enabled * @usb3_lpm_enabled: USB3 hardware LPM enabled + * @usb3_lpm_u1_enabled: USB3 hardware U1 LPM enabled + * @usb3_lpm_u2_enabled: USB3 hardware U2 LPM enabled * @string_langid: language ID for strings * @product: iProduct string, if present (static) * @manufacturer: iManufacturer string, if present (static) @@ -584,6 +586,8 @@ struct usb_device { unsigned usb2_hw_lpm_enabled:1; unsigned usb2_hw_lpm_allowed:1; unsigned usb3_lpm_enabled:1; + unsigned usb3_lpm_u1_enabled:1; + unsigned usb3_lpm_u2_enabled:1; int string_langid; /* static strings from the device */ -- cgit v1.2.3 From 513072d90a8dfe4bf83e1f81810de605eb5d7c3b Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Sat, 14 Nov 2015 16:26:33 +0800 Subject: usb: core: lpm: add sysfs node for usb3 lpm permit USB3 LPM is default on in Linux kernel if both xHCI host controller and the USB devices declare to be LPM-capable. Unfortunately, some devices are known to work well with LPM disabled, but to be broken if LPM is enabled, although it declares the LPM capability. Users won't be able to use this kind of devices, until someone puts them in the kernel blacklist and gets the kernel upgraded. This patch adds a sysfs node to permit or forbit USB3 LPM U1 or U2 entry for a port. The settings apply to both before and after device enumeration. Supported values are "0" - neither u1 nor u2 permitted, "u1" - only u1 is permitted, "u2" - only u2 is permitted, "u1_u2" - both u1 and u2 are permitted. With this interface, users can use an LPM-unfriendly USB device on a released Linux kernel. Signed-off-by: Lu Baolu Signed-off-by: Zhuang Jin Can Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 11 ++++ drivers/usb/core/hub.c | 15 +++++- drivers/usb/core/hub.h | 5 +- drivers/usb/core/port.c | 89 ++++++++++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 136ba17d2da0..0bd731cbb50c 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -189,6 +189,17 @@ Description: The file will read "hotplug", "wired" and "not used" if the information is available, and "unknown" otherwise. +What: /sys/bus/usb/devices/.../(hub interface)/portX/usb3_lpm_permit +Date: November 2015 +Contact: Lu Baolu +Description: + Some USB3.0 devices are not friendly to USB3 LPM. usb3_lpm_permit + attribute allows enabling/disabling usb3 lpm of a port. It takes + effect both before and after a usb device is enumerated. Supported + values are "0" if both u1 and u2 are NOT permitted, "u1" if only u1 + is permitted, "u2" if only u2 is permitted, "u1_u2" if both u1 and + u2 are permitted. + What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout Date: May 2013 Contact: Mathias Nyman diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 59f998e60030..db7683e2d34c 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4020,6 +4020,8 @@ EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm); void usb_enable_lpm(struct usb_device *udev) { struct usb_hcd *hcd; + struct usb_hub *hub; + struct usb_port *port_dev; if (!udev || !udev->parent || udev->speed != USB_SPEED_SUPER || @@ -4039,8 +4041,17 @@ void usb_enable_lpm(struct usb_device *udev) if (udev->lpm_disable_count > 0) return; - usb_enable_link_state(hcd, udev, USB3_LPM_U1); - usb_enable_link_state(hcd, udev, USB3_LPM_U2); + hub = usb_hub_to_struct_hub(udev->parent); + if (!hub) + return; + + port_dev = hub->ports[udev->portnum - 1]; + + if (port_dev->usb3_lpm_u1_permit) + usb_enable_link_state(hcd, udev, USB3_LPM_U1); + + if (port_dev->usb3_lpm_u2_permit) + usb_enable_link_state(hcd, udev, USB3_LPM_U2); } EXPORT_SYMBOL_GPL(usb_enable_lpm); diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 688817fb3246..45d070dd1d03 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -92,6 +92,8 @@ struct usb_hub { * @status_lock: synchronize port_event() vs usb_port_{suspend|resume} * @portnum: port index num based one * @is_superspeed cache super-speed status + * @usb3_lpm_u1_permit: whether USB3 U1 LPM is permitted. + * @usb3_lpm_u2_permit: whether USB3 U2 LPM is permitted. */ struct usb_port { struct usb_device *child; @@ -104,6 +106,8 @@ struct usb_port { struct mutex status_lock; u8 portnum; unsigned int is_superspeed:1; + unsigned int usb3_lpm_u1_permit:1; + unsigned int usb3_lpm_u2_permit:1; }; #define to_usb_port(_dev) \ @@ -155,4 +159,3 @@ static inline int hub_port_debounce_be_stable(struct usb_hub *hub, { return hub_port_debounce(hub, port1, false); } - diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 210618319f10..cb18ce0d9177 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -50,6 +50,72 @@ static ssize_t connect_type_show(struct device *dev, } static DEVICE_ATTR_RO(connect_type); +static ssize_t usb3_lpm_permit_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + const char *p; + + if (port_dev->usb3_lpm_u1_permit) { + if (port_dev->usb3_lpm_u2_permit) + p = "u1_u2"; + else + p = "u1"; + } else { + if (port_dev->usb3_lpm_u2_permit) + p = "u2"; + else + p = "0"; + } + + return sprintf(buf, "%s\n", p); +} + +static ssize_t usb3_lpm_permit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *udev = port_dev->child; + struct usb_hcd *hcd; + + if (!strncmp(buf, "u1_u2", 5)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 1; + + } else if (!strncmp(buf, "u1", 2)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 0; + + } else if (!strncmp(buf, "u2", 2)) { + port_dev->usb3_lpm_u1_permit = 0; + port_dev->usb3_lpm_u2_permit = 1; + + } else if (!strncmp(buf, "0", 1)) { + port_dev->usb3_lpm_u1_permit = 0; + port_dev->usb3_lpm_u2_permit = 0; + } else + return -EINVAL; + + /* If device is connected to the port, disable or enable lpm + * to make new u1 u2 setting take effect immediately. + */ + if (udev) { + hcd = bus_to_hcd(udev->bus); + if (!hcd) + return -EINVAL; + usb_lock_device(udev); + mutex_lock(hcd->bandwidth_mutex); + if (!usb_disable_lpm(udev)) + usb_enable_lpm(udev); + mutex_unlock(hcd->bandwidth_mutex); + usb_unlock_device(udev); + } + + return count; +} +static DEVICE_ATTR_RW(usb3_lpm_permit); + static struct attribute *port_dev_attrs[] = { &dev_attr_connect_type.attr, NULL, @@ -64,6 +130,21 @@ static const struct attribute_group *port_dev_group[] = { NULL, }; +static struct attribute *port_dev_usb3_attrs[] = { + &dev_attr_usb3_lpm_permit.attr, + NULL, +}; + +static struct attribute_group port_dev_usb3_attr_grp = { + .attrs = port_dev_usb3_attrs, +}; + +static const struct attribute_group *port_dev_usb3_group[] = { + &port_dev_attr_grp, + &port_dev_usb3_attr_grp, + NULL, +}; + static void usb_port_device_release(struct device *dev) { struct usb_port *port_dev = to_usb_port(dev); @@ -401,6 +482,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1) int usb_hub_create_port_device(struct usb_hub *hub, int port1) { struct usb_port *port_dev; + struct usb_device *hdev = hub->hdev; int retval; port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); @@ -417,7 +499,12 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) port_dev->portnum = port1; set_bit(port1, hub->power_bits); port_dev->dev.parent = hub->intfdev; - port_dev->dev.groups = port_dev_group; + if (hub_is_superspeed(hdev)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 1; + port_dev->dev.groups = port_dev_usb3_group; + } else + port_dev->dev.groups = port_dev_group; port_dev->dev.type = &usb_port_device_type; port_dev->dev.driver = &usb_port_driver; if (hub_is_superspeed(hub->hdev)) -- cgit v1.2.3 From 6b82b1223e3b14afd89d167795671a9f4f77b2f0 Mon Sep 17 00:00:00 2001 From: Al Cooper Date: Wed, 2 Dec 2015 14:55:07 -0500 Subject: usb: Add connected retry on resume for non SS devices Currently usb_port_resume waits for up to 2 seconds for CONNECT status for SS devices only. This change will do the same thing for non-SS devices even though the reason is a little different. This will fix an issue where VBUS is turned off during system wide "suspend to ram" and some 2.0 devices take greater than the current max of 100ms to show connected after VBUS is enabled. This is most commonly seen on hard drive based devices and USB3.0 devices plugged into a 2.0 only port. Signed-off-by: Al Cooper Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index db7683e2d34c..679b2ce01408 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3304,7 +3304,7 @@ static int finish_port_resume(struct usb_device *udev) /* * There are some SS USB devices which take longer time for link training. * XHCI specs 4.19.4 says that when Link training is successful, port - * sets CSC bit to 1. So if SW reads port status before successful link + * sets CCS bit to 1. So if SW reads port status before successful link * training, then it will not find device to be present. * USB Analyzer log with such buggy devices show that in some cases * device switch on the RX termination after long delay of host enabling @@ -3315,14 +3315,17 @@ static int finish_port_resume(struct usb_device *udev) * routine implements a 2000 ms timeout for link training. If in a case * link trains before timeout, loop will exit earlier. * + * There are also some 2.0 hard drive based devices and 3.0 thumb + * drives that, when plugged into a 2.0 only port, take a long + * time to set CCS after VBUS enable. + * * FIXME: If a device was connected before suspend, but was removed * while system was asleep, then the loop in the following routine will * only exit at timeout. * - * This routine should only be called when persist is enabled for a SS - * device. + * This routine should only be called when persist is enabled. */ -static int wait_for_ss_port_enable(struct usb_device *udev, +static int wait_for_connected(struct usb_device *udev, struct usb_hub *hub, int *port1, u16 *portchange, u16 *portstatus) { @@ -3335,6 +3338,7 @@ static int wait_for_ss_port_enable(struct usb_device *udev, delay_ms += 20; status = hub_port_status(hub, *port1, portstatus, portchange); } + dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms); return status; } @@ -3434,8 +3438,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) } } - if (udev->persist_enabled && hub_is_superspeed(hub->hdev)) - status = wait_for_ss_port_enable(udev, hub, &port1, &portchange, + if (udev->persist_enabled) + status = wait_for_connected(udev, hub, &port1, &portchange, &portstatus); status = check_port_resume_type(udev, -- cgit v1.2.3 From 2d80b52efe30b823b9b52521811fbfdd23b05648 Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Wed, 2 Dec 2015 18:45:47 -0300 Subject: usb: whci: fhci: remove comparison to bool Get rid of bool explicit comparisons. Caught by Coccinelle. Signed-off-by: Geyslan G. Bem Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fhci-tds.c | 2 +- drivers/usb/host/whci/qset.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/fhci-tds.c b/drivers/usb/host/fhci-tds.c index 1498061f0aea..f82ad5df1b0d 100644 --- a/drivers/usb/host/fhci-tds.c +++ b/drivers/usb/host/fhci-tds.c @@ -85,7 +85,7 @@ static struct usb_td __iomem *next_bd(struct usb_td __iomem *base, void fhci_push_dummy_bd(struct endpoint *ep) { - if (ep->already_pushed_dummy_bd == false) { + if (!ep->already_pushed_dummy_bd) { u16 td_status = in_be16(&ep->empty_td->status); out_be32(&ep->empty_td->buf_ptr, DUMMY_BD_BUFFER); diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c index 329747398f9a..5e48a605c71e 100644 --- a/drivers/usb/host/whci/qset.c +++ b/drivers/usb/host/whci/qset.c @@ -395,7 +395,7 @@ static void urb_dequeue_work(struct work_struct *work) struct whc *whc = qset->whc; unsigned long flags; - if (wurb->is_async == true) + if (wurb->is_async) asl_update(whc, WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB | WUSBCMD_ASYNC_QSET_RM); -- cgit v1.2.3 From debe26af03497eeea3ac06bff21176c9732cd54a Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Wed, 2 Dec 2015 19:18:19 -0300 Subject: usb: use BUG_ON() instead of BUG() Replace BUG() with BUG_ON(). Caught by coccinelle. Signed-off-by: Geyslan G. Bem Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/oxu210hp-hcd.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 1f139d82cee0..bc74aca8a54c 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -394,8 +394,7 @@ static void ehci_quiesce(struct oxu_hcd *oxu) u32 temp; #ifdef DEBUG - if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) - BUG(); + BUG_ON(!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)); #endif /* wait for any schedule enables/disables to take effect */ @@ -1709,9 +1708,8 @@ static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) #ifdef DEBUG assert_spin_locked(&oxu->lock); - if (oxu->reclaim || (qh->qh_state != QH_STATE_LINKED - && qh->qh_state != QH_STATE_UNLINK_WAIT)) - BUG(); + BUG_ON(oxu->reclaim || (qh->qh_state != QH_STATE_LINKED + && qh->qh_state != QH_STATE_UNLINK_WAIT)); #endif /* stop async schedule right now? */ -- cgit v1.2.3 From 097a9ea0e48fa33159ad47d1dc9ef3b215bfc090 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 3 Dec 2015 15:03:33 +0100 Subject: usb: make "nousb" a clear module parameter It shouldn't matter how usbcore is compiled. As it is a subsystem, the correct way to use nousb should be usbcore.nousb Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index f8bbd0b6d9fe..77e4c9bc0ab1 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -49,12 +49,7 @@ const char *usbcore_name = "usbcore"; static bool nousb; /* Disable USB when built into kernel image */ -/* To disable USB, kernel command line is 'nousb' not 'usbcore.nousb' */ -#ifdef MODULE module_param(nousb, bool, 0444); -#else -core_param(nousb, nousb, bool, 0444); -#endif /* * for external read access to -- cgit v1.2.3 From 1eaf35e4dd592c59041bc1ed3248c46326da1f5f Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 3 Dec 2015 15:03:34 +0100 Subject: xhci: refuse loading if nousb is used The module should fail to load. Signed-off-by: Oliver Neukum CC: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 887f308376f6..643d3121d100 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5068,6 +5068,10 @@ static int __init xhci_hcd_init(void) BUILD_BUG_ON(sizeof(struct xhci_intr_reg) != 8*32/8); /* xhci_run_regs has eight fields and embeds 128 xhci_intr_regs */ BUILD_BUG_ON(sizeof(struct xhci_run_regs) != (8+8*128)*32/8); + + if (usb_disabled()) + return -ENODEV; + return 0; } -- cgit v1.2.3 From 900937c0375efcbb2c9fb2eef50ccb5c98fc99ea Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Wed, 2 Dec 2015 20:25:23 -0300 Subject: usb: ehci: ohci: fix bool assignments When assigning bool use true instead of 1. If declaring it as static and it's false there's no need to initialize it, since static variables are zeroed by default. Caught by coccinelle. Signed-off-by: Geyslan G. Bem Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hcd.c | 2 +- drivers/usb/host/ohci-hcd.c | 4 ++-- drivers/usb/host/u132-hcd.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 48c92bf78bd0..14178bbf0694 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -98,7 +98,7 @@ module_param (park, uint, S_IRUGO); MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets"); /* for flakey hardware, ignore overcurrent indicators */ -static bool ignore_oc = 0; +static bool ignore_oc; module_param (ignore_oc, bool, S_IRUGO); MODULE_PARM_DESC (ignore_oc, "ignore bogus hardware overcurrent indications"); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 760cb57e954e..04dcedfdebf8 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -99,13 +99,13 @@ static void io_watchdog_func(unsigned long _ohci); /* Some boards misreport power switching/overcurrent */ -static bool distrust_firmware = 1; +static bool distrust_firmware = true; module_param (distrust_firmware, bool, 0); MODULE_PARM_DESC (distrust_firmware, "true to distrust firmware power/overcurrent setup"); /* Some boards leave IR set wrongly, since they fail BIOS/SMM handshakes */ -static bool no_handshake = 0; +static bool no_handshake; module_param (no_handshake, bool, 0); MODULE_PARM_DESC (no_handshake, "true (not default) disables BIOS handshake"); diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c index 692ccc69345e..05c85c7baf84 100644 --- a/drivers/usb/host/u132-hcd.c +++ b/drivers/usb/host/u132-hcd.c @@ -73,7 +73,7 @@ MODULE_LICENSE("GPL"); #define INT_MODULE_PARM(n, v) static int n = v;module_param(n, int, 0444) INT_MODULE_PARM(testing, 0); /* Some boards misreport power switching/overcurrent*/ -static bool distrust_firmware = 1; +static bool distrust_firmware = true; module_param(distrust_firmware, bool, 0); MODULE_PARM_DESC(distrust_firmware, "true to distrust firmware power/overcurren" "t setup"); -- cgit v1.2.3 From 232dce89b5b00078fcb633322edadf8badae326a Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Fri, 11 Dec 2015 06:46:41 -0300 Subject: USB: io_edgeport: remove redundant conditions This patch removes redundant conditions. (!A || (A && B)) is the same as (!A || B). Tested by compilation only. Caught by cppcheck. Signed-off-by: Geyslan G. Bem Signed-off-by: Johan Hovold --- drivers/usb/serial/io_edgeport.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index c0866971db2b..f49327d20ee8 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -1046,9 +1046,8 @@ static void edge_close(struct usb_serial_port *port) edge_port->closePending = true; - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPChase))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPChase) { /* flush and chase */ edge_port->chaseResponsePending = true; @@ -1061,9 +1060,8 @@ static void edge_close(struct usb_serial_port *port) edge_port->chaseResponsePending = false; } - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPClose))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPClose) { /* close the port */ dev_dbg(&port->dev, "%s - Sending IOSP_CMD_CLOSE_PORT\n", __func__); send_iosp_ext_cmd(edge_port, IOSP_CMD_CLOSE_PORT, 0); @@ -1612,9 +1610,8 @@ static void edge_break(struct tty_struct *tty, int break_state) struct edgeport_serial *edge_serial = usb_get_serial_data(port->serial); int status; - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPChase))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPChase) { /* flush and chase */ edge_port->chaseResponsePending = true; @@ -1628,9 +1625,8 @@ static void edge_break(struct tty_struct *tty, int break_state) } } - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetClrBreak))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetClrBreak) { if (break_state == -1) { dev_dbg(&port->dev, "%s - Sending IOSP_CMD_SET_BREAK\n", __func__); status = send_iosp_ext_cmd(edge_port, @@ -2465,9 +2461,8 @@ static void change_port_settings(struct tty_struct *tty, unsigned char stop_char = STOP_CHAR(tty); unsigned char start_char = START_CHAR(tty); - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetXChar))) { + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetXChar) { send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_XON_CHAR, start_char); send_iosp_ext_cmd(edge_port, @@ -2494,13 +2489,11 @@ static void change_port_settings(struct tty_struct *tty, } /* Set flow control to the configured value */ - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetRxFlow))) + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetRxFlow) send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_RX_FLOW, rxFlow); - if ((!edge_serial->is_epic) || - ((edge_serial->is_epic) && - (edge_serial->epic_descriptor.Supports.IOSPSetTxFlow))) + if (!edge_serial->is_epic || + edge_serial->epic_descriptor.Supports.IOSPSetTxFlow) send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_TX_FLOW, txFlow); -- cgit v1.2.3 From 365a0442f6def11b0b3012778d6a838c7a8e6912 Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Fri, 11 Dec 2015 06:46:42 -0300 Subject: USB: mos7840: remove redundant condition This patch removes redundant condition. (length && length > 5) can be reduced to a single evaluation. Tested by compilation only. Caught by cppcheck. Signed-off-by: Geyslan G. Bem Signed-off-by: Johan Hovold --- drivers/usb/serial/mos7840.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 8ac9b55f05af..2c69bfcdacc6 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -635,7 +635,7 @@ static void mos7840_interrupt_callback(struct urb *urb) * Byte 4 IIR Port 4 (port.number is 3) * Byte 5 FIFO status for both */ - if (length && length > 5) { + if (length > 5) { dev_dbg(&urb->dev->dev, "%s", "Wrong data !!!\n"); return; } -- cgit v1.2.3 From bb423984c28e9f94a8f466b791baa762cef0543d Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 16 Nov 2015 15:31:21 -0600 Subject: usb: dwc3: gadget: simplify dwc3_gadget_ep_queue() By moving our sanity checks our internal function __dwc3_gadget_ep_queue() we can simplify the externally visible API while also making sure that callers of __dwc3_gadget_ep_queue() also make use of the same checks. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index a58376fd65fe..98fb8028c792 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1044,6 +1044,17 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) struct dwc3 *dwc = dep->dwc; int ret; + if (!dep->endpoint.desc) { + dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n", + &req->request, dep->endpoint.name); + return -ESHUTDOWN; + } + + if (WARN(req->dep != dep, "request %p belongs to '%s'\n", + &req->request, req->dep->name)) { + return -EINVAL; + } + req->request.actual = 0; req->request.status = -EINPROGRESS; req->direction = dep->direction; @@ -1161,22 +1172,7 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); - if (!dep->endpoint.desc) { - dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n", - request, ep->name); - ret = -ESHUTDOWN; - goto out; - } - - if (WARN(req->dep != dep, "request %p belongs to '%s'\n", - request, req->dep->name)) { - ret = -EINVAL; - goto out; - } - ret = __dwc3_gadget_ep_queue(dep, req); - -out: spin_unlock_irqrestore(&dwc->lock, flags); return ret; -- cgit v1.2.3 From ec5e795cdefb74b55c9fce918aa8a9226a8eec41 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 16 Nov 2015 16:04:13 -0600 Subject: usb: dwc3: gadget: purge dev_dbg() calls The last few dev_dbg() messages are converted to tracepoints and we can finally ignore dev_dbg() messages during debug sessions. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 98fb8028c792..ca06ab8e2e4e 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -265,9 +265,6 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, usb_gadget_unmap_request(&dwc->gadget, &req->request, req->direction); - dev_dbg(dwc->dev, "request %p from %s completed %d/%d ===> %d\n", - req, dep->name, req->request.actual, - req->request.length, status); trace_dwc3_gadget_giveback(req); spin_unlock(&dwc->lock); @@ -985,8 +982,6 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, cmd |= DWC3_DEPCMD_PARAM(cmd_param); ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); if (ret < 0) { - dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n"); - /* * FIXME we need to iterate over the list of requests * here and stop, unmap, free and del each of the linked @@ -1045,13 +1040,16 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) int ret; if (!dep->endpoint.desc) { - dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n", + dwc3_trace(trace_dwc3_gadget, + "trying to queue request %p to disabled %s\n", &req->request, dep->endpoint.name); return -ESHUTDOWN; } if (WARN(req->dep != dep, "request %p belongs to '%s'\n", &req->request, req->dep->name)) { + dwc3_trace(trace_dwc3_gadget, "request %p belongs to '%s'\n", + &req->request, req->dep->name); return -EINVAL; } @@ -1152,7 +1150,8 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) out: if (ret && ret != -EBUSY) - dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dwc3_trace(trace_dwc3_gadget, + "%s: failed to kick transfers\n", dep->name); if (ret == -EBUSY) ret = 0; @@ -1242,7 +1241,8 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) if (!protocol && ((dep->direction && dep->flags & DWC3_EP_BUSY) || (!list_empty(&dep->req_queued) || !list_empty(&dep->request_list)))) { - dev_dbg(dwc->dev, "%s: pending request, cannot halt\n", + dwc3_trace(trace_dwc3_gadget, + "%s: pending request, cannot halt\n", dep->name); return -EAGAIN; } @@ -1369,7 +1369,7 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) speed = reg & DWC3_DSTS_CONNECTSPD; if (speed == DWC3_DSTS_SUPERSPEED) { - dev_dbg(dwc->dev, "no wakeup on SuperSpeed\n"); + dwc3_trace(trace_dwc3_gadget, "no wakeup on SuperSpeed\n"); ret = -EINVAL; goto out; } @@ -1381,8 +1381,9 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */ break; default: - dev_dbg(dwc->dev, "can't wakeup from link state %d\n", - link_state); + dwc3_trace(trace_dwc3_gadget, + "can't wakeup from '%s'\n", + dwc3_gadget_link_string(link_state)); ret = -EINVAL; goto out; } @@ -1821,7 +1822,8 @@ static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, if (count) { trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { - dev_dbg(dwc->dev, "incomplete IN transfer %s\n", + dwc3_trace(trace_dwc3_gadget, + "%s: incomplete IN transfer\n", dep->name); /* * If missed isoc occurred and there is @@ -2000,7 +2002,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, dep->resource_index = 0; if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - dev_dbg(dwc->dev, "%s is an Isochronous endpoint\n", + dwc3_trace(trace_dwc3_gadget, + "%s is an Isochronous endpoint\n", dep->name); return; } @@ -2027,7 +2030,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, if (!ret || ret == -EBUSY) return; - dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dwc3_trace(trace_dwc3_gadget, + "%s: failed to kick transfers\n", dep->name); } @@ -2049,11 +2053,12 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, case DEPEVT_STREAMEVT_NOTFOUND: /* FALLTHROUGH */ default: - dev_dbg(dwc->dev, "Couldn't find suitable stream\n"); + dwc3_trace(trace_dwc3_gadget, + "unable to find suitable stream\n"); } break; case DWC3_DEPEVT_RXTXFIFOEVT: - dev_dbg(dwc->dev, "%s FIFO Overrun\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "%s FIFO Overrun\n", dep->name); break; case DWC3_DEPEVT_EPCMDCMPLT: dwc3_trace(trace_dwc3_gadget, "Endpoint Command Complete"); -- cgit v1.2.3 From 1407bf13e3bf5f1168484c3e68b6ef9d8cf2bc72 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 16 Nov 2015 16:06:37 -0600 Subject: usb: dwc3: core: purge dev_dbg() calls The last few dev_dbg() messages are converted to tracepoints and we can finally ignore dev_dbg() messages during debug sessions. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 22b4797383cd..de5e01f41bc2 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -272,7 +272,8 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc) for (n = 0; n < dwc->num_event_buffers; n++) { evt = dwc->ev_buffs[n]; - dev_dbg(dwc->dev, "Event buf %p dma %08llx length %d\n", + dwc3_trace(trace_dwc3_core, + "Event buf %p dma %08llx length %d\n", evt->buf, (unsigned long long) evt->dma, evt->length); @@ -608,12 +609,13 @@ static int dwc3_core_init(struct dwc3 *dwc) reg |= DWC3_GCTL_GBLHIBERNATIONEN; break; default: - dev_dbg(dwc->dev, "No power optimization available\n"); + dwc3_trace(trace_dwc3_core, "No power optimization available\n"); } /* check if current dwc3 is on simulation board */ if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) { - dev_dbg(dwc->dev, "it is on FPGA board\n"); + dwc3_trace(trace_dwc3_core, + "running on FPGA platform\n"); dwc->is_fpga = true; } -- cgit v1.2.3 From acc38c4970caac17cd81dc941226ed17fe505d73 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 16 Nov 2015 16:08:09 -0600 Subject: usb: dwc3: ep0: purge dev_dbg() calls The last few dev_dbg() messages are converted to tracepoints and we can finally ignore dev_dbg() messages during debug sessions. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/ep0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 5320e939e090..3ea2bda0bd4d 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -971,7 +971,7 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, ret = usb_gadget_map_request(&dwc->gadget, &req->request, dep->number); if (ret) { - dev_dbg(dwc->dev, "failed to map request\n"); + dwc3_trace(trace_dwc3_ep0, "failed to map request\n"); return; } @@ -999,7 +999,7 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, ret = usb_gadget_map_request(&dwc->gadget, &req->request, dep->number); if (ret) { - dev_dbg(dwc->dev, "failed to map request\n"); + dwc3_trace(trace_dwc3_ep0, "failed to map request\n"); return; } -- cgit v1.2.3 From ac7bdcc1b3ad042d21bc65e57503d7b41fc69f05 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 16 Nov 2015 16:13:57 -0600 Subject: usb: dwc3: gadget: simplify next_request() return check In dwc3_cleanup_done_reqs() we expect that all iterations of our while (1) loop will find a valid struct dwc3_request *. In case we don't, we're dumping a WARN_ON_ONCE() splat so that people report the failure. This patch is a simple cleanup converting: if (!req) { WARN_ON_ONCE(1); return 1; } to: if (WARN_ON_ONCE(!req)) return 1; which is a little easier to read. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index ca06ab8e2e4e..3d131b7aae59 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1885,10 +1885,9 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, do { req = next_request(&dep->req_queued); - if (!req) { - WARN_ON_ONCE(1); + if (WARN_ON_ONCE(!req)) return 1; - } + i = 0; do { slot = req->start_slot + i; -- cgit v1.2.3 From b5d335e5ea6a60f5254c1f3d5fddd47f4531bccf Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 16 Nov 2015 16:20:34 -0600 Subject: usb: dwc3: ep0: fix setup_packet_pending initialization It just ocurred to me that dwc3 already gives a really hint of when a setup packet is pending and that's the SETUP_PENDING TRB Status for EP0 IRQs. Fix setup_packet_pending initialization based on that. While at that, also make sure the comment in gadget.c matches what code is doing. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/ep0.c | 8 +++++--- drivers/usb/dwc3/gadget.c | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 3ea2bda0bd4d..3a9354abcb68 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -817,6 +817,8 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc, status = DWC3_TRB_SIZE_TRBSTS(trb->size); if (status == DWC3_TRBSTS_SETUP_PENDING) { + dwc->setup_packet_pending = true; + dwc3_trace(trace_dwc3_ep0, "Setup Pending received"); if (r) @@ -916,8 +918,10 @@ static void dwc3_ep0_complete_status(struct dwc3 *dwc, } status = DWC3_TRB_SIZE_TRBSTS(trb->size); - if (status == DWC3_TRBSTS_SETUP_PENDING) + if (status == DWC3_TRBSTS_SETUP_PENDING) { + dwc->setup_packet_pending = true; dwc3_trace(trace_dwc3_ep0, "Setup Pending received"); + } dwc->ep0state = EP0_SETUP_PHASE; dwc3_ep0_out_start(dwc); @@ -1063,8 +1067,6 @@ static void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) static void dwc3_ep0_xfernotready(struct dwc3 *dwc, const struct dwc3_event_depevt *event) { - dwc->setup_packet_pending = true; - switch (event->status) { case DEPEVT_STATUS_CONTROL_DATA: dwc3_trace(trace_dwc3_ep0, "Control Data"); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 3d131b7aae59..e4203b79908a 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2230,8 +2230,8 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) * * Our suggested workaround is to follow the Disconnect * Event steps here, instead, based on a setup_packet_pending - * flag. Such flag gets set whenever we have a XferNotReady - * event on EP0 and gets cleared on XferComplete for the + * flag. Such flag gets set whenever we have a SETUP_PENDING + * status for EP0 TRBs and gets cleared on XferComplete for the * same endpoint. * * Refers to: -- cgit v1.2.3 From 16adc674d0d68a50dfc725574738d7ae11cf5d7e Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 18 Nov 2015 13:15:20 -0600 Subject: usb: dwc3: add generic OF glue layer For simple platforms which merely enable some clocks and populate its children, we can use this generic glue layer to avoid boilerplate code duplication. For now this supports Qcom and Xilinx, but if we find a way to add generic handling of regulators and optional PHYs, we can absorb exynos as well. Tested-by: Subbaraya Sundeep Bhatta Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/Kconfig | 9 ++ drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-of-simple.c | 178 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-of-simple.c (limited to 'drivers') diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 5a42c4590402..070e704829e5 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -87,6 +87,15 @@ config USB_DWC3_KEYSTONE Support of USB2/3 functionality in TI Keystone2 platforms. Say 'Y' or 'M' here if you have one such device +config USB_DWC3_OF_SIMPLE + tristate "Generic OF Simple Glue Layer" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB2/3 functionality in simple SoC integrations. + Currently supports Xilinx and Qualcomm DWC USB3 IP. + Say 'Y' or 'M' if you have one such device. + config USB_DWC3_ST tristate "STMicroelectronics Platforms" depends on ARCH_STI && OF diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index acc951d46c27..6491f9b474d4 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -37,5 +37,6 @@ obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o +obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c new file mode 100644 index 000000000000..60c4c5a44307 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -0,0 +1,178 @@ +/** + * dwc3-of-simple.c - OF glue layer for simple integrations + * + * Copyright (c) 2015 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Felipe Balbi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This is a combination of the old dwc3-qcom.c by Ivan T. Ivanov + * and the original patch adding support for Xilinx' SoC + * by Subbaraya Sundeep Bhatta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dwc3_of_simple { + struct device *dev; + struct clk **clks; + int num_clocks; +}; + +static int dwc3_of_simple_probe(struct platform_device *pdev) +{ + struct dwc3_of_simple *simple; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + int ret; + int i; + + simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL); + if (!simple) + return -ENOMEM; + + ret = of_clk_get_parent_count(np); + if (ret < 0) + return ret; + + simple->num_clocks = ret; + + simple->clks = devm_kcalloc(dev, simple->num_clocks, + sizeof(struct clk *), GFP_KERNEL); + if (!simple->clks) + return -ENOMEM; + + simple->dev = dev; + + for (i = 0; i < simple->num_clocks; i++) { + struct clk *clk; + + clk = of_clk_get(np, i); + if (IS_ERR(clk)) { + while (--i >= 0) + clk_put(simple->clks[i]); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret < 0) { + while (--i >= 0) { + clk_disable_unprepare(simple->clks[i]); + clk_put(simple->clks[i]); + } + clk_put(clk); + + return ret; + } + + simple->clks[i] = clk; + } + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) { + for (i = 0; i < simple->num_clocks; i++) { + clk_disable_unprepare(simple->clks[i]); + clk_put(simple->clks[i]); + } + + return ret; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + return 0; +} + +static int dwc3_of_simple_remove(struct platform_device *pdev) +{ + struct dwc3_of_simple *simple = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + for (i = 0; i < simple->num_clocks; i++) { + clk_unprepare(simple->clks[i]); + clk_put(simple->clks[i]); + } + + of_platform_depopulate(dev); + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return 0; +} + +static int dwc3_of_simple_runtime_suspend(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + int i; + + for (i = 0; i < simple->num_clocks; i++) + clk_disable(simple->clks[i]); + + return 0; +} + +static int dwc3_of_simple_runtime_resume(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + int ret; + int i; + + for (i = 0; i < simple->num_clocks; i++) { + ret = clk_enable(simple->clks[i]); + if (ret < 0) { + while (--i >= 0) + clk_disable(simple->clks[i]); + return ret; + } + } + + return 0; +} + +static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = { + SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend, + dwc3_of_simple_runtime_resume, NULL) +}; + +static const struct of_device_id of_dwc3_simple_match[] = { + { .compatible = "qcom,dwc3" }, + { .compatible = "xlnx,zynqmp-dwc3" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); + +static struct platform_driver dwc3_of_simple_driver = { + .probe = dwc3_of_simple_probe, + .remove = dwc3_of_simple_remove, + .driver = { + .name = "dwc3-of-simple", + .of_match_table = of_dwc3_simple_match, + }, +}; + +module_platform_driver(dwc3_of_simple_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 OF Simple Glue Layer"); +MODULE_AUTHOR("Felipe Balbi "); -- cgit v1.2.3 From b084662776be8b07ab9114ff1a16a4e9bf907d35 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 19 Nov 2015 12:15:43 -0600 Subject: usb: dwc3: remove dwc3-qcom in favor of dwc3-of-simple Now that we have a generic dwc3-of-simple.c, we can use that instead of maintaining dwc3-qcom.c which is extremely similar. Cc: Ivan T. Ivanov Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/Kconfig | 8 --- drivers/usb/dwc3/Makefile | 1 - drivers/usb/dwc3/dwc3-qcom.c | 130 ------------------------------------------- 3 files changed, 139 deletions(-) delete mode 100644 drivers/usb/dwc3/dwc3-qcom.c (limited to 'drivers') diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 070e704829e5..a64ce1c94d6d 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -105,12 +105,4 @@ config USB_DWC3_ST inside (i.e. STiH407). Say 'Y' or 'M' if you have one such device. -config USB_DWC3_QCOM - tristate "Qualcomm Platforms" - depends on ARCH_QCOM || COMPILE_TEST - default USB_DWC3 - help - Recent Qualcomm SoCs ship with one DesignWare Core USB3 IP inside, - say 'Y' or 'M' if you have one such device. - endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 6491f9b474d4..22420e17d68b 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -38,5 +38,4 @@ obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o -obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c deleted file mode 100644 index 088026048f49..000000000000 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ /dev/null @@ -1,130 +0,0 @@ -/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include - -struct dwc3_qcom { - struct device *dev; - - struct clk *core_clk; - struct clk *iface_clk; - struct clk *sleep_clk; -}; - -static int dwc3_qcom_probe(struct platform_device *pdev) -{ - struct device_node *node = pdev->dev.of_node; - struct dwc3_qcom *qdwc; - int ret; - - qdwc = devm_kzalloc(&pdev->dev, sizeof(*qdwc), GFP_KERNEL); - if (!qdwc) - return -ENOMEM; - - platform_set_drvdata(pdev, qdwc); - - qdwc->dev = &pdev->dev; - - qdwc->core_clk = devm_clk_get(qdwc->dev, "core"); - if (IS_ERR(qdwc->core_clk)) { - dev_err(qdwc->dev, "failed to get core clock\n"); - return PTR_ERR(qdwc->core_clk); - } - - qdwc->iface_clk = devm_clk_get(qdwc->dev, "iface"); - if (IS_ERR(qdwc->iface_clk)) { - dev_info(qdwc->dev, "failed to get optional iface clock\n"); - qdwc->iface_clk = NULL; - } - - qdwc->sleep_clk = devm_clk_get(qdwc->dev, "sleep"); - if (IS_ERR(qdwc->sleep_clk)) { - dev_info(qdwc->dev, "failed to get optional sleep clock\n"); - qdwc->sleep_clk = NULL; - } - - ret = clk_prepare_enable(qdwc->core_clk); - if (ret) { - dev_err(qdwc->dev, "failed to enable core clock\n"); - goto err_core; - } - - ret = clk_prepare_enable(qdwc->iface_clk); - if (ret) { - dev_err(qdwc->dev, "failed to enable optional iface clock\n"); - goto err_iface; - } - - ret = clk_prepare_enable(qdwc->sleep_clk); - if (ret) { - dev_err(qdwc->dev, "failed to enable optional sleep clock\n"); - goto err_sleep; - } - - ret = of_platform_populate(node, NULL, NULL, qdwc->dev); - if (ret) { - dev_err(qdwc->dev, "failed to register core - %d\n", ret); - goto err_clks; - } - - return 0; - -err_clks: - clk_disable_unprepare(qdwc->sleep_clk); -err_sleep: - clk_disable_unprepare(qdwc->iface_clk); -err_iface: - clk_disable_unprepare(qdwc->core_clk); -err_core: - return ret; -} - -static int dwc3_qcom_remove(struct platform_device *pdev) -{ - struct dwc3_qcom *qdwc = platform_get_drvdata(pdev); - - of_platform_depopulate(&pdev->dev); - - clk_disable_unprepare(qdwc->sleep_clk); - clk_disable_unprepare(qdwc->iface_clk); - clk_disable_unprepare(qdwc->core_clk); - - return 0; -} - -static const struct of_device_id of_dwc3_match[] = { - { .compatible = "qcom,dwc3" }, - { /* Sentinel */ } -}; -MODULE_DEVICE_TABLE(of, of_dwc3_match); - -static struct platform_driver dwc3_qcom_driver = { - .probe = dwc3_qcom_probe, - .remove = dwc3_qcom_remove, - .driver = { - .name = "qcom-dwc3", - .of_match_table = of_dwc3_match, - }, -}; - -module_platform_driver(dwc3_qcom_driver); - -MODULE_ALIAS("platform:qcom-dwc3"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("DesignWare USB3 QCOM Glue Layer"); -MODULE_AUTHOR("Ivan T. Ivanov "); -- cgit v1.2.3 From 0d6c3d96678d11505f4923759af1e6c5fd260ff8 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Thu, 19 Nov 2015 15:02:16 +0800 Subject: usb: gadget: f_sourcesink: add queue depth Add queue depth for both iso and bulk transfer, with more queues, we can do performance and stress test using sourcesink, and update g_zero accordingly. Reviewed-by: Krzysztof Opasiak Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_sourcesink.c | 142 ++++++++++++++++++++++------- drivers/usb/gadget/function/g_zero.h | 6 ++ drivers/usb/gadget/legacy/zero.c | 12 +++ 3 files changed, 127 insertions(+), 33 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index 9f3ced62d916..9df4aa1ea011 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -34,13 +34,6 @@ * plus two that support control-OUT tests. If the optional "autoresume" * mode is enabled, it provides good functional coverage for the "USBCV" * test harness from USB-IF. - * - * Note that because this doesn't queue more than one request at a time, - * some other function must be used to test queueing logic. The network - * link (g_ether) is the best overall option for that, since its TX and RX - * queues are relatively independent, will receive a range of packet sizes, - * and can often be made to run out completely. Those issues are important - * when stress testing peripheral controller drivers. */ struct f_sourcesink { struct usb_function function; @@ -57,6 +50,8 @@ struct f_sourcesink { unsigned isoc_mult; unsigned isoc_maxburst; unsigned buflen; + unsigned bulk_qlen; + unsigned iso_qlen; }; static inline struct f_sourcesink *func_to_ss(struct usb_function *f) @@ -595,31 +590,33 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in, { struct usb_ep *ep; struct usb_request *req; - int i, size, status; - - for (i = 0; i < 8; i++) { - if (is_iso) { - switch (speed) { - case USB_SPEED_SUPER: - size = ss->isoc_maxpacket * - (ss->isoc_mult + 1) * - (ss->isoc_maxburst + 1); - break; - case USB_SPEED_HIGH: - size = ss->isoc_maxpacket * (ss->isoc_mult + 1); - break; - default: - size = ss->isoc_maxpacket > 1023 ? - 1023 : ss->isoc_maxpacket; - break; - } - ep = is_in ? ss->iso_in_ep : ss->iso_out_ep; - req = ss_alloc_ep_req(ep, size); - } else { - ep = is_in ? ss->in_ep : ss->out_ep; - req = ss_alloc_ep_req(ep, 0); + int i, size, qlen, status = 0; + + if (is_iso) { + switch (speed) { + case USB_SPEED_SUPER: + size = ss->isoc_maxpacket * + (ss->isoc_mult + 1) * + (ss->isoc_maxburst + 1); + break; + case USB_SPEED_HIGH: + size = ss->isoc_maxpacket * (ss->isoc_mult + 1); + break; + default: + size = ss->isoc_maxpacket > 1023 ? + 1023 : ss->isoc_maxpacket; + break; } + ep = is_in ? ss->iso_in_ep : ss->iso_out_ep; + qlen = ss->iso_qlen; + } else { + ep = is_in ? ss->in_ep : ss->out_ep; + qlen = ss->bulk_qlen; + size = 0; + } + for (i = 0; i < qlen; i++) { + req = ss_alloc_ep_req(ep, size); if (!req) return -ENOMEM; @@ -639,9 +636,6 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in, ep->name, status); free_ep_req(ep, req); } - - if (!is_iso) - break; } return status; @@ -869,6 +863,8 @@ static struct usb_function *source_sink_alloc_func( ss->isoc_mult = ss_opts->isoc_mult; ss->isoc_maxburst = ss_opts->isoc_maxburst; ss->buflen = ss_opts->bulk_buflen; + ss->bulk_qlen = ss_opts->bulk_qlen; + ss->iso_qlen = ss_opts->iso_qlen; ss->function.name = "source/sink"; ss->function.bind = sourcesink_bind; @@ -1153,6 +1149,82 @@ end: CONFIGFS_ATTR(f_ss_opts_, bulk_buflen); +static ssize_t f_ss_opts_bulk_qlen_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->bulk_qlen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_bulk_qlen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->bulk_qlen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, bulk_qlen); + +static ssize_t f_ss_opts_iso_qlen_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->iso_qlen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_iso_qlen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->iso_qlen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, iso_qlen); + static struct configfs_attribute *ss_attrs[] = { &f_ss_opts_attr_pattern, &f_ss_opts_attr_isoc_interval, @@ -1160,6 +1232,8 @@ static struct configfs_attribute *ss_attrs[] = { &f_ss_opts_attr_isoc_mult, &f_ss_opts_attr_isoc_maxburst, &f_ss_opts_attr_bulk_buflen, + &f_ss_opts_attr_bulk_qlen, + &f_ss_opts_attr_iso_qlen, NULL, }; @@ -1189,6 +1263,8 @@ static struct usb_function_instance *source_sink_alloc_inst(void) ss_opts->isoc_interval = GZERO_ISOC_INTERVAL; ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET; ss_opts->bulk_buflen = GZERO_BULK_BUFLEN; + ss_opts->bulk_qlen = GZERO_SS_BULK_QLEN; + ss_opts->iso_qlen = GZERO_SS_ISO_QLEN; config_group_init_type_name(&ss_opts->func_inst.group, "", &ss_func_type); diff --git a/drivers/usb/gadget/function/g_zero.h b/drivers/usb/gadget/function/g_zero.h index 15f180904f8a..4f61d95bf177 100644 --- a/drivers/usb/gadget/function/g_zero.h +++ b/drivers/usb/gadget/function/g_zero.h @@ -10,6 +10,8 @@ #define GZERO_QLEN 32 #define GZERO_ISOC_INTERVAL 4 #define GZERO_ISOC_MAXPACKET 1024 +#define GZERO_SS_BULK_QLEN 1 +#define GZERO_SS_ISO_QLEN 8 struct usb_zero_options { unsigned pattern; @@ -19,6 +21,8 @@ struct usb_zero_options { unsigned isoc_maxburst; unsigned bulk_buflen; unsigned qlen; + unsigned ss_bulk_qlen; + unsigned ss_iso_qlen; }; struct f_ss_opts { @@ -29,6 +33,8 @@ struct f_ss_opts { unsigned isoc_mult; unsigned isoc_maxburst; unsigned bulk_buflen; + unsigned bulk_qlen; + unsigned iso_qlen; /* * Read/write access to configfs attributes is handled by configfs. diff --git a/drivers/usb/gadget/legacy/zero.c b/drivers/usb/gadget/legacy/zero.c index 37a410056fed..0ccdcd9c64a5 100644 --- a/drivers/usb/gadget/legacy/zero.c +++ b/drivers/usb/gadget/legacy/zero.c @@ -68,6 +68,8 @@ static struct usb_zero_options gzero_options = { .isoc_maxpacket = GZERO_ISOC_MAXPACKET, .bulk_buflen = GZERO_BULK_BUFLEN, .qlen = GZERO_QLEN, + .ss_bulk_qlen = GZERO_SS_BULK_QLEN, + .ss_iso_qlen = GZERO_SS_ISO_QLEN, }; /*-------------------------------------------------------------------------*/ @@ -255,6 +257,14 @@ static struct usb_function_instance *func_inst_lb; module_param_named(qlen, gzero_options.qlen, uint, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(qlen, "depth of loopback queue"); +module_param_named(ss_bulk_qlen, gzero_options.ss_bulk_qlen, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(bulk_qlen, "depth of sourcesink queue for bulk transfer"); + +module_param_named(ss_iso_qlen, gzero_options.ss_iso_qlen, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(iso_qlen, "depth of sourcesink queue for iso transfer"); + static int zero_bind(struct usb_composite_dev *cdev) { struct f_ss_opts *ss_opts; @@ -285,6 +295,8 @@ static int zero_bind(struct usb_composite_dev *cdev) ss_opts->isoc_mult = gzero_options.isoc_mult; ss_opts->isoc_maxburst = gzero_options.isoc_maxburst; ss_opts->bulk_buflen = gzero_options.bulk_buflen; + ss_opts->bulk_qlen = gzero_options.ss_bulk_qlen; + ss_opts->iso_qlen = gzero_options.ss_iso_qlen; func_ss = usb_get_function(func_inst_ss); if (IS_ERR(func_ss)) { -- cgit v1.2.3 From 4cee4fa5de70606e9eda1f605db1a681137cdaf8 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 18 Nov 2015 17:40:23 +0800 Subject: usb: misc: usbtest: improve the description for error message Now the function of complicated_callback is not only used for iso transfer, improve the error message to reflect it. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/misc/usbtest.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 637f3f7cfce8..c1bd1eb548bb 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -1849,7 +1849,7 @@ static void complicated_callback(struct urb *urb) goto done; default: dev_err(&ctx->dev->intf->dev, - "iso resubmit err %d\n", + "resubmit err %d\n", status); /* FALLTHROUGH */ case -ENODEV: /* disconnected */ @@ -1863,7 +1863,7 @@ static void complicated_callback(struct urb *urb) if (ctx->pending == 0) { if (ctx->errors) dev_err(&ctx->dev->intf->dev, - "iso test, %lu errors out of %lu\n", + "during the test, %lu errors out of %lu\n", ctx->errors, ctx->packet_count); complete(&ctx->done); } -- cgit v1.2.3 From 3ac38d260fa5dc8ec26ee5b6f5330d726ec00065 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:37 +0100 Subject: usb: dwc2: host: ensure filling of isoc desc is correctly done Increment qtd->isoc_frame_index_last before testing it, else below check will never be true and IOC (Interrupt On Complete) bit for last frame will never be set in descriptor status. /* Set IOC for each descriptor corresponding to last frame of URB */ if (qtd->isoc_frame_index_last == qtd->urb->packet_count) dma_desc->status |= HOST_DMA_IOC; Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd_ddma.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 78993aba9335..4b0be93dac6f 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -524,14 +524,15 @@ static void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, dma_desc->status = qh->n_bytes[idx] << HOST_DMA_ISOC_NBYTES_SHIFT & HOST_DMA_ISOC_NBYTES_MASK; + qh->ntd++; + qtd->isoc_frame_index_last++; + #ifdef ISOC_URB_GIVEBACK_ASAP /* Set IOC for each descriptor corresponding to last frame of URB */ if (qtd->isoc_frame_index_last == qtd->urb->packet_count) dma_desc->status |= HOST_DMA_IOC; #endif - qh->ntd++; - qtd->isoc_frame_index_last++; } static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, -- cgit v1.2.3 From dde4c1bf5df0f852e497e5644d3578885b969fdb Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:38 +0100 Subject: usb: dwc2: host: set active bit in isochronous descriptors Active bit must be enabled in all scheduled descriptors. Else transfer never start. Remove previous code which was not correctly configuring descriptors. Active bit was set before calling dwc2_fill_host_isoc_dma_desc() which is erasing dma_desc->status. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd_ddma.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 4b0be93dac6f..98d862726b5a 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -524,6 +524,9 @@ static void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, dma_desc->status = qh->n_bytes[idx] << HOST_DMA_ISOC_NBYTES_SHIFT & HOST_DMA_ISOC_NBYTES_MASK; + /* Set active bit */ + dma_desc->status |= HOST_DMA_A; + qh->ntd++; qtd->isoc_frame_index_last++; @@ -559,8 +562,6 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { while (qh->ntd < ntd_max && qtd->isoc_frame_index_last < qtd->urb->packet_count) { - if (n_desc > 1) - qh->desc_list[n_desc - 1].status |= HOST_DMA_A; dwc2_fill_host_isoc_dma_desc(hsotg, qtd, qh, max_xfer_size, idx); idx = dwc2_desclist_idx_inc(idx, inc, qh->dev_speed); @@ -606,12 +607,6 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, qh->desc_list[idx].status |= HOST_DMA_IOC; #endif - - if (n_desc) { - qh->desc_list[n_desc - 1].status |= HOST_DMA_A; - if (n_desc > 1) - qh->desc_list[0].status |= HOST_DMA_A; - } } static void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, -- cgit v1.2.3 From c503b38153852d88774b54ae17f7723f68c6dc33 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:39 +0100 Subject: usb: dwc2: host: rework isochronous halt path When a channel is halted because of urb dequeue during transfer completion, no other qtds must be scheduled until halt is done. Moreover, all in progress qtds must be given back. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd_ddma.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 98d862726b5a..5f7265637334 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -1161,6 +1161,21 @@ void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, /* Release the channel if halted or session completed */ if (halt_status != DWC2_HC_XFER_COMPLETE || list_empty(&qh->qtd_list)) { + struct dwc2_qtd *qtd, *qtd_tmp; + + /* + * Kill all remainings QTDs since channel has been + * halted. + */ + list_for_each_entry_safe(qtd, qtd_tmp, + &qh->qtd_list, + qtd_list_entry) { + dwc2_host_complete(hsotg, qtd, + -ECONNRESET); + dwc2_hcd_qtd_unlink_and_free(hsotg, + qtd, qh); + } + /* Halt the channel if session completed */ if (halt_status == DWC2_HC_XFER_COMPLETE) dwc2_hc_halt(hsotg, chan, halt_status); @@ -1170,7 +1185,12 @@ void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, /* Keep in assigned schedule to continue transfer */ list_move(&qh->qh_list_entry, &hsotg->periodic_sched_assigned); - continue_isoc_xfer = 1; + /* + * If channel has been halted during giveback of urb + * then prevent any new scheduling. + */ + if (!chan->halt_status) + continue_isoc_xfer = 1; } /* * Todo: Consider the case when period exceeds FrameList size. -- cgit v1.2.3 From 26a19ea699060fded98257e65b0ae5272a5ea1da Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:40 +0100 Subject: usb: dwc2: host: fix use of qtd after free in desc dma mode When completing non isoc xfer, dwc2_complete_non_isoc_xfer_ddma() is relying on qtd->n_desc to process the corresponding number of descriptors. During the processing of these descriptors, qtd could be unlinked and freed if xfer is done and urb is no more in progress. In this case, dwc2_complete_non_isoc_xfer_ddma() will read again qtd->n_desc whereas qtd has been freed. This will lead to unpredictable results since qtd->n_desc is no more valid value. To avoid this error, return a result != 0 in dwc2_process_non_isoc_desc(), so that dwc2_complete_non_isoc_xfer_ddma() stops desc processing. This has been seen with Slub debug enabled. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd_ddma.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 5f7265637334..4801e693353a 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -1033,7 +1033,10 @@ static int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, failed = dwc2_update_non_isoc_urb_state_ddma(hsotg, chan, qtd, dma_desc, halt_status, n_bytes, xfer_done); - if (failed || (*xfer_done && urb->status != -EINPROGRESS)) { + if (*xfer_done && urb->status != -EINPROGRESS) + failed = 1; + + if (failed) { dwc2_host_complete(hsotg, qtd, urb->status); dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); dev_vdbg(hsotg->dev, "failed=%1x xfer_done=%1x status=%08x\n", -- cgit v1.2.3 From 2b046bc5aaefd4aba7195e6a73afe14f7f786692 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:41 +0100 Subject: usb: dwc2: host: spinlock release channel Prevent dwc2 driver from accessing channel while it frees it. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd_ddma.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 4801e693353a..a76a58c35fea 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -360,6 +360,8 @@ err0: */ void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { + unsigned long flags; + dwc2_desc_list_free(hsotg, qh); /* @@ -369,8 +371,10 @@ void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) * when it comes here from endpoint disable routine * channel remains assigned. */ + spin_lock_irqsave(&hsotg->lock, flags); if (qh->channel) dwc2_release_channel_ddma(hsotg, qh); + spin_unlock_irqrestore(&hsotg->lock, flags); if ((qh->ep_type == USB_ENDPOINT_XFER_ISOC || qh->ep_type == USB_ENDPOINT_XFER_INT) && -- cgit v1.2.3 From b9392d9920fdce50abbe4af758cd1a24b922c81c Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:42 +0100 Subject: usb: dwc2: host: add function to compare frame index This function allow comparing frame index used for descriptor list which has 64 entries. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index f105bada2fd1..a19837f655c9 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -534,6 +534,19 @@ static inline bool dbg_perio(void) { return false; } /* Packet size for any kind of endpoint descriptor */ #define dwc2_max_packet(wmaxpacketsize) ((wmaxpacketsize) & 0x07ff) +/* + * Returns true if frame1 index is greater than frame2 index. The comparison + * is done modulo FRLISTEN_64_SIZE. This accounts for the rollover of the + * frame number when the max index frame number is reached. + */ +static inline bool dwc2_frame_idx_num_gt(u16 fr_idx1, u16 fr_idx2) +{ + u16 diff = fr_idx1 - fr_idx2; + u16 sign = diff & (FRLISTEN_64_SIZE >> 1); + + return diff && !sign; +} + /* * Returns true if frame1 is less than or equal to frame2. The comparison is * done modulo HFNUM_MAX_FRNUM. This accounts for the rollover of the -- cgit v1.2.3 From c17b337c1ea4c681595531912585a94f4bd7f8e7 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:43 +0100 Subject: usb: dwc2: host: program descriptor for next frame Isochronous descriptor is currently programmed for the frame after the last descriptor was programmed. If the last descriptor frame underrun, then current descriptor must take this into account and must be programmed on the current frame + 1. This overrun usually happens when system is loaded and dwc2 can't init descriptor list in time. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd.h | 2 ++ drivers/usb/dwc2/hcd_ddma.c | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index a19837f655c9..2e5e9d9a0016 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -340,6 +340,8 @@ struct dwc2_qtd { u8 isoc_split_pos; u16 isoc_frame_index; u16 isoc_split_offset; + u16 isoc_td_last; + u16 isoc_td_first; u32 ssplit_out_xfer_count; u8 error_count; u8 n_desc; diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index a76a58c35fea..9635d8d4bba4 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -547,11 +547,32 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, { struct dwc2_qtd *qtd; u32 max_xfer_size; - u16 idx, inc, n_desc, ntd_max = 0; + u16 idx, inc, n_desc = 0, ntd_max = 0; + u16 cur_idx; + u16 next_idx; idx = qh->td_last; inc = qh->interval; - n_desc = 0; + hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + cur_idx = dwc2_frame_list_idx(hsotg->frame_number); + next_idx = dwc2_desclist_idx_inc(qh->td_last, inc, qh->dev_speed); + + /* + * Ensure current frame number didn't overstep last scheduled + * descriptor. If it happens, the only way to recover is to move + * qh->td_last to current frame number + 1. + * So that next isoc descriptor will be scheduled on frame number + 1 + * and not on a past frame. + */ + if (dwc2_frame_idx_num_gt(cur_idx, next_idx) || (cur_idx == next_idx)) { + if (inc < 32) { + dev_vdbg(hsotg->dev, + "current frame number overstep last descriptor\n"); + qh->td_last = dwc2_desclist_idx_inc(cur_idx, inc, + qh->dev_speed); + idx = qh->td_last; + } + } if (qh->interval) { ntd_max = (dwc2_max_desc_num(qh) + qh->interval - 1) / @@ -564,6 +585,12 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, MAX_ISOC_XFER_SIZE_HS : MAX_ISOC_XFER_SIZE_FS; list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { + if (qtd->in_process && + qtd->isoc_frame_index_last == + qtd->urb->packet_count) + continue; + + qtd->isoc_td_first = idx; while (qh->ntd < ntd_max && qtd->isoc_frame_index_last < qtd->urb->packet_count) { dwc2_fill_host_isoc_dma_desc(hsotg, qtd, qh, @@ -571,6 +598,7 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, idx = dwc2_desclist_idx_inc(idx, inc, qh->dev_speed); n_desc++; } + qtd->isoc_td_last = idx; qtd->in_process = 1; } -- cgit v1.2.3 From 3f808bdae75eaf464b1b2710894950772a3784f8 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:44 +0100 Subject: usb: dwc2: host: always increment available host channel during release When releasing a channel, increment hsotg->available_host_channels even in case a periodic channel is released. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd_ddma.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 9635d8d4bba4..edccac662d74 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -278,6 +278,7 @@ static void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, hsotg->non_periodic_channels--; } else { dwc2_update_frame_list(hsotg, qh, 0); + hsotg->available_host_channels++; } /* -- cgit v1.2.3 From 762d3a1a9cd7438a8453e005ee5b2bab3203d9c3 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Thu, 5 Nov 2015 09:41:45 +0100 Subject: usb: dwc2: host: process all completed urbs Process all completed urbs, if more urbs are complete by the time driver processes completion interrupt. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd_ddma.c | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index edccac662d74..f98c7e91f6bc 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -940,17 +940,51 @@ static void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { if (!qtd->in_process) break; + + /* + * Ensure idx corresponds to descriptor where first urb of this + * qtd was added. In fact, during isoc desc init, dwc2 may skip + * an index if current frame number is already over this index. + */ + if (idx != qtd->isoc_td_first) { + dev_vdbg(hsotg->dev, + "try to complete %d instead of %d\n", + idx, qtd->isoc_td_first); + idx = qtd->isoc_td_first; + } + do { + struct dwc2_qtd *qtd_next; + u16 cur_idx; + rc = dwc2_cmpl_host_isoc_dma_desc(hsotg, chan, qtd, qh, idx); if (rc < 0) return; idx = dwc2_desclist_idx_inc(idx, qh->interval, chan->speed); - if (rc == DWC2_CMPL_STOP) - goto stop_scan; + if (!rc) + continue; + if (rc == DWC2_CMPL_DONE) break; + + /* rc == DWC2_CMPL_STOP */ + + if (qh->interval >= 32) + goto stop_scan; + + qh->td_first = idx; + cur_idx = dwc2_frame_list_idx(hsotg->frame_number); + qtd_next = list_first_entry(&qh->qtd_list, + struct dwc2_qtd, + qtd_list_entry); + if (dwc2_frame_idx_num_gt(cur_idx, + qtd_next->isoc_td_last)) + break; + + goto stop_scan; + } while (idx != qh->td_first); } -- cgit v1.2.3 From 51f141a97a1406bb0b59d490e837a39ccb7c3999 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Wed, 18 Nov 2015 14:34:09 +0900 Subject: usb: renesas_usbhs: Modify pipe configuration The current code has info->bufnmb_last to calculate the BUFNMB bits of PIPEBUF register. However, since the bufnmb_last is initialized in the usbhs_pipe_init() only, this driver is possible to set unexpected value to the register if usb_ep_{enable,disable}() are called many times. So, this patch modifies the pipe configuration via struct renesas_usbhs_driver_param to simplify the code. Also this patch changes: - a double buffer configuration - isochronous buffer size from 512 to 1024 Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- drivers/usb/renesas_usbhs/common.c | 69 +++++++++++---------- drivers/usb/renesas_usbhs/mod_host.c | 11 ++-- drivers/usb/renesas_usbhs/pipe.c | 112 ++++++++--------------------------- drivers/usb/renesas_usbhs/pipe.h | 1 - include/linux/usb/renesas_usbhs.h | 18 +++++- 5 files changed, 82 insertions(+), 129 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index d82fa36c3465..7ccc2fe4f6ec 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -302,37 +302,37 @@ static void usbhsc_set_buswait(struct usbhs_priv *priv) */ /* commonly used on old SH-Mobile SoCs */ -static u32 usbhsc_default_pipe_type[] = { - USB_ENDPOINT_XFER_CONTROL, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, +static struct renesas_usbhs_driver_pipe_config usbhsc_default_pipe[] = { + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_CONTROL, 64, 0x00, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x08, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x18, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x28, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x38, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x48, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x04, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x05, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x06, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x07, false), }; /* commonly used on newer SH-Mobile and R-Car SoCs */ -static u32 usbhsc_new_pipe_type[] = { - USB_ENDPOINT_XFER_CONTROL, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_ISOC, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_INT, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, - USB_ENDPOINT_XFER_BULK, +static struct renesas_usbhs_driver_pipe_config usbhsc_new_pipe[] = { + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_CONTROL, 64, 0x00, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x08, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x28, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x48, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x58, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x68, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x04, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x05, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x06, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x78, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x88, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x98, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xa8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xb8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xc8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xd8, true), }; /* @@ -564,10 +564,9 @@ static int usbhs_probe(struct platform_device *pdev) switch (priv->dparam.type) { case USBHS_TYPE_RCAR_GEN2: priv->pfunc = usbhs_rcar2_ops; - if (!priv->dparam.pipe_type) { - priv->dparam.pipe_type = usbhsc_new_pipe_type; - priv->dparam.pipe_size = - ARRAY_SIZE(usbhsc_new_pipe_type); + if (!priv->dparam.pipe_configs) { + priv->dparam.pipe_configs = usbhsc_new_pipe; + priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_new_pipe); } break; default: @@ -586,9 +585,9 @@ static int usbhs_probe(struct platform_device *pdev) dfunc->notify_hotplug = usbhsc_drvcllbck_notify_hotplug; /* set default param if platform doesn't have */ - if (!priv->dparam.pipe_type) { - priv->dparam.pipe_type = usbhsc_default_pipe_type; - priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_default_pipe_type); + if (!priv->dparam.pipe_configs) { + priv->dparam.pipe_configs = usbhsc_default_pipe; + priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_default_pipe); } if (!priv->dparam.pio_dma_border) priv->dparam.pio_dma_border = 64; /* 64byte */ diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c index bd050359926c..1a8e4c45c4c5 100644 --- a/drivers/usb/renesas_usbhs/mod_host.c +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -1414,7 +1414,8 @@ static void usbhsh_pipe_init_for_host(struct usbhs_priv *priv) { struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); struct usbhs_pipe *pipe; - u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); int pipe_size = usbhs_get_dparam(priv, pipe_size); int old_type, dir_in, i; @@ -1442,15 +1443,15 @@ static void usbhsh_pipe_init_for_host(struct usbhs_priv *priv) * USB_ENDPOINT_XFER_BULK -> dir in * ... */ - dir_in = (pipe_type[i] == old_type); - old_type = pipe_type[i]; + dir_in = (pipe_configs[i].type == old_type); + old_type = pipe_configs[i].type; - if (USB_ENDPOINT_XFER_CONTROL == pipe_type[i]) { + if (USB_ENDPOINT_XFER_CONTROL == pipe_configs[i].type) { pipe = usbhs_dcp_malloc(priv); usbhsh_hpriv_to_dcp(hpriv) = pipe; } else { pipe = usbhs_pipe_malloc(priv, - pipe_type[i], + pipe_configs[i].type, dir_in); } diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c index 4f9c3356127a..0e95d2925dc5 100644 --- a/drivers/usb/renesas_usbhs/pipe.c +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -44,6 +44,15 @@ char *usbhs_pipe_name(struct usbhs_pipe *pipe) return usbhsp_pipe_name[usbhs_pipe_type(pipe)]; } +static struct renesas_usbhs_driver_pipe_config +*usbhsp_get_pipe_config(struct usbhs_priv *priv, int pipe_num) +{ + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); + + return &pipe_configs[pipe_num]; +} + /* * DCPCTR/PIPEnCTR functions */ @@ -384,18 +393,6 @@ void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len) /* * pipe setup */ -static int usbhsp_possible_double_buffer(struct usbhs_pipe *pipe) -{ - /* - * only ISO / BULK pipe can use double buffer - */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK) || - usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC)) - return 1; - - return 0; -} - static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, int is_host, int dir_in) @@ -412,7 +409,6 @@ static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, [USB_ENDPOINT_XFER_INT] = TYPE_INT, [USB_ENDPOINT_XFER_ISOC] = TYPE_ISO, }; - int is_double = usbhsp_possible_double_buffer(pipe); if (usbhs_pipe_is_dcp(pipe)) return -EINVAL; @@ -434,10 +430,7 @@ static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) bfre = 0; /* FIXME */ - /* DBLB */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || - usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) - dblb = (is_double) ? DBLB : 0; + /* DBLB: see usbhs_pipe_config_update() */ /* CNTMD */ if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) @@ -473,13 +466,13 @@ static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) { struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); - struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); struct device *dev = usbhs_priv_to_dev(priv); int pipe_num = usbhs_pipe_number(pipe); - int is_double = usbhsp_possible_double_buffer(pipe); u16 buff_size; u16 bufnmb; u16 bufnmb_cnt; + struct renesas_usbhs_driver_pipe_config *pipe_config = + usbhsp_get_pipe_config(priv, pipe_num); /* * PIPEBUF @@ -489,56 +482,13 @@ static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) * - "Features" - "Pipe configuration" * - "Operation" - "FIFO Buffer Memory" * - "Operation" - "Pipe Control" - * - * ex) if pipe6 - pipe9 are USB_ENDPOINT_XFER_INT (SH7724) - * - * BUFNMB: PIPE - * 0: pipe0 (DCP 256byte) - * 1: - - * 2: - - * 3: - - * 4: pipe6 (INT 64byte) - * 5: pipe7 (INT 64byte) - * 6: pipe8 (INT 64byte) - * 7: pipe9 (INT 64byte) - * 8 - xx: free (for BULK, ISOC) */ - - /* - * FIXME - * - * it doesn't have good buffer allocator - * - * DCP : 256 byte - * BULK: 512 byte - * INT : 64 byte - * ISOC: 512 byte - */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_CONTROL)) - buff_size = 256; - else if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) - buff_size = 64; - else - buff_size = 512; + buff_size = pipe_config->bufsize; + bufnmb = pipe_config->bufnum; /* change buff_size to register value */ bufnmb_cnt = (buff_size / 64) - 1; - /* BUFNMB has been reserved for INT pipe - * see above */ - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) { - bufnmb = pipe_num - 2; - } else { - bufnmb = info->bufnmb_last; - info->bufnmb_last += bufnmb_cnt + 1; - - /* - * double buffer - */ - if (is_double) - info->bufnmb_last += bufnmb_cnt + 1; - } - dev_dbg(dev, "pipe : %d : buff_size 0x%x: bufnmb 0x%x\n", pipe_num, buff_size, bufnmb); @@ -549,8 +499,13 @@ static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, u16 epnum, u16 maxp) { + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + int pipe_num = usbhs_pipe_number(pipe); + struct renesas_usbhs_driver_pipe_config *pipe_config = + usbhsp_get_pipe_config(priv, pipe_num); + u16 dblb = pipe_config->double_buf ? DBLB : 0; + if (devsel > 0xA) { - struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); struct device *dev = usbhs_priv_to_dev(priv); dev_err(dev, "devsel error %d\n", devsel); @@ -568,7 +523,7 @@ void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, maxp); if (!usbhs_pipe_is_dcp(pipe)) - usbhsp_pipe_cfg_set(pipe, 0x000F, epnum); + usbhsp_pipe_cfg_set(pipe, 0x000F | DBLB, epnum | dblb); } /* @@ -708,23 +663,7 @@ void usbhs_pipe_init(struct usbhs_priv *priv, struct usbhs_pipe *pipe; int i; - /* - * FIXME - * - * driver needs good allocator. - * - * find first free buffer area (BULK, ISOC) - * (DCP, INT area is fixed) - * - * buffer number 0 - 3 have been reserved for DCP - * see - * usbhsp_to_bufnmb - */ - info->bufnmb_last = 4; usbhs_for_each_pipe_with_dcp(pipe, priv, i) { - if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) - info->bufnmb_last++; - usbhsp_flags_init(pipe); pipe->fifo = NULL; pipe->mod_private = NULL; @@ -851,12 +790,13 @@ int usbhs_pipe_probe(struct usbhs_priv *priv) struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); struct usbhs_pipe *pipe; struct device *dev = usbhs_priv_to_dev(priv); - u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); int pipe_size = usbhs_get_dparam(priv, pipe_size); int i; /* This driver expects 1st pipe is DCP */ - if (pipe_type[0] != USB_ENDPOINT_XFER_CONTROL) { + if (pipe_configs[0].type != USB_ENDPOINT_XFER_CONTROL) { dev_err(dev, "1st PIPE is not DCP\n"); return -EINVAL; } @@ -876,10 +816,10 @@ int usbhs_pipe_probe(struct usbhs_priv *priv) pipe->priv = priv; usbhs_pipe_type(pipe) = - pipe_type[i] & USB_ENDPOINT_XFERTYPE_MASK; + pipe_configs[i].type & USB_ENDPOINT_XFERTYPE_MASK; dev_dbg(dev, "pipe %x\t: %s\n", - i, usbhsp_pipe_name[pipe_type[i]]); + i, usbhsp_pipe_name[pipe_configs[i].type]); } return 0; diff --git a/drivers/usb/renesas_usbhs/pipe.h b/drivers/usb/renesas_usbhs/pipe.h index b0bc7b603016..3212ab51e844 100644 --- a/drivers/usb/renesas_usbhs/pipe.h +++ b/drivers/usb/renesas_usbhs/pipe.h @@ -46,7 +46,6 @@ struct usbhs_pipe { struct usbhs_pipe_info { struct usbhs_pipe *pipe; int size; /* array size of "pipe" */ - int bufnmb_last; /* FIXME : driver needs good allocator */ int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map); }; diff --git a/include/linux/usb/renesas_usbhs.h b/include/linux/usb/renesas_usbhs.h index bfb74723f151..4db191fe8c2c 100644 --- a/include/linux/usb/renesas_usbhs.h +++ b/include/linux/usb/renesas_usbhs.h @@ -105,12 +105,26 @@ struct renesas_usbhs_platform_callback { * some register needs USB chip specific parameters. * This struct show it to driver */ + +struct renesas_usbhs_driver_pipe_config { + u8 type; /* USB_ENDPOINT_XFER_xxx */ + u16 bufsize; + u8 bufnum; + bool double_buf; +}; +#define RENESAS_USBHS_PIPE(_type, _size, _num, _double_buf) { \ + .type = (_type), \ + .bufsize = (_size), \ + .bufnum = (_num), \ + .double_buf = (_double_buf), \ + } + struct renesas_usbhs_driver_param { /* * pipe settings */ - u32 *pipe_type; /* array of USB_ENDPOINT_XFER_xxx (from ep0) */ - int pipe_size; /* pipe_type array size */ + struct renesas_usbhs_driver_pipe_config *pipe_configs; + int pipe_size; /* pipe_configs array size */ /* * option: -- cgit v1.2.3 From 64c5f48b100e92f189a32b6d660e3329681ec9b5 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Wed, 18 Nov 2015 14:34:10 +0900 Subject: usb: renesas_usbhs: Modify ep.caps.type_xxx and usb_ep_maxpacket_limit() This patch modifies the ep.caps.type_{iso,bulk,int} setting and the second argument of usb_ep_maxpacket_limit() using the dparam.pipe_configs. In the previous code, all the type_{iso,bulk,int} were set to true. However, to avoid waste time for finding suitable pipe in usb_ep_enable(), this driver should set correct type. Also the second argument of usb_ep_maxpacket_limit() was set to 512 even if the pipe is isochronous or interrupt. So, this driver could not bind a gadget driver like the g_audio driver. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- drivers/usb/renesas_usbhs/mod_gadget.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c index 8f7a78e70975..657f9672ceba 100644 --- a/drivers/usb/renesas_usbhs/mod_gadget.c +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -1042,6 +1042,8 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv) struct usbhsg_gpriv *gpriv; struct usbhsg_uep *uep; struct device *dev = usbhs_priv_to_dev(priv); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); int pipe_size = usbhs_get_dparam(priv, pipe_size); int i; int ret; @@ -1111,13 +1113,16 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv) gpriv->gadget.ep0 = &uep->ep; usb_ep_set_maxpacket_limit(&uep->ep, 64); uep->ep.caps.type_control = true; - } - /* init normal pipe */ - else { - usb_ep_set_maxpacket_limit(&uep->ep, 512); - uep->ep.caps.type_iso = true; - uep->ep.caps.type_bulk = true; - uep->ep.caps.type_int = true; + } else { + /* init normal pipe */ + if (pipe_configs[i].type == USB_ENDPOINT_XFER_ISOC) + uep->ep.caps.type_iso = true; + if (pipe_configs[i].type == USB_ENDPOINT_XFER_BULK) + uep->ep.caps.type_bulk = true; + if (pipe_configs[i].type == USB_ENDPOINT_XFER_INT) + uep->ep.caps.type_int = true; + usb_ep_set_maxpacket_limit(&uep->ep, + pipe_configs[i].bufsize); list_add_tail(&uep->ep.ep_list, &gpriv->gadget.ep_list); } uep->ep.caps.dir_in = true; -- cgit v1.2.3 From bc1d3cdc9c3cbbd9040da8c53237e177c38048b0 Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Tue, 10 Nov 2015 17:52:04 +0000 Subject: usb: gadget: f_midi: remove duplicated code This code is duplicated from f_midi_start_ep(midi, f, midi->out_ep). Reviewed-by: Robert Baldyga Signed-off-by: Felipe F. Tonello Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_midi.c | 19 ------------------- 1 file changed, 19 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 898a570319f1..695cf75428e3 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -324,7 +324,6 @@ static int f_midi_start_ep(struct f_midi *midi, static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_midi *midi = func_to_midi(f); - struct usb_composite_dev *cdev = f->config->cdev; unsigned i; int err; @@ -340,24 +339,6 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (err) return err; - usb_ep_disable(midi->out_ep); - - err = config_ep_by_speed(midi->gadget, f, midi->out_ep); - if (err) { - ERROR(cdev, "can't configure %s: %d\n", - midi->out_ep->name, err); - return err; - } - - err = usb_ep_enable(midi->out_ep); - if (err) { - ERROR(cdev, "can't start %s: %d\n", - midi->out_ep->name, err); - return err; - } - - midi->out_ep->driver_data = midi; - /* allocate a bunch of read buffers and queue them all at once. */ for (i = 0; i < midi->qlen && err == 0; i++) { struct usb_request *req = -- cgit v1.2.3 From 079fe5a6da616891cca1a26e803e1df2a87e9ae5 Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Tue, 10 Nov 2015 17:52:05 +0000 Subject: usb: gadget: define free_ep_req as universal function This function is shared between gadget functions, so this avoid unnecessary duplicated code and potentially avoid memory leaks. Reviewed-by: Robert Baldyga Signed-off-by: Felipe F. Tonello Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_midi.c | 6 ------ drivers/usb/gadget/function/f_sourcesink.c | 6 ------ drivers/usb/gadget/function/g_zero.h | 1 - drivers/usb/gadget/u_f.c | 1 - drivers/usb/gadget/u_f.h | 10 ++++++++-- 5 files changed, 8 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 695cf75428e3..29bfca1a47bb 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -201,12 +201,6 @@ static inline struct usb_request *midi_alloc_ep_req(struct usb_ep *ep, return alloc_ep_req(ep, length, length); } -static void free_ep_req(struct usb_ep *ep, struct usb_request *req) -{ - kfree(req->buf); - usb_ep_free_request(ep, req); -} - static const uint8_t f_midi_cin_length[] = { 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 }; diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index 9df4aa1ea011..12e3eb4ee140 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -298,12 +298,6 @@ static inline struct usb_request *ss_alloc_ep_req(struct usb_ep *ep, int len) return alloc_ep_req(ep, len, ss->buflen); } -void free_ep_req(struct usb_ep *ep, struct usb_request *req) -{ - kfree(req->buf); - usb_ep_free_request(ep, req); -} - static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep) { int value; diff --git a/drivers/usb/gadget/function/g_zero.h b/drivers/usb/gadget/function/g_zero.h index 4f61d95bf177..492924d0d599 100644 --- a/drivers/usb/gadget/function/g_zero.h +++ b/drivers/usb/gadget/function/g_zero.h @@ -65,7 +65,6 @@ void lb_modexit(void); int lb_modinit(void); /* common utilities */ -void free_ep_req(struct usb_ep *ep, struct usb_request *req); void disable_endpoints(struct usb_composite_dev *cdev, struct usb_ep *in, struct usb_ep *out, struct usb_ep *iso_in, struct usb_ep *iso_out); diff --git a/drivers/usb/gadget/u_f.c b/drivers/usb/gadget/u_f.c index c6276f0268ae..4bc7eea8bfc8 100644 --- a/drivers/usb/gadget/u_f.c +++ b/drivers/usb/gadget/u_f.c @@ -11,7 +11,6 @@ * published by the Free Software Foundation. */ -#include #include "u_f.h" struct usb_request *alloc_ep_req(struct usb_ep *ep, int len, int default_len) diff --git a/drivers/usb/gadget/u_f.h b/drivers/usb/gadget/u_f.h index 1d5f0eb68552..4247cc098a89 100644 --- a/drivers/usb/gadget/u_f.h +++ b/drivers/usb/gadget/u_f.h @@ -16,6 +16,8 @@ #ifndef __U_F_H__ #define __U_F_H__ +#include + /* Variable Length Array Macros **********************************************/ #define vla_group(groupname) size_t groupname##__next = 0 #define vla_group_size(groupname) groupname##__next @@ -45,8 +47,12 @@ struct usb_ep; struct usb_request; +/* Requests allocated via alloc_ep_req() must be freed by free_ep_req(). */ struct usb_request *alloc_ep_req(struct usb_ep *ep, int len, int default_len); +static inline void free_ep_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} #endif /* __U_F_H__ */ - - -- cgit v1.2.3 From 38660ac7beb53b5f7bdb1d325a93c70cb7ba429f Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Tue, 10 Nov 2015 17:52:07 +0000 Subject: usb: gadget: gmidi: Cleanup legacy code Remove unnecessary headers and variables. Reviewed-by: Robert Baldyga Signed-off-by: Felipe F. Tonello Signed-off-by: Felipe Balbi --- drivers/usb/gadget/legacy/gmidi.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/legacy/gmidi.c b/drivers/usb/gadget/legacy/gmidi.c index 8a18348ae86e..be8e91d68e14 100644 --- a/drivers/usb/gadget/legacy/gmidi.c +++ b/drivers/usb/gadget/legacy/gmidi.c @@ -21,19 +21,12 @@ /* #define VERBOSE_DEBUG */ #include -#include #include -#include -#include #include -#include -#include #include #include -#include -#include #include "u_midi.h" @@ -42,7 +35,6 @@ MODULE_AUTHOR("Ben Williamson"); MODULE_LICENSE("GPL v2"); -static const char shortname[] = "g_midi"; static const char longname[] = "MIDI Gadget"; USB_GADGET_COMPOSITE_OPTIONS(); -- cgit v1.2.3 From 75f5c434c30b22a48a1d4a0f827473dc29036d78 Mon Sep 17 00:00:00 2001 From: Deepa Dinamani Date: Wed, 4 Nov 2015 15:29:11 -0800 Subject: usb: misc: usbtest: Remove timeval usage timeval is deprecated and not y2038 safe. Its size also changes according to 32 bit/ 64 bit compilation. Replace it with 32 and 64 bit versions of its individual fields, giving two ioctls with different code values. The two ioctls are necessary to maintain the 32 bit and 64 bit userspace compatibility with a 64/32 bit kernel. Change unsigned to __u32 types for a definitive userspace interface. This is in accordance with the psABI that the unsigned type is always 32 bits. Also use motonic timer instead of real time to ensure positive delta values. Refactor usbtest_ioctl for readability to isolate the handling of the testing timing measurement. The official testusb userspace tool can be changed in a separate patch to reflect the __u32 changes as well. It can use the usbtest_param_32 struct, since 32 bit seconds is long enough for test durations. Reviewed-by: Arnd Bergmann Signed-off-by: Deepa Dinamani Signed-off-by: Felipe Balbi --- drivers/usb/misc/usbtest.c | 229 +++++++++++++++++++++++++++++---------------- 1 file changed, 147 insertions(+), 82 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index c1bd1eb548bb..92fdb6e9faff 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -22,18 +22,42 @@ static void complicated_callback(struct urb *urb); /*-------------------------------------------------------------------------*/ /* FIXME make these public somewhere; usbdevfs.h? */ -struct usbtest_param { + +/* Parameter for usbtest driver. */ +struct usbtest_param_32 { /* inputs */ - unsigned test_num; /* 0..(TEST_CASES-1) */ - unsigned iterations; - unsigned length; - unsigned vary; - unsigned sglen; + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; /* outputs */ - struct timeval duration; + __s32 duration_sec; + __s32 duration_usec; }; -#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param) + +/* + * Compat parameter to the usbtest driver. + * This supports older user space binaries compiled with 64 bit compiler. + */ +struct usbtest_param_64 { + /* inputs */ + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; + + /* outputs */ + __s64 duration_sec; + __s64 duration_usec; +}; + +/* IOCTL interface to the driver. */ +#define USBTEST_REQUEST_32 _IOWR('U', 100, struct usbtest_param_32) +/* COMPAT IOCTL interface to the driver. */ +#define USBTEST_REQUEST_64 _IOWR('U', 100, struct usbtest_param_64) /*-------------------------------------------------------------------------*/ @@ -1030,7 +1054,7 @@ struct ctrl_ctx { unsigned pending; int status; struct urb **urb; - struct usbtest_param *param; + struct usbtest_param_32 *param; int last; }; @@ -1155,7 +1179,7 @@ error: } static int -test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param *param) +test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param) { struct usb_device *udev = testdev_to_usbdev(dev); struct urb **urb; @@ -1930,7 +1954,7 @@ static struct urb *iso_alloc_urb( } static int -test_queue(struct usbtest_dev *dev, struct usbtest_param *param, +test_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param, int pipe, struct usb_endpoint_descriptor *desc, unsigned offset) { struct transfer_context context; @@ -2049,81 +2073,20 @@ static int test_unaligned_bulk( return retval; } -/*-------------------------------------------------------------------------*/ - -/* We only have this one interface to user space, through usbfs. - * User mode code can scan usbfs to find N different devices (maybe on - * different busses) to use when testing, and allocate one thread per - * test. So discovery is simplified, and we have no device naming issues. - * - * Don't use these only as stress/load tests. Use them along with with - * other USB bus activity: plugging, unplugging, mousing, mp3 playback, - * video capture, and so on. Run different tests at different times, in - * different sequences. Nothing here should interact with other devices, - * except indirectly by consuming USB bandwidth and CPU resources for test - * threads and request completion. But the only way to know that for sure - * is to test when HC queues are in use by many devices. - * - * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), - * it locks out usbcore in certain code paths. Notably, if you disconnect - * the device-under-test, hub_wq will wait block forever waiting for the - * ioctl to complete ... so that usb_disconnect() can abort the pending - * urbs and then call usbtest_disconnect(). To abort a test, you're best - * off just killing the userspace task and waiting for it to exit. - */ - +/* Run tests. */ static int -usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +usbtest_do_ioctl(struct usb_interface *intf, struct usbtest_param_32 *param) { struct usbtest_dev *dev = usb_get_intfdata(intf); struct usb_device *udev = testdev_to_usbdev(dev); - struct usbtest_param *param = buf; - int retval = -EOPNOTSUPP; struct urb *urb; struct scatterlist *sg; struct usb_sg_request req; - struct timeval start; unsigned i; - - /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ - - pattern = mod_pattern; - - if (code != USBTEST_REQUEST) - return -EOPNOTSUPP; + int retval = -EOPNOTSUPP; if (param->iterations <= 0) return -EINVAL; - - if (param->sglen > MAX_SGLEN) - return -EINVAL; - - if (mutex_lock_interruptible(&dev->lock)) - return -ERESTARTSYS; - - /* FIXME: What if a system sleep starts while a test is running? */ - - /* some devices, like ez-usb default devices, need a non-default - * altsetting to have any active endpoints. some tests change - * altsettings; force a default so most tests don't need to check. - */ - if (dev->info->alt >= 0) { - int res; - - if (intf->altsetting->desc.bInterfaceNumber) { - mutex_unlock(&dev->lock); - return -ENODEV; - } - res = set_altsetting(dev, dev->info->alt); - if (res) { - dev_err(&intf->dev, - "set altsetting to %d failed, %d\n", - dev->info->alt, res); - mutex_unlock(&dev->lock); - return res; - } - } - /* * Just a bunch of test cases that every HCD is expected to handle. * @@ -2133,7 +2096,6 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) * FIXME add more tests! cancel requests, verify the data, control * queueing, concurrent read+write threads, and so on. */ - do_gettimeofday(&start); switch (param->test_num) { case 0: @@ -2548,13 +2510,116 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) dev->in_pipe, NULL, 0); break; } - do_gettimeofday(¶m->duration); - param->duration.tv_sec -= start.tv_sec; - param->duration.tv_usec -= start.tv_usec; - if (param->duration.tv_usec < 0) { - param->duration.tv_usec += 1000 * 1000; - param->duration.tv_sec -= 1; + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* We only have this one interface to user space, through usbfs. + * User mode code can scan usbfs to find N different devices (maybe on + * different busses) to use when testing, and allocate one thread per + * test. So discovery is simplified, and we have no device naming issues. + * + * Don't use these only as stress/load tests. Use them along with with + * other USB bus activity: plugging, unplugging, mousing, mp3 playback, + * video capture, and so on. Run different tests at different times, in + * different sequences. Nothing here should interact with other devices, + * except indirectly by consuming USB bandwidth and CPU resources for test + * threads and request completion. But the only way to know that for sure + * is to test when HC queues are in use by many devices. + * + * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), + * it locks out usbcore in certain code paths. Notably, if you disconnect + * the device-under-test, hub_wq will wait block forever waiting for the + * ioctl to complete ... so that usb_disconnect() can abort the pending + * urbs and then call usbtest_disconnect(). To abort a test, you're best + * off just killing the userspace task and waiting for it to exit. + */ + +static int +usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +{ + + struct usbtest_dev *dev = usb_get_intfdata(intf); + struct usbtest_param_64 *param_64 = buf; + struct usbtest_param_32 temp; + struct usbtest_param_32 *param_32 = buf; + struct timespec64 start; + struct timespec64 end; + struct timespec64 duration; + int retval = -EOPNOTSUPP; + + /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ + + pattern = mod_pattern; + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + + /* FIXME: What if a system sleep starts while a test is running? */ + + /* some devices, like ez-usb default devices, need a non-default + * altsetting to have any active endpoints. some tests change + * altsettings; force a default so most tests don't need to check. + */ + if (dev->info->alt >= 0) { + if (intf->altsetting->desc.bInterfaceNumber) { + retval = -ENODEV; + goto free_mutex; + } + retval = set_altsetting(dev, dev->info->alt); + if (retval) { + dev_err(&intf->dev, + "set altsetting to %d failed, %d\n", + dev->info->alt, retval); + goto free_mutex; + } + } + + switch (code) { + case USBTEST_REQUEST_64: + temp.test_num = param_64->test_num; + temp.iterations = param_64->iterations; + temp.length = param_64->length; + temp.sglen = param_64->sglen; + temp.vary = param_64->vary; + param_32 = &temp; + break; + + case USBTEST_REQUEST_32: + break; + + default: + retval = -EOPNOTSUPP; + goto free_mutex; + } + + ktime_get_ts64(&start); + + retval = usbtest_do_ioctl(intf, param_32); + if (retval) + goto free_mutex; + + ktime_get_ts64(&end); + + duration = timespec64_sub(end, start); + + temp.duration_sec = duration.tv_sec; + temp.duration_usec = duration.tv_nsec/NSEC_PER_USEC; + + switch (code) { + case USBTEST_REQUEST_32: + param_32->duration_sec = temp.duration_sec; + param_32->duration_usec = temp.duration_usec; + break; + + case USBTEST_REQUEST_64: + param_64->duration_sec = temp.duration_sec; + param_64->duration_usec = temp.duration_usec; + break; } + +free_mutex: mutex_unlock(&dev->lock); return retval; } -- cgit v1.2.3 From fbb9e22b15ad3c9a98c66bad801b4d1366e8bf20 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 20 Nov 2015 11:49:28 +0100 Subject: usb: dwc2: host: enable descriptor dma for fs devices As descriptor dma mode does not support split transfers, it can't be enabled for high speed devices. Add a core parameter to enable it for full speed devices. Ensure frame list and descriptor list are correctly freed during disconnect. Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 24 ++++++++++++++++++++++++ drivers/usb/dwc2/core.h | 20 ++++++++++++++++++++ drivers/usb/dwc2/hcd.c | 22 ++++++++++++++++++++++ drivers/usb/dwc2/hcd_intr.c | 15 +++++++++++++-- drivers/usb/dwc2/hcd_queue.c | 2 +- drivers/usb/dwc2/platform.c | 4 ++++ 6 files changed, 84 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index ef73e498e98f..5568d9c8e213 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -2485,6 +2485,29 @@ void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val) hsotg->core_params->dma_desc_enable = val; } +void dwc2_set_param_dma_desc_fs_enable(struct dwc2_hsotg *hsotg, int val) +{ + int valid = 1; + + if (val > 0 && (hsotg->core_params->dma_enable <= 0 || + !hsotg->hw_params.dma_desc_enable)) + valid = 0; + if (val < 0) + valid = 0; + + if (!valid) { + if (val >= 0) + dev_err(hsotg->dev, + "%d invalid for dma_desc_fs_enable parameter. Check HW configuration.\n", + val); + val = (hsotg->core_params->dma_enable > 0 && + hsotg->hw_params.dma_desc_enable); + } + + hsotg->core_params->dma_desc_fs_enable = val; + dev_dbg(hsotg->dev, "Setting dma_desc_fs_enable to %d\n", val); +} + void dwc2_set_param_host_support_fs_ls_low_power(struct dwc2_hsotg *hsotg, int val) { @@ -3016,6 +3039,7 @@ void dwc2_set_parameters(struct dwc2_hsotg *hsotg, dwc2_set_param_otg_cap(hsotg, params->otg_cap); dwc2_set_param_dma_enable(hsotg, params->dma_enable); dwc2_set_param_dma_desc_enable(hsotg, params->dma_desc_enable); + dwc2_set_param_dma_desc_fs_enable(hsotg, params->dma_desc_fs_enable); dwc2_set_param_host_support_fs_ls_low_power(hsotg, params->host_support_fs_ls_low_power); dwc2_set_param_enable_dynamic_fifo(hsotg, diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index a66d3cb62b65..fd4c2365069a 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -246,6 +246,13 @@ enum dwc2_ep0_state { * value for this if none is specified. * 0 - Address DMA * 1 - Descriptor DMA (default, if available) + * @dma_desc_fs_enable: When DMA mode is enabled, specifies whether to use + * address DMA mode or descriptor DMA mode for accessing + * the data FIFOs in Full Speed mode only. The driver + * will automatically detect the value for this if none is + * specified. + * 0 - Address DMA + * 1 - Descriptor DMA in FS (default, if available) * @speed: Specifies the maximum speed of operation in host and * device mode. The actual speed depends on the speed of * the attached device and the value of phy_type. @@ -375,6 +382,7 @@ struct dwc2_core_params { int otg_ver; int dma_enable; int dma_desc_enable; + int dma_desc_fs_enable; int speed; int enable_dynamic_fifo; int en_multiple_tx_fifo; @@ -456,6 +464,7 @@ struct dwc2_hw_params { unsigned op_mode:3; unsigned arch:2; unsigned dma_desc_enable:1; + unsigned dma_desc_fs_enable:1; unsigned enable_dynamic_fifo:1; unsigned en_multiple_tx_fifo:1; unsigned host_rx_fifo_size:16; @@ -770,6 +779,7 @@ struct dwc2_hsotg { u16 frame_number; u16 periodic_qh_count; bool bus_suspended; + bool new_connection; #ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS #define FRAME_NUM_ARRAY_SIZE 1000 @@ -941,6 +951,16 @@ extern void dwc2_set_param_dma_enable(struct dwc2_hsotg *hsotg, int val); */ extern void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val); +/* + * When DMA mode is enabled specifies whether to use + * address DMA or DMA Descritor mode with full speed devices + * for accessing the data FIFOs in host mode. + * 0 - address DMA + * 1 - FS DMA Descriptor(default, if available) + */ +extern void dwc2_set_param_dma_desc_fs_enable(struct dwc2_hsotg *hsotg, + int val); + /* * Specifies the maximum speed of operation in host and device mode. * The actual speed depends on the speed of the attached device and diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 571c21727ff9..32c84e7a81ab 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1734,6 +1734,28 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, port_status |= USB_PORT_STAT_TEST; /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + if (hsotg->core_params->dma_desc_fs_enable) { + /* + * Enable descriptor DMA only if a full speed + * device is connected. + */ + if (hsotg->new_connection && + ((port_status & + (USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_LOW_SPEED)) == + USB_PORT_STAT_CONNECTION)) { + u32 hcfg; + + dev_info(hsotg->dev, "Enabling descriptor DMA mode\n"); + hsotg->core_params->dma_desc_enable = 1; + hcfg = dwc2_readl(hsotg->regs + HCFG); + hcfg |= HCFG_DESCDMA; + dwc2_writel(hcfg, hsotg->regs + HCFG); + hsotg->new_connection = false; + } + } + dev_vdbg(hsotg->dev, "port_status=%08x\n", port_status); *(__le32 *)buf = cpu_to_le32(port_status); break; diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index bda0b21b850f..7c15f03b069c 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -372,10 +372,21 @@ static void dwc2_port_intr(struct dwc2_hsotg *hsotg) " --Port Interrupt HPRT0=0x%08x Port Enable Changed (now %d)--\n", hprt0, !!(hprt0 & HPRT0_ENA)); hprt0_modify |= HPRT0_ENACHG; - if (hprt0 & HPRT0_ENA) + if (hprt0 & HPRT0_ENA) { + hsotg->new_connection = true; dwc2_hprt0_enable(hsotg, hprt0, &hprt0_modify); - else + } else { hsotg->flags.b.port_enable_change = 1; + if (hsotg->core_params->dma_desc_fs_enable) { + u32 hcfg; + + hsotg->core_params->dma_desc_enable = 0; + hsotg->new_connection = false; + hcfg = dwc2_readl(hsotg->regs + HCFG); + hcfg &= ~HCFG_DESCDMA; + dwc2_writel(hcfg, hsotg->regs + HCFG); + } + } } /* Overcurrent Change Interrupt */ diff --git a/drivers/usb/dwc2/hcd_queue.c b/drivers/usb/dwc2/hcd_queue.c index 7d8d06cfe3c1..27d402f680a3 100644 --- a/drivers/usb/dwc2/hcd_queue.c +++ b/drivers/usb/dwc2/hcd_queue.c @@ -232,7 +232,7 @@ struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg, */ void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { - if (hsotg->core_params->dma_desc_enable > 0) { + if (qh->desc_list) { dwc2_hcd_qh_free_ddma(hsotg, qh); } else { /* kfree(NULL) is safe */ diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 39c1cbf0e75d..095bc05f76ca 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -59,6 +59,7 @@ static const struct dwc2_core_params params_bcm2835 = { .otg_ver = 0, /* 1.3 */ .dma_enable = 1, .dma_desc_enable = 0, + .dma_desc_fs_enable = 0, .speed = 0, /* High Speed */ .enable_dynamic_fifo = 1, .en_multiple_tx_fifo = 1, @@ -89,6 +90,7 @@ static const struct dwc2_core_params params_rk3066 = { .otg_ver = -1, .dma_enable = -1, .dma_desc_enable = 0, + .dma_desc_fs_enable = 0, .speed = -1, .enable_dynamic_fifo = 1, .en_multiple_tx_fifo = -1, @@ -348,8 +350,10 @@ static int dwc2_driver_probe(struct platform_device *dev) /* * Disable descriptor dma mode by default as the HW can support * it, but does not support it for SPLIT transactions. + * Disable it for FS devices as well. */ defparams.dma_desc_enable = 0; + defparams.dma_desc_fs_enable = 0; } hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL); -- cgit v1.2.3 From 95105a998dff0747327f11708ea24480ee0eca54 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 20 Nov 2015 11:49:29 +0100 Subject: usb: dwc2: host: avoid usage of dma_alloc_coherent with irqs disabled Use Streaming DMA mappings to handle cache coherency of frame list and descriptor list. Cache are always flushed before controller access it or before cpu access it. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 3 ++ drivers/usb/dwc2/core.h | 2 + drivers/usb/dwc2/hcd.c | 4 +- drivers/usb/dwc2/hcd.h | 4 ++ drivers/usb/dwc2/hcd_ddma.c | 106 ++++++++++++++++++++++++++++++++++---------- 5 files changed, 94 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 5568d9c8e213..542c9e6d95db 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -1934,6 +1934,9 @@ void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, dwc2_writel(hctsiz, hsotg->regs + HCTSIZ(chan->hc_num)); + dma_sync_single_for_device(hsotg->dev, chan->desc_list_addr, + chan->desc_list_sz, DMA_TO_DEVICE); + hc_dma = (u32)chan->desc_list_addr & HCDMA_DMA_ADDR_MASK; /* Always start from first descriptor */ diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index fd4c2365069a..e7cc54268f2f 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -685,6 +685,7 @@ struct dwc2_hregs_backup { * @otg_port: OTG port number * @frame_list: Frame list * @frame_list_dma: Frame list DMA address + * @frame_list_sz: Frame list size * * These are for peripheral mode: * @@ -804,6 +805,7 @@ struct dwc2_hsotg { u8 otg_port; u32 *frame_list; dma_addr_t frame_list_dma; + u32 frame_list_sz; #ifdef DEBUG u32 frrem_samples; diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 32c84e7a81ab..7fd4f41e12a7 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -881,8 +881,10 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) */ chan->multi_count = dwc2_hb_mult(qh->maxp); - if (hsotg->core_params->dma_desc_enable > 0) + if (hsotg->core_params->dma_desc_enable > 0) { chan->desc_list_addr = qh->desc_list_dma; + chan->desc_list_sz = qh->desc_list_sz; + } dwc2_hc_init(hsotg, chan); chan->qh = qh; diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index 2e5e9d9a0016..6e822661a69e 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -107,6 +107,7 @@ struct dwc2_qh; * @qh: QH for the transfer being processed by this channel * @hc_list_entry: For linking to list of host channels * @desc_list_addr: Current QH's descriptor list DMA address + * @desc_list_sz: Current QH's descriptor list size * * This structure represents the state of a single host channel when acting in * host mode. It contains the data items needed to transfer packets to an @@ -159,6 +160,7 @@ struct dwc2_host_chan { struct dwc2_qh *qh; struct list_head hc_list_entry; dma_addr_t desc_list_addr; + u32 desc_list_sz; }; struct dwc2_hcd_pipe_info { @@ -251,6 +253,7 @@ enum dwc2_transaction_type { * schedule * @desc_list: List of transfer descriptors * @desc_list_dma: Physical address of desc_list + * @desc_list_sz: Size of descriptors list * @n_bytes: Xfer Bytes array. Each element corresponds to a transfer * descriptor and indicates original XferSize value for the * descriptor @@ -284,6 +287,7 @@ struct dwc2_qh { struct list_head qh_list_entry; struct dwc2_hcd_dma_desc *desc_list; dma_addr_t desc_list_dma; + u32 desc_list_sz; u32 *n_bytes; unsigned tt_buffer_dirty:1; }; diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index f98c7e91f6bc..85d7816d29f1 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -87,22 +87,23 @@ static u16 dwc2_frame_incr_val(struct dwc2_qh *qh) static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, gfp_t flags) { - qh->desc_list = dma_alloc_coherent(hsotg->dev, - sizeof(struct dwc2_hcd_dma_desc) * - dwc2_max_desc_num(qh), &qh->desc_list_dma, - flags); + qh->desc_list_sz = sizeof(struct dwc2_hcd_dma_desc) * + dwc2_max_desc_num(qh); + qh->desc_list = kzalloc(qh->desc_list_sz, flags | GFP_DMA); if (!qh->desc_list) return -ENOMEM; - memset(qh->desc_list, 0, - sizeof(struct dwc2_hcd_dma_desc) * dwc2_max_desc_num(qh)); + qh->desc_list_dma = dma_map_single(hsotg->dev, qh->desc_list, + qh->desc_list_sz, + DMA_TO_DEVICE); qh->n_bytes = kzalloc(sizeof(u32) * dwc2_max_desc_num(qh), flags); if (!qh->n_bytes) { - dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) - * dwc2_max_desc_num(qh), qh->desc_list, - qh->desc_list_dma); + dma_unmap_single(hsotg->dev, qh->desc_list_dma, + qh->desc_list_sz, + DMA_FROM_DEVICE); + kfree(qh->desc_list); qh->desc_list = NULL; return -ENOMEM; } @@ -113,9 +114,9 @@ static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, static void dwc2_desc_list_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { if (qh->desc_list) { - dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) - * dwc2_max_desc_num(qh), qh->desc_list, - qh->desc_list_dma); + dma_unmap_single(hsotg->dev, qh->desc_list_dma, + qh->desc_list_sz, DMA_FROM_DEVICE); + kfree(qh->desc_list); qh->desc_list = NULL; } @@ -128,21 +129,20 @@ static int dwc2_frame_list_alloc(struct dwc2_hsotg *hsotg, gfp_t mem_flags) if (hsotg->frame_list) return 0; - hsotg->frame_list = dma_alloc_coherent(hsotg->dev, - 4 * FRLISTEN_64_SIZE, - &hsotg->frame_list_dma, - mem_flags); + hsotg->frame_list_sz = 4 * FRLISTEN_64_SIZE; + hsotg->frame_list = kzalloc(hsotg->frame_list_sz, GFP_ATOMIC | GFP_DMA); if (!hsotg->frame_list) return -ENOMEM; - memset(hsotg->frame_list, 0, 4 * FRLISTEN_64_SIZE); + hsotg->frame_list_dma = dma_map_single(hsotg->dev, hsotg->frame_list, + hsotg->frame_list_sz, + DMA_TO_DEVICE); + return 0; } static void dwc2_frame_list_free(struct dwc2_hsotg *hsotg) { - u32 *frame_list; - dma_addr_t frame_list_dma; unsigned long flags; spin_lock_irqsave(&hsotg->lock, flags); @@ -152,14 +152,14 @@ static void dwc2_frame_list_free(struct dwc2_hsotg *hsotg) return; } - frame_list = hsotg->frame_list; - frame_list_dma = hsotg->frame_list_dma; + dma_unmap_single(hsotg->dev, hsotg->frame_list_dma, + hsotg->frame_list_sz, DMA_FROM_DEVICE); + + kfree(hsotg->frame_list); hsotg->frame_list = NULL; spin_unlock_irqrestore(&hsotg->lock, flags); - dma_free_coherent(hsotg->dev, 4 * FRLISTEN_64_SIZE, frame_list, - frame_list_dma); } static void dwc2_per_sched_enable(struct dwc2_hsotg *hsotg, u32 fr_list_en) @@ -249,6 +249,15 @@ static void dwc2_update_frame_list(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, j = (j + inc) & (FRLISTEN_64_SIZE - 1); } while (j != i); + /* + * Sync frame list since controller will access it if periodic + * channel is currently enabled. + */ + dma_sync_single_for_device(hsotg->dev, + hsotg->frame_list_dma, + hsotg->frame_list_sz, + DMA_TO_DEVICE); + if (!enable) return; @@ -541,6 +550,11 @@ static void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, dma_desc->status |= HOST_DMA_IOC; #endif + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (idx * sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, @@ -610,6 +624,11 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, if (qh->ntd == ntd_max) { idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); qh->desc_list[idx].status |= HOST_DMA_IOC; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + (idx * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } #else /* @@ -639,6 +658,11 @@ static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); qh->desc_list[idx].status |= HOST_DMA_IOC; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (idx * sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); #endif } @@ -676,6 +700,12 @@ static void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, dma_desc->buf = (u32)chan->xfer_dma; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (n_desc * sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); + /* * Last (or only) descriptor of IN transfer with actual size less * than MaxPacket @@ -726,6 +756,12 @@ static void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, "set A bit in desc %d (%p)\n", n_desc - 1, &qh->desc_list[n_desc - 1]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + ((n_desc - 1) * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } dwc2_fill_host_dma_desc(hsotg, chan, qtd, qh, n_desc); dev_vdbg(hsotg->dev, @@ -751,10 +787,19 @@ static void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, HOST_DMA_IOC | HOST_DMA_EOL | HOST_DMA_A; dev_vdbg(hsotg->dev, "set IOC/EOL/A bits in desc %d (%p)\n", n_desc - 1, &qh->desc_list[n_desc - 1]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + (n_desc - 1) * + sizeof(struct dwc2_hcd_dma_desc), + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); if (n_desc > 1) { qh->desc_list[0].status |= HOST_DMA_A; dev_vdbg(hsotg->dev, "set A bit in desc 0 (%p)\n", &qh->desc_list[0]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma, + sizeof(struct dwc2_hcd_dma_desc), + DMA_TO_DEVICE); } chan->ntd = n_desc; } @@ -829,7 +874,7 @@ static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, struct dwc2_qh *qh, u16 idx) { - struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[idx]; + struct dwc2_hcd_dma_desc *dma_desc; struct dwc2_hcd_iso_packet_desc *frame_desc; u16 remain = 0; int rc = 0; @@ -837,6 +882,13 @@ static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, if (!qtd->urb) return -EINVAL; + dma_sync_single_for_cpu(hsotg->dev, qh->desc_list_dma + (idx * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_FROM_DEVICE); + + dma_desc = &qh->desc_list[idx]; + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; dma_desc->buf = (u32)(qtd->urb->dma + frame_desc->offset); if (chan->ep_is_in) @@ -1092,6 +1144,12 @@ static int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, if (!urb) return -EINVAL; + dma_sync_single_for_cpu(hsotg->dev, + qh->desc_list_dma + (desc_num * + sizeof(struct dwc2_hcd_dma_desc)), + sizeof(struct dwc2_hcd_dma_desc), + DMA_FROM_DEVICE); + dma_desc = &qh->desc_list[desc_num]; n_bytes = qh->n_bytes[desc_num]; dev_vdbg(hsotg->dev, -- cgit v1.2.3 From e23b8a54a440a2b8ee5c9dc3eb2099ecf813ef70 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 20 Nov 2015 11:49:30 +0100 Subject: usb: dwc2: host: fix descriptor list address masking Masks for HCDMA.CTD and HCDMA.DMAAddr are incorrect. As we always start from first descriptor, no need to mask the address anyway. Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 10 +++------- drivers/usb/dwc2/hw.h | 4 ---- 2 files changed, 3 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 542c9e6d95db..97de85521eb6 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -1905,7 +1905,6 @@ void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) { u32 hcchar; - u32 hc_dma; u32 hctsiz = 0; if (chan->do_ping) @@ -1937,14 +1936,11 @@ void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, dma_sync_single_for_device(hsotg->dev, chan->desc_list_addr, chan->desc_list_sz, DMA_TO_DEVICE); - hc_dma = (u32)chan->desc_list_addr & HCDMA_DMA_ADDR_MASK; + dwc2_writel(chan->desc_list_addr, hsotg->regs + HCDMA(chan->hc_num)); - /* Always start from first descriptor */ - hc_dma &= ~HCDMA_CTD_MASK; - dwc2_writel(hc_dma, hsotg->regs + HCDMA(chan->hc_num)); if (dbg_hc(chan)) - dev_vdbg(hsotg->dev, "Wrote %08x to HCDMA(%d)\n", - hc_dma, chan->hc_num); + dev_vdbg(hsotg->dev, "Wrote %pad to HCDMA(%d)\n", + &chan->desc_list_addr, chan->hc_num); hcchar = dwc2_readl(hsotg->regs + HCCHAR(chan->hc_num)); hcchar &= ~HCCHAR_MULTICNT_MASK; diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 553f24606c43..281b57b36ab4 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -769,10 +769,6 @@ #define TSIZ_XFERSIZE_SHIFT 0 #define HCDMA(_ch) HSOTG_REG(0x0514 + 0x20 * (_ch)) -#define HCDMA_DMA_ADDR_MASK (0x1fffff << 11) -#define HCDMA_DMA_ADDR_SHIFT 11 -#define HCDMA_CTD_MASK (0xff << 3) -#define HCDMA_CTD_SHIFT 3 #define HCDMAB(_ch) HSOTG_REG(0x051c + 0x20 * (_ch)) -- cgit v1.2.3 From 3b5fcc9ac2f4453a5609cc89ac7618b1b27ccb01 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 20 Nov 2015 11:49:31 +0100 Subject: usb: dwc2: host: use kmem cache to allocate descriptors Kmem caches help to get correct boundary for descriptor buffers which need to be 512 bytes aligned for dwc2 controller. Two kmem caches are needed for generic descriptors and for hs isochronous descriptors which doesn't have same size. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.h | 4 ++++ drivers/usb/dwc2/hcd.c | 50 ++++++++++++++++++++++++++++++++++++++++++++- drivers/usb/dwc2/hcd_ddma.c | 20 ++++++++++++++++-- 3 files changed, 71 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index e7cc54268f2f..baee2bc1e8a8 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -686,6 +686,8 @@ struct dwc2_hregs_backup { * @frame_list: Frame list * @frame_list_dma: Frame list DMA address * @frame_list_sz: Frame list size + * @desc_gen_cache: Kmem cache for generic descriptors + * @desc_hsisoc_cache: Kmem cache for hs isochronous descriptors * * These are for peripheral mode: * @@ -806,6 +808,8 @@ struct dwc2_hsotg { u32 *frame_list; dma_addr_t frame_list_dma; u32 frame_list_sz; + struct kmem_cache *desc_gen_cache; + struct kmem_cache *desc_hsisoc_cache; #ifdef DEBUG u32 frrem_samples; diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 7fd4f41e12a7..b2afe913d559 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -3146,6 +3146,47 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) if (!hsotg->status_buf) goto error3; + /* + * Create kmem caches to handle descriptor buffers in descriptor + * DMA mode. + * Alignment must be set to 512 bytes. + */ + if (hsotg->core_params->dma_desc_enable || + hsotg->core_params->dma_desc_fs_enable) { + hsotg->desc_gen_cache = kmem_cache_create("dwc2-gen-desc", + sizeof(struct dwc2_hcd_dma_desc) * + MAX_DMA_DESC_NUM_GENERIC, 512, SLAB_CACHE_DMA, + NULL); + if (!hsotg->desc_gen_cache) { + dev_err(hsotg->dev, + "unable to create dwc2 generic desc cache\n"); + + /* + * Disable descriptor dma mode since it will not be + * usable. + */ + hsotg->core_params->dma_desc_enable = 0; + hsotg->core_params->dma_desc_fs_enable = 0; + } + + hsotg->desc_hsisoc_cache = kmem_cache_create("dwc2-hsisoc-desc", + sizeof(struct dwc2_hcd_dma_desc) * + MAX_DMA_DESC_NUM_HS_ISOC, 512, 0, NULL); + if (!hsotg->desc_hsisoc_cache) { + dev_err(hsotg->dev, + "unable to create dwc2 hs isoc desc cache\n"); + + kmem_cache_destroy(hsotg->desc_gen_cache); + + /* + * Disable descriptor dma mode since it will not be + * usable. + */ + hsotg->core_params->dma_desc_enable = 0; + hsotg->core_params->dma_desc_fs_enable = 0; + } + } + hsotg->otg_port = 1; hsotg->frame_list = NULL; hsotg->frame_list_dma = 0; @@ -3169,7 +3210,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) */ retval = usb_add_hcd(hcd, irq, IRQF_SHARED); if (retval < 0) - goto error3; + goto error4; device_wakeup_enable(hcd->self.controller); @@ -3179,6 +3220,9 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) return 0; +error4: + kmem_cache_destroy(hsotg->desc_gen_cache); + kmem_cache_destroy(hsotg->desc_hsisoc_cache); error3: dwc2_hcd_release(hsotg); error2: @@ -3219,6 +3263,10 @@ void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) usb_remove_hcd(hcd); hsotg->priv = NULL; + + kmem_cache_destroy(hsotg->desc_gen_cache); + kmem_cache_destroy(hsotg->desc_hsisoc_cache); + dwc2_hcd_release(hsotg); usb_put_hcd(hcd); diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 85d7816d29f1..36606fc33c0d 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -87,10 +87,18 @@ static u16 dwc2_frame_incr_val(struct dwc2_qh *qh) static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, gfp_t flags) { + struct kmem_cache *desc_cache; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC + && qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; + qh->desc_list_sz = sizeof(struct dwc2_hcd_dma_desc) * dwc2_max_desc_num(qh); - qh->desc_list = kzalloc(qh->desc_list_sz, flags | GFP_DMA); + qh->desc_list = kmem_cache_zalloc(desc_cache, flags | GFP_DMA); if (!qh->desc_list) return -ENOMEM; @@ -113,10 +121,18 @@ static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, static void dwc2_desc_list_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) { + struct kmem_cache *desc_cache; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC + && qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; + if (qh->desc_list) { dma_unmap_single(hsotg->dev, qh->desc_list_dma, qh->desc_list_sz, DMA_FROM_DEVICE); - kfree(qh->desc_list); + kmem_cache_free(desc_cache, qh->desc_list); qh->desc_list = NULL; } -- cgit v1.2.3 From 37dd9d65cc41fcc7e77645a1cdf2659472809b96 Mon Sep 17 00:00:00 2001 From: Zhangfei Gao Date: Wed, 18 Nov 2015 15:39:47 +0800 Subject: usb: dwc2: add support of hi6220 Support hisilicon,hi6220-usb for HiKey board Acked-by: Rob Herring Acked-by: John Youn Signed-off-by: Zhangfei Gao Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/dwc2.txt | 1 + drivers/usb/dwc2/platform.c | 33 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/usb/dwc2.txt b/Documentation/devicetree/bindings/usb/dwc2.txt index fd132cbee70e..221368207ca4 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.txt +++ b/Documentation/devicetree/bindings/usb/dwc2.txt @@ -4,6 +4,7 @@ Platform DesignWare HS OTG USB 2.0 controller Required properties: - compatible : One of: - brcm,bcm2835-usb: The DWC2 USB controller instance in the BCM2835 SoC. + - hisilicon,hi6220-usb: The DWC2 USB controller instance in the hi6220 SoC. - rockchip,rk3066-usb: The DWC2 USB controller instance in the rk3066 Soc; - "rockchip,rk3188-usb", "rockchip,rk3066-usb", "snps,dwc2": for rk3188 Soc; - "rockchip,rk3288-usb", "rockchip,rk3066-usb", "snps,dwc2": for rk3288 Soc; diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 095bc05f76ca..5df2adf6f4e7 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -54,6 +54,38 @@ static const char dwc2_driver_name[] = "dwc2"; +static const struct dwc2_core_params params_hi6220 = { + .otg_cap = 2, /* No HNP/SRP capable */ + .otg_ver = 0, /* 1.3 */ + .dma_enable = 1, + .dma_desc_enable = 0, + .dma_desc_fs_enable = 0, + .speed = 0, /* High Speed */ + .enable_dynamic_fifo = 1, + .en_multiple_tx_fifo = 1, + .host_rx_fifo_size = 512, + .host_nperio_tx_fifo_size = 512, + .host_perio_tx_fifo_size = 512, + .max_transfer_size = 65535, + .max_packet_count = 511, + .host_channels = 16, + .phy_type = 1, /* UTMI */ + .phy_utmi_width = 8, + .phy_ulpi_ddr = 0, /* Single */ + .phy_ulpi_ext_vbus = 0, + .i2c_enable = 0, + .ulpi_fs_ls = 0, + .host_support_fs_ls_low_power = 0, + .host_ls_low_power_phy_clk = 0, /* 48 MHz */ + .ts_dline = 0, + .reload_ctl = 0, + .ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT, + .uframe_sched = 0, + .external_id_pin_ctl = -1, + .hibernation = -1, +}; + static const struct dwc2_core_params params_bcm2835 = { .otg_cap = 0, /* HNP/SRP capable */ .otg_ver = 0, /* 1.3 */ @@ -310,6 +342,7 @@ static int dwc2_driver_remove(struct platform_device *dev) static const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = ¶ms_bcm2835 }, + { .compatible = "hisilicon,hi6220-usb", .data = ¶ms_hi6220 }, { .compatible = "rockchip,rk3066-usb", .data = ¶ms_rk3066 }, { .compatible = "snps,dwc2", .data = NULL }, { .compatible = "samsung,s3c6400-hsotg", .data = NULL}, -- cgit v1.2.3 From 6a6595318ac2dd169d2931a1d9431a64f4ada75c Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 19 Nov 2015 13:23:14 -0800 Subject: usb: dwc2: host: Fix missing device insertions If you've got your interrupt signals bouncing a bit as you insert your USB device, you might end up in a state when the device is connected but the driver doesn't know it. Specifically, the observed order is: 1. hardware sees connect 2. hardware sees disconnect 3. hardware sees connect 4. dwc2_port_intr() - clears connect interrupt 5. dwc2_handle_common_intr() - calls dwc2_hcd_disconnect() Now you'll be stuck with the cable plugged in and no further interrupts coming in but the driver will think we're disconnected. We'll fix this by checking for the missing connect interrupt and re-connecting after the disconnect is posted. We don't skip the disconnect because if there is a transitory disconnect we really want to de-enumerate and re-enumerate. Notes: 1. As part of this change we add a "force" parameter to dwc2_hcd_disconnect() so that when we're unloading the module we avoid the new behavior. The need for this was pointed out by John Youn. 2. The bit of code needed at the end of dwc2_hcd_disconnect() is exactly the same bit of code from dwc2_port_intr(). To avoid duplication, we refactor that code out into a new function dwc2_hcd_connect(). Signed-off-by: Douglas Anderson Acked-by: John Youn Tested-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.h | 6 ++++-- drivers/usb/dwc2/core_intr.c | 4 ++-- drivers/usb/dwc2/hcd.c | 40 ++++++++++++++++++++++++++++++++++++++-- drivers/usb/dwc2/hcd_intr.c | 6 +----- 4 files changed, 45 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index baee2bc1e8a8..05f01785de05 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -1180,12 +1180,14 @@ static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) extern int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); -extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_connect(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force); extern void dwc2_hcd_start(struct dwc2_hsotg *hsotg); #else static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) { return 0; } -static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) {} static inline void dwc2_hcd_start(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {} static inline int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index 27daa42788b1..61601d16e233 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -239,7 +239,7 @@ static void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "a_suspend->a_peripheral (%d)\n", hsotg->op_state); spin_unlock(&hsotg->lock); - dwc2_hcd_disconnect(hsotg); + dwc2_hcd_disconnect(hsotg, false); spin_lock(&hsotg->lock); hsotg->op_state = OTG_STATE_A_PERIPHERAL; } else { @@ -401,7 +401,7 @@ static void dwc2_handle_disconnect_intr(struct dwc2_hsotg *hsotg) dwc2_op_state_str(hsotg)); if (hsotg->op_state == OTG_STATE_A_HOST) - dwc2_hcd_disconnect(hsotg); + dwc2_hcd_disconnect(hsotg, false); dwc2_writel(GINTSTS_DISCONNINT, hsotg->regs + GINTSTS); } diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index b2afe913d559..cbfdcb804adb 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -267,16 +267,34 @@ static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) } } +/** + * dwc2_hcd_connect() - Handles connect of the HCD + * + * @hsotg: Pointer to struct dwc2_hsotg + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) +{ + if (hsotg->lx_state != DWC2_L0) + usb_hcd_resume_root_hub(hsotg->priv); + + hsotg->flags.b.port_connect_status_change = 1; + hsotg->flags.b.port_connect_status = 1; +} + /** * dwc2_hcd_disconnect() - Handles disconnect of the HCD * * @hsotg: Pointer to struct dwc2_hsotg + * @force: If true, we won't try to reconnect even if we see device connected. * * Must be called with interrupt disabled and spinlock held */ -void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) { u32 intr; + u32 hprt0; /* Set status flags for the hub driver */ hsotg->flags.b.port_connect_status_change = 1; @@ -315,6 +333,24 @@ void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) dwc2_hcd_cleanup_channels(hsotg); dwc2_host_disconnect(hsotg); + + /* + * Add an extra check here to see if we're actually connected but + * we don't have a detection interrupt pending. This can happen if: + * 1. hardware sees connect + * 2. hardware sees disconnect + * 3. hardware sees connect + * 4. dwc2_port_intr() - clears connect interrupt + * 5. dwc2_handle_common_intr() - calls here + * + * Without the extra check here we will end calling disconnect + * and won't get any future interrupts to handle the connect. + */ + if (!force) { + hprt0 = dwc2_readl(hsotg->regs + HPRT0); + if (!(hprt0 & HPRT0_CONNDET) && (hprt0 & HPRT0_CONNSTS)) + dwc2_hcd_connect(hsotg); + } } /** @@ -2390,7 +2426,7 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) spin_lock_irqsave(&hsotg->lock, flags); /* Ensure hcd is disconnected */ - dwc2_hcd_disconnect(hsotg); + dwc2_hcd_disconnect(hsotg, true); dwc2_hcd_stop(hsotg); hsotg->lx_state = DWC2_L3; hcd->state = HC_STATE_HALT; diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index 7c15f03b069c..a1eb48e54858 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -350,11 +350,7 @@ static void dwc2_port_intr(struct dwc2_hsotg *hsotg) dev_vdbg(hsotg->dev, "--Port Interrupt HPRT0=0x%08x Port Connect Detected--\n", hprt0); - if (hsotg->lx_state != DWC2_L0) - usb_hcd_resume_root_hub(hsotg->priv); - - hsotg->flags.b.port_connect_status_change = 1; - hsotg->flags.b.port_connect_status = 1; + dwc2_hcd_connect(hsotg); hprt0_modify |= HPRT0_CONNDET; /* -- cgit v1.2.3 From 69b76cdff592058ea445cd40e18c75dffaba4cb9 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Wed, 11 Nov 2015 10:33:52 -0800 Subject: usb: dwc2: host: Support immediate retries for split transactions In some cases, like when you've got a "Microsoft Wireless Keyboard 2000" connected to dwc2 with a hub, expected that we'll get some transfer errors sometimes. The controller is expected to try at least 3 times before giving up. See figure "Figure A-67. Normal HS CSPLIT 3 Strikes Smash" in the USB spec. The dwc2 controller has a way to support this by using the "EC_MC" field. The Raspberry Pi driver has logic for setting this right. See fiq_fsm_queue_split_transaction() in their "dwc_otg_hcd.c". Let's use the same logic. After making this change, we no longer get dropped characters from the above mentioned keyboard. Other devices on the same bus as the keyboard also behave more properly. Thanks for Julius Werner for the expert analysis and suggestions. Acked-by: John Youn Signed-off-by: Douglas Anderson Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 97de85521eb6..0a9ebe00fbcc 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -1707,6 +1707,7 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, u32 hcchar; u32 hctsiz = 0; u16 num_packets; + u32 ec_mc; if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "%s()\n", __func__); @@ -1743,6 +1744,13 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & TSIZ_XFERSIZE_MASK; + + /* For split set ec_mc for immediate retries */ + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + ec_mc = 3; + else + ec_mc = 1; } else { if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "no split\n"); @@ -1805,6 +1813,9 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & TSIZ_XFERSIZE_MASK; + + /* The ec_mc gets the multi_count for non-split */ + ec_mc = chan->multi_count; } chan->start_pkt_count = num_packets; @@ -1855,8 +1866,7 @@ void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, hcchar = dwc2_readl(hsotg->regs + HCCHAR(chan->hc_num)); hcchar &= ~HCCHAR_MULTICNT_MASK; - hcchar |= chan->multi_count << HCCHAR_MULTICNT_SHIFT & - HCCHAR_MULTICNT_MASK; + hcchar |= (ec_mc << HCCHAR_MULTICNT_SHIFT) & HCCHAR_MULTICNT_MASK; dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); if (hcchar & HCCHAR_CHDIS) -- cgit v1.2.3 From 0aecfc1b359dffddf74bd0e0ea5ee47066d210ac Mon Sep 17 00:00:00 2001 From: Igor Kotrasinski Date: Tue, 20 Oct 2015 18:33:13 +0200 Subject: usb: gadget: composite: remove redundant bcdUSB setting in legacy Since composite now overwrites bcdUSB for any gadget, remove setting it in legacy gadgets. All legacy gadgets set 0x0200, the same as the value additionally set by composite, so there is no behaviour change. Signed-off-by: Igor Kotrasinski Rebase onto current balbi/next Signed-off-by: Krzysztof Opasiak Signed-off-by: Felipe Balbi --- drivers/usb/gadget/legacy/acm_ms.c | 2 +- drivers/usb/gadget/legacy/audio.c | 2 +- drivers/usb/gadget/legacy/cdc2.c | 2 +- drivers/usb/gadget/legacy/ether.c | 2 +- drivers/usb/gadget/legacy/g_ffs.c | 2 +- drivers/usb/gadget/legacy/gmidi.c | 2 +- drivers/usb/gadget/legacy/hid.c | 2 +- drivers/usb/gadget/legacy/mass_storage.c | 2 +- drivers/usb/gadget/legacy/multi.c | 2 +- drivers/usb/gadget/legacy/ncm.c | 2 +- drivers/usb/gadget/legacy/nokia.c | 2 +- drivers/usb/gadget/legacy/printer.c | 2 +- drivers/usb/gadget/legacy/serial.c | 2 +- drivers/usb/gadget/legacy/tcm_usb_gadget.c | 2 +- drivers/usb/gadget/legacy/webcam.c | 2 +- drivers/usb/gadget/legacy/zero.c | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/legacy/acm_ms.c b/drivers/usb/gadget/legacy/acm_ms.c index 4b158e2d1e57..c16089efc322 100644 --- a/drivers/usb/gadget/legacy/acm_ms.c +++ b/drivers/usb/gadget/legacy/acm_ms.c @@ -40,7 +40,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_MISC /* 0xEF */, .bDeviceSubClass = 2, diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c index 685cf3b4b78f..5d7b3c6a422b 100644 --- a/drivers/usb/gadget/legacy/audio.c +++ b/drivers/usb/gadget/legacy/audio.c @@ -123,7 +123,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x200), + /* .bcdUSB = DYNAMIC */ #ifdef CONFIG_GADGET_UAC1 .bDeviceClass = USB_CLASS_PER_INTERFACE, diff --git a/drivers/usb/gadget/legacy/cdc2.c b/drivers/usb/gadget/legacy/cdc2.c index ecd8c8d62f2e..51c08682de84 100644 --- a/drivers/usb/gadget/legacy/cdc2.c +++ b/drivers/usb/gadget/legacy/cdc2.c @@ -43,7 +43,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .bDeviceSubClass = 0, diff --git a/drivers/usb/gadget/legacy/ether.c b/drivers/usb/gadget/legacy/ether.c index 31e9160223e9..25a2c2e48592 100644 --- a/drivers/usb/gadget/legacy/ether.c +++ b/drivers/usb/gadget/legacy/ether.c @@ -151,7 +151,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16 (0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .bDeviceSubClass = 0, diff --git a/drivers/usb/gadget/legacy/g_ffs.c b/drivers/usb/gadget/legacy/g_ffs.c index 320a81b2baa6..f85639ef8a8f 100644 --- a/drivers/usb/gadget/legacy/g_ffs.c +++ b/drivers/usb/gadget/legacy/g_ffs.c @@ -69,7 +69,7 @@ static struct usb_device_descriptor gfs_dev_desc = { .bLength = sizeof gfs_dev_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .idVendor = cpu_to_le16(GFS_VENDOR_ID), diff --git a/drivers/usb/gadget/legacy/gmidi.c b/drivers/usb/gadget/legacy/gmidi.c index be8e91d68e14..e27aad5e50b9 100644 --- a/drivers/usb/gadget/legacy/gmidi.c +++ b/drivers/usb/gadget/legacy/gmidi.c @@ -78,7 +78,7 @@ MODULE_PARM_DESC(out_ports, "Number of MIDI output ports"); static struct usb_device_descriptor device_desc = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM), .idProduct = cpu_to_le16(DRIVER_PRODUCT_NUM), diff --git a/drivers/usb/gadget/legacy/hid.c b/drivers/usb/gadget/legacy/hid.c index 7e5d2c48476e..a71a884f79fc 100644 --- a/drivers/usb/gadget/legacy/hid.c +++ b/drivers/usb/gadget/legacy/hid.c @@ -47,7 +47,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ /* .bDeviceClass = USB_CLASS_COMM, */ /* .bDeviceSubClass = 0, */ diff --git a/drivers/usb/gadget/legacy/mass_storage.c b/drivers/usb/gadget/legacy/mass_storage.c index bda3c519110f..e61af53c7d2b 100644 --- a/drivers/usb/gadget/legacy/mass_storage.c +++ b/drivers/usb/gadget/legacy/mass_storage.c @@ -55,7 +55,7 @@ static struct usb_device_descriptor msg_device_desc = { .bLength = sizeof msg_device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, /* Vendor and product id can be overridden by module parameters. */ diff --git a/drivers/usb/gadget/legacy/multi.c b/drivers/usb/gadget/legacy/multi.c index 4fe794ddcd49..229d704a620b 100644 --- a/drivers/usb/gadget/legacy/multi.c +++ b/drivers/usb/gadget/legacy/multi.c @@ -67,7 +67,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_MISC /* 0xEF */, .bDeviceSubClass = 2, diff --git a/drivers/usb/gadget/legacy/ncm.c b/drivers/usb/gadget/legacy/ncm.c index 2bae4381332d..0aba68253e3d 100644 --- a/drivers/usb/gadget/legacy/ncm.c +++ b/drivers/usb/gadget/legacy/ncm.c @@ -49,7 +49,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16 (0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .bDeviceSubClass = 0, diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c index 8b3f6fb1825d..09975046c694 100644 --- a/drivers/usb/gadget/legacy/nokia.c +++ b/drivers/usb/gadget/legacy/nokia.c @@ -89,7 +89,7 @@ static struct usb_gadget_strings *dev_strings[] = { static struct usb_device_descriptor device_desc = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_COMM, .idVendor = cpu_to_le16(NOKIA_VENDOR_ID), .idProduct = cpu_to_le16(NOKIA_PRODUCT_ID), diff --git a/drivers/usb/gadget/legacy/printer.c b/drivers/usb/gadget/legacy/printer.c index a22d30a4def1..6f969a86175c 100644 --- a/drivers/usb/gadget/legacy/printer.c +++ b/drivers/usb/gadget/legacy/printer.c @@ -71,7 +71,7 @@ static struct usb_function *f_printer; static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, diff --git a/drivers/usb/gadget/legacy/serial.c b/drivers/usb/gadget/legacy/serial.c index c5d42e0347a9..9d89adce756d 100644 --- a/drivers/usb/gadget/legacy/serial.c +++ b/drivers/usb/gadget/legacy/serial.c @@ -65,7 +65,7 @@ static struct usb_gadget_strings *dev_strings[] = { static struct usb_device_descriptor device_desc = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ /* .bDeviceClass = f(use_acm) */ .bDeviceSubClass = 0, .bDeviceProtocol = 0, diff --git a/drivers/usb/gadget/legacy/tcm_usb_gadget.c b/drivers/usb/gadget/legacy/tcm_usb_gadget.c index 22e56158d585..7857fa411636 100644 --- a/drivers/usb/gadget/legacy/tcm_usb_gadget.c +++ b/drivers/usb/gadget/legacy/tcm_usb_gadget.c @@ -1974,7 +1974,7 @@ static struct usb_descriptor_header *uasp_ss_function_desc[] = { static struct usb_device_descriptor usbg_device_desc = { .bLength = sizeof(usbg_device_desc), .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_PER_INTERFACE, .idVendor = cpu_to_le16(UAS_VENDOR_ID), .idProduct = cpu_to_le16(UAS_PRODUCT_ID), diff --git a/drivers/usb/gadget/legacy/webcam.c b/drivers/usb/gadget/legacy/webcam.c index 72c976bf3530..f9661cd627c8 100644 --- a/drivers/usb/gadget/legacy/webcam.c +++ b/drivers/usb/gadget/legacy/webcam.c @@ -77,7 +77,7 @@ static struct usb_function *f_uvc; static struct usb_device_descriptor webcam_device_descriptor = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_MISC, .bDeviceSubClass = 0x02, .bDeviceProtocol = 0x01, diff --git a/drivers/usb/gadget/legacy/zero.c b/drivers/usb/gadget/legacy/zero.c index 0ccdcd9c64a5..d02e2ce73ea5 100644 --- a/drivers/usb/gadget/legacy/zero.c +++ b/drivers/usb/gadget/legacy/zero.c @@ -115,7 +115,7 @@ static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = cpu_to_le16(0x0200), + /* .bcdUSB = DYNAMIC */ .bDeviceClass = USB_CLASS_VENDOR_SPEC, .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM), -- cgit v1.2.3 From fa4dce2022241f3c938372af0faf9d263061f6a9 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Thu, 19 Nov 2015 15:02:19 +0800 Subject: usb: gadget: f_sourcesink: quit if usb_ep_queue returns error Since now, we may have more than one request during the test, and it is better we just quit once the error occurs instead of try queueing further requests. Signed-off-by: Peter Chen Suggested-by: Krzysztof Opasiak Reviewed-by: Krzysztof Opasiak Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_sourcesink.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index 12e3eb4ee140..242ba5caffe5 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -629,6 +629,7 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in, is_iso ? "ISO-" : "", is_in ? "IN" : "OUT", ep->name, status); free_ep_req(ep, req); + return status; } } -- cgit v1.2.3 From 98bfb39466954c69d2a448e6ddcab6d91cd48e25 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 3 Nov 2015 11:51:15 -0600 Subject: usb: of: add an api to get dr_mode by the phy node Some USB phy drivers have different handling for the controller in each dr_mode. But the phy driver does not have visibility to the dr_mode of the controller. This adds an api to return the dr_mode of the controller which associates the given phy node. Signed-off-by: Bin Liu Signed-off-by: Felipe Balbi --- drivers/usb/common/common.c | 60 ++++++++++++++++++++++++++++++++++++++++----- include/linux/usb/of.h | 5 ++++ 2 files changed, 59 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c index 673d53038ed2..e6ec125e4485 100644 --- a/drivers/usb/common/common.c +++ b/drivers/usb/common/common.c @@ -17,6 +17,7 @@ #include #include #include +#include const char *usb_otg_state_string(enum usb_otg_state state) { @@ -106,24 +107,71 @@ static const char *const usb_dr_modes[] = { [USB_DR_MODE_OTG] = "otg", }; +static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(usb_dr_modes); i++) + if (!strcmp(usb_dr_modes[i], str)) + return i; + + return USB_DR_MODE_UNKNOWN; +} + enum usb_dr_mode usb_get_dr_mode(struct device *dev) { const char *dr_mode; - int err, i; + int err; err = device_property_read_string(dev, "dr_mode", &dr_mode); if (err < 0) return USB_DR_MODE_UNKNOWN; - for (i = 0; i < ARRAY_SIZE(usb_dr_modes); i++) - if (!strcmp(dr_mode, usb_dr_modes[i])) - return i; - - return USB_DR_MODE_UNKNOWN; + return usb_get_dr_mode_from_string(dr_mode); } EXPORT_SYMBOL_GPL(usb_get_dr_mode); #ifdef CONFIG_OF +/** + * of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device + * which is associated with the given phy device_node + * @np: Pointer to the given phy device_node + * + * In dts a usb controller associates with phy devices. The function gets + * the string from property 'dr_mode' of the controller associated with the + * given phy device node, and returns the correspondig enum usb_dr_mode. + */ +enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *phy_np) +{ + struct device_node *controller = NULL; + struct device_node *phy; + const char *dr_mode; + int index; + int err; + + do { + controller = of_find_node_with_property(controller, "phys"); + index = 0; + do { + phy = of_parse_phandle(controller, "phys", index); + of_node_put(phy); + if (phy == phy_np) + goto finish; + index++; + } while (phy); + } while (controller); + +finish: + err = of_property_read_string(controller, "dr_mode", &dr_mode); + of_node_put(controller); + + if (err < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(dr_mode); +} +EXPORT_SYMBOL_GPL(of_usb_get_dr_mode_by_phy); + /** * of_usb_host_tpl_support - to get if Targeted Peripheral List is supported * for given targeted hosts (non-PC hosts) diff --git a/include/linux/usb/of.h b/include/linux/usb/of.h index c3fe9e48ce27..3805757dcdc2 100644 --- a/include/linux/usb/of.h +++ b/include/linux/usb/of.h @@ -12,10 +12,15 @@ #include #if IS_ENABLED(CONFIG_OF) +enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *phy_np); bool of_usb_host_tpl_support(struct device_node *np); int of_usb_update_otg_caps(struct device_node *np, struct usb_otg_caps *otg_caps); #else +enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *phy_np) +{ + return USB_DR_MODE_UNKNOWN; +} static inline bool of_usb_host_tpl_support(struct device_node *np) { return false; -- cgit v1.2.3 From 4a065c7bdbec9536f7b899241b125b9c3b5ba97a Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Fri, 20 Nov 2015 09:06:27 -0800 Subject: usb: dwc2: host: Add missing spinlock in dwc2_hcd_reset_func() The dwc2_hcd_reset_func() function is only ever called directly by a delayed work function. As such no locks are already held when the function is called. Doing a read-modify-write of CPU registers and setting fields in the main hsotg data structure is a bad idea without locks. Let's add locks. The bug was found by code inspection only. It turns out that the dwc2_hcd_reset_func() is only ever called today if the "host_support_fs_ls_low_power" parameter is enabled and no code in mainline enables that parameter. Thus no known issues in mainline are fixed by this patch, but it's still probably wise to fix the function. Signed-off-by: Douglas Anderson Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/hcd.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index cbfdcb804adb..880b549f737a 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -2358,13 +2358,19 @@ static void dwc2_hcd_reset_func(struct work_struct *work) { struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, reset_work.work); + unsigned long flags; u32 hprt0; dev_dbg(hsotg->dev, "USB RESET function called\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + hprt0 = dwc2_read_hprt0(hsotg); hprt0 &= ~HPRT0_RST; dwc2_writel(hprt0, hsotg->regs + HPRT0); hsotg->flags.b.port_reset_change = 1; + + spin_unlock_irqrestore(&hsotg->lock, flags); } /* -- cgit v1.2.3 From 29539019b46f0e5f64f80f2e9dc8f9bb34d16b4b Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Fri, 20 Nov 2015 09:06:28 -0800 Subject: usb: dwc2: host: Clear interrupts before handling them In general it is wise to clear interrupts before processing them. If you don't do that, you can get: 1. Interrupt happens 2. You look at system state and process interrupt 3. A new interrupt happens 4. You clear interrupt without processing it. This patch was actually a first attempt to fix missing device insertions as described in (usb: dwc2: host: Fix missing device insertions) and it did solve some of the signal bouncing problems but not all of them (which is why I submitted the other patch). Specifically, this patch itself would sometimes change: 1. hardware sees connect 2. hardware sees disconnect 3. hardware sees connect 4. dwc2_port_intr() - clears connect interrupt 5. dwc2_handle_common_intr() - calls dwc2_hcd_disconnect() ...to: 1. hardware sees connect 2. hardware sees disconnect 3. dwc2_port_intr() - clears connect interrupt 4. hardware sees connect 5. dwc2_handle_common_intr() - calls dwc2_hcd_disconnect() ...but with different timing then sometimes we'd still miss cable insertions. In any case, though this patch doesn't fix any (known) problems, it still seems wise as a general policy to clear interrupt before handling them. Note that for dwc2_handle_usb_port_intr(), instead of moving the clear of PRTINT to the beginning of the function we remove it completely. The only way to clear PRTINT is to clear the sources that set it in the first place. Signed-off-by: Douglas Anderson Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core_intr.c | 49 +++++++++++++++++++++----------------------- drivers/usb/dwc2/hcd_intr.c | 18 ++++++++-------- 2 files changed, 32 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index 61601d16e233..d85c5c9f96c1 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -86,9 +86,6 @@ static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) hprt0 &= ~HPRT0_ENA; dwc2_writel(hprt0, hsotg->regs + HPRT0); } - - /* Clear interrupt */ - dwc2_writel(GINTSTS_PRTINT, hsotg->regs + GINTSTS); } /** @@ -98,11 +95,11 @@ static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) */ static void dwc2_handle_mode_mismatch_intr(struct dwc2_hsotg *hsotg) { - dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", - dwc2_is_host_mode(hsotg) ? "Host" : "Device"); - /* Clear interrupt */ dwc2_writel(GINTSTS_MODEMIS, hsotg->regs + GINTSTS); + + dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", + dwc2_is_host_mode(hsotg) ? "Host" : "Device"); } /** @@ -276,9 +273,13 @@ static void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) */ static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) { - u32 gintmsk = dwc2_readl(hsotg->regs + GINTMSK); + u32 gintmsk; + + /* Clear interrupt */ + dwc2_writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); /* Need to disable SOF interrupt immediately */ + gintmsk = dwc2_readl(hsotg->regs + GINTMSK); gintmsk &= ~GINTSTS_SOF; dwc2_writel(gintmsk, hsotg->regs + GINTMSK); @@ -295,9 +296,6 @@ static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) queue_work(hsotg->wq_otg, &hsotg->wf_otg); spin_lock(&hsotg->lock); } - - /* Clear interrupt */ - dwc2_writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); } /** @@ -315,12 +313,12 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) { int ret; - dev_dbg(hsotg->dev, "Session request interrupt - lx_state=%d\n", - hsotg->lx_state); - /* Clear interrupt */ dwc2_writel(GINTSTS_SESSREQINT, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "Session request interrupt - lx_state=%d\n", + hsotg->lx_state); + if (dwc2_is_device_mode(hsotg)) { if (hsotg->lx_state == DWC2_L2) { ret = dwc2_exit_hibernation(hsotg, true); @@ -347,6 +345,10 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) { int ret; + + /* Clear interrupt */ + dwc2_writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "++Resume or Remote Wakeup Detected Interrupt++\n"); dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); @@ -368,10 +370,9 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) /* Change to L0 state */ hsotg->lx_state = DWC2_L0; } else { - if (hsotg->core_params->hibernation) { - dwc2_writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); + if (hsotg->core_params->hibernation) return; - } + if (hsotg->lx_state != DWC2_L1) { u32 pcgcctl = dwc2_readl(hsotg->regs + PCGCTL); @@ -385,9 +386,6 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) hsotg->lx_state = DWC2_L0; } } - - /* Clear interrupt */ - dwc2_writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); } /* @@ -396,14 +394,14 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) */ static void dwc2_handle_disconnect_intr(struct dwc2_hsotg *hsotg) { + dwc2_writel(GINTSTS_DISCONNINT, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "++Disconnect Detected Interrupt++ (%s) %s\n", dwc2_is_host_mode(hsotg) ? "Host" : "Device", dwc2_op_state_str(hsotg)); if (hsotg->op_state == OTG_STATE_A_HOST) dwc2_hcd_disconnect(hsotg, false); - - dwc2_writel(GINTSTS_DISCONNINT, hsotg->regs + GINTSTS); } /* @@ -419,6 +417,9 @@ static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) u32 dsts; int ret; + /* Clear interrupt */ + dwc2_writel(GINTSTS_USBSUSP, hsotg->regs + GINTSTS); + dev_dbg(hsotg->dev, "USB SUSPEND\n"); if (dwc2_is_device_mode(hsotg)) { @@ -437,7 +438,7 @@ static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) if (!dwc2_is_device_connected(hsotg)) { dev_dbg(hsotg->dev, "ignore suspend request before enumeration\n"); - goto clear_int; + return; } ret = dwc2_enter_hibernation(hsotg); @@ -476,10 +477,6 @@ skip_power_saving: hsotg->op_state = OTG_STATE_A_HOST; } } - -clear_int: - /* Clear interrupt */ - dwc2_writel(GINTSTS_USBSUSP, hsotg->regs + GINTSTS); } #define GINTMSK_COMMON (GINTSTS_WKUPINT | GINTSTS_SESSREQINT | \ diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index a1eb48e54858..f8253803a050 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -122,6 +122,9 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) struct dwc2_qh *qh; enum dwc2_transaction_type tr_type; + /* Clear interrupt */ + dwc2_writel(GINTSTS_SOF, hsotg->regs + GINTSTS); + #ifdef DEBUG_SOF dev_vdbg(hsotg->dev, "--Start of Frame Interrupt--\n"); #endif @@ -146,9 +149,6 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) tr_type = dwc2_hcd_select_transactions(hsotg); if (tr_type != DWC2_TRANSACTION_NONE) dwc2_hcd_queue_transactions(hsotg, tr_type); - - /* Clear interrupt */ - dwc2_writel(GINTSTS_SOF, hsotg->regs + GINTSTS); } /* @@ -312,6 +312,7 @@ static void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, if (do_reset) { *hprt0_modify |= HPRT0_RST; + dwc2_writel(*hprt0_modify, hsotg->regs + HPRT0); queue_delayed_work(hsotg->wq_otg, &hsotg->reset_work, msecs_to_jiffies(60)); } else { @@ -347,11 +348,12 @@ static void dwc2_port_intr(struct dwc2_hsotg *hsotg) * Set flag and clear if detected */ if (hprt0 & HPRT0_CONNDET) { + dwc2_writel(hprt0_modify | HPRT0_CONNDET, hsotg->regs + HPRT0); + dev_vdbg(hsotg->dev, "--Port Interrupt HPRT0=0x%08x Port Connect Detected--\n", hprt0); dwc2_hcd_connect(hsotg); - hprt0_modify |= HPRT0_CONNDET; /* * The Hub driver asserts a reset when it sees port connect @@ -364,10 +366,10 @@ static void dwc2_port_intr(struct dwc2_hsotg *hsotg) * Clear if detected - Set internal flag if disabled */ if (hprt0 & HPRT0_ENACHG) { + dwc2_writel(hprt0_modify | HPRT0_ENACHG, hsotg->regs + HPRT0); dev_vdbg(hsotg->dev, " --Port Interrupt HPRT0=0x%08x Port Enable Changed (now %d)--\n", hprt0, !!(hprt0 & HPRT0_ENA)); - hprt0_modify |= HPRT0_ENACHG; if (hprt0 & HPRT0_ENA) { hsotg->new_connection = true; dwc2_hprt0_enable(hsotg, hprt0, &hprt0_modify); @@ -387,15 +389,13 @@ static void dwc2_port_intr(struct dwc2_hsotg *hsotg) /* Overcurrent Change Interrupt */ if (hprt0 & HPRT0_OVRCURRCHG) { + dwc2_writel(hprt0_modify | HPRT0_OVRCURRCHG, + hsotg->regs + HPRT0); dev_vdbg(hsotg->dev, " --Port Interrupt HPRT0=0x%08x Port Overcurrent Changed--\n", hprt0); hsotg->flags.b.port_over_current_change = 1; - hprt0_modify |= HPRT0_OVRCURRCHG; } - - /* Clear Port Interrupts */ - dwc2_writel(hprt0_modify, hsotg->regs + HPRT0); } /* -- cgit v1.2.3 From 3ff4b5733ba70f77b84272b01bfe5937218b1301 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Fri, 27 Nov 2015 11:38:21 +0100 Subject: usb: musb: convert printk to pr_* This file already uses pr_debug in a few places; this converts the remaining printks. Signed-off-by: Rasmus Villemoes Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_core.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index ee9ff7028b92..cd37e9fc2e62 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1360,8 +1360,7 @@ static int ep_config_from_table(struct musb *musb) break; } - printk(KERN_DEBUG "%s: setup fifo_mode %d\n", - musb_driver_name, fifo_mode); + pr_debug("%s: setup fifo_mode %d\n", musb_driver_name, fifo_mode); done: @@ -1390,7 +1389,7 @@ done: musb->nr_endpoints = max(epn, musb->nr_endpoints); } - printk(KERN_DEBUG "%s: %d/%d max ep, %d/%d memory\n", + pr_debug("%s: %d/%d max ep, %d/%d memory\n", musb_driver_name, n + 1, musb->config->num_eps * 2 - 1, offset, (1 << (musb->config->ram_bits + 2))); @@ -1491,8 +1490,7 @@ static int musb_core_init(u16 musb_type, struct musb *musb) if (reg & MUSB_CONFIGDATA_SOFTCONE) strcat(aInfo, ", SoftConn"); - printk(KERN_DEBUG "%s: ConfigData=0x%02x (%s)\n", - musb_driver_name, reg, aInfo); + pr_debug("%s: ConfigData=0x%02x (%s)\n", musb_driver_name, reg, aInfo); aDate[0] = 0; if (MUSB_CONTROLLER_MHDRC == musb_type) { @@ -1502,9 +1500,8 @@ static int musb_core_init(u16 musb_type, struct musb *musb) musb->is_multipoint = 0; type = ""; #ifndef CONFIG_USB_OTG_BLACKLIST_HUB - printk(KERN_ERR - "%s: kernel must blacklist external hubs\n", - musb_driver_name); + pr_err("%s: kernel must blacklist external hubs\n", + musb_driver_name); #endif } @@ -1513,8 +1510,8 @@ static int musb_core_init(u16 musb_type, struct musb *musb) snprintf(aRevision, 32, "%d.%d%s", MUSB_HWVERS_MAJOR(musb->hwvers), MUSB_HWVERS_MINOR(musb->hwvers), (musb->hwvers & MUSB_HWVERS_RC) ? "RC" : ""); - printk(KERN_DEBUG "%s: %sHDRC RTL version %s %s\n", - musb_driver_name, type, aRevision, aDate); + pr_debug("%s: %sHDRC RTL version %s %s\n", + musb_driver_name, type, aRevision, aDate); /* configure ep0 */ musb_configure_ep0(musb); -- cgit v1.2.3 From 04c03d10e507052cfce6910ddf34091196e79e1c Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 2 Dec 2015 10:06:45 -0600 Subject: usb: dwc3: gadget: handle request->zero So far, dwc3 has always missed request->zero handling for every endpoint. Let's implement that so we can handle cases where transfer must be finished with a ZLP. Note that dwc3 is a little special. Even though we're dealing with a ZLP, we still need a buffer of wMaxPacketSize bytes; to hide that detail from every gadget driver, we have a preallocated buffer of 1024 bytes (biggest bulk size) to use (and share) among all endpoints. Reported-by: Ravi B Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 3 +++ drivers/usb/dwc3/gadget.c | 50 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 36f1cb74588c..29130682e547 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -37,6 +37,7 @@ #define DWC3_MSG_MAX 500 /* Global constants */ +#define DWC3_ZLP_BUF_SIZE 1024 /* size of a superspeed bulk */ #define DWC3_EP0_BOUNCE_SIZE 512 #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 @@ -647,6 +648,7 @@ struct dwc3_scratchpad_array { * @ctrl_req: usb control request which is used for ep0 * @ep0_trb: trb which is used for the ctrl_req * @ep0_bounce: bounce buffer for ep0 + * @zlp_buf: used when request->zero is set * @setup_buf: used while precessing STD USB requests * @ctrl_req_addr: dma address of ctrl_req * @ep0_trb: dma address of ep0_trb @@ -734,6 +736,7 @@ struct dwc3 { struct usb_ctrlrequest *ctrl_req; struct dwc3_trb *ep0_trb; void *ep0_bounce; + void *zlp_buf; void *scratchbuf; u8 *setup_buf; dma_addr_t ctrl_req_addr; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index e4203b79908a..975801839355 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1159,6 +1159,32 @@ out: return ret; } +static void __dwc3_gadget_ep_zlp_complete(struct usb_ep *ep, + struct usb_request *request) +{ + dwc3_gadget_ep_free_request(ep, request); +} + +static int __dwc3_gadget_ep_queue_zlp(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_request *req; + struct usb_request *request; + struct usb_ep *ep = &dep->endpoint; + + dwc3_trace(trace_dwc3_gadget, "queueing ZLP\n"); + request = dwc3_gadget_ep_alloc_request(ep, GFP_ATOMIC); + if (!request) + return -ENOMEM; + + request->length = 0; + request->buf = dwc->zlp_buf; + request->complete = __dwc3_gadget_ep_zlp_complete; + + req = to_dwc3_request(request); + + return __dwc3_gadget_ep_queue(dep, req); +} + static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, gfp_t gfp_flags) { @@ -1172,6 +1198,16 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_queue(dep, req); + + /* + * Okay, here's the thing, if gadget driver has requested for a ZLP by + * setting request->zero, instead of doing magic, we will just queue an + * extra usb_request ourselves so that it gets handled the same way as + * any other request. + */ + if (ret == 0 && request->zero && (request->length % ep->maxpacket == 0)) + ret = __dwc3_gadget_ep_queue_zlp(dwc, dep); + spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -2744,6 +2780,12 @@ int dwc3_gadget_init(struct dwc3 *dwc) goto err3; } + dwc->zlp_buf = kzalloc(DWC3_ZLP_BUF_SIZE, GFP_KERNEL); + if (!dwc->zlp_buf) { + ret = -ENOMEM; + goto err4; + } + dwc->gadget.ops = &dwc3_gadget_ops; dwc->gadget.speed = USB_SPEED_UNKNOWN; dwc->gadget.sg_supported = true; @@ -2785,16 +2827,19 @@ int dwc3_gadget_init(struct dwc3 *dwc) ret = dwc3_gadget_init_endpoints(dwc); if (ret) - goto err4; + goto err5; ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); if (ret) { dev_err(dwc->dev, "failed to register udc\n"); - goto err4; + goto err5; } return 0; +err5: + kfree(dwc->zlp_buf); + err4: dwc3_gadget_free_endpoints(dwc); dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE, @@ -2827,6 +2872,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc) dwc->ep0_bounce, dwc->ep0_bounce_addr); kfree(dwc->setup_buf); + kfree(dwc->zlp_buf); dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb), dwc->ep0_trb, dwc->ep0_trb_addr); -- cgit v1.2.3 From 46a01427e969e53d993b988ebbdb8c7c3562b66f Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 3 Dec 2015 15:27:32 -0600 Subject: usb: dwc3: trace: show request flags struct usb_request have 3 flags which might be important to know about during debug. This patch shows each of the 3 flags as a single letter: z -> for zero s -> short not okay i -> interrupt A capital letter means the feature is enabled while a lower case letter means it is disabled; Thus 'zsI' indicates that a ZLP is not needed, that we can accept a short packet and interrupt for this request should be enabled. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/trace.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 9c10669ab91f..3ac7252f4427 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -117,6 +117,9 @@ DECLARE_EVENT_CLASS(dwc3_log_request, __field(unsigned, actual) __field(unsigned, length) __field(int, status) + __field(int, zero) + __field(int, short_not_ok) + __field(int, no_interrupt) ), TP_fast_assign( snprintf(__get_str(name), DWC3_MSG_MAX, "%s", req->dep->name); @@ -124,9 +127,15 @@ DECLARE_EVENT_CLASS(dwc3_log_request, __entry->actual = req->request.actual; __entry->length = req->request.length; __entry->status = req->request.status; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; ), - TP_printk("%s: req %p length %u/%u ==> %d", + TP_printk("%s: req %p length %u/%u %s%s%s ==> %d", __get_str(name), __entry->req, __entry->actual, __entry->length, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "i" : "I", __entry->status ) ); -- cgit v1.2.3 From 2de59c09feb735874ea38c29d396b13e46dd0a69 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 8 Dec 2015 02:31:13 +0200 Subject: usb: gadget: lpc32xxx_udc: clean up and sort include directives out Remove mach/irq.h from the list of included headers, there is no compilation dependency on this include file, and the change is needed to prevent a compilation failure, when mach/irq.h is removed. Additionally remove other unneeded includes and sort out their order. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/lpc32xx_udc.c | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 00b5006baf15..79fe6b77ee44 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -28,42 +28,29 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include -#include -#include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include #include -#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include - -#include -#include -#include #ifdef CONFIG_USB_GADGET_DEBUG_FILES #include #include #endif +#include +#include + /* * USB device configuration structure */ -- cgit v1.2.3 From 375da6271b685e97d2d936fffa6e405b93674c26 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 4 Dec 2015 17:04:25 +0100 Subject: usb: phy: Remove unused Renesas R-Car (Gen1) USB PHY driver As of commit 3d7608e4c169af03 ("ARM: shmobile: bockw: remove legacy board file and config"), the Renesas R-Car (Gen1) USB PHY driver is no longer used. In theory it could still be used on R-Car Gen1 SoCs, but that would require adding DT support to the driver. Instead, a new driver using the generic PHY framework should be written, as was done for R-Car Gen2. Remove the driver for good. Acked-by: Simon Horman Signed-off-by: Geert Uytterhoeven Signed-off-by: Felipe Balbi --- drivers/usb/phy/Kconfig | 13 -- drivers/usb/phy/Makefile | 1 - drivers/usb/phy/phy-rcar-usb.c | 247 ----------------------------- include/linux/platform_data/usb-rcar-phy.h | 28 ---- 4 files changed, 289 deletions(-) delete mode 100644 drivers/usb/phy/phy-rcar-usb.c delete mode 100644 include/linux/platform_data/usb-rcar-phy.h (limited to 'drivers') diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 22e8ecb6bfbd..155694c1a536 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -186,19 +186,6 @@ config USB_MXS_PHY MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x. -config USB_RCAR_PHY - tristate "Renesas R-Car USB PHY support" - depends on USB || USB_GADGET - depends on ARCH_R8A7778 || ARCH_R8A7779 || COMPILE_TEST - select USB_PHY - help - Say Y here to add support for the Renesas R-Car USB common PHY driver. - This chip is typically used as USB PHY for USB host, gadget. - This driver supports R8A7778 and R8A7779. - - To compile this driver as a module, choose M here: the - module will be called phy-rcar-usb. - config USB_ULPI bool "Generic ULPI Transceiver Driver" depends on ARM || ARM64 diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 19c0dccbb116..b433e5d89be4 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -23,7 +23,6 @@ obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o obj-$(CONFIG_USB_QCOM_8X16_PHY) += phy-qcom-8x16-usb.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o -obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o diff --git a/drivers/usb/phy/phy-rcar-usb.c b/drivers/usb/phy/phy-rcar-usb.c deleted file mode 100644 index 1e09b8377885..000000000000 --- a/drivers/usb/phy/phy-rcar-usb.c +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Renesas R-Car USB phy driver - * - * Copyright (C) 2012-2013 Renesas Solutions Corp. - * Kuninori Morimoto - * Copyright (C) 2013 Cogent Embedded, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include - -/* REGS block */ -#define USBPCTRL0 0x00 -#define USBPCTRL1 0x04 -#define USBST 0x08 -#define USBEH0 0x0C -#define USBOH0 0x1C -#define USBCTL0 0x58 - -/* High-speed signal quality characteristic control registers (R8A7778 only) */ -#define HSQCTL1 0x24 -#define HSQCTL2 0x28 - -/* USBPCTRL0 */ -#define OVC2 (1 << 10) /* (R8A7779 only) */ - /* Switches the OVC input pin for port 2: */ - /* 1: USB_OVC2, 0: OVC2 */ -#define OVC1_VBUS1 (1 << 9) /* Switches the OVC input pin for port 1: */ - /* 1: USB_OVC1, 0: OVC1/VBUS1 */ - /* Function mode: set to 0 */ -#define OVC0 (1 << 8) /* Switches the OVC input pin for port 0: */ - /* 1: USB_OVC0 pin, 0: OVC0 */ -#define OVC2_ACT (1 << 6) /* (R8A7779 only) */ - /* Host mode: OVC2 polarity: */ - /* 1: active-high, 0: active-low */ -#define PENC (1 << 4) /* Function mode: output level of PENC1 pin: */ - /* 1: high, 0: low */ -#define OVC0_ACT (1 << 3) /* Host mode: OVC0 polarity: */ - /* 1: active-high, 0: active-low */ -#define OVC1_ACT (1 << 1) /* Host mode: OVC1 polarity: */ - /* 1: active-high, 0: active-low */ - /* Function mode: be sure to set to 1 */ -#define PORT1 (1 << 0) /* Selects port 1 mode: */ - /* 1: function, 0: host */ -/* USBPCTRL1 */ -#define PHY_RST (1 << 2) -#define PLL_ENB (1 << 1) -#define PHY_ENB (1 << 0) - -/* USBST */ -#define ST_ACT (1 << 31) -#define ST_PLL (1 << 30) - -struct rcar_usb_phy_priv { - struct usb_phy phy; - spinlock_t lock; - - void __iomem *reg0; - void __iomem *reg1; - int counter; -}; - -#define usb_phy_to_priv(p) container_of(p, struct rcar_usb_phy_priv, phy) - - -/* - * USB initial/install operation. - * - * This function setup USB phy. - * The used value and setting order came from - * [USB :: Initial setting] on datasheet. - */ -static int rcar_usb_phy_init(struct usb_phy *phy) -{ - struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy); - struct device *dev = phy->dev; - struct rcar_phy_platform_data *pdata = dev_get_platdata(dev); - void __iomem *reg0 = priv->reg0; - void __iomem *reg1 = priv->reg1; - static const u8 ovcn_act[] = { OVC0_ACT, OVC1_ACT, OVC2_ACT }; - int i; - u32 val; - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); - if (priv->counter++ == 0) { - - /* - * USB phy start-up - */ - - /* (1) USB-PHY standby release */ - iowrite32(PHY_ENB, (reg0 + USBPCTRL1)); - - /* (2) start USB-PHY internal PLL */ - iowrite32(PHY_ENB | PLL_ENB, (reg0 + USBPCTRL1)); - - /* (3) set USB-PHY in accord with the conditions of usage */ - if (reg1) { - u32 hsqctl1 = pdata->ferrite_bead ? 0x41 : 0; - u32 hsqctl2 = pdata->ferrite_bead ? 0x0d : 7; - - iowrite32(hsqctl1, reg1 + HSQCTL1); - iowrite32(hsqctl2, reg1 + HSQCTL2); - } - - /* (4) USB module status check */ - for (i = 0; i < 1024; i++) { - udelay(10); - val = ioread32(reg0 + USBST); - if (val == (ST_ACT | ST_PLL)) - break; - } - - if (val != (ST_ACT | ST_PLL)) { - dev_err(dev, "USB phy not ready\n"); - goto phy_init_end; - } - - /* (5) USB-PHY reset clear */ - iowrite32(PHY_ENB | PLL_ENB | PHY_RST, (reg0 + USBPCTRL1)); - - /* Board specific port settings */ - val = 0; - if (pdata->port1_func) - val |= PORT1; - if (pdata->penc1) - val |= PENC; - for (i = 0; i < 3; i++) { - /* OVCn bits follow each other in the right order */ - if (pdata->ovc_pin[i].select_3_3v) - val |= OVC0 << i; - /* OVCn_ACT bits are spaced by irregular intervals */ - if (pdata->ovc_pin[i].active_high) - val |= ovcn_act[i]; - } - iowrite32(val, (reg0 + USBPCTRL0)); - - /* - * Bus alignment settings - */ - - /* (1) EHCI bus alignment (little endian) */ - iowrite32(0x00000000, (reg0 + USBEH0)); - - /* (1) OHCI bus alignment (little endian) */ - iowrite32(0x00000000, (reg0 + USBOH0)); - } - -phy_init_end: - spin_unlock_irqrestore(&priv->lock, flags); - - return 0; -} - -static void rcar_usb_phy_shutdown(struct usb_phy *phy) -{ - struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy); - void __iomem *reg0 = priv->reg0; - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); - - if (priv->counter-- == 1) /* last user */ - iowrite32(0x00000000, (reg0 + USBPCTRL1)); - - spin_unlock_irqrestore(&priv->lock, flags); -} - -static int rcar_usb_phy_probe(struct platform_device *pdev) -{ - struct rcar_usb_phy_priv *priv; - struct resource *res0, *res1; - struct device *dev = &pdev->dev; - void __iomem *reg0, *reg1 = NULL; - int ret; - - if (!dev_get_platdata(&pdev->dev)) { - dev_err(dev, "No platform data\n"); - return -EINVAL; - } - - res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); - reg0 = devm_ioremap_resource(dev, res0); - if (IS_ERR(reg0)) - return PTR_ERR(reg0); - - res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); - reg1 = devm_ioremap_resource(dev, res1); - if (IS_ERR(reg1)) - return PTR_ERR(reg1); - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->reg0 = reg0; - priv->reg1 = reg1; - priv->counter = 0; - priv->phy.dev = dev; - priv->phy.label = dev_name(dev); - priv->phy.init = rcar_usb_phy_init; - priv->phy.shutdown = rcar_usb_phy_shutdown; - spin_lock_init(&priv->lock); - - ret = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2); - if (ret < 0) { - dev_err(dev, "usb phy addition error\n"); - return ret; - } - - platform_set_drvdata(pdev, priv); - - return ret; -} - -static int rcar_usb_phy_remove(struct platform_device *pdev) -{ - struct rcar_usb_phy_priv *priv = platform_get_drvdata(pdev); - - usb_remove_phy(&priv->phy); - - return 0; -} - -static struct platform_driver rcar_usb_phy_driver = { - .driver = { - .name = "rcar_usb_phy", - }, - .probe = rcar_usb_phy_probe, - .remove = rcar_usb_phy_remove, -}; - -module_platform_driver(rcar_usb_phy_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Renesas R-Car USB phy"); -MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/include/linux/platform_data/usb-rcar-phy.h b/include/linux/platform_data/usb-rcar-phy.h deleted file mode 100644 index 8ec6964a32a5..000000000000 --- a/include/linux/platform_data/usb-rcar-phy.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2013 Renesas Solutions Corp. - * Copyright (C) 2013 Cogent Embedded, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef __USB_RCAR_PHY_H -#define __USB_RCAR_PHY_H - -#include - -struct rcar_phy_platform_data { - bool ferrite_bead:1; /* (R8A7778 only) */ - - bool port1_func:1; /* true: port 1 used by function, false: host */ - unsigned penc1:1; /* Output of the PENC1 pin in function mode */ - struct { /* Overcurrent pin control for ports 0..2 */ - bool select_3_3v:1; /* true: USB_OVCn pin, false: OVCn pin */ - /* Set to false on port 1 in function mode */ - bool active_high:1; /* true: active high, false: active low */ - /* Set to true on port 1 in function mode */ - } ovc_pin[3]; /* (R8A7778 only has 2 ports) */ -}; - -#endif /* __USB_RCAR_PHY_H */ -- cgit v1.2.3 From 5306661eff1a70f99456340eddf8e0cf85c2e8af Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Fri, 20 Nov 2015 16:13:07 -0600 Subject: usb: phy: correct the am335x phy header filename The filename of am35x-phy-control.h is confusing. The header is used by the am335x phy driver, but the filename refers to am35x. Even worse there is indeed another device called am35x but it does not use this header at all. Signed-off-by: Bin Liu Signed-off-by: Felipe Balbi --- drivers/usb/phy/am35x-phy-control.h | 21 --------------------- drivers/usb/phy/phy-am335x-control.c | 2 +- drivers/usb/phy/phy-am335x-control.h | 21 +++++++++++++++++++++ drivers/usb/phy/phy-am335x.c | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) delete mode 100644 drivers/usb/phy/am35x-phy-control.h create mode 100644 drivers/usb/phy/phy-am335x-control.h (limited to 'drivers') diff --git a/drivers/usb/phy/am35x-phy-control.h b/drivers/usb/phy/am35x-phy-control.h deleted file mode 100644 index b96594d1962c..000000000000 --- a/drivers/usb/phy/am35x-phy-control.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef _AM335x_PHY_CONTROL_H_ -#define _AM335x_PHY_CONTROL_H_ - -struct phy_control { - void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on); - void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on); -}; - -static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, bool on) -{ - phy_ctrl->phy_power(phy_ctrl, id, on); -} - -static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on) -{ - phy_ctrl->phy_wkup(phy_ctrl, id, on); -} - -struct phy_control *am335x_get_phy_control(struct device *dev); - -#endif diff --git a/drivers/usb/phy/phy-am335x-control.c b/drivers/usb/phy/phy-am335x-control.c index 7b3035ff9434..23fca5192a6b 100644 --- a/drivers/usb/phy/phy-am335x-control.c +++ b/drivers/usb/phy/phy-am335x-control.c @@ -4,7 +4,7 @@ #include #include #include -#include "am35x-phy-control.h" +#include "phy-am335x-control.h" struct am335x_control_usb { struct device *dev; diff --git a/drivers/usb/phy/phy-am335x-control.h b/drivers/usb/phy/phy-am335x-control.h new file mode 100644 index 000000000000..b96594d1962c --- /dev/null +++ b/drivers/usb/phy/phy-am335x-control.h @@ -0,0 +1,21 @@ +#ifndef _AM335x_PHY_CONTROL_H_ +#define _AM335x_PHY_CONTROL_H_ + +struct phy_control { + void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on); + void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on); +}; + +static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, bool on) +{ + phy_ctrl->phy_power(phy_ctrl, id, on); +} + +static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on) +{ + phy_ctrl->phy_wkup(phy_ctrl, id, on); +} + +struct phy_control *am335x_get_phy_control(struct device *dev); + +#endif diff --git a/drivers/usb/phy/phy-am335x.c b/drivers/usb/phy/phy-am335x.c index 90b67a4ca221..8b6139dec5eb 100644 --- a/drivers/usb/phy/phy-am335x.c +++ b/drivers/usb/phy/phy-am335x.c @@ -9,7 +9,7 @@ #include #include -#include "am35x-phy-control.h" +#include "phy-am335x-control.h" #include "phy-generic.h" struct am335x_phy { -- cgit v1.2.3 From 59f042f644c5aa10b65b7881966bed78c5c82923 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 8 Dec 2015 10:31:50 -0600 Subject: usb: phy: phy-am335x: bypass first VBUS sensing for host-only mode To prevent VBUS contention, the am335x MUSB phy senses VBUS first before transitioning to host mode. However, for host-only mode, VBUS could be directly tied to 5V power rail which could prevent MUSB transitions to host mode. This change receives dr_mode of the controller then bypass the first VBUS sensing for host-only mode, so that MUSB can work in host mode event if VBUS is tied to 5V. Signed-off-by: Bin Liu Signed-off-by: Felipe Balbi --- drivers/usb/phy/Kconfig | 1 + drivers/usb/phy/phy-am335x-control.c | 14 +++++++++++--- drivers/usb/phy/phy-am335x-control.h | 8 +++++--- drivers/usb/phy/phy-am335x.c | 15 ++++++++++----- 4 files changed, 27 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 155694c1a536..c6904742e2aa 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -66,6 +66,7 @@ config AM335X_PHY_USB select USB_PHY select AM335X_CONTROL_USB select NOP_USB_XCEIV + select USB_COMMON help This driver provides PHY support for that phy which part for the AM335x SoC. diff --git a/drivers/usb/phy/phy-am335x-control.c b/drivers/usb/phy/phy-am335x-control.c index 23fca5192a6b..42a1afe36a90 100644 --- a/drivers/usb/phy/phy-am335x-control.c +++ b/drivers/usb/phy/phy-am335x-control.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "phy-am335x-control.h" struct am335x_control_usb { @@ -58,7 +59,8 @@ static void am335x_phy_wkup(struct phy_control *phy_ctrl, u32 id, bool on) spin_unlock(&usb_ctrl->lock); } -static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, bool on) +static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on) { struct am335x_control_usb *usb_ctrl; u32 val; @@ -80,8 +82,14 @@ static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, bool on) val = readl(usb_ctrl->phy_reg + reg); if (on) { - val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN); - val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN; + if (dr_mode == USB_DR_MODE_HOST) { + val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN | + USBPHY_OTGVDET_EN); + val |= USBPHY_OTGSESSEND_EN; + } else { + val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN); + val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN; + } } else { val |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN; } diff --git a/drivers/usb/phy/phy-am335x-control.h b/drivers/usb/phy/phy-am335x-control.h index b96594d1962c..e86b3165d69d 100644 --- a/drivers/usb/phy/phy-am335x-control.h +++ b/drivers/usb/phy/phy-am335x-control.h @@ -2,13 +2,15 @@ #define _AM335x_PHY_CONTROL_H_ struct phy_control { - void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on); + void (*phy_power)(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on); void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on); }; -static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, bool on) +static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on) { - phy_ctrl->phy_power(phy_ctrl, id, on); + phy_ctrl->phy_power(phy_ctrl, id, dr_mode, on); } static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on) diff --git a/drivers/usb/phy/phy-am335x.c b/drivers/usb/phy/phy-am335x.c index 8b6139dec5eb..39b424f7f629 100644 --- a/drivers/usb/phy/phy-am335x.c +++ b/drivers/usb/phy/phy-am335x.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "phy-am335x-control.h" #include "phy-generic.h" @@ -16,13 +17,14 @@ struct am335x_phy { struct usb_phy_generic usb_phy_gen; struct phy_control *phy_ctrl; int id; + enum usb_dr_mode dr_mode; }; static int am335x_init(struct usb_phy *phy) { struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); return 0; } @@ -30,7 +32,7 @@ static void am335x_shutdown(struct usb_phy *phy) { struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); } static int am335x_phy_probe(struct platform_device *pdev) @@ -46,12 +48,15 @@ static int am335x_phy_probe(struct platform_device *pdev) am_phy->phy_ctrl = am335x_get_phy_control(dev); if (!am_phy->phy_ctrl) return -EPROBE_DEFER; + am_phy->id = of_alias_get_id(pdev->dev.of_node, "phy"); if (am_phy->id < 0) { dev_err(&pdev->dev, "Missing PHY id: %d\n", am_phy->id); return am_phy->id; } + am_phy->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node); + ret = usb_phy_gen_create_phy(dev, &am_phy->usb_phy_gen, NULL); if (ret) return ret; @@ -75,7 +80,7 @@ static int am335x_phy_probe(struct platform_device *pdev) */ device_set_wakeup_enable(dev, false); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); return 0; } @@ -105,7 +110,7 @@ static int am335x_phy_suspend(struct device *dev) if (device_may_wakeup(dev)) phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, true); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); return 0; } @@ -115,7 +120,7 @@ static int am335x_phy_resume(struct device *dev) struct platform_device *pdev = to_platform_device(dev); struct am335x_phy *am_phy = platform_get_drvdata(pdev); - phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); if (device_may_wakeup(dev)) phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, false); -- cgit v1.2.3 From 2284b29d3d9dd16490909962574d7f3fef83fd97 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Mon, 23 Nov 2015 09:56:35 +0100 Subject: usb: gadget: bind UDC by name passed via usb_gadget_driver structure Introduce new 'udc_name' member to usb_gadget_driver structure. The 'udc_name' is a name of UDC that usb_gadget_driver should be bound to. If udc_name is NULL, it will be bound to any available UDC. Tested-by: Maxime Ripard Signed-off-by: Ruslan Bilovol Signed-off-by: Marek Szyprowski Tested-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/udc-core.c | 24 +++++++++++++++++++----- include/linux/usb/gadget.h | 4 ++++ 2 files changed, 23 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/udc-core.c b/drivers/usb/gadget/udc/udc-core.c index f660afba715d..429d64e67941 100644 --- a/drivers/usb/gadget/udc/udc-core.c +++ b/drivers/usb/gadget/udc/udc-core.c @@ -549,21 +549,35 @@ EXPORT_SYMBOL_GPL(usb_udc_attach_driver); int usb_gadget_probe_driver(struct usb_gadget_driver *driver) { struct usb_udc *udc = NULL; - int ret; + int ret = -ENODEV; if (!driver || !driver->bind || !driver->setup) return -EINVAL; mutex_lock(&udc_lock); - list_for_each_entry(udc, &udc_list, list) { - /* For now we take the first one */ - if (!udc->driver) + if (driver->udc_name) { + list_for_each_entry(udc, &udc_list, list) { + ret = strcmp(driver->udc_name, dev_name(&udc->dev)); + if (!ret) + break; + } + if (ret) + ret = -ENODEV; + else if (udc->driver) + ret = -EBUSY; + else goto found; + } else { + list_for_each_entry(udc, &udc_list, list) { + /* For now we take the first one */ + if (!udc->driver) + goto found; + } } pr_debug("couldn't find an available UDC\n"); mutex_unlock(&udc_lock); - return -ENODEV; + return ret; found: ret = udc_bind_to_driver(udc, driver); mutex_unlock(&udc_lock); diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 3d583a10b926..63963c21866d 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -1012,6 +1012,8 @@ static inline int usb_gadget_activate(struct usb_gadget *gadget) * @reset: Invoked on USB bus reset. It is mandatory for all gadget drivers * and should be called in_interrupt. * @driver: Driver model state for this driver. + * @udc_name: A name of UDC this driver should be bound to. If udc_name is NULL, + * this driver will be bound to any available UDC. * * Devices are disabled till a gadget driver successfully bind()s, which * means the driver will handle setup() requests needed to enumerate (and @@ -1072,6 +1074,8 @@ struct usb_gadget_driver { /* FIXME support safe rmmod */ struct device_driver driver; + + char *udc_name; }; -- cgit v1.2.3 From afdaadc3c8530b4bc20777bc6ec15bda89b3bd65 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Mon, 23 Nov 2015 09:56:36 +0100 Subject: usb: gadget: configfs: pass UDC name via usb_gadget_driver struct Now when udc-core supports binding to specific UDC by passing its name via 'udc_name' member of usb_gadget_driver struct, switch to this generic approach. Tested-by: Maxime Ripard Signed-off-by: Ruslan Bilovol [rebased and fixed checkpatch issues] Signed-off-by: Marek Szyprowski Tested-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/configfs.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 163d305e1200..590c44989e5e 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -56,7 +56,6 @@ struct gadget_info { struct list_head string_list; struct list_head available_func; - const char *udc_name; struct usb_composite_driver composite; struct usb_composite_dev cdev; bool use_os_desc; @@ -233,21 +232,23 @@ static ssize_t gadget_dev_desc_bcdUSB_store(struct config_item *item, static ssize_t gadget_dev_desc_UDC_show(struct config_item *item, char *page) { - return sprintf(page, "%s\n", to_gadget_info(item)->udc_name ?: ""); + char *udc_name = to_gadget_info(item)->composite.gadget_driver.udc_name; + + return sprintf(page, "%s\n", udc_name ?: ""); } static int unregister_gadget(struct gadget_info *gi) { int ret; - if (!gi->udc_name) + if (!gi->composite.gadget_driver.udc_name) return -ENODEV; ret = usb_gadget_unregister_driver(&gi->composite.gadget_driver); if (ret) return ret; - kfree(gi->udc_name); - gi->udc_name = NULL; + kfree(gi->composite.gadget_driver.udc_name); + gi->composite.gadget_driver.udc_name = NULL; return 0; } @@ -271,14 +272,16 @@ static ssize_t gadget_dev_desc_UDC_store(struct config_item *item, if (ret) goto err; } else { - if (gi->udc_name) { + if (gi->composite.gadget_driver.udc_name) { ret = -EBUSY; goto err; } - ret = usb_udc_attach_driver(name, &gi->composite.gadget_driver); - if (ret) + gi->composite.gadget_driver.udc_name = name; + ret = usb_gadget_probe_driver(&gi->composite.gadget_driver); + if (ret) { + gi->composite.gadget_driver.udc_name = NULL; goto err; - gi->udc_name = name; + } } mutex_unlock(&gi->lock); return len; @@ -427,9 +430,9 @@ static int config_usb_cfg_unlink( * remove the function. */ mutex_lock(&gi->lock); - if (gi->udc_name) + if (gi->composite.gadget_driver.udc_name) unregister_gadget(gi); - WARN_ON(gi->udc_name); + WARN_ON(gi->composite.gadget_driver.udc_name); list_for_each_entry(f, &cfg->func_list, list) { if (f->fi == fi) { @@ -873,10 +876,10 @@ static int os_desc_unlink(struct config_item *os_desc_ci, struct usb_composite_dev *cdev = &gi->cdev; mutex_lock(&gi->lock); - if (gi->udc_name) + if (gi->composite.gadget_driver.udc_name) unregister_gadget(gi); cdev->os_desc_config = NULL; - WARN_ON(gi->udc_name); + WARN_ON(gi->composite.gadget_driver.udc_name); mutex_unlock(&gi->lock); return 0; } -- cgit v1.2.3 From 88f73ebdfa75602af18e070a4d5d6d9091bcfada Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Mon, 23 Nov 2015 09:56:37 +0100 Subject: usb: gadget: udc-core: remove unused usb_udc_attach_driver() Now when last user of usb_udc_attach_driver() is switched to passing UDC name via usb_gadget_driver struct, it's safe to remove this function Tested-by: Maxime Ripard Signed-off-by: Ruslan Bilovol Signed-off-by: Marek Szyprowski Tested-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/udc-core.c | 26 -------------------------- include/linux/usb/gadget.h | 2 -- 2 files changed, 28 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/udc-core.c b/drivers/usb/gadget/udc/udc-core.c index 429d64e67941..f76ebc8c1ed2 100644 --- a/drivers/usb/gadget/udc/udc-core.c +++ b/drivers/usb/gadget/udc/udc-core.c @@ -520,32 +520,6 @@ err1: return ret; } -int usb_udc_attach_driver(const char *name, struct usb_gadget_driver *driver) -{ - struct usb_udc *udc = NULL; - int ret = -ENODEV; - - mutex_lock(&udc_lock); - list_for_each_entry(udc, &udc_list, list) { - ret = strcmp(name, dev_name(&udc->dev)); - if (!ret) - break; - } - if (ret) { - ret = -ENODEV; - goto out; - } - if (udc->driver) { - ret = -EBUSY; - goto out; - } - ret = udc_bind_to_driver(udc, driver); -out: - mutex_unlock(&udc_lock); - return ret; -} -EXPORT_SYMBOL_GPL(usb_udc_attach_driver); - int usb_gadget_probe_driver(struct usb_gadget_driver *driver) { struct usb_udc *udc = NULL; diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 63963c21866d..ce2188d338e6 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -1121,8 +1121,6 @@ extern int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)); extern int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget); extern void usb_del_gadget_udc(struct usb_gadget *gadget); -extern int usb_udc_attach_driver(const char *name, - struct usb_gadget_driver *driver); /*-------------------------------------------------------------------------*/ -- cgit v1.2.3 From 855ed04a3758b205e84b269f92d26ab36ed8e2f7 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Mon, 23 Nov 2015 09:56:38 +0100 Subject: usb: gadget: udc-core: independent registration of gadgets and gadget drivers Change behavior during registration of gadgets and gadget drivers in udc-core. Instead of previous approach when for successful probe of usb gadget driver at least one usb gadget should be already registered use another one where gadget drivers and gadgets can be registered in udc-core independently. Independent registration of gadgets and gadget drivers is useful for built-in into kernel gadget and gadget driver case - because it's possible that gadget is really probed only on late_init stage (due to deferred probe) whereas gadget driver's probe is silently failed on module_init stage due to no any UDC added. Also it is useful for modules case - now there is no difference what module to insert first: gadget module or gadget driver one. Tested-by: Maxime Ripard Signed-off-by: Ruslan Bilovol [simplified code as requested by Alan Stern and Felipe Balbi, fixed checkpatch issues] Signed-off-by: Marek Szyprowski Tested-by: Peter Chen Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/udc-core.c | 41 ++++++++++++++++++++++++++++++--------- include/linux/usb/gadget.h | 2 ++ 2 files changed, 34 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/udc-core.c b/drivers/usb/gadget/udc/udc-core.c index f76ebc8c1ed2..fd73a3ea07c2 100644 --- a/drivers/usb/gadget/udc/udc-core.c +++ b/drivers/usb/gadget/udc/udc-core.c @@ -51,8 +51,12 @@ struct usb_udc { static struct class *udc_class; static LIST_HEAD(udc_list); +static LIST_HEAD(gadget_driver_pending_list); static DEFINE_MUTEX(udc_lock); +static int udc_bind_to_driver(struct usb_udc *udc, + struct usb_gadget_driver *driver); + /* ------------------------------------------------------------------------- */ #ifdef CONFIG_HAS_DMA @@ -356,6 +360,7 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)) { struct usb_udc *udc; + struct usb_gadget_driver *driver; int ret = -ENOMEM; udc = kzalloc(sizeof(*udc), GFP_KERNEL); @@ -403,6 +408,18 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); udc->vbus = true; + /* pick up one of pending gadget drivers */ + list_for_each_entry(driver, &gadget_driver_pending_list, pending) { + if (!driver->udc_name || strcmp(driver->udc_name, + dev_name(&udc->dev)) == 0) { + ret = udc_bind_to_driver(udc, driver); + if (ret) + goto err4; + list_del(&driver->pending); + break; + } + } + mutex_unlock(&udc_lock); return 0; @@ -473,10 +490,14 @@ void usb_del_gadget_udc(struct usb_gadget *gadget) mutex_lock(&udc_lock); list_del(&udc->list); - mutex_unlock(&udc_lock); - if (udc->driver) + if (udc->driver) { + struct usb_gadget_driver *driver = udc->driver; + usb_gadget_remove_driver(udc); + list_add(&driver->pending, &gadget_driver_pending_list); + } + mutex_unlock(&udc_lock); kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); flush_work(&gadget->work); @@ -535,11 +556,7 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver) if (!ret) break; } - if (ret) - ret = -ENODEV; - else if (udc->driver) - ret = -EBUSY; - else + if (!ret && !udc->driver) goto found; } else { list_for_each_entry(udc, &udc_list, list) { @@ -549,9 +566,11 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver) } } - pr_debug("couldn't find an available UDC\n"); + list_add_tail(&driver->pending, &gadget_driver_pending_list); + pr_info("udc-core: couldn't find an available UDC - added [%s] to list of pending drivers\n", + driver->function); mutex_unlock(&udc_lock); - return ret; + return 0; found: ret = udc_bind_to_driver(udc, driver); mutex_unlock(&udc_lock); @@ -577,6 +596,10 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) break; } + if (ret) { + list_del(&driver->pending); + ret = 0; + } mutex_unlock(&udc_lock); return ret; } diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index ce2188d338e6..92467eea76de 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -1014,6 +1014,7 @@ static inline int usb_gadget_activate(struct usb_gadget *gadget) * @driver: Driver model state for this driver. * @udc_name: A name of UDC this driver should be bound to. If udc_name is NULL, * this driver will be bound to any available UDC. + * @pending: UDC core private data used for deferred probe of this driver. * * Devices are disabled till a gadget driver successfully bind()s, which * means the driver will handle setup() requests needed to enumerate (and @@ -1076,6 +1077,7 @@ struct usb_gadget_driver { struct device_driver driver; char *udc_name; + struct list_head pending; }; -- cgit v1.2.3 From 95ca961c758cd9ce789247098b09c39017637e58 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 10 Dec 2015 13:08:20 -0600 Subject: usb: dwc3: gadget: pass a condition to dev_WARN_ONCE() instead of using: if (condition) { dev_WARN_ONCE(dev, true, "foo"); return -EINVAL; } let's use: if (dev_WARN_ONCE(dev, condition, "foo")) return -EINVAL; Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 975801839355..64b2a8303d33 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -661,11 +661,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, dep = to_dwc3_ep(ep); dwc = dep->dwc; - if (dep->flags & DWC3_EP_ENABLED) { - dev_WARN_ONCE(dwc->dev, true, "%s is already enabled\n", - dep->name); + if (dev_WARN_ONCE(dwc->dev, dep->flags & DWC3_EP_ENABLED, + "%s is already enabled\n", + dep->name)) return 0; - } spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false, false); @@ -689,11 +688,10 @@ static int dwc3_gadget_ep_disable(struct usb_ep *ep) dep = to_dwc3_ep(ep); dwc = dep->dwc; - if (!(dep->flags & DWC3_EP_ENABLED)) { - dev_WARN_ONCE(dwc->dev, true, "%s is already disabled\n", - dep->name); + if (dev_WARN_ONCE(dwc->dev, !(dep->flags & DWC3_EP_ENABLED), + "%s is already disabled\n", + dep->name)) return 0; - } spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_disable(dep); -- cgit v1.2.3 From ca07099460231576cd7aceed5da47a06cee8063b Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Thu, 10 Dec 2015 17:50:10 -0300 Subject: usb: gadget: s3c-hsudc: remove redundant condition This patch removes redundant condition. (!A || (A && B)) is the same as (!A || B). Tested by compilation only. Caught by cppcheck. Signed-off-by: Geyslan G. Bem Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/s3c-hsudc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/s3c-hsudc.c b/drivers/usb/gadget/udc/s3c-hsudc.c index e9def42ce50d..82a9e2a3bedc 100644 --- a/drivers/usb/gadget/udc/s3c-hsudc.c +++ b/drivers/usb/gadget/udc/s3c-hsudc.c @@ -569,7 +569,7 @@ static int s3c_hsudc_handle_reqfeat(struct s3c_hsudc *hsudc, hsep = &hsudc->ep[ep_num]; switch (le16_to_cpu(ctrl->wValue)) { case USB_ENDPOINT_HALT: - if (set || (!set && !hsep->wedge)) + if (set || !hsep->wedge) s3c_hsudc_set_halt(&hsep->ep, set); return 0; } -- cgit v1.2.3 From 1a1716260008b16887d72b417bd069ee4220c42e Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Thu, 10 Dec 2015 17:50:12 -0300 Subject: usb: musb: gadget: remove redundant condition This patch removes redundant condition. (!A || (A && B)) is the same as (!A || B). Fixes indentation too. Tested by: compilation only Caught by: cppcheck Signed-off-by: Geyslan G. Bem Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_gadget.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 67ad630c86c9..87bd578799a8 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -353,9 +353,8 @@ static void txstate(struct musb *musb, struct musb_request *req) * 1 >0 Yes(FS bulk) */ if (!musb_ep->hb_mult || - (musb_ep->hb_mult && - can_bulk_split(musb, - musb_ep->type))) + can_bulk_split(musb, + musb_ep->type)) csr |= MUSB_TXCSR_AUTOSET; } csr &= ~MUSB_TXCSR_P_UNDERRUN; -- cgit v1.2.3 From 8055555fc4590fbda32d4bbf7888bdb2cd4b2b74 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 30 Nov 2015 21:37:12 -0800 Subject: usb: musb: core: Fix handling of the phy notifications We currently can't unload omap2430 MUSB platform glue driver module and this cause issues for fixing the MUSB code further. The reason we can't remove omap2430 is because it uses the PHY functions and also exports the omap_musb_mailbox function that some PHY drivers are using. Let's fix the issue by exporting a more generic musb_mailbox function from the MUSB core and allow platform glue layers to register phy_callback function as needed. And now we can now also get rid of the include/linux/musb-omap.h. Cc: Bin Liu Cc: Felipe Balbi Cc: Kishon Vijay Abraham I Cc: NeilBrown Reviewed-by: Kishon Vijay Abraham I Signed-off-by: Tony Lindgren Signed-off-by: Felipe Balbi --- drivers/phy/phy-twl4030-usb.c | 32 ++++++++++++++++---------------- drivers/usb/musb/musb_core.c | 21 +++++++++++++++++++++ drivers/usb/musb/musb_core.h | 2 ++ drivers/usb/musb/omap2430.c | 27 ++++++++++++++------------- drivers/usb/phy/phy-twl6030-usb.c | 30 +++++++++++++++--------------- include/linux/usb/musb-omap.h | 30 ------------------------------ include/linux/usb/musb.h | 15 +++++++++++++++ 7 files changed, 83 insertions(+), 74 deletions(-) delete mode 100644 include/linux/usb/musb-omap.h (limited to 'drivers') diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c index 3a707dd14238..4a3fc6e59f8e 100644 --- a/drivers/phy/phy-twl4030-usb.c +++ b/drivers/phy/phy-twl4030-usb.c @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include #include @@ -148,10 +148,10 @@ * If VBUS is valid or ID is ground, then we know a * cable is present and we need to be runtime-enabled */ -static inline bool cable_present(enum omap_musb_vbus_id_status stat) +static inline bool cable_present(enum musb_vbus_id_status stat) { - return stat == OMAP_MUSB_VBUS_VALID || - stat == OMAP_MUSB_ID_GROUND; + return stat == MUSB_VBUS_VALID || + stat == MUSB_ID_GROUND; } struct twl4030_usb { @@ -170,7 +170,7 @@ struct twl4030_usb { enum twl4030_usb_mode usb_mode; int irq; - enum omap_musb_vbus_id_status linkstat; + enum musb_vbus_id_status linkstat; bool vbus_supplied; struct delayed_work id_workaround_work; @@ -276,11 +276,11 @@ static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; } -static enum omap_musb_vbus_id_status +static enum musb_vbus_id_status twl4030_usb_linkstat(struct twl4030_usb *twl) { int status; - enum omap_musb_vbus_id_status linkstat = OMAP_MUSB_UNKNOWN; + enum musb_vbus_id_status linkstat = MUSB_UNKNOWN; twl->vbus_supplied = false; @@ -306,14 +306,14 @@ static enum omap_musb_vbus_id_status } if (status & BIT(2)) - linkstat = OMAP_MUSB_ID_GROUND; + linkstat = MUSB_ID_GROUND; else if (status & BIT(7)) - linkstat = OMAP_MUSB_VBUS_VALID; + linkstat = MUSB_VBUS_VALID; else - linkstat = OMAP_MUSB_VBUS_OFF; + linkstat = MUSB_VBUS_OFF; } else { - if (twl->linkstat != OMAP_MUSB_UNKNOWN) - linkstat = OMAP_MUSB_VBUS_OFF; + if (twl->linkstat != MUSB_UNKNOWN) + linkstat = MUSB_VBUS_OFF; } dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", @@ -535,7 +535,7 @@ static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); static irqreturn_t twl4030_usb_irq(int irq, void *_twl) { struct twl4030_usb *twl = _twl; - enum omap_musb_vbus_id_status status; + enum musb_vbus_id_status status; bool status_changed = false; status = twl4030_usb_linkstat(twl); @@ -567,11 +567,11 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) pm_runtime_mark_last_busy(twl->dev); pm_runtime_put_autosuspend(twl->dev); } - omap_musb_mailbox(status); + musb_mailbox(status); } /* don't schedule during sleep - irq works right then */ - if (status == OMAP_MUSB_ID_GROUND && pm_runtime_active(twl->dev)) { + if (status == MUSB_ID_GROUND && pm_runtime_active(twl->dev)) { cancel_delayed_work(&twl->id_workaround_work); schedule_delayed_work(&twl->id_workaround_work, HZ); } @@ -670,7 +670,7 @@ static int twl4030_usb_probe(struct platform_device *pdev) twl->dev = &pdev->dev; twl->irq = platform_get_irq(pdev, 0); twl->vbus_supplied = false; - twl->linkstat = OMAP_MUSB_UNKNOWN; + twl->linkstat = MUSB_UNKNOWN; twl->phy.dev = twl->dev; twl->phy.label = "twl4030"; diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index cd37e9fc2e62..04548423094b 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1702,6 +1702,23 @@ EXPORT_SYMBOL_GPL(musb_dma_completion); #define use_dma 0 #endif +static void (*musb_phy_callback)(enum musb_vbus_id_status status); + +/* + * musb_mailbox - optional phy notifier function + * @status phy state change + * + * Optionally gets called from the USB PHY. Note that the USB PHY must be + * disabled at the point the phy_callback is registered or unregistered. + */ +void musb_mailbox(enum musb_vbus_id_status status) +{ + if (musb_phy_callback) + musb_phy_callback(status); + +}; +EXPORT_SYMBOL_GPL(musb_mailbox); + /*-------------------------------------------------------------------------*/ static ssize_t @@ -2114,6 +2131,9 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb->xceiv->io_ops = &musb_ulpi_access; } + if (musb->ops->phy_callback) + musb_phy_callback = musb->ops->phy_callback; + pm_runtime_get_sync(musb->controller); if (use_dma && dev->dma_mask) { @@ -2292,6 +2312,7 @@ static int musb_remove(struct platform_device *pdev) */ musb_exit_debugfs(musb); musb_shutdown(pdev); + musb_phy_callback = NULL; if (musb->dma_controller) musb_dma_controller_destroy(musb->dma_controller); diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 2337d7a7d62d..fd215fb45fd4 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -168,6 +168,7 @@ struct musb_io; * @adjust_channel_params: pre check for standard dma channel_program func * @pre_root_reset_end: called before the root usb port reset flag gets cleared * @post_root_reset_end: called after the root usb port reset flag gets cleared + * @phy_callback: optional callback function for the phy to call */ struct musb_platform_ops { @@ -214,6 +215,7 @@ struct musb_platform_ops { dma_addr_t *dma_addr, u32 *len); void (*pre_root_reset_end)(struct musb *musb); void (*post_root_reset_end)(struct musb *musb); + void (*phy_callback)(enum musb_vbus_id_status status); }; /* diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 1bd9232ff76f..bf05f807729f 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include @@ -46,7 +46,7 @@ struct omap2430_glue { struct device *dev; struct platform_device *musb; - enum omap_musb_vbus_id_status status; + enum musb_vbus_id_status status; struct work_struct omap_musb_mailbox_work; struct device *control_otghs; }; @@ -234,7 +234,7 @@ static inline void omap2430_low_level_init(struct musb *musb) musb_writel(musb->mregs, OTG_FORCESTDBY, l); } -void omap_musb_mailbox(enum omap_musb_vbus_id_status status) +static void omap2430_musb_mailbox(enum musb_vbus_id_status status) { struct omap2430_glue *glue = _glue; @@ -251,7 +251,6 @@ void omap_musb_mailbox(enum omap_musb_vbus_id_status status) schedule_work(&glue->omap_musb_mailbox_work); } -EXPORT_SYMBOL_GPL(omap_musb_mailbox); static void omap_musb_set_mailbox(struct omap2430_glue *glue) { @@ -262,7 +261,7 @@ static void omap_musb_set_mailbox(struct omap2430_glue *glue) struct usb_otg *otg = musb->xceiv->otg; switch (glue->status) { - case OMAP_MUSB_ID_GROUND: + case MUSB_ID_GROUND: dev_dbg(dev, "ID GND\n"); otg->default_a = true; @@ -276,7 +275,7 @@ static void omap_musb_set_mailbox(struct omap2430_glue *glue) } break; - case OMAP_MUSB_VBUS_VALID: + case MUSB_VBUS_VALID: dev_dbg(dev, "VBUS Connect\n"); otg->default_a = false; @@ -287,8 +286,8 @@ static void omap_musb_set_mailbox(struct omap2430_glue *glue) omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE); break; - case OMAP_MUSB_ID_FLOAT: - case OMAP_MUSB_VBUS_OFF: + case MUSB_ID_FLOAT: + case MUSB_VBUS_OFF: dev_dbg(dev, "VBUS Disconnect\n"); musb->xceiv->last_event = USB_EVENT_NONE; @@ -430,7 +429,7 @@ static int omap2430_musb_init(struct musb *musb) setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); - if (glue->status != OMAP_MUSB_UNKNOWN) + if (glue->status != MUSB_UNKNOWN) omap_musb_set_mailbox(glue); phy_init(musb->phy); @@ -455,7 +454,7 @@ static void omap2430_musb_enable(struct musb *musb) switch (glue->status) { - case OMAP_MUSB_ID_GROUND: + case MUSB_ID_GROUND: omap_control_usb_set_mode(glue->control_otghs, USB_MODE_HOST); if (data->interface_type != MUSB_INTERFACE_UTMI) break; @@ -474,7 +473,7 @@ static void omap2430_musb_enable(struct musb *musb) } break; - case OMAP_MUSB_VBUS_VALID: + case MUSB_VBUS_VALID: omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE); break; @@ -488,7 +487,7 @@ static void omap2430_musb_disable(struct musb *musb) struct device *dev = musb->controller; struct omap2430_glue *glue = dev_get_drvdata(dev->parent); - if (glue->status != OMAP_MUSB_UNKNOWN) + if (glue->status != MUSB_UNKNOWN) omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DISCONNECT); } @@ -520,6 +519,8 @@ static const struct musb_platform_ops omap2430_ops = { .enable = omap2430_musb_enable, .disable = omap2430_musb_disable, + + .phy_callback = omap2430_musb_mailbox, }; static u64 omap2430_dmamask = DMA_BIT_MASK(32); @@ -551,7 +552,7 @@ static int omap2430_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; - glue->status = OMAP_MUSB_UNKNOWN; + glue->status = MUSB_UNKNOWN; glue->control_otghs = ERR_PTR(-ENODEV); if (np) { diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c index 12741856a75c..014dbbd72132 100644 --- a/drivers/usb/phy/phy-twl6030-usb.c +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -102,7 +102,7 @@ struct twl6030_usb { int irq1; int irq2; - enum omap_musb_vbus_id_status linkstat; + enum musb_vbus_id_status linkstat; u8 asleep; bool vbus_enable; const char *regulator; @@ -189,13 +189,13 @@ static ssize_t twl6030_usb_vbus_show(struct device *dev, spin_lock_irqsave(&twl->lock, flags); switch (twl->linkstat) { - case OMAP_MUSB_VBUS_VALID: + case MUSB_VBUS_VALID: ret = snprintf(buf, PAGE_SIZE, "vbus\n"); break; - case OMAP_MUSB_ID_GROUND: + case MUSB_ID_GROUND: ret = snprintf(buf, PAGE_SIZE, "id\n"); break; - case OMAP_MUSB_VBUS_OFF: + case MUSB_VBUS_OFF: ret = snprintf(buf, PAGE_SIZE, "none\n"); break; default: @@ -210,7 +210,7 @@ static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL); static irqreturn_t twl6030_usb_irq(int irq, void *_twl) { struct twl6030_usb *twl = _twl; - enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; + enum musb_vbus_id_status status = MUSB_UNKNOWN; u8 vbus_state, hw_state; int ret; @@ -225,14 +225,14 @@ static irqreturn_t twl6030_usb_irq(int irq, void *_twl) dev_err(twl->dev, "Failed to enable usb3v3\n"); twl->asleep = 1; - status = OMAP_MUSB_VBUS_VALID; + status = MUSB_VBUS_VALID; twl->linkstat = status; - omap_musb_mailbox(status); + musb_mailbox(status); } else { - if (twl->linkstat != OMAP_MUSB_UNKNOWN) { - status = OMAP_MUSB_VBUS_OFF; + if (twl->linkstat != MUSB_UNKNOWN) { + status = MUSB_VBUS_OFF; twl->linkstat = status; - omap_musb_mailbox(status); + musb_mailbox(status); if (twl->asleep) { regulator_disable(twl->usb3v3); twl->asleep = 0; @@ -248,7 +248,7 @@ static irqreturn_t twl6030_usb_irq(int irq, void *_twl) static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) { struct twl6030_usb *twl = _twl; - enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; + enum musb_vbus_id_status status = MUSB_UNKNOWN; u8 hw_state; int ret; @@ -262,9 +262,9 @@ static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) twl->asleep = 1; twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_CLR); twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_SET); - status = OMAP_MUSB_ID_GROUND; + status = MUSB_ID_GROUND; twl->linkstat = status; - omap_musb_mailbox(status); + musb_mailbox(status); } else { twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_CLR); twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); @@ -334,7 +334,7 @@ static int twl6030_usb_probe(struct platform_device *pdev) twl->dev = &pdev->dev; twl->irq1 = platform_get_irq(pdev, 0); twl->irq2 = platform_get_irq(pdev, 1); - twl->linkstat = OMAP_MUSB_UNKNOWN; + twl->linkstat = MUSB_UNKNOWN; twl->comparator.set_vbus = twl6030_set_vbus; twl->comparator.start_srp = twl6030_start_srp; diff --git a/include/linux/usb/musb-omap.h b/include/linux/usb/musb-omap.h deleted file mode 100644 index 7774c5986f07..000000000000 --- a/include/linux/usb/musb-omap.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2011-2012 by Texas Instruments - * - * The Inventra Controller Driver for Linux is free software; you - * can redistribute it and/or modify it under the terms of the GNU - * General Public License version 2 as published by the Free Software - * Foundation. - */ - -#ifndef __MUSB_OMAP_H__ -#define __MUSB_OMAP_H__ - -enum omap_musb_vbus_id_status { - OMAP_MUSB_UNKNOWN = 0, - OMAP_MUSB_ID_GROUND, - OMAP_MUSB_ID_FLOAT, - OMAP_MUSB_VBUS_VALID, - OMAP_MUSB_VBUS_OFF, -}; - -#if (defined(CONFIG_USB_MUSB_OMAP2PLUS) || \ - defined(CONFIG_USB_MUSB_OMAP2PLUS_MODULE)) -void omap_musb_mailbox(enum omap_musb_vbus_id_status status); -#else -static inline void omap_musb_mailbox(enum omap_musb_vbus_id_status status) -{ -} -#endif - -#endif /* __MUSB_OMAP_H__ */ diff --git a/include/linux/usb/musb.h b/include/linux/usb/musb.h index fa6dc132bd1b..96ddfb7ab018 100644 --- a/include/linux/usb/musb.h +++ b/include/linux/usb/musb.h @@ -133,6 +133,21 @@ struct musb_hdrc_platform_data { const void *platform_ops; }; +enum musb_vbus_id_status { + MUSB_UNKNOWN = 0, + MUSB_ID_GROUND, + MUSB_ID_FLOAT, + MUSB_VBUS_VALID, + MUSB_VBUS_OFF, +}; + +#if IS_ENABLED(CONFIG_USB_MUSB_HDRC) +void musb_mailbox(enum musb_vbus_id_status status); +#else +static inline void musb_mailbox(enum musb_vbus_id_status status) +{ +} +#endif /* TUSB 6010 support */ -- cgit v1.2.3 From 03e43528ab68449921201744a731c1bac50bc9d1 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 30 Nov 2015 21:37:13 -0800 Subject: usb: musb: Fix unbalanced pm_runtime_enable When reloading omap2430 kernel module we get a warning about unbalanced pm_runtime_enable. Let's fix this. Note that we need to do this after the child musb-core platform_device is removed because of pm_runtime_irq_safe being set at the child. Cc: Bin Liu Cc: Felipe Balbi Cc: Kishon Vijay Abraham I Cc: NeilBrown Reviewed-by: Kishon Vijay Abraham I Signed-off-by: Tony Lindgren Signed-off-by: Felipe Balbi --- drivers/usb/musb/omap2430.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index bf05f807729f..c84e0322c108 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -664,8 +664,11 @@ static int omap2430_remove(struct platform_device *pdev) { struct omap2430_glue *glue = platform_get_drvdata(pdev); + pm_runtime_get_sync(glue->dev); cancel_work_sync(&glue->omap_musb_mailbox_work); platform_device_unregister(glue->musb); + pm_runtime_put_sync(glue->dev); + pm_runtime_disable(glue->dev); return 0; } -- cgit v1.2.3 From 919de443c2acd9fbd691ea0ed7d1ad858e5f8bed Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Tue, 1 Dec 2015 18:31:00 +0000 Subject: usb: gadget: f_midi: set altsettings only for MIDIStreaming interface This avoids duplication of USB requests for OUT endpoint and re-enabling endpoints. Signed-off-by: Felipe F. Tonello Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_midi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 29bfca1a47bb..e804231bd8e5 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -75,6 +75,7 @@ struct f_midi { struct usb_ep *in_ep, *out_ep; struct snd_card *card; struct snd_rawmidi *rmidi; + u8 ms_id; struct snd_rawmidi_substream *in_substream[MAX_PORTS]; struct snd_rawmidi_substream *out_substream[MAX_PORTS]; @@ -321,8 +322,8 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) unsigned i; int err; - /* For Control Device interface we do nothing */ - if (intf == 0) + /* we only set alt for MIDIStreaming interface */ + if (intf != midi->ms_id) return 0; err = f_midi_start_ep(midi, f, midi->in_ep); @@ -730,6 +731,7 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) goto fail; ms_interface_desc.bInterfaceNumber = status; ac_header_desc.baInterfaceNr[0] = status; + midi->ms_id = status; status = -ENODEV; -- cgit v1.2.3 From f0f1b8cac4d8d973e95f25d9ea132775fb43c5f4 Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Tue, 1 Dec 2015 18:31:01 +0000 Subject: usb: gadget: f_midi: fail if set_alt fails to allocate requests This ensures that the midi function will only work if the proper number of IN and OUT requrests are allocated. Otherwise the function will work with less requests then what the user wants. Signed-off-by: Felipe F. Tonello Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_midi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index e804231bd8e5..79dc611a2fc4 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -344,9 +344,10 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) req->complete = f_midi_complete; err = usb_ep_queue(midi->out_ep, req, GFP_ATOMIC); if (err) { - ERROR(midi, "%s queue req: %d\n", + ERROR(midi, "%s: couldn't enqueue request: %d\n", midi->out_ep->name, err); free_ep_req(midi->out_ep, req); + return err; } } -- cgit v1.2.3 From e1e3d7ec5da32af3bded733a61c248d7db0b4e34 Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Tue, 1 Dec 2015 18:31:02 +0000 Subject: usb: gadget: f_midi: pre-allocate IN requests This patch introduces pre-allocation of IN endpoint USB requests. This improves on latency (requires no usb request allocation on transmit) and avoid several potential probles on allocating too many usb requests (which involves DMA pool allocation problems). This implementation also handles better multiple MIDI Gadget ports, always processing the last processed MIDI substream if the last USB request wasn't enought to handle the whole stream. Signed-off-by: Felipe F. Tonello Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_midi.c | 166 +++++++++++++++++++++++++++-------- drivers/usb/gadget/legacy/gmidi.c | 2 +- 2 files changed, 129 insertions(+), 39 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 79dc611a2fc4..fb1fe96d3215 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,9 @@ struct f_midi { int index; char *id; unsigned int buflen, qlen; + /* This fifo is used as a buffer ring for pre-allocated IN usb_requests */ + DECLARE_KFIFO_PTR(in_req_fifo, struct usb_request *); + unsigned int in_last_port; }; static inline struct f_midi *func_to_midi(struct usb_function *f) @@ -95,7 +99,7 @@ static inline struct f_midi *func_to_midi(struct usb_function *f) return container_of(f, struct f_midi, func); } -static void f_midi_transmit(struct f_midi *midi, struct usb_request *req); +static void f_midi_transmit(struct f_midi *midi); DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1); @@ -253,7 +257,8 @@ f_midi_complete(struct usb_ep *ep, struct usb_request *req) } else if (ep == midi->in_ep) { /* Our transmit completed. See if there's more to go. * f_midi_transmit eats req, don't queue it again. */ - f_midi_transmit(midi, req); + req->length = 0; + f_midi_transmit(midi); return; } break; @@ -264,10 +269,12 @@ f_midi_complete(struct usb_ep *ep, struct usb_request *req) case -ESHUTDOWN: /* disconnect from host */ VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status, req->actual, req->length); - if (ep == midi->out_ep) + if (ep == midi->out_ep) { f_midi_handle_out_data(ep, req); - - free_ep_req(ep, req); + /* We don't need to free IN requests because it's handled + * by the midi->in_req_fifo. */ + free_ep_req(ep, req); + } return; case -EOVERFLOW: /* buffer overrun on read means that @@ -334,6 +341,20 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (err) return err; + /* pre-allocate write usb requests to use on f_midi_transmit. */ + while (kfifo_avail(&midi->in_req_fifo)) { + struct usb_request *req = + midi_alloc_ep_req(midi->in_ep, midi->buflen); + + if (req == NULL) + return -ENOMEM; + + req->length = 0; + req->complete = f_midi_complete; + + kfifo_put(&midi->in_req_fifo, req); + } + /* allocate a bunch of read buffers and queue them all at once. */ for (i = 0; i < midi->qlen && err == 0; i++) { struct usb_request *req = @@ -358,6 +379,7 @@ static void f_midi_disable(struct usb_function *f) { struct f_midi *midi = func_to_midi(f); struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = NULL; DBG(cdev, "disable\n"); @@ -367,6 +389,10 @@ static void f_midi_disable(struct usb_function *f) */ usb_ep_disable(midi->in_ep); usb_ep_disable(midi->out_ep); + + /* release IN requests */ + while (kfifo_get(&midi->in_req_fifo, &req)) + free_ep_req(midi->in_ep, req); } static int f_midi_snd_free(struct snd_device *device) @@ -488,57 +514,113 @@ static void f_midi_transmit_byte(struct usb_request *req, } } -static void f_midi_transmit(struct f_midi *midi, struct usb_request *req) +static void f_midi_drop_out_substreams(struct f_midi *midi) { - struct usb_ep *ep = midi->in_ep; - int i; - - if (!ep) - return; - - if (!req) - req = midi_alloc_ep_req(ep, midi->buflen); - - if (!req) { - ERROR(midi, "%s: alloc_ep_request failed\n", __func__); - return; - } - req->length = 0; - req->complete = f_midi_complete; + unsigned int i; for (i = 0; i < MAX_PORTS; i++) { struct gmidi_in_port *port = midi->in_port[i]; struct snd_rawmidi_substream *substream = midi->in_substream[i]; - if (!port || !port->active || !substream) + if (!port) + break; + + if (!port->active || !substream) continue; - while (req->length + 3 < midi->buflen) { - uint8_t b; - if (snd_rawmidi_transmit(substream, &b, 1) != 1) { - port->active = 0; + snd_rawmidi_drop_output(substream); + } +} + +static void f_midi_transmit(struct f_midi *midi) +{ + struct usb_ep *ep = midi->in_ep; + bool active; + + /* We only care about USB requests if IN endpoint is enabled */ + if (!ep || !ep->enabled) + goto drop_out; + + do { + struct usb_request *req = NULL; + unsigned int len, i; + + active = false; + + /* We peek the request in order to reuse it if it fails + * to enqueue on its endpoint */ + len = kfifo_peek(&midi->in_req_fifo, &req); + if (len != 1) { + ERROR(midi, "%s: Couldn't get usb request\n", __func__); + goto drop_out; + } + + /* If buffer overrun, then we ignore this transmission. + * IMPORTANT: This will cause the user-space rawmidi device to block until a) usb + * requests have been completed or b) snd_rawmidi_write() times out. */ + if (req->length > 0) + return; + + for (i = midi->in_last_port; i < MAX_PORTS; i++) { + struct gmidi_in_port *port = midi->in_port[i]; + struct snd_rawmidi_substream *substream = midi->in_substream[i]; + + if (!port) { + /* Reset counter when we reach the last available port */ + midi->in_last_port = 0; + break; + } + + if (!port->active || !substream) + continue; + + while (req->length + 3 < midi->buflen) { + uint8_t b; + + if (snd_rawmidi_transmit(substream, &b, 1) != 1) { + port->active = 0; + break; + } + f_midi_transmit_byte(req, port, b); + } + + active = !!port->active; + /* Check if last port is still active, which means that + * there is still data on that substream but this current + * request run out of space. */ + if (active) { + midi->in_last_port = i; + /* There is no need to re-iterate though midi ports. */ break; } - f_midi_transmit_byte(req, port, b); } - } - if (req->length > 0 && ep->enabled) { - int err; + if (req->length > 0) { + int err; - err = usb_ep_queue(ep, req, GFP_ATOMIC); - if (err < 0) - ERROR(midi, "%s queue req: %d\n", - midi->in_ep->name, err); - } else { - free_ep_req(ep, req); - } + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err < 0) { + ERROR(midi, "%s failed to queue req: %d\n", + midi->in_ep->name, err); + req->length = 0; /* Re-use request next time. */ + } else { + /* Upon success, put request at the back of the queue. */ + kfifo_skip(&midi->in_req_fifo); + kfifo_put(&midi->in_req_fifo, req); + } + } + } while (active); + + return; + +drop_out: + f_midi_drop_out_substreams(midi); } static void f_midi_in_tasklet(unsigned long data) { struct f_midi *midi = (struct f_midi *) data; - f_midi_transmit(midi, NULL); + f_midi_transmit(midi); } static int f_midi_in_open(struct snd_rawmidi_substream *substream) @@ -664,6 +746,7 @@ static int f_midi_register_card(struct f_midi *midi) goto fail; } midi->rmidi = rmidi; + midi->in_last_port = 0; strcpy(rmidi->name, card->shortname); rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | @@ -1053,6 +1136,7 @@ static void f_midi_free(struct usb_function *f) mutex_lock(&opts->lock); for (i = opts->in_ports - 1; i >= 0; --i) kfree(midi->in_port[i]); + kfifo_free(&midi->in_req_fifo); kfree(midi); --opts->refcnt; mutex_unlock(&opts->lock); @@ -1126,6 +1210,12 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi) midi->index = opts->index; midi->buflen = opts->buflen; midi->qlen = opts->qlen; + midi->in_last_port = 0; + + status = kfifo_alloc(&midi->in_req_fifo, midi->qlen, GFP_KERNEL); + if (status) + goto setup_fail; + ++opts->refcnt; mutex_unlock(&opts->lock); diff --git a/drivers/usb/gadget/legacy/gmidi.c b/drivers/usb/gadget/legacy/gmidi.c index e27aad5e50b9..fc2ac150f5ff 100644 --- a/drivers/usb/gadget/legacy/gmidi.c +++ b/drivers/usb/gadget/legacy/gmidi.c @@ -53,7 +53,7 @@ MODULE_PARM_DESC(buflen, "MIDI buffer length"); static unsigned int qlen = 32; module_param(qlen, uint, S_IRUGO); -MODULE_PARM_DESC(qlen, "USB read request queue length"); +MODULE_PARM_DESC(qlen, "USB read and write request queue length"); static unsigned int in_ports = 1; module_param(in_ports, uint, S_IRUGO); -- cgit v1.2.3 From ab738ff1991d183a67c37ce38b3fc39cd28798c6 Mon Sep 17 00:00:00 2001 From: Mike Looijmans Date: Mon, 30 Nov 2015 12:18:23 +0100 Subject: usb: gadget: ether: Allow changing the MTU The gadget ethernet driver supports changing the MTU, but only allows this when the USB cable is removed. The comment indicates that this is because the "peer won't know". Even if the network link is still down and only the USB link is established, the driver won't allow the change. Other network interfaces allow changing the MTU any time, and don't force the link to be disabled. This makes perfect sense, because in order to be able to negotiate the MTU, the link needs to be up. Remove the restriction so that it is now actually possible to change the MTU (e.g. using "ifconfig usb0 mtu 15000") without having to manually pull the plug or change the driver's default setting. This is especially important after commit bba787a860fa ("usb: gadget: ether: Allow jumbo frames") Signed-off-by: Mike Looijmans Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/u_ether.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index 6554322af2c1..637809e3bd0d 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -143,21 +143,11 @@ static inline int qlen(struct usb_gadget *gadget, unsigned qmult) static int ueth_change_mtu(struct net_device *net, int new_mtu) { - struct eth_dev *dev = netdev_priv(net); - unsigned long flags; - int status = 0; + if (new_mtu <= ETH_HLEN || new_mtu > GETHER_MAX_ETH_FRAME_LEN) + return -ERANGE; + net->mtu = new_mtu; - /* don't change MTU on "live" link (peer won't know) */ - spin_lock_irqsave(&dev->lock, flags); - if (dev->port_usb) - status = -EBUSY; - else if (new_mtu <= ETH_HLEN || new_mtu > GETHER_MAX_ETH_FRAME_LEN) - status = -ERANGE; - else - net->mtu = new_mtu; - spin_unlock_irqrestore(&dev->lock, flags); - - return status; + return 0; } static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) -- cgit v1.2.3 From 2f1a993a0da652c50e08159f16590b7f9820d192 Mon Sep 17 00:00:00 2001 From: Simon Horman Date: Wed, 16 Dec 2015 13:51:51 +0900 Subject: usb: renesas_usbhs: add fallback compatibility strings Add fallback compatibility strings for R-Car Gen2 and Gen3. This is in keeping with the fallback scheme being adopted wherever appropriate for drivers for Renesas SoCs. Signed-off-by: Simon Horman Acked-by: Kuninori Morimoto Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/renesas_usbhs.txt | 12 ++++++++++-- drivers/usb/renesas_usbhs/common.c | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt index a14c0bb561d5..45d9ae13ffa3 100644 --- a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt +++ b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt @@ -1,11 +1,19 @@ Renesas Electronics USBHS driver Required properties: - - compatible: Must contain one of the following: + - compatible: Must contain one or more of the following: + - "renesas,usbhs-r8a7790" for r8a7790 (R-Car H2) compatible device - "renesas,usbhs-r8a7791" for r8a7791 (R-Car M2-W) compatible device - "renesas,usbhs-r8a7794" for r8a7794 (R-Car E2) compatible device - "renesas,usbhs-r8a7795" for r8a7795 (R-Car H3) compatible device + - "renesas,rcar-gen2-usbhs" for R-Car Gen2 compatible device + - "renesas,rcar-gen3-usbhs" for R-Car Gen3 compatible device + + When compatible with the generic version, nodes must list the + SoC-specific version corresponding to the platform first followed + by the generic version. + - reg: Base address and length of the register for the USBHS - interrupts: Interrupt specifier for the USBHS - clocks: A list of phandle + clock specifier pairs @@ -22,7 +30,7 @@ Optional properties: Example: usbhs: usb@e6590000 { - compatible = "renesas,usbhs-r8a7790"; + compatible = "renesas,usbhs-r8a7790", "renesas,rcar-gen2-usbhs"; reg = <0 0xe6590000 0 0x100>; interrupts = <0 107 IRQ_TYPE_LEVEL_HIGH>; clocks = <&mstp7_clks R8A7790_CLK_HSUSB>; diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 7ccc2fe4f6ec..5af9ca5d54ab 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -481,6 +481,15 @@ static const struct of_device_id usbhs_of_match[] = { .compatible = "renesas,usbhs-r8a7795", .data = (void *)USBHS_TYPE_RCAR_GEN2, }, + { + .compatible = "renesas,rcar-gen2-usbhs", + .data = (void *)USBHS_TYPE_RCAR_GEN2, + }, + { + /* Gen3 is compatible with Gen2 */ + .compatible = "renesas,rcar-gen3-usbhs", + .data = (void *)USBHS_TYPE_RCAR_GEN2, + }, { }, }; MODULE_DEVICE_TABLE(of, usbhs_of_match); -- cgit v1.2.3 From b7bd98b7db9fc8fe19da1a5ff0215311c6b95e46 Mon Sep 17 00:00:00 2001 From: David Eccher Date: Fri, 11 Dec 2015 22:13:55 +0100 Subject: usb: gadget: inode.c: fix unbalanced spin_lock in ep0_write Fix bad unlock balance: ep0_write enter with the locks locked from inode.c:1769, hence it must exit with spinlock held to avoid double unlock in dev_config. Signed-off-by: David Eccher Signed-off-by: Felipe Balbi --- drivers/usb/gadget/legacy/inode.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index f454c7af489c..365afd7e14f8 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1137,10 +1137,9 @@ ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) dev->gadget->ep0, dev->req, GFP_KERNEL); } + spin_lock_irq(&dev->lock); if (retval < 0) { - spin_lock_irq (&dev->lock); clean_req (dev->gadget->ep0, dev->req); - spin_unlock_irq (&dev->lock); } else retval = len; -- cgit v1.2.3 From f3b5a8d9b50d71b8c9fb72aa9c8ea948ad1a4ef9 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 30 Nov 2015 10:44:30 +0900 Subject: phy: rcar-gen3-usb2: Add R-Car Gen3 USB2 PHY driver This patch adds support for R-Car generation 3 USB2 PHY driver. This SoC has 3 EHCI/OHCI channels, and the channel 0 is shared with the HSUSB (USB2.0 peripheral) device. And each channel has independent registers about the PHYs. So, the purpose of this driver is: 1) initializes some registers of SoC specific to use the {ehci,ohci}-platform driver. 2) detects id pin to select host or peripheral on the channel 0. For now, this driver only supports 1) above. Signed-off-by: Yoshihiro Shimoda Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/rcar-gen3-phy-usb2.txt | 37 ++++ drivers/phy/Kconfig | 7 + drivers/phy/Makefile | 1 + drivers/phy/phy-rcar-gen3-usb2.c | 217 +++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt create mode 100644 drivers/phy/phy-rcar-gen3-usb2.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt new file mode 100644 index 000000000000..affa0f72658b --- /dev/null +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -0,0 +1,37 @@ +* Renesas R-Car generation 3 USB 2.0 PHY + +This file provides information on what the device node for the R-Car generation +3 USB 2.0 PHY contains. + +Required properties: +- compatible: "renesas,usb2-phy-r8a7795" if the device is a part of an R8A7795 + SoC. +- reg: offset and length of the partial USB 2.0 Host register block. +- reg-names: must be "usb2_host". +- clocks: clock phandle and specifier pair(s). +- #phy-cells: see phy-bindings.txt in the same directory, must be <0>. + +Optional properties: +To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are +combined, the device tree node should set HSUSB properties to reg and reg-names +properties. This is because HSUSB has registers to select USB 2.0 host or +peripheral at that channel: +- reg: offset and length of the partial HSUSB register block. +- reg-names: must be "hsusb". + +Example (R-Car H3): + + usb-phy@ee080200 { + compatible = "renesas,usb2-phy-r8a7795"; + reg = <0 0xee080200 0 0x700>, <0 0xe6590100 0 0x100>; + reg-names = "usb2_host", "hsusb"; + clocks = <&mstp7_clks R8A7795_CLK_EHCI0>, + <&mstp7_clks R8A7795_CLK_HSUSB>; + }; + + usb-phy@ee0a0200 { + compatible = "renesas,usb2-phy-r8a7795"; + reg = <0 0xee0a0200 0 0x700>; + reg-names = "usb2_host"; + clocks = <&mstp7_clks R8A7795_CLK_EHCI0>; + }; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 03cb3ea2d2c0..f90b7660dd3e 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -118,6 +118,13 @@ config PHY_RCAR_GEN2 help Support for USB PHY found on Renesas R-Car generation 2 SoCs. +config PHY_RCAR_GEN3_USB2 + tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" + depends on OF && ARCH_SHMOBILE + select GENERIC_PHY + help + Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. + config OMAP_CONTROL_PHY tristate "OMAP CONTROL PHY Driver" depends on ARCH_OMAP2PLUS || COMPILE_TEST diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 075db1a81aa5..91d7a62c6794 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o obj-$(CONFIG_PHY_MIPHY365X) += phy-miphy365x.o obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c new file mode 100644 index 000000000000..269615228b1b --- /dev/null +++ b/drivers/phy/phy-rcar-gen3-usb2.c @@ -0,0 +1,217 @@ +/* + * Renesas R-Car Gen3 for USB2.0 PHY driver + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * This is based on the phy-rcar-gen2 driver: + * Copyright (C) 2014 Renesas Solutions Corp. + * Copyright (C) 2014 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +/******* USB2.0 Host registers (original offset is +0x200) *******/ +#define USB2_INT_ENABLE 0x000 +#define USB2_USBCTR 0x00c +#define USB2_SPD_RSM_TIMSET 0x10c +#define USB2_OC_TIMSET 0x110 + +/* INT_ENABLE */ +#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) +#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) +#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_USBH_INTB_EN | \ + USB2_INT_ENABLE_USBH_INTA_EN) + +/* USBCTR */ +#define USB2_USBCTR_DIRPD BIT(2) +#define USB2_USBCTR_PLL_RST BIT(1) + +/* SPD_RSM_TIMSET */ +#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b + +/* OC_TIMSET */ +#define USB2_OC_TIMSET_INIT 0x000209ab + +/******* HSUSB registers (original offset is +0x100) *******/ +#define HSUSB_LPSTS 0x02 +#define HSUSB_UGCTRL2 0x84 + +/* Low Power Status register (LPSTS) */ +#define HSUSB_LPSTS_SUSPM 0x4000 + +/* USB General control register 2 (UGCTRL2) */ +#define HSUSB_UGCTRL2_MASK 0x00000031 /* bit[31:6] should be 0 */ +#define HSUSB_UGCTRL2_USB0SEL 0x00000030 +#define HSUSB_UGCTRL2_USB0SEL_HOST 0x00000010 +#define HSUSB_UGCTRL2_USB0SEL_HS_USB 0x00000020 +#define HSUSB_UGCTRL2_USB0SEL_OTG 0x00000030 + +struct rcar_gen3_data { + void __iomem *base; + struct clk *clk; +}; + +struct rcar_gen3_chan { + struct rcar_gen3_data usb2; + struct rcar_gen3_data hsusb; + struct phy *phy; +}; + +static int rcar_gen3_phy_usb2_init(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *usb2_base = channel->usb2.base; + void __iomem *hsusb_base = channel->hsusb.base; + u32 val; + + /* Initialize USB2 part */ + writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE); + writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET); + writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); + + /* Initialize HSUSB part */ + if (hsusb_base) { + /* TODO: support "OTG" mode */ + val = readl(hsusb_base + HSUSB_UGCTRL2); + val = (val & ~HSUSB_UGCTRL2_USB0SEL) | + HSUSB_UGCTRL2_USB0SEL_HOST; + writel(val & HSUSB_UGCTRL2_MASK, hsusb_base + HSUSB_UGCTRL2); + } + + return 0; +} + +static int rcar_gen3_phy_usb2_exit(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + + writel(0, channel->usb2.base + USB2_INT_ENABLE); + + return 0; +} + +static int rcar_gen3_phy_usb2_power_on(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *usb2_base = channel->usb2.base; + void __iomem *hsusb_base = channel->hsusb.base; + u32 val; + + val = readl(usb2_base + USB2_USBCTR); + val |= USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + val &= ~USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + + /* + * TODO: To reduce power consuming, this driver should set the SUSPM + * after the PHY detects ID pin as peripheral. + */ + if (hsusb_base) { + /* Power on HSUSB PHY */ + val = readw(hsusb_base + HSUSB_LPSTS); + val |= HSUSB_LPSTS_SUSPM; + writew(val, hsusb_base + HSUSB_LPSTS); + } + + return 0; +} + +static int rcar_gen3_phy_usb2_power_off(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *hsusb_base = channel->hsusb.base; + u32 val; + + if (hsusb_base) { + /* Power off HSUSB PHY */ + val = readw(hsusb_base + HSUSB_LPSTS); + val &= ~HSUSB_LPSTS_SUSPM; + writew(val, hsusb_base + HSUSB_LPSTS); + } + + return 0; +} + +static struct phy_ops rcar_gen3_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .power_on = rcar_gen3_phy_usb2_power_on, + .power_off = rcar_gen3_phy_usb2_power_off, + .owner = THIS_MODULE, +}; + +static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { + { .compatible = "renesas,usb2-phy-r8a7795" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); + +static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen3_chan *channel; + struct phy_provider *provider; + struct resource *res; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2_host"); + channel->usb2.base = devm_ioremap_resource(dev, res); + if (IS_ERR(channel->usb2.base)) + return PTR_ERR(channel->usb2.base); + + /* "hsusb" memory resource is optional */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hsusb"); + + /* To avoid error message by devm_ioremap_resource() */ + if (res) { + channel->hsusb.base = devm_ioremap_resource(dev, res); + if (IS_ERR(channel->hsusb.base)) + channel->hsusb.base = NULL; + } + + /* devm_phy_create() will call pm_runtime_enable(dev); */ + channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops); + if (IS_ERR(channel->phy)) { + dev_err(dev, "Failed to create USB2 PHY\n"); + return PTR_ERR(channel->phy); + } + + phy_set_drvdata(channel->phy, channel); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) + dev_err(dev, "Failed to register PHY provider\n"); + + return PTR_ERR_OR_ZERO(provider); +} + +static struct platform_driver rcar_gen3_phy_usb2_driver = { + .driver = { + .name = "phy_rcar_gen3_usb2", + .of_match_table = rcar_gen3_phy_usb2_match_table, + }, + .probe = rcar_gen3_phy_usb2_probe, +}; +module_platform_driver(rcar_gen3_phy_usb2_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda "); -- cgit v1.2.3 From 1114e2d3173190be3e7339449c45ef11302d905a Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 30 Nov 2015 10:44:31 +0900 Subject: phy: rcar-gen3-usb2: change the mode to OTG on the combined channel To use the channel 0 of R-Car gen3 as periperal mode, This patch changes the mode to OTG instead of HOST. Then, this driver needs to set some registers to enable host mode and detects ID pin and VBUS pin at phy_init() timing. For now, the channel 0 can be used as host mode only. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-rcar-gen3-usb2.c | 124 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c index 269615228b1b..2b5d890554ad 100644 --- a/drivers/phy/phy-rcar-gen3-usb2.c +++ b/drivers/phy/phy-rcar-gen3-usb2.c @@ -24,6 +24,10 @@ #define USB2_USBCTR 0x00c #define USB2_SPD_RSM_TIMSET 0x10c #define USB2_OC_TIMSET 0x110 +#define USB2_COMMCTRL 0x600 +#define USB2_VBCTRL 0x60c +#define USB2_LINECTRL1 0x610 +#define USB2_ADPCTRL 0x630 /* INT_ENABLE */ #define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) @@ -41,6 +45,24 @@ /* OC_TIMSET */ #define USB2_OC_TIMSET_INIT 0x000209ab +/* COMMCTRL */ +#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ + +/* VBCTRL */ +#define USB2_VBCTRL_DRVVBUSSEL BIT(8) + +/* LINECTRL1 */ +#define USB2_LINECTRL1_DPRPD_EN BIT(19) +#define USB2_LINECTRL1_DP_RPD BIT(18) +#define USB2_LINECTRL1_DMRPD_EN BIT(17) +#define USB2_LINECTRL1_DM_RPD BIT(16) + +/* ADPCTRL */ +#define USB2_ADPCTRL_OTGSESSVLD BIT(20) +#define USB2_ADPCTRL_IDDIG BIT(19) +#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ +#define USB2_ADPCTRL_DRVVBUS BIT(4) + /******* HSUSB registers (original offset is +0x100) *******/ #define HSUSB_LPSTS 0x02 #define HSUSB_UGCTRL2 0x84 @@ -66,6 +88,102 @@ struct rcar_gen3_chan { struct phy *phy; }; +static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val = readl(usb2_base + USB2_COMMCTRL); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host); + if (host) + val &= ~USB2_COMMCTRL_OTG_PERI; + else + val |= USB2_COMMCTRL_OTG_PERI; + writel(val, usb2_base + USB2_COMMCTRL); +} + +static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val = readl(usb2_base + USB2_LINECTRL1); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); + val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD); + if (dp) + val |= USB2_LINECTRL1_DP_RPD; + if (dm) + val |= USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val = readl(usb2_base + USB2_ADPCTRL); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus); + if (vbus) + val |= USB2_ADPCTRL_DRVVBUS; + else + val &= ~USB2_ADPCTRL_DRVVBUS; + writel(val, usb2_base + USB2_ADPCTRL); +} + +static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 1); +} + +static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 0); +} + +static bool rcar_gen3_check_vbus(struct rcar_gen3_chan *ch) +{ + return !!(readl(ch->usb2.base + USB2_ADPCTRL) & + USB2_ADPCTRL_OTGSESSVLD); +} + +static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) +{ + return !!(readl(ch->usb2.base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); +} + +static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) +{ + bool is_host = true; + + /* B-device? */ + if (rcar_gen3_check_id(ch) && rcar_gen3_check_vbus(ch)) + is_host = false; + + if (is_host) + rcar_gen3_init_for_host(ch); + else + rcar_gen3_init_for_peri(ch); +} + +static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->usb2.base; + u32 val; + + val = readl(usb2_base + USB2_VBCTRL); + writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); + val = readl(usb2_base + USB2_ADPCTRL); + writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); + val = readl(usb2_base + USB2_LINECTRL1); + rcar_gen3_set_linectrl(ch, 0, 0); + writel(val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN, + usb2_base + USB2_LINECTRL1); + + rcar_gen3_device_recognition(ch); +} + static int rcar_gen3_phy_usb2_init(struct phy *p) { struct rcar_gen3_chan *channel = phy_get_drvdata(p); @@ -80,11 +198,13 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) /* Initialize HSUSB part */ if (hsusb_base) { - /* TODO: support "OTG" mode */ val = readl(hsusb_base + HSUSB_UGCTRL2); val = (val & ~HSUSB_UGCTRL2_USB0SEL) | - HSUSB_UGCTRL2_USB0SEL_HOST; + HSUSB_UGCTRL2_USB0SEL_OTG; writel(val & HSUSB_UGCTRL2_MASK, hsusb_base + HSUSB_UGCTRL2); + + /* Initialize otg part */ + rcar_gen3_init_otg(channel); } return 0; -- cgit v1.2.3 From 9f391c574efc15f00a6c7e3e120c8b84fc9e792f Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 30 Nov 2015 10:44:32 +0900 Subject: phy: rcar-gen3-usb2: add runtime ID/VBUS pin detection This patch adds support for runtime ID/VBUS pin detection if the channel 0 of R-Car gen3 is used. So, we are able to use the channel as both host and peripheral. Signed-off-by: Yoshihiro Shimoda Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/rcar-gen3-phy-usb2.txt | 2 + drivers/phy/phy-rcar-gen3-usb2.c | 43 +++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt index affa0f72658b..2390e4e9c84c 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -18,6 +18,7 @@ properties. This is because HSUSB has registers to select USB 2.0 host or peripheral at that channel: - reg: offset and length of the partial HSUSB register block. - reg-names: must be "hsusb". +- interrupts: interrupt specifier for the PHY. Example (R-Car H3): @@ -25,6 +26,7 @@ Example (R-Car H3): compatible = "renesas,usb2-phy-r8a7795"; reg = <0 0xee080200 0 0x700>, <0 0xe6590100 0 0x100>; reg-names = "usb2_host", "hsusb"; + interrupts = ; clocks = <&mstp7_clks R8A7795_CLK_EHCI0>, <&mstp7_clks R8A7795_CLK_HSUSB>; }; diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c index 2b5d890554ad..ef332ef4abc7 100644 --- a/drivers/phy/phy-rcar-gen3-usb2.c +++ b/drivers/phy/phy-rcar-gen3-usb2.c @@ -12,6 +12,7 @@ * published by the Free Software Foundation. */ +#include #include #include #include @@ -25,14 +26,18 @@ #define USB2_SPD_RSM_TIMSET 0x10c #define USB2_OC_TIMSET 0x110 #define USB2_COMMCTRL 0x600 +#define USB2_OBINTSTA 0x604 +#define USB2_OBINTEN 0x608 #define USB2_VBCTRL 0x60c #define USB2_LINECTRL1 0x610 #define USB2_ADPCTRL 0x630 /* INT_ENABLE */ +#define USB2_INT_ENABLE_UCOM_INTEN BIT(3) #define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) #define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) -#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_USBH_INTB_EN | \ +#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \ + USB2_INT_ENABLE_USBH_INTB_EN | \ USB2_INT_ENABLE_USBH_INTA_EN) /* USBCTR */ @@ -48,6 +53,12 @@ /* COMMCTRL */ #define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ +/* OBINTSTA and OBINTEN */ +#define USB2_OBINT_SESSVLDCHG BIT(12) +#define USB2_OBINT_IDDIGCHG BIT(11) +#define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \ + USB2_OBINT_IDDIGCHG) + /* VBCTRL */ #define USB2_VBCTRL_DRVVBUSSEL BIT(8) @@ -174,6 +185,9 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) val = readl(usb2_base + USB2_VBCTRL); writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); + writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); + val = readl(usb2_base + USB2_OBINTEN); + writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); val = readl(usb2_base + USB2_ADPCTRL); writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); val = readl(usb2_base + USB2_LINECTRL1); @@ -270,6 +284,23 @@ static struct phy_ops rcar_gen3_phy_usb2_ops = { .owner = THIS_MODULE, }; +static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) +{ + struct rcar_gen3_chan *ch = _ch; + void __iomem *usb2_base = ch->usb2.base; + u32 status = readl(usb2_base + USB2_OBINTSTA); + irqreturn_t ret = IRQ_NONE; + + if (status & USB2_OBINT_BITS) { + dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status); + writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); + rcar_gen3_device_recognition(ch); + ret = IRQ_HANDLED; + } + + return ret; +} + static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { { .compatible = "renesas,usb2-phy-r8a7795" }, { } @@ -302,9 +333,19 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) /* To avoid error message by devm_ioremap_resource() */ if (res) { + int irq; + channel->hsusb.base = devm_ioremap_resource(dev, res); if (IS_ERR(channel->hsusb.base)) channel->hsusb.base = NULL; + /* call request_irq for OTG */ + irq = platform_get_irq(pdev, 0); + if (irq >= 0) + irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq, + IRQF_SHARED, dev_name(dev), + channel); + if (irq < 0) + dev_err(dev, "No irq handler (%d)\n", irq); } /* devm_phy_create() will call pm_runtime_enable(dev); */ -- cgit v1.2.3 From 43f53b19074e846f236ef230d1eb4b14b601e965 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 4 Dec 2015 10:08:56 +0800 Subject: phy: phy-mt65xx-usb3: fix test fail of HS receiver sensitivity when use the default value 8 of RG_USB20_SQTH, the HS receiver sensitivity test of HQA will fail, set it as 2 to fix up the issue. Signed-off-by: Chunfeng Yun Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-mt65xx-usb3.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-mt65xx-usb3.c b/drivers/phy/phy-mt65xx-usb3.c index e427c3b788ff..2afbf9f36d83 100644 --- a/drivers/phy/phy-mt65xx-usb3.c +++ b/drivers/phy/phy-mt65xx-usb3.c @@ -49,6 +49,8 @@ #define PA6_RG_U2_ISO_EN BIT(31) #define PA6_RG_U2_BC11_SW_EN BIT(23) #define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20) +#define PA6_RG_U2_SQTH GENMASK(3, 0) +#define PA6_RG_U2_SQTH_VAL(x) (0xf & (x)) #define U3P_U2PHYACR4 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0020) #define P2C_RG_USB20_GPIO_CTL BIT(9) @@ -165,9 +167,10 @@ static void phy_instance_init(struct mt65xx_u3phy *u3phy, writel(tmp, port_base + U3P_U2PHYDTM0); } - /* DP/DM BC1.1 path Disable */ tmp = readl(port_base + U3P_USBPHYACR6); - tmp &= ~PA6_RG_U2_BC11_SW_EN; + tmp &= ~PA6_RG_U2_BC11_SW_EN; /* DP/DM BC1.1 path Disable */ + tmp &= ~PA6_RG_U2_SQTH; + tmp |= PA6_RG_U2_SQTH_VAL(2); writel(tmp, port_base + U3P_USBPHYACR6); tmp = readl(port_base + U3P_U3PHYA_DA_REG0); -- cgit v1.2.3 From 75f072f9eab72e10a45886d84a9a762b1c65071d Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 4 Dec 2015 10:11:05 +0800 Subject: phy: phy-mt65xx-usb3: improve HS eye diagram calibrate HS slew rate and switch 100uA current to SSUSB to improve HS eye diagram of HQA test. Signed-off-by: Chunfeng Yun Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-mt65xx-usb3.c | 99 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-mt65xx-usb3.c b/drivers/phy/phy-mt65xx-usb3.c index 2afbf9f36d83..c0e7b4b0cf5c 100644 --- a/drivers/phy/phy-mt65xx-usb3.c +++ b/drivers/phy/phy-mt65xx-usb3.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ * relative to USB3_SIF2_BASE base address */ #define SSUSB_SIFSLV_SPLLC 0x0000 +#define SSUSB_SIFSLV_U2FREQ 0x0100 /* offsets of sub-segment in each port registers */ #define SSUSB_SIFSLV_U2PHY_COM_BASE 0x0000 @@ -41,6 +43,7 @@ #define PA2_RG_SIF_U2PLL_FORCE_EN BIT(18) #define U3P_USBPHYACR5 (SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0014) +#define PA5_RG_U2_HSTX_SRCAL_EN BIT(15) #define PA5_RG_U2_HSTX_SRCTRL GENMASK(14, 12) #define PA5_RG_U2_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12) #define PA5_RG_U2_HS_100U_U3_EN BIT(11) @@ -113,6 +116,24 @@ #define XC3_RG_U3_XTAL_RX_PWD BIT(9) #define XC3_RG_U3_FRC_XTAL_RX_PWD BIT(8) +#define U3P_U2FREQ_FMCR0 (SSUSB_SIFSLV_U2FREQ + 0x00) +#define P2F_RG_MONCLK_SEL GENMASK(27, 26) +#define P2F_RG_MONCLK_SEL_VAL(x) ((0x3 & (x)) << 26) +#define P2F_RG_FREQDET_EN BIT(24) +#define P2F_RG_CYCLECNT GENMASK(23, 0) +#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x)) + +#define U3P_U2FREQ_VALUE (SSUSB_SIFSLV_U2FREQ + 0x0c) + +#define U3P_U2FREQ_FMMONR1 (SSUSB_SIFSLV_U2FREQ + 0x10) +#define P2F_USB_FM_VALID BIT(0) +#define P2F_RG_FRCK_EN BIT(8) + +#define U3P_REF_CLK 26 /* MHZ */ +#define U3P_SLEW_RATE_COEF 28 +#define U3P_SR_COEF_DIVISOR 1000 +#define U3P_FM_DET_CYCLE_CNT 1024 + struct mt65xx_phy_instance { struct phy *phy; void __iomem *port_base; @@ -128,6 +149,77 @@ struct mt65xx_u3phy { int nphys; }; +static void hs_slew_rate_calibrate(struct mt65xx_u3phy *u3phy, + struct mt65xx_phy_instance *instance) +{ + void __iomem *sif_base = u3phy->sif_base; + int calibration_val; + int fm_out; + u32 tmp; + + /* enable USB ring oscillator */ + tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp |= PA5_RG_U2_HSTX_SRCAL_EN; + writel(tmp, instance->port_base + U3P_USBPHYACR5); + udelay(1); + + /*enable free run clock */ + tmp = readl(sif_base + U3P_U2FREQ_FMMONR1); + tmp |= P2F_RG_FRCK_EN; + writel(tmp, sif_base + U3P_U2FREQ_FMMONR1); + + /* set cycle count as 1024, and select u2 channel */ + tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL); + tmp |= P2F_RG_CYCLECNT_VAL(U3P_FM_DET_CYCLE_CNT); + tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index); + writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + + /* enable frequency meter */ + tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp |= P2F_RG_FREQDET_EN; + writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + + /* ignore return value */ + readl_poll_timeout(sif_base + U3P_U2FREQ_FMMONR1, tmp, + (tmp & P2F_USB_FM_VALID), 10, 200); + + fm_out = readl(sif_base + U3P_U2FREQ_VALUE); + + /* disable frequency meter */ + tmp = readl(sif_base + U3P_U2FREQ_FMCR0); + tmp &= ~P2F_RG_FREQDET_EN; + writel(tmp, sif_base + U3P_U2FREQ_FMCR0); + + /*disable free run clock */ + tmp = readl(sif_base + U3P_U2FREQ_FMMONR1); + tmp &= ~P2F_RG_FRCK_EN; + writel(tmp, sif_base + U3P_U2FREQ_FMMONR1); + + if (fm_out) { + /* ( 1024 / FM_OUT ) x reference clock frequency x 0.028 */ + tmp = U3P_FM_DET_CYCLE_CNT * U3P_REF_CLK * U3P_SLEW_RATE_COEF; + tmp /= fm_out; + calibration_val = DIV_ROUND_CLOSEST(tmp, U3P_SR_COEF_DIVISOR); + } else { + /* if FM detection fail, set default value */ + calibration_val = 4; + } + dev_dbg(u3phy->dev, "phy:%d, fm_out:%d, calib:%d\n", + instance->index, fm_out, calibration_val); + + /* set HS slew rate */ + tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp &= ~PA5_RG_U2_HSTX_SRCTRL; + tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(calibration_val); + writel(tmp, instance->port_base + U3P_USBPHYACR5); + + /* disable USB ring oscillator */ + tmp = readl(instance->port_base + U3P_USBPHYACR5); + tmp &= ~PA5_RG_U2_HSTX_SRCAL_EN; + writel(tmp, instance->port_base + U3P_USBPHYACR5); +} + static void phy_instance_init(struct mt65xx_u3phy *u3phy, struct mt65xx_phy_instance *instance) { @@ -226,9 +318,9 @@ static void phy_instance_power_on(struct mt65xx_u3phy *u3phy, tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD; writel(tmp, u3phy->sif_base + U3P_XTALCTL3); - /* [mt8173]disable Change 100uA current from SSUSB */ + /* [mt8173]switch 100uA current to SSUSB */ tmp = readl(port_base + U3P_USBPHYACR5); - tmp &= ~PA5_RG_U2_HS_100U_U3_EN; + tmp |= PA5_RG_U2_HS_100U_U3_EN; writel(tmp, port_base + U3P_USBPHYACR5); } @@ -273,7 +365,7 @@ static void phy_instance_power_off(struct mt65xx_u3phy *u3phy, writel(tmp, port_base + U3P_USBPHYACR6); if (!index) { - /* (also disable)Change 100uA current switch to USB2.0 */ + /* switch 100uA current back to USB2.0 */ tmp = readl(port_base + U3P_USBPHYACR5); tmp &= ~PA5_RG_U2_HS_100U_U3_EN; writel(tmp, port_base + U3P_USBPHYACR5); @@ -343,6 +435,7 @@ static int mt65xx_phy_power_on(struct phy *phy) struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent); phy_instance_power_on(u3phy, instance); + hs_slew_rate_calibrate(u3phy, instance); return 0; } -- cgit v1.2.3 From 68dbc2ce77bbe18a0475a497f4d6adde7c5d0b0c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 11 Dec 2015 16:32:17 +0100 Subject: phy-sun4i-usb: Use of_match_node to get model specific config data Use of_match_node instead of calling of_device_is_compatible a ton of times to get model specific config data. Signed-off-by: Hans de Goede Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-sun4i-usb.c | 121 +++++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 42 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c index b12964b70625..35b1fa3b71fe 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/phy-sun4i-usb.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -88,12 +89,23 @@ #define DEBOUNCE_TIME msecs_to_jiffies(50) #define POLL_TIME msecs_to_jiffies(250) +enum sun4i_usb_phy_type { + sun4i_a10_phy, + sun8i_a33_phy, +}; + +struct sun4i_usb_phy_cfg { + int num_phys; + enum sun4i_usb_phy_type type; + u32 disc_thresh; + u8 phyctl_offset; + bool dedicated_clocks; +}; + struct sun4i_usb_phy_data { void __iomem *base; + const struct sun4i_usb_phy_cfg *cfg; struct mutex mutex; - int num_phys; - u32 disc_thresh; - bool has_a33_phyctl; struct sun4i_usb_phy { struct phy *phy; void __iomem *pmu; @@ -159,17 +171,14 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, { struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); u32 temp, usbc_bit = BIT(phy->index * 2); - void *phyctl; + void *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; int i; mutex_lock(&phy_data->mutex); - if (phy_data->has_a33_phyctl) { - phyctl = phy_data->base + REG_PHYCTL_A33; + if (phy_data->cfg->type == sun8i_a33_phy) { /* A33 needs us to set phyctl to 0 explicitly */ writel(0, phyctl); - } else { - phyctl = phy_data->base + REG_PHYCTL_A10; } for (i = 0; i < len; i++) { @@ -249,7 +258,8 @@ static int sun4i_usb_phy_init(struct phy *_phy) sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); /* Disconnect threshold adjustment */ - sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2); + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, + data->cfg->disc_thresh, 2); sun4i_usb_phy_passby(phy, 1); @@ -476,7 +486,7 @@ static struct phy *sun4i_usb_phy_xlate(struct device *dev, { struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); - if (args->args[0] >= data->num_phys) + if (args->args[0] >= data->cfg->num_phys) return ERR_PTR(-ENODEV); return data->phys[args->args[0]].phy; @@ -511,7 +521,6 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct phy_provider *phy_provider; - bool dedicated_clocks; struct resource *res; int i, ret; @@ -522,29 +531,9 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) mutex_init(&data->mutex); INIT_DELAYED_WORK(&data->detect, sun4i_usb_phy0_id_vbus_det_scan); dev_set_drvdata(dev, data); - - if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a23-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy")) - data->num_phys = 2; - else - data->num_phys = 3; - - if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy") || - of_device_is_compatible(np, "allwinner,sun7i-a20-usb-phy")) - data->disc_thresh = 2; - else - data->disc_thresh = 3; - - if (of_device_is_compatible(np, "allwinner,sun6i-a31-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a23-usb-phy") || - of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy")) - dedicated_clocks = true; - else - dedicated_clocks = false; - - if (of_device_is_compatible(np, "allwinner,sun8i-a33-usb-phy")) - data->has_a33_phyctl = true; + data->cfg = of_device_get_match_data(dev); + if (!data->cfg) + return -EINVAL; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); data->base = devm_ioremap_resource(dev, res); @@ -590,7 +579,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) } } - for (i = 0; i < data->num_phys; i++) { + for (i = 0; i < data->cfg->num_phys; i++) { struct sun4i_usb_phy *phy = data->phys + i; char name[16]; @@ -602,7 +591,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) phy->vbus = NULL; } - if (dedicated_clocks) + if (data->cfg->dedicated_clocks) snprintf(name, sizeof(name), "usb%d_phy", i); else strlcpy(name, "usb_phy", sizeof(name)); @@ -689,13 +678,61 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) return 0; } +static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, +}; + +static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, +}; + +static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = true, +}; + +static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = true, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { + .num_phys = 2, + .type = sun8i_a33_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, +}; + static const struct of_device_id sun4i_usb_phy_of_match[] = { - { .compatible = "allwinner,sun4i-a10-usb-phy" }, - { .compatible = "allwinner,sun5i-a13-usb-phy" }, - { .compatible = "allwinner,sun6i-a31-usb-phy" }, - { .compatible = "allwinner,sun7i-a20-usb-phy" }, - { .compatible = "allwinner,sun8i-a23-usb-phy" }, - { .compatible = "allwinner,sun8i-a33-usb-phy" }, + { .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg }, + { .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg }, + { .compatible = "allwinner,sun6i-a31-usb-phy", .data = &sun6i_a31_cfg }, + { .compatible = "allwinner,sun7i-a20-usb-phy", .data = &sun7i_a20_cfg }, + { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg }, + { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg }, { }, }; MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); -- cgit v1.2.3 From 626a630e003c10c800a816cb994b3f9e505a88a9 Mon Sep 17 00:00:00 2001 From: Reinder de Haan Date: Fri, 11 Dec 2015 16:32:18 +0100 Subject: phy-sun4i-usb: Add support for the host usb-phys found on the H3 SoC Note this commit only adds support for phys 1-3, phy 0, the otg phy, is not yet (fully) supported after this commit. Signed-off-by: Reinder de Haan Signed-off-by: Hans de Goede Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/sun4i-usb-phy.txt | 1 + drivers/phy/phy-sun4i-usb.c | 41 +++++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt index 0cebf7454517..95736d77fbb7 100644 --- a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt @@ -9,6 +9,7 @@ Required properties: * allwinner,sun7i-a20-usb-phy * allwinner,sun8i-a23-usb-phy * allwinner,sun8i-a33-usb-phy + * allwinner,sun8i-h3-usb-phy - reg : a list of offset + length pairs - reg-names : * "phy_ctrl" diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c index 35b1fa3b71fe..bae54f7a1f48 100644 --- a/drivers/phy/phy-sun4i-usb.c +++ b/drivers/phy/phy-sun4i-usb.c @@ -47,6 +47,9 @@ #define REG_PHYBIST 0x08 #define REG_PHYTUNE 0x0c #define REG_PHYCTL_A33 0x10 +#define REG_PHY_UNK_H3 0x20 + +#define REG_PMU_UNK_H3 0x10 #define PHYCTL_DATA BIT(7) @@ -80,7 +83,7 @@ #define PHY_DISCON_TH_SEL 0x2a #define PHY_SQUELCH_DETECT 0x3c -#define MAX_PHYS 3 +#define MAX_PHYS 4 /* * Note do not raise the debounce time, we must report Vusb high within 100ms @@ -92,6 +95,7 @@ enum sun4i_usb_phy_type { sun4i_a10_phy, sun8i_a33_phy, + sun8i_h3_phy, }; struct sun4i_usb_phy_cfg { @@ -239,6 +243,7 @@ static int sun4i_usb_phy_init(struct phy *_phy) struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); int ret; + u32 val; ret = clk_prepare_enable(phy->clk); if (ret) @@ -250,16 +255,26 @@ static int sun4i_usb_phy_init(struct phy *_phy) return ret; } - /* Enable USB 45 Ohm resistor calibration */ - if (phy->index == 0) - sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); + if (data->cfg->type == sun8i_h3_phy) { + if (phy->index == 0) { + val = readl(data->base + REG_PHY_UNK_H3); + writel(val & ~1, data->base + REG_PHY_UNK_H3); + } + + val = readl(phy->pmu + REG_PMU_UNK_H3); + writel(val & ~2, phy->pmu + REG_PMU_UNK_H3); + } else { + /* Enable USB 45 Ohm resistor calibration */ + if (phy->index == 0) + sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); - /* Adjust PHY's magnitude and rate */ - sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); + /* Adjust PHY's magnitude and rate */ + sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); - /* Disconnect threshold adjustment */ - sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, - data->cfg->disc_thresh, 2); + /* Disconnect threshold adjustment */ + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, + data->cfg->disc_thresh, 2); + } sun4i_usb_phy_passby(phy, 1); @@ -726,6 +741,13 @@ static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { .dedicated_clocks = true, }; +static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { + .num_phys = 4, + .type = sun8i_h3_phy, + .disc_thresh = 3, + .dedicated_clocks = true, +}; + static const struct of_device_id sun4i_usb_phy_of_match[] = { { .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg }, { .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg }, @@ -733,6 +755,7 @@ static const struct of_device_id sun4i_usb_phy_of_match[] = { { .compatible = "allwinner,sun7i-a20-usb-phy", .data = &sun7i_a20_cfg }, { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg }, { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg }, + { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg }, { }, }; MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); -- cgit v1.2.3 From 30e9a0b2147c8405109ad98ae670829dd92e4516 Mon Sep 17 00:00:00 2001 From: Zhangfei Gao Date: Mon, 23 Nov 2015 11:46:27 +0800 Subject: phy: add phy-hi6220-usb Support hi6220 use phy for HiKey board Acked-by: Rob Herring Signed-off-by: Zhangfei Gao Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/phy-hi6220-usb.txt | 16 ++ drivers/phy/Kconfig | 9 ++ drivers/phy/Makefile | 1 + drivers/phy/phy-hi6220-usb.c | 168 +++++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-hi6220-usb.txt create mode 100644 drivers/phy/phy-hi6220-usb.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/phy-hi6220-usb.txt b/Documentation/devicetree/bindings/phy/phy-hi6220-usb.txt new file mode 100644 index 000000000000..f17a56e2152f --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-hi6220-usb.txt @@ -0,0 +1,16 @@ +Hisilicon hi6220 usb PHY +----------------------- + +Required properties: +- compatible: should be "hisilicon,hi6220-usb-phy" +- #phy-cells: must be 0 +- hisilicon,peripheral-syscon: phandle of syscon used to control phy. +Refer to phy/phy-bindings.txt for the generic PHY binding properties + +Example: + usb_phy: usbphy { + compatible = "hisilicon,hi6220-usb-phy"; + #phy-cells = <0>; + phy-supply = <&fixed_5v_hub>; + hisilicon,peripheral-syscon = <&sys_ctrl>; + }; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index f90b7660dd3e..90eec60d7ba5 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -222,6 +222,15 @@ config PHY_MT65XX_USB3 for mt65xx SoCs. it supports two usb2.0 ports and one usb3.0 port. +config PHY_HI6220_USB + tristate "hi6220 USB PHY support" + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON HI6220 USB PHY. + + To compile this driver as a module, choose M here. + config PHY_SUN4I_USB tristate "Allwinner sunxi SoC USB PHY driver" depends on ARCH_SUNXI && HAS_IOMEM && OF diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 91d7a62c6794..c80f09df3bb8 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o +obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o obj-$(CONFIG_PHY_MT65XX_USB3) += phy-mt65xx-usb3.o obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o diff --git a/drivers/phy/phy-hi6220-usb.c b/drivers/phy/phy-hi6220-usb.c new file mode 100644 index 000000000000..b2141cbd4cf6 --- /dev/null +++ b/drivers/phy/phy-hi6220-usb.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2015 Linaro Ltd. + * Copyright (c) 2015 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#define SC_PERIPH_CTRL4 0x00c + +#define CTRL4_PICO_SIDDQ BIT(6) +#define CTRL4_PICO_OGDISABLE BIT(8) +#define CTRL4_PICO_VBUSVLDEXT BIT(10) +#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11) +#define CTRL4_OTG_PHY_SEL BIT(21) + +#define SC_PERIPH_CTRL5 0x010 + +#define CTRL5_USBOTG_RES_SEL BIT(3) +#define CTRL5_PICOPHY_ACAENB BIT(4) +#define CTRL5_PICOPHY_BC_MODE BIT(5) +#define CTRL5_PICOPHY_CHRGSEL BIT(6) +#define CTRL5_PICOPHY_VDATSRCEND BIT(7) +#define CTRL5_PICOPHY_VDATDETENB BIT(8) +#define CTRL5_PICOPHY_DCDENB BIT(9) +#define CTRL5_PICOPHY_IDDIG BIT(10) + +#define SC_PERIPH_CTRL8 0x018 +#define SC_PERIPH_RSTEN0 0x300 +#define SC_PERIPH_RSTDIS0 0x304 + +#define RST0_USBOTG_BUS BIT(4) +#define RST0_POR_PICOPHY BIT(5) +#define RST0_USBOTG BIT(6) +#define RST0_USBOTG_32K BIT(7) + +#define EYE_PATTERN_PARA 0x7053348c + +struct hi6220_priv { + struct regmap *reg; + struct device *dev; +}; + +static void hi6220_phy_init(struct hi6220_priv *priv) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + + val = RST0_USBOTG_BUS | RST0_POR_PICOPHY | + RST0_USBOTG | RST0_USBOTG_32K; + mask = val; + regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val); + regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val); +} + +static int hi6220_phy_setup(struct hi6220_priv *priv, bool on) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + int ret; + + if (on) { + val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB; + mask = val | CTRL5_PICOPHY_BC_MODE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val); + if (ret) + goto out; + + val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL | + CTRL4_OTG_PHY_SEL; + mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + + ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA); + if (ret) + goto out; + } else { + val = CTRL4_PICO_SIDDQ; + mask = val; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + } + + return 0; +out: + dev_err(priv->dev, "failed to setup phy ret: %d\n", ret); + return ret; +} + +static int hi6220_phy_start(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, true); +} + +static int hi6220_phy_exit(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, false); +} + +static struct phy_ops hi6220_phy_ops = { + .init = hi6220_phy_start, + .exit = hi6220_phy_exit, + .owner = THIS_MODULE, +}; + +static int hi6220_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *phy; + struct hi6220_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,peripheral-syscon"); + if (IS_ERR(priv->reg)) { + dev_err(dev, "no hisilicon,peripheral-syscon\n"); + return PTR_ERR(priv->reg); + } + + hi6220_phy_init(priv); + + phy = devm_phy_create(dev, NULL, &hi6220_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id hi6220_phy_of_match[] = { + {.compatible = "hisilicon,hi6220-usb-phy",}, + { }, +}; +MODULE_DEVICE_TABLE(of, hi6220_phy_of_match); + +static struct platform_driver hi6220_phy_driver = { + .probe = hi6220_phy_probe, + .driver = { + .name = "hi6220-usb-phy", + .of_match_table = hi6220_phy_of_match, + } +}; +module_platform_driver(hi6220_phy_driver); + +MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver"); +MODULE_ALIAS("platform:hi6220-usb-phy"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 75d390fecf9e69eebca7f15de40d761e33fcfd7b Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 19 Nov 2015 22:22:22 +0100 Subject: phy: rockchip-usb: fix clock get-put mismatch Currently the phy driver only gets the optional clock reference but never puts it again, neither during error handling nor on remove. Fix that by moving the clk_put to a devm-action that gets called at the right time when all other devm actions are done. Signed-off-by: Heiko Stuebner Reviewed-by: Douglas Anderson Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-rockchip-usb.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index 62c43c435194..e941444072ba 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -90,6 +90,14 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +static void rockchip_usb_phy_action(void *data) +{ + struct rockchip_usb_phy *rk_phy = data; + + if (rk_phy->clk) + clk_put(rk_phy->clk); +} + static int rockchip_usb_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -123,6 +131,10 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev) rk_phy->reg_offset = reg_offset; rk_phy->reg_base = grf; + err = devm_add_action(dev, rockchip_usb_phy_action, rk_phy); + if (err) + return err; + rk_phy->clk = of_clk_get_by_name(child, "phyclk"); if (IS_ERR(rk_phy->clk)) rk_phy->clk = NULL; -- cgit v1.2.3 From 5fdbb97dec0ca6106c5595c2db667a7957ad8c0f Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 19 Nov 2015 22:22:23 +0100 Subject: phy: rockchip-usb: introduce a common data-struct for the device This introduces a common struct that holds data belonging to the umbrella device that contains all the phys and that we want to use later. Signed-off-by: Heiko Stuebner Reviewed-by: Douglas Anderson Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-rockchip-usb.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index e941444072ba..2b4802a7497d 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -36,9 +36,14 @@ #define SIDDQ_ON BIT(13) #define SIDDQ_OFF (0 << 13) +struct rockchip_usb_phy_base { + struct device *dev; + struct regmap *reg_base; +}; + struct rockchip_usb_phy { + struct rockchip_usb_phy_base *base; unsigned int reg_offset; - struct regmap *reg_base; struct clk *clk; struct phy *phy; }; @@ -46,7 +51,7 @@ struct rockchip_usb_phy { static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, bool siddq) { - return regmap_write(phy->reg_base, phy->reg_offset, + return regmap_write(phy->base->reg_base, phy->reg_offset, SIDDQ_WRITE_ENA | (siddq ? SIDDQ_ON : SIDDQ_OFF)); } @@ -101,17 +106,23 @@ static void rockchip_usb_phy_action(void *data) static int rockchip_usb_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct rockchip_usb_phy_base *phy_base; struct rockchip_usb_phy *rk_phy; struct phy_provider *phy_provider; struct device_node *child; - struct regmap *grf; unsigned int reg_offset; int err; - grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); - if (IS_ERR(grf)) { + phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL); + if (!phy_base) + return -ENOMEM; + + phy_base->dev = dev; + phy_base->reg_base = syscon_regmap_lookup_by_phandle(dev->of_node, + "rockchip,grf"); + if (IS_ERR(phy_base->reg_base)) { dev_err(&pdev->dev, "Missing rockchip,grf property\n"); - return PTR_ERR(grf); + return PTR_ERR(phy_base->reg_base); } for_each_available_child_of_node(dev->of_node, child) { @@ -121,6 +132,8 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev) goto put_child; } + rk_phy->base = phy_base; + if (of_property_read_u32(child, "reg", ®_offset)) { dev_err(dev, "missing reg property in node %s\n", child->name); @@ -129,7 +142,6 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev) } rk_phy->reg_offset = reg_offset; - rk_phy->reg_base = grf; err = devm_add_action(dev, rockchip_usb_phy_action, rk_phy); if (err) -- cgit v1.2.3 From 97dd9101091bb13b1ca3fe42edacf2bd7cf10924 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 19 Nov 2015 22:22:24 +0100 Subject: phy: rockchip-usb: move per-phy init into a separate function This unclutters the loop in probe a lot and makes current (and future) error handling easier to read. Signed-off-by: Heiko Stuebner Reviewed-by: Douglas Anderson Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-rockchip-usb.c | 83 ++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 40 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index 2b4802a7497d..ff3ac3379c61 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -103,14 +103,52 @@ static void rockchip_usb_phy_action(void *data) clk_put(rk_phy->clk); } +static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, + struct device_node *child) +{ + struct rockchip_usb_phy *rk_phy; + unsigned int reg_offset; + int err; + + rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL); + if (!rk_phy) + return -ENOMEM; + + rk_phy->base = base; + + if (of_property_read_u32(child, "reg", ®_offset)) { + dev_err(base->dev, "missing reg property in node %s\n", + child->name); + return -EINVAL; + } + + rk_phy->reg_offset = reg_offset; + + err = devm_add_action(base->dev, rockchip_usb_phy_action, rk_phy); + if (err) + return err; + + rk_phy->clk = of_clk_get_by_name(child, "phyclk"); + if (IS_ERR(rk_phy->clk)) + rk_phy->clk = NULL; + + rk_phy->phy = devm_phy_create(base->dev, child, &ops); + if (IS_ERR(rk_phy->phy)) { + dev_err(base->dev, "failed to create PHY\n"); + return PTR_ERR(rk_phy->phy); + } + phy_set_drvdata(rk_phy->phy, rk_phy); + + /* only power up usb phy when it use, so disable it when init*/ + return rockchip_usb_phy_power(rk_phy, 1); +} + static int rockchip_usb_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rockchip_usb_phy_base *phy_base; - struct rockchip_usb_phy *rk_phy; struct phy_provider *phy_provider; struct device_node *child; - unsigned int reg_offset; int err; phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL); @@ -126,50 +164,15 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev) } for_each_available_child_of_node(dev->of_node, child) { - rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); - if (!rk_phy) { - err = -ENOMEM; - goto put_child; - } - - rk_phy->base = phy_base; - - if (of_property_read_u32(child, "reg", ®_offset)) { - dev_err(dev, "missing reg property in node %s\n", - child->name); - err = -EINVAL; - goto put_child; - } - - rk_phy->reg_offset = reg_offset; - - err = devm_add_action(dev, rockchip_usb_phy_action, rk_phy); - if (err) + err = rockchip_usb_phy_init(phy_base, child); + if (err) { + of_node_put(child); return err; - - rk_phy->clk = of_clk_get_by_name(child, "phyclk"); - if (IS_ERR(rk_phy->clk)) - rk_phy->clk = NULL; - - rk_phy->phy = devm_phy_create(dev, child, &ops); - if (IS_ERR(rk_phy->phy)) { - dev_err(dev, "failed to create PHY\n"); - err = PTR_ERR(rk_phy->phy); - goto put_child; } - phy_set_drvdata(rk_phy->phy, rk_phy); - - /* only power up usb phy when it use, so disable it when init*/ - err = rockchip_usb_phy_power(rk_phy, 1); - if (err) - goto put_child; } phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); return PTR_ERR_OR_ZERO(phy_provider); -put_child: - of_node_put(child); - return err; } static const struct of_device_id rockchip_usb_phy_dt_ids[] = { -- cgit v1.2.3 From c2bfc3b88813ab8711317a19f5c9cd74aeaf536c Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 19 Nov 2015 22:22:25 +0100 Subject: phy: rockchip-usb: add compatible values for rk3066a and rk3188 We need custom handling for these two socs in the driver shortly, so add the necessary compatible values to binding and driver. Signed-off-by: Heiko Stuebner Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt | 5 ++++- drivers/phy/phy-rockchip-usb.c | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt index 826454ac43bb..9b37242fb8d3 100644 --- a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt @@ -1,7 +1,10 @@ ROCKCHIP USB2 PHY Required properties: - - compatible: rockchip,rk3288-usb-phy + - compatible: matching the soc type, one of + "rockchip,rk3066a-usb-phy" + "rockchip,rk3188-usb-phy" + "rockchip,rk3288-usb-phy" - rockchip,grf : phandle to the syscon managing the "general register files" - #address-cells: should be 1 diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index ff3ac3379c61..16cd533b21f0 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -176,6 +176,8 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev) } static const struct of_device_id rockchip_usb_phy_dt_ids[] = { + { .compatible = "rockchip,rk3066a-usb-phy" }, + { .compatible = "rockchip,rk3188-usb-phy" }, { .compatible = "rockchip,rk3288-usb-phy" }, {} }; -- cgit v1.2.3 From b74fe7c7617fd267c10d53e525984df81a5f317f Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 19 Nov 2015 22:22:26 +0100 Subject: phy: rockchip-usb: expose the phy-internal PLLs The USB phys on Rockchip SoCs contain their own internal PLLs to create the 480MHz needed. Additionally this PLL output is also fed back into the core clock-controller as possible source for clocks like the GPU or others. Until now this was modelled incorrectly with a "virtual" factor clock in the clock controller. The one big caveat is that if we turn off the usb phy via the siddq signal, all analog components get turned off, including the PLLs. It is therefore possible that a source clock gets disabled without the clock driver ever knowing, possibly making the system hang. Therefore register the phy-plls as real clocks that the clock driver can then reference again normally, making the clock hirarchy finally reflect the actual hardware. The phy-ops get converted to simply turning that new clock on and off which in turn controls the siddq signal of the phy. Through this the driver gains handling for platform-specific data, to handle the phy->clock name association. Signed-off-by: Heiko Stuebner Reviewed-by: Douglas Anderson Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/rockchip-usb-phy.txt | 1 + drivers/phy/phy-rockchip-usb.c | 188 ++++++++++++++++++--- 2 files changed, 162 insertions(+), 27 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt index 9b37242fb8d3..68498d560354 100644 --- a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt @@ -24,6 +24,7 @@ required properties: Optional Properties: - clocks : phandle + clock specifier for the phy clocks - clock-names: string, clock name, must be "phyclk" +- #clock-cells: for users of the phy-pll, should be 0 Example: diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c index 16cd533b21f0..33a80eba1cb4 100644 --- a/drivers/phy/phy-rockchip-usb.c +++ b/drivers/phy/phy-rockchip-usb.c @@ -15,12 +15,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -36,15 +38,28 @@ #define SIDDQ_ON BIT(13) #define SIDDQ_OFF (0 << 13) +struct rockchip_usb_phys { + int reg; + const char *pll_name; +}; + +struct rockchip_usb_phy_pdata { + struct rockchip_usb_phys *phys; +}; + struct rockchip_usb_phy_base { struct device *dev; struct regmap *reg_base; + const struct rockchip_usb_phy_pdata *pdata; }; struct rockchip_usb_phy { struct rockchip_usb_phy_base *base; + struct device_node *np; unsigned int reg_offset; struct clk *clk; + struct clk *clk480m; + struct clk_hw clk480m_hw; struct phy *phy; }; @@ -55,17 +70,59 @@ static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, SIDDQ_WRITE_ENA | (siddq ? SIDDQ_ON : SIDDQ_OFF)); } -static int rockchip_usb_phy_power_off(struct phy *_phy) +static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) { - struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); - int ret = 0; + return 480000000; +} + +static void rockchip_usb_phy480m_disable(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); /* Power down usb phy analog blocks by set siddq 1 */ - ret = rockchip_usb_phy_power(phy, 1); - if (ret) + rockchip_usb_phy_power(phy, 1); +} + +static int rockchip_usb_phy480m_enable(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + + /* Power up usb phy analog blocks by set siddq 0 */ + return rockchip_usb_phy_power(phy, 0); +} + +static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + int ret; + u32 val; + + ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val); + if (ret < 0) return ret; - clk_disable_unprepare(phy->clk); + return (val & SIDDQ_ON) ? 0 : 1; +} + +static const struct clk_ops rockchip_usb_phy480m_ops = { + .enable = rockchip_usb_phy480m_enable, + .disable = rockchip_usb_phy480m_disable, + .is_enabled = rockchip_usb_phy480m_is_enabled, + .recalc_rate = rockchip_usb_phy480m_recalc_rate, +}; + +static int rockchip_usb_phy_power_off(struct phy *_phy) +{ + struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); + + clk_disable_unprepare(phy->clk480m); return 0; } @@ -73,20 +130,8 @@ static int rockchip_usb_phy_power_off(struct phy *_phy) static int rockchip_usb_phy_power_on(struct phy *_phy) { struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); - int ret = 0; - ret = clk_prepare_enable(phy->clk); - if (ret) - return ret; - - /* Power up usb phy analog blocks by set siddq 0 */ - ret = rockchip_usb_phy_power(phy, 0); - if (ret) { - clk_disable_unprepare(phy->clk); - return ret; - } - - return 0; + return clk_prepare_enable(phy->clk480m); } static const struct phy_ops ops = { @@ -99,6 +144,9 @@ static void rockchip_usb_phy_action(void *data) { struct rockchip_usb_phy *rk_phy = data; + of_clk_del_provider(rk_phy->np); + clk_unregister(rk_phy->clk480m); + if (rk_phy->clk) clk_put(rk_phy->clk); } @@ -108,13 +156,16 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, { struct rockchip_usb_phy *rk_phy; unsigned int reg_offset; - int err; + const char *clk_name; + struct clk_init_data init; + int err, i; rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL); if (!rk_phy) return -ENOMEM; rk_phy->base = base; + rk_phy->np = child; if (of_property_read_u32(child, "reg", ®_offset)) { dev_err(base->dev, "missing reg property in node %s\n", @@ -124,14 +175,54 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, rk_phy->reg_offset = reg_offset; - err = devm_add_action(base->dev, rockchip_usb_phy_action, rk_phy); - if (err) - return err; - rk_phy->clk = of_clk_get_by_name(child, "phyclk"); if (IS_ERR(rk_phy->clk)) rk_phy->clk = NULL; + i = 0; + init.name = NULL; + while (base->pdata->phys[i].reg) { + if (base->pdata->phys[i].reg == reg_offset) { + init.name = base->pdata->phys[i].pll_name; + break; + } + i++; + } + + if (!init.name) { + dev_err(base->dev, "phy data not found\n"); + return -EINVAL; + } + + if (rk_phy->clk) { + clk_name = __clk_get_name(rk_phy->clk); + init.flags = 0; + init.parent_names = &clk_name; + init.num_parents = 1; + } else { + init.flags = CLK_IS_ROOT; + init.parent_names = NULL; + init.num_parents = 0; + } + + init.ops = &rockchip_usb_phy480m_ops; + rk_phy->clk480m_hw.init = &init; + + rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw); + if (IS_ERR(rk_phy->clk480m)) { + err = PTR_ERR(rk_phy->clk480m); + goto err_clk; + } + + err = of_clk_add_provider(child, of_clk_src_simple_get, + rk_phy->clk480m); + if (err < 0) + goto err_clk_prov; + + err = devm_add_action(base->dev, rockchip_usb_phy_action, rk_phy); + if (err) + goto err_devm_action; + rk_phy->phy = devm_phy_create(base->dev, child, &ops); if (IS_ERR(rk_phy->phy)) { dev_err(base->dev, "failed to create PHY\n"); @@ -141,13 +232,48 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, /* only power up usb phy when it use, so disable it when init*/ return rockchip_usb_phy_power(rk_phy, 1); + +err_devm_action: + of_clk_del_provider(child); +err_clk_prov: + clk_unregister(rk_phy->clk480m); +err_clk: + if (rk_phy->clk) + clk_put(rk_phy->clk); + return err; } +static const struct rockchip_usb_phy_pdata rk3066a_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" }, + { /* sentinel */ } + }, +}; + +static const struct rockchip_usb_phy_pdata rk3188_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" }, + { /* sentinel */ } + }, +}; + +static const struct rockchip_usb_phy_pdata rk3288_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x320, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x334, .pll_name = "sclk_otgphy1_480m" }, + { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" }, + { /* sentinel */ } + }, +}; + static int rockchip_usb_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rockchip_usb_phy_base *phy_base; struct phy_provider *phy_provider; + const struct of_device_id *match; struct device_node *child; int err; @@ -155,6 +281,14 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev) if (!phy_base) return -ENOMEM; + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "missing phy data\n"); + return -EINVAL; + } + + phy_base->pdata = match->data; + phy_base->dev = dev; phy_base->reg_base = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); @@ -176,9 +310,9 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev) } static const struct of_device_id rockchip_usb_phy_dt_ids[] = { - { .compatible = "rockchip,rk3066a-usb-phy" }, - { .compatible = "rockchip,rk3188-usb-phy" }, - { .compatible = "rockchip,rk3288-usb-phy" }, + { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata }, + { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata }, + { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata }, {} }; -- cgit v1.2.3 From 1dc3a8582c3b99bd88b47f7aafd07ceb934024dc Mon Sep 17 00:00:00 2001 From: Jaedon Shin Date: Thu, 26 Nov 2015 11:56:33 +0900 Subject: phy: phy_brcmstb_sata: remove duplicate definitions Remove duplicate definitions. Signed-off-by: Jaedon Shin Acked-by: Florian Fainelli Acked-by: Brian Norris Tested-by: Florian Fainelli Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-brcmstb-sata.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-brcmstb-sata.c b/drivers/phy/phy-brcmstb-sata.c index cd9dba820566..ddb9b9e8270d 100644 --- a/drivers/phy/phy-brcmstb-sata.c +++ b/drivers/phy/phy-brcmstb-sata.c @@ -26,8 +26,6 @@ #define SATA_MDIO_BANK_OFFSET 0x23c #define SATA_MDIO_REG_OFFSET(ofs) ((ofs) * 4) -#define SATA_MDIO_REG_SPACE_SIZE 0x1000 -#define SATA_MDIO_REG_LENGTH 0x1f00 #define MAX_PORTS 2 -- cgit v1.2.3 From 810c6f169fd1d8a37df884c0942727863f00319a Mon Sep 17 00:00:00 2001 From: Jaedon Shin Date: Thu, 26 Nov 2015 11:56:34 +0900 Subject: phy: phy_brcmstb_sata: add data for phy version Add data for phy version, and the default value of version is using the BRCM_SATA_PHY_28NM. Signed-off-by: Jaedon Shin Tested-by: Florian Fainelli Acked-by: Brian Norris Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-brcmstb-sata.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-brcmstb-sata.c b/drivers/phy/phy-brcmstb-sata.c index ddb9b9e8270d..555fb2de11d4 100644 --- a/drivers/phy/phy-brcmstb-sata.c +++ b/drivers/phy/phy-brcmstb-sata.c @@ -30,7 +30,11 @@ #define MAX_PORTS 2 /* Register offset between PHYs in PCB space */ -#define SATA_MDIO_REG_SPACE_SIZE 0x1000 +#define SATA_MDIO_REG_28NM_SPACE_SIZE 0x1000 + +enum brcm_sata_phy_version { + BRCM_SATA_PHY_28NM, +}; struct brcm_sata_port { int portnum; @@ -42,6 +46,7 @@ struct brcm_sata_port { struct brcm_sata_phy { struct device *dev; void __iomem *phy_base; + enum brcm_sata_phy_version version; struct brcm_sata_port phys[MAX_PORTS]; }; @@ -64,8 +69,12 @@ enum sata_mdio_phy_regs_28nm { static inline void __iomem *brcm_sata_phy_base(struct brcm_sata_port *port) { struct brcm_sata_phy *priv = port->phy_priv; + u32 offset; - return priv->phy_base + (port->portnum * SATA_MDIO_REG_SPACE_SIZE); + if (priv->version == BRCM_SATA_PHY_28NM) + offset = SATA_MDIO_REG_28NM_SPACE_SIZE; + + return priv->phy_base + (port->portnum * offset); } static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs, @@ -126,7 +135,8 @@ static const struct phy_ops phy_ops_28nm = { }; static const struct of_device_id brcm_sata_phy_of_match[] = { - { .compatible = "brcm,bcm7445-sata-phy" }, + { .compatible = "brcm,bcm7445-sata-phy", + .data = (void *)BRCM_SATA_PHY_28NM }, {}, }; MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); @@ -135,6 +145,7 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *dn = dev->of_node, *child; + const struct of_device_id *of_id; struct brcm_sata_phy *priv; struct resource *res; struct phy_provider *provider; @@ -154,6 +165,12 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) if (IS_ERR(priv->phy_base)) return PTR_ERR(priv->phy_base); + of_id = of_match_node(brcm_sata_phy_of_match, dn); + if (of_id) + priv->version = (enum brcm_sata_phy_version)of_id->data; + else + priv->version = BRCM_SATA_PHY_28NM; + for_each_available_child_of_node(dn, child) { unsigned int id; struct brcm_sata_port *port; -- cgit v1.2.3 From c1602a1a0fbe12ab8e67deedf32e5a85f8553a07 Mon Sep 17 00:00:00 2001 From: Jaedon Shin Date: Thu, 26 Nov 2015 11:56:35 +0900 Subject: phy: phy_brcmstb_sata: add support for MIPS-based platforms The BCM7xxx ARM-based and MIPS-based platforms share a similar hardware block for AHCI SATA3. This new compatible string, "brcm,bcm7425-sata-phy", may be used for most MIPS-based platforms of 40nm process technology. Signed-off-by: Jaedon Shin Acked-by: Rob Herring Tested-by: Florian Fainelli Acked-by: Brian Norris Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/brcm,brcmstb-sata-phy.txt | 1 + drivers/phy/Kconfig | 4 ++-- drivers/phy/phy-brcmstb-sata.c | 24 ++++++++++++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt b/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt index 7f81ef90146a..d87ab7c127b8 100644 --- a/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt +++ b/Documentation/devicetree/bindings/phy/brcm,brcmstb-sata-phy.txt @@ -2,6 +2,7 @@ Required properties: - compatible: should be one or more of + "brcm,bcm7425-sata-phy" "brcm,bcm7445-sata-phy" "brcm,phy-sata3" - address-cells: should be 1 diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 90eec60d7ba5..e7e117d5dbbe 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -390,11 +390,11 @@ config PHY_TUSB1210 config PHY_BRCMSTB_SATA tristate "Broadcom STB SATA PHY driver" - depends on ARCH_BRCMSTB + depends on ARCH_BRCMSTB || BMIPS_GENERIC depends on OF select GENERIC_PHY help - Enable this to support the SATA3 PHY on 28nm Broadcom STB SoCs. + Enable this to support the SATA3 PHY on 28nm or 40nm Broadcom STB SoCs. Likely useful only with CONFIG_SATA_BRCMSTB enabled. config PHY_CYGNUS_PCIE diff --git a/drivers/phy/phy-brcmstb-sata.c b/drivers/phy/phy-brcmstb-sata.c index 555fb2de11d4..a23172ff40e3 100644 --- a/drivers/phy/phy-brcmstb-sata.c +++ b/drivers/phy/phy-brcmstb-sata.c @@ -32,8 +32,14 @@ /* Register offset between PHYs in PCB space */ #define SATA_MDIO_REG_28NM_SPACE_SIZE 0x1000 +/* The older SATA PHY registers duplicated per port registers within the map, + * rather than having a separate map per port. + */ +#define SATA_MDIO_REG_40NM_SPACE_SIZE 0x10 + enum brcm_sata_phy_version { BRCM_SATA_PHY_28NM, + BRCM_SATA_PHY_40NM, }; struct brcm_sata_port { @@ -51,7 +57,7 @@ struct brcm_sata_phy { struct brcm_sata_port phys[MAX_PORTS]; }; -enum sata_mdio_phy_regs_28nm { +enum sata_mdio_phy_regs { PLL_REG_BANK_0 = 0x50, PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, @@ -69,10 +75,14 @@ enum sata_mdio_phy_regs_28nm { static inline void __iomem *brcm_sata_phy_base(struct brcm_sata_port *port) { struct brcm_sata_phy *priv = port->phy_priv; - u32 offset; + u32 offset = 0; if (priv->version == BRCM_SATA_PHY_28NM) offset = SATA_MDIO_REG_28NM_SPACE_SIZE; + else if (priv->version == BRCM_SATA_PHY_40NM) + offset = SATA_MDIO_REG_40NM_SPACE_SIZE; + else + dev_err(priv->dev, "invalid phy version\n"); return priv->phy_base + (port->portnum * offset); } @@ -93,7 +103,7 @@ static void brcm_sata_mdio_wr(void __iomem *addr, u32 bank, u32 ofs, #define FMAX_VAL_DEFAULT 0x3df #define FMAX_VAL_SSC 0x83 -static void brcm_sata_cfg_ssc_28nm(struct brcm_sata_port *port) +static void brcm_sata_cfg_ssc(struct brcm_sata_port *port) { void __iomem *base = brcm_sata_phy_base(port); struct brcm_sata_phy *priv = port->phy_priv; @@ -124,12 +134,12 @@ static int brcm_sata_phy_init(struct phy *phy) { struct brcm_sata_port *port = phy_get_drvdata(phy); - brcm_sata_cfg_ssc_28nm(port); + brcm_sata_cfg_ssc(port); return 0; } -static const struct phy_ops phy_ops_28nm = { +static const struct phy_ops phy_ops = { .init = brcm_sata_phy_init, .owner = THIS_MODULE, }; @@ -137,6 +147,8 @@ static const struct phy_ops phy_ops_28nm = { static const struct of_device_id brcm_sata_phy_of_match[] = { { .compatible = "brcm,bcm7445-sata-phy", .data = (void *)BRCM_SATA_PHY_28NM }, + { .compatible = "brcm,bcm7425-sata-phy", + .data = (void *)BRCM_SATA_PHY_40NM }, {}, }; MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); @@ -196,7 +208,7 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) port = &priv->phys[id]; port->portnum = id; port->phy_priv = priv; - port->phy = devm_phy_create(dev, child, &phy_ops_28nm); + port->phy = devm_phy_create(dev, child, &phy_ops); port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); if (IS_ERR(port->phy)) { dev_err(dev, "failed to create PHY\n"); -- cgit v1.2.3 From c7a0b20ed42c0f4e1f80e7a9dfa6502894a82e22 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 4 Dec 2015 21:57:00 +0800 Subject: phy: berlin-usb: remove non-necessary header files We don't need gpio related header files, so remove them. Signed-off-by: Jisheng Zhang Acked-by: Antoine Tenart Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-berlin-usb.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-berlin-usb.c b/drivers/phy/phy-berlin-usb.c index 797ba17c404f..7bbb47389fb0 100644 --- a/drivers/phy/phy-berlin-usb.c +++ b/drivers/phy/phy-berlin-usb.c @@ -9,11 +9,9 @@ * warranty of any kind, whether express or implied. */ -#include #include #include #include -#include #include #include #include -- cgit v1.2.3 From c3ab642f2f08349c41f7d837f224ce1e63b58197 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 4 Dec 2015 21:57:01 +0800 Subject: phy: berlin-usb: don't set device's driver_data After commit 739ae3452d0e ("phy: berlin-usb: Set drvdata for phy and use it"), we get the address of priv by phy_get_drvdata(), so there's no need to set device's driver_data any more. This patch removes the call of platform_set_drvdata(). Signed-off-by: Jisheng Zhang Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-berlin-usb.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/phy/phy-berlin-usb.c b/drivers/phy/phy-berlin-usb.c index 7bbb47389fb0..2017751ede26 100644 --- a/drivers/phy/phy-berlin-usb.c +++ b/drivers/phy/phy-berlin-usb.c @@ -193,7 +193,6 @@ static int phy_berlin_usb_probe(struct platform_device *pdev) return PTR_ERR(phy); } - platform_set_drvdata(pdev, priv); phy_set_drvdata(phy, priv); phy_provider = -- cgit v1.2.3 From d65ff52eb8ed5c8480fbff9ef720e2b56d9a6fb2 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:05 +0530 Subject: phy: ti-pipe3: introduce local struct device* in probe No functional change. Introduce local struct device pointer in probe and replace using &pdev->dev/phy->dev with the local device pointer. This is in preparation to split ti_pipe3_probe and add separate functions for getting mem resource, getting sysctrl and getting clocks. Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 54 +++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 27 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 93bc1120af12..c5111057d241 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -319,40 +319,41 @@ static int ti_pipe3_probe(struct platform_device *pdev) struct platform_device *control_pdev; const struct of_device_id *match; struct clk *clk; + struct device *dev = &pdev->dev; - phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); if (!phy) return -ENOMEM; - phy->dev = &pdev->dev; + phy->dev = dev; if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - match = of_match_device(ti_pipe3_id_table, &pdev->dev); + match = of_match_device(ti_pipe3_id_table, dev); if (!match) return -EINVAL; phy->dpll_map = (struct pipe3_dpll_map *)match->data; if (!phy->dpll_map) { - dev_err(&pdev->dev, "no DPLL data\n"); + dev_err(dev, "no DPLL data\n"); return -EINVAL; } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_ctrl"); - phy->pll_ctrl_base = devm_ioremap_resource(&pdev->dev, res); + phy->pll_ctrl_base = devm_ioremap_resource(dev, res); if (IS_ERR(phy->pll_ctrl_base)) return PTR_ERR(phy->pll_ctrl_base); - phy->sys_clk = devm_clk_get(phy->dev, "sysclk"); + phy->sys_clk = devm_clk_get(dev, "sysclk"); if (IS_ERR(phy->sys_clk)) { - dev_err(&pdev->dev, "unable to get sysclk\n"); + dev_err(dev, "unable to get sysclk\n"); return -EINVAL; } } - phy->refclk = devm_clk_get(phy->dev, "refclk"); + phy->refclk = devm_clk_get(dev, "refclk"); if (IS_ERR(phy->refclk)) { - dev_err(&pdev->dev, "unable to get refclk\n"); + dev_err(dev, "unable to get refclk\n"); /* older DTBs have missing refclk in SATA PHY * so don't bail out in case of SATA PHY. */ @@ -361,9 +362,9 @@ static int ti_pipe3_probe(struct platform_device *pdev) } if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { - phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); + phy->wkupclk = devm_clk_get(dev, "wkupclk"); if (IS_ERR(phy->wkupclk)) { - dev_err(&pdev->dev, "unable to get wkupclk\n"); + dev_err(dev, "unable to get wkupclk\n"); return PTR_ERR(phy->wkupclk); } } else { @@ -371,14 +372,14 @@ static int ti_pipe3_probe(struct platform_device *pdev) phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, "syscon-pllreset"); if (IS_ERR(phy->dpll_reset_syscon)) { - dev_info(&pdev->dev, + dev_info(dev, "can't get syscon-pllreset, sata dpll won't idle\n"); phy->dpll_reset_syscon = NULL; } else { if (of_property_read_u32_index(node, "syscon-pllreset", 1, &phy->dpll_reset_reg)) { - dev_err(&pdev->dev, + dev_err(dev, "couldn't get pllreset reg. offset\n"); return -EINVAL; } @@ -387,30 +388,30 @@ static int ti_pipe3_probe(struct platform_device *pdev) if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - clk = devm_clk_get(phy->dev, "dpll_ref"); + clk = devm_clk_get(dev, "dpll_ref"); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "unable to get dpll ref clk\n"); + dev_err(dev, "unable to get dpll ref clk\n"); return PTR_ERR(clk); } clk_set_rate(clk, 1500000000); - clk = devm_clk_get(phy->dev, "dpll_ref_m2"); + clk = devm_clk_get(dev, "dpll_ref_m2"); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "unable to get dpll ref m2 clk\n"); + dev_err(dev, "unable to get dpll ref m2 clk\n"); return PTR_ERR(clk); } clk_set_rate(clk, 100000000); - clk = devm_clk_get(phy->dev, "phy-div"); + clk = devm_clk_get(dev, "phy-div"); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "unable to get phy-div clk\n"); + dev_err(dev, "unable to get phy-div clk\n"); return PTR_ERR(clk); } clk_set_rate(clk, 100000000); - phy->div_clk = devm_clk_get(phy->dev, "div-clk"); + phy->div_clk = devm_clk_get(dev, "div-clk"); if (IS_ERR(phy->div_clk)) { - dev_err(&pdev->dev, "unable to get div-clk\n"); + dev_err(dev, "unable to get div-clk\n"); return PTR_ERR(phy->div_clk); } } else { @@ -419,13 +420,13 @@ static int ti_pipe3_probe(struct platform_device *pdev) control_node = of_parse_phandle(node, "ctrl-module", 0); if (!control_node) { - dev_err(&pdev->dev, "Failed to get control device phandle\n"); + dev_err(dev, "Failed to get control device phandle\n"); return -EINVAL; } control_pdev = of_find_device_by_node(control_node); if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); + dev_err(dev, "Failed to get control device\n"); return -EINVAL; } @@ -434,7 +435,7 @@ static int ti_pipe3_probe(struct platform_device *pdev) omap_control_phy_power(phy->control_dev, 0); platform_set_drvdata(pdev, phy); - pm_runtime_enable(phy->dev); + pm_runtime_enable(dev); /* * Prevent auto-disable of refclk for SATA PHY due to Errata i783 @@ -446,13 +447,12 @@ static int ti_pipe3_probe(struct platform_device *pdev) } } - generic_phy = devm_phy_create(phy->dev, NULL, &ops); + generic_phy = devm_phy_create(dev, NULL, &ops); if (IS_ERR(generic_phy)) return PTR_ERR(generic_phy); phy_set_drvdata(generic_phy, phy); - phy_provider = devm_of_phy_provider_register(phy->dev, - of_phy_simple_xlate); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(phy_provider)) return PTR_ERR(phy_provider); -- cgit v1.2.3 From 234738ea33903a185530a997f9ddff68709929b9 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:06 +0530 Subject: phy: ti-pipe3: move clk initialization to a separate function No functional change. Moved clock initialization done in probe to a separate function as part of cleaning up ti_pipe3_probe. Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 127 +++++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 55 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index c5111057d241..3379a4deeb16 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -308,48 +308,11 @@ static const struct phy_ops ops = { static const struct of_device_id ti_pipe3_id_table[]; -static int ti_pipe3_probe(struct platform_device *pdev) +static int ti_pipe3_get_clk(struct ti_pipe3 *phy) { - struct ti_pipe3 *phy; - struct phy *generic_phy; - struct phy_provider *phy_provider; - struct resource *res; - struct device_node *node = pdev->dev.of_node; - struct device_node *control_node; - struct platform_device *control_pdev; - const struct of_device_id *match; struct clk *clk; - struct device *dev = &pdev->dev; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - phy->dev = dev; - - if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - match = of_match_device(ti_pipe3_id_table, dev); - if (!match) - return -EINVAL; - - phy->dpll_map = (struct pipe3_dpll_map *)match->data; - if (!phy->dpll_map) { - dev_err(dev, "no DPLL data\n"); - return -EINVAL; - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "pll_ctrl"); - phy->pll_ctrl_base = devm_ioremap_resource(dev, res); - if (IS_ERR(phy->pll_ctrl_base)) - return PTR_ERR(phy->pll_ctrl_base); - - phy->sys_clk = devm_clk_get(dev, "sysclk"); - if (IS_ERR(phy->sys_clk)) { - dev_err(dev, "unable to get sysclk\n"); - return -EINVAL; - } - } + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; phy->refclk = devm_clk_get(dev, "refclk"); if (IS_ERR(phy->refclk)) { @@ -369,25 +332,17 @@ static int ti_pipe3_probe(struct platform_device *pdev) } } else { phy->wkupclk = ERR_PTR(-ENODEV); - phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, - "syscon-pllreset"); - if (IS_ERR(phy->dpll_reset_syscon)) { - dev_info(dev, - "can't get syscon-pllreset, sata dpll won't idle\n"); - phy->dpll_reset_syscon = NULL; - } else { - if (of_property_read_u32_index(node, - "syscon-pllreset", 1, - &phy->dpll_reset_reg)) { - dev_err(dev, - "couldn't get pllreset reg. offset\n"); - return -EINVAL; - } + } + + if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + phy->sys_clk = devm_clk_get(dev, "sysclk"); + if (IS_ERR(phy->sys_clk)) { + dev_err(dev, "unable to get sysclk\n"); + return -EINVAL; } } if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - clk = devm_clk_get(dev, "dpll_ref"); if (IS_ERR(clk)) { dev_err(dev, "unable to get dpll ref clk\n"); @@ -418,6 +373,68 @@ static int ti_pipe3_probe(struct platform_device *pdev) phy->div_clk = ERR_PTR(-ENODEV); } + return 0; +} + +static int ti_pipe3_probe(struct platform_device *pdev) +{ + struct ti_pipe3 *phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct resource *res; + struct device_node *node = pdev->dev.of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + const struct of_device_id *match; + struct device *dev = &pdev->dev; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->dev = dev; + + if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + match = of_match_device(ti_pipe3_id_table, dev); + if (!match) + return -EINVAL; + + phy->dpll_map = (struct pipe3_dpll_map *)match->data; + if (!phy->dpll_map) { + dev_err(dev, "no DPLL data\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pll_ctrl_base)) + return PTR_ERR(phy->pll_ctrl_base); + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pllreset"); + if (IS_ERR(phy->dpll_reset_syscon)) { + dev_info(dev, + "can't get syscon-pllreset, sata dpll won't idle\n"); + phy->dpll_reset_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pllreset", 1, + &phy->dpll_reset_reg)) { + dev_err(dev, + "couldn't get pllreset reg. offset\n"); + return -EINVAL; + } + } + } + + ret = ti_pipe3_get_clk(phy); + if (ret) + return ret; + control_node = of_parse_phandle(node, "ctrl-module", 0); if (!control_node) { dev_err(dev, "Failed to get control device phandle\n"); -- cgit v1.2.3 From 73bbc78e57c96aba9c7d6473ee4ea8374ae257ee Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:07 +0530 Subject: phy: ti-pipe3: move sysctrl initialization to a separate function No functional change. Moved sysctrl initialization done in probe to a separate function as part of cleaning up ti_pipe3_probe. Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 33 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 3379a4deeb16..3154da08ff37 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -376,6 +376,48 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy) return 0; } +static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) +{ + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(dev, "Failed to get control device phandle\n"); + return -EINVAL; + } + + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(dev, "Failed to get control device\n"); + return -EINVAL; + } + + phy->control_dev = &control_pdev->dev; + + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pllreset"); + if (IS_ERR(phy->dpll_reset_syscon)) { + dev_info(dev, + "can't get syscon-pllreset, sata dpll won't idle\n"); + phy->dpll_reset_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pllreset", 1, + &phy->dpll_reset_reg)) { + dev_err(dev, + "couldn't get pllreset reg. offset\n"); + return -EINVAL; + } + } + } + + return 0; +} + static int ti_pipe3_probe(struct platform_device *pdev) { struct ti_pipe3 *phy; @@ -383,8 +425,6 @@ static int ti_pipe3_probe(struct platform_device *pdev) struct phy_provider *phy_provider; struct resource *res; struct device_node *node = pdev->dev.of_node; - struct device_node *control_node; - struct platform_device *control_pdev; const struct of_device_id *match; struct device *dev = &pdev->dev; int ret; @@ -413,42 +453,14 @@ static int ti_pipe3_probe(struct platform_device *pdev) return PTR_ERR(phy->pll_ctrl_base); } - if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { - phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, - "syscon-pllreset"); - if (IS_ERR(phy->dpll_reset_syscon)) { - dev_info(dev, - "can't get syscon-pllreset, sata dpll won't idle\n"); - phy->dpll_reset_syscon = NULL; - } else { - if (of_property_read_u32_index(node, - "syscon-pllreset", 1, - &phy->dpll_reset_reg)) { - dev_err(dev, - "couldn't get pllreset reg. offset\n"); - return -EINVAL; - } - } - } + ret = ti_pipe3_get_sysctrl(phy); + if (ret) + return ret; ret = ti_pipe3_get_clk(phy); if (ret) return ret; - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(dev, "Failed to get control device phandle\n"); - return -EINVAL; - } - - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(dev, "Failed to get control device\n"); - return -EINVAL; - } - - phy->control_dev = &control_pdev->dev; - omap_control_phy_power(phy->control_dev, 0); platform_set_drvdata(pdev, phy); -- cgit v1.2.3 From 1fe521225a830e159449cba9f7b365ba207e22c9 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:08 +0530 Subject: phy: ti-pipe3: move mem resource initialization to a separate function No functional change. Moved mem resource initialization done in probe to a separate function as part of cleaning up ti_pipe3_probe. Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-ti-pipe3.c | 52 +++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 3154da08ff37..1991efdd7d2a 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -418,14 +418,42 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) return 0; } +static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy) +{ + struct resource *res; + const struct of_device_id *match; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct platform_device *pdev = to_platform_device(dev); + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) + return 0; + + match = of_match_device(ti_pipe3_id_table, dev); + if (!match) + return -EINVAL; + + phy->dpll_map = (struct pipe3_dpll_map *)match->data; + if (!phy->dpll_map) { + dev_err(dev, "no DPLL data\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pll_ctrl_base)) + return PTR_ERR(phy->pll_ctrl_base); + + return 0; +} + static int ti_pipe3_probe(struct platform_device *pdev) { struct ti_pipe3 *phy; struct phy *generic_phy; struct phy_provider *phy_provider; - struct resource *res; struct device_node *node = pdev->dev.of_node; - const struct of_device_id *match; struct device *dev = &pdev->dev; int ret; @@ -435,23 +463,9 @@ static int ti_pipe3_probe(struct platform_device *pdev) phy->dev = dev; - if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - match = of_match_device(ti_pipe3_id_table, dev); - if (!match) - return -EINVAL; - - phy->dpll_map = (struct pipe3_dpll_map *)match->data; - if (!phy->dpll_map) { - dev_err(dev, "no DPLL data\n"); - return -EINVAL; - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "pll_ctrl"); - phy->pll_ctrl_base = devm_ioremap_resource(dev, res); - if (IS_ERR(phy->pll_ctrl_base)) - return PTR_ERR(phy->pll_ctrl_base); - } + ret = ti_pipe3_get_pll_base(phy); + if (ret) + return ret; ret = ti_pipe3_get_sysctrl(phy); if (ret) -- cgit v1.2.3 From cc34ace73dfa24d9cda2fd2c4666e38a515a9052 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:09 +0530 Subject: phy: ti-pipe3: use ti_pipe3_power_off to power off the PHY during probe No functional change. Previously omap_control_phy_power() was used to power off the PHY during probe. But once PIPE3 driver is adapted to use syscon, omap_control_phy_power() cannot be used. Hence used ti_pipe3_power_off to power off the PHY. Signed-off-by: Kishon Vijay Abraham I Acked-by: Roger Quadros --- drivers/phy/phy-ti-pipe3.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 1991efdd7d2a..0ce4194e09a5 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -475,8 +475,6 @@ static int ti_pipe3_probe(struct platform_device *pdev) if (ret) return ret; - omap_control_phy_power(phy->control_dev, 0); - platform_set_drvdata(pdev, phy); pm_runtime_enable(dev); @@ -495,6 +493,9 @@ static int ti_pipe3_probe(struct platform_device *pdev) return PTR_ERR(generic_phy); phy_set_drvdata(generic_phy, phy); + + ti_pipe3_power_off(generic_phy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(phy_provider)) return PTR_ERR(phy_provider); -- cgit v1.2.3 From c396a1c7ee1b3b460cf01824b03e0627a7db5b01 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:10 +0530 Subject: phy: ti-pipe3: use *syscon* framework API to power on/off the PHY Deprecate using phy-omap-control driver to power on/off the PHY and use *syscon* framework to do the same. Signed-off-by: Kishon Vijay Abraham I Acked-by: Rob Herring --- Documentation/devicetree/bindings/phy/ti-phy.txt | 10 ++- drivers/phy/phy-ti-pipe3.c | 88 ++++++++++++++++++++---- 2 files changed, 81 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index 9cf9446eaf2e..e06f980fa2ba 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -77,8 +77,6 @@ Required properties: * "div-clk" - apll clock Optional properties: - - ctrl-module : phandle of the control module used by PHY driver to power on - the PHY. - id: If there are multiple instance of the same type, in order to differentiate between each instance "id" can be used (e.g., multi-lane PCIe PHY). If "id" is not provided, it is set to default value of '1'. @@ -86,6 +84,14 @@ Optional properties: CTRL_CORE_SMA_SW_0 register and register offset to the CTRL_CORE_SMA_SW_0 register that contains the SATA_PLL_SOFT_RESET bit. Only valid for sata_phy. +Deprecated properties: + - ctrl-module : phandle of the control module used by PHY driver to power on + the PHY. + +Recommended properies: + - syscon-phy-power : phandle/offset pair. Phandle to the system control + module and the register offset to power on/off the PHY. + This is usually a subnode of ocp2scp to which it is connected. usb3phy@4a084400 { diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 0ce4194e09a5..a47b1902a36f 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -56,6 +56,15 @@ #define SATA_PLL_SOFT_RESET BIT(18) +#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000 +#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14 + +#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000 +#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22 + +#define PIPE3_PHY_TX_RX_POWERON 0x3 +#define PIPE3_PHY_TX_RX_POWEROFF 0x0 + /* * This is an Empirical value that works, need to confirm the actual * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status @@ -86,8 +95,10 @@ struct ti_pipe3 { struct clk *refclk; struct clk *div_clk; struct pipe3_dpll_map *dpll_map; + struct regmap *phy_power_syscon; /* ctrl. reg. acces */ struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */ unsigned int dpll_reset_reg; /* reg. index within syscon */ + unsigned int power_reg; /* power reg. index within syscon */ bool sata_refclk_enabled; }; @@ -144,20 +155,49 @@ static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy); static int ti_pipe3_power_off(struct phy *x) { + u32 val; + int ret; struct ti_pipe3 *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 0); + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 0); + return 0; + } - return 0; + val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val); + return ret; } static int ti_pipe3_power_on(struct phy *x) { + u32 val; + u32 mask; + int ret; + unsigned long rate; struct ti_pipe3 *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 1); + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 1); + return 0; + } - return 0; + rate = clk_get_rate(phy->sys_clk); + if (!rate) { + dev_err(phy->dev, "Invalid clock rate\n"); + return -EINVAL; + } + rate = rate / 1000000; + mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK; + val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + mask, val); + return ret; } static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy) @@ -334,7 +374,8 @@ static int ti_pipe3_get_clk(struct ti_pipe3 *phy) phy->wkupclk = ERR_PTR(-ENODEV); } - if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") || + phy->phy_power_syscon) { phy->sys_clk = devm_clk_get(dev, "sysclk"); if (IS_ERR(phy->sys_clk)) { dev_err(dev, "unable to get sysclk\n"); @@ -383,19 +424,36 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) struct device_node *control_node; struct platform_device *control_pdev; - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(dev, "Failed to get control device phandle\n"); - return -EINVAL; + phy->phy_power_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->phy_power_syscon)) { + dev_dbg(dev, + "can't get syscon-phy-power, using control device\n"); + phy->phy_power_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(dev, "couldn't get power reg. offset\n"); + return -EINVAL; + } } - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(dev, "Failed to get control device\n"); - return -EINVAL; - } + if (!phy->phy_power_syscon) { + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(dev, "Failed to get control device phandle\n"); + return -EINVAL; + } - phy->control_dev = &control_pdev->dev; + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(dev, "Failed to get control device\n"); + return -EINVAL; + } + + phy->control_dev = &control_pdev->dev; + } if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, -- cgit v1.2.3 From 3f2362c56f7ab0bf2ca1281227ca61f77c0c63fd Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:11 +0530 Subject: phy: ti-pipe3: use *syscon* framework API to set PCS value of the PHY Deprecate using phy-omap-control driver to set PCS value of the PHY and start using *syscon* API to do the same. Signed-off-by: Kishon Vijay Abraham I Acked-by: Roger Quadros Acked-by: Rob Herring --- Documentation/devicetree/bindings/phy/ti-phy.txt | 2 ++ drivers/phy/phy-ti-pipe3.c | 34 ++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index e06f980fa2ba..49e5b0c6ed87 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -83,6 +83,8 @@ Optional properties: - syscon-pllreset: Handle to system control region that contains the CTRL_CORE_SMA_SW_0 register and register offset to the CTRL_CORE_SMA_SW_0 register that contains the SATA_PLL_SOFT_RESET bit. Only valid for sata_phy. + - syscon-pcs : phandle/offset pair. Phandle to the system control module and the + register offset to write the PCS delay value. Deprecated properties: - ctrl-module : phandle of the control module used by PHY driver to power on diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index a47b1902a36f..0a477d24cf76 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -65,6 +65,9 @@ #define PIPE3_PHY_TX_RX_POWERON 0x3 #define PIPE3_PHY_TX_RX_POWEROFF 0x0 +#define PCIE_PCS_MASK 0xFF0000 +#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 + /* * This is an Empirical value that works, need to confirm the actual * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status @@ -96,9 +99,11 @@ struct ti_pipe3 { struct clk *div_clk; struct pipe3_dpll_map *dpll_map; struct regmap *phy_power_syscon; /* ctrl. reg. acces */ + struct regmap *pcs_syscon; /* ctrl. reg. acces */ struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */ unsigned int dpll_reset_reg; /* reg. index within syscon */ unsigned int power_reg; /* power reg. index within syscon */ + unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */ bool sata_refclk_enabled; }; @@ -269,8 +274,15 @@ static int ti_pipe3_init(struct phy *x) * 18-1804. */ if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { - omap_control_pcie_pcs(phy->control_dev, 0x96); - return 0; + if (!phy->pcs_syscon) { + omap_control_pcie_pcs(phy->control_dev, 0x96); + return 0; + } + + val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT; + ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg, + PCIE_PCS_MASK, val); + return ret; } /* Bring it out of IDLE if it is IDLE */ @@ -455,6 +467,24 @@ static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) phy->control_dev = &control_pdev->dev; } + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pcs"); + if (IS_ERR(phy->pcs_syscon)) { + dev_dbg(dev, + "can't get syscon-pcs, using omap control\n"); + phy->pcs_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pcs", 1, + &phy->pcie_pcs_reg)) { + dev_err(dev, + "couldn't get pcie pcs reg. offset\n"); + return -EINVAL; + } + } + } + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, "syscon-pllreset"); -- cgit v1.2.3 From 86c574e4bc9d840b8501cf604ea08d98fbd2c23c Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:12 +0530 Subject: phy: omap-usb2: use omap_usb_power_off to power off the PHY during probe No functional change. Previously omap_control_phy_power() was used to power off the PHY during probe. But once phy-omap-usb2 driver is adapted to use syscon, omap_control_phy_power() cannot be used. Hence used omap_usb_power_off to power off the PHY. Signed-off-by: Kishon Vijay Abraham I Acked-by: Roger Quadros --- drivers/phy/phy-omap-usb2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index 0fe80589ffbe..c79633efd7fc 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -241,7 +241,6 @@ static int omap_usb2_probe(struct platform_device *pdev) } phy->control_dev = &control_pdev->dev; - omap_control_phy_power(phy->control_dev, 0); otg->set_host = omap_usb_set_host; otg->set_peripheral = omap_usb_set_peripheral; @@ -261,6 +260,7 @@ static int omap_usb2_probe(struct platform_device *pdev) } phy_set_drvdata(generic_phy, phy); + omap_usb_power_off(generic_phy); phy_provider = devm_of_phy_provider_register(phy->dev, of_phy_simple_xlate); -- cgit v1.2.3 From 9955a7835bf376e12482583958b2661f501b868b Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Mon, 21 Dec 2015 14:24:13 +0530 Subject: phy: omap-usb2: use *syscon* framework API to power on/off the PHY Deprecate using phy-omap-control driver to power on/off the PHY, and use *syscon* framework to do the same. This handles powering on/off the PHY for the USB2 PHYs used in various TI SoCs. Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/ti-phy.txt | 8 ++- drivers/phy/phy-omap-usb2.c | 92 +++++++++++++++++++----- include/linux/phy/omap_usb.h | 23 ++++++ 3 files changed, 105 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/phy/ti-phy.txt b/Documentation/devicetree/bindings/phy/ti-phy.txt index 49e5b0c6ed87..a3b394587874 100644 --- a/Documentation/devicetree/bindings/phy/ti-phy.txt +++ b/Documentation/devicetree/bindings/phy/ti-phy.txt @@ -31,6 +31,8 @@ OMAP USB2 PHY Required properties: - compatible: Should be "ti,omap-usb2" + Should be "ti,dra7x-usb2-phy2" for the 2nd instance of USB2 PHY + in DRA7x - reg : Address and length of the register set for the device. - #phy-cells: determine the number of cells that should be given in the phandle while referencing this phy. @@ -40,10 +42,14 @@ Required properties: * "wkupclk" - wakeup clock. * "refclk" - reference clock (optional). -Optional properties: +Deprecated properties: - ctrl-module : phandle of the control module used by PHY driver to power on the PHY. +Recommended properies: +- syscon-phy-power : phandle/offset pair. Phandle to the system control + module and the register offset to power on/off the PHY. + This is usually a subnode of ocp2scp to which it is connected. usb2phy@4a0ad080 { diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c index c79633efd7fc..c134989052f5 100644 --- a/drivers/phy/phy-omap-usb2.c +++ b/drivers/phy/phy-omap-usb2.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #define USB2PHY_DISCON_BYP_LATCH (1 << 31) @@ -97,22 +99,38 @@ static int omap_usb_set_peripheral(struct usb_otg *otg, return 0; } +static int omap_usb_phy_power(struct omap_usb *phy, int on) +{ + u32 val; + int ret; + + if (!phy->syscon_phy_power) { + omap_control_phy_power(phy->control_dev, on); + return 0; + } + + if (on) + val = phy->power_on; + else + val = phy->power_off; + + ret = regmap_update_bits(phy->syscon_phy_power, phy->power_reg, + phy->mask, val); + return ret; +} + static int omap_usb_power_off(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 0); - - return 0; + return omap_usb_phy_power(phy, false); } static int omap_usb_power_on(struct phy *x) { struct omap_usb *phy = phy_get_drvdata(x); - omap_control_phy_power(phy->control_dev, 1); - - return 0; + return omap_usb_phy_power(phy, true); } static int omap_usb_init(struct phy *x) @@ -147,21 +165,38 @@ static const struct phy_ops ops = { static const struct usb_phy_data omap_usb2_data = { .label = "omap_usb2", .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, }; static const struct usb_phy_data omap5_usb2_data = { .label = "omap5_usb2", .flags = 0, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, }; static const struct usb_phy_data dra7x_usb2_data = { .label = "dra7x_usb2", .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data dra7x_usb2_phy2_data = { + .label = "dra7x_usb2_phy2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_USB2_PHY_PD, + .power_off = OMAP_USB2_PHY_PD, }; static const struct usb_phy_data am437x_usb2_data = { .label = "am437x_usb2", .flags = 0, + .mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD | + AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD, }; static const struct of_device_id omap_usb2_id_table[] = { @@ -177,6 +212,10 @@ static const struct of_device_id omap_usb2_id_table[] = { .compatible = "ti,dra7x-usb2", .data = &dra7x_usb2_data, }, + { + .compatible = "ti,dra7x-usb2-phy2", + .data = &dra7x_usb2_phy2_data, + }, { .compatible = "ti,am437x-usb2", .data = &am437x_usb2_data, @@ -219,6 +258,9 @@ static int omap_usb2_probe(struct platform_device *pdev) phy->phy.label = phy_data->label; phy->phy.otg = otg; phy->phy.type = USB_PHY_TYPE_USB2; + phy->mask = phy_data->mask; + phy->power_on = phy_data->power_on; + phy->power_off = phy_data->power_off; if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -228,20 +270,36 @@ static int omap_usb2_probe(struct platform_device *pdev) phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT; } - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(&pdev->dev, "Failed to get control device phandle\n"); - return -EINVAL; - } + phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->syscon_phy_power)) { + dev_dbg(&pdev->dev, + "can't get syscon-phy-power, using control device\n"); + phy->syscon_phy_power = NULL; + + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(&pdev->dev, + "Failed to get control device phandle\n"); + return -EINVAL; + } - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); - return -EINVAL; + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + return -EINVAL; + } + phy->control_dev = &control_pdev->dev; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(&pdev->dev, + "couldn't get power reg. offset\n"); + return -EINVAL; + } } - phy->control_dev = &control_pdev->dev; - otg->set_host = omap_usb_set_host; otg->set_peripheral = omap_usb_set_peripheral; if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS) diff --git a/include/linux/phy/omap_usb.h b/include/linux/phy/omap_usb.h index dc2c541a619b..2e5fb870efa9 100644 --- a/include/linux/phy/omap_usb.h +++ b/include/linux/phy/omap_usb.h @@ -30,6 +30,12 @@ struct usb_dpll_params { u32 mf; }; +enum omap_usb_phy_type { + TYPE_USB2, /* USB2_PHY, power down in CONTROL_DEV_CONF */ + TYPE_DRA7USB2, /* USB2 PHY, power and power_aux e.g. DRA7 */ + TYPE_AM437USB2, /* USB2 PHY, power e.g. AM437x */ +}; + struct omap_usb { struct usb_phy phy; struct phy_companion *comparator; @@ -40,11 +46,20 @@ struct omap_usb { struct clk *wkupclk; struct clk *optclk; u8 flags; + enum omap_usb_phy_type type; + struct regmap *syscon_phy_power; /* ctrl. reg. acces */ + unsigned int power_reg; /* power reg. index within syscon */ + u32 mask; + u32 power_on; + u32 power_off; }; struct usb_phy_data { const char *label; u8 flags; + u32 mask; + u32 power_on; + u32 power_off; }; /* Driver Flags */ @@ -52,6 +67,14 @@ struct usb_phy_data { #define OMAP_USB2_HAS_SET_VBUS (1 << 1) #define OMAP_USB2_CALIBRATE_FALSE_DISCONNECT (1 << 2) +#define OMAP_DEV_PHY_PD BIT(0) +#define OMAP_USB2_PHY_PD BIT(28) + +#define AM437X_USB2_PHY_PD BIT(0) +#define AM437X_USB2_OTG_PD BIT(1) +#define AM437X_USB2_OTGVDET_EN BIT(19) +#define AM437X_USB2_OTGSESSEND_EN BIT(20) + #define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) #if defined(CONFIG_OMAP_USB2) || defined(CONFIG_OMAP_USB2_MODULE) -- cgit v1.2.3 From a5beaaf39455e4388251e95ef2ce6849cabf3393 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Sat, 21 Nov 2015 15:44:53 +0800 Subject: usb: gadget: Add the console support for usb-to-serial port It dose not work when we want to use the usb-to-serial port based on one usb gadget as a console. Thus this patch adds the console initialization to support this request. To avoid the re-entrance when transferring data with usb endpoint, it introduces a kthread to do the IO transmission. Signed-off-by: Baolin Wang Signed-off-by: Felipe Balbi --- drivers/usb/gadget/Kconfig | 6 + drivers/usb/gadget/function/u_serial.c | 258 +++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 33834aa09ed4..be5aab9c13f2 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -127,6 +127,12 @@ config USB_GADGET_STORAGE_NUM_BUFFERS a module parameter as well. If unsure, say 2. +config U_SERIAL_CONSOLE + bool "Serial gadget console support" + depends on USB_G_SERIAL + help + It supports the serial gadget can be used as a console. + source "drivers/usb/gadget/udc/Kconfig" # diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index f7771d86ad6c..6af145f2a99d 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include "u_serial.h" @@ -79,6 +81,7 @@ */ #define QUEUE_SIZE 16 #define WRITE_BUF_SIZE 8192 /* TX only */ +#define GS_CONSOLE_BUF_SIZE 8192 /* circular buffer */ struct gs_buf { @@ -88,6 +91,17 @@ struct gs_buf { char *buf_put; }; +/* console info */ +struct gscons_info { + struct gs_port *port; + struct task_struct *console_thread; + struct gs_buf con_buf; + /* protect the buf and busy flag */ + spinlock_t con_lock; + int req_busy; + struct usb_request *console_req; +}; + /* * The port structure holds info for each port, one for each minor number * (and thus for each /dev/ node). @@ -1023,6 +1037,246 @@ static const struct tty_operations gs_tty_ops = { static struct tty_driver *gs_tty_driver; +#ifdef CONFIG_U_SERIAL_CONSOLE + +static struct gscons_info gscons_info; +static struct console gserial_cons; + +static struct usb_request *gs_request_new(struct usb_ep *ep) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!req) + return NULL; + + req->buf = kmalloc(ep->maxpacket, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void gs_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (!req) + return; + + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void gs_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct gscons_info *info = &gscons_info; + + switch (req->status) { + default: + pr_warn("%s: unexpected %s status %d\n", + __func__, ep->name, req->status); + case 0: + /* normal completion */ + spin_lock(&info->con_lock); + info->req_busy = 0; + spin_unlock(&info->con_lock); + + wake_up_process(info->console_thread); + break; + case -ESHUTDOWN: + /* disconnect */ + pr_vdebug("%s: %s shutdown\n", __func__, ep->name); + break; + } +} + +static int gs_console_connect(int port_num) +{ + struct gscons_info *info = &gscons_info; + struct gs_port *port; + struct usb_ep *ep; + + if (port_num != gserial_cons.index) { + pr_err("%s: port num [%d] is not support console\n", + __func__, port_num); + return -ENXIO; + } + + port = ports[port_num].port; + ep = port->port_usb->in; + if (!info->console_req) { + info->console_req = gs_request_new(ep); + if (!info->console_req) + return -ENOMEM; + info->console_req->complete = gs_complete_out; + } + + info->port = port; + spin_lock(&info->con_lock); + info->req_busy = 0; + spin_unlock(&info->con_lock); + pr_vdebug("port[%d] console connect!\n", port_num); + return 0; +} + +static void gs_console_disconnect(struct usb_ep *ep) +{ + struct gscons_info *info = &gscons_info; + struct usb_request *req = info->console_req; + + gs_request_free(req, ep); + info->console_req = NULL; +} + +static int gs_console_thread(void *data) +{ + struct gscons_info *info = &gscons_info; + struct gs_port *port; + struct usb_request *req; + struct usb_ep *ep; + int xfer, ret, count, size; + + do { + port = info->port; + set_current_state(TASK_INTERRUPTIBLE); + if (!port || !port->port_usb + || !port->port_usb->in || !info->console_req) + goto sched; + + req = info->console_req; + ep = port->port_usb->in; + + spin_lock_irq(&info->con_lock); + count = gs_buf_data_avail(&info->con_buf); + size = ep->maxpacket; + + if (count > 0 && !info->req_busy) { + set_current_state(TASK_RUNNING); + if (count < size) + size = count; + + xfer = gs_buf_get(&info->con_buf, req->buf, size); + req->length = xfer; + + spin_unlock(&info->con_lock); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&info->con_lock); + if (ret < 0) + info->req_busy = 0; + else + info->req_busy = 1; + + spin_unlock_irq(&info->con_lock); + } else { + spin_unlock_irq(&info->con_lock); +sched: + if (kthread_should_stop()) { + set_current_state(TASK_RUNNING); + break; + } + schedule(); + } + } while (1); + + return 0; +} + +static int gs_console_setup(struct console *co, char *options) +{ + struct gscons_info *info = &gscons_info; + int status; + + info->port = NULL; + info->console_req = NULL; + info->req_busy = 0; + spin_lock_init(&info->con_lock); + + status = gs_buf_alloc(&info->con_buf, GS_CONSOLE_BUF_SIZE); + if (status) { + pr_err("%s: allocate console buffer failed\n", __func__); + return status; + } + + info->console_thread = kthread_create(gs_console_thread, + co, "gs_console"); + if (IS_ERR(info->console_thread)) { + pr_err("%s: cannot create console thread\n", __func__); + gs_buf_free(&info->con_buf); + return PTR_ERR(info->console_thread); + } + wake_up_process(info->console_thread); + + return 0; +} + +static void gs_console_write(struct console *co, + const char *buf, unsigned count) +{ + struct gscons_info *info = &gscons_info; + unsigned long flags; + + spin_lock_irqsave(&info->con_lock, flags); + gs_buf_put(&info->con_buf, buf, count); + spin_unlock_irqrestore(&info->con_lock, flags); + + wake_up_process(info->console_thread); +} + +static struct tty_driver *gs_console_device(struct console *co, int *index) +{ + struct tty_driver **p = (struct tty_driver **)co->data; + + if (!*p) + return NULL; + + *index = co->index; + return *p; +} + +static struct console gserial_cons = { + .name = "ttyGS", + .write = gs_console_write, + .device = gs_console_device, + .setup = gs_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &gs_tty_driver, +}; + +static void gserial_console_init(void) +{ + register_console(&gserial_cons); +} + +static void gserial_console_exit(void) +{ + struct gscons_info *info = &gscons_info; + + unregister_console(&gserial_cons); + kthread_stop(info->console_thread); + gs_buf_free(&info->con_buf); +} + +#else + +static int gs_console_connect(int port_num) +{ + return 0; +} + +static void gs_console_disconnect(struct usb_ep *ep) +{ +} + +static void gserial_console_init(void) +{ +} + +static void gserial_console_exit(void) +{ +} + +#endif + static int gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) { @@ -1096,6 +1350,7 @@ void gserial_free_line(unsigned char port_num) gserial_free_port(port); tty_unregister_device(gs_tty_driver, port_num); + gserial_console_exit(); } EXPORT_SYMBOL_GPL(gserial_free_line); @@ -1138,6 +1393,7 @@ int gserial_alloc_line(unsigned char *line_num) goto err; } *line_num = port_num; + gserial_console_init(); err: return ret; } @@ -1219,6 +1475,7 @@ int gserial_connect(struct gserial *gser, u8 port_num) gser->disconnect(gser); } + status = gs_console_connect(port_num); spin_unlock_irqrestore(&port->port_lock, flags); return status; @@ -1277,6 +1534,7 @@ void gserial_disconnect(struct gserial *gser) port->read_allocated = port->read_started = port->write_allocated = port->write_started = 0; + gs_console_disconnect(gser->in); spin_unlock_irqrestore(&port->port_lock, flags); } EXPORT_SYMBOL_GPL(gserial_disconnect); -- cgit v1.2.3 From 0676c7e734e3807f4e91f5d0edcaeed1f5ff412a Mon Sep 17 00:00:00 2001 From: "Du, Changbin" Date: Fri, 4 Dec 2015 15:38:23 +0800 Subject: usb: dwc2: fix transfer stop programming for out endpoint To stop an out endpoint, software should set sets the Global OUT NAK, but not the Global Non-periodic IN NAK. This driver bug leads the out-ep failed be in disabled state with below error. dwc2_hsotg_ep_stop_xfr: timeout DOEPCTL.EPDisable Acked-by: John Youn Signed-off-by: Du, Changbin Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/gadget.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 0abf73c91beb..92a182feb936 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2911,15 +2911,15 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, "%s: timeout DIEPINT.NAKEFF\n", __func__); } else { /* Clear any pending nak effect interrupt */ - dwc2_writel(GINTSTS_GINNAKEFF, hsotg->regs + GINTSTS); + dwc2_writel(GINTSTS_GOUTNAKEFF, hsotg->regs + GINTSTS); - __orr32(hsotg->regs + DCTL, DCTL_SGNPINNAK); + __orr32(hsotg->regs + DCTL, DCTL_SGOUTNAK); /* Wait for global nak to take effect */ if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, - GINTSTS_GINNAKEFF, 100)) + GINTSTS_GOUTNAKEFF, 100)) dev_warn(hsotg->dev, - "%s: timeout GINTSTS.GINNAKEFF\n", __func__); + "%s: timeout GINTSTS.GOUTNAKEFF\n", __func__); } /* Disable ep */ @@ -2944,7 +2944,7 @@ static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, /* TODO: Flush shared tx fifo */ } else { /* Remove global NAKs */ - __bic32(hsotg->regs + DCTL, DCTL_SGNPINNAK); + __bic32(hsotg->regs + DCTL, DCTL_SGOUTNAK); } } -- cgit v1.2.3 From 3be99cd0e882dd2127b8cfe3942f5e464915aeba Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Mon, 7 Dec 2015 12:07:31 +0100 Subject: usb: dwc2: gadget: don't overwrite DCTL register on NAKEFF interrupts When receiving GINTSTS_GINNAKEFF or GINTSTS_GOUTNAKEFF interrupt, DCTL will be overwritten with DCTL_CGOUTNAK or DCTL_CGNPINNAK values. Instead of overwriting it, write only needed bits. It could cause an issue if GINTSTS_GINNAKEFF or GINTSTS_GOUTNAKEFF interrupt is received after dwc2 disabled pullup by writing DCTL_SFTDISCON bit. Pullup will then be re-enabled whereas it should not. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/gadget.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 92a182feb936..3a24cbf355a9 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2585,7 +2585,7 @@ irq_retry: if (gintsts & GINTSTS_GOUTNAKEFF) { dev_info(hsotg->dev, "GOUTNakEff triggered\n"); - dwc2_writel(DCTL_CGOUTNAK, hsotg->regs + DCTL); + __orr32(hsotg->regs + DCTL, DCTL_CGOUTNAK); dwc2_hsotg_dump(hsotg); } @@ -2593,7 +2593,7 @@ irq_retry: if (gintsts & GINTSTS_GINNAKEFF) { dev_info(hsotg->dev, "GINNakEff triggered\n"); - dwc2_writel(DCTL_CGNPINNAK, hsotg->regs + DCTL); + __orr32(hsotg->regs + DCTL, DCTL_CGNPINNAK); dwc2_hsotg_dump(hsotg); } -- cgit v1.2.3 From 991824677fe0a555394d8093b64647dbd08b89b0 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 17 Dec 2015 11:14:12 -0800 Subject: usb: dwc2: Restore GUSBCFG in dwc2_get_hwparams() Previously dwc2_get_hwparams() was changing GUSBCFG and not putting it back the way it was (specifically it set and cleared FORCEHOSTMODE). Since we want to move dwc2_core_reset() _before_ dwc2_get_hwparams() we should make sure dwc2_get_hwparams() isn't messing with things in a permanent way. Since we're now looking at GUSBCFG, it's obvious that we shouldn't need all the extra delays if FORCEHOSTMODE was already set. This will avoid some delays for any ports that have forced host mode. Signed-off-by: Douglas Anderson Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 0a9ebe00fbcc..0bc1c0ed12eb 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -3129,18 +3129,20 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) /* Force host mode to get HPTXFSIZ / GNPTXFSIZ exact power on value */ gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg |= GUSBCFG_FORCEHOSTMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - usleep_range(100000, 150000); + if (!(gusbcfg & GUSBCFG_FORCEHOSTMODE)) { + dwc2_writel(gusbcfg | GUSBCFG_FORCEHOSTMODE, + hsotg->regs + GUSBCFG); + usleep_range(100000, 150000); + } gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - usleep_range(100000, 150000); + if (!(gusbcfg & GUSBCFG_FORCEHOSTMODE)) { + dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); + usleep_range(100000, 150000); + } /* hwcfg2 */ hw->op_mode = (hwcfg2 & GHWCFG2_OP_MODE_MASK) >> -- cgit v1.2.3 From cebfdbf329ae929ccb71632888a7c2100c3d1eeb Mon Sep 17 00:00:00 2001 From: Yunzhi Li Date: Thu, 17 Dec 2015 11:14:26 -0800 Subject: usb: dwc2: reset dwc2 core before dwc2_get_hwparams() We initiate dwc2 usb controller in BIOS, dwc2_core_reset() should be called before dwc2_get_hwparams() to reset core registers to default value. Without this the FIFO setting might be incorrect because calculating FIFO size need power-on value of GRXFSIZ/GNPTXFSIZ/HPTXFSIZ registers. This patch could avoid warnning massage like in rk3288 platform: [ 2.074764] dwc2 ff580000.usb: 256 invalid for host_perio_tx_fifo_size. Check HW configuration. Signed-off-by: Yunzhi Li Signed-off-by: Douglas Anderson Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 2 +- drivers/usb/dwc2/core.h | 1 + drivers/usb/dwc2/platform.c | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 0bc1c0ed12eb..0a584ecb233f 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -481,7 +481,7 @@ static void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) * Do core a soft reset of the core. Be careful with this because it * resets all the internal state machines of the core. */ -static int dwc2_core_reset(struct dwc2_hsotg *hsotg) +int dwc2_core_reset(struct dwc2_hsotg *hsotg) { u32 greset; int count = 0; diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 05f01785de05..d1d855af63e6 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -880,6 +880,7 @@ enum dwc2_halt_status { * The following functions support initialization of the core driver component * and the DWC_otg controller */ +extern int dwc2_core_reset(struct dwc2_hsotg *hsotg); extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg); extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg); extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 5df2adf6f4e7..a72302d740dd 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -456,6 +456,12 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) return retval; + /* + * Reset before dwc2_get_hwparams() then it could get power-on real + * reset value form registers. + */ + dwc2_core_reset(hsotg); + /* Detect config values from hardware */ retval = dwc2_get_hwparams(hsotg); if (retval) -- cgit v1.2.3 From 0fe239bc190453fe82252c6d41a74e685730cd93 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 17 Dec 2015 11:14:40 -0800 Subject: usb: dwc2: Avoid double-reset at boot time In (usb: dwc2: reset dwc2 core before dwc2_get_hwparams()) we added an extra reset to the probe path for the dwc2 USB controllers. This allowed proper detection of parameters even if the firmware had already used the USB part. Unfortunately, this extra reset is quite slow and is affecting boot speed. We can avoid the double-reset by skipping the extra reset that would happen just after the one we added. Logic that explains why this is safe: * As of the CL mentioned above, we now always call dwc2_core_reset() in dwc2_driver_probe() before dwc2_hcd_init(). * The only caller of dwc2_hcd_init() is dwc2_driver_probe(), so we're guaranteed that dwc2_core_reset() was called before dwc2_hdc_init(). * dwc2_hdc_init() is the only caller that passes an irq other than -1 to dwc2_core_init(). Thus if dwc2_core_init() is called with an irq other than -1 we're guaranteed that dwc2_core_reset was called before dwc2_core_init(). ...this allows us to remove the dwc2_core_reset() in dwc2_core_init() if irq is not < 0. Note that since "irq" wasn't used in the function dwc2_core_init() anyway and since select_phy was always set at exactly the same times we could avoid the reset, we remove "irq" and rename "select_phy" to "initial_setup" and adjust the callers accordingly. Signed-off-by: Douglas Anderson Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 29 ++++++++++++++++++----------- drivers/usb/dwc2/core.h | 2 +- drivers/usb/dwc2/hcd.c | 6 +++--- 3 files changed, 22 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 0a584ecb233f..af12778b0e96 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -765,11 +765,10 @@ static void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) * dwc2_core_init() - Initializes the DWC_otg controller registers and * prepares the core for device mode or host mode operation * - * @hsotg: Programming view of the DWC_otg controller - * @select_phy: If true then also set the Phy type - * @irq: If >= 0, the irq to register + * @hsotg: Programming view of the DWC_otg controller + * @initial_setup: If true then this is the first init for this instance. */ -int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) { u32 usbcfg, otgctl; int retval; @@ -791,18 +790,26 @@ int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); - /* Reset the Controller */ - retval = dwc2_core_reset(hsotg); - if (retval) { - dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", - __func__); - return retval; + /* + * Reset the Controller + * + * We only need to reset the controller if this is a re-init. + * For the first init we know for sure that earlier code reset us (it + * needed to in order to properly detect various parameters). + */ + if (!initial_setup) { + retval = dwc2_core_reset(hsotg); + if (retval) { + dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", + __func__); + return retval; + } } /* * This needs to happen in FS mode before any other programming occurs */ - retval = dwc2_phy_init(hsotg, select_phy); + retval = dwc2_phy_init(hsotg, initial_setup); if (retval) return retval; diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index d1d855af63e6..cbece3a68c30 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -918,7 +918,7 @@ extern void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes); extern void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num); extern void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg); -extern int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq); +extern int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup); extern void dwc2_enable_global_interrupts(struct dwc2_hsotg *hcd); extern void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 880b549f737a..8847c72e55f6 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1420,7 +1420,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) dev_err(hsotg->dev, "Connection id status change timed out\n"); hsotg->op_state = OTG_STATE_B_PERIPHERAL; - dwc2_core_init(hsotg, false, -1); + dwc2_core_init(hsotg, false); dwc2_enable_global_interrupts(hsotg); spin_lock_irqsave(&hsotg->lock, flags); dwc2_hsotg_core_init_disconnected(hsotg, false); @@ -1443,7 +1443,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) hsotg->op_state = OTG_STATE_A_HOST; /* Initialize the Core for Host mode */ - dwc2_core_init(hsotg, false, -1); + dwc2_core_init(hsotg, false); dwc2_enable_global_interrupts(hsotg); dwc2_hcd_start(hsotg); } @@ -3120,7 +3120,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq) dwc2_disable_global_interrupts(hsotg); /* Initialize the DWC_otg core, and select the Phy type */ - retval = dwc2_core_init(hsotg, true, irq); + retval = dwc2_core_init(hsotg, true); if (retval) goto error2; -- cgit v1.2.3 From f619473140df4e1a10f4c10f693d214807ebdb03 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 17 Dec 2015 11:14:54 -0800 Subject: usb: dwc2: Speed dwc2_get_hwparams() on some host-only ports On some host-only DWC2 ports (like the one in rk3288) when we set GUSBCFG_FORCEHOSTMODE in GUSBCFG and then read back, we don't see the bit set. Presumably that's because the port is always forced to HOST mode so there's no reason to implement these status bits. Since we know dwc2_core_reset() is always called before dwc2_get_hwparams() and we know dwc2_core_reset() should have set GUSBCFG_FORCEHOSTMODE whenever hsotg->dr_mode == USB_DR_MODE_HOST, we can just check hsotg->dr_mode to decide that we can skip the delays in dwc2_get_hwparams(). Signed-off-by: Douglas Anderson Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index af12778b0e96..c143ac444bba 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -3102,7 +3102,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) unsigned width; u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; u32 hptxfsiz, grxfsiz, gnptxfsiz; - u32 gusbcfg; + u32 gusbcfg = 0; /* * Attempt to ensure this device is really a DWC_otg Controller. @@ -3135,8 +3135,8 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "grxfsiz=%08x\n", grxfsiz); /* Force host mode to get HPTXFSIZ / GNPTXFSIZ exact power on value */ - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - if (!(gusbcfg & GUSBCFG_FORCEHOSTMODE)) { + if (hsotg->dr_mode != USB_DR_MODE_HOST) { + gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); dwc2_writel(gusbcfg | GUSBCFG_FORCEHOSTMODE, hsotg->regs + GUSBCFG); usleep_range(100000, 150000); @@ -3146,7 +3146,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); - if (!(gusbcfg & GUSBCFG_FORCEHOSTMODE)) { + if (hsotg->dr_mode != USB_DR_MODE_HOST) { dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); usleep_range(100000, 150000); } -- cgit v1.2.3 From 20bde643434d541bc5f662c5836a05e9e276eca3 Mon Sep 17 00:00:00 2001 From: Yunzhi Li Date: Thu, 17 Dec 2015 11:15:08 -0800 Subject: usb: dwc2: reduce dwc2 driver probe time I found that the probe function of dwc2 driver takes much time when kernel boot up. There are many long delays in the probe function these take almost 1 second. This patch trying to reduce unnecessary delay time. In dwc2_core_reset() I see it use two at least 20ms delays to wait AHB idle and core soft reset, but dwc2 data book said that dwc2 core soft reset and AHB idle just need a few clocks (I think it refers to AHB clock, and AHB clock run at 150MHz in my RK3288 board), so 20ms is too long, delay 1us for wait AHB idle and soft reset is enough. And in dwc2_get_hwparams() it takes 150ms to wait ForceHostMode and ForceDeviceMode valid but in data book it said software must wait at least 25ms before the change to take effect, so I reduce this time to 25ms~50ms. By the way, is there any state bit show that the force mode take effect ? Could we poll curmod bit for figuring out if the change take effect ? It seems that usleep_range() at boot time will pick the longest value in the range. In dwc2_core_reset() there is a very long delay takes 200ms, and this function run twice when probe, could any one tell me is this delay time resonable ? I have tried this patch in my RK3288-evb board. It works well. Signed-off-by: Yunzhi Li Signed-off-by: Douglas Anderson Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index c143ac444bba..9659dbd33c8c 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -491,7 +491,7 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) /* Wait for AHB master IDLE state */ do { - usleep_range(20000, 40000); + udelay(1); greset = dwc2_readl(hsotg->regs + GRSTCTL); if (++count > 50) { dev_warn(hsotg->dev, @@ -506,7 +506,7 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) greset |= GRSTCTL_CSFTRST; dwc2_writel(greset, hsotg->regs + GRSTCTL); do { - usleep_range(20000, 40000); + udelay(1); greset = dwc2_readl(hsotg->regs + GRSTCTL); if (++count > 50) { dev_warn(hsotg->dev, @@ -537,7 +537,7 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) * NOTE: This long sleep is _very_ important, otherwise the core will * not stay in host mode after a connector ID change! */ - usleep_range(150000, 200000); + usleep_range(150000, 160000); return 0; } @@ -3139,7 +3139,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); dwc2_writel(gusbcfg | GUSBCFG_FORCEHOSTMODE, hsotg->regs + GUSBCFG); - usleep_range(100000, 150000); + usleep_range(25000, 50000); } gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); @@ -3148,7 +3148,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); if (hsotg->dr_mode != USB_DR_MODE_HOST) { dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - usleep_range(100000, 150000); + usleep_range(25000, 50000); } /* hwcfg2 */ -- cgit v1.2.3 From 7d56cc2620f523eba7a831daa22186c8ae5bbdfe Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 17 Dec 2015 11:15:21 -0800 Subject: usb: dwc2: Avoid more calls to dwc2_core_reset() Calls to dwc2_core_reset() are currently very slow, taking at least 150ms (possibly more). It behooves us to take as many of these calls out as possible. It turns out that the calls in dwc2_fs_phy_init() and dwc2_hs_phy_init() should (as documented in the code) only be needed if we need to do a PHY SELECT. That means that if we see that we can avoid the PHY SELECT then we can avoid the reset. This patch appears to successfully bypass two resets (one per USB device) on rk3288-based ARM Chromebooks. Signed-off-by: Douglas Anderson Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 9659dbd33c8c..c8f66ad48a55 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -553,16 +553,20 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) */ if (select_phy) { dev_dbg(hsotg->dev, "FS PHY selected\n"); + usbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - usbcfg |= GUSBCFG_PHYSEL; - dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); + if (!(usbcfg & GUSBCFG_PHYSEL)) { + usbcfg |= GUSBCFG_PHYSEL; + dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); - /* Reset after a PHY select */ - retval = dwc2_core_reset(hsotg); - if (retval) { - dev_err(hsotg->dev, "%s() Reset failed, aborting", - __func__); - return retval; + /* Reset after a PHY select */ + retval = dwc2_core_reset(hsotg); + + if (retval) { + dev_err(hsotg->dev, + "%s: Reset failed, aborting", __func__); + return retval; + } } } @@ -597,13 +601,13 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) { - u32 usbcfg; + u32 usbcfg, usbcfg_old; int retval = 0; if (!select_phy) return 0; - usbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + usbcfg = usbcfg_old = dwc2_readl(hsotg->regs + GUSBCFG); /* * HS PHY parameters. These parameters are preserved during soft reset @@ -631,14 +635,16 @@ static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) break; } - dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); + if (usbcfg != usbcfg_old) { + dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); - /* Reset after setting the PHY parameters */ - retval = dwc2_core_reset(hsotg); - if (retval) { - dev_err(hsotg->dev, "%s() Reset failed, aborting", - __func__); - return retval; + /* Reset after setting the PHY parameters */ + retval = dwc2_core_reset(hsotg); + if (retval) { + dev_err(hsotg->dev, + "%s: Reset failed, aborting", __func__); + return retval; + } } return retval; -- cgit v1.2.3 From b8ccc593eeeacde0e6794c4dcec0a57eba7356e6 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:15:35 -0800 Subject: usb: dwc2: Reorder AHBIDLE and CSFTRST in dwc2_core_reset() According to the databook, the core soft reset should be done before checking for AHBIDLE. The gadget version of core reset had it correct but the hcd version did not. This fixes the hcd version. Signed-off-by: John Youn Reviewed-by: Douglas Anderson Tested-by: Douglas Anderson Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index c8f66ad48a55..62505068abf2 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -489,32 +489,33 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) dev_vdbg(hsotg->dev, "%s()\n", __func__); - /* Wait for AHB master IDLE state */ + /* Core Soft Reset */ + greset = dwc2_readl(hsotg->regs + GRSTCTL); + greset |= GRSTCTL_CSFTRST; + dwc2_writel(greset, hsotg->regs + GRSTCTL); do { udelay(1); greset = dwc2_readl(hsotg->regs + GRSTCTL); if (++count > 50) { dev_warn(hsotg->dev, - "%s() HANG! AHB Idle GRSTCTL=%0x\n", + "%s() HANG! Soft Reset GRSTCTL=%0x\n", __func__, greset); return -EBUSY; } - } while (!(greset & GRSTCTL_AHBIDLE)); + } while (greset & GRSTCTL_CSFTRST); - /* Core Soft Reset */ + /* Wait for AHB master IDLE state */ count = 0; - greset |= GRSTCTL_CSFTRST; - dwc2_writel(greset, hsotg->regs + GRSTCTL); do { udelay(1); greset = dwc2_readl(hsotg->regs + GRSTCTL); if (++count > 50) { dev_warn(hsotg->dev, - "%s() HANG! Soft Reset GRSTCTL=%0x\n", + "%s() HANG! AHB Idle GRSTCTL=%0x\n", __func__, greset); return -EBUSY; } - } while (greset & GRSTCTL_CSFTRST); + } while (!(greset & GRSTCTL_AHBIDLE)); if (hsotg->dr_mode == USB_DR_MODE_HOST) { gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); -- cgit v1.2.3 From 6d58f346a61ff50eda740e6216e9829e572d75c8 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:15:49 -0800 Subject: usb: dwc2: Rename dwc2_core_reset() Renamed dwc2_core_reset() to dwc2_core_reset_and_force_dr_mode(). This describes what it is doing more accurately. This is in preparation of introducing a plain dwc2_core_reset() function that only performs the reset and doesn't force the mode. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 8 ++++---- drivers/usb/dwc2/core.h | 2 +- drivers/usb/dwc2/platform.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 62505068abf2..eb291c9e3c16 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -481,7 +481,7 @@ static void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) * Do core a soft reset of the core. Be careful with this because it * resets all the internal state machines of the core. */ -int dwc2_core_reset(struct dwc2_hsotg *hsotg) +int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) { u32 greset; int count = 0; @@ -561,7 +561,7 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); /* Reset after a PHY select */ - retval = dwc2_core_reset(hsotg); + retval = dwc2_core_reset_and_force_dr_mode(hsotg); if (retval) { dev_err(hsotg->dev, @@ -640,7 +640,7 @@ static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) dwc2_writel(usbcfg, hsotg->regs + GUSBCFG); /* Reset after setting the PHY parameters */ - retval = dwc2_core_reset(hsotg); + retval = dwc2_core_reset_and_force_dr_mode(hsotg); if (retval) { dev_err(hsotg->dev, "%s: Reset failed, aborting", __func__); @@ -805,7 +805,7 @@ int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) * needed to in order to properly detect various parameters). */ if (!initial_setup) { - retval = dwc2_core_reset(hsotg); + retval = dwc2_core_reset_and_force_dr_mode(hsotg); if (retval) { dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", __func__); diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index cbece3a68c30..d1989c68c728 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -880,7 +880,7 @@ enum dwc2_halt_status { * The following functions support initialization of the core driver component * and the DWC_otg controller */ -extern int dwc2_core_reset(struct dwc2_hsotg *hsotg); +extern int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg); extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg); extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg); extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index a72302d740dd..8bd7315a06c9 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -460,7 +460,7 @@ static int dwc2_driver_probe(struct platform_device *dev) * Reset before dwc2_get_hwparams() then it could get power-on real * reset value form registers. */ - dwc2_core_reset(hsotg); + dwc2_core_reset_and_force_dr_mode(hsotg); /* Detect config values from hardware */ retval = dwc2_get_hwparams(hsotg); -- cgit v1.2.3 From b5d308abef1c5c0f24128845e41d414a8f8438f6 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:16:03 -0800 Subject: usb: dwc2: Add dwc2_core_reset() dwc2_core_reset() was previously renamed to dwc2_core_reset_and_dr_force_mode(). Now add back dwc2_core_reset() which performs only a basic core reset without forcing the mode. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 22 ++++++++++++++++++++-- drivers/usb/dwc2/core.h | 1 + 2 files changed, 21 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index eb291c9e3c16..15f359fe76d3 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -481,11 +481,10 @@ static void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) * Do core a soft reset of the core. Be careful with this because it * resets all the internal state machines of the core. */ -int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) +int dwc2_core_reset(struct dwc2_hsotg *hsotg) { u32 greset; int count = 0; - u32 gusbcfg; dev_vdbg(hsotg->dev, "%s()\n", __func__); @@ -517,6 +516,25 @@ int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) } } while (!(greset & GRSTCTL_AHBIDLE)); + return 0; +} + +/* + * Do core a soft reset of the core. Be careful with this because it + * resets all the internal state machines of the core. + * + * Additionally this will apply force mode as per the hsotg->dr_mode + * parameter. + */ +int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) +{ + int retval; + u32 gusbcfg; + + retval = dwc2_core_reset(hsotg); + if (retval) + return retval; + if (hsotg->dr_mode == USB_DR_MODE_HOST) { gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); gusbcfg &= ~GUSBCFG_FORCEDEVMODE; diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index d1989c68c728..15e27bb509b5 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -880,6 +880,7 @@ enum dwc2_halt_status { * The following functions support initialization of the core driver component * and the DWC_otg controller */ +extern int dwc2_core_reset(struct dwc2_hsotg *hsotg); extern int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg); extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg); extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg); -- cgit v1.2.3 From 6bea962053e76a4407f0d138184a8737eea960ee Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:16:17 -0800 Subject: usb: dwc2: Add functions to check the HW OTG config Added functions to query the GHWCFG2.OTG_MODE. This tells us whether the controller hardware is configured for OTG, device-only, or host-only. Signed-off-by: John Youn Tested-by: Douglas Anderson Reviewed-by: Douglas Anderson Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 37 +++++++++++++++++++++++++++++++++++++ drivers/usb/dwc2/core.h | 13 +++++++++++++ 2 files changed, 50 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 15f359fe76d3..b700a47e026a 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -3342,6 +3342,43 @@ void dwc2_disable_global_interrupts(struct dwc2_hsotg *hsotg) dwc2_writel(ahbcfg, hsotg->regs + GAHBCFG); } +/* Returns the controller's GHWCFG2.OTG_MODE. */ +unsigned dwc2_op_mode(struct dwc2_hsotg *hsotg) +{ + u32 ghwcfg2 = dwc2_readl(hsotg->regs + GHWCFG2); + + return (ghwcfg2 & GHWCFG2_OP_MODE_MASK) >> + GHWCFG2_OP_MODE_SHIFT; +} + +/* Returns true if the controller is capable of DRD. */ +bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg) +{ + unsigned op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE); +} + +/* Returns true if the controller is host-only. */ +bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg) +{ + unsigned op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_HOST) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST); +} + +/* Returns true if the controller is device-only. */ +bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg) +{ + unsigned op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE); +} + MODULE_DESCRIPTION("DESIGNWARE HS OTG Core"); MODULE_AUTHOR("Synopsys, Inc."); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 15e27bb509b5..1fd434510a43 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -1137,6 +1137,19 @@ extern int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); extern int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg); extern int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); +/* + * The following functions check the controller's OTG operation mode + * capability (GHWCFG2.OTG_MODE). + * + * These functions can be used before the internal hsotg->hw_params + * are read in and cached so they always read directly from the + * GHWCFG2 register. + */ +unsigned dwc2_op_mode(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg); + /* * Dump core registers and SPRAM */ -- cgit v1.2.3 From 5268ed9d2e3b52f703f3661eef14cecbb2b572d4 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:16:31 -0800 Subject: usb: dwc2: Fix dr_mode validation The dr_mode parameter was being checked against how the dwc2 module was being configured at compile time. But it wasn't checked against the hardware capabilities, nor were the hardware capabilities checked against the compilation parameters. This commit adds those checks and adjusts dr_mode to an appropriate value, if needed. If the hardware capabilities and module compilation do not match then we fail as it wouldn't be possible to run properly. The hardware, module, and dr_mode, can each be set to host, device, or otg. Check that all these values are compatible and adjust the value of dr_mode if possible. The following table summarizes the behavior: actual HW MOD dr_mode dr_mode ------------------------------ HST HST any : HST HST DEV any : --- HST OTG any : HST DEV HST any : --- DEV DEV any : DEV DEV OTG any : DEV OTG HST any : HST OTG DEV any : DEV OTG OTG any : dr_mode Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/platform.c | 82 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 8bd7315a06c9..f4366513acb6 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -149,6 +149,71 @@ static const struct dwc2_core_params params_rk3066 = { .hibernation = -1, }; +/* + * Check the dr_mode against the module configuration and hardware + * capabilities. + * + * The hardware, module, and dr_mode, can each be set to host, device, + * or otg. Check that all these values are compatible and adjust the + * value of dr_mode if possible. + * + * actual + * HW MOD dr_mode dr_mode + * ------------------------------ + * HST HST any : HST + * HST DEV any : --- + * HST OTG any : HST + * + * DEV HST any : --- + * DEV DEV any : DEV + * DEV OTG any : DEV + * + * OTG HST any : HST + * OTG DEV any : DEV + * OTG OTG any : dr_mode + */ +static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg) +{ + enum usb_dr_mode mode; + + hsotg->dr_mode = usb_get_dr_mode(hsotg->dev); + if (hsotg->dr_mode == USB_DR_MODE_UNKNOWN) + hsotg->dr_mode = USB_DR_MODE_OTG; + + mode = hsotg->dr_mode; + + if (dwc2_hw_is_device(hsotg)) { + if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) { + dev_err(hsotg->dev, + "Controller does not support host mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_PERIPHERAL; + } else if (dwc2_hw_is_host(hsotg)) { + if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) { + dev_err(hsotg->dev, + "Controller does not support device mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_HOST; + } else { + if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) + mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) + mode = USB_DR_MODE_PERIPHERAL; + } + + if (mode != hsotg->dr_mode) { + dev_warn(hsotg->dev, + "Configuration mismatch. dr_mode forced to %s\n", + mode == USB_DR_MODE_HOST ? "host" : "device"); + + hsotg->dr_mode = mode; + } + + return 0; +} + static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg) { struct platform_device *pdev = to_platform_device(hsotg->dev); @@ -412,19 +477,6 @@ static int dwc2_driver_probe(struct platform_device *dev) dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n", (unsigned long)res->start, hsotg->regs); - hsotg->dr_mode = usb_get_dr_mode(&dev->dev); - if (IS_ENABLED(CONFIG_USB_DWC2_HOST) && - hsotg->dr_mode != USB_DR_MODE_HOST) { - hsotg->dr_mode = USB_DR_MODE_HOST; - dev_warn(hsotg->dev, - "Configuration mismatch. Forcing host mode\n"); - } else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) && - hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { - hsotg->dr_mode = USB_DR_MODE_PERIPHERAL; - dev_warn(hsotg->dev, - "Configuration mismatch. Forcing peripheral mode\n"); - } - retval = dwc2_lowlevel_hw_init(hsotg); if (retval) return retval; @@ -456,6 +508,10 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) return retval; + retval = dwc2_get_dr_mode(hsotg); + if (retval) + return retval; + /* * Reset before dwc2_get_hwparams() then it could get power-on real * reset value form registers. -- cgit v1.2.3 From 1696d5ab99ef885ae62da5ad58f9eff16da7ff78 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:16:45 -0800 Subject: usb: dwc2: Move mode querying functions into core.h These functions should go in core.h where they can be called from core, device, or host. Signed-off-by: John Youn Reviewed-by: Douglas Anderson Tested-by: Douglas Anderson Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.h | 12 ++++++++++++ drivers/usb/dwc2/hcd.h | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 1fd434510a43..263a9da9edb2 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -1150,6 +1150,18 @@ bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg); bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg); bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg); +/* + * Returns the mode of operation, host or device + */ +static inline int dwc2_is_host_mode(struct dwc2_hsotg *hsotg) +{ + return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) != 0; +} +static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) +{ + return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) == 0; +} + /* * Dump core registers and SPRAM */ diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index 6e822661a69e..8f0a29cefdf7 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -383,18 +383,6 @@ static inline void disable_hc_int(struct dwc2_hsotg *hsotg, int chnum, u32 intr) dwc2_writel(mask, hsotg->regs + HCINTMSK(chnum)); } -/* - * Returns the mode of operation, host or device - */ -static inline int dwc2_is_host_mode(struct dwc2_hsotg *hsotg) -{ - return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) != 0; -} -static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) -{ - return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) == 0; -} - /* * Reads HPRT0 in preparation to modify. It keeps the WC bits 0 so that if they * are read as 1, they won't clear when written back. -- cgit v1.2.3 From 263b7fb557f797d9d4d1dcf93fb6bb2efc3f1d46 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:16:58 -0800 Subject: usb: dwc2: Move reset into dwc2_get_hwparams() The reset is required to get reset values of the hardware parameters but the force mode is not. Move the base reset into dwc2_get_hwparams() and do the reset and force mode afterwards. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 8 ++++++++ drivers/usb/dwc2/platform.c | 10 +++------- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index b700a47e026a..3b55635c878c 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -3120,6 +3120,9 @@ void dwc2_set_parameters(struct dwc2_hsotg *hsotg, /** * During device initialization, read various hardware configuration * registers and interpret the contents. + * + * This should be called during driver probe. It will perform a core + * soft reset in order to get the reset values of the parameters. */ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) { @@ -3128,6 +3131,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; u32 hptxfsiz, grxfsiz, gnptxfsiz; u32 gusbcfg = 0; + int retval; /* * Attempt to ensure this device is really a DWC_otg Controller. @@ -3147,6 +3151,10 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf, hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid); + retval = dwc2_core_reset(hsotg); + if (retval) + return retval; + hwcfg1 = dwc2_readl(hsotg->regs + GHWCFG1); hwcfg2 = dwc2_readl(hsotg->regs + GHWCFG2); hwcfg3 = dwc2_readl(hsotg->regs + GHWCFG3); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index f4366513acb6..bfa4a6a8a1f3 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -512,13 +512,7 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) return retval; - /* - * Reset before dwc2_get_hwparams() then it could get power-on real - * reset value form registers. - */ - dwc2_core_reset_and_force_dr_mode(hsotg); - - /* Detect config values from hardware */ + /* Reset the controller and detect hardware config values */ retval = dwc2_get_hwparams(hsotg); if (retval) goto error; @@ -526,6 +520,8 @@ static int dwc2_driver_probe(struct platform_device *dev) /* Validate parameter values */ dwc2_set_parameters(hsotg, params); + dwc2_core_reset_and_force_dr_mode(hsotg); + if (hsotg->dr_mode != USB_DR_MODE_HOST) { retval = dwc2_gadget_init(hsotg, irq); if (retval) -- cgit v1.2.3 From 09c96980dc723462ed2eeacc945fed5bcb278f85 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:17:12 -0800 Subject: usb: dwc2: Add functions to set and clear force mode Added functions to set force mode for host and device. These functions will check the current mode and only force if needed thus avoiding unnecessary force mode delays. However clearing the mode is currently done unconditionally and with the delay in place. This is needed during the connector ID status change interrupt in order to ensure that the mode has changed properly. This preserves the old behavior only for this case. The warning comment about this is moved into the clear mode condition. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 149 ++++++++++++++++++++++++++++++++++++++++-------- drivers/usb/dwc2/core.h | 2 + 2 files changed, 127 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 3b55635c878c..436e7d1ef3c8 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -519,6 +519,114 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) return 0; } +/* + * Force the mode of the controller. + * + * Forcing the mode is needed for two cases: + * + * 1) If the dr_mode is set to either HOST or PERIPHERAL we force the + * controller to stay in a particular mode regardless of ID pin + * changes. We do this usually after a core reset. + * + * 2) During probe we want to read reset values of the hw + * configuration registers that are only available in either host or + * device mode. We may need to force the mode if the current mode does + * not allow us to access the register in the mode that we want. + * + * In either case it only makes sense to force the mode if the + * controller hardware is OTG capable. + * + * Checks are done in this function to determine whether doing a force + * would be valid or not. + * + * If a force is done, it requires a 25ms delay to take effect. + * + * Returns true if the mode was forced. + */ +static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) +{ + u32 gusbcfg; + u32 set; + u32 clear; + + dev_dbg(hsotg->dev, "Forcing mode to %s\n", host ? "host" : "device"); + + /* + * Force mode has no effect if the hardware is not OTG. + */ + if (!dwc2_hw_is_otg(hsotg)) + return false; + + /* + * If dr_mode is either peripheral or host only, there is no + * need to ever force the mode to the opposite mode. + */ + if (WARN_ON(host && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) + return false; + + if (WARN_ON(!host && hsotg->dr_mode == USB_DR_MODE_HOST)) + return false; + + gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + + set = host ? GUSBCFG_FORCEHOSTMODE : GUSBCFG_FORCEDEVMODE; + clear = host ? GUSBCFG_FORCEDEVMODE : GUSBCFG_FORCEHOSTMODE; + + /* + * If the force mode bit is already set, don't set it. + */ + if ((gusbcfg & set) && !(gusbcfg & clear)) + return false; + + gusbcfg &= ~clear; + gusbcfg |= set; + dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); + + msleep(25); + return true; +} + +/* + * Clears the force mode bits. + */ +static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) +{ + u32 gusbcfg; + + gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; + gusbcfg &= ~GUSBCFG_FORCEDEVMODE; + dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); + + /* + * NOTE: This long sleep is _very_ important, otherwise the core will + * not stay in host mode after a connector ID change! + */ + usleep_range(150000, 160000); +} + +/* + * Sets or clears force mode based on the dr_mode parameter. + */ +void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) +{ + switch (hsotg->dr_mode) { + case USB_DR_MODE_HOST: + dwc2_force_mode(hsotg, true); + break; + case USB_DR_MODE_PERIPHERAL: + dwc2_force_mode(hsotg, false); + break; + case USB_DR_MODE_OTG: + dwc2_clear_force_mode(hsotg); + break; + default: + dev_warn(hsotg->dev, "%s() Invalid dr_mode=%d\n", + __func__, hsotg->dr_mode); + break; + } +} + /* * Do core a soft reset of the core. Be careful with this because it * resets all the internal state machines of the core. @@ -529,35 +637,12 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg) { int retval; - u32 gusbcfg; retval = dwc2_core_reset(hsotg); if (retval) return retval; - if (hsotg->dr_mode == USB_DR_MODE_HOST) { - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEDEVMODE; - gusbcfg |= GUSBCFG_FORCEHOSTMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - } else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) { - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; - gusbcfg |= GUSBCFG_FORCEDEVMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - } else if (hsotg->dr_mode == USB_DR_MODE_OTG) { - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; - gusbcfg &= ~GUSBCFG_FORCEDEVMODE; - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - } - - /* - * NOTE: This long sleep is _very_ important, otherwise the core will - * not stay in host mode after a connector ID change! - */ - usleep_range(150000, 160000); - + dwc2_force_dr_mode(hsotg); return 0; } @@ -3117,6 +3202,22 @@ void dwc2_set_parameters(struct dwc2_hsotg *hsotg, dwc2_set_param_hibernation(hsotg, params->hibernation); } +/* + * Forces either host or device mode if the controller is not + * currently in that mode. + * + * Returns true if the mode was forced. + */ +static bool dwc2_force_mode_if_needed(struct dwc2_hsotg *hsotg, bool host) +{ + if (host && dwc2_is_host_mode(hsotg)) + return false; + else if (!host && dwc2_is_device_mode(hsotg)) + return false; + + return dwc2_force_mode(hsotg, host); +} + /** * During device initialization, read various hardware configuration * registers and interpret the contents. diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 263a9da9edb2..efdbe455697f 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -886,6 +886,8 @@ extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg); extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg); extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore); +void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg); + /* * Host core Functions. * The following functions support managing the DWC_otg controller in host -- cgit v1.2.3 From 55e1040e424b59063da627fb580ec953f4c01de7 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:17:31 -0800 Subject: usb: dwc2: Improve handling of host and device hwparams Adds separate functions to get the host and device specific hardware parameters. The functions check whether the parameters need to be read at all, depending on dr_mode, and forces the mode only if necessary. This saves some delays during probe. This also adds two device mode parameters that will be used by the gadget. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 88 +++++++++++++++++++++++++++++++++++++------------ drivers/usb/dwc2/core.h | 3 ++ 2 files changed, 70 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 436e7d1ef3c8..9dca83544b5e 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -3218,6 +3218,63 @@ static bool dwc2_force_mode_if_needed(struct dwc2_hsotg *hsotg, bool host) return dwc2_force_mode(hsotg, host); } +/* + * Gets host hardware parameters. Forces host mode if not currently in + * host mode. Should be called immediately after a core soft reset in + * order to get the reset values. + */ +static void dwc2_get_host_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + u32 gnptxfsiz; + u32 hptxfsiz; + bool forced; + + if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) + return; + + forced = dwc2_force_mode_if_needed(hsotg, true); + + gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); + hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); + dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); + dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); + + if (forced) + dwc2_clear_force_mode(hsotg); + + hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; + hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; +} + +/* + * Gets device hardware parameters. Forces device mode if not + * currently in device mode. Should be called immediately after a core + * soft reset in order to get the reset values. + */ +static void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + bool forced; + u32 gnptxfsiz; + + if (hsotg->dr_mode == USB_DR_MODE_HOST) + return; + + forced = dwc2_force_mode_if_needed(hsotg, false); + + gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); + dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); + + if (forced) + dwc2_clear_force_mode(hsotg); + + hw->dev_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; +} + /** * During device initialization, read various hardware configuration * registers and interpret the contents. @@ -3230,8 +3287,7 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) struct dwc2_hw_params *hw = &hsotg->hw_params; unsigned width; u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; - u32 hptxfsiz, grxfsiz, gnptxfsiz; - u32 gusbcfg = 0; + u32 grxfsiz; int retval; /* @@ -3268,22 +3324,16 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "hwcfg4=%08x\n", hwcfg4); dev_dbg(hsotg->dev, "grxfsiz=%08x\n", grxfsiz); - /* Force host mode to get HPTXFSIZ / GNPTXFSIZ exact power on value */ - if (hsotg->dr_mode != USB_DR_MODE_HOST) { - gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); - dwc2_writel(gusbcfg | GUSBCFG_FORCEHOSTMODE, - hsotg->regs + GUSBCFG); - usleep_range(25000, 50000); - } + /* + * Host specific hardware parameters. Reading these parameters + * requires the controller to be in host mode. The mode will + * be forced, if necessary, to read these values. + */ + dwc2_get_host_hwparams(hsotg); + dwc2_get_dev_hwparams(hsotg); - gnptxfsiz = dwc2_readl(hsotg->regs + GNPTXFSIZ); - hptxfsiz = dwc2_readl(hsotg->regs + HPTXFSIZ); - dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); - dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); - if (hsotg->dr_mode != USB_DR_MODE_HOST) { - dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - usleep_range(25000, 50000); - } + /* hwcfg1 */ + hw->dev_ep_dirs = hwcfg1; /* hwcfg2 */ hw->op_mode = (hwcfg2 & GHWCFG2_OP_MODE_MASK) >> @@ -3339,10 +3389,6 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) /* fifo sizes */ hw->host_rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> GRXFSIZ_DEPTH_SHIFT; - hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> - FIFOSIZE_DEPTH_SHIFT; - hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> - FIFOSIZE_DEPTH_SHIFT; dev_dbg(hsotg->dev, "Detected values from hardware:\n"); dev_dbg(hsotg->dev, " op_mode=%d\n", diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index efdbe455697f..7fb6434f4639 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -459,6 +459,7 @@ struct dwc2_core_params { * 1 - 16 bits * 2 - 8 or 16 bits * @snpsid: Value from SNPSID register + * @dev_ep_dirs: Direction of device endpoints (GHWCFG1) */ struct dwc2_hw_params { unsigned op_mode:3; @@ -469,6 +470,7 @@ struct dwc2_hw_params { unsigned en_multiple_tx_fifo:1; unsigned host_rx_fifo_size:16; unsigned host_nperio_tx_fifo_size:16; + unsigned dev_nperio_tx_fifo_size:16; unsigned host_perio_tx_fifo_size:16; unsigned nperio_tx_q_depth:3; unsigned host_perio_tx_q_depth:3; @@ -485,6 +487,7 @@ struct dwc2_hw_params { unsigned power_optimized:1; unsigned utmi_phy_data_width:2; u32 snpsid; + u32 dev_ep_dirs; }; /* Size of control and EP0 buffers */ -- cgit v1.2.3 From 43e9034904dd37db7ed87fa8f5039c561c4004cd Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:17:45 -0800 Subject: usb: dwc2: gadget: Use hw params from core Use the previously cached hw params in the gadget. This saves a reset and force mode in the gadget initialization during probe and makes getting the hardware parameters consistent between gadget and host. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/gadget.c | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 3a24cbf355a9..5f63058f854e 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3403,8 +3403,8 @@ static int dwc2_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) /* check hardware configuration */ - cfg = dwc2_readl(hsotg->regs + GHWCFG2); - hsotg->num_of_eps = (cfg >> GHWCFG2_NUM_DEV_EP_SHIFT) & 0xF; + hsotg->num_of_eps = hsotg->hw_params.num_dev_ep; + /* Add ep0 */ hsotg->num_of_eps++; @@ -3415,7 +3415,7 @@ static int dwc2_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) /* Same dwc2_hsotg_ep is used in both directions for ep0 */ hsotg->eps_out[0] = hsotg->eps_in[0]; - cfg = dwc2_readl(hsotg->regs + GHWCFG1); + cfg = hsotg->hw_params.dev_ep_dirs; for (i = 1, cfg >>= 2; i < hsotg->num_of_eps; i++, cfg >>= 2) { ep_type = cfg & 3; /* Direction in or both */ @@ -3434,11 +3434,8 @@ static int dwc2_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) } } - cfg = dwc2_readl(hsotg->regs + GHWCFG3); - hsotg->fifo_mem = (cfg >> GHWCFG3_DFIFO_DEPTH_SHIFT); - - cfg = dwc2_readl(hsotg->regs + GHWCFG4); - hsotg->dedicated_fifos = (cfg >> GHWCFG4_DED_FIFO_SHIFT) & 1; + hsotg->fifo_mem = hsotg->hw_params.total_fifo_size; + hsotg->dedicated_fifos = hsotg->hw_params.en_multiple_tx_fifo; dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", hsotg->num_of_eps, @@ -3563,6 +3560,17 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) memcpy(&hsotg->g_tx_fifo_sz[1], p_tx_fifo, sizeof(p_tx_fifo)); /* Device tree specific probe */ dwc2_hsotg_of_probe(hsotg); + + /* Check against largest possible value. */ + if (hsotg->g_np_g_tx_fifo_sz > + hsotg->hw_params.dev_nperio_tx_fifo_size) { + dev_warn(dev, "Specified GNPTXFDEP=%d > %d\n", + hsotg->g_np_g_tx_fifo_sz, + hsotg->hw_params.dev_nperio_tx_fifo_size); + hsotg->g_np_g_tx_fifo_sz = + hsotg->hw_params.dev_nperio_tx_fifo_size; + } + /* Dump fifo information */ dev_dbg(dev, "NonPeriodic TXFIFO size: %d\n", hsotg->g_np_g_tx_fifo_sz); @@ -3579,20 +3587,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) hsotg->op_state = OTG_STATE_B_PERIPHERAL; - /* - * Force Device mode before initialization. - * This allows correctly configuring fifo for device mode. - */ - __bic32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEHOSTMODE); - __orr32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEDEVMODE); - - /* - * According to Synopsys databook, this sleep is needed for the force - * device mode to take effect. - */ - msleep(25); - - dwc2_hsotg_corereset(hsotg); ret = dwc2_hsotg_hw_cfg(hsotg); if (ret) { dev_err(hsotg->dev, "Hardware configuration failed: %d\n", ret); @@ -3601,9 +3595,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) dwc2_hsotg_init(hsotg); - /* Switch back to default configuration */ - __bic32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEDEVMODE); - hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); if (!hsotg->ctrl_buff) { -- cgit v1.2.3 From 241729baa932a69cd203dbaa81abbb8af5b77b65 Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:17:59 -0800 Subject: usb: dwc2: gadget: Replace dwc2_hsotg_corereset() The dwc2_core_reset() function exists in the core so use that one instead. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/gadget.c | 50 +---------------------------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5f63058f854e..628ba74d8e50 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2243,54 +2243,6 @@ static void dwc2_hsotg_irq_fifoempty(struct dwc2_hsotg *hsotg, bool periodic) GINTSTS_PTXFEMP | \ GINTSTS_RXFLVL) -/** - * dwc2_hsotg_corereset - issue softreset to the core - * @hsotg: The device state - * - * Issue a soft reset to the core, and await the core finishing it. - */ -static int dwc2_hsotg_corereset(struct dwc2_hsotg *hsotg) -{ - int timeout; - u32 grstctl; - - dev_dbg(hsotg->dev, "resetting core\n"); - - /* issue soft reset */ - dwc2_writel(GRSTCTL_CSFTRST, hsotg->regs + GRSTCTL); - - timeout = 10000; - do { - grstctl = dwc2_readl(hsotg->regs + GRSTCTL); - } while ((grstctl & GRSTCTL_CSFTRST) && timeout-- > 0); - - if (grstctl & GRSTCTL_CSFTRST) { - dev_err(hsotg->dev, "Failed to get CSftRst asserted\n"); - return -EINVAL; - } - - timeout = 10000; - - while (1) { - u32 grstctl = dwc2_readl(hsotg->regs + GRSTCTL); - - if (timeout-- < 0) { - dev_info(hsotg->dev, - "%s: reset failed, GRSTCTL=%08x\n", - __func__, grstctl); - return -ETIMEDOUT; - } - - if (!(grstctl & GRSTCTL_AHBIDLE)) - continue; - - break; /* reset done */ - } - - dev_dbg(hsotg->dev, "reset successful\n"); - return 0; -} - /** * dwc2_hsotg_core_init - issue softreset to the core * @hsotg: The device state @@ -2307,7 +2259,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); if (!is_usb_reset) - if (dwc2_hsotg_corereset(hsotg)) + if (dwc2_core_reset(hsotg)) return; /* -- cgit v1.2.3 From 97e463886b873f62bea2293e7edf81fdb884b84f Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:18:13 -0800 Subject: usb: dwc2: Reduce delay when forcing mode in reset The delay for force mode is only 25ms according to the databook. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 9dca83544b5e..39a0fa8a4c0a 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -602,7 +602,7 @@ static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) * NOTE: This long sleep is _very_ important, otherwise the core will * not stay in host mode after a connector ID change! */ - usleep_range(150000, 160000); + msleep(25); } /* -- cgit v1.2.3 From 25362d318371e1e271dda24995ceabb8457b3b7c Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:18:27 -0800 Subject: usb: dwc2: Remove redundant reset in probe Reset already happens before this so just force the dr_mode. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index bfa4a6a8a1f3..1e7cb99d2100 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -520,7 +520,7 @@ static int dwc2_driver_probe(struct platform_device *dev) /* Validate parameter values */ dwc2_set_parameters(hsotg, params); - dwc2_core_reset_and_force_dr_mode(hsotg); + dwc2_force_dr_mode(hsotg); if (hsotg->dr_mode != USB_DR_MODE_HOST) { retval = dwc2_gadget_init(hsotg, irq); -- cgit v1.2.3 From 60c0288c72c980fb37ed4e48f68c9743a53b662c Mon Sep 17 00:00:00 2001 From: John Youn Date: Thu, 17 Dec 2015 11:18:41 -0800 Subject: usb: dwc2: gadget: Remove call to dwc2_hsotg_init() Remove call to dwc2_hsotg_init() from dwc2_gadget_init(). The gadget_init function should not access any device registers because the mode isn't guaranteed here. Also, this is already called elsewhere before anything starts on the gadget so it is not necessary here. Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/gadget.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 628ba74d8e50..8ab7a9e0e547 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3545,8 +3545,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) return ret; } - dwc2_hsotg_init(hsotg); - hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); if (!hsotg->ctrl_buff) { -- cgit v1.2.3 From 6d76c92c2fcbee4fd1f6d7b375d71057c7a615b1 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 18 Dec 2015 03:26:17 +0100 Subject: usb: dwc2: gadget: Repair DSTS register decoding The "enumspd" field is located in register DSTS[2:1], but the code which checks the bitfield does not shift the value accordingly. This in turn causes incorrect detection of gadget link partner speed in dwc2_hsotg_irq_enumdone() . Shift the value accordingly to fix the problem with speed detection. Acked-by: John Youn Signed-off-by: Marek Vasut Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 8ab7a9e0e547..422ab7da4eb5 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2095,7 +2095,7 @@ static void dwc2_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) */ /* catch both EnumSpd_FS and EnumSpd_FS48 */ - switch (dsts & DSTS_ENUMSPD_MASK) { + switch ((dsts & DSTS_ENUMSPD_MASK) >> DSTS_ENUMSPD_SHIFT) { case DSTS_ENUMSPD_FS: case DSTS_ENUMSPD_FS48: hsotg->gadget.speed = USB_SPEED_FULL; -- cgit v1.2.3 From 39cee200c23eb3e28056011a1dec053beba4a18a Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Fri, 18 Dec 2015 12:02:04 +0100 Subject: usb: musb: core: call init and shutdown for the usb phy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The phy's init routine must be called before it can be used. Do so in musb_init_controller and the matching shutdown in musb_remove. Signed-off-by: Uwe Kleine-König Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 04548423094b..c3791a01ab31 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2136,6 +2136,10 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) pm_runtime_get_sync(musb->controller); + status = usb_phy_init(musb->xceiv); + if (status < 0) + goto err_usb_phy_init; + if (use_dma && dev->dma_mask) { musb->dma_controller = musb_dma_controller_create(musb, musb->mregs); @@ -2256,7 +2260,11 @@ fail3: cancel_delayed_work_sync(&musb->deassert_reset_work); if (musb->dma_controller) musb_dma_controller_destroy(musb->dma_controller); + fail2_5: + usb_phy_shutdown(musb->xceiv); + +err_usb_phy_init: pm_runtime_put_sync(musb->controller); fail2: @@ -2317,6 +2325,8 @@ static int musb_remove(struct platform_device *pdev) if (musb->dma_controller) musb_dma_controller_destroy(musb->dma_controller); + usb_phy_shutdown(musb->xceiv); + cancel_work_sync(&musb->irq_work); cancel_delayed_work_sync(&musb->finish_resume_work); cancel_delayed_work_sync(&musb->deassert_reset_work); -- cgit v1.2.3 From ea4a8cb1c2b47570e77f4489659e2c653ca66f9e Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Sat, 19 Dec 2015 00:34:34 +0800 Subject: usb: gadget: bcm63xx_udc: use list_for_each_entry_safe Use list_for_each_entry_safe() instead of list_for_each_safe() to simplify the code. Signed-off-by: Geliang Tang Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/bcm63xx_udc.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index 8cbb00325824..f5fccb3e4152 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -1083,7 +1083,7 @@ static int bcm63xx_ep_disable(struct usb_ep *ep) struct bcm63xx_ep *bep = our_ep(ep); struct bcm63xx_udc *udc = bep->udc; struct iudma_ch *iudma = bep->iudma; - struct list_head *pos, *n; + struct bcm63xx_req *breq, *n; unsigned long flags; if (!ep || !ep->desc) @@ -1099,10 +1099,7 @@ static int bcm63xx_ep_disable(struct usb_ep *ep) iudma_reset_channel(udc, iudma); if (!list_empty(&bep->queue)) { - list_for_each_safe(pos, n, &bep->queue) { - struct bcm63xx_req *breq = - list_entry(pos, struct bcm63xx_req, queue); - + list_for_each_entry_safe(breq, n, &bep->queue, queue) { usb_gadget_unmap_request(&udc->gadget, &breq->req, iudma->is_tx); list_del(&breq->queue); -- cgit v1.2.3 From a40a00318c7fcdd23e73cfffac0e33430a43a3e3 Mon Sep 17 00:00:00 2001 From: Heiko Stübner Date: Fri, 18 Dec 2015 19:30:59 +0100 Subject: usb: dwc2: add shutdown callback to platform variant In specific conditions (involving usb hubs) dwc2 devices can create a lot of interrupts, even to the point of overwhelming devices running at low frequencies. Some devices need to do special clock handling at shutdown-time which may bring the system clock below the threshold of being able to handle the dwc2 interrupts. Disabling dwc2-irqs in a shutdown callbacks prevents reboots/poweroffs from getting stuck in such cases. The hsotg struct already contains an unused irq element, so we can just use it to store the irq number for the shutdown callback. Reviewed-by: Douglas Anderson Acked-by: John Youn Signed-off-by: Heiko Stuebner Signed-off-by: Felipe Balbi --- drivers/usb/dwc2/platform.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 1e7cb99d2100..510f787434b3 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -405,6 +405,25 @@ static int dwc2_driver_remove(struct platform_device *dev) return 0; } +/** + * dwc2_driver_shutdown() - Called on device shutdown + * + * @dev: Platform device + * + * In specific conditions (involving usb hubs) dwc2 devices can create a + * lot of interrupts, even to the point of overwhelming devices running + * at low frequencies. Some devices need to do special clock handling + * at shutdown-time which may bring the system clock below the threshold + * of being able to handle the dwc2 interrupts. Disabling dwc2-irqs + * prevents reboots/poweroffs from getting stuck in such cases. + */ +static void dwc2_driver_shutdown(struct platform_device *dev) +{ + struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); + + disable_irq(hsotg->irq); +} + static const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = ¶ms_bcm2835 }, { .compatible = "hisilicon,hi6220-usb", .data = ¶ms_hi6220 }, @@ -435,7 +454,6 @@ static int dwc2_driver_probe(struct platform_device *dev) struct dwc2_hsotg *hsotg; struct resource *res; int retval; - int irq; match = of_match_device(dwc2_of_match_table, &dev->dev); if (match && match->data) { @@ -490,15 +508,15 @@ static int dwc2_driver_probe(struct platform_device *dev) dwc2_set_all_params(hsotg->core_params, -1); - irq = platform_get_irq(dev, 0); - if (irq < 0) { + hsotg->irq = platform_get_irq(dev, 0); + if (hsotg->irq < 0) { dev_err(&dev->dev, "missing IRQ resource\n"); - return irq; + return hsotg->irq; } dev_dbg(hsotg->dev, "registering common handler for irq%d\n", - irq); - retval = devm_request_irq(hsotg->dev, irq, + hsotg->irq); + retval = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_handle_common_intr, IRQF_SHARED, dev_name(hsotg->dev), hsotg); if (retval) @@ -523,14 +541,14 @@ static int dwc2_driver_probe(struct platform_device *dev) dwc2_force_dr_mode(hsotg); if (hsotg->dr_mode != USB_DR_MODE_HOST) { - retval = dwc2_gadget_init(hsotg, irq); + retval = dwc2_gadget_init(hsotg, hsotg->irq); if (retval) goto error; hsotg->gadget_enabled = 1; } if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { - retval = dwc2_hcd_init(hsotg, irq); + retval = dwc2_hcd_init(hsotg, hsotg->irq); if (retval) { if (hsotg->gadget_enabled) dwc2_hsotg_remove(hsotg); @@ -597,6 +615,7 @@ static struct platform_driver dwc2_platform_driver = { }, .probe = dwc2_driver_probe, .remove = dwc2_driver_remove, + .shutdown = dwc2_driver_shutdown, }; module_platform_driver(dwc2_platform_driver); -- cgit v1.2.3 From 746bfe63bba37ad55956b7377c9af494e7e28929 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 21 Dec 2015 18:40:04 +0900 Subject: usb: gadget: renesas_usb3: add support for Renesas USB3.0 peripheral controller R-Car H3 has USB3.0 peripheral controllers. This controller's has the following features: - Supports super, high and full speed - Contains 30 pipes for bulk or interrupt transfer - Contains dedicated DMA controller This driver doesn't support the dedicated DMAC for now. Acked-by: Rob Herring Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- .../devicetree/bindings/usb/renesas_usb3.txt | 23 + drivers/usb/gadget/udc/Kconfig | 11 + drivers/usb/gadget/udc/Makefile | 1 + drivers/usb/gadget/udc/renesas_usb3.c | 1975 ++++++++++++++++++++ 4 files changed, 2010 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/renesas_usb3.txt create mode 100644 drivers/usb/gadget/udc/renesas_usb3.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/usb/renesas_usb3.txt b/Documentation/devicetree/bindings/usb/renesas_usb3.txt new file mode 100644 index 000000000000..8d52766f07b9 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/renesas_usb3.txt @@ -0,0 +1,23 @@ +Renesas Electronics USB3.0 Peripheral driver + +Required properties: + - compatible: Must contain one of the following: + - "renesas,r8a7795-usb3-peri" + - reg: Base address and length of the register for the USB3.0 Peripheral + - interrupts: Interrupt specifier for the USB3.0 Peripheral + - clocks: clock phandle and specifier pair + +Example: + usb3_peri0: usb@ee020000 { + compatible = "renesas,r8a7795-usb3-peri"; + reg = <0 0xee020000 0 0x400>; + interrupts = ; + clocks = <&cpg CPG_MOD 328>; + }; + + usb3_peri1: usb@ee060000 { + compatible = "renesas,r8a7795-usb3-peri"; + reg = <0 0xee060000 0 0x400>; + interrupts = ; + clocks = <&cpg CPG_MOD 327>; + }; diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index cdbff54e07ac..753c29bd11ad 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -174,6 +174,17 @@ config USB_RENESAS_USBHS_UDC dynamically linked module called "renesas_usbhs" and force all gadget drivers to also be dynamically linked. +config USB_RENESAS_USB3 + tristate 'Renesas USB3.0 Peripheral controller' + depends on ARCH_SHMOBILE || COMPILE_TEST + help + Renesas USB3.0 Peripheral controller is a USB peripheral controller + that supports super, high, and full speed USB 3.0 data transfers. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usb3" and force all + gadget drivers to also be dynamically linked. + config USB_PXA27X tristate "PXA 27x" help diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index fba2049bf985..dfee53446319 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -19,6 +19,7 @@ fsl_usb2_udc-y := fsl_udc_core.o fsl_usb2_udc-$(CONFIG_ARCH_MXC) += fsl_mxc_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o +obj-$(CONFIG_USB_RENESAS_USB3) += renesas_usb3.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_S3C_HSUDC) += s3c-hsudc.o obj-$(CONFIG_USB_LPC32XX) += lpc32xx_udc.o diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c new file mode 100644 index 000000000000..93a3bec81df7 --- /dev/null +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -0,0 +1,1975 @@ +/* + * Renesas USB3.0 Peripheral driver (USB gadget) + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register definitions */ +#define USB3_AXI_INT_STA 0x008 +#define USB3_AXI_INT_ENA 0x00c +#define USB3_DMA_INT_STA 0x010 +#define USB3_DMA_INT_ENA 0x014 +#define USB3_USB_COM_CON 0x200 +#define USB3_USB20_CON 0x204 +#define USB3_USB30_CON 0x208 +#define USB3_USB_STA 0x210 +#define USB3_DRD_CON 0x218 +#define USB3_USB_INT_STA_1 0x220 +#define USB3_USB_INT_STA_2 0x224 +#define USB3_USB_INT_ENA_1 0x228 +#define USB3_USB_INT_ENA_2 0x22c +#define USB3_STUP_DAT_0 0x230 +#define USB3_STUP_DAT_1 0x234 +#define USB3_P0_MOD 0x280 +#define USB3_P0_CON 0x288 +#define USB3_P0_STA 0x28c +#define USB3_P0_INT_STA 0x290 +#define USB3_P0_INT_ENA 0x294 +#define USB3_P0_LNG 0x2a0 +#define USB3_P0_READ 0x2a4 +#define USB3_P0_WRITE 0x2a8 +#define USB3_PIPE_COM 0x2b0 +#define USB3_PN_MOD 0x2c0 +#define USB3_PN_RAMMAP 0x2c4 +#define USB3_PN_CON 0x2c8 +#define USB3_PN_STA 0x2cc +#define USB3_PN_INT_STA 0x2d0 +#define USB3_PN_INT_ENA 0x2d4 +#define USB3_PN_LNG 0x2e0 +#define USB3_PN_READ 0x2e4 +#define USB3_PN_WRITE 0x2e8 +#define USB3_SSIFCMD 0x340 + +/* AXI_INT_ENA and AXI_INT_STA */ +#define AXI_INT_DMAINT BIT(31) +#define AXI_INT_EPCINT BIT(30) + +/* LCLKSEL */ +#define LCLKSEL_LSEL BIT(18) + +/* USB_COM_CON */ +#define USB_COM_CON_CONF BIT(24) +#define USB_COM_CON_SPD_MODE BIT(17) +#define USB_COM_CON_EP0_EN BIT(16) +#define USB_COM_CON_DEV_ADDR_SHIFT 8 +#define USB_COM_CON_DEV_ADDR_MASK GENMASK(14, USB_COM_CON_DEV_ADDR_SHIFT) +#define USB_COM_CON_DEV_ADDR(n) (((n) << USB_COM_CON_DEV_ADDR_SHIFT) & \ + USB_COM_CON_DEV_ADDR_MASK) +#define USB_COM_CON_RX_DETECTION BIT(1) +#define USB_COM_CON_PIPE_CLR BIT(0) + +/* USB20_CON */ +#define USB20_CON_B2_PUE BIT(31) +#define USB20_CON_B2_SUSPEND BIT(24) +#define USB20_CON_B2_CONNECT BIT(17) +#define USB20_CON_B2_TSTMOD_SHIFT 8 +#define USB20_CON_B2_TSTMOD_MASK GENMASK(10, USB20_CON_B2_TSTMOD_SHIFT) +#define USB20_CON_B2_TSTMOD(n) (((n) << USB20_CON_B2_TSTMOD_SHIFT) & \ + USB20_CON_B2_TSTMOD_MASK) +#define USB20_CON_B2_TSTMOD_EN BIT(0) + +/* USB30_CON */ +#define USB30_CON_POW_SEL_SHIFT 24 +#define USB30_CON_POW_SEL_MASK GENMASK(26, USB30_CON_POW_SEL_SHIFT) +#define USB30_CON_POW_SEL_IN_U3 BIT(26) +#define USB30_CON_POW_SEL_IN_DISCON 0 +#define USB30_CON_POW_SEL_P2_TO_P0 BIT(25) +#define USB30_CON_POW_SEL_P0_TO_P3 BIT(24) +#define USB30_CON_POW_SEL_P0_TO_P2 0 +#define USB30_CON_B3_PLLWAKE BIT(23) +#define USB30_CON_B3_CONNECT BIT(17) +#define USB30_CON_B3_HOTRST_CMP BIT(1) + +/* USB_STA */ +#define USB_STA_SPEED_MASK (BIT(2) | BIT(1)) +#define USB_STA_SPEED_HS BIT(2) +#define USB_STA_SPEED_FS BIT(1) +#define USB_STA_SPEED_SS 0 +#define USB_STA_VBUS_STA BIT(0) + +/* DRD_CON */ +#define DRD_CON_PERI_CON BIT(24) + +/* USB_INT_ENA_1 and USB_INT_STA_1 */ +#define USB_INT_1_B3_PLLWKUP BIT(31) +#define USB_INT_1_B3_LUPSUCS BIT(30) +#define USB_INT_1_B3_DISABLE BIT(27) +#define USB_INT_1_B3_WRMRST BIT(21) +#define USB_INT_1_B3_HOTRST BIT(20) +#define USB_INT_1_B2_USBRST BIT(12) +#define USB_INT_1_B2_L1SPND BIT(11) +#define USB_INT_1_B2_SPND BIT(9) +#define USB_INT_1_B2_RSUM BIT(8) +#define USB_INT_1_SPEED BIT(1) +#define USB_INT_1_VBUS_CNG BIT(0) + +/* USB_INT_ENA_2 and USB_INT_STA_2 */ +#define USB_INT_2_PIPE(n) BIT(n) + +/* P0_MOD */ +#define P0_MOD_DIR BIT(6) + +/* P0_CON and PN_CON */ +#define PX_CON_BYTE_EN_MASK (BIT(10) | BIT(9)) +#define PX_CON_BYTE_EN_SHIFT 9 +#define PX_CON_BYTE_EN_BYTES(n) (((n) << PX_CON_BYTE_EN_SHIFT) & \ + PX_CON_BYTE_EN_MASK) +#define PX_CON_SEND BIT(8) + +/* P0_CON */ +#define P0_CON_ST_RES_MASK (BIT(27) | BIT(26)) +#define P0_CON_ST_RES_FORCE_STALL BIT(27) +#define P0_CON_ST_RES_NORMAL BIT(26) +#define P0_CON_ST_RES_FORCE_NRDY 0 +#define P0_CON_OT_RES_MASK (BIT(25) | BIT(24)) +#define P0_CON_OT_RES_FORCE_STALL BIT(25) +#define P0_CON_OT_RES_NORMAL BIT(24) +#define P0_CON_OT_RES_FORCE_NRDY 0 +#define P0_CON_IN_RES_MASK (BIT(17) | BIT(16)) +#define P0_CON_IN_RES_FORCE_STALL BIT(17) +#define P0_CON_IN_RES_NORMAL BIT(16) +#define P0_CON_IN_RES_FORCE_NRDY 0 +#define P0_CON_RES_WEN BIT(7) +#define P0_CON_BCLR BIT(1) + +/* P0_STA and PN_STA */ +#define PX_STA_BUFSTS BIT(0) + +/* P0_INT_ENA and P0_INT_STA */ +#define P0_INT_STSED BIT(18) +#define P0_INT_STSST BIT(17) +#define P0_INT_SETUP BIT(16) +#define P0_INT_RCVNL BIT(8) +#define P0_INT_ERDY BIT(7) +#define P0_INT_FLOW BIT(6) +#define P0_INT_STALL BIT(2) +#define P0_INT_NRDY BIT(1) +#define P0_INT_BFRDY BIT(0) +#define P0_INT_ALL_BITS (P0_INT_STSED | P0_INT_SETUP | P0_INT_BFRDY) + +/* PN_MOD */ +#define PN_MOD_DIR BIT(6) +#define PN_MOD_TYPE_SHIFT 4 +#define PN_MOD_TYPE_MASK GENMASK(5, PN_MOD_TYPE_SHIFT) +#define PN_MOD_TYPE(n) (((n) << PN_MOD_TYPE_SHIFT) & \ + PN_MOD_TYPE_MASK) +#define PN_MOD_EPNUM_MASK GENMASK(3, 0) +#define PN_MOD_EPNUM(n) ((n) & PN_MOD_EPNUM_MASK) + +/* PN_RAMMAP */ +#define PN_RAMMAP_RAMAREA_SHIFT 29 +#define PN_RAMMAP_RAMAREA_MASK GENMASK(31, PN_RAMMAP_RAMAREA_SHIFT) +#define PN_RAMMAP_RAMAREA_16KB BIT(31) +#define PN_RAMMAP_RAMAREA_8KB (BIT(30) | BIT(29)) +#define PN_RAMMAP_RAMAREA_4KB BIT(30) +#define PN_RAMMAP_RAMAREA_2KB BIT(29) +#define PN_RAMMAP_RAMAREA_1KB 0 +#define PN_RAMMAP_MPKT_SHIFT 16 +#define PN_RAMMAP_MPKT_MASK GENMASK(26, PN_RAMMAP_MPKT_SHIFT) +#define PN_RAMMAP_MPKT(n) (((n) << PN_RAMMAP_MPKT_SHIFT) & \ + PN_RAMMAP_MPKT_MASK) +#define PN_RAMMAP_RAMIF_SHIFT 14 +#define PN_RAMMAP_RAMIF_MASK GENMASK(15, PN_RAMMAP_RAMIF_SHIFT) +#define PN_RAMMAP_RAMIF(n) (((n) << PN_RAMMAP_RAMIF_SHIFT) & \ + PN_RAMMAP_RAMIF_MASK) +#define PN_RAMMAP_BASEAD_MASK GENMASK(13, 0) +#define PN_RAMMAP_BASEAD(offs) (((offs) >> 3) & PN_RAMMAP_BASEAD_MASK) +#define PN_RAMMAP_DATA(area, ramif, basead) ((PN_RAMMAP_##area) | \ + (PN_RAMMAP_RAMIF(ramif)) | \ + (PN_RAMMAP_BASEAD(basead))) + +/* PN_CON */ +#define PN_CON_EN BIT(31) +#define PN_CON_DATAIF_EN BIT(30) +#define PN_CON_RES_MASK (BIT(17) | BIT(16)) +#define PN_CON_RES_FORCE_STALL BIT(17) +#define PN_CON_RES_NORMAL BIT(16) +#define PN_CON_RES_FORCE_NRDY 0 +#define PN_CON_LAST BIT(11) +#define PN_CON_RES_WEN BIT(7) +#define PN_CON_CLR BIT(0) + +/* PN_INT_STA and PN_INT_ENA */ +#define PN_INT_LSTTR BIT(4) +#define PN_INT_BFRDY BIT(0) + +/* USB3_SSIFCMD */ +#define SSIFCMD_URES_U2 BIT(9) +#define SSIFCMD_URES_U1 BIT(8) +#define SSIFCMD_UDIR_U2 BIT(7) +#define SSIFCMD_UDIR_U1 BIT(6) +#define SSIFCMD_UREQ_U2 BIT(5) +#define SSIFCMD_UREQ_U1 BIT(4) + +#define USB3_EP0_SS_MAX_PACKET_SIZE 512 +#define USB3_EP0_HSFS_MAX_PACKET_SIZE 64 +#define USB3_EP0_BUF_SIZE 8 +#define USB3_MAX_NUM_PIPES 30 +#define USB3_WAIT_US 3 + +struct renesas_usb3; +struct renesas_usb3_request { + struct usb_request req; + struct list_head queue; +}; + +#define USB3_EP_NAME_SIZE 8 +struct renesas_usb3_ep { + struct usb_ep ep; + struct renesas_usb3 *usb3; + int num; + char ep_name[USB3_EP_NAME_SIZE]; + struct list_head queue; + u32 rammap_val; + bool dir_in; + bool halt; + bool wedge; + bool started; +}; + +struct renesas_usb3_priv { + int ramsize_per_ramif; /* unit = bytes */ + int num_ramif; + int ramsize_per_pipe; /* unit = bytes */ + bool workaround_for_vbus; /* if true, don't check vbus signal */ +}; + +struct renesas_usb3 { + void __iomem *reg; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct renesas_usb3_ep *usb3_ep; + int num_usb3_eps; + + spinlock_t lock; + int disabled_count; + + struct usb_request *ep0_req; + u16 test_mode; + u8 ep0_buf[USB3_EP0_BUF_SIZE]; + bool softconnect; + bool workaround_for_vbus; +}; + +#define gadget_to_renesas_usb3(_gadget) \ + container_of(_gadget, struct renesas_usb3, gadget) +#define renesas_usb3_to_gadget(renesas_usb3) (&renesas_usb3->gadget) +#define usb3_to_dev(_usb3) (_usb3->gadget.dev.parent) + +#define usb_ep_to_usb3_ep(_ep) container_of(_ep, struct renesas_usb3_ep, ep) +#define usb3_ep_to_usb3(_usb3_ep) (_usb3_ep->usb3) +#define usb_req_to_usb3_req(_req) container_of(_req, \ + struct renesas_usb3_request, req) + +#define usb3_get_ep(usb3, n) ((usb3)->usb3_ep + (n)) +#define usb3_for_each_ep(usb3_ep, usb3, i) \ + for ((i) = 0, usb3_ep = usb3_get_ep(usb3, (i)); \ + (i) < (usb3)->num_usb3_eps; \ + (i)++, usb3_ep = usb3_get_ep(usb3, (i))) + +static const char udc_name[] = "renesas_usb3"; + +static void usb3_write(struct renesas_usb3 *usb3, u32 data, u32 offs) +{ + iowrite32(data, usb3->reg + offs); +} + +static u32 usb3_read(struct renesas_usb3 *usb3, u32 offs) +{ + return ioread32(usb3->reg + offs); +} + +static void usb3_set_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_read(usb3, offs); + + val |= bits; + usb3_write(usb3, val, offs); +} + +static void usb3_clear_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_read(usb3, offs); + + val &= ~bits; + usb3_write(usb3, val, offs); +} + +static int usb3_wait(struct renesas_usb3 *usb3, u32 reg, u32 mask, + u32 expected) +{ + int i; + + for (i = 0; i < USB3_WAIT_US; i++) { + if ((usb3_read(usb3, reg) & mask) == expected) + return 0; + udelay(1); + } + + dev_dbg(usb3_to_dev(usb3), "%s: timed out (%8x, %08x, %08x)\n", + __func__, reg, mask, expected); + + return -EBUSY; +} + +static void usb3_enable_irq_1(struct renesas_usb3 *usb3, u32 bits) +{ + usb3_set_bit(usb3, bits, USB3_USB_INT_ENA_1); +} + +static void usb3_disable_irq_1(struct renesas_usb3 *usb3, u32 bits) +{ + usb3_clear_bit(usb3, bits, USB3_USB_INT_ENA_1); +} + +static void usb3_enable_pipe_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_set_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2); +} + +static void usb3_disable_pipe_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_clear_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2); +} + +static void usb3_init_axi_bridge(struct renesas_usb3 *usb3) +{ + /* Set AXI_INT */ + usb3_write(usb3, ~0, USB3_DMA_INT_STA); + usb3_write(usb3, 0, USB3_DMA_INT_ENA); + usb3_set_bit(usb3, AXI_INT_DMAINT | AXI_INT_EPCINT, USB3_AXI_INT_ENA); +} + +static void usb3_init_epc_registers(struct renesas_usb3 *usb3) +{ + /* FIXME: How to change host / peripheral mode as well? */ + usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON); + + usb3_write(usb3, ~0, USB3_USB_INT_STA_1); + usb3_enable_irq_1(usb3, USB_INT_1_VBUS_CNG); +} + +static bool usb3_wakeup_usb2_phy(struct renesas_usb3 *usb3) +{ + if (!(usb3_read(usb3, USB3_USB20_CON) & USB20_CON_B2_SUSPEND)) + return true; /* already waked it up */ + + usb3_clear_bit(usb3, USB20_CON_B2_SUSPEND, USB3_USB20_CON); + usb3_enable_irq_1(usb3, USB_INT_1_B2_RSUM); + + return false; +} + +static void usb3_usb2_pullup(struct renesas_usb3 *usb3, int pullup) +{ + u32 bits = USB20_CON_B2_PUE | USB20_CON_B2_CONNECT; + + if (usb3->softconnect && pullup) + usb3_set_bit(usb3, bits, USB3_USB20_CON); + else + usb3_clear_bit(usb3, bits, USB3_USB20_CON); +} + +static void usb3_set_test_mode(struct renesas_usb3 *usb3) +{ + u32 val = usb3_read(usb3, USB3_USB20_CON); + + val &= ~USB20_CON_B2_TSTMOD_MASK; + val |= USB20_CON_B2_TSTMOD(usb3->test_mode); + usb3_write(usb3, val | USB20_CON_B2_TSTMOD_EN, USB3_USB20_CON); + if (!usb3->test_mode) + usb3_clear_bit(usb3, USB20_CON_B2_TSTMOD_EN, USB3_USB20_CON); +} + +static void usb3_start_usb2_connection(struct renesas_usb3 *usb3) +{ + usb3->disabled_count++; + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB_COM_CON_SPD_MODE, USB3_USB_COM_CON); + usb3_usb2_pullup(usb3, 1); +} + +static int usb3_is_usb3_phy_in_u3(struct renesas_usb3 *usb3) +{ + return usb3_read(usb3, USB3_USB30_CON) & USB30_CON_POW_SEL_IN_U3; +} + +static bool usb3_wakeup_usb3_phy(struct renesas_usb3 *usb3) +{ + if (!usb3_is_usb3_phy_in_u3(usb3)) + return true; /* already waked it up */ + + usb3_set_bit(usb3, USB30_CON_B3_PLLWAKE, USB3_USB30_CON); + usb3_enable_irq_1(usb3, USB_INT_1_B3_PLLWKUP); + + return false; +} + +static u16 usb3_feature_get_un_enabled(struct renesas_usb3 *usb3) +{ + u32 mask_u2 = SSIFCMD_UDIR_U2 | SSIFCMD_UREQ_U2; + u32 mask_u1 = SSIFCMD_UDIR_U1 | SSIFCMD_UREQ_U1; + u32 val = usb3_read(usb3, USB3_SSIFCMD); + u16 ret = 0; + + /* Enables {U2,U1} if the bits of UDIR and UREQ are set to 0 */ + if (!(val & mask_u2)) + ret |= 1 << USB_DEV_STAT_U2_ENABLED; + if (!(val & mask_u1)) + ret |= 1 << USB_DEV_STAT_U1_ENABLED; + + return ret; +} + +static void usb3_feature_u2_enable(struct renesas_usb3 *usb3, bool enable) +{ + u32 bits = SSIFCMD_UDIR_U2 | SSIFCMD_UREQ_U2; + + /* Enables U2 if the bits of UDIR and UREQ are set to 0 */ + if (enable) + usb3_clear_bit(usb3, bits, USB3_SSIFCMD); + else + usb3_set_bit(usb3, bits, USB3_SSIFCMD); +} + +static void usb3_feature_u1_enable(struct renesas_usb3 *usb3, bool enable) +{ + u32 bits = SSIFCMD_UDIR_U1 | SSIFCMD_UREQ_U1; + + /* Enables U1 if the bits of UDIR and UREQ are set to 0 */ + if (enable) + usb3_clear_bit(usb3, bits, USB3_SSIFCMD); + else + usb3_set_bit(usb3, bits, USB3_SSIFCMD); +} + +static void usb3_start_operation_for_usb3(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_clear_bit(usb3, USB_COM_CON_SPD_MODE, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); +} + +static void usb3_start_usb3_connection(struct renesas_usb3 *usb3) +{ + usb3_start_operation_for_usb3(usb3); + usb3_set_bit(usb3, USB_COM_CON_RX_DETECTION, USB3_USB_COM_CON); + + usb3_enable_irq_1(usb3, USB_INT_1_B3_LUPSUCS | USB_INT_1_B3_DISABLE | + USB_INT_1_SPEED); +} + +static void usb3_stop_usb3_connection(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); +} + +static void usb3_transition_to_default_state(struct renesas_usb3 *usb3, + bool is_usb3) +{ + usb3_set_bit(usb3, USB_INT_2_PIPE(0), USB3_USB_INT_ENA_2); + usb3_write(usb3, P0_INT_ALL_BITS, USB3_P0_INT_STA); + usb3_set_bit(usb3, P0_INT_ALL_BITS, USB3_P0_INT_ENA); + + if (is_usb3) + usb3_enable_irq_1(usb3, USB_INT_1_B3_WRMRST | + USB_INT_1_B3_HOTRST); + else + usb3_enable_irq_1(usb3, USB_INT_1_B2_SPND | + USB_INT_1_B2_L1SPND | USB_INT_1_B2_USBRST); +} + +static void usb3_connect(struct renesas_usb3 *usb3) +{ + if (usb3_wakeup_usb3_phy(usb3)) + usb3_start_usb3_connection(usb3); +} + +static void usb3_reset_epc(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + usb3_clear_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB_COM_CON_PIPE_CLR, USB3_USB_COM_CON); + usb3->test_mode = 0; + usb3_set_test_mode(usb3); +} + +static void usb3_disconnect(struct renesas_usb3 *usb3) +{ + usb3->disabled_count = 0; + usb3_usb2_pullup(usb3, 0); + usb3_clear_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); + usb3_reset_epc(usb3); + + if (usb3->driver) + usb3->driver->disconnect(&usb3->gadget); +} + +static void usb3_check_vbus(struct renesas_usb3 *usb3) +{ + if (usb3->workaround_for_vbus) { + usb3_connect(usb3); + } else { + if (usb3_read(usb3, USB3_USB_STA) & USB_STA_VBUS_STA) + usb3_connect(usb3); + else + usb3_disconnect(usb3); + } +} + +static void renesas_usb3_init_controller(struct renesas_usb3 *usb3) +{ + usb3_init_axi_bridge(usb3); + usb3_init_epc_registers(usb3); + + usb3_check_vbus(usb3); +} + +static void renesas_usb3_stop_controller(struct renesas_usb3 *usb3) +{ + usb3_disconnect(usb3); + usb3_write(usb3, 0, USB3_P0_INT_ENA); + usb3_write(usb3, 0, USB3_PN_INT_ENA); + usb3_write(usb3, 0, USB3_USB_INT_ENA_1); + usb3_write(usb3, 0, USB3_USB_INT_ENA_2); + usb3_write(usb3, 0, USB3_AXI_INT_ENA); +} + +static void usb3_irq_epc_int_1_pll_wakeup(struct renesas_usb3 *usb3) +{ + usb3_disable_irq_1(usb3, USB_INT_1_B3_PLLWKUP); + usb3_clear_bit(usb3, USB30_CON_B3_PLLWAKE, USB3_USB30_CON); + usb3_start_usb3_connection(usb3); +} + +static void usb3_irq_epc_int_1_linkup_success(struct renesas_usb3 *usb3) +{ + usb3_transition_to_default_state(usb3, true); +} + +static void usb3_irq_epc_int_1_resume(struct renesas_usb3 *usb3) +{ + usb3_disable_irq_1(usb3, USB_INT_1_B2_RSUM); + usb3_start_usb2_connection(usb3); + usb3_transition_to_default_state(usb3, false); +} + +static void usb3_irq_epc_int_1_disable(struct renesas_usb3 *usb3) +{ + usb3_stop_usb3_connection(usb3); + if (usb3_wakeup_usb2_phy(usb3)) + usb3_irq_epc_int_1_resume(usb3); +} + +static void usb3_irq_epc_int_1_bus_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + if (usb3->disabled_count < 3) + usb3_start_usb3_connection(usb3); + else + usb3_start_usb2_connection(usb3); +} + +static void usb3_irq_epc_int_1_vbus_change(struct renesas_usb3 *usb3) +{ + usb3_check_vbus(usb3); +} + +static void usb3_irq_epc_int_1_hot_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + + /* This bit shall be set within 12ms from the start of HotReset */ + usb3_set_bit(usb3, USB30_CON_B3_HOTRST_CMP, USB3_USB30_CON); +} + +static void usb3_irq_epc_int_1_warm_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + + usb3_start_operation_for_usb3(usb3); + usb3_enable_irq_1(usb3, USB_INT_1_SPEED); +} + +static void usb3_irq_epc_int_1_speed(struct renesas_usb3 *usb3) +{ + u32 speed = usb3_read(usb3, USB3_USB_STA) & USB_STA_SPEED_MASK; + + switch (speed) { + case USB_STA_SPEED_SS: + usb3->gadget.speed = USB_SPEED_SUPER; + break; + case USB_STA_SPEED_HS: + usb3->gadget.speed = USB_SPEED_HIGH; + break; + case USB_STA_SPEED_FS: + usb3->gadget.speed = USB_SPEED_FULL; + break; + default: + usb3->gadget.speed = USB_SPEED_UNKNOWN; + break; + } +} + +static void usb3_irq_epc_int_1(struct renesas_usb3 *usb3, u32 int_sta_1) +{ + if (int_sta_1 & USB_INT_1_B3_PLLWKUP) + usb3_irq_epc_int_1_pll_wakeup(usb3); + + if (int_sta_1 & USB_INT_1_B3_LUPSUCS) + usb3_irq_epc_int_1_linkup_success(usb3); + + if (int_sta_1 & USB_INT_1_B3_HOTRST) + usb3_irq_epc_int_1_hot_reset(usb3); + + if (int_sta_1 & USB_INT_1_B3_WRMRST) + usb3_irq_epc_int_1_warm_reset(usb3); + + if (int_sta_1 & USB_INT_1_B3_DISABLE) + usb3_irq_epc_int_1_disable(usb3); + + if (int_sta_1 & USB_INT_1_B2_USBRST) + usb3_irq_epc_int_1_bus_reset(usb3); + + if (int_sta_1 & USB_INT_1_B2_RSUM) + usb3_irq_epc_int_1_resume(usb3); + + if (int_sta_1 & USB_INT_1_SPEED) + usb3_irq_epc_int_1_speed(usb3); + + if (int_sta_1 & USB_INT_1_VBUS_CNG) + usb3_irq_epc_int_1_vbus_change(usb3); +} + +static struct renesas_usb3_request *__usb3_get_request(struct renesas_usb3_ep + *usb3_ep) +{ + return list_first_entry_or_null(&usb3_ep->queue, + struct renesas_usb3_request, queue); +} + +static struct renesas_usb3_request *usb3_get_request(struct renesas_usb3_ep + *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_request *usb3_req; + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + usb3_req = __usb3_get_request(usb3_ep); + spin_unlock_irqrestore(&usb3->lock, flags); + + return usb3_req; +} + +static void usb3_request_done(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, int status) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + dev_dbg(usb3_to_dev(usb3), "giveback: ep%2d, %u, %u, %d\n", + usb3_ep->num, usb3_req->req.length, usb3_req->req.actual, + status); + usb3_req->req.status = status; + spin_lock_irqsave(&usb3->lock, flags); + usb3_ep->started = false; + list_del_init(&usb3_req->queue); + spin_unlock_irqrestore(&usb3->lock, flags); + usb_gadget_giveback_request(&usb3_ep->ep, &usb3_req->req); +} + +static void usb3_irq_epc_pipe0_status_end(struct renesas_usb3 *usb3) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (usb3_req) + usb3_request_done(usb3_ep, usb3_req, 0); + if (usb3->test_mode) + usb3_set_test_mode(usb3); +} + +static void usb3_get_setup_data(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + u32 *data = (u32 *)ctrl; + + *data++ = usb3_read(usb3, USB3_STUP_DAT_0); + *data = usb3_read(usb3, USB3_STUP_DAT_1); + + /* update this driver's flag */ + usb3_ep->dir_in = !!(ctrl->bRequestType & USB_DIR_IN); +} + +static void usb3_set_p0_con_update_res(struct renesas_usb3 *usb3, u32 res) +{ + u32 val = usb3_read(usb3, USB3_P0_CON); + + val &= ~(P0_CON_ST_RES_MASK | P0_CON_OT_RES_MASK | P0_CON_IN_RES_MASK); + val |= res | P0_CON_RES_WEN; + usb3_write(usb3, val, USB3_P0_CON); +} + +static void usb3_set_p0_con_for_ctrl_read_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_NORMAL); +} + +static void usb3_set_p0_con_for_ctrl_read_status(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_NORMAL); +} + +static void usb3_set_p0_con_for_ctrl_write_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_NORMAL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_for_ctrl_write_status(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_NORMAL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_for_no_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_stall(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_STALL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_stop(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_FORCE_NRDY | + P0_CON_IN_RES_FORCE_NRDY); +} + +static int usb3_pn_change(struct renesas_usb3 *usb3, int num) +{ + if (num == 0 || num > usb3->num_usb3_eps) + return -ENXIO; + + usb3_write(usb3, num, USB3_PIPE_COM); + + return 0; +} + +static void usb3_set_pn_con_update_res(struct renesas_usb3 *usb3, u32 res) +{ + u32 val = usb3_read(usb3, USB3_PN_CON); + + val &= ~PN_CON_RES_MASK; + val |= res & PN_CON_RES_MASK; + val |= PN_CON_RES_WEN; + usb3_write(usb3, val, USB3_PN_CON); +} + +static void usb3_pn_start(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_NORMAL); +} + +static void usb3_pn_stop(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_FORCE_NRDY); +} + +static void usb3_pn_stall(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_FORCE_STALL); +} + +static int usb3_pn_con_clear(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, PN_CON_CLR, USB3_PN_CON); + + return usb3_wait(usb3, USB3_PN_CON, PN_CON_CLR, 0); +} + +static bool usb3_is_transfer_complete(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct usb_request *req = &usb3_req->req; + + if ((!req->zero && req->actual == req->length) || + (req->actual % usb3_ep->ep.maxpacket) || (req->length == 0)) + return true; + else + return false; +} + +static int usb3_wait_pipe_status(struct renesas_usb3_ep *usb3_ep, u32 mask) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 sta_reg = usb3_ep->num ? USB3_PN_STA : USB3_P0_STA; + + return usb3_wait(usb3, sta_reg, mask, mask); +} + +static void usb3_set_px_con_send(struct renesas_usb3_ep *usb3_ep, int bytes, + bool last) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 con_reg = usb3_ep->num ? USB3_PN_CON : USB3_P0_CON; + u32 val = usb3_read(usb3, con_reg); + + val |= PX_CON_SEND | PX_CON_BYTE_EN_BYTES(bytes); + val |= (usb3_ep->num && last) ? PN_CON_LAST : 0; + usb3_write(usb3, val, con_reg); +} + +static int usb3_write_pipe(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, + u32 fifo_reg) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + int len = min_t(unsigned, usb3_req->req.length - usb3_req->req.actual, + usb3_ep->ep.maxpacket); + u8 *buf = usb3_req->req.buf + usb3_req->req.actual; + u32 tmp = 0; + bool is_last; + + if (usb3_wait_pipe_status(usb3_ep, PX_STA_BUFSTS) < 0) + return -EBUSY; + + /* Update gadget driver parameter */ + usb3_req->req.actual += len; + + /* Write data to the register */ + if (len >= 4) { + iowrite32_rep(usb3->reg + fifo_reg, buf, len / 4); + buf += (len / 4) * 4; + len %= 4; /* update len to use usb3_set_pX_con_send() */ + } + + if (len) { + for (i = 0; i < len; i++) + tmp |= buf[i] << (8 * i); + usb3_write(usb3, tmp, fifo_reg); + } + + is_last = usb3_is_transfer_complete(usb3_ep, usb3_req); + /* Send the data */ + usb3_set_px_con_send(usb3_ep, len, is_last); + + return is_last ? 0 : -EAGAIN; +} + +static u32 usb3_get_received_length(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 lng_reg = usb3_ep->num ? USB3_PN_LNG : USB3_P0_LNG; + + return usb3_read(usb3, lng_reg); +} + +static int usb3_read_pipe(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, u32 fifo_reg) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + int len = min_t(unsigned, usb3_req->req.length - usb3_req->req.actual, + usb3_get_received_length(usb3_ep)); + u8 *buf = usb3_req->req.buf + usb3_req->req.actual; + u32 tmp = 0; + + if (!len) + return 0; + + /* Update gadget driver parameter */ + usb3_req->req.actual += len; + + /* Read data from the register */ + if (len >= 4) { + ioread32_rep(usb3->reg + fifo_reg, buf, len / 4); + buf += (len / 4) * 4; + len %= 4; + } + + if (len) { + tmp = usb3_read(usb3, fifo_reg); + for (i = 0; i < len; i++) + buf[i] = (tmp >> (8 * i)) & 0xff; + } + + return usb3_is_transfer_complete(usb3_ep, usb3_req) ? 0 : -EAGAIN; +} + +static void usb3_set_status_stage(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (usb3_ep->dir_in) { + usb3_set_p0_con_for_ctrl_read_status(usb3); + } else { + if (!usb3_req->req.length) + usb3_set_p0_con_for_no_data(usb3); + else + usb3_set_p0_con_for_ctrl_write_status(usb3); + } +} + +static void usb3_p0_xfer(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + int ret = -EAGAIN; + + if (usb3_ep->dir_in) + ret = usb3_write_pipe(usb3_ep, usb3_req, USB3_P0_WRITE); + else + ret = usb3_read_pipe(usb3_ep, usb3_req, USB3_P0_READ); + + if (!ret) + usb3_set_status_stage(usb3_ep, usb3_req); +} + +static void usb3_start_pipe0(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (usb3_ep->started) + return; + + usb3_ep->started = true; + + if (usb3_ep->dir_in) { + usb3_set_bit(usb3, P0_MOD_DIR, USB3_P0_MOD); + usb3_set_p0_con_for_ctrl_read_data(usb3); + } else { + usb3_clear_bit(usb3, P0_MOD_DIR, USB3_P0_MOD); + usb3_set_p0_con_for_ctrl_write_data(usb3); + } + + usb3_p0_xfer(usb3_ep, usb3_req); +} + +static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_request *usb3_req_first = usb3_get_request(usb3_ep); + unsigned long flags; + int ret = -EAGAIN; + u32 enable_bits = 0; + + if (usb3_ep->halt || usb3_ep->started) + return; + if (usb3_req != usb3_req_first) + return; + + spin_lock_irqsave(&usb3->lock, flags); + if (usb3_pn_change(usb3, usb3_ep->num) < 0) + goto out; + + usb3_ep->started = true; + usb3_pn_start(usb3); + + if (usb3_ep->dir_in) { + ret = usb3_write_pipe(usb3_ep, usb3_req, USB3_PN_WRITE); + enable_bits |= PN_INT_LSTTR; + } + + if (ret < 0) + enable_bits |= PN_INT_BFRDY; + + if (enable_bits) { + usb3_set_bit(usb3, enable_bits, USB3_PN_INT_ENA); + usb3_enable_pipe_irq(usb3, usb3_ep->num); + } +out: + spin_unlock_irqrestore(&usb3->lock, flags); +} + +static int renesas_usb3_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + dev_dbg(usb3_to_dev(usb3), "ep_queue: ep%2d, %u\n", usb3_ep->num, + _req->length); + + _req->status = -EINPROGRESS; + _req->actual = 0; + spin_lock_irqsave(&usb3->lock, flags); + list_add_tail(&usb3_req->queue, &usb3_ep->queue); + spin_unlock_irqrestore(&usb3->lock, flags); + + if (!usb3_ep->num) + usb3_start_pipe0(usb3_ep, usb3_req); + else + usb3_start_pipen(usb3_ep, usb3_req); + + return 0; +} + +static void usb3_set_device_address(struct renesas_usb3 *usb3, u16 addr) +{ + /* DEV_ADDR bit field is cleared by WarmReset, HotReset and BusReset */ + usb3_set_bit(usb3, USB_COM_CON_DEV_ADDR(addr), USB3_USB_COM_CON); +} + +static bool usb3_std_req_set_address(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + if (ctrl->wValue >= 128) + return true; /* stall */ + + usb3_set_device_address(usb3, ctrl->wValue); + usb3_set_p0_con_for_no_data(usb3); + + return false; +} + +static void usb3_pipe0_internal_xfer(struct renesas_usb3 *usb3, + void *tx_data, size_t len, + void (*complete)(struct usb_ep *ep, + struct usb_request *req)) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + + if (tx_data) + memcpy(usb3->ep0_buf, tx_data, + min_t(size_t, len, USB3_EP0_BUF_SIZE)); + + usb3->ep0_req->buf = &usb3->ep0_buf; + usb3->ep0_req->length = len; + usb3->ep0_req->complete = complete; + renesas_usb3_ep_queue(&usb3_ep->ep, usb3->ep0_req, GFP_ATOMIC); +} + +static void usb3_pipe0_get_status_completion(struct usb_ep *ep, + struct usb_request *req) +{ +} + +static bool usb3_std_req_get_status(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + bool stall = false; + struct renesas_usb3_ep *usb3_ep; + int num; + u16 status = 0; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + if (usb3->gadget.is_selfpowered) + status |= 1 << USB_DEVICE_SELF_POWERED; + if (usb3->gadget.speed == USB_SPEED_SUPER) + status |= usb3_feature_get_un_enabled(usb3); + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + num = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + usb3_ep = usb3_get_ep(usb3, num); + if (usb3_ep->halt) + status |= 1 << USB_ENDPOINT_HALT; + break; + default: + stall = true; + break; + } + + if (!stall) { + status = cpu_to_le16(status); + dev_dbg(usb3_to_dev(usb3), "get_status: req = %p\n", + usb_req_to_usb3_req(usb3->ep0_req)); + usb3_pipe0_internal_xfer(usb3, &status, sizeof(status), + usb3_pipe0_get_status_completion); + } + + return stall; +} + +static bool usb3_std_req_feature_device(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, bool set) +{ + bool stall = true; + u16 w_value = le16_to_cpu(ctrl->wValue); + + switch (w_value) { + case USB_DEVICE_TEST_MODE: + if (!set) + break; + usb3->test_mode = le16_to_cpu(ctrl->wIndex) >> 8; + stall = false; + break; + case USB_DEVICE_U1_ENABLE: + case USB_DEVICE_U2_ENABLE: + if (usb3->gadget.speed != USB_SPEED_SUPER) + break; + if (w_value == USB_DEVICE_U1_ENABLE) + usb3_feature_u1_enable(usb3, set); + if (w_value == USB_DEVICE_U2_ENABLE) + usb3_feature_u2_enable(usb3, set); + stall = false; + break; + default: + break; + } + + return stall; +} + +static int usb3_set_halt_p0(struct renesas_usb3_ep *usb3_ep, bool halt) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (unlikely(usb3_ep->num)) + return -EINVAL; + + usb3_ep->halt = halt; + if (halt) + usb3_set_p0_con_stall(usb3); + else + usb3_set_p0_con_stop(usb3); + + return 0; +} + +static int usb3_set_halt_pn(struct renesas_usb3_ep *usb3_ep, bool halt, + bool is_clear_feature) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_ep->halt = halt; + if (halt) { + usb3_pn_stall(usb3); + } else if (!is_clear_feature || !usb3_ep->wedge) { + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + usb3_pn_stop(usb3); + } + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int usb3_set_halt(struct renesas_usb3_ep *usb3_ep, bool halt, + bool is_clear_feature) +{ + int ret = 0; + + if (halt && usb3_ep->started) + return -EAGAIN; + + if (usb3_ep->num) + ret = usb3_set_halt_pn(usb3_ep, halt, is_clear_feature); + else + ret = usb3_set_halt_p0(usb3_ep, halt); + + return ret; +} + +static bool usb3_std_req_feature_endpoint(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, + bool set) +{ + int num = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + struct renesas_usb3_ep *usb3_ep; + struct renesas_usb3_request *usb3_req; + + if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) + return true; /* stall */ + + usb3_ep = usb3_get_ep(usb3, num); + usb3_set_halt(usb3_ep, set, true); + + /* Restarts a queue if clear feature */ + if (!set) { + usb3_ep->started = false; + usb3_req = usb3_get_request(usb3_ep); + if (usb3_req) + usb3_start_pipen(usb3_ep, usb3_req); + } + + return false; +} + +static bool usb3_std_req_feature(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, bool set) +{ + bool stall = false; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + stall = usb3_std_req_feature_device(usb3, ctrl, set); + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + stall = usb3_std_req_feature_endpoint(usb3, ctrl, set); + break; + default: + stall = true; + break; + } + + if (!stall) + usb3_set_p0_con_for_no_data(usb3); + + return stall; +} + +static void usb3_pipe0_set_sel_completion(struct usb_ep *ep, + struct usb_request *req) +{ + /* TODO */ +} + +static bool usb3_std_req_set_sel(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + u16 w_length = le16_to_cpu(ctrl->wLength); + + if (w_length != 6) + return true; /* stall */ + + dev_dbg(usb3_to_dev(usb3), "set_sel: req = %p\n", + usb_req_to_usb3_req(usb3->ep0_req)); + usb3_pipe0_internal_xfer(usb3, NULL, 6, usb3_pipe0_set_sel_completion); + + return false; +} + +static bool usb3_std_req_set_configuration(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + if (ctrl->wValue > 0) + usb3_set_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + else + usb3_clear_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + + return false; +} + +/** + * usb3_handle_standard_request - handle some standard requests + * @usb3: the renesas_usb3 pointer + * @ctrl: a pointer of setup data + * + * Returns true if this function handled a standard request + */ +static bool usb3_handle_standard_request(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + bool ret = false; + bool stall = false; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + stall = usb3_std_req_set_address(usb3, ctrl); + ret = true; + break; + case USB_REQ_GET_STATUS: + stall = usb3_std_req_get_status(usb3, ctrl); + ret = true; + break; + case USB_REQ_CLEAR_FEATURE: + stall = usb3_std_req_feature(usb3, ctrl, false); + ret = true; + break; + case USB_REQ_SET_FEATURE: + stall = usb3_std_req_feature(usb3, ctrl, true); + ret = true; + break; + case USB_REQ_SET_SEL: + stall = usb3_std_req_set_sel(usb3, ctrl); + ret = true; + break; + case USB_REQ_SET_ISOCH_DELAY: + /* This hardware doesn't support Isochronous xfer */ + stall = true; + ret = true; + break; + case USB_REQ_SET_CONFIGURATION: + usb3_std_req_set_configuration(usb3, ctrl); + break; + default: + break; + } + } + + if (stall) + usb3_set_p0_con_stall(usb3); + + return ret; +} + +static int usb3_p0_con_clear_buffer(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, P0_CON_BCLR, USB3_P0_CON); + + return usb3_wait(usb3, USB3_P0_CON, P0_CON_BCLR, 0); +} + +static void usb3_irq_epc_pipe0_setup(struct renesas_usb3 *usb3) +{ + struct usb_ctrlrequest ctrl; + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + + /* Call giveback function if previous transfer is not completed */ + if (usb3_ep->started) + usb3_request_done(usb3_ep, usb3_get_request(usb3_ep), + -ECONNRESET); + + usb3_p0_con_clear_buffer(usb3); + usb3_get_setup_data(usb3, &ctrl); + if (!usb3_handle_standard_request(usb3, &ctrl)) + if (usb3->driver->setup(&usb3->gadget, &ctrl) < 0) + usb3_set_p0_con_stall(usb3); +} + +static void usb3_irq_epc_pipe0_bfrdy(struct renesas_usb3 *usb3) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + usb3_p0_xfer(usb3_ep, usb3_req); +} + +static void usb3_irq_epc_pipe0(struct renesas_usb3 *usb3) +{ + u32 p0_int_sta = usb3_read(usb3, USB3_P0_INT_STA); + + p0_int_sta &= usb3_read(usb3, USB3_P0_INT_ENA); + usb3_write(usb3, p0_int_sta, USB3_P0_INT_STA); + if (p0_int_sta & P0_INT_STSED) + usb3_irq_epc_pipe0_status_end(usb3); + if (p0_int_sta & P0_INT_SETUP) + usb3_irq_epc_pipe0_setup(usb3); + if (p0_int_sta & P0_INT_BFRDY) + usb3_irq_epc_pipe0_bfrdy(usb3); +} + +static void usb3_request_done_pipen(struct renesas_usb3 *usb3, + struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, + int status) +{ + usb3_pn_stop(usb3); + usb3_disable_pipe_irq(usb3, usb3_ep->num); + usb3_request_done(usb3_ep, usb3_req, status); + + /* get next usb3_req */ + usb3_req = usb3_get_request(usb3_ep); + if (usb3_req) + usb3_start_pipen(usb3_ep, usb3_req); +} + +static void usb3_irq_epc_pipen_lsttr(struct renesas_usb3 *usb3, int num) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, num); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + if (usb3_ep->dir_in) { + dev_dbg(usb3_to_dev(usb3), "%s: len = %u, actual = %u\n", + __func__, usb3_req->req.length, usb3_req->req.actual); + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, 0); + } +} + +static void usb3_irq_epc_pipen_bfrdy(struct renesas_usb3 *usb3, int num) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, num); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + if (usb3_ep->dir_in) { + /* Do not stop the IN pipe here to detect LSTTR interrupt */ + if (!usb3_write_pipe(usb3_ep, usb3_req, USB3_PN_WRITE)) + usb3_clear_bit(usb3, PN_INT_BFRDY, USB3_PN_INT_ENA); + } else { + if (!usb3_read_pipe(usb3_ep, usb3_req, USB3_PN_READ)) + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, 0); + } +} + +static void usb3_irq_epc_pipen(struct renesas_usb3 *usb3, int num) +{ + u32 pn_int_sta; + + if (usb3_pn_change(usb3, num) < 0) + return; + + pn_int_sta = usb3_read(usb3, USB3_PN_INT_STA); + pn_int_sta &= usb3_read(usb3, USB3_PN_INT_ENA); + usb3_write(usb3, pn_int_sta, USB3_PN_INT_STA); + if (pn_int_sta & PN_INT_LSTTR) + usb3_irq_epc_pipen_lsttr(usb3, num); + if (pn_int_sta & PN_INT_BFRDY) + usb3_irq_epc_pipen_bfrdy(usb3, num); +} + +static void usb3_irq_epc_int_2(struct renesas_usb3 *usb3, u32 int_sta_2) +{ + int i; + + for (i = 0; i < usb3->num_usb3_eps; i++) { + if (int_sta_2 & USB_INT_2_PIPE(i)) { + if (!i) + usb3_irq_epc_pipe0(usb3); + else + usb3_irq_epc_pipen(usb3, i); + } + } +} + +static void usb3_irq_epc(struct renesas_usb3 *usb3) +{ + u32 int_sta_1 = usb3_read(usb3, USB3_USB_INT_STA_1); + u32 int_sta_2 = usb3_read(usb3, USB3_USB_INT_STA_2); + + int_sta_1 &= usb3_read(usb3, USB3_USB_INT_ENA_1); + if (int_sta_1) { + usb3_write(usb3, int_sta_1, USB3_USB_INT_STA_1); + usb3_irq_epc_int_1(usb3, int_sta_1); + } + + int_sta_2 &= usb3_read(usb3, USB3_USB_INT_ENA_2); + if (int_sta_2) + usb3_irq_epc_int_2(usb3, int_sta_2); +} + +static irqreturn_t renesas_usb3_irq(int irq, void *_usb3) +{ + struct renesas_usb3 *usb3 = _usb3; + irqreturn_t ret = IRQ_NONE; + u32 axi_int_sta = usb3_read(usb3, USB3_AXI_INT_STA); + + if (axi_int_sta & AXI_INT_EPCINT) { + usb3_irq_epc(usb3); + ret = IRQ_HANDLED; + } + + return ret; +} + +static void usb3_write_pn_mod(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 val = 0; + + val |= usb3_ep->dir_in ? PN_MOD_DIR : 0; + val |= PN_MOD_TYPE(usb_endpoint_type(desc)); + val |= PN_MOD_EPNUM(usb_endpoint_num(desc)); + usb3_write(usb3, val, USB3_PN_MOD); +} + +static u32 usb3_calc_ramarea(int ram_size) +{ + WARN_ON(ram_size > SZ_16K); + + if (ram_size <= SZ_1K) + return PN_RAMMAP_RAMAREA_1KB; + else if (ram_size <= SZ_2K) + return PN_RAMMAP_RAMAREA_2KB; + else if (ram_size <= SZ_4K) + return PN_RAMMAP_RAMAREA_4KB; + else if (ram_size <= SZ_8K) + return PN_RAMMAP_RAMAREA_8KB; + else + return PN_RAMMAP_RAMAREA_16KB; +} + +static u32 usb3_calc_rammap_val(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + return usb3_ep->rammap_val | PN_RAMMAP_MPKT(usb_endpoint_maxp(desc)); +} + +static int usb3_enable_pipe_n(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + usb3_ep->dir_in = usb_endpoint_dir_in(desc); + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_write_pn_mod(usb3_ep, desc); + usb3_write(usb3, usb3_calc_rammap_val(usb3_ep, desc), + USB3_PN_RAMMAP); + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int usb3_disable_pipe_n(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + usb3_ep->halt = false; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_write(usb3, 0, USB3_PN_RAMMAP); + usb3_clear_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +/*------- usb_ep_ops -----------------------------------------------------*/ +static int renesas_usb3_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + + return usb3_enable_pipe_n(usb3_ep, desc); +} + +static int renesas_usb3_ep_disable(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req; + + do { + usb3_req = usb3_get_request(usb3_ep); + if (!usb3_req) + break; + usb3_request_done(usb3_ep, usb3_req, -ESHUTDOWN); + } while (1); + + return usb3_disable_pipe_n(usb3_ep); +} + +static struct usb_request *__renesas_usb3_ep_alloc_request(gfp_t gfp_flags) +{ + struct renesas_usb3_request *usb3_req; + + usb3_req = kzalloc(sizeof(struct renesas_usb3_request), gfp_flags); + if (!usb3_req) + return NULL; + + INIT_LIST_HEAD(&usb3_req->queue); + + return &usb3_req->req; +} + +static void __renesas_usb3_ep_free_request(struct usb_request *_req) +{ + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + + kfree(usb3_req); +} + +static struct usb_request *renesas_usb3_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + return __renesas_usb3_ep_alloc_request(gfp_flags); +} + +static void renesas_usb3_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + __renesas_usb3_ep_free_request(_req); +} + +static int renesas_usb3_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + dev_dbg(usb3_to_dev(usb3), "ep_dequeue: ep%2d, %u\n", usb3_ep->num, + _req->length); + + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, -ECONNRESET); + + return 0; +} + +static int renesas_usb3_ep_set_halt(struct usb_ep *_ep, int value) +{ + return usb3_set_halt(usb_ep_to_usb3_ep(_ep), !!value, false); +} + +static int renesas_usb3_ep_set_wedge(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + + usb3_ep->wedge = true; + return usb3_set_halt(usb3_ep, true, false); +} + +static void renesas_usb3_ep_fifo_flush(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + if (usb3_ep->num) { + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + } else { + usb3_p0_con_clear_buffer(usb3); + } +} + +static struct usb_ep_ops renesas_usb3_ep_ops = { + .enable = renesas_usb3_ep_enable, + .disable = renesas_usb3_ep_disable, + + .alloc_request = renesas_usb3_ep_alloc_request, + .free_request = renesas_usb3_ep_free_request, + + .queue = renesas_usb3_ep_queue, + .dequeue = renesas_usb3_ep_dequeue, + + .set_halt = renesas_usb3_ep_set_halt, + .set_wedge = renesas_usb3_ep_set_wedge, + .fifo_flush = renesas_usb3_ep_fifo_flush, +}; + +/*------- usb_gadget_ops -------------------------------------------------*/ +static int renesas_usb3_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct renesas_usb3 *usb3; + + if (!driver || driver->max_speed < USB_SPEED_FULL || + !driver->setup) + return -EINVAL; + + usb3 = gadget_to_renesas_usb3(gadget); + + /* hook up the driver */ + usb3->driver = driver; + + renesas_usb3_init_controller(usb3); + + return 0; +} + +static int renesas_usb3_stop(struct usb_gadget *gadget) +{ + struct renesas_usb3 *usb3 = gadget_to_renesas_usb3(gadget); + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + usb3->softconnect = false; + usb3->gadget.speed = USB_SPEED_UNKNOWN; + usb3->driver = NULL; + renesas_usb3_stop_controller(usb3); + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int renesas_usb3_get_frame(struct usb_gadget *_gadget) +{ + return -EOPNOTSUPP; +} + +static int renesas_usb3_pullup(struct usb_gadget *gadget, int is_on) +{ + struct renesas_usb3 *usb3 = gadget_to_renesas_usb3(gadget); + + usb3->softconnect = !!is_on; + + return 0; +} + +static int renesas_usb3_set_selfpowered(struct usb_gadget *gadget, int is_self) +{ + gadget->is_selfpowered = !!is_self; + + return 0; +} + +static const struct usb_gadget_ops renesas_usb3_gadget_ops = { + .get_frame = renesas_usb3_get_frame, + .udc_start = renesas_usb3_start, + .udc_stop = renesas_usb3_stop, + .pullup = renesas_usb3_pullup, + .set_selfpowered = renesas_usb3_set_selfpowered, +}; + +/*------- platform_driver ------------------------------------------------*/ +static int renesas_usb3_remove(struct platform_device *pdev) +{ + struct renesas_usb3 *usb3 = platform_get_drvdata(pdev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + usb_del_gadget_udc(&usb3->gadget); + + __renesas_usb3_ep_free_request(usb3->ep0_req); + + return 0; +} + +static int renesas_usb3_init_ep(struct renesas_usb3 *usb3, struct device *dev, + const struct renesas_usb3_priv *priv) +{ + struct renesas_usb3_ep *usb3_ep; + int i; + + /* calculate num_usb3_eps from renesas_usb3_priv */ + usb3->num_usb3_eps = priv->ramsize_per_ramif * priv->num_ramif * 2 / + priv->ramsize_per_pipe + 1; + + if (usb3->num_usb3_eps > USB3_MAX_NUM_PIPES) + usb3->num_usb3_eps = USB3_MAX_NUM_PIPES; + + usb3->usb3_ep = devm_kzalloc(dev, sizeof(*usb3_ep) * usb3->num_usb3_eps, + GFP_KERNEL); + if (!usb3->usb3_ep) + return -ENOMEM; + + dev_dbg(dev, "%s: num_usb3_eps = %d\n", __func__, usb3->num_usb3_eps); + /* + * This driver prepares pipes as the followings: + * - odd pipes = IN pipe + * - even pipes = OUT pipe (except pipe 0) + */ + usb3_for_each_ep(usb3_ep, usb3, i) { + snprintf(usb3_ep->ep_name, sizeof(usb3_ep->ep_name), "ep%d", i); + usb3_ep->usb3 = usb3; + usb3_ep->num = i; + usb3_ep->ep.name = usb3_ep->ep_name; + usb3_ep->ep.ops = &renesas_usb3_ep_ops; + INIT_LIST_HEAD(&usb3_ep->queue); + INIT_LIST_HEAD(&usb3_ep->ep.ep_list); + if (!i) { + /* for control pipe */ + usb3->gadget.ep0 = &usb3_ep->ep; + usb_ep_set_maxpacket_limit(&usb3_ep->ep, + USB3_EP0_HSFS_MAX_PACKET_SIZE); + usb3_ep->ep.caps.type_control = true; + usb3_ep->ep.caps.dir_in = true; + usb3_ep->ep.caps.dir_out = true; + continue; + } + + /* for bulk or interrupt pipe */ + usb_ep_set_maxpacket_limit(&usb3_ep->ep, ~0); + list_add_tail(&usb3_ep->ep.ep_list, &usb3->gadget.ep_list); + usb3_ep->ep.caps.type_bulk = true; + usb3_ep->ep.caps.type_int = true; + if (i & 1) + usb3_ep->ep.caps.dir_in = true; + else + usb3_ep->ep.caps.dir_out = true; + } + + return 0; +} + +static void renesas_usb3_init_ram(struct renesas_usb3 *usb3, struct device *dev, + const struct renesas_usb3_priv *priv) +{ + struct renesas_usb3_ep *usb3_ep; + int i; + u32 ramif[2], basead[2]; /* index 0 = for IN pipes */ + u32 *cur_ramif, *cur_basead; + u32 val; + + memset(ramif, 0, sizeof(ramif)); + memset(basead, 0, sizeof(basead)); + + /* + * This driver prepares pipes as the followings: + * - all pipes = the same size as "ramsize_per_pipe" + * Please refer to the "Method of Specifying RAM Mapping" + */ + usb3_for_each_ep(usb3_ep, usb3, i) { + if (!i) + continue; /* out of scope if ep num = 0 */ + if (usb3_ep->ep.caps.dir_in) { + cur_ramif = &ramif[0]; + cur_basead = &basead[0]; + } else { + cur_ramif = &ramif[1]; + cur_basead = &basead[1]; + } + + if (*cur_basead > priv->ramsize_per_ramif) + continue; /* out of memory for IN or OUT pipe */ + + /* calculate rammap_val */ + val = PN_RAMMAP_RAMIF(*cur_ramif); + val |= usb3_calc_ramarea(priv->ramsize_per_pipe); + val |= PN_RAMMAP_BASEAD(*cur_basead); + usb3_ep->rammap_val = val; + + dev_dbg(dev, "ep%2d: val = %08x, ramif = %d, base = %x\n", + i, val, *cur_ramif, *cur_basead); + + /* update current ramif */ + if (*cur_ramif + 1 == priv->num_ramif) { + *cur_ramif = 0; + *cur_basead += priv->ramsize_per_pipe; + } else { + (*cur_ramif)++; + } + } +} + +static const struct renesas_usb3_priv renesas_usb3_priv_r8a7795 = { + .ramsize_per_ramif = SZ_16K, + .num_ramif = 2, + .ramsize_per_pipe = SZ_4K, + .workaround_for_vbus = true, +}; + +static const struct of_device_id usb3_of_match[] = { + { + .compatible = "renesas,r8a7795-usb3-peri", + .data = &renesas_usb3_priv_r8a7795, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, usb3_of_match); + +static int renesas_usb3_probe(struct platform_device *pdev) +{ + struct renesas_usb3 *usb3; + struct resource *res; + const struct of_device_id *match; + int irq, ret; + const struct renesas_usb3_priv *priv; + + match = of_match_node(usb3_of_match, pdev->dev.of_node); + if (!match) + return -ENODEV; + priv = match->data; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENODEV; + + usb3 = devm_kzalloc(&pdev->dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + usb3->reg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(usb3->reg)) + return PTR_ERR(usb3->reg); + + platform_set_drvdata(pdev, usb3); + spin_lock_init(&usb3->lock); + + usb3->gadget.ops = &renesas_usb3_gadget_ops; + usb3->gadget.name = udc_name; + usb3->gadget.max_speed = USB_SPEED_SUPER; + INIT_LIST_HEAD(&usb3->gadget.ep_list); + ret = renesas_usb3_init_ep(usb3, &pdev->dev, priv); + if (ret < 0) + return ret; + renesas_usb3_init_ram(usb3, &pdev->dev, priv); + + ret = devm_request_irq(&pdev->dev, irq, renesas_usb3_irq, 0, + dev_name(&pdev->dev), usb3); + if (ret < 0) + return ret; + + /* for ep0 handling */ + usb3->ep0_req = __renesas_usb3_ep_alloc_request(GFP_KERNEL); + if (!usb3->ep0_req) + return -ENOMEM; + + ret = usb_add_gadget_udc(&pdev->dev, &usb3->gadget); + if (ret < 0) + goto err_add_udc; + + usb3->workaround_for_vbus = priv->workaround_for_vbus; + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + dev_info(&pdev->dev, "probed\n"); + + return 0; + +err_add_udc: + __renesas_usb3_ep_free_request(usb3->ep0_req); + + return ret; +} + +static struct platform_driver renesas_usb3_driver = { + .probe = renesas_usb3_probe, + .remove = renesas_usb3_remove, + .driver = { + .name = (char *)udc_name, + .of_match_table = of_match_ptr(usb3_of_match), + }, +}; +module_platform_driver(renesas_usb3_driver); + +MODULE_DESCRIPTION("Renesas USB3.0 Peripheral driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yoshihiro Shimoda "); +MODULE_ALIAS("platform:renesas_usb3"); -- cgit v1.2.3 From d9261898a4b2c143c28568dc686a1becfc637a99 Mon Sep 17 00:00:00 2001 From: John Youn Date: Tue, 22 Dec 2015 12:23:20 -0800 Subject: usb: dwc3: gadget: don't send extra ZLP If the request->length is zero, a ZLP should already be sent due to that and another ZLP is not needed to terminate the transfer. Fixes: 04c03d10e507 ("usb: dwc3: gadget: handle request->zero") Signed-off-by: John Youn Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 64b2a8303d33..af023a81a0b0 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1203,7 +1203,8 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, * extra usb_request ourselves so that it gets handled the same way as * any other request. */ - if (ret == 0 && request->zero && (request->length % ep->maxpacket == 0)) + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) ret = __dwc3_gadget_ep_queue_zlp(dwc, dep); spin_unlock_irqrestore(&dwc->lock, flags); -- cgit v1.2.3 From 5072cfc40a80cea3749fd3413b3896630d8c787e Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 22 Dec 2015 21:56:10 -0600 Subject: usb: dwc3: of-simple: fix build warning on !PM if we have a !PM kernel build, our runtime suspend/resume callbacks will be left defined but unused. Add a ifdef CONFIG_PM guard. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-of-simple.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index 60c4c5a44307..9c9f74155066 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -122,6 +122,7 @@ static int dwc3_of_simple_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM static int dwc3_of_simple_runtime_suspend(struct device *dev) { struct dwc3_of_simple *simple = dev_get_drvdata(dev); @@ -150,6 +151,7 @@ static int dwc3_of_simple_runtime_resume(struct device *dev) return 0; } +#endif static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = { SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend, -- cgit v1.2.3 From e46fed9fb3a12b1070e2cc5ad03a21b84f94408d Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Fri, 18 Sep 2015 18:30:19 +0100 Subject: usb: chipidea: udc: _ep_queue and _hw_queue cleanup Update comments to reflect current state of functions. Signed-off-by: Felipe F. Tonello Signed-off-by: Peter Chen --- drivers/usb/chipidea/udc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index b292b454c77b..d917b3f27ddb 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -403,9 +403,9 @@ static inline u8 _usb_addr(struct ci_hw_ep *ep) } /** - * _hardware_queue: configures a request at hardware level - * @gadget: gadget + * _hardware_enqueue: configures a request at hardware level * @hwep: endpoint + * @hwreq: request * * This function returns an error code */ @@ -787,8 +787,12 @@ static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) /** * _ep_queue: queues (submits) an I/O request to an endpoint + * @ep: endpoint + * @req: request + * @gfp_flags: GFP flags (not used) * * Caller must hold lock + * This function returns an error code */ static int _ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t __maybe_unused gfp_flags) -- cgit v1.2.3 From 779debdf26d4b49598e61e0c28ac97146a2b96fe Mon Sep 17 00:00:00 2001 From: "Felipe F. Tonello" Date: Tue, 27 Oct 2015 10:36:32 +0000 Subject: usb: chipidea: udc: improve error handling on _hardware_enqueue _hardware_enqueue() didn't check for errors when using add_td_to_list() which can fail if dma_pool_alloc fails, thus causing a kernel panic when lastnode->ptr is NULL. Signed-off-by: Felipe F. Tonello Signed-off-by: Peter Chen --- drivers/usb/chipidea/udc.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index d917b3f27ddb..b1e7b716e8c9 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -434,19 +434,28 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) if (hwreq->req.dma % PAGE_SIZE) pages--; - if (rest == 0) - add_td_to_list(hwep, hwreq, 0); + if (rest == 0) { + ret = add_td_to_list(hwep, hwreq, 0); + if (ret < 0) + goto done; + } while (rest > 0) { unsigned count = min(hwreq->req.length - hwreq->req.actual, (unsigned)(pages * CI_HDRC_PAGE_SIZE)); - add_td_to_list(hwep, hwreq, count); + ret = add_td_to_list(hwep, hwreq, count); + if (ret < 0) + goto done; + rest -= count; } if (hwreq->req.zero && hwreq->req.length && hwep->dir == TX - && (hwreq->req.length % hwep->ep.maxpacket == 0)) - add_td_to_list(hwep, hwreq, 0); + && (hwreq->req.length % hwep->ep.maxpacket == 0)) { + ret = add_td_to_list(hwep, hwreq, 0); + if (ret < 0) + goto done; + } firstnode = list_first_entry(&hwreq->tds, struct td_node, td); -- cgit v1.2.3 From 9d8c850d02b01f3e0157a8f9859fe3f04cab68fe Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 23 Oct 2015 10:33:58 +0800 Subject: usb: chipidea: support debugfs without CONFIG_USB_CHIPIDEA_DEBUG Since we need to mount debugfs to show/store the things we want to debug, it is duplicated to add another configuration to enable it. Meanwhile, with CONFIG_USB_CHIPIDEA_DEBUG, we can't support chipidea debugfs at runtime. Signed-off-by: Peter Chen Cc: Jun Li --- drivers/usb/chipidea/Makefile | 3 +-- drivers/usb/chipidea/ci.h | 3 +++ drivers/usb/chipidea/core.c | 1 - drivers/usb/chipidea/debug.c | 1 - drivers/usb/chipidea/debug.h | 30 ------------------------------ drivers/usb/chipidea/udc.c | 1 - 6 files changed, 4 insertions(+), 35 deletions(-) delete mode 100644 drivers/usb/chipidea/debug.h (limited to 'drivers') diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index 4decb12f2578..c437d5f1a0d5 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -2,10 +2,9 @@ ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o -ci_hdrc-y := core.o otg.o +ci_hdrc-y := core.o otg.o debug.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o -ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o # Glue/Bridge layers go here diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 41d7cf6d63ba..cd414559040f 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -433,4 +433,7 @@ int hw_wait_reg(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask, void ci_platform_configure(struct ci_hdrc *ci); +int dbg_create_files(struct ci_hdrc *ci); + +void dbg_remove_files(struct ci_hdrc *ci); #endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 965d0e240dcb..18e77e02842b 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -71,7 +71,6 @@ #include "udc.h" #include "bits.h" #include "host.h" -#include "debug.h" #include "otg.h" #include "otg_fsm.h" diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c index 58c8485a0715..a4f7db2e18dd 100644 --- a/drivers/usb/chipidea/debug.c +++ b/drivers/usb/chipidea/debug.c @@ -15,7 +15,6 @@ #include "ci.h" #include "udc.h" #include "bits.h" -#include "debug.h" #include "otg.h" /** diff --git a/drivers/usb/chipidea/debug.h b/drivers/usb/chipidea/debug.h deleted file mode 100644 index e16478c4a943..000000000000 --- a/drivers/usb/chipidea/debug.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * debug.h - ChipIdea USB driver debug interfaces - * - * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. - * - * Author: David Lopo - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef __DRIVERS_USB_CHIPIDEA_DEBUG_H -#define __DRIVERS_USB_CHIPIDEA_DEBUG_H - -#ifdef CONFIG_USB_CHIPIDEA_DEBUG -int dbg_create_files(struct ci_hdrc *ci); -void dbg_remove_files(struct ci_hdrc *ci); -#else -static inline int dbg_create_files(struct ci_hdrc *ci) -{ - return 0; -} - -static inline void dbg_remove_files(struct ci_hdrc *ci) -{ -} -#endif - -#endif /* __DRIVERS_USB_CHIPIDEA_DEBUG_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index b1e7b716e8c9..3eafa2c9a2ba 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -26,7 +26,6 @@ #include "ci.h" #include "udc.h" #include "bits.h" -#include "debug.h" #include "otg.h" #include "otg_fsm.h" -- cgit v1.2.3 From 383da2450c7150b4c470dab3759ab86532c65b78 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 27 Oct 2015 16:08:30 +0800 Subject: usb: chipidea: delete static debug support Since we have dynamic debug support, delete static debug for chipidea Signed-off-by: Peter Chen --- drivers/usb/chipidea/Kconfig | 5 ----- drivers/usb/chipidea/Makefile | 2 -- 2 files changed, 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 5619b8ca3bf3..3644a3500b70 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -37,9 +37,4 @@ config USB_CHIPIDEA_HOST Say Y here to enable host controller functionality of the ChipIdea driver. -config USB_CHIPIDEA_DEBUG - bool "ChipIdea driver debug" - help - Say Y here to enable debugging output of the ChipIdea driver. - endif diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index c437d5f1a0d5..518e445476c3 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -1,5 +1,3 @@ -ccflags-$(CONFIG_USB_CHIPIDEA_DEBUG) := -DDEBUG - obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o ci_hdrc-y := core.o otg.o debug.o -- cgit v1.2.3 From 3a35d59a69398b3b522d449404669624486d2b68 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 3 Nov 2015 16:21:20 +0800 Subject: usb: chipidea: clean up CONFIG_USB_CHIPIDEA_DEBUG reference Since this configuration option has deleted, cleans up all its references. Signed-off-by: Peter Chen Reported-by: Valentin Rothberg --- Documentation/usb/chipidea.txt | 4 ++-- drivers/usb/chipidea/core.c | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/Documentation/usb/chipidea.txt b/Documentation/usb/chipidea.txt index 3f848c1f2940..05f735a1b5a5 100644 --- a/Documentation/usb/chipidea.txt +++ b/Documentation/usb/chipidea.txt @@ -7,8 +7,8 @@ with 2 Freescale i.MX6Q sabre SD boards. --------------------------------------- Select CONFIG_USB_OTG_FSM, rebuild kernel Image and modules. If you want to check some internal variables for otg fsm, -select CONFIG_USB_CHIPIDEA_DEBUG, there are 2 files which -can show otg fsm variables and some controller registers value: +mount debugfs, there are 2 files which can show otg fsm +variables and some controller registers value: cat /sys/kernel/debug/ci_hdrc.0/otg cat /sys/kernel/debug/ci_hdrc.0/registers diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 18e77e02842b..3a237d03260c 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -23,7 +23,6 @@ * - BUS: bus glue code, bus abstraction layer * * Compile Options - * - CONFIG_USB_CHIPIDEA_DEBUG: enable debug facilities * - STALL_IN: non-empty bulk-in pipes cannot be halted * if defined mass storage compliance succeeds but with warnings * => case 4: Hi > Dn -- cgit v1.2.3 From b09b5224fe86b3c9adef1bd9f0bd81800e8ff0b3 Mon Sep 17 00:00:00 2001 From: Andreas Fenkart Date: Thu, 12 Nov 2015 14:14:40 +0100 Subject: usb: chipidea: implement platform shutdown callback disable wakeup irq during shutdown, otherwise kexec fails for kernels that setup irq handlers before resetting the hardware Signed-off-by: Andreas Fenkart Signed-off-by: Peter Chen --- drivers/usb/chipidea/ci_hdrc_imx.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 5a048b7b92e8..f14f4ab47ebb 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -345,6 +345,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) return 0; } +static void ci_hdrc_imx_shutdown(struct platform_device *pdev) +{ + ci_hdrc_imx_remove(pdev); +} + #ifdef CONFIG_PM static int imx_controller_suspend(struct device *dev) { @@ -462,6 +467,7 @@ static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { static struct platform_driver ci_hdrc_imx_driver = { .probe = ci_hdrc_imx_probe, .remove = ci_hdrc_imx_remove, + .shutdown = ci_hdrc_imx_shutdown, .driver = { .name = "imx_usb", .of_match_table = ci_hdrc_imx_dt_ids, -- cgit v1.2.3 From 4b19b78aa6f1f57a997d93fa05df6724792a85ba Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Wed, 18 Nov 2015 09:40:12 +0530 Subject: usb: chipidea: removing of_find_property call to of_find_property() before of_property_read_u32() is unnecessary. of_property_read_u32() anyway calls to of_find_property() only. Signed-off-by: Saurabh Sengar Signed-off-by: Peter Chen --- drivers/usb/chipidea/core.c | 57 +++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 3a237d03260c..7404064b9bbc 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -686,52 +686,39 @@ static int ci_get_platdata(struct device *dev, if (usb_get_maximum_speed(dev) == USB_SPEED_FULL) platdata->flags |= CI_HDRC_FORCE_FULLSPEED; - if (of_find_property(dev->of_node, "phy-clkgate-delay-us", NULL)) - of_property_read_u32(dev->of_node, "phy-clkgate-delay-us", + of_property_read_u32(dev->of_node, "phy-clkgate-delay-us", &platdata->phy_clkgate_delay_us); platdata->itc_setting = 1; - if (of_find_property(dev->of_node, "itc-setting", NULL)) { - ret = of_property_read_u32(dev->of_node, "itc-setting", - &platdata->itc_setting); - if (ret) { - dev_err(dev, - "failed to get itc-setting\n"); - return ret; - } - } - if (of_find_property(dev->of_node, "ahb-burst-config", NULL)) { - ret = of_property_read_u32(dev->of_node, "ahb-burst-config", - &platdata->ahb_burst_config); - if (ret) { - dev_err(dev, - "failed to get ahb-burst-config\n"); - return ret; - } + of_property_read_u32(dev->of_node, "itc-setting", + &platdata->itc_setting); + + ret = of_property_read_u32(dev->of_node, "ahb-burst-config", + &platdata->ahb_burst_config); + if (!ret) { platdata->flags |= CI_HDRC_OVERRIDE_AHB_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get ahb-burst-config\n"); + return ret; } - if (of_find_property(dev->of_node, "tx-burst-size-dword", NULL)) { - ret = of_property_read_u32(dev->of_node, "tx-burst-size-dword", - &platdata->tx_burst_size); - if (ret) { - dev_err(dev, - "failed to get tx-burst-size-dword\n"); - return ret; - } + ret = of_property_read_u32(dev->of_node, "tx-burst-size-dword", + &platdata->tx_burst_size); + if (!ret) { platdata->flags |= CI_HDRC_OVERRIDE_TX_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get tx-burst-size-dword\n"); + return ret; } - if (of_find_property(dev->of_node, "rx-burst-size-dword", NULL)) { - ret = of_property_read_u32(dev->of_node, "rx-burst-size-dword", - &platdata->rx_burst_size); - if (ret) { - dev_err(dev, - "failed to get rx-burst-size-dword\n"); - return ret; - } + ret = of_property_read_u32(dev->of_node, "rx-burst-size-dword", + &platdata->rx_burst_size); + if (!ret) { platdata->flags |= CI_HDRC_OVERRIDE_RX_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get rx-burst-size-dword\n"); + return ret; } ext_id = ERR_PTR(-ENODEV); -- cgit v1.2.3 From 43a404577a93d236913b67e41758adf5b9a8f45d Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 15 Dec 2015 17:47:47 +0800 Subject: usb: chipidea: host: set host to be null after hcd is freed Set ci->hcd and ci->otg.host to be null in host_stop since the hcd already freed. Signed-off-by: Li Jun Signed-off-by: Peter Chen --- drivers/usb/chipidea/host.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 3d24304405b3..053bac9d983c 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -190,6 +190,8 @@ static void host_stop(struct ci_hdrc *ci) (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON)) regulator_disable(ci->platdata->reg_vbus); } + ci->hcd = NULL; + ci->otg.host = NULL; } -- cgit v1.2.3 From 8c100e74409ad3bbcb8a1ccdff4540fc0aa7a0fc Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 15 Dec 2015 17:51:29 +0800 Subject: usb: chipidea: otg: use usb autosuspend to suspend bus for HNP Directly manipulate the controller regsiter to suspend the usb bus for HNP is not the proper way, this should be done through the usbcore by usb autosuspend. So to start HNP, autosuspend support should be added for OTG devices interface driver if it's not enabled. Signed-off-by: Li Jun Signed-off-by: Peter Chen --- drivers/usb/chipidea/otg_fsm.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 00ab59d45da1..ba90dc66703d 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -485,20 +485,30 @@ static void ci_otg_loc_conn(struct otg_fsm *fsm, int on) /* * Generate SOF by host. - * This is controlled through suspend/resume the port. * In host mode, controller will automatically send SOF. * Suspend will block the data on the port. + * + * This is controlled through usbcore by usb autosuspend, + * so the usb device class driver need support autosuspend, + * otherwise the bus suspend will not happen. */ static void ci_otg_loc_sof(struct otg_fsm *fsm, int on) { - struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + struct usb_device *udev; - if (on) - hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR, - PORTSC_FPR); - else - hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP, - PORTSC_SUSP); + if (!fsm->otg->host) + return; + + udev = usb_hub_find_child(fsm->otg->host->root_hub, 1); + if (!udev) + return; + + if (on) { + usb_disable_autosuspend(udev); + } else { + pm_runtime_set_autosuspend_delay(&udev->dev, 0); + usb_enable_autosuspend(udev); + } } /* -- cgit v1.2.3 From f7d7f59ab124748156ea551edf789994f05da342 Mon Sep 17 00:00:00 2001 From: Oliver Freyermuth Date: Mon, 28 Dec 2015 18:37:38 +0100 Subject: USB: cp210x: add ID for ELV Marble Sound Board 1 Add the USB device ID for ELV Marble Sound Board 1. Signed-off-by: Oliver Freyermuth Cc: stable Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 7d4f51a32e66..59b2126b21a3 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -160,6 +160,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */ { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */ { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */ + { USB_DEVICE(0x18EF, 0xE025) }, /* ELV Marble Sound Board 1 */ { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ -- cgit v1.2.3 From 0b2b093ad405b56a9e6f4f20a25da77ebfa9549c Mon Sep 17 00:00:00 2001 From: Mathieu OTHACEHE Date: Mon, 28 Dec 2015 21:21:25 +0100 Subject: USB: serial: add Moxa UPORT 11x0 driver Add a driver which supports : - UPort 1110 : 1 port RS-232 USB to Serial Hub. - UPort 1130 : 1 port RS-422/485 USB to Serial Hub. - UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation. - UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub. - UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation. This driver is based on GPL MOXA driver written by Hen Huang and available on MOXA website. The original driver was based on io_ti serial driver. Signed-off-by: Mathieu OTHACEHE Signed-off-by: Johan Hovold --- drivers/usb/serial/Kconfig | 16 + drivers/usb/serial/Makefile | 1 + drivers/usb/serial/mxu11x0.c | 995 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1012 insertions(+) create mode 100644 drivers/usb/serial/mxu11x0.c (limited to 'drivers') diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 56ecb8b5115d..f612dda9c977 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -475,6 +475,22 @@ config USB_SERIAL_MOS7840 To compile this driver as a module, choose M here: the module will be called mos7840. If unsure, choose N. +config USB_SERIAL_MXUPORT11 + tristate "USB Moxa UPORT 11x0 Serial Driver" + ---help--- + Say Y here if you want to use a MOXA UPort 11x0 Serial hub. + + This driver supports: + + - UPort 1110 : 1 port RS-232 USB to Serial Hub. + - UPort 1130 : 1 port RS-422/485 USB to Serial Hub. + - UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation. + - UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub. + - UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation. + + To compile this driver as a module, choose M here: the + module will be called mxu11x0. + config USB_SERIAL_MXUPORT tristate "USB Moxa UPORT Serial Driver" ---help--- diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 349d9df0895f..f3fa5e53702d 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_USB_SERIAL_METRO) += metro-usb.o obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o obj-$(CONFIG_USB_SERIAL_MXUPORT) += mxuport.o +obj-$(CONFIG_USB_SERIAL_MXUPORT11) += mxu11x0.o obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c new file mode 100644 index 000000000000..8884ca276e67 --- /dev/null +++ b/drivers/usb/serial/mxu11x0.c @@ -0,0 +1,995 @@ +/* + * + * USB Moxa UPORT 11x0 Serial Driver + * + * Copyright (C) 2007 MOXA Technologies Co., Ltd. + * Copyright (C) 2015 Mathieu Othacehe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * + * Supports the following Moxa USB to serial converters: + * UPort 1110, 1 port RS-232 USB to Serial Hub. + * UPort 1130, 1 port RS-422/485 USB to Serial Hub. + * UPort 1130I, 1 port RS-422/485 USB to Serial Hub with isolation + * protection. + * UPort 1150, 1 port RS-232/422/485 USB to Serial Hub. + * UPort 1150I, 1 port RS-232/422/485 USB to Serial Hub with isolation + * protection. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Vendor and product ids */ +#define MXU1_VENDOR_ID 0x110a +#define MXU1_1110_PRODUCT_ID 0x1110 +#define MXU1_1130_PRODUCT_ID 0x1130 +#define MXU1_1150_PRODUCT_ID 0x1150 +#define MXU1_1151_PRODUCT_ID 0x1151 +#define MXU1_1131_PRODUCT_ID 0x1131 + +/* Commands */ +#define MXU1_GET_VERSION 0x01 +#define MXU1_GET_PORT_STATUS 0x02 +#define MXU1_GET_PORT_DEV_INFO 0x03 +#define MXU1_GET_CONFIG 0x04 +#define MXU1_SET_CONFIG 0x05 +#define MXU1_OPEN_PORT 0x06 +#define MXU1_CLOSE_PORT 0x07 +#define MXU1_START_PORT 0x08 +#define MXU1_STOP_PORT 0x09 +#define MXU1_TEST_PORT 0x0A +#define MXU1_PURGE_PORT 0x0B +#define MXU1_RESET_EXT_DEVICE 0x0C +#define MXU1_GET_OUTQUEUE 0x0D +#define MXU1_WRITE_DATA 0x80 +#define MXU1_READ_DATA 0x81 +#define MXU1_REQ_TYPE_CLASS 0x82 + +/* Module identifiers */ +#define MXU1_I2C_PORT 0x01 +#define MXU1_IEEE1284_PORT 0x02 +#define MXU1_UART1_PORT 0x03 +#define MXU1_UART2_PORT 0x04 +#define MXU1_RAM_PORT 0x05 + +/* Modem status */ +#define MXU1_MSR_DELTA_CTS 0x01 +#define MXU1_MSR_DELTA_DSR 0x02 +#define MXU1_MSR_DELTA_RI 0x04 +#define MXU1_MSR_DELTA_CD 0x08 +#define MXU1_MSR_CTS 0x10 +#define MXU1_MSR_DSR 0x20 +#define MXU1_MSR_RI 0x40 +#define MXU1_MSR_CD 0x80 +#define MXU1_MSR_DELTA_MASK 0x0F +#define MXU1_MSR_MASK 0xF0 + +/* Line status */ +#define MXU1_LSR_OVERRUN_ERROR 0x01 +#define MXU1_LSR_PARITY_ERROR 0x02 +#define MXU1_LSR_FRAMING_ERROR 0x04 +#define MXU1_LSR_BREAK 0x08 +#define MXU1_LSR_ERROR 0x0F +#define MXU1_LSR_RX_FULL 0x10 +#define MXU1_LSR_TX_EMPTY 0x20 + +/* Modem control */ +#define MXU1_MCR_LOOP 0x04 +#define MXU1_MCR_DTR 0x10 +#define MXU1_MCR_RTS 0x20 + +/* Mask settings */ +#define MXU1_UART_ENABLE_RTS_IN 0x0001 +#define MXU1_UART_DISABLE_RTS 0x0002 +#define MXU1_UART_ENABLE_PARITY_CHECKING 0x0008 +#define MXU1_UART_ENABLE_DSR_OUT 0x0010 +#define MXU1_UART_ENABLE_CTS_OUT 0x0020 +#define MXU1_UART_ENABLE_X_OUT 0x0040 +#define MXU1_UART_ENABLE_XA_OUT 0x0080 +#define MXU1_UART_ENABLE_X_IN 0x0100 +#define MXU1_UART_ENABLE_DTR_IN 0x0800 +#define MXU1_UART_DISABLE_DTR 0x1000 +#define MXU1_UART_ENABLE_MS_INTS 0x2000 +#define MXU1_UART_ENABLE_AUTO_START_DMA 0x4000 +#define MXU1_UART_SEND_BREAK_SIGNAL 0x8000 + +/* Parity */ +#define MXU1_UART_NO_PARITY 0x00 +#define MXU1_UART_ODD_PARITY 0x01 +#define MXU1_UART_EVEN_PARITY 0x02 +#define MXU1_UART_MARK_PARITY 0x03 +#define MXU1_UART_SPACE_PARITY 0x04 + +/* Stop bits */ +#define MXU1_UART_1_STOP_BITS 0x00 +#define MXU1_UART_1_5_STOP_BITS 0x01 +#define MXU1_UART_2_STOP_BITS 0x02 + +/* Bits per character */ +#define MXU1_UART_5_DATA_BITS 0x00 +#define MXU1_UART_6_DATA_BITS 0x01 +#define MXU1_UART_7_DATA_BITS 0x02 +#define MXU1_UART_8_DATA_BITS 0x03 + +/* Operation modes */ +#define MXU1_UART_232 0x00 +#define MXU1_UART_485_RECEIVER_DISABLED 0x01 +#define MXU1_UART_485_RECEIVER_ENABLED 0x02 + +/* Pipe transfer mode and timeout */ +#define MXU1_PIPE_MODE_CONTINUOUS 0x01 +#define MXU1_PIPE_MODE_MASK 0x03 +#define MXU1_PIPE_TIMEOUT_MASK 0x7C +#define MXU1_PIPE_TIMEOUT_ENABLE 0x80 + +/* Config struct */ +struct mxu1_uart_config { + __be16 wBaudRate; + __be16 wFlags; + u8 bDataBits; + u8 bParity; + u8 bStopBits; + char cXon; + char cXoff; + u8 bUartMode; +} __packed; + +/* Purge modes */ +#define MXU1_PURGE_OUTPUT 0x00 +#define MXU1_PURGE_INPUT 0x80 + +/* Read/Write data */ +#define MXU1_RW_DATA_ADDR_SFR 0x10 +#define MXU1_RW_DATA_ADDR_IDATA 0x20 +#define MXU1_RW_DATA_ADDR_XDATA 0x30 +#define MXU1_RW_DATA_ADDR_CODE 0x40 +#define MXU1_RW_DATA_ADDR_GPIO 0x50 +#define MXU1_RW_DATA_ADDR_I2C 0x60 +#define MXU1_RW_DATA_ADDR_FLASH 0x70 +#define MXU1_RW_DATA_ADDR_DSP 0x80 + +#define MXU1_RW_DATA_UNSPECIFIED 0x00 +#define MXU1_RW_DATA_BYTE 0x01 +#define MXU1_RW_DATA_WORD 0x02 +#define MXU1_RW_DATA_DOUBLE_WORD 0x04 + +struct mxu1_write_data_bytes { + u8 bAddrType; + u8 bDataType; + u8 bDataCounter; + __be16 wBaseAddrHi; + __be16 wBaseAddrLo; + u8 bData[0]; +} __packed; + +/* Interrupt codes */ +#define MXU1_CODE_HARDWARE_ERROR 0xFF +#define MXU1_CODE_DATA_ERROR 0x03 +#define MXU1_CODE_MODEM_STATUS 0x04 + +static inline int mxu1_get_func_from_code(unsigned char code) +{ + return code & 0x0f; +} + +/* Download firmware max packet size */ +#define MXU1_DOWNLOAD_MAX_PACKET_SIZE 64 + +/* Firmware image header */ +struct mxu1_firmware_header { + __le16 wLength; + u8 bCheckSum; +} __packed; + +#define MXU1_UART_BASE_ADDR 0xFFA0 +#define MXU1_UART_OFFSET_MCR 0x0004 + +#define MXU1_BAUD_BASE 923077 + +#define MXU1_TRANSFER_TIMEOUT 2 +#define MXU1_DOWNLOAD_TIMEOUT 1000 +#define MXU1_DEFAULT_CLOSING_WAIT 4000 /* in .01 secs */ + +struct mxu1_port { + u8 msr; + u8 mcr; + u8 uart_mode; + spinlock_t spinlock; /* Protects msr */ + struct mutex mutex; /* Protects mcr */ + bool send_break; +}; + +struct mxu1_device { + u16 mxd_model; +}; + +static const struct usb_device_id mxu1_idtable[] = { + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1110_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1130_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1150_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1151_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1131_PRODUCT_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, mxu1_idtable); + +/* Write the given buffer out to the control pipe. */ +static int mxu1_send_ctrl_data_urb(struct usb_serial *serial, + u8 request, + u16 value, u16 index, + void *data, size_t size) +{ + int status; + + status = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, + (USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE), value, index, + data, size, + USB_CTRL_SET_TIMEOUT); + if (status < 0) { + dev_err(&serial->interface->dev, + "%s - usb_control_msg failed: %d\n", + __func__, status); + return status; + } + + if (status != size) { + dev_err(&serial->interface->dev, + "%s - short write (%d / %zd)\n", + __func__, status, size); + return -EIO; + } + + return 0; +} + +/* Send a vendor request without any data */ +static int mxu1_send_ctrl_urb(struct usb_serial *serial, + u8 request, u16 value, u16 index) +{ + return mxu1_send_ctrl_data_urb(serial, request, value, index, + NULL, 0); +} + +static int mxu1_download_firmware(struct usb_serial *serial, + const struct firmware *fw_p) +{ + int status = 0; + int buffer_size; + int pos; + int len; + int done; + u8 cs = 0; + u8 *buffer; + struct usb_device *dev = serial->dev; + struct mxu1_firmware_header *header; + unsigned int pipe; + + pipe = usb_sndbulkpipe(dev, serial->port[0]->bulk_out_endpointAddress); + + buffer_size = fw_p->size + sizeof(*header); + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, fw_p->data, fw_p->size); + memset(buffer + fw_p->size, 0xff, buffer_size - fw_p->size); + + for (pos = sizeof(*header); pos < buffer_size; pos++) + cs = (u8)(cs + buffer[pos]); + + header = (struct mxu1_firmware_header *)buffer; + header->wLength = cpu_to_le16(buffer_size - sizeof(*header)); + header->bCheckSum = cs; + + dev_dbg(&dev->dev, "%s - downloading firmware\n", __func__); + + for (pos = 0; pos < buffer_size; pos += done) { + len = min(buffer_size - pos, MXU1_DOWNLOAD_MAX_PACKET_SIZE); + + status = usb_bulk_msg(dev, pipe, buffer + pos, len, &done, + MXU1_DOWNLOAD_TIMEOUT); + if (status) + break; + } + + kfree(buffer); + + if (status) { + dev_err(&dev->dev, "failed to download firmware: %d\n", status); + return status; + } + + msleep_interruptible(100); + usb_reset_device(dev); + + dev_dbg(&dev->dev, "%s - download successful\n", __func__); + + return 0; +} + +static int mxu1_port_probe(struct usb_serial_port *port) +{ + struct mxu1_port *mxport; + struct mxu1_device *mxdev; + struct urb *urb; + + mxport = kzalloc(sizeof(struct mxu1_port), GFP_KERNEL); + if (!mxport) + return -ENOMEM; + + spin_lock_init(&mxport->spinlock); + mutex_init(&mxport->mutex); + + mxdev = usb_get_serial_data(port->serial); + + urb = port->interrupt_in_urb; + if (!urb) { + dev_err(&port->dev, "%s - no interrupt urb\n", __func__); + return -EINVAL; + } + + switch (mxdev->mxd_model) { + case MXU1_1110_PRODUCT_ID: + case MXU1_1150_PRODUCT_ID: + case MXU1_1151_PRODUCT_ID: + mxport->uart_mode = MXU1_UART_232; + break; + case MXU1_1130_PRODUCT_ID: + case MXU1_1131_PRODUCT_ID: + mxport->uart_mode = MXU1_UART_485_RECEIVER_DISABLED; + break; + } + + usb_set_serial_port_data(port, mxport); + + port->port.closing_wait = + msecs_to_jiffies(MXU1_DEFAULT_CLOSING_WAIT * 10); + port->port.drain_delay = 1; + + return 0; +} + +static int mxu1_startup(struct usb_serial *serial) +{ + struct mxu1_device *mxdev; + struct usb_device *dev = serial->dev; + struct usb_host_interface *cur_altsetting; + char fw_name[32]; + const struct firmware *fw_p = NULL; + int err; + int status = 0; + + dev_dbg(&serial->interface->dev, "%s - product 0x%04X, num configurations %d, configuration value %d\n", + __func__, le16_to_cpu(dev->descriptor.idProduct), + dev->descriptor.bNumConfigurations, + dev->actconfig->desc.bConfigurationValue); + + /* create device structure */ + mxdev = kzalloc(sizeof(struct mxu1_device), GFP_KERNEL); + if (!mxdev) + return -ENOMEM; + + usb_set_serial_data(serial, mxdev); + + mxdev->mxd_model = le16_to_cpu(dev->descriptor.idProduct); + + cur_altsetting = serial->interface->cur_altsetting; + + /* if we have only 1 configuration, download firmware */ + if (cur_altsetting->desc.bNumEndpoints == 1) { + + snprintf(fw_name, + sizeof(fw_name), + "moxa/moxa-%04x.fw", + mxdev->mxd_model); + + err = request_firmware(&fw_p, fw_name, &serial->interface->dev); + if (err) { + dev_err(&serial->interface->dev, "failed to request firmware: %d\n", + err); + kfree(mxdev); + return err; + } + + err = mxu1_download_firmware(serial, fw_p); + if (err) { + release_firmware(fw_p); + kfree(mxdev); + return err; + } + + status = -ENODEV; + release_firmware(fw_p); + } + + return status; +} + +static int mxu1_write_byte(struct usb_serial_port *port, u32 addr, + u8 mask, u8 byte) +{ + int status; + size_t size; + struct mxu1_write_data_bytes *data; + + dev_dbg(&port->dev, "%s - addr 0x%08X, mask 0x%02X, byte 0x%02X\n", + __func__, addr, mask, byte); + + size = sizeof(struct mxu1_write_data_bytes) + 2; + data = kzalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->bAddrType = MXU1_RW_DATA_ADDR_XDATA; + data->bDataType = MXU1_RW_DATA_BYTE; + data->bDataCounter = 1; + data->wBaseAddrHi = cpu_to_be16(addr >> 16); + data->wBaseAddrLo = cpu_to_be16(addr); + data->bData[0] = mask; + data->bData[1] = byte; + + status = mxu1_send_ctrl_data_urb(port->serial, MXU1_WRITE_DATA, 0, + MXU1_RAM_PORT, data, size); + if (status < 0) + dev_err(&port->dev, "%s - failed: %d\n", __func__, status); + + kfree(data); + + return status; +} + +static int mxu1_set_mcr(struct usb_serial_port *port, unsigned int mcr) +{ + int status; + + status = mxu1_write_byte(port, + MXU1_UART_BASE_ADDR + MXU1_UART_OFFSET_MCR, + MXU1_MCR_RTS | MXU1_MCR_DTR | MXU1_MCR_LOOP, + mcr); + return status; +} + +static void mxu1_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct mxu1_port *mxport = usb_get_serial_port_data(port); + struct mxu1_uart_config *config; + tcflag_t cflag, iflag; + speed_t baud; + int status; + unsigned int mcr; + + cflag = tty->termios.c_cflag; + iflag = tty->termios.c_iflag; + + if (old_termios && + !tty_termios_hw_change(&tty->termios, old_termios) && + tty->termios.c_iflag == old_termios->c_iflag) { + dev_dbg(&port->dev, "%s - nothing to change\n", __func__); + return; + } + + dev_dbg(&port->dev, + "%s - clfag %08x, iflag %08x\n", __func__, cflag, iflag); + + if (old_termios) { + dev_dbg(&port->dev, "%s - old clfag %08x, old iflag %08x\n", + __func__, + old_termios->c_cflag, + old_termios->c_iflag); + } + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) + return; + + /* these flags must be set */ + config->wFlags |= MXU1_UART_ENABLE_MS_INTS; + config->wFlags |= MXU1_UART_ENABLE_AUTO_START_DMA; + if (mxport->send_break) + config->wFlags |= MXU1_UART_SEND_BREAK_SIGNAL; + config->bUartMode = mxport->uart_mode; + + switch (C_CSIZE(tty)) { + case CS5: + config->bDataBits = MXU1_UART_5_DATA_BITS; + break; + case CS6: + config->bDataBits = MXU1_UART_6_DATA_BITS; + break; + case CS7: + config->bDataBits = MXU1_UART_7_DATA_BITS; + break; + default: + case CS8: + config->bDataBits = MXU1_UART_8_DATA_BITS; + break; + } + + if (C_PARENB(tty)) { + config->wFlags |= MXU1_UART_ENABLE_PARITY_CHECKING; + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + config->bParity = MXU1_UART_MARK_PARITY; + else + config->bParity = MXU1_UART_SPACE_PARITY; + } else { + if (C_PARODD(tty)) + config->bParity = MXU1_UART_ODD_PARITY; + else + config->bParity = MXU1_UART_EVEN_PARITY; + } + } else { + config->bParity = MXU1_UART_NO_PARITY; + } + + if (C_CSTOPB(tty)) + config->bStopBits = MXU1_UART_2_STOP_BITS; + else + config->bStopBits = MXU1_UART_1_STOP_BITS; + + if (C_CRTSCTS(tty)) { + /* RTS flow control must be off to drop RTS for baud rate B0 */ + if (C_BAUD(tty) != B0) + config->wFlags |= MXU1_UART_ENABLE_RTS_IN; + config->wFlags |= MXU1_UART_ENABLE_CTS_OUT; + } + + if (I_IXOFF(tty) || I_IXON(tty)) { + config->cXon = START_CHAR(tty); + config->cXoff = STOP_CHAR(tty); + + if (I_IXOFF(tty)) + config->wFlags |= MXU1_UART_ENABLE_X_IN; + + if (I_IXON(tty)) + config->wFlags |= MXU1_UART_ENABLE_X_OUT; + } + + baud = tty_get_baud_rate(tty); + if (!baud) + baud = 9600; + config->wBaudRate = MXU1_BAUD_BASE / baud; + + dev_dbg(&port->dev, "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d\n", + __func__, baud, config->wBaudRate, config->wFlags, + config->bDataBits, config->bParity, config->bStopBits, + config->cXon, config->cXoff, config->bUartMode); + + cpu_to_be16s(&config->wBaudRate); + cpu_to_be16s(&config->wFlags); + + status = mxu1_send_ctrl_data_urb(port->serial, MXU1_SET_CONFIG, 0, + MXU1_UART1_PORT, config, + sizeof(*config)); + if (status) + dev_err(&port->dev, "cannot set config: %d\n", status); + + mutex_lock(&mxport->mutex); + mcr = mxport->mcr; + + if (C_BAUD(tty) == B0) + mcr &= ~(MXU1_MCR_DTR | MXU1_MCR_RTS); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + mcr |= ~(MXU1_MCR_DTR | MXU1_MCR_RTS); + + status = mxu1_set_mcr(port, mcr); + if (status) + dev_err(&port->dev, "cannot set modem control: %d\n", status); + else + mxport->mcr = mcr; + + mutex_unlock(&mxport->mutex); + + kfree(config); +} + +static int mxu1_get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *ret_arg) +{ + struct serial_struct ret_serial; + unsigned cwait; + + if (!ret_arg) + return -EFAULT; + + cwait = port->port.closing_wait; + if (cwait != ASYNC_CLOSING_WAIT_NONE) + cwait = jiffies_to_msecs(cwait) / 10; + + memset(&ret_serial, 0, sizeof(ret_serial)); + + ret_serial.type = PORT_16550A; + ret_serial.line = port->minor; + ret_serial.port = 0; + ret_serial.xmit_fifo_size = port->bulk_out_size; + ret_serial.baud_base = MXU1_BAUD_BASE; + ret_serial.close_delay = 5*HZ; + ret_serial.closing_wait = cwait; + + if (copy_to_user(ret_arg, &ret_serial, sizeof(*ret_arg))) + return -EFAULT; + + return 0; +} + + +static int mxu1_set_serial_info(struct usb_serial_port *port, + struct serial_struct __user *new_arg) +{ + struct serial_struct new_serial; + unsigned cwait; + + if (copy_from_user(&new_serial, new_arg, sizeof(new_serial))) + return -EFAULT; + + cwait = new_serial.closing_wait; + if (cwait != ASYNC_CLOSING_WAIT_NONE) + cwait = msecs_to_jiffies(10 * new_serial.closing_wait); + + port->port.closing_wait = cwait; + + return 0; +} + +static int mxu1_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + switch (cmd) { + case TIOCGSERIAL: + return mxu1_get_serial_info(port, + (struct serial_struct __user *)arg); + case TIOCSSERIAL: + return mxu1_set_serial_info(port, + (struct serial_struct __user *)arg); + } + + return -ENOIOCTLCMD; +} + +static int mxu1_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct mxu1_port *mxport = usb_get_serial_port_data(port); + unsigned int result; + unsigned int msr; + unsigned int mcr; + unsigned long flags; + + mutex_lock(&mxport->mutex); + spin_lock_irqsave(&mxport->spinlock, flags); + + msr = mxport->msr; + mcr = mxport->mcr; + + spin_unlock_irqrestore(&mxport->spinlock, flags); + mutex_unlock(&mxport->mutex); + + result = ((mcr & MXU1_MCR_DTR) ? TIOCM_DTR : 0) | + ((mcr & MXU1_MCR_RTS) ? TIOCM_RTS : 0) | + ((mcr & MXU1_MCR_LOOP) ? TIOCM_LOOP : 0) | + ((msr & MXU1_MSR_CTS) ? TIOCM_CTS : 0) | + ((msr & MXU1_MSR_CD) ? TIOCM_CAR : 0) | + ((msr & MXU1_MSR_RI) ? TIOCM_RI : 0) | + ((msr & MXU1_MSR_DSR) ? TIOCM_DSR : 0); + + dev_dbg(&port->dev, "%s - 0x%04X\n", __func__, result); + + return result; +} + +static int mxu1_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct mxu1_port *mxport = usb_get_serial_port_data(port); + int err; + unsigned int mcr; + + mutex_lock(&mxport->mutex); + mcr = mxport->mcr; + + if (set & TIOCM_RTS) + mcr |= MXU1_MCR_RTS; + if (set & TIOCM_DTR) + mcr |= MXU1_MCR_DTR; + if (set & TIOCM_LOOP) + mcr |= MXU1_MCR_LOOP; + + if (clear & TIOCM_RTS) + mcr &= ~MXU1_MCR_RTS; + if (clear & TIOCM_DTR) + mcr &= ~MXU1_MCR_DTR; + if (clear & TIOCM_LOOP) + mcr &= ~MXU1_MCR_LOOP; + + err = mxu1_set_mcr(port, mcr); + if (!err) + mxport->mcr = mcr; + + mutex_unlock(&mxport->mutex); + + return err; +} + +static void mxu1_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct mxu1_port *mxport = usb_get_serial_port_data(port); + + if (break_state == -1) + mxport->send_break = true; + else + mxport->send_break = false; + + mxu1_set_termios(tty, port, NULL); +} + +static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct mxu1_port *mxport = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int status; + u16 open_settings; + + open_settings = (MXU1_PIPE_MODE_CONTINUOUS | + MXU1_PIPE_TIMEOUT_ENABLE | + (MXU1_TRANSFER_TIMEOUT << 2)); + + mxport->msr = 0; + + dev_dbg(&port->dev, "%s - start interrupt in urb\n", __func__); + status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (status) { + dev_err(&port->dev, "failed to submit interrupt urb: %d\n", + status); + return status; + } + + if (tty) + mxu1_set_termios(tty, port, NULL); + + status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, + open_settings, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "%s - cannot send open command: %d\n", + __func__, status); + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, + 0, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "%s - cannot send start command: %d\n", + __func__, status); + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, + MXU1_PURGE_INPUT, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "%s - cannot clear input buffers: %d\n", + __func__, status); + + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, + MXU1_PURGE_OUTPUT, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "%s - cannot clear output buffers: %d\n", + __func__, status); + + goto unlink_int_urb; + } + + /* + * reset the data toggle on the bulk endpoints to work around bug in + * host controllers where things get out of sync some times + */ + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + + if (tty) + mxu1_set_termios(tty, port, NULL); + + status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, + open_settings, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "%s - cannot send open command: %d\n", + __func__, status); + goto unlink_int_urb; + } + + status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, + 0, MXU1_UART1_PORT); + if (status) { + dev_err(&port->dev, "%s - cannot send start command: %d\n", + __func__, status); + goto unlink_int_urb; + } + + status = usb_serial_generic_open(tty, port); + if (status) { + dev_err(&port->dev, "%s - submit read urb failed: %d\n", + __func__, status); + goto unlink_int_urb; + } + + return status; + +unlink_int_urb: + usb_kill_urb(port->interrupt_in_urb); + + return status; +} + +static void mxu1_close(struct usb_serial_port *port) +{ + int status; + + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); + + status = mxu1_send_ctrl_urb(port->serial, MXU1_CLOSE_PORT, + 0, MXU1_UART1_PORT); + if (status) + dev_err(&port->dev, "failed to send close port command: %d\n", + status); +} + +static void mxu1_handle_new_msr(struct usb_serial_port *port, u8 msr) +{ + struct async_icount *icount; + struct mxu1_port *mxport; + unsigned long flags; + + dev_dbg(&port->dev, "%s - msr 0x%02X\n", __func__, msr); + + mxport = usb_get_serial_port_data(port); + + spin_lock_irqsave(&mxport->spinlock, flags); + mxport->msr = msr & MXU1_MSR_MASK; + spin_unlock_irqrestore(&mxport->spinlock, flags); + + if (msr & MXU1_MSR_DELTA_MASK) { + icount = &port->icount; + if (msr & MXU1_MSR_DELTA_CTS) + icount->cts++; + if (msr & MXU1_MSR_DELTA_DSR) + icount->dsr++; + if (msr & MXU1_MSR_DELTA_CD) + icount->dcd++; + if (msr & MXU1_MSR_DELTA_RI) + icount->rng++; + + wake_up_interruptible(&port->port.delta_msr_wait); + } +} + +static void mxu1_interrupt_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + int length = urb->actual_length; + int function; + int status; + u8 msr; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb shutting down: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status: %d\n", + __func__, urb->status); + goto exit; + } + + if (length != 2) { + dev_dbg(&port->dev, "%s - bad packet size: %d\n", + __func__, length); + goto exit; + } + + if (data[0] == MXU1_CODE_HARDWARE_ERROR) { + dev_err(&port->dev, "%s - hardware error: %d\n", + __func__, data[1]); + goto exit; + } + + function = mxu1_get_func_from_code(data[0]); + + dev_dbg(&port->dev, "%s - function %d, data 0x%02X\n", + __func__, function, data[1]); + + switch (function) { + case MXU1_CODE_DATA_ERROR: + dev_dbg(&port->dev, "%s - DATA ERROR, data 0x%02X\n", + __func__, data[1]); + break; + + case MXU1_CODE_MODEM_STATUS: + msr = data[1]; + mxu1_handle_new_msr(port, msr); + break; + + default: + dev_err(&port->dev, "%s - unknown interrupt code: 0x%02X\n", + __func__, data[1]); + break; + } + +exit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dev_err(&port->dev, "resubmit interrupt urb failed: %d\n", + status); +} + +static struct usb_serial_driver mxuport11_device = { + .driver = { + .owner = THIS_MODULE, + .name = "mxuport11", + }, + .description = "MOXA UPort 11x0", + .id_table = mxu1_idtable, + .num_ports = 1, + .port_probe = mxu1_port_probe, + .attach = mxu1_startup, + .open = mxu1_open, + .close = mxu1_close, + .ioctl = mxu1_ioctl, + .set_termios = mxu1_set_termios, + .tiocmget = mxu1_tiocmget, + .tiocmset = mxu1_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .break_ctl = mxu1_break, + .read_int_callback = mxu1_interrupt_callback, +}; + +static struct usb_serial_driver *const serial_drivers[] = { + &mxuport11_device, NULL +}; + +module_usb_serial_driver(serial_drivers, mxu1_idtable); + +MODULE_AUTHOR("Mathieu Othacehe "); +MODULE_DESCRIPTION("MOXA UPort 11x0 USB to Serial Hub Driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("moxa/moxa-1110.fw"); +MODULE_FIRMWARE("moxa/moxa-1130.fw"); +MODULE_FIRMWARE("moxa/moxa-1131.fw"); +MODULE_FIRMWARE("moxa/moxa-1150.fw"); +MODULE_FIRMWARE("moxa/moxa-1151.fw"); -- cgit v1.2.3 From 924eccc73db2f64a24865d21ff11be8043b24375 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 29 Dec 2015 13:36:11 +0100 Subject: USB: mxu11x0: fix memory leak in port-probe error path Fix memory leak in port-probe error path by verifying the interrupt-in urb before allocating the private data. Signed-off-by: Johan Hovold --- drivers/usb/serial/mxu11x0.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c index 8884ca276e67..89426c3eba98 100644 --- a/drivers/usb/serial/mxu11x0.c +++ b/drivers/usb/serial/mxu11x0.c @@ -333,7 +333,11 @@ static int mxu1_port_probe(struct usb_serial_port *port) { struct mxu1_port *mxport; struct mxu1_device *mxdev; - struct urb *urb; + + if (!port->interrupt_in_urb) { + dev_err(&port->dev, "no interrupt urb\n"); + return -ENODEV; + } mxport = kzalloc(sizeof(struct mxu1_port), GFP_KERNEL); if (!mxport) @@ -344,12 +348,6 @@ static int mxu1_port_probe(struct usb_serial_port *port) mxdev = usb_get_serial_data(port->serial); - urb = port->interrupt_in_urb; - if (!urb) { - dev_err(&port->dev, "%s - no interrupt urb\n", __func__); - return -EINVAL; - } - switch (mxdev->mxd_model) { case MXU1_1110_PRODUCT_ID: case MXU1_1150_PRODUCT_ID: -- cgit v1.2.3 From e69f7a6724182e3f3a4f3d73e74c08dd8f657a9d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 29 Dec 2015 13:36:12 +0100 Subject: USB: mxu11x0: fix memory leak on firmware download Make sure to release the private data before returning -ENODEV after having downloaded the firmware during first probe. Clean up the error paths while at it. Signed-off-by: Johan Hovold --- drivers/usb/serial/mxu11x0.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c index 89426c3eba98..c6c4776997fc 100644 --- a/drivers/usb/serial/mxu11x0.c +++ b/drivers/usb/serial/mxu11x0.c @@ -377,7 +377,6 @@ static int mxu1_startup(struct usb_serial *serial) char fw_name[32]; const struct firmware *fw_p = NULL; int err; - int status = 0; dev_dbg(&serial->interface->dev, "%s - product 0x%04X, num configurations %d, configuration value %d\n", __func__, le16_to_cpu(dev->descriptor.idProduct), @@ -407,22 +406,26 @@ static int mxu1_startup(struct usb_serial *serial) if (err) { dev_err(&serial->interface->dev, "failed to request firmware: %d\n", err); - kfree(mxdev); - return err; + goto err_free_mxdev; } err = mxu1_download_firmware(serial, fw_p); - if (err) { - release_firmware(fw_p); - kfree(mxdev); - return err; - } + if (err) + goto err_release_firmware; - status = -ENODEV; - release_firmware(fw_p); + /* device is being reset */ + err = -ENODEV; + goto err_release_firmware; } - return status; + return 0; + +err_release_firmware: + release_firmware(fw_p); +err_free_mxdev: + kfree(mxdev); + + return err; } static int mxu1_write_byte(struct usb_serial_port *port, u32 addr, -- cgit v1.2.3 From 9631595a253e62a2be7476f99a23ade786829d5f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 29 Dec 2015 13:36:13 +0100 Subject: USB: mxu11x0: fix modem-control handling on B0-transitions Make sure to raise DTR and RTS on transitions from B0 and leave the other bits unchanged. Signed-off-by: Johan Hovold --- drivers/usb/serial/mxu11x0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c index c6c4776997fc..c408cd7b4dc6 100644 --- a/drivers/usb/serial/mxu11x0.c +++ b/drivers/usb/serial/mxu11x0.c @@ -595,7 +595,7 @@ static void mxu1_set_termios(struct tty_struct *tty, if (C_BAUD(tty) == B0) mcr &= ~(MXU1_MCR_DTR | MXU1_MCR_RTS); else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) - mcr |= ~(MXU1_MCR_DTR | MXU1_MCR_RTS); + mcr |= MXU1_MCR_DTR | MXU1_MCR_RTS; status = mxu1_set_mcr(port, mcr); if (status) -- cgit v1.2.3 From 554eb0f205959081361965d04361ef03ca0e066d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 29 Dec 2015 13:36:14 +0100 Subject: USB: mxu11x0: rename usb-serial driver Rename the usb-serial driver "mxu11x0" to match the USB driver name. Signed-off-by: Johan Hovold --- drivers/usb/serial/mxu11x0.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c index c408cd7b4dc6..73cc8564a562 100644 --- a/drivers/usb/serial/mxu11x0.c +++ b/drivers/usb/serial/mxu11x0.c @@ -958,10 +958,10 @@ exit: status); } -static struct usb_serial_driver mxuport11_device = { +static struct usb_serial_driver mxu11x0_device = { .driver = { .owner = THIS_MODULE, - .name = "mxuport11", + .name = "mxu11x0", }, .description = "MOXA UPort 11x0", .id_table = mxu1_idtable, @@ -981,7 +981,7 @@ static struct usb_serial_driver mxuport11_device = { }; static struct usb_serial_driver *const serial_drivers[] = { - &mxuport11_device, NULL + &mxu11x0_device, NULL }; module_usb_serial_driver(serial_drivers, mxu1_idtable); -- cgit v1.2.3 From 3645ea87b9cd50123054aa0fd2417f601c45566f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 29 Dec 2015 13:36:15 +0100 Subject: USB: mxu11x0: fix debug-message typos Fix a couple of debug-message typos, and do some minor clean ups. Signed-off-by: Johan Hovold --- drivers/usb/serial/mxu11x0.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c index 73cc8564a562..c8c959679827 100644 --- a/drivers/usb/serial/mxu11x0.c +++ b/drivers/usb/serial/mxu11x0.c @@ -1,5 +1,4 @@ /* - * * USB Moxa UPORT 11x0 Serial Driver * * Copyright (C) 2007 MOXA Technologies Co., Ltd. @@ -494,10 +493,10 @@ static void mxu1_set_termios(struct tty_struct *tty, } dev_dbg(&port->dev, - "%s - clfag %08x, iflag %08x\n", __func__, cflag, iflag); + "%s - cflag 0x%08x, iflag 0x%08x\n", __func__, cflag, iflag); if (old_termios) { - dev_dbg(&port->dev, "%s - old clfag %08x, old iflag %08x\n", + dev_dbg(&port->dev, "%s - old cflag 0x%08x, old iflag 0x%08x\n", __func__, old_termios->c_cflag, old_termios->c_iflag); @@ -764,7 +763,6 @@ static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) mxport->msr = 0; - dev_dbg(&port->dev, "%s - start interrupt in urb\n", __func__); status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); if (status) { dev_err(&port->dev, "failed to submit interrupt urb: %d\n", @@ -842,7 +840,7 @@ static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) goto unlink_int_urb; } - return status; + return 0; unlink_int_urb: usb_kill_urb(port->interrupt_in_urb); @@ -859,21 +857,20 @@ static void mxu1_close(struct usb_serial_port *port) status = mxu1_send_ctrl_urb(port->serial, MXU1_CLOSE_PORT, 0, MXU1_UART1_PORT); - if (status) + if (status) { dev_err(&port->dev, "failed to send close port command: %d\n", status); + } } static void mxu1_handle_new_msr(struct usb_serial_port *port, u8 msr) { + struct mxu1_port *mxport = usb_get_serial_port_data(port); struct async_icount *icount; - struct mxu1_port *mxport; unsigned long flags; dev_dbg(&port->dev, "%s - msr 0x%02X\n", __func__, msr); - mxport = usb_get_serial_port_data(port); - spin_lock_irqsave(&mxport->spinlock, flags); mxport->msr = msr & MXU1_MSR_MASK; spin_unlock_irqrestore(&mxport->spinlock, flags); @@ -953,9 +950,10 @@ static void mxu1_interrupt_callback(struct urb *urb) exit: status = usb_submit_urb(urb, GFP_ATOMIC); - if (status) + if (status) { dev_err(&port->dev, "resubmit interrupt urb failed: %d\n", status); + } } static struct usb_serial_driver mxu11x0_device = { -- cgit v1.2.3 From 6ff9d2761b8655991f48f113f7edaefea5c92905 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 29 Dec 2015 13:36:16 +0100 Subject: USB: mxu11x0: drop redundant function name from error messages Drop redundant function name from a few error messages. Drop redundant error message when generic open fails. Signed-off-by: Johan Hovold --- drivers/usb/serial/mxu11x0.c | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mxu11x0.c b/drivers/usb/serial/mxu11x0.c index c8c959679827..e3c3f57c2d82 100644 --- a/drivers/usb/serial/mxu11x0.c +++ b/drivers/usb/serial/mxu11x0.c @@ -776,24 +776,22 @@ static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, open_settings, MXU1_UART1_PORT); if (status) { - dev_err(&port->dev, "%s - cannot send open command: %d\n", - __func__, status); + dev_err(&port->dev, "cannot send open command: %d\n", status); goto unlink_int_urb; } status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, 0, MXU1_UART1_PORT); if (status) { - dev_err(&port->dev, "%s - cannot send start command: %d\n", - __func__, status); + dev_err(&port->dev, "cannot send start command: %d\n", status); goto unlink_int_urb; } status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, MXU1_PURGE_INPUT, MXU1_UART1_PORT); if (status) { - dev_err(&port->dev, "%s - cannot clear input buffers: %d\n", - __func__, status); + dev_err(&port->dev, "cannot clear input buffers: %d\n", + status); goto unlink_int_urb; } @@ -801,8 +799,8 @@ static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT, MXU1_PURGE_OUTPUT, MXU1_UART1_PORT); if (status) { - dev_err(&port->dev, "%s - cannot clear output buffers: %d\n", - __func__, status); + dev_err(&port->dev, "cannot clear output buffers: %d\n", + status); goto unlink_int_urb; } @@ -820,25 +818,20 @@ static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port) status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT, open_settings, MXU1_UART1_PORT); if (status) { - dev_err(&port->dev, "%s - cannot send open command: %d\n", - __func__, status); + dev_err(&port->dev, "cannot send open command: %d\n", status); goto unlink_int_urb; } status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT, 0, MXU1_UART1_PORT); if (status) { - dev_err(&port->dev, "%s - cannot send start command: %d\n", - __func__, status); + dev_err(&port->dev, "cannot send start command: %d\n", status); goto unlink_int_urb; } status = usb_serial_generic_open(tty, port); - if (status) { - dev_err(&port->dev, "%s - submit read urb failed: %d\n", - __func__, status); + if (status) goto unlink_int_urb; - } return 0; @@ -921,8 +914,7 @@ static void mxu1_interrupt_callback(struct urb *urb) } if (data[0] == MXU1_CODE_HARDWARE_ERROR) { - dev_err(&port->dev, "%s - hardware error: %d\n", - __func__, data[1]); + dev_err(&port->dev, "hardware error: %d\n", data[1]); goto exit; } @@ -943,8 +935,8 @@ static void mxu1_interrupt_callback(struct urb *urb) break; default: - dev_err(&port->dev, "%s - unknown interrupt code: 0x%02X\n", - __func__, data[1]); + dev_err(&port->dev, "unknown interrupt code: 0x%02X\n", + data[1]); break; } -- cgit v1.2.3 From 46c236dc7d1212d7417e6fb0317f91c44c719322 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 26 Dec 2015 22:57:44 +0100 Subject: USB: usbmon: remove assignment from IS_ERR argument The semantic patch that makes this change is as follows: (http://coccinelle.lip6.fr/) // @@ expression e1,e2; statement S1,S2; @@ +e1 = e2; if (IS_ERR( e1 - = e2 )) S1 else S2 // Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mon/mon_text.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mon/mon_text.c b/drivers/usb/mon/mon_text.c index 98e4f63e6823..e59334b09c41 100644 --- a/drivers/usb/mon/mon_text.c +++ b/drivers/usb/mon/mon_text.c @@ -387,7 +387,8 @@ static ssize_t mon_text_read_t(struct file *file, char __user *buf, struct mon_event_text *ep; struct mon_text_ptr ptr; - if (IS_ERR(ep = mon_text_read_wait(rp, file))) + ep = mon_text_read_wait(rp, file); + if (IS_ERR(ep)) return PTR_ERR(ep); mutex_lock(&rp->printf_lock); ptr.cnt = 0; @@ -414,7 +415,8 @@ static ssize_t mon_text_read_u(struct file *file, char __user *buf, struct mon_event_text *ep; struct mon_text_ptr ptr; - if (IS_ERR(ep = mon_text_read_wait(rp, file))) + ep = mon_text_read_wait(rp, file); + if (IS_ERR(ep)) return PTR_ERR(ep); mutex_lock(&rp->printf_lock); ptr.cnt = 0; -- cgit v1.2.3