From 92c9691bf38007853345f719543908d1383e3e46 Mon Sep 17 00:00:00 2001 From: Lin Wang Date: Fri, 9 Jan 2015 16:06:27 +0200 Subject: xhci: Clean up work to xhci_add_endpoint(). This patch removes unused variable "out_ctx" and avoid multiple calls to function xhci_get_endpoint_flag(). Signed-off-by: Lin Wang Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 01fcbb5..93200db 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1714,7 +1714,7 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep) { struct xhci_hcd *xhci; - struct xhci_container_ctx *in_ctx, *out_ctx; + struct xhci_container_ctx *in_ctx; unsigned int ep_index; struct xhci_input_control_ctx *ctrl_ctx; u32 added_ctxs; @@ -1745,7 +1745,6 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, virt_dev = xhci->devs[udev->slot_id]; in_ctx = virt_dev->in_ctx; - out_ctx = virt_dev->out_ctx; ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", @@ -1758,8 +1757,7 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, * to add it again without dropping it, reject the addition. */ if (virt_dev->eps[ep_index].ring && - !(le32_to_cpu(ctrl_ctx->drop_flags) & - xhci_get_endpoint_flag(&ep->desc))) { + !(le32_to_cpu(ctrl_ctx->drop_flags) & added_ctxs)) { xhci_warn(xhci, "Trying to add endpoint 0x%x " "without dropping it.\n", (unsigned int) ep->desc.bEndpointAddress); @@ -1769,8 +1767,7 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, /* If the HCD has already noted the endpoint is enabled, * ignore this request. */ - if (le32_to_cpu(ctrl_ctx->add_flags) & - xhci_get_endpoint_flag(&ep->desc)) { + if (le32_to_cpu(ctrl_ctx->add_flags) & added_ctxs) { xhci_warn(xhci, "xHCI %s called with enabled ep %p\n", __func__, ep); return 0; -- cgit v0.10.2 From dc0b177cf8be1e7371cfb92cfbccac595bf8dac8 Mon Sep 17 00:00:00 2001 From: Lin Wang Date: Fri, 9 Jan 2015 16:06:28 +0200 Subject: xhci: remove unused parameter 'xhci' in function xhci_handshake(). Parameter 'xhci' is no longer be used in function xhci_handshake(), just remove it. Signed-off-by: Lin Wang Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index e692e76..c84a959 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -299,7 +299,7 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci) * seconds), then it should assume that the there are * larger problems with the xHC and assert HCRST. */ - ret = xhci_handshake(xhci, &xhci->op_regs->cmd_ring, + ret = xhci_handshake(&xhci->op_regs->cmd_ring, CMD_RING_RUNNING, 0, 5 * 1000 * 1000); if (ret < 0) { xhci_err(xhci, "Stopped the command ring failed, " diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 93200db..5ec74e2 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -60,8 +60,7 @@ MODULE_PARM_DESC(quirks, "Bit flags for quirks to be enabled as default"); * handshake done). There are two failure modes: "usec" have passed (major * hardware flakeout), or the register reads as all-ones (hardware removed). */ -int xhci_handshake(struct xhci_hcd *xhci, void __iomem *ptr, - u32 mask, u32 done, int usec) +int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec) { u32 result; @@ -111,7 +110,7 @@ int xhci_halt(struct xhci_hcd *xhci) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Halt the HC"); xhci_quiesce(xhci); - ret = xhci_handshake(xhci, &xhci->op_regs->status, + ret = xhci_handshake(&xhci->op_regs->status, STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC); if (!ret) { xhci->xhc_state |= XHCI_STATE_HALTED; @@ -140,7 +139,7 @@ static int xhci_start(struct xhci_hcd *xhci) * Wait for the HCHalted Status bit to be 0 to indicate the host is * running. */ - ret = xhci_handshake(xhci, &xhci->op_regs->status, + ret = xhci_handshake(&xhci->op_regs->status, STS_HALT, 0, XHCI_MAX_HALT_USEC); if (ret == -ETIMEDOUT) xhci_err(xhci, "Host took too long to start, " @@ -175,7 +174,7 @@ int xhci_reset(struct xhci_hcd *xhci) command |= CMD_RESET; writel(command, &xhci->op_regs->command); - ret = xhci_handshake(xhci, &xhci->op_regs->command, + ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, 10 * 1000 * 1000); if (ret) return ret; @@ -186,7 +185,7 @@ int xhci_reset(struct xhci_hcd *xhci) * xHCI cannot write to any doorbells or operational registers other * than status until the "Controller Not Ready" flag is cleared. */ - ret = xhci_handshake(xhci, &xhci->op_regs->status, + ret = xhci_handshake(&xhci->op_regs->status, STS_CNR, 0, 10 * 1000 * 1000); for (i = 0; i < 2; ++i) { @@ -929,7 +928,7 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) /* Some chips from Fresco Logic need an extraordinary delay */ delay *= (xhci->quirks & XHCI_SLOW_SUSPEND) ? 10 : 1; - if (xhci_handshake(xhci, &xhci->op_regs->status, + if (xhci_handshake(&xhci->op_regs->status, STS_HALT, STS_HALT, delay)) { xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n"); spin_unlock_irq(&xhci->lock); @@ -944,7 +943,7 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) command = readl(&xhci->op_regs->command); command |= CMD_CSS; writel(command, &xhci->op_regs->command); - if (xhci_handshake(xhci, &xhci->op_regs->status, + if (xhci_handshake(&xhci->op_regs->status, STS_SAVE, 0, 10 * 1000)) { xhci_warn(xhci, "WARN: xHC save state timeout\n"); spin_unlock_irq(&xhci->lock); @@ -1011,7 +1010,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) command = readl(&xhci->op_regs->command); command |= CMD_CRS; writel(command, &xhci->op_regs->command); - if (xhci_handshake(xhci, &xhci->op_regs->status, + if (xhci_handshake(&xhci->op_regs->status, STS_RESTORE, 0, 10 * 1000)) { xhci_warn(xhci, "WARN: xHC restore state timeout\n"); spin_unlock_irq(&xhci->lock); @@ -1082,7 +1081,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) command = readl(&xhci->op_regs->command); command |= CMD_RUN; writel(command, &xhci->op_regs->command); - xhci_handshake(xhci, &xhci->op_regs->status, STS_HALT, + xhci_handshake(&xhci->op_regs->status, STS_HALT, 0, 250 * 1000); /* step 5: walk topology and initialize portsc, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index cc7c5bb..eeea5c0 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1733,8 +1733,7 @@ void xhci_free_command(struct xhci_hcd *xhci, /* xHCI host controller glue */ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); -int xhci_handshake(struct xhci_hcd *xhci, void __iomem *ptr, - u32 mask, u32 done, int usec); +int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec); void xhci_quiesce(struct xhci_hcd *xhci); int xhci_halt(struct xhci_hcd *xhci); int xhci_reset(struct xhci_hcd *xhci); -- cgit v0.10.2 From fc8abe02e5616817735e8a8de2ab37f6ce5e4f46 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 9 Jan 2015 16:06:29 +0200 Subject: xhci: Use setup_timer Convert a call to init_timer and accompanying intializations of the timer's data and function fields to a call to setup_timer. A simplified version of the semantic match that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression t,f,d; @@ -init_timer(&t); +setup_timer(&t,f,d); -t.data = d; -t.function = f; // Signed-off-by: Julia Lawall Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 5ec74e2..1e3f39c 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -472,10 +472,8 @@ static void compliance_mode_recovery(unsigned long arg) static void compliance_mode_recovery_timer_init(struct xhci_hcd *xhci) { xhci->port_status_u0 = 0; - init_timer(&xhci->comp_mode_recovery_timer); - - xhci->comp_mode_recovery_timer.data = (unsigned long) xhci; - xhci->comp_mode_recovery_timer.function = compliance_mode_recovery; + setup_timer(&xhci->comp_mode_recovery_timer, + compliance_mode_recovery, (unsigned long)xhci); xhci->comp_mode_recovery_timer.expires = jiffies + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS); -- cgit v0.10.2 From 9e08a03dc12a41ce695559f8c6d999aaf245b8be Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 9 Jan 2015 16:06:30 +0200 Subject: xhci-mem: Use setup_timer Convert a call to init_timer and accompanying intializations of the timer's data and function fields to a call to setup_timer. A simplified version of the semantic match that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression t,f,d; @@ -init_timer(&t); +setup_timer(&t,f,d); -t.data = d; -t.function = f; // Signed-off-by: Julia Lawall Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 5cb3d7a..e72265c 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -833,9 +833,8 @@ void xhci_free_stream_info(struct xhci_hcd *xhci, static void xhci_init_endpoint_timer(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) { - init_timer(&ep->stop_cmd_timer); - ep->stop_cmd_timer.data = (unsigned long) ep; - ep->stop_cmd_timer.function = xhci_stop_endpoint_command_watchdog; + setup_timer(&ep->stop_cmd_timer, xhci_stop_endpoint_command_watchdog, + (unsigned long)ep); ep->xhci = xhci; } @@ -2509,9 +2508,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci_print_ir_set(xhci, 0); /* init command timeout timer */ - init_timer(&xhci->cmd_timer); - xhci->cmd_timer.data = (unsigned long) xhci; - xhci->cmd_timer.function = xhci_handle_command_timeout; + setup_timer(&xhci->cmd_timer, xhci_handle_command_timeout, + (unsigned long)xhci); /* * XXX: Might need to set the Interrupter Moderation Register to -- cgit v0.10.2 From 4daf9df51fbfb03b8c2bcb538cb467f7c7af1f11 Mon Sep 17 00:00:00 2001 From: Lin Wang Date: Fri, 9 Jan 2015 16:06:31 +0200 Subject: xhci: clean up work to remove unused parameters for functions in xhci-mem.c Some parameters are not used by functions in xhci-mem.c, just remove it. Changes compared to v1: - Rebase to the latest usb-next branch Signed-off-by: Lin Wang Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index bb89175..745717e 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -552,7 +552,7 @@ void xhci_dbg_ctx(struct xhci_hcd *xhci, if (ctx->type == XHCI_CTX_TYPE_INPUT) { struct xhci_input_control_ctx *ctrl_ctx = - xhci_get_input_control_ctx(xhci, ctx); + xhci_get_input_control_ctx(ctx); if (!ctrl_ctx) { xhci_warn(xhci, "Could not get input context, bad type.\n"); return; diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index e72265c..f833640 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -535,7 +535,7 @@ static void xhci_free_container_ctx(struct xhci_hcd *xhci, kfree(ctx); } -struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, +struct xhci_input_control_ctx *xhci_get_input_control_ctx( struct xhci_container_ctx *ctx) { if (ctx->type != XHCI_CTX_TYPE_INPUT) @@ -784,8 +784,7 @@ void xhci_setup_streams_ep_input_ctx(struct xhci_hcd *xhci, * Reinstalls the "normal" endpoint ring (at its previous dequeue mark, * not at the beginning of the ring). */ -void xhci_setup_no_streams_ep_input_ctx(struct xhci_hcd *xhci, - struct xhci_ep_ctx *ep_ctx, +void xhci_setup_no_streams_ep_input_ctx(struct xhci_ep_ctx *ep_ctx, struct xhci_virt_ep *ep) { dma_addr_t addr; @@ -1341,8 +1340,7 @@ static u32 xhci_get_endpoint_mult(struct usb_device *udev, return ep->ss_ep_comp.bmAttributes; } -static u32 xhci_get_endpoint_type(struct usb_device *udev, - struct usb_host_endpoint *ep) +static u32 xhci_get_endpoint_type(struct usb_host_endpoint *ep) { int in; u32 type; @@ -1375,8 +1373,7 @@ static u32 xhci_get_endpoint_type(struct usb_device *udev, * Basically, this is the maxpacket size, multiplied by the burst size * and mult size. */ -static u32 xhci_get_max_esit_payload(struct xhci_hcd *xhci, - struct usb_device *udev, +static u32 xhci_get_max_esit_payload(struct usb_device *udev, struct usb_host_endpoint *ep) { int max_burst; @@ -1417,7 +1414,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); - endpoint_type = xhci_get_endpoint_type(udev, ep); + endpoint_type = xhci_get_endpoint_type(ep); if (!endpoint_type) return -EINVAL; ep_ctx->ep_info2 = cpu_to_le32(endpoint_type); @@ -1483,7 +1480,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, } ep_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(max_packet) | MAX_BURST(max_burst)); - max_esit_payload = xhci_get_max_esit_payload(xhci, udev, ep); + max_esit_payload = xhci_get_max_esit_payload(udev, ep); ep_ctx->tx_info = cpu_to_le32(MAX_ESIT_PAYLOAD_FOR_EP(max_esit_payload)); /* @@ -1772,7 +1769,7 @@ struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci, return command; } -void xhci_urb_free_priv(struct xhci_hcd *xhci, struct urb_priv *urb_priv) +void xhci_urb_free_priv(struct urb_priv *urb_priv) { if (urb_priv) { kfree(urb_priv->td[0]); @@ -1925,7 +1922,7 @@ static int xhci_test_trb_in_td(struct xhci_hcd *xhci, } /* TRB math checks for xhci_trb_in_td(), using the command and event rings. */ -static int xhci_check_trb_in_td_math(struct xhci_hcd *xhci, gfp_t mem_flags) +static int xhci_check_trb_in_td_math(struct xhci_hcd *xhci) { struct { dma_addr_t input_dma; @@ -2451,7 +2448,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) flags); if (!xhci->event_ring) goto fail; - if (xhci_check_trb_in_td_math(xhci, flags) < 0) + if (xhci_check_trb_in_td_math(xhci) < 0) goto fail; xhci->erst.entries = dma_alloc_coherent(dev, diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index c84a959..067f18a 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -609,7 +609,7 @@ static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci, spin_unlock(&xhci->lock); usb_hcd_giveback_urb(hcd, urb, status); - xhci_urb_free_priv(xhci, urb_priv); + xhci_urb_free_priv(urb_priv); spin_lock(&xhci->lock); } } @@ -1110,7 +1110,7 @@ static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, * is not waiting on the configure endpoint command. */ virt_dev = xhci->devs[slot_id]; - ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "Could not get input context, bad type.\n"); return; @@ -2497,7 +2497,7 @@ cleanup: urb = td->urb; urb_priv = urb->hcpriv; - xhci_urb_free_priv(xhci, urb_priv); + xhci_urb_free_priv(urb_priv); usb_hcd_unlink_urb_from_ep(bus_to_hcd(urb->dev->bus), urb); if ((urb->actual_length != urb->transfer_buffer_length && diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 1e3f39c..e282765 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1273,7 +1273,7 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id, return -ENOMEM; command->in_ctx = xhci->devs[slot_id]->in_ctx; - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -1371,7 +1371,7 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) ret = xhci_check_maxpacket(xhci, slot_id, ep_index, urb); if (ret < 0) { - xhci_urb_free_priv(xhci, urb_priv); + xhci_urb_free_priv(urb_priv); urb->hcpriv = NULL; return ret; } @@ -1437,7 +1437,7 @@ dying: urb->ep->desc.bEndpointAddress, urb); ret = -ESHUTDOWN; free_priv: - xhci_urb_free_priv(xhci, urb_priv); + xhci_urb_free_priv(urb_priv); urb->hcpriv = NULL; spin_unlock_irqrestore(&xhci->lock, flags); return ret; @@ -1550,7 +1550,7 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) usb_hcd_unlink_urb_from_ep(hcd, urb); spin_unlock_irqrestore(&xhci->lock, flags); usb_hcd_giveback_urb(hcd, urb, -ESHUTDOWN); - xhci_urb_free_priv(xhci, urb_priv); + xhci_urb_free_priv(urb_priv); return ret; } if ((xhci->xhc_state & XHCI_STATE_DYING) || @@ -1657,7 +1657,7 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, in_ctx = xhci->devs[udev->slot_id]->in_ctx; out_ctx = xhci->devs[udev->slot_id]->out_ctx; - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -1742,7 +1742,7 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, virt_dev = xhci->devs[udev->slot_id]; in_ctx = virt_dev->in_ctx; - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -1810,7 +1810,7 @@ static void xhci_zero_in_ctx(struct xhci_hcd *xhci, struct xhci_virt_device *vir struct xhci_slot_ctx *slot_ctx; int i; - ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -2536,7 +2536,7 @@ static int xhci_reserve_bandwidth(struct xhci_hcd *xhci, if (virt_dev->tt_info) old_active_eps = virt_dev->tt_info->active_eps; - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -2633,7 +2633,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, spin_lock_irqsave(&xhci->lock, flags); virt_dev = xhci->devs[udev->slot_id]; - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); if (!ctrl_ctx) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_warn(xhci, "%s: Could not get input context, bad type.\n", @@ -2752,7 +2752,7 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) command->in_ctx = virt_dev->in_ctx; /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -2877,7 +2877,7 @@ static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, dma_addr_t addr; in_ctx = xhci->devs[slot_id]->in_ctx; - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -3167,7 +3167,7 @@ int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); return -ENOMEM; } - ctrl_ctx = xhci_get_input_control_ctx(xhci, config_cmd->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(config_cmd->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -3322,7 +3322,7 @@ int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, */ ep_index = xhci_get_endpoint_index(&eps[0]->desc); command = vdev->eps[ep_index].stream_info->free_streams_command; - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); if (!ctrl_ctx) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_warn(xhci, "%s: Could not get input context, bad type.\n", @@ -3340,7 +3340,7 @@ int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, xhci_endpoint_copy(xhci, command->in_ctx, vdev->out_ctx, ep_index); - xhci_setup_no_streams_ep_input_ctx(xhci, ep_ctx, + xhci_setup_no_streams_ep_input_ctx(ep_ctx, &vdev->eps[ep_index]); } xhci_setup_input_ctx_for_config_ep(xhci, command->in_ctx, @@ -3805,7 +3805,7 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, command->completion = &xhci->addr_dev; slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); - ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); @@ -3988,7 +3988,7 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci, /* Attempt to issue an Evaluate Context command to change the MEL. */ command = xhci->lpm_command; - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); if (!ctrl_ctx) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_warn(xhci, "%s: Could not get input context, bad type.\n", @@ -4726,7 +4726,7 @@ int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); return -ENOMEM; } - ctrl_ctx = xhci_get_input_control_ctx(xhci, config_cmd->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(config_cmd->in_ctx); if (!ctrl_ctx) { xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index eeea5c0..e23f31d 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1712,8 +1712,7 @@ void xhci_free_stream_info(struct xhci_hcd *xhci, void xhci_setup_streams_ep_input_ctx(struct xhci_hcd *xhci, struct xhci_ep_ctx *ep_ctx, struct xhci_stream_info *stream_info); -void xhci_setup_no_streams_ep_input_ctx(struct xhci_hcd *xhci, - struct xhci_ep_ctx *ep_ctx, +void xhci_setup_no_streams_ep_input_ctx(struct xhci_ep_ctx *ep_ctx, struct xhci_virt_ep *ep); void xhci_free_device_endpoint_resources(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, bool drop_control_ep); @@ -1727,7 +1726,7 @@ struct xhci_ring *xhci_stream_id_to_ring( struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci, bool allocate_in_ctx, bool allocate_completion, gfp_t mem_flags); -void xhci_urb_free_priv(struct xhci_hcd *xhci, struct urb_priv *urb_priv); +void xhci_urb_free_priv(struct urb_priv *urb_priv); void xhci_free_command(struct xhci_hcd *xhci, struct xhci_command *command); @@ -1863,7 +1862,7 @@ int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); /* xHCI contexts */ -struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); +struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_container_ctx *ctx); struct xhci_slot_ctx *xhci_get_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, unsigned int ep_index); -- cgit v0.10.2 From 86cd740a621d4571252f04b2d618fd054e4ddfc4 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 9 Jan 2015 16:06:32 +0200 Subject: xhci: Add completion code to the debug ouput of unhandled transfer events Helps debugging to know the unhandled event type. Also make the debug message grepable Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 067f18a..88da8d6 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2354,8 +2354,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, status = 0; break; } - xhci_warn(xhci, "ERROR Unknown event condition, HC probably " - "busted\n"); + xhci_warn(xhci, "ERROR Unknown event condition %u, HC probably busted\n", + trb_comp_code); goto cleanup; } -- cgit v0.10.2 From 5efd2ea8c9f4f12916ffc8ba636792ce052f6911 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 5 Dec 2014 15:13:54 +0100 Subject: usb: core: buffer: smallest buffer should start at ARCH_DMA_MINALIGN the following error pops up during "testusb -a -t 10" | musb-hdrc musb-hdrc.1.auto: dma_pool_free buffer-128, f134e000/be842000 (bad dma) hcd_buffer_create() creates a few buffers, the smallest has 32 bytes of size. ARCH_KMALLOC_MINALIGN is set to 64 bytes. This combo results in hcd_buffer_alloc() returning memory which is 32 bytes aligned and it might by identified by buffer_offset() as another buffer. This means the buffer which is on a 32 byte boundary will not get freed, instead it tries to free another buffer with the error message. This patch fixes the issue by creating the smallest DMA buffer with the size of ARCH_KMALLOC_MINALIGN (or 32 in case ARCH_KMALLOC_MINALIGN is smaller). This might be 32, 64 or even 128 bytes. The next three pools will have the size 128, 512 and 2048. In case the smallest pool is 128 bytes then we have only three pools instead of four (and zero the first entry in the array). The last pool size is always 2048 bytes which is the assumed PAGE_SIZE / 2 of 4096. I doubt it makes sense to continue using PAGE_SIZE / 2 where we would end up with 8KiB buffer in case we have 16KiB pages. Instead I think it makes sense to have a common size(s) and extend them if there is need to. There is a BUILD_BUG_ON() now in case someone has a minalign of more than 128 bytes. Cc: stable@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c index 684ef70..506b969 100644 --- a/drivers/usb/core/buffer.c +++ b/drivers/usb/core/buffer.c @@ -22,17 +22,25 @@ */ /* FIXME tune these based on pool statistics ... */ -static const size_t pool_max[HCD_BUFFER_POOLS] = { - /* platforms without dma-friendly caches might need to - * prevent cacheline sharing... - */ - 32, - 128, - 512, - PAGE_SIZE / 2 - /* bigger --> allocate pages */ +static size_t pool_max[HCD_BUFFER_POOLS] = { + 32, 128, 512, 2048, }; +void __init usb_init_pool_max(void) +{ + /* + * The pool_max values must never be smaller than + * ARCH_KMALLOC_MINALIGN. + */ + if (ARCH_KMALLOC_MINALIGN <= 32) + ; /* Original value is okay */ + else if (ARCH_KMALLOC_MINALIGN <= 64) + pool_max[0] = 64; + else if (ARCH_KMALLOC_MINALIGN <= 128) + pool_max[0] = 0; /* Don't use this pool */ + else + BUILD_BUG(); /* We don't allow this */ +} /* SETUP primitives */ diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 2a92b97..b1fb9ae 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1049,6 +1049,7 @@ static int __init usb_init(void) pr_info("%s: USB support disabled\n", usbcore_name); return 0; } + usb_init_pool_max(); retval = usb_debugfs_init(); if (retval) diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 086bf13..8968f61 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -453,6 +453,7 @@ extern const struct dev_pm_ops usb_hcd_pci_pm_ops; #endif /* CONFIG_PCI */ /* pci-ish (pdev null is ok) buffer alloc/mapping support */ +void usb_init_pool_max(void); int hcd_buffer_create(struct usb_hcd *hcd); void hcd_buffer_destroy(struct usb_hcd *hcd); -- cgit v0.10.2 From 788bfe88d552b89842b028ca78e5451a1e64b21b Mon Sep 17 00:00:00 2001 From: Asaf Vertz Date: Mon, 15 Dec 2014 09:22:07 +0200 Subject: usb: host: max3421-hcd: use time_after() To be future-proof and for better readability the time comparisons are modified to use time_after() instead of plain, error-prone math. Signed-off-by: Asaf Vertz Acked-by: David Mosberger Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index 6234c75..aaaff94 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -55,6 +55,7 @@ * single thread (max3421_spi_thread). */ +#include #include #include #include @@ -1291,7 +1292,7 @@ max3421_handle_irqs(struct usb_hcd *hcd) char sbuf[16 * 16], *dp, *end; int i; - if (jiffies - last_time > 5*HZ) { + if (time_after(jiffies, last_time + 5*HZ)) { dp = sbuf; end = sbuf + sizeof(sbuf); *dp = '\0'; -- cgit v0.10.2 From 78a629e95137bb4d39d5e05d03273685401dce06 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 29 Dec 2014 14:35:28 +0200 Subject: USB: use %*ph specifier in mikrotek driver There is a %*ph specifier that allows to dump small buffers. This patch converts the code to use the specifier. Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 37b44b0..6431d08 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -299,9 +299,7 @@ static inline void mts_show_command(struct scsi_cmnd *srb) MTS_DEBUG( "Command %s (%d bytes)\n", what, srb->cmd_len); out: - MTS_DEBUG( " %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", - srb->cmnd[0], srb->cmnd[1], srb->cmnd[2], srb->cmnd[3], srb->cmnd[4], srb->cmnd[5], - srb->cmnd[6], srb->cmnd[7], srb->cmnd[8], srb->cmnd[9]); + MTS_DEBUG( " %10ph\n", srb->cmnd); } #else -- cgit v0.10.2 From 5d31a6dc78b414406b04bc76f7cce19390663fc7 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 29 Dec 2014 14:35:29 +0200 Subject: USB: use %*ph specifier in uss720 driver There is a %*ph specifier that allows to dump small buffers. This patch converts the code to use the specifier. Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index 40ef40a..588d62a 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -124,12 +124,8 @@ static void async_complete(struct urb *urb) } else if (rq->dr->bRequest == 3) { memcpy(priv->reg, rq->reg, sizeof(priv->reg)); #if 0 - dev_dbg(&priv->usbdev->dev, - "async_complete regs %02x %02x %02x %02x %02x %02x %02x\n", - (unsigned int)priv->reg[0], (unsigned int)priv->reg[1], - (unsigned int)priv->reg[2], (unsigned int)priv->reg[3], - (unsigned int)priv->reg[4], (unsigned int)priv->reg[5], - (unsigned int)priv->reg[6]); + dev_dbg(&priv->usbdev->dev, "async_complete regs %7ph\n", + priv->reg); #endif /* if nAck interrupts are enabled and we have an interrupt, call the interrupt procedure */ if (rq->reg[2] & rq->reg[1] & 0x10 && pp) @@ -742,9 +738,7 @@ static int uss720_probe(struct usb_interface *intf, set_1284_register(pp, 2, 0x0c, GFP_KERNEL); /* debugging */ get_1284_register(pp, 0, ®, GFP_KERNEL); - dev_dbg(&intf->dev, "reg: %02x %02x %02x %02x %02x %02x %02x\n", - priv->reg[0], priv->reg[1], priv->reg[2], priv->reg[3], - priv->reg[4], priv->reg[5], priv->reg[6]); + dev_dbg(&intf->dev, "reg: %7ph\n", priv->reg); endpoint = &interface->endpoint[2]; dev_dbg(&intf->dev, "epaddr %d interval %d\n", -- cgit v0.10.2 From 886f310b0aee256103822e7e3062861c3357e87e Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 26 Dec 2014 15:35:54 +0100 Subject: usb: r8a66597-hcd: Use setup_timer Convert a call to init_timer and accompanying intializations of the timer's data and function fields to a call to setup_timer. A simplified version of the semantic match that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression t,f,d; @@ -init_timer(&t); +setup_timer(&t,f,d); -t.function = f; -t.data = d; // Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index c4bcfae..a048b8e 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2483,9 +2483,8 @@ static int r8a66597_probe(struct platform_device *pdev) r8a66597->max_root_hub = 2; spin_lock_init(&r8a66597->lock); - init_timer(&r8a66597->rh_timer); - r8a66597->rh_timer.function = r8a66597_timer; - r8a66597->rh_timer.data = (unsigned long)r8a66597; + setup_timer(&r8a66597->rh_timer, r8a66597_timer, + (unsigned long)r8a66597); r8a66597->reg = reg; /* make sure no interrupts are pending */ @@ -2496,9 +2495,8 @@ static int r8a66597_probe(struct platform_device *pdev) for (i = 0; i < R8A66597_MAX_NUM_PIPE; i++) { INIT_LIST_HEAD(&r8a66597->pipe_queue[i]); - init_timer(&r8a66597->td_timer[i]); - r8a66597->td_timer[i].function = r8a66597_td_timer; - r8a66597->td_timer[i].data = (unsigned long)r8a66597; + setup_timer(&r8a66597->td_timer[i], r8a66597_td_timer, + (unsigned long)r8a66597); setup_timer(&r8a66597->interval_timer[i], r8a66597_interval_timer, (unsigned long)r8a66597); -- cgit v0.10.2 From 25821b4a7a39d3ae5757762076367827a5f1145c Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 26 Dec 2014 15:35:53 +0100 Subject: usb: oxu210hp-hcd: Use setup_timer Convert a call to init_timer and accompanying intializations of the timer's data and function fields to a call to setup_timer. A simplified version of the semantic match that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression t,f,d; @@ -init_timer(&t); +setup_timer(&t,f,d); -t.function = f; -t.data = d; // Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 036924e..ea0ecbc 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -2598,9 +2598,7 @@ static int oxu_hcd_init(struct usb_hcd *hcd) spin_lock_init(&oxu->lock); - init_timer(&oxu->watchdog); - oxu->watchdog.function = oxu_watchdog; - oxu->watchdog.data = (unsigned long) oxu; + setup_timer(&oxu->watchdog, oxu_watchdog, (unsigned long)oxu); /* * hw default: 1K periodic list heads, one per frame. -- cgit v0.10.2 From b4ab5e85d6e7d902a4efbfaf96abc889b335b10a Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 26 Dec 2014 15:35:52 +0100 Subject: usb: sl811-hcd: Use setup_timer Convert a call to init_timer and accompanying intializations of the timer's data and function fields to a call to setup_timer. A simplified version of the semantic match that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression t,f,d; @@ -init_timer(&t); +setup_timer(&t,f,d); -t.function = f; -t.data = d; // Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index 25fb1da..cef3140 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -1691,9 +1691,7 @@ sl811h_probe(struct platform_device *dev) spin_lock_init(&sl811->lock); INIT_LIST_HEAD(&sl811->async); sl811->board = dev_get_platdata(&dev->dev); - init_timer(&sl811->timer); - sl811->timer.function = sl811h_timer; - sl811->timer.data = (unsigned long) sl811; + setup_timer(&sl811->timer, sl811h_timer, (unsigned long)sl811); sl811->addr_reg = addr_reg; sl811->data_reg = data_reg; -- cgit v0.10.2 From b7aa4cc32c94c0f304e88bdcc996fef9c38f40d8 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 26 Dec 2014 15:35:51 +0100 Subject: usb: isp1760: Use setup_timer Convert a call to init_timer and accompanying intializations of the timer's data and function fields to a call to setup_timer. A simplified version of the semantic match that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression t,f,d; @@ -init_timer(&t); +setup_timer(&t,f,d); -t.function = f; -t.data = d; // Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 395649f..79261d5 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -1359,9 +1359,7 @@ static int isp1760_run(struct usb_hcd *hcd) if (retval) return retval; - init_timer(&errata2_timer); - errata2_timer.function = errata2_function; - errata2_timer.data = (unsigned long) hcd; + setup_timer(&errata2_timer, errata2_function, (unsigned long)hcd); errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; add_timer(&errata2_timer); -- cgit v0.10.2 From 7690037d5b99f4579b61bed815ada8925b4290cc Mon Sep 17 00:00:00 2001 From: Jeremiah Mahler Date: Sun, 21 Dec 2014 05:29:20 -0800 Subject: doc: usbmon: fix wording "be reading until" The usbmon documentation uses "be reading until" which is an unusual wording. Change it to "read until it" which is more clear. Signed-off-by: Jeremiah Mahler Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/usb/usbmon.txt b/Documentation/usb/usbmon.txt index c42bb9c..be2ccb9 100644 --- a/Documentation/usb/usbmon.txt +++ b/Documentation/usb/usbmon.txt @@ -72,7 +72,7 @@ to listen on a single bus, otherwise, to listen on all buses, type: # cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out -This process will be reading until killed. Naturally, the output can be +This process will read until it is killed. Naturally, the output can be redirected to a desirable location. This is preferred, because it is going to be quite long. -- cgit v0.10.2 From 11a6322a93f17af28f2b5609993facd157c01e03 Mon Sep 17 00:00:00 2001 From: Jeremiah Mahler Date: Sun, 21 Dec 2014 05:30:04 -0800 Subject: doc: usbmon: fix spelling s/unpriviledged/unprivileged/ Signed-off-by: Jeremiah Mahler Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/usb/usbmon.txt b/Documentation/usb/usbmon.txt index be2ccb9..28425f7 100644 --- a/Documentation/usb/usbmon.txt +++ b/Documentation/usb/usbmon.txt @@ -231,7 +231,7 @@ number. Number zero (/dev/usbmon0) is special and means "all buses". Note that specific naming policy is set by your Linux distribution. If you create /dev/usbmon0 by hand, make sure that it is owned by root -and has mode 0600. Otherwise, unpriviledged users will be able to snoop +and has mode 0600. Otherwise, unprivileged users will be able to snoop keyboard traffic. The following ioctl calls are available, with MON_IOC_MAGIC 0x92: -- cgit v0.10.2 From 96e418543d27212d73f2fec76490f833baa27e3b Mon Sep 17 00:00:00 2001 From: Wu Liang feng Date: Wed, 24 Dec 2014 18:22:18 +0800 Subject: dt-bindings: usb-ehci: Add an optional property "needs-reset-on-resume" Add device-tree bindings for EHCI so we can use "needs-reset-on-resume" property to force EHCI reset after resume if necessary. This is necessary on platforms like rk3288 that need a reset after resume to detect if a device has been disconnected during suspend time. Signed-off-by: Wu Liang feng Reviewed-by: Julius Werner Reviewed-by: Doug Anderson Reviewed-by: Tomasz Figa Reviewed-by: Pawel Osciak Reviewed-by: Sonny Rao Acked-by: Alan Stern Tested-by: Doug Anderson Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/devicetree/bindings/usb/usb-ehci.txt b/Documentation/devicetree/bindings/usb/usb-ehci.txt index 43c1a4e..0b04fdf 100644 --- a/Documentation/devicetree/bindings/usb/usb-ehci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ehci.txt @@ -12,6 +12,7 @@ Optional properties: - big-endian-regs : boolean, set this for hcds with big-endian registers - big-endian-desc : boolean, set this for hcds with big-endian descriptors - big-endian : boolean, for hcds with big-endian-regs + big-endian-desc + - needs-reset-on-resume : boolean, set this to force EHCI reset after resume - clocks : a list of phandle + clock specifier pairs - phys : phandle + phy specifier pair - phy-names : "usb" -- cgit v0.10.2 From 314b41b16a71ee824f55e2791fcb92997672da37 Mon Sep 17 00:00:00 2001 From: Wu Liang feng Date: Wed, 24 Dec 2014 18:22:19 +0800 Subject: USB: ehci-platform: Support ehci reset after resume quirk The Rockchip rk3288 EHCI controller doesn't properly detect the case when a device is removed during suspend. Specifically, when usb resume from suspend, the EHCI controller maintaining the USB state (FLAG_CF is 1, Current Connect Status is 1), but a USB device (like a USB camera on rk3288) may have been disconnected actually. Let's add a quirk to force ehci to go into the usb_root_hub_lost_power() path and reset after resume. This should generally reset the whole controller and all ports and initialize everything cleanly again, and bring the devices back up. As part of this, rename the "hibernation" paramter of ehci_resume() to force_reset since hibernation is simply another case where we can't trust the autodetected status and need to force a reset of devices. Signed-off-by: Wu Liang feng Reviewed-by: Julius Werner Reviewed-by: Doug Anderson Reviewed-by: Tomasz Figa Reviewed-by: Pawel Osciak Reviewed-by: Sonny Rao Acked-by: Alan Stern Tested-by: Doug Anderson Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 38bfeed..85e56d1 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1110,7 +1110,7 @@ int ehci_suspend(struct usb_hcd *hcd, bool do_wakeup) EXPORT_SYMBOL_GPL(ehci_suspend); /* Returns 0 if power was preserved, 1 if power was lost */ -int ehci_resume(struct usb_hcd *hcd, bool hibernated) +int ehci_resume(struct usb_hcd *hcd, bool force_reset) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -1124,12 +1124,12 @@ int ehci_resume(struct usb_hcd *hcd, bool hibernated) return 0; /* Controller is dead */ /* - * If CF is still set and we aren't resuming from hibernation + * If CF is still set and reset isn't forced * then we maintained suspend power. * Just undo the effect of ehci_suspend(). */ if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF && - !hibernated) { + !force_reset) { int mask = INTR_MASK; ehci_prepare_ports_for_controller_resume(ehci); diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 8557803..db5c29e 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -185,6 +185,10 @@ static int ehci_platform_probe(struct platform_device *dev) if (of_property_read_bool(dev->dev.of_node, "big-endian")) ehci->big_endian_mmio = ehci->big_endian_desc = 1; + if (of_property_read_bool(dev->dev.of_node, + "needs-reset-on-resume")) + pdata->reset_on_resume = 1; + priv->phy = devm_phy_get(&dev->dev, "usb"); if (IS_ERR(priv->phy)) { err = PTR_ERR(priv->phy); @@ -340,7 +344,7 @@ static int ehci_platform_resume(struct device *dev) return err; } - ehci_resume(hcd, false); + ehci_resume(hcd, pdata->reset_on_resume); return 0; } #endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 6f0577b..52ef084 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -871,7 +871,7 @@ extern int ehci_handshake(struct ehci_hcd *ehci, void __iomem *ptr, #ifdef CONFIG_PM extern int ehci_suspend(struct usb_hcd *hcd, bool do_wakeup); -extern int ehci_resume(struct usb_hcd *hcd, bool hibernated); +extern int ehci_resume(struct usb_hcd *hcd, bool force_reset); #endif /* CONFIG_PM */ extern int ehci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, diff --git a/include/linux/usb/ehci_pdriver.h b/include/linux/usb/ehci_pdriver.h index 7eb4dcd..6287b39 100644 --- a/include/linux/usb/ehci_pdriver.h +++ b/include/linux/usb/ehci_pdriver.h @@ -34,6 +34,8 @@ struct usb_hcd; * after initialization. * @no_io_watchdog: set to 1 if the controller does not need the I/O * watchdog to run. + * @reset_on_resume: set to 1 if the controller needs to be reset after + * a suspend / resume cycle (but can't detect that itself). * * These are general configuration options for the EHCI controller. All of * these options are activating more or less workarounds for some hardware. @@ -45,6 +47,7 @@ struct usb_ehci_pdata { unsigned big_endian_desc:1; unsigned big_endian_mmio:1; unsigned no_io_watchdog:1; + unsigned reset_on_resume:1; /* Turn on all power and clocks */ int (*power_on)(struct platform_device *pdev); -- cgit v0.10.2 From a95cfa6b86a19a822877e75d5a73b2a95d249e70 Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Tue, 6 Jan 2015 13:48:56 +0100 Subject: USB: host: Remove hard-coded octeon platform information for ehci/ohci Instead rely on device tree information for ehci and ohci. This was suggested with http://www.linux-mips.org/cgi-bin/mesg.cgi?a=linux-mips&i=1401358203-60225-4-git-send-email-alex.smith%40imgtec.com "The device tree will *always* have correct ehci/ohci clock configuration, so use it. This allows us to remove a big chunk of platform configuration code from octeon-platform.c." More or less I rebased that patch on Alan's work to remove ehci-octeon and ohci-octeon drivers. Cc: David Daney Cc: Alex Smith Cc: Alan Stern Signed-off-by: Andreas Herrmann Acked-by: Ralf Baechle Tested-by: Aaro Koskinen Signed-off-by: Greg Kroah-Hartman diff --git a/arch/mips/cavium-octeon/octeon-platform.c b/arch/mips/cavium-octeon/octeon-platform.c index b67ddf0..eea60b6 100644 --- a/arch/mips/cavium-octeon/octeon-platform.c +++ b/arch/mips/cavium-octeon/octeon-platform.c @@ -77,7 +77,7 @@ static DEFINE_MUTEX(octeon2_usb_clocks_mutex); static int octeon2_usb_clock_start_cnt; -static void octeon2_usb_clocks_start(void) +static void octeon2_usb_clocks_start(struct device *dev) { u64 div; union cvmx_uctlx_if_ena if_ena; @@ -86,6 +86,8 @@ static void octeon2_usb_clocks_start(void) union cvmx_uctlx_uphy_portx_ctl_status port_ctl_status; int i; unsigned long io_clk_64_to_ns; + u32 clock_rate = 12000000; + bool is_crystal_clock = false; mutex_lock(&octeon2_usb_clocks_mutex); @@ -96,6 +98,28 @@ static void octeon2_usb_clocks_start(void) io_clk_64_to_ns = 64000000000ull / octeon_get_io_clock_rate(); + if (dev->of_node) { + struct device_node *uctl_node; + const char *clock_type; + + uctl_node = of_get_parent(dev->of_node); + if (!uctl_node) { + dev_err(dev, "No UCTL device node\n"); + goto exit; + } + i = of_property_read_u32(uctl_node, + "refclk-frequency", &clock_rate); + if (i) { + dev_err(dev, "No UCTL \"refclk-frequency\"\n"); + goto exit; + } + i = of_property_read_string(uctl_node, + "refclk-type", &clock_type); + + if (!i && strcmp("crystal", clock_type) == 0) + is_crystal_clock = true; + } + /* * Step 1: Wait for voltages stable. That surely happened * before starting the kernel. @@ -126,9 +150,22 @@ static void octeon2_usb_clocks_start(void) cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); /* 3b */ - /* 12MHz crystal. */ - clk_rst_ctl.s.p_refclk_sel = 0; - clk_rst_ctl.s.p_refclk_div = 0; + clk_rst_ctl.s.p_refclk_sel = is_crystal_clock ? 0 : 1; + switch (clock_rate) { + default: + pr_err("Invalid UCTL clock rate of %u, using 12000000 instead\n", + clock_rate); + /* Fall through */ + case 12000000: + clk_rst_ctl.s.p_refclk_div = 0; + break; + case 24000000: + clk_rst_ctl.s.p_refclk_div = 1; + break; + case 48000000: + clk_rst_ctl.s.p_refclk_div = 2; + break; + } cvmx_write_csr(CVMX_UCTLX_CLK_RST_CTL(0), clk_rst_ctl.u64); /* 3c */ @@ -259,7 +296,7 @@ static void octeon2_usb_clocks_stop(void) static int octeon_ehci_power_on(struct platform_device *pdev) { - octeon2_usb_clocks_start(); + octeon2_usb_clocks_start(&pdev->dev); return 0; } @@ -277,11 +314,11 @@ static struct usb_ehci_pdata octeon_ehci_pdata = { .power_off = octeon_ehci_power_off, }; -static void __init octeon_ehci_hw_start(void) +static void __init octeon_ehci_hw_start(struct device *dev) { union cvmx_uctlx_ehci_ctl ehci_ctl; - octeon2_usb_clocks_start(); + octeon2_usb_clocks_start(dev); ehci_ctl.u64 = cvmx_read_csr(CVMX_UCTLX_EHCI_CTL(0)); /* Use 64-bit addressing. */ @@ -299,59 +336,28 @@ static u64 octeon_ehci_dma_mask = DMA_BIT_MASK(64); static int __init octeon_ehci_device_init(void) { struct platform_device *pd; + struct device_node *ehci_node; int ret = 0; - struct resource usb_resources[] = { - { - .flags = IORESOURCE_MEM, - }, { - .flags = IORESOURCE_IRQ, - } - }; - - /* Only Octeon2 has ehci/ohci */ - if (!OCTEON_IS_MODEL(OCTEON_CN63XX)) + ehci_node = of_find_node_by_name(NULL, "ehci"); + if (!ehci_node) return 0; - if (octeon_is_simulation() || usb_disabled()) - return 0; /* No USB in the simulator. */ - - pd = platform_device_alloc("ehci-platform", 0); - if (!pd) { - ret = -ENOMEM; - goto out; - } - - usb_resources[0].start = 0x00016F0000000000ULL; - usb_resources[0].end = usb_resources[0].start + 0x100; - - usb_resources[1].start = OCTEON_IRQ_USB0; - usb_resources[1].end = OCTEON_IRQ_USB0; - - ret = platform_device_add_resources(pd, usb_resources, - ARRAY_SIZE(usb_resources)); - if (ret) - goto fail; + pd = of_find_device_by_node(ehci_node); + if (!pd) + return 0; pd->dev.dma_mask = &octeon_ehci_dma_mask; pd->dev.platform_data = &octeon_ehci_pdata; - octeon_ehci_hw_start(); - - ret = platform_device_add(pd); - if (ret) - goto fail; + octeon_ehci_hw_start(&pd->dev); return ret; -fail: - platform_device_put(pd); -out: - return ret; } device_initcall(octeon_ehci_device_init); static int octeon_ohci_power_on(struct platform_device *pdev) { - octeon2_usb_clocks_start(); + octeon2_usb_clocks_start(&pdev->dev); return 0; } @@ -369,11 +375,11 @@ static struct usb_ohci_pdata octeon_ohci_pdata = { .power_off = octeon_ohci_power_off, }; -static void __init octeon_ohci_hw_start(void) +static void __init octeon_ohci_hw_start(struct device *dev) { union cvmx_uctlx_ohci_ctl ohci_ctl; - octeon2_usb_clocks_start(); + octeon2_usb_clocks_start(dev); ohci_ctl.u64 = cvmx_read_csr(CVMX_UCTLX_OHCI_CTL(0)); ohci_ctl.s.l2c_addr_msb = 0; @@ -387,57 +393,27 @@ static void __init octeon_ohci_hw_start(void) static int __init octeon_ohci_device_init(void) { struct platform_device *pd; + struct device_node *ohci_node; int ret = 0; - struct resource usb_resources[] = { - { - .flags = IORESOURCE_MEM, - }, { - .flags = IORESOURCE_IRQ, - } - }; - - /* Only Octeon2 has ehci/ohci */ - if (!OCTEON_IS_MODEL(OCTEON_CN63XX)) + ohci_node = of_find_node_by_name(NULL, "ohci"); + if (!ohci_node) return 0; - if (octeon_is_simulation() || usb_disabled()) - return 0; /* No USB in the simulator. */ - - pd = platform_device_alloc("ohci-platform", 0); - if (!pd) { - ret = -ENOMEM; - goto out; - } - - usb_resources[0].start = 0x00016F0000000400ULL; - usb_resources[0].end = usb_resources[0].start + 0x100; - - usb_resources[1].start = OCTEON_IRQ_USB0; - usb_resources[1].end = OCTEON_IRQ_USB0; - - ret = platform_device_add_resources(pd, usb_resources, - ARRAY_SIZE(usb_resources)); - if (ret) - goto fail; + pd = of_find_device_by_node(ohci_node); + if (!pd) + return 0; pd->dev.platform_data = &octeon_ohci_pdata; - octeon_ohci_hw_start(); - - ret = platform_device_add(pd); - if (ret) - goto fail; + octeon_ohci_hw_start(&pd->dev); return ret; -fail: - platform_device_put(pd); -out: - return ret; } device_initcall(octeon_ohci_device_init); #endif /* CONFIG_USB */ + static struct of_device_id __initdata octeon_ids[] = { { .compatible = "simple-bus", }, { .compatible = "cavium,octeon-6335-uctl", }, diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index db5c29e..28aae64 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -353,6 +353,7 @@ static const struct of_device_id vt8500_ehci_ids[] = { { .compatible = "via,vt8500-ehci", }, { .compatible = "wm,prizm-ehci", }, { .compatible = "generic-ehci", }, + { .compatible = "cavium,octeon-6335-ehci", }, {} }; MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index b81d202..9c06b01 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -328,6 +328,7 @@ static int ohci_platform_resume(struct device *dev) static const struct of_device_id ohci_platform_ids[] = { { .compatible = "generic-ohci", }, + { .compatible = "cavium,octeon-6335-ohci", }, { } }; MODULE_DEVICE_TABLE(of, ohci_platform_ids); -- cgit v0.10.2 From f1161256258e8ea24c3e5846abfdaccc508134b4 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Dec 2014 22:14:42 +0100 Subject: usb: gadget: udc: bdc: drop owner assignment from platform_drivers This platform_driver does not need to set an owner, it will be populated by the driver core. Signed-off-by: Wolfram Sang Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index c6dfef8..5c8f4ef 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -521,7 +521,6 @@ static int bdc_remove(struct platform_device *pdev) static struct platform_driver bdc_driver = { .driver = { .name = BRCM_BDC_NAME, - .owner = THIS_MODULE }, .probe = bdc_probe, .remove = bdc_remove, -- cgit v0.10.2 From 1d97869198a36dfc2920abd5776d91767fbead63 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Dec 2014 21:28:24 +0100 Subject: usb: host: drop owner assignment from platform_drivers These platform_drivers do not need to set an owner, it will be populated by the driver core. Signed-off-by: Wolfram Sang Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index fb7bd0c..ab4eee3 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -709,7 +709,6 @@ static struct platform_driver ehci_fsl_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "fsl-ehci", - .owner = THIS_MODULE, .pm = EHCI_FSL_PM_OPS, }, }; diff --git a/drivers/usb/host/ehci-grlib.c b/drivers/usb/host/ehci-grlib.c index 495b6fb..2165004 100644 --- a/drivers/usb/host/ehci-grlib.c +++ b/drivers/usb/host/ehci-grlib.c @@ -187,7 +187,6 @@ static struct platform_driver ehci_grlib_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "grlib-ehci", - .owner = THIS_MODULE, .of_match_table = ehci_hcd_grlib_of_match, }, }; diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c index 7d75465..342816a 100644 --- a/drivers/usb/host/ehci-pmcmsp.c +++ b/drivers/usb/host/ehci-pmcmsp.c @@ -325,6 +325,5 @@ static struct platform_driver ehci_hcd_msp_driver = { .remove = ehci_hcd_msp_drv_remove, .driver = { .name = "pmcmsp-ehci", - .owner = THIS_MODULE, }, }; diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index 5479247..1a10c8d 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -234,7 +234,6 @@ static struct platform_driver ehci_hcd_ppc_of_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ppc-of-ehci", - .owner = THIS_MODULE, .of_match_table = ehci_hcd_ppc_of_match, }, }; diff --git a/drivers/usb/host/ehci-sead3.c b/drivers/usb/host/ehci-sead3.c index 9b6e8d0..3d86cc2 100644 --- a/drivers/usb/host/ehci-sead3.c +++ b/drivers/usb/host/ehci-sead3.c @@ -178,7 +178,6 @@ static struct platform_driver ehci_hcd_sead3_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "sead3-ehci", - .owner = THIS_MODULE, .pm = SEAD3_EHCI_PMOPS, } }; diff --git a/drivers/usb/host/ehci-sh.c b/drivers/usb/host/ehci-sh.c index 0e0ce68..5caf88d 100644 --- a/drivers/usb/host/ehci-sh.c +++ b/drivers/usb/host/ehci-sh.c @@ -189,7 +189,6 @@ static struct platform_driver ehci_hcd_sh_driver = { .shutdown = ehci_hcd_sh_shutdown, .driver = { .name = "sh_ehci", - .owner = THIS_MODULE, }, }; diff --git a/drivers/usb/host/ehci-tilegx.c b/drivers/usb/host/ehci-tilegx.c index 0d24767..bdb93b6 100644 --- a/drivers/usb/host/ehci-tilegx.c +++ b/drivers/usb/host/ehci-tilegx.c @@ -210,7 +210,6 @@ static struct platform_driver ehci_hcd_tilegx_driver = { .shutdown = ehci_hcd_tilegx_drv_shutdown, .driver = { .name = "tilegx-ehci", - .owner = THIS_MODULE, } }; diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c index a232836..f544808 100644 --- a/drivers/usb/host/ehci-xilinx-of.c +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -236,7 +236,6 @@ static struct platform_driver ehci_hcd_xilinx_of_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "xilinx-of-ehci", - .owner = THIS_MODULE, .of_match_table = ehci_hcd_xilinx_of_match, }, }; diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c index 1c76999..e5c33bc 100644 --- a/drivers/usb/host/ohci-da8xx.c +++ b/drivers/usb/host/ohci-da8xx.c @@ -431,7 +431,6 @@ static struct platform_driver ohci_hcd_da8xx_driver = { .resume = ohci_da8xx_resume, #endif .driver = { - .owner = THIS_MODULE, .name = "ohci", }, }; diff --git a/drivers/usb/host/ohci-jz4740.c b/drivers/usb/host/ohci-jz4740.c index 8ddd8f5..4db78f1 100644 --- a/drivers/usb/host/ohci-jz4740.c +++ b/drivers/usb/host/ohci-jz4740.c @@ -239,7 +239,6 @@ static struct platform_driver ohci_hcd_jz4740_driver = { .remove = jz4740_ohci_remove, .driver = { .name = "jz4740-ohci", - .owner = THIS_MODULE, }, }; diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 965e3e9..4f87a5c 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -229,7 +229,6 @@ static struct platform_driver ohci_hcd_ppc_of_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "ppc-of-ohci", - .owner = THIS_MODULE, .of_match_table = ohci_hcd_ppc_of_match, }, }; diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c index 4e81c80..a8b8d8b 100644 --- a/drivers/usb/host/ohci-sm501.c +++ b/drivers/usb/host/ohci-sm501.c @@ -265,7 +265,6 @@ static struct platform_driver ohci_hcd_sm501_driver = { .suspend = ohci_sm501_suspend, .resume = ohci_sm501_resume, .driver = { - .owner = THIS_MODULE, .name = "sm501-usb", }, }; diff --git a/drivers/usb/host/ohci-tilegx.c b/drivers/usb/host/ohci-tilegx.c index bef6dfb..e1b208d 100644 --- a/drivers/usb/host/ohci-tilegx.c +++ b/drivers/usb/host/ohci-tilegx.c @@ -199,7 +199,6 @@ static struct platform_driver ohci_hcd_tilegx_driver = { .shutdown = ohci_hcd_tilegx_drv_shutdown, .driver = { .name = "tilegx-ohci", - .owner = THIS_MODULE, } }; diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index bb40958..e9a6eec 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -368,6 +368,5 @@ static struct platform_driver ohci_hcd_tmio_driver = { .resume = ohci_hcd_tmio_drv_resume, .driver = { .name = "tmio-ohci", - .owner = THIS_MODULE, }, }; diff --git a/drivers/usb/host/uhci-grlib.c b/drivers/usb/host/uhci-grlib.c index 05f57ff..0342991 100644 --- a/drivers/usb/host/uhci-grlib.c +++ b/drivers/usb/host/uhci-grlib.c @@ -188,7 +188,6 @@ static struct platform_driver uhci_grlib_driver = { .shutdown = uhci_hcd_grlib_shutdown, .driver = { .name = "grlib-uhci", - .owner = THIS_MODULE, .of_match_table = uhci_hcd_grlib_of_match, }, }; diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index cf8f460..3a3e3ee 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -147,7 +147,6 @@ static struct platform_driver uhci_platform_driver = { .shutdown = uhci_hcd_platform_shutdown, .driver = { .name = "platform-uhci", - .owner = THIS_MODULE, .of_match_table = platform_uhci_ids, }, }; -- cgit v0.10.2 From dc93b41a15614f51bc1ea47d12b245d60f79731d Mon Sep 17 00:00:00 2001 From: Mickael Maison Date: Tue, 23 Dec 2014 17:34:43 +0100 Subject: usb: dwc3: Fixed a typo in comments Fixed a typo in comments Signed-off-by: Mickael Maison Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f03b136..bd9f48d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2077,7 +2077,7 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) * We have discussed this with the IP Provider and it was * suggested to giveback all requests here, but give HW some * extra time to synchronize with the interconnect. We're using - * an arbitraty 100us delay for that. + * an arbitrary 100us delay for that. * * Note also that a similar handling was tested by Synopsys * (thanks a lot Paul) and nothing bad has come out of it. -- cgit v0.10.2 From d16cd0b8f70062f7d0f96adc42f86421b71eb21c Mon Sep 17 00:00:00 2001 From: Rickard Strandqvist Date: Sat, 20 Dec 2014 23:30:11 +0100 Subject: usb: gadget: udc: s3c2410_udc.c: Remove some unused functions Removes some functions that are not used anywhere: s3c2410_udc_clear_ep_state() s3c2410_udc_set_ep0_sse_out() This was partially found by using a static code analysis program called cppcheck. Signed-off-by: Rickard Strandqvist Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/s3c2410_udc.c b/drivers/usb/gadget/udc/s3c2410_udc.c index 824cf12..256a67b 100644 --- a/drivers/usb/gadget/udc/s3c2410_udc.c +++ b/drivers/usb/gadget/udc/s3c2410_udc.c @@ -238,14 +238,6 @@ static inline void s3c2410_udc_set_ep0_de_out(void __iomem *base) S3C2410_UDC_EP0_CSR_REG); } -static inline void s3c2410_udc_set_ep0_sse_out(void __iomem *base) -{ - udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); - udc_writeb(base, (S3C2410_UDC_EP0_CSR_SOPKTRDY - | S3C2410_UDC_EP0_CSR_SSE), - S3C2410_UDC_EP0_CSR_REG); -} - static inline void s3c2410_udc_set_ep0_de_in(void __iomem *base) { udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); @@ -291,18 +283,6 @@ static void s3c2410_udc_nuke(struct s3c2410_udc *udc, } } -static inline void s3c2410_udc_clear_ep_state(struct s3c2410_udc *dev) -{ - unsigned i; - - /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint - * fifos, and pending transactions mustn't be continued in any case. - */ - - for (i = 1; i < S3C2410_ENDPOINTS; i++) - s3c2410_udc_nuke(dev, &dev->ep[i], -ECONNABORTED); -} - static inline int s3c2410_udc_fifo_count_out(void) { int tmp; -- cgit v0.10.2 From 997f4f81dff243858b6cdd8c3ddb39e05a050c22 Mon Sep 17 00:00:00 2001 From: Mickael Maison Date: Tue, 23 Dec 2014 17:39:45 +0100 Subject: usb: dwc2: Fixed a few typos in comments Fixed 3 typos in comments Signed-off-by: Mickael Maison Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 7605850b..d5197d4 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -462,7 +462,7 @@ int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) dwc2_enable_common_interrupts(hsotg); /* - * Do device or host intialization based on mode during PCD and + * Do device or host initialization based on mode during PCD and * HCD initialization */ if (dwc2_is_host_mode(hsotg)) { diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 7a70a13..0d2ee29 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -381,7 +381,7 @@ struct dwc2_core_params { * @power_optimized Are power optimizations enabled? * @num_dev_ep Number of device endpoints available * @num_dev_perio_in_ep Number of device periodic IN endpoints - * avaialable + * available * @dev_token_q_depth Device Mode IN Token Sequence Learning Queue * Depth * 0 to 30 diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 200168e..ede69dc 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -65,7 +65,7 @@ static inline void __bic32(void __iomem *ptr, u32 val) writel(readl(ptr) & ~val, ptr); } -/* forward decleration of functions */ +/* forward declaration of functions */ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg); /** -- cgit v0.10.2 From 110381e11b5fbf84bc0994d4c3f4629bc362ab4c Mon Sep 17 00:00:00 2001 From: Amit Virdi Date: Fri, 19 Dec 2014 12:40:17 +0530 Subject: usb: dwc3: gadget: Remove redundant check dwc3_gadget_init_hw_endpoints calls dwc3_alloc_trb_pool only if epnum is not equal to 0 or 1. Hence, rechecking it in the called function is redundant. Signed-off-by: Amit Virdi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index bd9f48d..4e25939 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -352,9 +352,6 @@ static int dwc3_alloc_trb_pool(struct dwc3_ep *dep) if (dep->trb_pool) return 0; - if (dep->number == 0 || dep->number == 1) - return 0; - dep->trb_pool = dma_alloc_coherent(dwc->dev, sizeof(struct dwc3_trb) * DWC3_TRB_NUM, &dep->trb_pool_dma, GFP_KERNEL); -- cgit v0.10.2 From 3cd0e29d5e28331dc78416bf7acd4e7cb63664aa Mon Sep 17 00:00:00 2001 From: Amit Virdi Date: Fri, 19 Dec 2014 12:40:18 +0530 Subject: usb: dwc3: Remove current_trb as it is unused This field was introduced but never used. So, remove it. Signed-off-by: Amit Virdi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 4bb9aa6..0842aa8 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -431,7 +431,6 @@ struct dwc3_event_buffer { * @dwc: pointer to DWC controller * @saved_state: ep state saved during hibernation * @flags: endpoint flags (wedged, stalled, ...) - * @current_trb: index of current used trb * @number: endpoint number (1 - 15) * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK * @resource_index: Resource transfer index @@ -464,8 +463,6 @@ struct dwc3_ep { /* This last one is specific to EP0 */ #define DWC3_EP0_DIR_IN (1 << 31) - unsigned current_trb; - u8 number; u8 type; u8 resource_index; -- cgit v0.10.2 From b797ef4e167b68dc5d6bfcb1b77f7f077dddb825 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:24 +0100 Subject: Documentation: usb: gadget_serial: update generic serial setup instruction Using module parameters to specify accepted Vendor ID, Product ID is considered legacy now. Update the documentation to reflect it. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget_serial.txt b/Documentation/usb/gadget_serial.txt index 61e67f6..6b4a88a 100644 --- a/Documentation/usb/gadget_serial.txt +++ b/Documentation/usb/gadget_serial.txt @@ -236,8 +236,12 @@ I: If#= 0 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=serial E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms -You must explicitly load the usbserial driver with parameters to -configure it to recognize the gadget serial device, like this: +You must load the usbserial driver and explicitly set its parameters +to configure it to recognize the gadget serial device, like this: + + echo 0x0525 0xA4A6 >/sys/bus/usb-serial/drivers/generic/new_id + +The legacy way is to use module parameters: modprobe usbserial vendor=0x0525 product=0xA4A6 -- cgit v0.10.2 From e38eb2c8cb435729579576b2b5bc5247ebcd6f5b Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:25 +0100 Subject: Documentation: usb: ACM function testing The newly added file will be used to provide descriptions of how to test the functions of USB gadgets. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt new file mode 100644 index 0000000..2a448f8 --- /dev/null +++ b/Documentation/usb/gadget-testing.txt @@ -0,0 +1,34 @@ +This file summarizes information on basic testing of USB functions +provided by gadgets. + +1. ACM function + + +1. ACM function +=============== + +The function is provided by usb_f_acm.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "acm". +The ACM function provides just one attribute in its function directory: + + port_num + +The attribute is read-only. + +There can be at most 4 ACM/generic serial/OBEX ports in the system. + + +Testing the ACM function +------------------------ + +On the host: cat > /dev/ttyACM +On the device : cat /dev/ttyGS + +then the other way round + +On the device: cat > /dev/ttyGS +On the host: cat /dev/ttyACM -- cgit v0.10.2 From d5862ca6da545ec63ec2d2a8c687bf214c87e00e Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:26 +0100 Subject: Documentation: usb: ECM function testing Summary of how to test ECM function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 2a448f8..7df8785 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -2,6 +2,7 @@ This file summarizes information on basic testing of USB functions provided by gadgets. 1. ACM function +2. ECM function 1. ACM function @@ -32,3 +33,36 @@ then the other way round On the device: cat > /dev/ttyGS On the host: cat /dev/ttyACM + +2. ECM function +=============== + +The function is provided by usb_f_ecm.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "ecm". +The ECM function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/ecm. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the ECM function +------------------------ + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 7bfbc6e3fbd9ae35b28db1839689b7eb4025c93d Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:27 +0100 Subject: Documentation: usb: ECM subset function testing Summary of how to test ECM subset function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 7df8785..b40db75 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -3,6 +3,7 @@ provided by gadgets. 1. ACM function 2. ECM function +3. ECM subset function 1. ACM function @@ -66,3 +67,36 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +3. ECM subset function +====================== + +The function is provided by usb_f_ecm_subset.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "geth". +The ECM subset function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/ecm. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the ECM subset function +------------------------------- + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 4ca560a6d31d1deae004a9977adf64766e87a021 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:28 +0100 Subject: Documentation: usb: EEM function testing Summary of how to test EEM function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index b40db75..50b0a6c 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -4,6 +4,7 @@ provided by gadgets. 1. ACM function 2. ECM function 3. ECM subset function +4. EEM function 1. ACM function @@ -100,3 +101,36 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +4. EEM function +=============== + +The function is provided by usb_f_eem.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "eem". +The EEM function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/eem. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the EEM function +------------------------ + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 2c0f62f9e4f2c450342369dfd5858db51bc90fe3 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:29 +0100 Subject: Documentation: usb: FFS function testing Summary of how to test FFS (FunctionFS) function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 50b0a6c..141a1d0 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -5,6 +5,7 @@ provided by gadgets. 2. ECM function 3. ECM subset function 4. EEM function +5. FFS function 1. ACM function @@ -134,3 +135,26 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +5. FFS function +=============== + +The function is provided by usb_f_fs.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "ffs". +The function directory is intentionally empty and not modifiable. + +After creating the directory there is a new instance (a "device") of FunctionFS +available in the system. Once a "device" is available, the user should follow +the standard procedure for using FunctionFS (mount it, run the userspace +process which implements the function proper). The gadget should be enabled +by writing a suitable string to usb_gadget//UDC. + +Testing the FFS function +------------------------ + +On the device: start the function's userspace daemon, enable the gadget +On the host: use the USB function provided by the device -- cgit v0.10.2 From f7e3c3cd2401b3531a3ac4e279a08d03adb722e3 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:30 +0100 Subject: Documentation: usb: HID function testing Summary of how to test HID function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 141a1d0..f117e5c 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -6,6 +6,7 @@ provided by gadgets. 3. ECM subset function 4. EEM function 5. FFS function +6. HID function 1. ACM function @@ -158,3 +159,49 @@ Testing the FFS function On the device: start the function's userspace daemon, enable the gadget On the host: use the USB function provided by the device + +6. HID function +=============== + +The function is provided by usb_f_hid.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "hid". +The HID function provides these attributes in its function directory: + + protocol - HID protocol to use + report_desc - data to be used in HID reports, except data + passed with /dev/hidg + report_length - HID report length + subclass - HID subclass to use + +For a keyboard the protocol and the subclass are 1, the report_length is 8, +while the report_desc is: + +$ hd my_report_desc +00000000 05 01 09 06 a1 01 05 07 19 e0 29 e7 15 00 25 01 |..........)...%.| +00000010 75 01 95 08 81 02 95 01 75 08 81 03 95 05 75 01 |u.......u.....u.| +00000020 05 08 19 01 29 05 91 02 95 01 75 03 91 03 95 06 |....).....u.....| +00000030 75 08 15 00 25 65 05 07 19 00 29 65 81 00 c0 |u...%e....)e...| +0000003f + +Such a sequence of bytes can be stored to the attribute with echo: + +$ echo -ne \\x05\\x01\\x09\\x06\\xa1..... + +Testing the HID function +------------------------ + +Device: +- create the gadget +- connect the gadget to a host, preferably not the one used +to control the gadget +- run a program which writes to /dev/hidg, e.g. +a userspace program found in Documentation/usb/gadget_hid.txt: + +$ ./hid_gadget_test /dev/hidg0 keyboard + +Host: +- observe the keystrokes from the gadget -- cgit v0.10.2 From ec91aff7630df1e1864d95f5efd25c212d484837 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:31 +0100 Subject: Documentation: usb: LOOPBACK function testing Summary of how to test LOOPBACK function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index f117e5c..b13491d 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -7,6 +7,7 @@ provided by gadgets. 4. EEM function 5. FFS function 6. HID function +7. LOOPBACK function 1. ACM function @@ -205,3 +206,25 @@ $ ./hid_gadget_test /dev/hidg0 keyboard Host: - observe the keystrokes from the gadget + +7. LOOPBACK function +==================== + +The function is provided by usb_f_ss_lb.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "Loopback". +The LOOPBACK function provides these attributes in its function directory: + + qlen - depth of loopback queue + bulk_buflen - buffer length + +Testing the LOOPBACK function +----------------------------- + +device: run the gadget +host: test-usb + +http://www.linux-usb.org/usbtest/testusb.c -- cgit v0.10.2 From cdbe287d271637cb90f239d6a826e2aed555c1d7 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:32 +0100 Subject: Documentation: usb: MASS STORAGE function testing Summary of how to test MASS STORAGE function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index b13491d..01b9ffe 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -8,6 +8,7 @@ provided by gadgets. 5. FFS function 6. HID function 7. LOOPBACK function +8. MASS STORAGE function 1. ACM function @@ -228,3 +229,56 @@ device: run the gadget host: test-usb http://www.linux-usb.org/usbtest/testusb.c + +8. MASS STORAGE function +======================== + +The function is provided by usb_f_mass_storage.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "mass_storage". +The MASS STORAGE function provides these attributes in its directory: +files: + + stall - Set to permit function to halt bulk endpoints. + Disabled on some USB devices known not to work + correctly. You should set it to true. + num_buffers - Number of pipeline buffers. Valid numbers + are 2..4. Available only if + CONFIG_USB_GADGET_DEBUG_FILES is set. + +and a default lun.0 directory corresponding to SCSI LUN #0. + +A new lun can be added with mkdir: + +$ mkdir functions/mass_storage.0/partition.5 + +Lun numbering does not have to be continuous, except for lun #0 which is +created by default. A maximum of 8 luns can be specified and they all must be +named following the . scheme. The numbers can be 0..8. +Probably a good convention is to name the luns "lun.", +although it is not mandatory. + +In each lun directory there are the following attribute files: + + file - The path to the backing file for the LUN. + Required if LUN is not marked as removable. + ro - Flag specifying access to the LUN shall be + read-only. This is implied if CD-ROM emulation + is enabled as well as when it was impossible + to open "filename" in R/W mode. + removable - Flag specifying that LUN shall be indicated as + being removable. + cdrom - Flag specifying that LUN shall be reported as + being a CD-ROM. + nofua - Flag specifying that FUA flag + in SCSI WRITE(10,12) + +Testing the MASS STORAGE function +--------------------------------- + +device: connect the gadget, enable it +host: dmesg, see the USB drives appear (if system configured to automatically +mount) -- cgit v0.10.2 From 0d6be59a1d8a93086cd81e1c59f8d60ba35b40a2 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:33 +0100 Subject: Documentation: usb: MIDI function testing Summary of how to test MIDI function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 01b9ffe..c2f148d 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -9,6 +9,7 @@ provided by gadgets. 6. HID function 7. LOOPBACK function 8. MASS STORAGE function +9. MIDI function 1. ACM function @@ -282,3 +283,86 @@ Testing the MASS STORAGE function device: connect the gadget, enable it host: dmesg, see the USB drives appear (if system configured to automatically mount) + +9. MIDI function +================ + +The function is provided by usb_f_midi.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "midi". +The MIDI function provides these attributes in its function directory: + + buflen - MIDI buffer length + id - ID string for the USB MIDI adapter + in_ports - number of MIDI input ports + index - index value for the USB MIDI adapter + out_ports - number of MIDI output ports + qlen - USB read request queue length + +Testing the MIDI function +------------------------- + +There are two cases: playing a mid from the gadget to +the host and playing a mid from the host to the gadget. + +1) Playing a mid from the gadget to the host +host) + +$ arecordmidi -l + Port Client name Port name + 14:0 Midi Through Midi Through Port-0 + 24:0 MIDI Gadget MIDI Gadget MIDI 1 +$ arecordmidi -p 24:0 from_gadget.mid + +gadget) + +$ aplaymidi -l + Port Client name Port name + 20:0 f_midi f_midi + +$ aplaymidi -p 20:0 to_host.mid + +2) Playing a mid from the host to the gadget +gadget) + +$ arecordmidi -l + Port Client name Port name + 20:0 f_midi f_midi + +$ arecordmidi -p 20:0 from_host.mid + +host) + +$ aplaymidi -l + Port Client name Port name + 14:0 Midi Through Midi Through Port-0 + 24:0 MIDI Gadget MIDI Gadget MIDI 1 + +$ aplaymidi -p24:0 to_gadget.mid + +The from_gadget.mid should sound identical to the to_host.mid. +The from_host.id should sound identical to the to_gadget.mid. + +MIDI files can be played to speakers/headphones with e.g. timidity installed + +$ aplaymidi -l + Port Client name Port name + 14:0 Midi Through Midi Through Port-0 + 24:0 MIDI Gadget MIDI Gadget MIDI 1 +128:0 TiMidity TiMidity port 0 +128:1 TiMidity TiMidity port 1 +128:2 TiMidity TiMidity port 2 +128:3 TiMidity TiMidity port 3 + +$ aplaymidi -p 128:0 file.mid + +MIDI ports can be logically connected using the aconnect utility, e.g.: + +$ aconnect 24:0 128:0 # try it on the host + +After the gadget's MIDI port is connected to timidity's MIDI port, +whatever is played at the gadget side with aplaymidi -l is audible +in host's speakers/headphones. -- cgit v0.10.2 From 4d0fa79e0ef78f516888068e240d38650eb404d2 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:34 +0100 Subject: Documentation: usb: NCM function testing Summary of how to test NCM function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index c2f148d..493f79b 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -10,6 +10,7 @@ provided by gadgets. 7. LOOPBACK function 8. MASS STORAGE function 9. MIDI function +10. NCM function 1. ACM function @@ -366,3 +367,36 @@ $ aconnect 24:0 128:0 # try it on the host After the gadget's MIDI port is connected to timidity's MIDI port, whatever is played at the gadget side with aplaymidi -l is audible in host's speakers/headphones. + +10. NCM function +================ + +The function is provided by usb_f_ncm.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "ncm". +The NCM function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/ncm. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the NCM function +------------------------ + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From d81b85dcf186f6fa4b59e5b8181e85366ebfdd61 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:35 +0100 Subject: Documentation: usb: OBEX function testing Summary of how to test OBEX function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 493f79b..c8ae540 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -11,6 +11,7 @@ provided by gadgets. 8. MASS STORAGE function 9. MIDI function 10. NCM function +11. OBEX function 1. ACM function @@ -400,3 +401,31 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +11. OBEX function +================= + +The function is provided by usb_f_obex.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "obex". +The OBEX function provides just one attribute in its function directory: + + port_num + +The attribute is read-only. + +There can be at most 4 ACM/generic serial/OBEX ports in the system. + +Testing the OBEX function +------------------------- + +On device: seriald -f /dev/ttyGS -s 1024 +On host: serialc -v -p -i -a1 -s1024 \ + -t -r + +where seriald and serialc are Felipe's utilities found here: + +https://git.gitorious.org/usb/usb-tools.git master -- cgit v0.10.2 From da2907d2b08c2495c2e39f799b9ccbf76fd1aa58 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:36 +0100 Subject: Documentation: usb: PHONET function testing Summary of how to test PHONET function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index c8ae540..02d4366 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -12,6 +12,7 @@ provided by gadgets. 9. MIDI function 10. NCM function 11. OBEX function +12. PHONET function 1. ACM function @@ -429,3 +430,66 @@ On host: serialc -v -p -i -a1 -s1024 \ where seriald and serialc are Felipe's utilities found here: https://git.gitorious.org/usb/usb-tools.git master + +12. PHONET function +=================== + +The function is provided by usb_f_phonet.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "phonet". +The PHONET function provides just one attribute in its function directory: + + ifname - network device interface name associated with this + function instance + +Testing the PHONET function +--------------------------- + +It is not possible to test the SOCK_STREAM protocol without a specific piece +of hardware, so only SOCK_DGRAM has been tested. For the latter to work, +in the past I had to apply the patch mentioned here: + +http://www.spinics.net/lists/linux-usb/msg85689.html + +These tools are required: + +git://git.gitorious.org/meego-cellular/phonet-utils.git + +On the host: + +$ ./phonet -a 0x10 -i usbpn0 +$ ./pnroute add 0x6c usbpn0 +$./pnroute add 0x10 usbpn0 +$ ifconfig usbpn0 up + +On the device: + +$ ./phonet -a 0x6c -i upnlink0 +$ ./pnroute add 0x10 upnlink0 +$ ifconfig upnlink0 up + +Then a test program can be used: + +http://www.spinics.net/lists/linux-usb/msg85690.html + +On the device: + +$ ./pnxmit -a 0x6c -r + +On the host: + +$ ./pnxmit -a 0x10 -s 0x6c + +As a result some data should be sent from host to device. +Then the other way round: + +On the host: + +$ ./pnxmit -a 0x10 -r + +On the device: + +$ ./pnxmit -a 0x6c -s 0x10 -- cgit v0.10.2 From ddb722449cfcfe30d3c3ed2cd404551e1c3c3670 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:37 +0100 Subject: Documentation: usb: RNDIS function testing Summary of how to test RNDIS function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 02d4366..61bc5f5 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -13,6 +13,7 @@ provided by gadgets. 10. NCM function 11. OBEX function 12. PHONET function +13. RNDIS function 1. ACM function @@ -493,3 +494,38 @@ $ ./pnxmit -a 0x10 -r On the device: $ ./pnxmit -a 0x6c -s 0x10 + +13. RNDIS function +================== + +The function is provided by usb_f_rndis.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "rndis". +The RNDIS function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/rndis. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +By default there can be only 1 RNDIS interface in the system. + +Testing the RNDIS function +-------------------------- + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 4dfcec8a2f9e22ac409ae512d5ff6acced8c588f Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:38 +0100 Subject: Documentation: usb: SERIAL function testing Summary of how to test SERIAL function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 61bc5f5..bc57c65 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -14,6 +14,7 @@ provided by gadgets. 11. OBEX function 12. PHONET function 13. RNDIS function +14. SERIAL function 1. ACM function @@ -529,3 +530,33 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +14. SERIAL function +=================== + +The function is provided by usb_f_gser.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "gser". +The SERIAL function provides just one attribute in its function directory: + + port_num + +The attribute is read-only. + +There can be at most 4 ACM/generic serial/OBEX ports in the system. + +Testing the SERIAL function +--------------------------- + +On host: insmod usbserial + echo VID PID >/sys/bus/usb-serial/drivers/generic/new_id +On host: cat > /dev/ttyUSB +On target: cat /dev/ttyGS + +then the other way round + +On target: cat > /dev/ttyGS +On host: cat /dev/ttyUSB -- cgit v0.10.2 From 480a912be4eb51e30aec721731b9cf476b2ee861 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:39 +0100 Subject: Documentation: usb: SOURCESINK function testing Summary of how to test SOURCESINK function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index bc57c65..73a5b0c 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -15,6 +15,7 @@ provided by gadgets. 12. PHONET function 13. RNDIS function 14. SERIAL function +15. SOURCESINK function 1. ACM function @@ -560,3 +561,29 @@ then the other way round On target: cat > /dev/ttyGS On host: cat /dev/ttyUSB + +15. SOURCESINK function +======================= + +The function is provided by usb_f_ss_lb.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "SourceSink". +The SOURCESINK function provides these attributes in its function directory: + + pattern - 0 (all zeros), 1 (mod63), 2 (none) + isoc_interval - 1..16 + isoc_maxpacket - 0 - 1023 (fs), 0 - 1024 (hs/ss) + isoc_mult - 0..2 (hs/ss only) + isoc_maxburst - 0..15 (ss only) + bulk_buflen - buffer length + +Testing the SOURCESINK function +------------------------------- + +device: run the gadget +host: test-usb + +http://www.linux-usb.org/usbtest/testusb.c -- cgit v0.10.2 From dae0358694abe0bbca3b83a922698b1f24c4299a Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:40 +0100 Subject: Documentation: usb: UAC1 function testing Summary of how to test UAC1 function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 73a5b0c..40d22d8 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -16,6 +16,7 @@ provided by gadgets. 13. RNDIS function 14. SERIAL function 15. SOURCESINK function +16. UAC1 function 1. ACM function @@ -587,3 +588,29 @@ device: run the gadget host: test-usb http://www.linux-usb.org/usbtest/testusb.c + +16. UAC1 function +================= + +The function is provided by usb_f_uac1.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uac1". +The uac1 function provides these attributes in its function directory: + + audio_buf_size - audio buffer size + fn_cap - capture pcm device file name + fn_cntl - control device file name + fn_play - playback pcm device file name + req_buf_size - ISO OUT endpoint request buffer size + req_count - ISO OUT endpoint request count + +The attributes have sane default values. + +Testing the UAC1 function +------------------------- + +device: run the gadget +host: aplay -l # should list our USB Audio Gadget -- cgit v0.10.2 From 020c6f93484370672060fabebc49aed5d1a7c58f Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:41 +0100 Subject: Documentation: usb: UAC2 function testing Summary of how to test UAC2 function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 40d22d8..5ac9dc7 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -17,6 +17,7 @@ provided by gadgets. 14. SERIAL function 15. SOURCESINK function 16. UAC1 function +17. UAC2 function 1. ACM function @@ -614,3 +615,41 @@ Testing the UAC1 function device: run the gadget host: aplay -l # should list our USB Audio Gadget + +17. UAC2 function +================= + +The function is provided by usb_f_uac2.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uac2". +The uac2 function provides these attributes in its function directory: + + chmask - capture channel mask + c_srate - capture sampling rate + c_ssize - capture sample size (bytes) + p_chmask - playback channel mask + p_srate - playback sampling rate + p_ssize - playback sample size (bytes) + +The attributes have sane default values. + +Testing the UAC2 function +------------------------- + +device: run the gadget +host: aplay -l # should list our USB Audio Gadget + +This function does not require real hardware support, it just +sends a stream of audio data to/from the host. In order to +actually hear something at the device side, a command similar +to this must be used at the device side: + +$ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 & + +e.g.: + +$ arecord -f dat -t wav -D hw:CARD=UAC2Gadget,DEV=0 | \ +aplay -D default:CARD=OdroidU3 -- cgit v0.10.2 From 9c4f538bfaf696c5cb24ec58fbeba12155c56c31 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:42 +0100 Subject: Documentation: usb: UVC function testing Summary of how to test UVC function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 5ac9dc7..076ac7b 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -18,6 +18,7 @@ provided by gadgets. 15. SOURCESINK function 16. UAC1 function 17. UAC2 function +18. UVC function 1. ACM function @@ -653,3 +654,75 @@ e.g.: $ arecord -f dat -t wav -D hw:CARD=UAC2Gadget,DEV=0 | \ aplay -D default:CARD=OdroidU3 + +18. UVC function +================ + +The function is provided by usb_f_uvc.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uvc". +The uvc function provides these attributes in its function directory: + + streaming_interval - interval for polling endpoint for data transfers + streaming_maxburst - bMaxBurst for super speed companion descriptor + streaming_maxpacket - maximum packet size this endpoint is capable of + sending or receiving when this configuration is + selected + +There are also "control" and "streaming" subdirectories, each of which contain +a number of their subdirectories. There are some sane defaults provided, but +the user must provide the following: + + control header - create in control/header, link from control/class/fs + and/or control/class/ss + streaming header - create in streaming/header, link from + streaming/class/fs and/or streaming/class/hs and/or + streaming/class/ss + format description - create in streaming/mjpeg and/or + streaming/uncompressed + frame description - create in streaming/mjpeg/ and/or in + streaming/uncompressed/ + +Each frame description contains frame interval specification, and each +such specification consists of a number of lines with an inverval value +in each line. The rules stated above are best illustrated with an example: + +# mkdir functions/uvc.usb0/control/header/h +# cd functions/uvc.usb0/control/header/h +# ln -s header/h class/fs +# ln -s header/h class/ss +# mkdir -p functions/uvc.usb0/streaming/uncompressed/u/360p +# cat < functions/uvc.usb0/streaming/uncompressed/u/360p/dwFrameInterval +666666 +1000000 +5000000 +EOF +# cd $GADGET_CONFIGFS_ROOT +# mkdir functions/uvc.usb0/streaming/header/h +# cd functions/uvc.usb0/streaming/header/h +# ln -s ../../uncompressed/u +# cd ../../class/fs +# ln -s ../../header/h +# cd ../../class/hs +# ln -s ../../header/h +# cd ../../class/ss +# ln -s ../../header/h + + +Testing the UVC function +------------------------ + +device: run the gadget, modprobe vivid + +# uvc-gadget -u /dev/video -v /dev/video + +where uvc-gadget is this program: +http://git.ideasonboard.org/uvc-gadget.git + +with these patches: +http://www.spinics.net/lists/linux-usb/msg99220.html + +host: luvcview -f yuv -- cgit v0.10.2 From 39a2ac2738658fd3e56987fb8b43b604298e9ad2 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 15 Dec 2014 13:50:05 +0100 Subject: usb: gadget: hid: consistently use 2^n - 1 for max values A maximum value which fits in 16 bits, unsigned, is 65535. Acked-by: Geert Uytterhoeven Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 6e04e30..9660ade 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -758,7 +758,7 @@ static struct f_hid_opts_attribute f_hid_opts_##name = \ F_HID_OPT(subclass, 8, 255); F_HID_OPT(protocol, 8, 255); -F_HID_OPT(report_length, 16, 65536); +F_HID_OPT(report_length, 16, 65535); static ssize_t f_hid_opts_report_desc_show(struct f_hid_opts *opts, char *page) { -- cgit v0.10.2 From 368504c00a2dfce637a55fcbf0cc0339f9ff5ca3 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Fri, 12 Dec 2014 22:53:18 +0300 Subject: usb: renesas_usbhs: kill dead code in usbhs_probe() usbhsc_drvcllbck_notify_hotplug() always returns 0, so it's rather pointless to store and check its result for being < 0. Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 3714787..a706fc4 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -632,16 +632,12 @@ static int usbhs_probe(struct platform_device *pdev) /* * manual call notify_hotplug for cold plug */ - ret = usbhsc_drvcllbck_notify_hotplug(pdev); - if (ret < 0) - goto probe_end_call_remove; + usbhsc_drvcllbck_notify_hotplug(pdev); dev_info(&pdev->dev, "probed\n"); return ret; -probe_end_call_remove: - usbhs_platform_call(priv, hardware_exit, pdev); probe_end_mod_exit: usbhs_mod_remove(priv); probe_end_fifo_exit: -- cgit v0.10.2 From 6b1b0fe0698f0e4d528a33f43d5c56f7e6e8f756 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 12 Dec 2014 13:08:26 -0200 Subject: Documentation: usb: phy: nop: Fix the description of 'vcc-supply' Since bd27fa44e13830d2b ("usb: phy: generic: Don't use regulator framework for RESET line") we no longer model the reset line as a regulator supply, so adapt the documentation accordingly. Acked-by: Roger Quadros Signed-off-by: Fabio Estevam Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt index 1bd37fa..8db5b33 100644 --- a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt +++ b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt @@ -13,7 +13,7 @@ Optional properties: - clock-frequency: the clock frequency (in Hz) that the PHY clock must be configured to. -- vcc-supply: phandle to the regulator that provides RESET to the PHY. +- vcc-supply: phandle to the regulator that provides power to the PHY. - reset-gpios: Should specify the GPIO for reset. -- cgit v0.10.2 From bbea6de1bd12dd509f774f2a3165216eeac7bc54 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Wed, 10 Dec 2014 12:34:00 +0100 Subject: usb: gadget: f_uvc: rename a macro to avoid conflicts When configfs is integrated, CONFIGFS_ATTR_STRUCT and CONFIGFS_ATTR_OPS macros should be used, but the latter expects that tere is a to_f_uvc_opts function accepting a config_item, whereas the macro being changed can be applied to a different type of argument. Signed-off-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 945b3bd..5b4ab39 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -605,7 +605,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) INFO(cdev, "uvc_function_bind\n"); - opts = to_f_uvc_opts(f->fi); + opts = fi_to_f_uvc_opts(f->fi); /* Sanity check the streaming endpoint module parameters. */ opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U); @@ -766,7 +766,7 @@ error: static void uvc_free_inst(struct usb_function_instance *f) { - struct f_uvc_opts *opts = to_f_uvc_opts(f); + struct f_uvc_opts *opts = fi_to_f_uvc_opts(f); kfree(opts); } @@ -818,7 +818,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) return ERR_PTR(-ENOMEM); uvc->state = UVC_STATE_DISCONNECTED; - opts = to_f_uvc_opts(fi); + opts = fi_to_f_uvc_opts(fi); uvc->desc.fs_control = opts->fs_control; uvc->desc.ss_control = opts->ss_control; diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 2a8dfdf..c0706a3 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -18,7 +18,7 @@ #include -#define to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst) +#define fi_to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst) struct f_uvc_opts { struct usb_function_instance func_inst; -- cgit v0.10.2 From 6c25955ed632227d28b85db274e519b766e26ddd Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Wed, 10 Dec 2014 12:34:01 +0100 Subject: usb: gadget: uvc: verify descriptors presence If the caller of uvc_alloc() does not provide enough descriptors, binding the function should fail, so appropriate code is returned from uvc_copy_descriptors(). uvc_function_bind() is modified accordingly to account for possible errors from uvc_copy_descriptors(). Signed-off-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 5b4ab39..62ca0c5 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -509,6 +509,9 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) break; } + if (!uvc_control_desc || !uvc_streaming_cls) + return ERR_PTR(-ENODEV); + /* Descriptors layout * * uvc_iad @@ -700,10 +703,27 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) /* Copy descriptors */ f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL); - if (gadget_is_dualspeed(cdev->gadget)) + if (IS_ERR(f->fs_descriptors)) { + ret = PTR_ERR(f->fs_descriptors); + f->fs_descriptors = NULL; + goto error; + } + if (gadget_is_dualspeed(cdev->gadget)) { f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); - if (gadget_is_superspeed(c->cdev->gadget)) + if (IS_ERR(f->hs_descriptors)) { + ret = PTR_ERR(f->hs_descriptors); + f->hs_descriptors = NULL; + goto error; + } + } + if (gadget_is_superspeed(c->cdev->gadget)) { f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); + if (IS_ERR(f->ss_descriptors)) { + ret = PTR_ERR(f->ss_descriptors); + f->ss_descriptors = NULL; + goto error; + } + } /* Preallocate control endpoint request. */ uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); -- cgit v0.10.2 From 46919a23ee87bbc4eeb6d958471174e26836f0e1 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Wed, 10 Dec 2014 12:34:02 +0100 Subject: usb: gadget: uvc: configfs support in uvc function Add support for using the uvc function as a component of USB gadgets composed with configfs. Acked-by: Laurent Pinchart Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc new file mode 100644 index 0000000..2f4a005 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -0,0 +1,265 @@ +What: /config/usb-gadget/gadget/functions/uvc.name +Date: Dec 2014 +KernelVersion: 3.20 +Description: UVC function directory + + streaming_maxburst - 0..15 (ss only) + streaming_maxpacket - 1..1023 (fs), 1..3072 (hs/ss) + streaming_interval - 1..16 + +What: /config/usb-gadget/gadget/functions/uvc.name/control +Date: Dec 2014 +KernelVersion: 3.20 +Description: Control descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/class +Date: Dec 2014 +KernelVersion: 3.20 +Description: Class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/class/ss +Date: Dec 2014 +KernelVersion: 3.20 +Description: Super speed control class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/class/fs +Date: Dec 2014 +KernelVersion: 3.20 +Description: Full speed control class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal +Date: Dec 2014 +KernelVersion: 3.20 +Description: Terminal descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/output +Date: Dec 2014 +KernelVersion: 3.20 +Description: Output terminal descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/output/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default output terminal descriptors + + All attributes read only: + iTerminal - index of string descriptor + bSourceID - id of the terminal to which this terminal + is connected + bAssocTerminal - id of the input terminal to which this output + terminal is associated + wTerminalType - terminal type + bTerminalID - a non-zero id of this terminal + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/camera +Date: Dec 2014 +KernelVersion: 3.20 +Description: Camera terminal descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/camera/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default camera terminal descriptors + + All attributes read only: + bmControls - bitmap specifying which controls are + supported for the video stream + wOcularFocalLength - the value of Locular + wObjectiveFocalLengthMax- the value of Lmin + wObjectiveFocalLengthMin- the value of Lmax + iTerminal - index of string descriptor + bAssocTerminal - id of the output terminal to which + this terminal is connected + wTerminalType - terminal type + bTerminalID - a non-zero id of this terminal + +What: /config/usb-gadget/gadget/functions/uvc.name/control/processing +Date: Dec 2014 +KernelVersion: 3.20 +Description: Processing unit descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/processing/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default processing unit descriptors + + All attributes read only: + iProcessing - index of string descriptor + bmControls - bitmap specifying which controls are + supported for the video stream + wMaxMultiplier - maximum digital magnification x100 + bSourceID - id of the terminal to which this unit is + connected + bUnitID - a non-zero id of this unit + +What: /config/usb-gadget/gadget/functions/uvc.name/control/header +Date: Dec 2014 +KernelVersion: 3.20 +Description: Control header descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/header/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific control header descriptors + +dwClockFrequency +bcdUVC +What: /config/usb-gadget/gadget/functions/uvc.name/streaming +Date: Dec 2014 +KernelVersion: 3.20 +Description: Streaming descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class +Date: Dec 2014 +KernelVersion: 3.20 +Description: Streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class/ss +Date: Dec 2014 +KernelVersion: 3.20 +Description: Super speed streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class/hs +Date: Dec 2014 +KernelVersion: 3.20 +Description: High speed streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class/fs +Date: Dec 2014 +KernelVersion: 3.20 +Description: Full speed streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/color_matching +Date: Dec 2014 +KernelVersion: 3.20 +Description: Color matching descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/color_matching/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default color matching descriptors + + All attributes read only: + bMatrixCoefficients - matrix used to compute luma and + chroma values from the color primaries + bTransferCharacteristics- optoelectronic transfer + characteristic of the source picutre, + also called the gamma function + bColorPrimaries - color primaries and the reference + white + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/mjpeg +Date: Dec 2014 +KernelVersion: 3.20 +Description: MJPEG format descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/mjpeg/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific MJPEG format descriptors + + All attributes read only, + except bmaControls and bDefaultFrameIndex: + bmaControls - this format's data for bmaControls in + the streaming header + bmInterfaceFlags - specifies interlace information, + read-only + bAspectRatioY - the X dimension of the picture aspect + ratio, read-only + bAspectRatioX - the Y dimension of the picture aspect + ratio, read-only + bmFlags - characteristics of this format, + read-only + bDefaultFrameIndex - optimum frame index for this stream + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/mjpeg/name/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific MJPEG frame descriptors + + dwFrameInterval - indicates how frame interval can be + programmed; a number of values + separated by newline can be specified + dwDefaultFrameInterval - the frame interval the device would + like to use as default + dwMaxVideoFrameBufferSize- the maximum number of bytes the + compressor will produce for a video + frame or still image + dwMaxBitRate - the maximum bit rate at the shortest + frame interval in bps + dwMinBitRate - the minimum bit rate at the longest + frame interval in bps + wHeight - height of decoded bitmap frame in px + wWidth - width of decoded bitmam frame in px + bmCapabilities - still image support, fixed frame-rate + support + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/uncompressed +Date: Dec 2014 +KernelVersion: 3.20 +Description: Uncompressed format descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/uncompressed/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific uncompressed format descriptors + + bmaControls - this format's data for bmaControls in + the streaming header + bmInterfaceFlags - specifies interlace information, + read-only + bAspectRatioY - the X dimension of the picture aspect + ratio, read-only + bAspectRatioX - the Y dimension of the picture aspect + ratio, read-only + bDefaultFrameIndex - optimum frame index for this stream + bBitsPerPixel - number of bits per pixel used to + specify color in the decoded video + frame + guidFormat - globally unique id used to identify + stream-encoding format + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/uncompressed/name/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific uncompressed frame descriptors + + dwFrameInterval - indicates how frame interval can be + programmed; a number of values + separated by newline can be specified + dwDefaultFrameInterval - the frame interval the device would + like to use as default + dwMaxVideoFrameBufferSize- the maximum number of bytes the + compressor will produce for a video + frame or still image + dwMaxBitRate - the maximum bit rate at the shortest + frame interval in bps + dwMinBitRate - the minimum bit rate at the longest + frame interval in bps + wHeight - height of decoded bitmap frame in px + wWidth - width of decoded bitmam frame in px + bmCapabilities - still image support, fixed frame-rate + support + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/header +Date: Dec 2014 +KernelVersion: 3.20 +Description: Streaming header descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/header/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific streaming header descriptors + + All attributes read only: + bTriggerUsage - how the host software will respond to + a hardware trigger interrupt event + bTriggerSupport - flag specifying if hardware + triggering is supported + bStillCaptureMethod - method of still image caputre + supported + bTerminalLink - id of the output terminal to which + the video endpoint of this interface + is connected + bmInfo - capabilities of this video streaming + interface diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 747ef53..65f7f12 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -423,6 +423,17 @@ config USB_CONFIGFS_F_HID For more information, see Documentation/usb/gadget_hid.txt. +config USB_CONFIGFS_F_UVC + boolean "USB Webcam function" + depends on USB_CONFIGFS + depends on VIDEO_DEV + select VIDEOBUF2_VMALLOC + select USB_F_UVC + help + The Webcam function acts as a composite USB Audio and Video Class + device. It provides a userspace API to process UVC control requests + and stream video data to the host. + source "drivers/usb/gadget/legacy/Kconfig" endchoice diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index dd68091..f71b1aa 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -36,7 +36,7 @@ usb_f_uac1-y := f_uac1.o u_uac1.o obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o usb_f_uac2-y := f_uac2.o obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o -usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o +usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o usb_f_midi-y := f_midi.o obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 62ca0c5..76891ad 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -27,10 +27,11 @@ #include #include +#include "u_uvc.h" #include "uvc.h" +#include "uvc_configfs.h" #include "uvc_v4l2.h" #include "uvc_video.h" -#include "u_uvc.h" unsigned int uvc_gadget_trace_param; @@ -788,25 +789,104 @@ static void uvc_free_inst(struct usb_function_instance *f) { struct f_uvc_opts *opts = fi_to_f_uvc_opts(f); + mutex_destroy(&opts->lock); kfree(opts); } static struct usb_function_instance *uvc_alloc_inst(void) { struct f_uvc_opts *opts; + struct uvc_camera_terminal_descriptor *cd; + struct uvc_processing_unit_descriptor *pd; + struct uvc_output_terminal_descriptor *od; + struct uvc_color_matching_descriptor *md; + struct uvc_descriptor_header **ctl_cls; opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return ERR_PTR(-ENOMEM); opts->func_inst.free_func_inst = uvc_free_inst; - + mutex_init(&opts->lock); + + cd = &opts->uvc_camera_terminal; + cd->bLength = UVC_DT_CAMERA_TERMINAL_SIZE(3); + cd->bDescriptorType = USB_DT_CS_INTERFACE; + cd->bDescriptorSubType = UVC_VC_INPUT_TERMINAL; + cd->bTerminalID = 1; + cd->wTerminalType = cpu_to_le16(0x0201); + cd->bAssocTerminal = 0; + cd->iTerminal = 0; + cd->wObjectiveFocalLengthMin = cpu_to_le16(0); + cd->wObjectiveFocalLengthMax = cpu_to_le16(0); + cd->wOcularFocalLength = cpu_to_le16(0); + cd->bControlSize = 3; + cd->bmControls[0] = 2; + cd->bmControls[1] = 0; + cd->bmControls[2] = 0; + + pd = &opts->uvc_processing; + pd->bLength = UVC_DT_PROCESSING_UNIT_SIZE(2); + pd->bDescriptorType = USB_DT_CS_INTERFACE; + pd->bDescriptorSubType = UVC_VC_PROCESSING_UNIT; + pd->bUnitID = 2; + pd->bSourceID = 1; + pd->wMaxMultiplier = cpu_to_le16(16*1024); + pd->bControlSize = 2; + pd->bmControls[0] = 1; + pd->bmControls[1] = 0; + pd->iProcessing = 0; + + od = &opts->uvc_output_terminal; + od->bLength = UVC_DT_OUTPUT_TERMINAL_SIZE; + od->bDescriptorType = USB_DT_CS_INTERFACE; + od->bDescriptorSubType = UVC_VC_OUTPUT_TERMINAL; + od->bTerminalID = 3; + od->wTerminalType = cpu_to_le16(0x0101); + od->bAssocTerminal = 0; + od->bSourceID = 2; + od->iTerminal = 0; + + md = &opts->uvc_color_matching; + md->bLength = UVC_DT_COLOR_MATCHING_SIZE; + md->bDescriptorType = USB_DT_CS_INTERFACE; + md->bDescriptorSubType = UVC_VS_COLORFORMAT; + md->bColorPrimaries = 1; + md->bTransferCharacteristics = 1; + md->bMatrixCoefficients = 4; + + /* Prepare fs control class descriptors for configfs-based gadgets */ + ctl_cls = opts->uvc_fs_control_cls; + ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ + ctl_cls[1] = (struct uvc_descriptor_header *)cd; + ctl_cls[2] = (struct uvc_descriptor_header *)pd; + ctl_cls[3] = (struct uvc_descriptor_header *)od; + ctl_cls[4] = NULL; /* NULL-terminate */ + opts->fs_control = + (const struct uvc_descriptor_header * const *)ctl_cls; + + /* Prepare hs control class descriptors for configfs-based gadgets */ + ctl_cls = opts->uvc_ss_control_cls; + ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ + ctl_cls[1] = (struct uvc_descriptor_header *)cd; + ctl_cls[2] = (struct uvc_descriptor_header *)pd; + ctl_cls[3] = (struct uvc_descriptor_header *)od; + ctl_cls[4] = NULL; /* NULL-terminate */ + opts->ss_control = + (const struct uvc_descriptor_header * const *)ctl_cls; + + opts->streaming_interval = 1; + opts->streaming_maxpacket = 1024; + + uvcg_attach_configfs(opts); return &opts->func_inst; } static void uvc_free(struct usb_function *f) { struct uvc_device *uvc = to_uvc(f); - + struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts, + func_inst); + --opts->refcnt; kfree(uvc); } @@ -832,6 +912,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) { struct uvc_device *uvc; struct f_uvc_opts *opts; + struct uvc_descriptor_header **strm_cls; uvc = kzalloc(sizeof(*uvc), GFP_KERNEL); if (uvc == NULL) @@ -840,11 +921,30 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) uvc->state = UVC_STATE_DISCONNECTED; opts = fi_to_f_uvc_opts(fi); + mutex_lock(&opts->lock); + if (opts->uvc_fs_streaming_cls) { + strm_cls = opts->uvc_fs_streaming_cls; + opts->fs_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + if (opts->uvc_hs_streaming_cls) { + strm_cls = opts->uvc_hs_streaming_cls; + opts->hs_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + if (opts->uvc_ss_streaming_cls) { + strm_cls = opts->uvc_ss_streaming_cls; + opts->ss_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + uvc->desc.fs_control = opts->fs_control; uvc->desc.ss_control = opts->ss_control; uvc->desc.fs_streaming = opts->fs_streaming; uvc->desc.hs_streaming = opts->hs_streaming; uvc->desc.ss_streaming = opts->ss_streaming; + ++opts->refcnt; + mutex_unlock(&opts->lock); /* Register the function. */ uvc->func.name = "uvc"; diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index c0706a3..4676b60 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -17,6 +17,7 @@ #define U_UVC_H #include +#include #define fi_to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst) @@ -26,11 +27,60 @@ struct f_uvc_opts { unsigned int streaming_interval; unsigned int streaming_maxpacket; unsigned int streaming_maxburst; + + /* + * Control descriptors array pointers for full-/high-speed and + * super-speed. They point by default to the uvc_fs_control_cls and + * uvc_ss_control_cls arrays respectively. Legacy gadgets must + * override them in their gadget bind callback. + */ const struct uvc_descriptor_header * const *fs_control; const struct uvc_descriptor_header * const *ss_control; + + /* + * Streaming descriptors array pointers for full-speed, high-speed and + * super-speed. They will point to the uvc_[fhs]s_streaming_cls arrays + * for configfs-based gadgets. Legacy gadgets must initialize them in + * their gadget bind callback. + */ const struct uvc_descriptor_header * const *fs_streaming; const struct uvc_descriptor_header * const *hs_streaming; const struct uvc_descriptor_header * const *ss_streaming; + + /* Default control descriptors for configfs-based gadgets. */ + struct uvc_camera_terminal_descriptor uvc_camera_terminal; + struct uvc_processing_unit_descriptor uvc_processing; + struct uvc_output_terminal_descriptor uvc_output_terminal; + struct uvc_color_matching_descriptor uvc_color_matching; + + /* + * Control descriptors pointers arrays for full-/high-speed and + * super-speed. The first element is a configurable control header + * descriptor, the other elements point to the fixed default control + * descriptors. Used by configfs only, must not be touched by legacy + * gadgets. + */ + struct uvc_descriptor_header *uvc_fs_control_cls[5]; + struct uvc_descriptor_header *uvc_ss_control_cls[5]; + + /* + * Streaming descriptors for full-speed, high-speed and super-speed. + * Used by configfs only, must not be touched by legacy gadgets. The + * arrays are allocated at runtime as the number of descriptors isn't + * known in advance. + */ + struct uvc_descriptor_header **uvc_fs_streaming_cls; + struct uvc_descriptor_header **uvc_hs_streaming_cls; + struct uvc_descriptor_header **uvc_ss_streaming_cls; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This lock protects the descriptors from concurrent access by + * read/write and symlink creation/removal. + */ + struct mutex lock; + int refcnt; }; void uvc_set_trace_param(unsigned int trace); diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c new file mode 100644 index 0000000..33d92ab --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -0,0 +1,2439 @@ +/* + * uvc_configfs.c + * + * Configfs support for the uvc function. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + * + * 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 "u_uvc.h" +#include "uvc_configfs.h" + +#define UVCG_STREAMING_CONTROL_SIZE 1 + +#define CONFIGFS_ATTR_OPS_RO(_item) \ +static ssize_t _item##_attr_show(struct config_item *item, \ + struct configfs_attribute *attr, \ + char *page) \ +{ \ + struct _item *_item = to_##_item(item); \ + struct _item##_attribute *_item##_attr = \ + container_of(attr, struct _item##_attribute, attr); \ + ssize_t ret = 0; \ + \ + if (_item##_attr->show) \ + ret = _item##_attr->show(_item, page); \ + return ret; \ +} + +static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item); + +/* control/header/ */ +DECLARE_UVC_HEADER_DESCRIPTOR(1); + +struct uvcg_control_header { + struct config_item item; + struct UVC_HEADER_DESCRIPTOR(1) desc; + unsigned linked; +}; + +struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) +{ + return container_of(item, struct uvcg_control_header, item); +} + +CONFIGFS_ATTR_STRUCT(uvcg_control_header); +CONFIGFS_ATTR_OPS(uvcg_control_header); + +static struct configfs_item_operations uvcg_control_header_item_ops = { + .show_attribute = uvcg_control_header_attr_show, + .store_attribute = uvcg_control_header_attr_store, +}; + +#define UVCG_CTRL_HDR_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +static ssize_t uvcg_control_header_##cname##_show( \ + struct uvcg_control_header *ch, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = ch->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(ch->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_control_header_##cname##_store(struct uvcg_control_header *ch, \ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\ + int ret; \ + uxx num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = ch->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (ch->linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = str2u(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + ch->desc.aname = vnoc(num); \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_control_header_attribute \ + uvcg_control_header_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_control_header_##cname##_show, \ + uvcg_control_header_##cname##_store) + +UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, le16_to_cpu, kstrtou16, u16, cpu_to_le16, + 0xffff); + +UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, le32_to_cpu, kstrtou32, + u32, cpu_to_le32, 0x7fffffff); + +#undef UVCG_CTRL_HDR_ATTR + +static struct configfs_attribute *uvcg_control_header_attrs[] = { + &uvcg_control_header_bcd_uvc.attr, + &uvcg_control_header_dw_clock_frequency.attr, + NULL, +}; + +struct config_item_type uvcg_control_header_type = { + .ct_item_ops = &uvcg_control_header_item_ops, + .ct_attrs = uvcg_control_header_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *uvcg_control_header_make(struct config_group *group, + const char *name) +{ + struct uvcg_control_header *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->desc.bLength = UVC_DT_HEADER_SIZE(1); + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VC_HEADER; + h->desc.bcdUVC = cpu_to_le16(0x0100); + h->desc.dwClockFrequency = cpu_to_le32(48000000); + + config_item_init_type_name(&h->item, name, &uvcg_control_header_type); + + return &h->item; +} + +void uvcg_control_header_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_control_header *h = to_uvcg_control_header(item); + + kfree(h); +} + +/* control/header */ +static struct uvcg_control_header_grp { + struct config_group group; +} uvcg_control_header_grp; + +static struct configfs_group_operations uvcg_control_header_grp_ops = { + .make_item = uvcg_control_header_make, + .drop_item = uvcg_control_header_drop, +}; + +static struct config_item_type uvcg_control_header_grp_type = { + .ct_group_ops = &uvcg_control_header_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* control/processing/default */ +static struct uvcg_default_processing { + struct config_group group; +} uvcg_default_processing; + +static inline struct uvcg_default_processing +*to_uvcg_default_processing(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_processing, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_processing); +CONFIGFS_ATTR_OPS_RO(uvcg_default_processing); + +static struct configfs_item_operations uvcg_default_processing_item_ops = { + .show_attribute = uvcg_default_processing_attr_show, +}; + +#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_processing_##cname##_show( \ + struct uvcg_default_processing *dp, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; \ + struct uvc_processing_unit_descriptor *pd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + pd = &opts->uvc_processing; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(pd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_default_processing_attribute \ + uvcg_default_processing_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_processing_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, identity_conv); +UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, identity_conv); +UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, le16_to_cpu); +UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, identity_conv); + +#undef identity_conv + +#undef UVCG_DEFAULT_PROCESSING_ATTR + +static ssize_t uvcg_default_processing_bm_controls_show( + struct uvcg_default_processing *dp, char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; + struct uvc_processing_unit_descriptor *pd; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + pd = &opts->uvc_processing; + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < pd->bControlSize; ++i) { + result += sprintf(pg, "%d\n", pd->bmControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static struct uvcg_default_processing_attribute + uvcg_default_processing_bm_controls = + __CONFIGFS_ATTR_RO(bmControls, + uvcg_default_processing_bm_controls_show); + +static struct configfs_attribute *uvcg_default_processing_attrs[] = { + &uvcg_default_processing_b_unit_id.attr, + &uvcg_default_processing_b_source_id.attr, + &uvcg_default_processing_w_max_multiplier.attr, + &uvcg_default_processing_bm_controls.attr, + &uvcg_default_processing_i_processing.attr, + NULL, +}; + +static struct config_item_type uvcg_default_processing_type = { + .ct_item_ops = &uvcg_default_processing_item_ops, + .ct_attrs = uvcg_default_processing_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_processing {}; */ + +static struct config_group *uvcg_processing_default_groups[] = { + &uvcg_default_processing.group, + NULL, +}; + +/* control/processing */ +static struct uvcg_processing_grp { + struct config_group group; +} uvcg_processing_grp; + +static struct config_item_type uvcg_processing_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* control/terminal/camera/default */ +static struct uvcg_default_camera { + struct config_group group; +} uvcg_default_camera; + +static inline struct uvcg_default_camera +*to_uvcg_default_camera(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_camera, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_camera); +CONFIGFS_ATTR_OPS_RO(uvcg_default_camera); + +static struct configfs_item_operations uvcg_default_camera_item_ops = { + .show_attribute = uvcg_default_camera_attr_show, +}; + +#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_camera_##cname##_show( \ + struct uvcg_default_camera *dc, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \ + struct uvc_camera_terminal_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> \ + ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_camera_terminal; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return result; \ +} \ + \ +static struct uvcg_default_camera_attribute \ + uvcg_default_camera_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_camera_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, identity_conv); +UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, le16_to_cpu); +UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv); +UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, identity_conv); +UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin, + le16_to_cpu); +UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax, + le16_to_cpu); +UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength, + le16_to_cpu); + +#undef identity_conv + +#undef UVCG_DEFAULT_CAMERA_ATTR + +static ssize_t uvcg_default_camera_bm_controls_show( + struct uvcg_default_camera *dc, char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; + struct uvc_camera_terminal_descriptor *cd; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> + ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_camera_terminal; + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < cd->bControlSize; ++i) { + result += sprintf(pg, "%d\n", cd->bmControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static struct uvcg_default_camera_attribute + uvcg_default_camera_bm_controls = + __CONFIGFS_ATTR_RO(bmControls, uvcg_default_camera_bm_controls_show); + +static struct configfs_attribute *uvcg_default_camera_attrs[] = { + &uvcg_default_camera_b_terminal_id.attr, + &uvcg_default_camera_w_terminal_type.attr, + &uvcg_default_camera_b_assoc_terminal.attr, + &uvcg_default_camera_i_terminal.attr, + &uvcg_default_camera_w_objective_focal_length_min.attr, + &uvcg_default_camera_w_objective_focal_length_max.attr, + &uvcg_default_camera_w_ocular_focal_length.attr, + &uvcg_default_camera_bm_controls.attr, + NULL, +}; + +static struct config_item_type uvcg_default_camera_type = { + .ct_item_ops = &uvcg_default_camera_item_ops, + .ct_attrs = uvcg_default_camera_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_camera {}; */ + +static struct config_group *uvcg_camera_default_groups[] = { + &uvcg_default_camera.group, + NULL, +}; + +/* control/terminal/camera */ +static struct uvcg_camera_grp { + struct config_group group; +} uvcg_camera_grp; + +static struct config_item_type uvcg_camera_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* control/terminal/output/default */ +static struct uvcg_default_output { + struct config_group group; +} uvcg_default_output; + +static inline struct uvcg_default_output +*to_uvcg_default_output(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_output, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_output); +CONFIGFS_ATTR_OPS_RO(uvcg_default_output); + +static struct configfs_item_operations uvcg_default_output_item_ops = { + .show_attribute = uvcg_default_output_attr_show, +}; + +#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_output_##cname##_show( \ + struct uvcg_default_output *dout, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dout->group.cg_subsys->su_mutex; \ + struct uvc_output_terminal_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dout->group.cg_item.ci_parent->ci_parent-> \ + ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_output_terminal; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return result; \ +} \ + \ +static struct uvcg_default_output_attribute \ + uvcg_default_output_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_output_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, identity_conv); +UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, le16_to_cpu); +UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv); +UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, identity_conv); +UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, identity_conv); + +#undef identity_conv + +#undef UVCG_DEFAULT_OUTPUT_ATTR + +static struct configfs_attribute *uvcg_default_output_attrs[] = { + &uvcg_default_output_b_terminal_id.attr, + &uvcg_default_output_w_terminal_type.attr, + &uvcg_default_output_b_assoc_terminal.attr, + &uvcg_default_output_b_source_id.attr, + &uvcg_default_output_i_terminal.attr, + NULL, +}; + +static struct config_item_type uvcg_default_output_type = { + .ct_item_ops = &uvcg_default_output_item_ops, + .ct_attrs = uvcg_default_output_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_output {}; */ + +static struct config_group *uvcg_output_default_groups[] = { + &uvcg_default_output.group, + NULL, +}; + +/* control/terminal/output */ +static struct uvcg_output_grp { + struct config_group group; +} uvcg_output_grp; + +static struct config_item_type uvcg_output_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_terminal_default_groups[] = { + &uvcg_camera_grp.group, + &uvcg_output_grp.group, + NULL, +}; + +/* control/terminal */ +static struct uvcg_terminal_grp { + struct config_group group; +} uvcg_terminal_grp; + +static struct config_item_type uvcg_terminal_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* control/class/{fs} */ +static struct uvcg_control_class { + struct config_group group; +} uvcg_control_class_fs, uvcg_control_class_ss; + + +static inline struct uvc_descriptor_header +**uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o) +{ + struct uvcg_control_class *cl = container_of(to_config_group(i), + struct uvcg_control_class, group); + + if (cl == &uvcg_control_class_fs) + return o->uvc_fs_control_cls; + + if (cl == &uvcg_control_class_ss) + return o->uvc_ss_control_cls; + + return NULL; +} + +static int uvcg_control_class_allow_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *control, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header **class_array; + struct uvcg_control_header *target_hdr; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + control = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(control), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(control->ci_parent); + + mutex_lock(&opts->lock); + + class_array = uvcg_get_ctl_class_arr(src, opts); + if (!class_array) + goto unlock; + if (opts->refcnt || class_array[0]) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_control_header(target); + ++target_hdr->linked; + class_array[0] = (struct uvc_descriptor_header *)&target_hdr->desc; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static int uvcg_control_class_drop_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *control, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header **class_array; + struct uvcg_control_header *target_hdr; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + control = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(control), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(control->ci_parent); + + mutex_lock(&opts->lock); + + class_array = uvcg_get_ctl_class_arr(src, opts); + if (!class_array) + goto unlock; + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_control_header(target); + --target_hdr->linked; + class_array[0] = NULL; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static struct configfs_item_operations uvcg_control_class_item_ops = { + .allow_link = uvcg_control_class_allow_link, + .drop_link = uvcg_control_class_drop_link, +}; + +static struct config_item_type uvcg_control_class_type = { + .ct_item_ops = &uvcg_control_class_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_control_class_default_groups[] = { + &uvcg_control_class_fs.group, + &uvcg_control_class_ss.group, + NULL, +}; + +/* control/class */ +static struct uvcg_control_class_grp { + struct config_group group; +} uvcg_control_class_grp; + +static struct config_item_type uvcg_control_class_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_control_default_groups[] = { + &uvcg_control_header_grp.group, + &uvcg_processing_grp.group, + &uvcg_terminal_grp.group, + &uvcg_control_class_grp.group, + NULL, +}; + +/* control */ +static struct uvcg_control_grp { + struct config_group group; +} uvcg_control_grp; + +static struct config_item_type uvcg_control_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* streaming/uncompressed */ +static struct uvcg_uncompressed_grp { + struct config_group group; +} uvcg_uncompressed_grp; + +/* streaming/mjpeg */ +static struct uvcg_mjpeg_grp { + struct config_group group; +} uvcg_mjpeg_grp; + +static struct config_item *fmt_parent[] = { + &uvcg_uncompressed_grp.group.cg_item, + &uvcg_mjpeg_grp.group.cg_item, +}; + +enum uvcg_format_type { + UVCG_UNCOMPRESSED = 0, + UVCG_MJPEG, +}; + +struct uvcg_format { + struct config_group group; + enum uvcg_format_type type; + unsigned linked; + unsigned num_frames; + __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; +}; + +struct uvcg_format *to_uvcg_format(struct config_item *item) +{ + return container_of(to_config_group(item), struct uvcg_format, group); +} + +static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &f->group.cg_subsys->su_mutex; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = f->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result = sprintf(pg, "0x"); + pg += result; + for (i = 0; i < UVCG_STREAMING_CONTROL_SIZE; ++i) { + result += sprintf(pg, "%x\n", f->bmaControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static ssize_t uvcg_format_bma_controls_store(struct uvcg_format *ch, + const char *page, size_t len) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->group.cg_subsys->su_mutex; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + if (len < 4 || *page != '0' || + (*(page + 1) != 'x' && *(page + 1) != 'X')) + goto end; + ret = hex2bin(ch->bmaControls, page + 2, 1); + if (ret < 0) + goto end; + ret = len; +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +struct uvcg_format_ptr { + struct uvcg_format *fmt; + struct list_head entry; +}; + +/* streaming/header/ */ +struct uvcg_streaming_header { + struct config_item item; + struct uvc_input_header_descriptor desc; + unsigned linked; + struct list_head formats; + unsigned num_fmt; +}; + +struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) +{ + return container_of(item, struct uvcg_streaming_header, item); +} + +CONFIGFS_ATTR_STRUCT(uvcg_streaming_header); +CONFIGFS_ATTR_OPS(uvcg_streaming_header); + +static int uvcg_streaming_header_allow_link(struct config_item *src, + struct config_item *target) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + struct uvcg_streaming_header *src_hdr; + struct uvcg_format *target_fmt = NULL; + struct uvcg_format_ptr *format_ptr; + int i, ret = -EINVAL; + + src_hdr = to_uvcg_streaming_header(src); + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + if (src_hdr->linked) { + ret = -EBUSY; + goto out; + } + + for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i) + if (target->ci_parent == fmt_parent[i]) + break; + if (i == ARRAY_SIZE(fmt_parent)) + goto out; + + target_fmt = container_of(to_config_group(target), struct uvcg_format, + group); + if (!target_fmt) + goto out; + + format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL); + if (!format_ptr) { + ret = PTR_ERR(format_ptr); + goto out; + } + ret = 0; + format_ptr->fmt = target_fmt; + list_add_tail(&format_ptr->entry, &src_hdr->formats); + ++src_hdr->num_fmt; + +out: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static int uvcg_streaming_header_drop_link(struct config_item *src, + struct config_item *target) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + struct uvcg_streaming_header *src_hdr; + struct uvcg_format *target_fmt = NULL; + struct uvcg_format_ptr *format_ptr, *tmp; + int ret = -EINVAL; + + src_hdr = to_uvcg_streaming_header(src); + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + target_fmt = container_of(to_config_group(target), struct uvcg_format, + group); + if (!target_fmt) + goto out; + + list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry) + if (format_ptr->fmt == target_fmt) { + list_del(&format_ptr->entry); + kfree(format_ptr); + --src_hdr->num_fmt; + break; + } + +out: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; + +} + +static struct configfs_item_operations uvcg_streaming_header_item_ops = { + .show_attribute = uvcg_streaming_header_attr_show, + .store_attribute = uvcg_streaming_header_attr_store, + .allow_link = uvcg_streaming_header_allow_link, + .drop_link = uvcg_streaming_header_drop_link, +}; + +#define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv) \ +static ssize_t uvcg_streaming_header_##cname##_show( \ + struct uvcg_streaming_header *sh, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &sh->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = sh->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(sh->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_streaming_header_attribute \ + uvcg_streaming_header_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_streaming_header_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod, + identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, identity_conv); + +#undef identity_conv + +#undef UVCG_STREAMING_HEADER_ATTR + +static struct configfs_attribute *uvcg_streaming_header_attrs[] = { + &uvcg_streaming_header_bm_info.attr, + &uvcg_streaming_header_b_terminal_link.attr, + &uvcg_streaming_header_b_still_capture_method.attr, + &uvcg_streaming_header_b_trigger_support.attr, + &uvcg_streaming_header_b_trigger_usage.attr, + NULL, +}; + +struct config_item_type uvcg_streaming_header_type = { + .ct_item_ops = &uvcg_streaming_header_item_ops, + .ct_attrs = uvcg_streaming_header_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item +*uvcg_streaming_header_make(struct config_group *group, const char *name) +{ + struct uvcg_streaming_header *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + INIT_LIST_HEAD(&h->formats); + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_INPUT_HEADER; + h->desc.bTerminalLink = 3; + h->desc.bControlSize = UVCG_STREAMING_CONTROL_SIZE; + + config_item_init_type_name(&h->item, name, &uvcg_streaming_header_type); + + return &h->item; +} + +void uvcg_streaming_header_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_streaming_header *h = to_uvcg_streaming_header(item); + + kfree(h); +} + +/* streaming/header */ +static struct uvcg_streaming_header_grp { + struct config_group group; +} uvcg_streaming_header_grp; + +static struct configfs_group_operations uvcg_streaming_header_grp_ops = { + .make_item = uvcg_streaming_header_make, + .drop_item = uvcg_streaming_header_drop, +}; + +static struct config_item_type uvcg_streaming_header_grp_type = { + .ct_group_ops = &uvcg_streaming_header_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* streaming/// */ +struct uvcg_frame { + struct { + u8 b_length; + u8 b_descriptor_type; + u8 b_descriptor_subtype; + u8 b_frame_index; + u8 bm_capabilities; + u16 w_width; + u16 w_height; + u32 dw_min_bit_rate; + u32 dw_max_bit_rate; + u32 dw_max_video_frame_buffer_size; + u32 dw_default_frame_interval; + u8 b_frame_interval_type; + } __attribute__((packed)) frame; + u32 *dw_frame_interval; + enum uvcg_format_type fmt_type; + struct config_item item; +}; + +struct uvcg_frame *to_uvcg_frame(struct config_item *item) +{ + return container_of(item, struct uvcg_frame, item); +} + +CONFIGFS_ATTR_STRUCT(uvcg_frame); +CONFIGFS_ATTR_OPS(uvcg_frame); + +static struct configfs_item_operations uvcg_frame_item_ops = { + .show_attribute = uvcg_frame_attr_show, + .store_attribute = uvcg_frame_attr_store, +}; + +#define UVCG_FRAME_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(f->frame.cname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t uvcg_frame_##cname##_store(struct uvcg_frame *f, \ + const char *page, size_t len)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct uvcg_format *fmt; \ + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ + int ret; \ + uxx num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + fmt = to_uvcg_format(f->item.ci_parent); \ + \ + mutex_lock(&opts->lock); \ + if (fmt->linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = str2u(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + f->frame.cname = vnoc(num); \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_frame_attribute \ + uvcg_frame_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_frame_##cname##_show, \ + uvcg_frame_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, identity_conv, kstrtou8, u8, + identity_conv, 0xFF); +UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, kstrtou16, u16, cpu_to_le16, + 0xFFFF); +UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, kstrtou16, u16, cpu_to_le16, + 0xFFFF); +UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, kstrtou32, u32, + cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, kstrtou32, u32, + cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, + le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, + le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); + +#undef identity_conv + +#undef UVCG_FRAME_ATTR + +static ssize_t uvcg_frame_dw_frame_interval_show(struct uvcg_frame *frm, + char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &frm->item.ci_group->cg_subsys->su_mutex; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = frm->item.ci_parent->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) { + result += sprintf(pg, "%d\n", + le32_to_cpu(frm->dw_frame_interval[i])); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static inline int __uvcg_count_frm_intrv(char *buf, void *priv) +{ + ++*((int *)priv); + return 0; +} + +static inline int __uvcg_fill_frm_intrv(char *buf, void *priv) +{ + u32 num, **interv; + int ret; + + ret = kstrtou32(buf, 0, &num); + if (ret) + return ret; + if (num > 0xFFFFFFFF) + return -EINVAL; + + interv = priv; + **interv = cpu_to_le32(num); + ++*interv; + + return 0; +} + +static int __uvcg_iter_frm_intrv(const char *page, size_t len, + int (*fun)(char *, void *), void *priv) +{ + /* sign, base 2 representation, newline, terminator */ + char buf[1 + sizeof(u32) * 8 + 1 + 1]; + const char *pg = page; + int i, ret; + + if (!fun) + return -EINVAL; + + while (pg - page < len) { + i = 0; + while (i < sizeof(buf) && (pg - page < len) && + *pg != '\0' && *pg != '\n') + buf[i++] = *pg++; + if (i == sizeof(buf)) + return -EINVAL; + while ((pg - page < len) && (*pg == '\0' || *pg == '\n')) + ++pg; + buf[i] = '\0'; + ret = fun(buf, priv); + if (ret) + return ret; + } + + return 0; +} + +static ssize_t uvcg_frame_dw_frame_interval_store(struct uvcg_frame *ch, + const char *page, size_t len) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct uvcg_format *fmt; + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex; + int ret = 0, n = 0; + u32 *frm_intrv, *tmp; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->item.ci_parent->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + fmt = to_uvcg_format(ch->item.ci_parent); + + mutex_lock(&opts->lock); + if (fmt->linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = __uvcg_iter_frm_intrv(page, len, __uvcg_count_frm_intrv, &n); + if (ret) + goto end; + + tmp = frm_intrv = kcalloc(n, sizeof(u32), GFP_KERNEL); + if (!frm_intrv) { + ret = -ENOMEM; + goto end; + } + + ret = __uvcg_iter_frm_intrv(page, len, __uvcg_fill_frm_intrv, &tmp); + if (ret) { + kfree(frm_intrv); + goto end; + } + + kfree(ch->dw_frame_interval); + ch->dw_frame_interval = frm_intrv; + ch->frame.b_frame_interval_type = n; + ret = len; + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static struct uvcg_frame_attribute + uvcg_frame_dw_frame_interval = + __CONFIGFS_ATTR(dwFrameInterval, S_IRUGO | S_IWUSR, + uvcg_frame_dw_frame_interval_show, + uvcg_frame_dw_frame_interval_store); + +static struct configfs_attribute *uvcg_frame_attrs[] = { + &uvcg_frame_bm_capabilities.attr, + &uvcg_frame_w_width.attr, + &uvcg_frame_w_height.attr, + &uvcg_frame_dw_min_bit_rate.attr, + &uvcg_frame_dw_max_bit_rate.attr, + &uvcg_frame_dw_max_video_frame_buffer_size.attr, + &uvcg_frame_dw_default_frame_interval.attr, + &uvcg_frame_dw_frame_interval.attr, + NULL, +}; + +struct config_item_type uvcg_frame_type = { + .ct_item_ops = &uvcg_frame_item_ops, + .ct_attrs = uvcg_frame_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *uvcg_frame_make(struct config_group *group, + const char *name) +{ + struct uvcg_frame *h; + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->frame.b_descriptor_type = USB_DT_CS_INTERFACE; + h->frame.b_frame_index = 1; + h->frame.w_width = cpu_to_le16(640); + h->frame.w_height = cpu_to_le16(360); + h->frame.dw_min_bit_rate = cpu_to_le32(18432000); + h->frame.dw_max_bit_rate = cpu_to_le32(55296000); + h->frame.dw_max_video_frame_buffer_size = cpu_to_le32(460800); + h->frame.dw_default_frame_interval = cpu_to_le32(666666); + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + fmt = to_uvcg_format(&group->cg_item); + if (fmt->type == UVCG_UNCOMPRESSED) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_UNCOMPRESSED; + h->fmt_type = UVCG_UNCOMPRESSED; + } else if (fmt->type == UVCG_MJPEG) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG; + h->fmt_type = UVCG_MJPEG; + } else { + mutex_unlock(&opts->lock); + return ERR_PTR(-EINVAL); + } + ++fmt->num_frames; + mutex_unlock(&opts->lock); + + config_item_init_type_name(&h->item, name, &uvcg_frame_type); + + return &h->item; +} + +void uvcg_frame_drop(struct config_group *group, struct config_item *item) +{ + struct uvcg_frame *h = to_uvcg_frame(item); + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + fmt = to_uvcg_format(&group->cg_item); + --fmt->num_frames; + kfree(h); + mutex_unlock(&opts->lock); +} + +/* streaming/uncompressed/ */ +struct uvcg_uncompressed { + struct uvcg_format fmt; + struct uvc_format_uncompressed desc; +}; + +struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) +{ + return container_of( + container_of(to_config_group(item), struct uvcg_format, group), + struct uvcg_uncompressed, fmt); +} + +CONFIGFS_ATTR_STRUCT(uvcg_uncompressed); +CONFIGFS_ATTR_OPS(uvcg_uncompressed); + +static struct configfs_item_operations uvcg_uncompressed_item_ops = { + .show_attribute = uvcg_uncompressed_attr_show, + .store_attribute = uvcg_uncompressed_attr_store, +}; + +static struct configfs_group_operations uvcg_uncompressed_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +static ssize_t uvcg_uncompressed_guid_format_show(struct uvcg_uncompressed *ch, + char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(ch->desc.guidFormat); +} + +static ssize_t uvcg_uncompressed_guid_format_store(struct uvcg_uncompressed *ch, + const char *page, size_t len) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + int ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->fmt.linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + memcpy(ch->desc.guidFormat, page, + min(sizeof(ch->desc.guidFormat), len)); + ret = sizeof(ch->desc.guidFormat); + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static struct uvcg_uncompressed_attribute uvcg_uncompressed_guid_format = + __CONFIGFS_ATTR(guidFormat, S_IRUGO | S_IWUSR, + uvcg_uncompressed_guid_format_show, + uvcg_uncompressed_guid_format_store); + + +#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, conv) \ +static ssize_t uvcg_uncompressed_##cname##_show( \ + struct uvcg_uncompressed *u, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_uncompressed_attribute \ + uvcg_uncompressed_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_uncompressed_##cname##_show) + +#define UVCG_UNCOMPRESSED_ATTR(cname, aname, conv) \ +static ssize_t uvcg_uncompressed_##cname##_show( \ + struct uvcg_uncompressed *u, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_uncompressed_##cname##_store(struct uvcg_uncompressed *u, \ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > 255) { \ + ret = -EINVAL; \ + goto end; \ + } \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_uncompressed_attribute \ + uvcg_uncompressed_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_uncompressed_##cname##_show, \ + uvcg_uncompressed_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, identity_conv); +UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, + identity_conv); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv); +UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv); + +#undef identity_conv + +#undef UVCG_UNCOMPRESSED_ATTR +#undef UVCG_UNCOMPRESSED_ATTR_RO + +static inline ssize_t +uvcg_uncompressed_bma_controls_show(struct uvcg_uncompressed *unc, char *page) +{ + return uvcg_format_bma_controls_show(&unc->fmt, page); +} + +static inline ssize_t +uvcg_uncompressed_bma_controls_store(struct uvcg_uncompressed *ch, + const char *page, size_t len) +{ + return uvcg_format_bma_controls_store(&ch->fmt, page, len); +} + +static struct uvcg_uncompressed_attribute uvcg_uncompressed_bma_controls = + __CONFIGFS_ATTR(bmaControls, S_IRUGO | S_IWUSR, + uvcg_uncompressed_bma_controls_show, + uvcg_uncompressed_bma_controls_store); + +static struct configfs_attribute *uvcg_uncompressed_attrs[] = { + &uvcg_uncompressed_guid_format.attr, + &uvcg_uncompressed_b_bits_per_pixel.attr, + &uvcg_uncompressed_b_default_frame_index.attr, + &uvcg_uncompressed_b_aspect_ratio_x.attr, + &uvcg_uncompressed_b_aspect_ratio_y.attr, + &uvcg_uncompressed_bm_interface_flags.attr, + &uvcg_uncompressed_bma_controls.attr, + NULL, +}; + +struct config_item_type uvcg_uncompressed_type = { + .ct_item_ops = &uvcg_uncompressed_item_ops, + .ct_group_ops = &uvcg_uncompressed_group_ops, + .ct_attrs = uvcg_uncompressed_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_uncompressed_make(struct config_group *group, + const char *name) +{ + static char guid[] = { + 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + }; + struct uvcg_uncompressed *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->desc.bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_UNCOMPRESSED; + memcpy(h->desc.guidFormat, guid, sizeof(guid)); + h->desc.bBitsPerPixel = 16; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterfaceFlags = 0; + h->desc.bCopyProtect = 0; + + h->fmt.type = UVCG_UNCOMPRESSED; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_uncompressed_type); + + return &h->fmt.group; +} + +void uvcg_uncompressed_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_uncompressed *h = to_uvcg_uncompressed(item); + + kfree(h); +} + +static struct configfs_group_operations uvcg_uncompressed_grp_ops = { + .make_group = uvcg_uncompressed_make, + .drop_item = uvcg_uncompressed_drop, +}; + +static struct config_item_type uvcg_uncompressed_grp_type = { + .ct_group_ops = &uvcg_uncompressed_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* streaming/mjpeg/ */ +struct uvcg_mjpeg { + struct uvcg_format fmt; + struct uvc_format_mjpeg desc; +}; + +struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) +{ + return container_of( + container_of(to_config_group(item), struct uvcg_format, group), + struct uvcg_mjpeg, fmt); +} + +CONFIGFS_ATTR_STRUCT(uvcg_mjpeg); +CONFIGFS_ATTR_OPS(uvcg_mjpeg); + +static struct configfs_item_operations uvcg_mjpeg_item_ops = { + .show_attribute = uvcg_mjpeg_attr_show, + .store_attribute = uvcg_mjpeg_attr_store, +}; + +static struct configfs_group_operations uvcg_mjpeg_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +#define UVCG_MJPEG_ATTR_RO(cname, aname, conv) \ +static ssize_t uvcg_mjpeg_##cname##_show(struct uvcg_mjpeg *u, char *page)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_mjpeg_attribute \ + uvcg_mjpeg_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_mjpeg_##cname##_show) + +#define UVCG_MJPEG_ATTR(cname, aname, conv) \ +static ssize_t uvcg_mjpeg_##cname##_show(struct uvcg_mjpeg *u, char *page)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_mjpeg_##cname##_store(struct uvcg_mjpeg *u, \ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > 255) { \ + ret = -EINVAL; \ + goto end; \ + } \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_mjpeg_attribute \ + uvcg_mjpeg_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_mjpeg_##cname##_show, \ + uvcg_mjpeg_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, + identity_conv); +UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, identity_conv); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv); +UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv); + +#undef identity_conv + +#undef UVCG_MJPEG_ATTR +#undef UVCG_MJPEG_ATTR_RO + +static inline ssize_t +uvcg_mjpeg_bma_controls_show(struct uvcg_mjpeg *unc, char *page) +{ + return uvcg_format_bma_controls_show(&unc->fmt, page); +} + +static inline ssize_t +uvcg_mjpeg_bma_controls_store(struct uvcg_mjpeg *ch, + const char *page, size_t len) +{ + return uvcg_format_bma_controls_store(&ch->fmt, page, len); +} + +static struct uvcg_mjpeg_attribute uvcg_mjpeg_bma_controls = + __CONFIGFS_ATTR(bmaControls, S_IRUGO | S_IWUSR, + uvcg_mjpeg_bma_controls_show, + uvcg_mjpeg_bma_controls_store); + +static struct configfs_attribute *uvcg_mjpeg_attrs[] = { + &uvcg_mjpeg_b_default_frame_index.attr, + &uvcg_mjpeg_bm_flags.attr, + &uvcg_mjpeg_b_aspect_ratio_x.attr, + &uvcg_mjpeg_b_aspect_ratio_y.attr, + &uvcg_mjpeg_bm_interface_flags.attr, + &uvcg_mjpeg_bma_controls.attr, + NULL, +}; + +struct config_item_type uvcg_mjpeg_type = { + .ct_item_ops = &uvcg_mjpeg_item_ops, + .ct_group_ops = &uvcg_mjpeg_group_ops, + .ct_attrs = uvcg_mjpeg_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_mjpeg_make(struct config_group *group, + const char *name) +{ + struct uvcg_mjpeg *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->desc.bLength = UVC_DT_FORMAT_MJPEG_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_MJPEG; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterfaceFlags = 0; + h->desc.bCopyProtect = 0; + + h->fmt.type = UVCG_MJPEG; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_mjpeg_type); + + return &h->fmt.group; +} + +void uvcg_mjpeg_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_mjpeg *h = to_uvcg_mjpeg(item); + + kfree(h); +} + +static struct configfs_group_operations uvcg_mjpeg_grp_ops = { + .make_group = uvcg_mjpeg_make, + .drop_item = uvcg_mjpeg_drop, +}; + +static struct config_item_type uvcg_mjpeg_grp_type = { + .ct_group_ops = &uvcg_mjpeg_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* streaming/color_matching/default */ +static struct uvcg_default_color_matching { + struct config_group group; +} uvcg_default_color_matching; + +static inline struct uvcg_default_color_matching +*to_uvcg_default_color_matching(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_color_matching, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_color_matching); +CONFIGFS_ATTR_OPS_RO(uvcg_default_color_matching); + +static struct configfs_item_operations uvcg_default_color_matching_item_ops = { + .show_attribute = uvcg_default_color_matching_attr_show, +}; + +#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_color_matching_##cname##_show( \ + struct uvcg_default_color_matching *dc, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \ + struct uvc_color_matching_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_color_matching; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_default_color_matching_attribute \ + uvcg_default_color_matching_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_color_matching_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, + identity_conv); +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics, + bTransferCharacteristics, identity_conv); +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, + identity_conv); + +#undef identity_conv + +#undef UVCG_DEFAULT_COLOR_MATCHING_ATTR + +static struct configfs_attribute *uvcg_default_color_matching_attrs[] = { + &uvcg_default_color_matching_b_color_primaries.attr, + &uvcg_default_color_matching_b_transfer_characteristics.attr, + &uvcg_default_color_matching_b_matrix_coefficients.attr, + NULL, +}; + +static struct config_item_type uvcg_default_color_matching_type = { + .ct_item_ops = &uvcg_default_color_matching_item_ops, + .ct_attrs = uvcg_default_color_matching_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_color_matching {}; */ + +static struct config_group *uvcg_color_matching_default_groups[] = { + &uvcg_default_color_matching.group, + NULL, +}; + +/* streaming/color_matching */ +static struct uvcg_color_matching_grp { + struct config_group group; +} uvcg_color_matching_grp; + +static struct config_item_type uvcg_color_matching_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* streaming/class/{fs|hs|ss} */ +static struct uvcg_streaming_class { + struct config_group group; +} uvcg_streaming_class_fs, uvcg_streaming_class_hs, uvcg_streaming_class_ss; + + +static inline struct uvc_descriptor_header +***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o) +{ + struct uvcg_streaming_class *cl = container_of(to_config_group(i), + struct uvcg_streaming_class, group); + + if (cl == &uvcg_streaming_class_fs) + return &o->uvc_fs_streaming_cls; + + if (cl == &uvcg_streaming_class_hs) + return &o->uvc_hs_streaming_cls; + + if (cl == &uvcg_streaming_class_ss) + return &o->uvc_ss_streaming_cls; + + return NULL; +} + +enum uvcg_strm_type { + UVCG_HEADER = 0, + UVCG_FORMAT, + UVCG_FRAME +}; + +static int __uvcg_iter_strm_cls(void *priv1, void *priv2, void *priv3, + int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type)) +{ + struct uvcg_streaming_header *h = priv1; + struct uvcg_format_ptr *f; + struct config_group *grp; + struct config_item *item; + struct uvcg_frame *frm; + int ret, i, j; + + if (!fun) + return -EINVAL; + + i = j = 0; + ret = fun(h, priv2, priv3, 0, UVCG_HEADER); + if (ret) + return ret; + list_for_each_entry(f, &h->formats, entry) { + ret = fun(f->fmt, priv2, priv3, i++, UVCG_FORMAT); + if (ret) + return ret; + grp = &f->fmt->group; + list_for_each_entry(item, &grp->cg_children, ci_entry) { + frm = to_uvcg_frame(item); + ret = fun(frm, priv2, priv3, j++, UVCG_FRAME); + if (ret) + return ret; + } + } + + return ret; +} + +static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, + enum uvcg_strm_type type) +{ + size_t *size = priv2; + size_t *count = priv3; + + switch (type) { + case UVCG_HEADER: { + struct uvcg_streaming_header *h = priv1; + + *size += sizeof(h->desc); + /* bmaControls */ + *size += h->num_fmt * UVCG_STREAMING_CONTROL_SIZE; + } + break; + case UVCG_FORMAT: { + struct uvcg_format *fmt = priv1; + + if (fmt->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *u = + container_of(fmt, struct uvcg_uncompressed, + fmt); + + *size += sizeof(u->desc); + } else if (fmt->type == UVCG_MJPEG) { + struct uvcg_mjpeg *m = + container_of(fmt, struct uvcg_mjpeg, fmt); + + *size += sizeof(m->desc); + } else { + return -EINVAL; + } + } + break; + case UVCG_FRAME: { + struct uvcg_frame *frm = priv1; + int sz = sizeof(frm->dw_frame_interval); + + *size += sizeof(frm->frame); + *size += frm->frame.b_frame_interval_type * sz; + } + break; + } + + ++*count; + + return 0; +} + +static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, + enum uvcg_strm_type type) +{ + void **dest = priv2; + struct uvc_descriptor_header ***array = priv3; + size_t sz; + + **array = *dest; + ++*array; + + switch (type) { + case UVCG_HEADER: { + struct uvc_input_header_descriptor *ihdr = *dest; + struct uvcg_streaming_header *h = priv1; + struct uvcg_format_ptr *f; + + memcpy(*dest, &h->desc, sizeof(h->desc)); + *dest += sizeof(h->desc); + sz = UVCG_STREAMING_CONTROL_SIZE; + list_for_each_entry(f, &h->formats, entry) { + memcpy(*dest, f->fmt->bmaControls, sz); + *dest += sz; + } + ihdr->bLength = sizeof(h->desc) + h->num_fmt * sz; + ihdr->bNumFormats = h->num_fmt; + } + break; + case UVCG_FORMAT: { + struct uvcg_format *fmt = priv1; + + if (fmt->type == UVCG_UNCOMPRESSED) { + struct uvc_format_uncompressed *unc = *dest; + struct uvcg_uncompressed *u = + container_of(fmt, struct uvcg_uncompressed, + fmt); + + memcpy(*dest, &u->desc, sizeof(u->desc)); + *dest += sizeof(u->desc); + unc->bNumFrameDescriptors = fmt->num_frames; + unc->bFormatIndex = n + 1; + } else if (fmt->type == UVCG_MJPEG) { + struct uvc_format_mjpeg *mjp = *dest; + struct uvcg_mjpeg *m = + container_of(fmt, struct uvcg_mjpeg, fmt); + + memcpy(*dest, &m->desc, sizeof(m->desc)); + *dest += sizeof(m->desc); + mjp->bNumFrameDescriptors = fmt->num_frames; + mjp->bFormatIndex = n + 1; + } else { + return -EINVAL; + } + } + break; + case UVCG_FRAME: { + struct uvcg_frame *frm = priv1; + struct uvc_descriptor_header *h = *dest; + + sz = sizeof(frm->frame); + memcpy(*dest, &frm->frame, sz); + *dest += sz; + sz = frm->frame.b_frame_interval_type * + sizeof(*frm->dw_frame_interval); + memcpy(*dest, frm->dw_frame_interval, sz); + *dest += sz; + if (frm->fmt_type == UVCG_UNCOMPRESSED) + h->bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE( + frm->frame.b_frame_interval_type); + else if (frm->fmt_type == UVCG_MJPEG) + h->bLength = UVC_DT_FRAME_MJPEG_SIZE( + frm->frame.b_frame_interval_type); + } + break; + } + + return 0; +} + +static int uvcg_streaming_class_allow_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *streaming, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header ***class_array, **cl_arr; + struct uvcg_streaming_header *target_hdr; + void *data; + size_t size = 0, count = 0; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + streaming = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(streaming), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(streaming->ci_parent); + + mutex_lock(&opts->lock); + + class_array = __uvcg_get_stream_class_arr(src, opts); + if (!class_array || *class_array || opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_streaming_header(target); + ret = __uvcg_iter_strm_cls(target_hdr, &size, &count, __uvcg_cnt_strm); + if (ret) + goto unlock; + + count += 2; /* color_matching, NULL */ + *class_array = kcalloc(count, sizeof(void *), GFP_KERNEL); + if (!*class_array) { + ret = PTR_ERR(*class_array); + goto unlock; + } + + data = kzalloc(size, GFP_KERNEL); + if (!data) { + kfree(*class_array); + *class_array = NULL; + ret = PTR_ERR(data); + goto unlock; + } + cl_arr = *class_array; + ret = __uvcg_iter_strm_cls(target_hdr, &data, &cl_arr, + __uvcg_fill_strm); + if (ret) { + kfree(*class_array); + *class_array = NULL; + kfree(data); + goto unlock; + } + *cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching; + + ++target_hdr->linked; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static int uvcg_streaming_class_drop_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *streaming, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header ***class_array; + struct uvcg_streaming_header *target_hdr; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + streaming = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(streaming), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(streaming->ci_parent); + + mutex_lock(&opts->lock); + + class_array = __uvcg_get_stream_class_arr(src, opts); + if (!class_array || !*class_array) + goto unlock; + + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_streaming_header(target); + --target_hdr->linked; + kfree(**class_array); + kfree(*class_array); + *class_array = NULL; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static struct configfs_item_operations uvcg_streaming_class_item_ops = { + .allow_link = uvcg_streaming_class_allow_link, + .drop_link = uvcg_streaming_class_drop_link, +}; + +static struct config_item_type uvcg_streaming_class_type = { + .ct_item_ops = &uvcg_streaming_class_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_streaming_class_default_groups[] = { + &uvcg_streaming_class_fs.group, + &uvcg_streaming_class_hs.group, + &uvcg_streaming_class_ss.group, + NULL, +}; + +/* streaming/class */ +static struct uvcg_streaming_class_grp { + struct config_group group; +} uvcg_streaming_class_grp; + +static struct config_item_type uvcg_streaming_class_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_streaming_default_groups[] = { + &uvcg_streaming_header_grp.group, + &uvcg_uncompressed_grp.group, + &uvcg_mjpeg_grp.group, + &uvcg_color_matching_grp.group, + &uvcg_streaming_class_grp.group, + NULL, +}; + +/* streaming */ +static struct uvcg_streaming_grp { + struct config_group group; +} uvcg_streaming_grp; + +static struct config_item_type uvcg_streaming_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_default_groups[] = { + &uvcg_control_grp.group, + &uvcg_streaming_grp.group, + NULL, +}; + +static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uvc_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_uvc_opts); +CONFIGFS_ATTR_OPS(f_uvc_opts); + +static void uvc_attr_release(struct config_item *item) +{ + struct f_uvc_opts *opts = to_f_uvc_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations uvc_item_ops = { + .release = uvc_attr_release, + .show_attribute = f_uvc_opts_attr_show, + .store_attribute = f_uvc_opts_attr_store, +}; + +#define UVCG_OPTS_ATTR(cname, conv, str2u, uxx, vnoc, limit) \ +static ssize_t f_uvc_opts_##cname##_show( \ + struct f_uvc_opts *opts, char *page) \ +{ \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(opts->cname)); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t \ +f_uvc_opts_##cname##_store(struct f_uvc_opts *opts, \ + const char *page, size_t len) \ +{ \ + int ret; \ + uxx num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = str2u(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->cname = vnoc(num); \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +static struct f_uvc_opts_attribute \ + f_uvc_opts_attribute_##cname = \ + __CONFIGFS_ATTR(cname, S_IRUGO | S_IWUSR, \ + f_uvc_opts_##cname##_show, \ + f_uvc_opts_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_OPTS_ATTR(streaming_interval, identity_conv, kstrtou8, u8, identity_conv, + 16); +UVCG_OPTS_ATTR(streaming_maxpacket, le16_to_cpu, kstrtou16, u16, le16_to_cpu, + 3072); +UVCG_OPTS_ATTR(streaming_maxburst, identity_conv, kstrtou8, u8, identity_conv, + 15); + +#undef identity_conv + +#undef UVCG_OPTS_ATTR + +static struct configfs_attribute *uvc_attrs[] = { + &f_uvc_opts_attribute_streaming_interval.attr, + &f_uvc_opts_attribute_streaming_maxpacket.attr, + &f_uvc_opts_attribute_streaming_maxburst.attr, + NULL, +}; + +static struct config_item_type uvc_func_type = { + .ct_item_ops = &uvc_item_ops, + .ct_attrs = uvc_attrs, + .ct_owner = THIS_MODULE, +}; + +static inline void uvcg_init_group(struct config_group *g, + struct config_group **default_groups, + const char *name, + struct config_item_type *type) +{ + g->default_groups = default_groups; + config_group_init_type_name(g, name, type); +} + +int uvcg_attach_configfs(struct f_uvc_opts *opts) +{ + config_group_init_type_name(&uvcg_control_header_grp.group, + "header", + &uvcg_control_header_grp_type); + config_group_init_type_name(&uvcg_default_processing.group, + "default", + &uvcg_default_processing_type); + uvcg_init_group(&uvcg_processing_grp.group, + uvcg_processing_default_groups, + "processing", + &uvcg_processing_grp_type); + config_group_init_type_name(&uvcg_default_camera.group, + "default", + &uvcg_default_camera_type); + uvcg_init_group(&uvcg_camera_grp.group, + uvcg_camera_default_groups, + "camera", + &uvcg_camera_grp_type); + config_group_init_type_name(&uvcg_default_output.group, + "default", + &uvcg_default_output_type); + uvcg_init_group(&uvcg_output_grp.group, + uvcg_output_default_groups, + "output", + &uvcg_output_grp_type); + uvcg_init_group(&uvcg_terminal_grp.group, + uvcg_terminal_default_groups, + "terminal", + &uvcg_terminal_grp_type); + config_group_init_type_name(&uvcg_control_class_fs.group, + "fs", + &uvcg_control_class_type); + config_group_init_type_name(&uvcg_control_class_ss.group, + "ss", + &uvcg_control_class_type); + uvcg_init_group(&uvcg_control_class_grp.group, + uvcg_control_class_default_groups, + "class", + &uvcg_control_class_grp_type); + uvcg_init_group(&uvcg_control_grp.group, + uvcg_control_default_groups, + "control", + &uvcg_control_grp_type); + config_group_init_type_name(&uvcg_streaming_header_grp.group, + "header", + &uvcg_streaming_header_grp_type); + config_group_init_type_name(&uvcg_uncompressed_grp.group, + "uncompressed", + &uvcg_uncompressed_grp_type); + config_group_init_type_name(&uvcg_mjpeg_grp.group, + "mjpeg", + &uvcg_mjpeg_grp_type); + config_group_init_type_name(&uvcg_default_color_matching.group, + "default", + &uvcg_default_color_matching_type); + uvcg_init_group(&uvcg_color_matching_grp.group, + uvcg_color_matching_default_groups, + "color_matching", + &uvcg_color_matching_grp_type); + config_group_init_type_name(&uvcg_streaming_class_fs.group, + "fs", + &uvcg_streaming_class_type); + config_group_init_type_name(&uvcg_streaming_class_hs.group, + "hs", + &uvcg_streaming_class_type); + config_group_init_type_name(&uvcg_streaming_class_ss.group, + "ss", + &uvcg_streaming_class_type); + uvcg_init_group(&uvcg_streaming_class_grp.group, + uvcg_streaming_class_default_groups, + "class", + &uvcg_streaming_class_grp_type); + uvcg_init_group(&uvcg_streaming_grp.group, + uvcg_streaming_default_groups, + "streaming", + &uvcg_streaming_grp_type); + uvcg_init_group(&opts->func_inst.group, + uvcg_default_groups, + "", + &uvc_func_type); + return 0; +} diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h new file mode 100644 index 0000000..085e67b --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -0,0 +1,22 @@ +/* + * uvc_configfs.h + * + * Configfs support for the uvc function. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + * + * 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 UVC_CONFIGFS_H +#define UVC_CONFIGFS_H + +struct f_uvc_opts; + +int uvcg_attach_configfs(struct f_uvc_opts *opts); + +#endif /* UVC_CONFIGFS_H */ -- cgit v0.10.2 From 135b3c4304dacd8bb01f1aa6fe4165126bee3812 Mon Sep 17 00:00:00 2001 From: Yunzhi Li Date: Mon, 8 Dec 2014 17:46:26 +0800 Subject: usb: dwc2: platform: add generic PHY framework support Get PHY parameters from devicetree and power off usb PHY during system suspend. Acked-by: Paul Zimmerman Signed-off-by: Yunzhi Li Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index ede69dc..a461406 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3410,8 +3410,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) { struct device *dev = hsotg->dev; struct s3c_hsotg_plat *plat = dev->platform_data; - struct phy *phy; - struct usb_phy *uphy; struct s3c_hsotg_ep *eps; int epnum; int ret; @@ -3421,30 +3419,23 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) hsotg->phyif = GUSBCFG_PHYIF16; /* - * Attempt to find a generic PHY, then look for an old style - * USB PHY, finally fall back to pdata + * If platform probe couldn't find a generic PHY or an old style + * USB PHY, fall back to pdata */ - phy = devm_phy_get(dev, "usb2-phy"); - if (IS_ERR(phy)) { - uphy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); - if (IS_ERR(uphy)) { - /* Fallback for pdata */ - plat = dev_get_platdata(dev); - if (!plat) { - dev_err(dev, - "no platform data or transceiver defined\n"); - return -EPROBE_DEFER; - } - hsotg->plat = plat; - } else - hsotg->uphy = uphy; - } else { - hsotg->phy = phy; + if (IS_ERR_OR_NULL(hsotg->phy) && IS_ERR_OR_NULL(hsotg->uphy)) { + plat = dev_get_platdata(dev); + if (!plat) { + dev_err(dev, + "no platform data or transceiver defined\n"); + return -EPROBE_DEFER; + } + hsotg->plat = plat; + } else if (hsotg->phy) { /* * If using the generic PHY framework, check if the PHY bus * width is 8-bit and set the phyif appropriately. */ - if (phy_get_bus_width(phy) == 8) + if (phy_get_bus_width(hsotg->phy) == 8) hsotg->phyif = GUSBCFG_PHYIF8; } diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 6a795aa..ae095f0 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -155,6 +155,8 @@ static int dwc2_driver_probe(struct platform_device *dev) struct dwc2_core_params defparams; struct dwc2_hsotg *hsotg; struct resource *res; + struct phy *phy; + struct usb_phy *uphy; int retval; int irq; @@ -212,6 +214,24 @@ static int dwc2_driver_probe(struct platform_device *dev) hsotg->dr_mode = of_usb_get_dr_mode(dev->dev.of_node); + /* + * Attempt to find a generic PHY, then look for an old style + * USB PHY + */ + phy = devm_phy_get(&dev->dev, "usb2-phy"); + if (IS_ERR(phy)) { + hsotg->phy = NULL; + uphy = devm_usb_get_phy(&dev->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(uphy)) + hsotg->uphy = NULL; + else + hsotg->uphy = uphy; + } else { + hsotg->phy = phy; + phy_power_on(hsotg->phy); + phy_init(hsotg->phy); + } + spin_lock_init(&hsotg->lock); mutex_init(&hsotg->init_mutex); retval = dwc2_gadget_init(hsotg, irq); @@ -231,8 +251,15 @@ static int __maybe_unused dwc2_suspend(struct device *dev) struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); int ret = 0; - if (dwc2_is_device_mode(dwc2)) + if (dwc2_is_device_mode(dwc2)) { ret = s3c_hsotg_suspend(dwc2); + } else { + if (dwc2->lx_state == DWC2_L0) + return 0; + phy_exit(dwc2->phy); + phy_power_off(dwc2->phy); + + } return ret; } @@ -241,8 +268,13 @@ static int __maybe_unused dwc2_resume(struct device *dev) struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); int ret = 0; - if (dwc2_is_device_mode(dwc2)) + if (dwc2_is_device_mode(dwc2)) { ret = s3c_hsotg_resume(dwc2); + } else { + phy_power_on(dwc2->phy); + phy_init(dwc2->phy); + + } return ret; } -- cgit v0.10.2 From 5e841efef7543209c1d62526311fbbcf85f3613d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 25 Nov 2014 18:55:57 +0300 Subject: usb: gadget: udc: clean up a printk We already know what "value" is, so there is no need to check. It puzzles static checkers to have the unneeded condition. Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index ff67cea..ebf82bd 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -881,8 +881,8 @@ static int ep_set_halt(struct bdc_ep *ep, u32 value) ret = bdc_ep_set_stall(bdc, ep->ep_num); if (ret) - dev_err(bdc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", ep->name); + dev_err(bdc->dev, "failed to set STALL on %s\n", + ep->name); else ep->flags |= BDC_EP_STALL; } else { @@ -890,8 +890,8 @@ static int ep_set_halt(struct bdc_ep *ep, u32 value) dev_dbg(bdc->dev, "Before Clear\n"); ret = bdc_ep_clear_stall(bdc, ep->ep_num); if (ret) - dev_err(bdc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", ep->name); + dev_err(bdc->dev, "failed to clear STALL on %s\n", + ep->name); else ep->flags &= ~BDC_EP_STALL; dev_dbg(bdc->dev, "After Clear\n"); -- cgit v0.10.2 From d71b0d7764a7b018bf73ae44f67dc8545e614eef Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 25 Nov 2014 18:56:22 +0300 Subject: usb: gadget: udc: remove bogus NULL check "ep" isn't NULL here, and static checkers complain because we dereferenced it on the previous line. Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index ebf82bd..fdc0e9f 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -719,7 +719,7 @@ static int ep_queue(struct bdc_ep *ep, struct bdc_req *req) int ret = 0; bdc = ep->bdc; - if (!req || !ep || !ep->usb_ep.desc) + if (!req || !ep->usb_ep.desc) return -EINVAL; req->usb_req.actual = 0; -- cgit v0.10.2 From 90f9e53345d307f53b040e7a50193f20f88ab87a Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Wed, 26 Nov 2014 07:00:45 +0100 Subject: usb: gadget: fix platform_no_drv_owner.cocci warnings Remove .owner field if calls are used which set it automatically Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci Signed-off-by: Julia Lawall Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index c6dfef8..5c8f4ef 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -521,7 +521,6 @@ static int bdc_remove(struct platform_device *pdev) static struct platform_driver bdc_driver = { .driver = { .name = BRCM_BDC_NAME, - .owner = THIS_MODULE }, .probe = bdc_probe, .remove = bdc_remove, -- cgit v0.10.2 From 625763d101c7aa706b35f052cd978a61f1ef26bc Mon Sep 17 00:00:00 2001 From: Xuebing Wang Date: Wed, 10 Dec 2014 16:28:14 +0800 Subject: usb: gadget: f_uac1: configure endpoint before using it UAC1 forget to do it. Signed-off-by: Xuebing Wang Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index f7b2032..380b137 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -584,6 +584,7 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (intf == 1) { if (alt == 1) { + config_ep_by_speed(cdev->gadget, f, out_ep); usb_ep_enable(out_ep); out_ep->driver_data = audio; audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); -- cgit v0.10.2 From 8d252db174ab27485126f540f368149e3c875e5a Mon Sep 17 00:00:00 2001 From: Xuebing Wang Date: Wed, 10 Dec 2014 16:28:15 +0800 Subject: usb: gadget: f_uac1: update Class-Specific AudioControl Interface Header Descriptor Update this according to USB Audio Class 1.0 spec. This fixes the Windows 7 detection issue. Cc: Rafael Brune Signed-off-by: Xuebing Wang Signed-off-by: Fabio Estevam Signed-off-by: Peter Chen (Fixed some code style issues) Tested-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 380b137..d8b4af6 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -31,7 +31,7 @@ static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); */ #define F_AUDIO_AC_INTERFACE 0 #define F_AUDIO_AS_INTERFACE 1 -#define F_AUDIO_NUM_INTERFACES 2 +#define F_AUDIO_NUM_INTERFACES 1 /* B.3.1 Standard AC Interface Descriptor */ static struct usb_interface_descriptor ac_interface_desc = { @@ -42,14 +42,18 @@ static struct usb_interface_descriptor ac_interface_desc = { .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, }; -DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); +/* + * The number of AudioStreaming and MIDIStreaming interfaces + * in the Audio Interface Collection + */ +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); #define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) /* 1 input terminal, 1 output terminal and 1 feature unit */ #define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \ + UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0)) /* B.3.2 Class-Specific AC Interface Descriptor */ -static struct uac1_ac_header_descriptor_2 ac_header_desc = { +static struct uac1_ac_header_descriptor_1 ac_header_desc = { .bLength = UAC_DT_AC_HEADER_LENGTH, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_HEADER, @@ -57,8 +61,8 @@ static struct uac1_ac_header_descriptor_2 ac_header_desc = { .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), .bInCollection = F_AUDIO_NUM_INTERFACES, .baInterfaceNr = { - [0] = F_AUDIO_AC_INTERFACE, - [1] = F_AUDIO_AS_INTERFACE, + /* Interface number of the first AudioStream interface */ + [0] = 1, } }; -- cgit v0.10.2 From 9f56ce075dd83d6b69e26d4d4fcf49ec703119cf Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Tue, 23 Dec 2014 19:50:14 +0100 Subject: usb: gadget: cleanup on stack DECLARE_COMPLETIONs fixups for incorrect use of DECLARE_COMPLETION. see also commit 6e9a4738 ("completions: lockdep annotate on stack completions") patch is against 3.18.0 linux-next This was only code reviewed and compile tested Signed-off-by: Nicholas Mc Guire Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c index 795c99c..e0822f1 100644 --- a/drivers/usb/gadget/udc/fsl_qe_udc.c +++ b/drivers/usb/gadget/udc/fsl_qe_udc.c @@ -2630,7 +2630,7 @@ static int qe_udc_remove(struct platform_device *ofdev) struct qe_udc *udc = platform_get_drvdata(ofdev); struct qe_ep *ep; unsigned int size; - DECLARE_COMPLETION(done); + DECLARE_COMPLETION_ONSTACK(done); usb_del_gadget_udc(&udc->gadget); diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 2df8074..c3830ad 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2529,7 +2529,7 @@ static int __exit fsl_udc_remove(struct platform_device *pdev) struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); - DECLARE_COMPLETION(done); + DECLARE_COMPLETION_ONSTACK(done); if (!udc_controller) return -ENODEV; -- cgit v0.10.2 From 48ac1e57665fa351b2bde534fd64cce2e1b3a4a1 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Wed, 5 Nov 2014 01:48:48 +0300 Subject: usb: renesas_usbhs: fix platform init error message There is a typo ("prove" instead of "probe") in the error message printed when the platform initialization fails. Replace that word with more fitting "init". Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index a706fc4..76d14f5 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -615,7 +615,7 @@ static int usbhs_probe(struct platform_device *pdev) */ ret = usbhs_platform_call(priv, hardware_init, pdev); if (ret < 0) { - dev_err(&pdev->dev, "platform prove failed.\n"); + dev_err(&pdev->dev, "platform init failed.\n"); goto probe_end_mod_exit; } -- cgit v0.10.2 From 906641980c3ae4e4175268168897ebdc7314c73c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:46 +0100 Subject: usb: gadget: udc: net2280: Remove obsolete module param use_dma_chaining use_dma_chaining module parameter was designed to avoid creating one irq per package on a group of packages (with the help of the driver's flag no_interrupt). Unfortunately, when this parameter is enabled, the driver fails to work on both net2280 and 3380 chips. This patch removes this parameter, which was disabled by default. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index d6411e0..08c16ee 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -12,11 +12,7 @@ * the Mass Storage, Serial, and Ethernet/RNDIS gadget drivers * as well as Gadget Zero and Gadgetfs. * - * DMA is enabled by default. Drivers using transfer queues might use - * DMA chaining to remove IRQ latencies between transfers. (Except when - * short OUT transfers happen.) Drivers can use the req->no_interrupt - * hint to completely eliminate some IRQs, if a later IRQ is guaranteed - * and DMA chaining is enabled. + * DMA is enabled by default. * * MSI is enabled by default. The legacy IRQ is used if MSI couldn't * be enabled. @@ -85,7 +81,6 @@ static const char *const ep_name[] = { }; /* use_dma -- general goodness, fewer interrupts, less cpu load (vs PIO) - * use_dma_chaining -- dma descriptor queueing gives even more irq reduction * * The net2280 DMA engines are not tightly integrated with their FIFOs; * not all cases are (yet) handled well in this driver or the silicon. @@ -93,12 +88,10 @@ static const char *const ep_name[] = { * These two parameters let you use PIO or more aggressive DMA. */ static bool use_dma = true; -static bool use_dma_chaining; static bool use_msi = true; /* "modprobe net2280 use_dma=n" etc */ module_param(use_dma, bool, 0444); -module_param(use_dma_chaining, bool, 0444); module_param(use_msi, bool, 0444); /* mode 0 == ep-{a,b,c,d} 1K fifo each @@ -202,15 +195,6 @@ net2280_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) /* set speed-dependent max packet; may kick in high bandwidth */ set_max_speed(ep, max); - /* FIFO lines can't go to different packets. PIO is ok, so - * use it instead of troublesome (non-bulk) multi-packet DMA. - */ - if (ep->dma && (max % 4) != 0 && use_dma_chaining) { - ep_dbg(ep->dev, "%s, no dma for maxpacket %d\n", - ep->ep.name, ep->ep.maxpacket); - ep->dma = NULL; - } - /* set type, direction, address; reset fifo counters */ writel(BIT(FIFO_FLUSH), &ep->regs->ep_stat); tmp = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); @@ -747,8 +731,7 @@ static void fill_dma_desc(struct net2280_ep *ep, req->valid = valid; if (valid) dmacount |= BIT(VALID_BIT); - if (likely(!req->req.no_interrupt || !use_dma_chaining)) - dmacount |= BIT(DMA_DONE_INTERRUPT_ENABLE); + dmacount |= BIT(DMA_DONE_INTERRUPT_ENABLE); /* td->dmadesc = previously set by caller */ td->dmaaddr = cpu_to_le32 (req->req.dma); @@ -862,8 +845,7 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) req->td->dmadesc = cpu_to_le32 (ep->td_dma); fill_dma_desc(ep, req, 1); - if (!use_dma_chaining) - req->td->dmacount |= cpu_to_le32(BIT(END_OF_CHAIN)); + req->td->dmacount |= cpu_to_le32(BIT(END_OF_CHAIN)); start_queue(ep, tmp, req->td_dma); } @@ -1150,64 +1132,12 @@ static void scan_dma_completions(struct net2280_ep *ep) static void restart_dma(struct net2280_ep *ep) { struct net2280_request *req; - u32 dmactl = dmactl_default; if (ep->stopped) return; req = list_entry(ep->queue.next, struct net2280_request, queue); - if (!use_dma_chaining) { - start_dma(ep, req); - return; - } - - /* the 2280 will be processing the queue unless queue hiccups after - * the previous transfer: - * IN: wanted automagic zlp, head doesn't (or vice versa) - * DMA_FIFO_VALIDATE doesn't init from dma descriptors. - * OUT: was "usb-short", we must restart. - */ - if (ep->is_in && !req->valid) { - struct net2280_request *entry, *prev = NULL; - int reqmode, done = 0; - - ep_dbg(ep->dev, "%s dma hiccup td %p\n", ep->ep.name, req->td); - ep->in_fifo_validate = likely(req->req.zero || - (req->req.length % ep->ep.maxpacket) != 0); - if (ep->in_fifo_validate) - dmactl |= BIT(DMA_FIFO_VALIDATE); - list_for_each_entry(entry, &ep->queue, queue) { - __le32 dmacount; - - if (entry == req) - continue; - dmacount = entry->td->dmacount; - if (!done) { - reqmode = likely(entry->req.zero || - (entry->req.length % ep->ep.maxpacket)); - if (reqmode == ep->in_fifo_validate) { - entry->valid = 1; - dmacount |= valid_bit; - entry->td->dmacount = dmacount; - prev = entry; - continue; - } else { - /* force a hiccup */ - prev->td->dmacount |= dma_done_ie; - done = 1; - } - } - - /* walk the rest of the queue so unlinks behave */ - entry->valid = 0; - dmacount &= ~valid_bit; - entry->td->dmacount = dmacount; - prev = entry; - } - } - - writel(0, &ep->dma->dmactl); - start_queue(ep, dmactl, req->td_dma); + start_dma(ep, req); } static void abort_dma_228x(struct net2280_ep *ep) @@ -1306,25 +1236,6 @@ static int net2280_dequeue(struct usb_ep *_ep, struct usb_request *_req) done(ep, req, -ECONNRESET); } req = NULL; - - /* patch up hardware chaining data */ - } else if (ep->dma && use_dma_chaining) { - if (req->queue.prev == ep->queue.next) { - writel(le32_to_cpu(req->td->dmadesc), - &ep->dma->dmadesc); - if (req->td->dmacount & dma_done_ie) - writel(readl(&ep->dma->dmacount) | - le32_to_cpu(dma_done_ie), - &ep->dma->dmacount); - } else { - struct net2280_request *prev; - - prev = list_entry(req->queue.prev, - struct net2280_request, queue); - prev->td->dmadesc = req->td->dmadesc; - if (req->td->dmacount & dma_done_ie) - prev->td->dmacount |= dma_done_ie; - } } if (req) @@ -1609,9 +1520,7 @@ static ssize_t registers_show(struct device *_dev, "pci irqenb0 %02x irqenb1 %08x " "irqstat0 %04x irqstat1 %08x\n", driver_name, dev->chiprev, - use_dma - ? (use_dma_chaining ? "chaining" : "enabled") - : "disabled", + use_dma ? "enabled" : "disabled", readl(&dev->regs->devinit), readl(&dev->regs->fifoctl), s, @@ -3423,17 +3332,12 @@ static void handle_stat1_irqs(struct net2280 *dev, u32 stat) continue; } - /* chaining should stop on abort, short OUT from fifo, - * or (stat0 codepath) short OUT transfer. - */ - if (!use_dma_chaining) { - if (!(tmp & BIT(DMA_TRANSACTION_DONE_INTERRUPT))) { - ep_dbg(ep->dev, "%s no xact done? %08x\n", - ep->ep.name, tmp); - continue; - } - stop_dma(ep->dma); + if (!(tmp & BIT(DMA_TRANSACTION_DONE_INTERRUPT))) { + ep_dbg(ep->dev, "%s no xact done? %08x\n", + ep->ep.name, tmp); + continue; } + stop_dma(ep->dma); /* OUT transfers terminate when the data from the * host is in our memory. Process whatever's done. @@ -3448,30 +3352,9 @@ static void handle_stat1_irqs(struct net2280 *dev, u32 stat) scan_dma_completions(ep); /* disable dma on inactive queues; else maybe restart */ - if (list_empty(&ep->queue)) { - if (use_dma_chaining) - stop_dma(ep->dma); - } else { + if (!list_empty(&ep->queue)) { tmp = readl(&dma->dmactl); - if (!use_dma_chaining || (tmp & BIT(DMA_ENABLE)) == 0) - restart_dma(ep); - else if (ep->is_in && use_dma_chaining) { - struct net2280_request *req; - __le32 dmacount; - - /* the descriptor at the head of the chain - * may still have VALID_BIT clear; that's - * used to trigger changing DMA_FIFO_VALIDATE - * (affects automagic zlp writes). - */ - req = list_entry(ep->queue.next, - struct net2280_request, queue); - dmacount = req->td->dmacount; - dmacount &= cpu_to_le32(BIT(VALID_BIT) | - DMA_BYTE_COUNT_MASK); - if (dmacount && (dmacount & valid_bit) == 0) - restart_dma(ep); - } + restart_dma(ep); } ep->irqs++; } @@ -3581,9 +3464,6 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) void __iomem *base = NULL; int retval, i; - if (!use_dma) - use_dma_chaining = 0; - /* alloc, and start init */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (dev == NULL) { @@ -3742,8 +3622,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) ep_info(dev, "irq %d, pci mem %p, chip rev %04x\n", pdev->irq, base, dev->chiprev); ep_info(dev, "version: " DRIVER_VERSION "; dma %s %s\n", - use_dma ? (use_dma_chaining ? "chaining" : "enabled") - : "disabled", + use_dma ? "enabled" : "disabled", dev->enhanced_mode ? "enhanced mode" : "legacy mode"); retval = device_create_file(&pdev->dev, &dev_attr_registers); if (retval) -- cgit v0.10.2 From 7a74c48172d8fad6da0f9be5d353cf322305af07 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:47 +0100 Subject: usb: gadget: udc: net2280: remove full_speed module parameter This patch removes the full_speed parameter used force full-speed operation. It was designed exclusively for testing purposes, and there is no reason to maintain this in a production kernel. Reverts: 2f0760774711c957c395b31131b848043af98edf Suggested-by: Alan Stern Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 08c16ee..27b94c6 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -113,11 +113,6 @@ static bool enable_suspend; /* "modprobe net2280 enable_suspend=1" etc */ module_param(enable_suspend, bool, 0444); -/* force full-speed operation */ -static bool full_speed; -module_param(full_speed, bool, 0444); -MODULE_PARM_DESC(full_speed, "force full-speed mode -- for testing only!"); - #define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out") static char *type_string(u8 bmAttributes) @@ -2291,11 +2286,7 @@ static int net2280_start(struct usb_gadget *_gadget, if (retval) goto err_func; - /* Enable force-full-speed testing mode, if desired */ - if (full_speed && (dev->quirks & PLX_LEGACY)) - writel(BIT(FORCE_FULL_SPEED_MODE), &dev->usb->xcvrdiag); - - /* ... then enable host detection and ep0; and we're ready + /* enable host detection and ep0; and we're ready * for set_configuration as well as eventual disconnect. */ net2280_led_active(dev, 1); @@ -2353,10 +2344,6 @@ static int net2280_stop(struct usb_gadget *_gadget) net2280_led_active(dev, 0); - /* Disable full-speed test mode */ - if (dev->quirks & PLX_LEGACY) - writel(0, &dev->usb->xcvrdiag); - device_remove_file(&dev->pdev->dev, &dev_attr_function); device_remove_file(&dev->pdev->dev, &dev_attr_queues); @@ -3655,9 +3642,6 @@ static void net2280_shutdown(struct pci_dev *pdev) /* disable the pullup so the host will think we're gone */ writel(0, &dev->usb->usbctl); - /* Disable full-speed test mode */ - if (dev->quirks & PLX_LEGACY) - writel(0, &dev->usb->xcvrdiag); } -- cgit v0.10.2 From 9c864c23424fa6a2e23b8e81faa6a100ab01481c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:48 +0100 Subject: usb: gadget: udc: net2280: Remove module parameter use_msi Parameter use_msi was used to enable msi irq on usb338x chips, it was enabled by default. There is no reason to prefer non-msi irq on usb338x, and it falls back to non msi on error. Therefore remove this option. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 27b94c6..fc5c0a2 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -88,11 +88,9 @@ static const char *const ep_name[] = { * These two parameters let you use PIO or more aggressive DMA. */ static bool use_dma = true; -static bool use_msi = true; /* "modprobe net2280 use_dma=n" etc */ module_param(use_dma, bool, 0444); -module_param(use_msi, bool, 0444); /* mode 0 == ep-{a,b,c,d} 1K fifo each * mode 1 == ep-{a,b} 2K fifo each, ep-{c,d} unavailable @@ -3426,7 +3424,7 @@ static void net2280_remove(struct pci_dev *pdev) } if (dev->got_irq) free_irq(pdev->irq, dev); - if (use_msi && dev->quirks & PLX_SUPERSPEED) + if (dev->quirks & PLX_SUPERSPEED) pci_disable_msi(pdev); if (dev->regs) iounmap(dev->regs); @@ -3549,7 +3547,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto done; } - if (use_msi && (dev->quirks & PLX_SUPERSPEED)) + if (dev->quirks & PLX_SUPERSPEED) if (pci_enable_msi(pdev)) ep_err(dev, "Failed to enable MSI mode\n"); -- cgit v0.10.2 From d588ff58c20d8503f789b9cbbed55e796ae6d04e Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:49 +0100 Subject: usb: gadget: udc: net2280: Remove use_dma module parameter use_dma parameter was designed to enable the dma on the chip. It was enabled by default. It comes from the time when the dma was not reliable. Now it has been working ok in production. This patch removes this parameter. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index fc5c0a2..230b8d4 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -80,18 +80,6 @@ static const char *const ep_name[] = { "ep-e", "ep-f", "ep-g", "ep-h", }; -/* use_dma -- general goodness, fewer interrupts, less cpu load (vs PIO) - * - * The net2280 DMA engines are not tightly integrated with their FIFOs; - * not all cases are (yet) handled well in this driver or the silicon. - * Some gadget drivers work better with the dma support here than others. - * These two parameters let you use PIO or more aggressive DMA. - */ -static bool use_dma = true; - -/* "modprobe net2280 use_dma=n" etc */ -module_param(use_dma, bool, 0444); - /* mode 0 == ep-{a,b,c,d} 1K fifo each * mode 1 == ep-{a,b} 2K fifo each, ep-{c,d} unavailable * mode 2 == ep-a 2K fifo, ep-{b,c} 1K each, ep-d unavailable @@ -455,7 +443,7 @@ static int net2280_disable(struct usb_ep *_ep) /* synch memory views with the device */ (void)readl(&ep->cfg->ep_cfg); - if (use_dma && !ep->dma && ep->num >= 1 && ep->num <= 4) + if (!ep->dma && ep->num >= 1 && ep->num <= 4) ep->dma = &ep->dev->dma[ep->num - 1]; spin_unlock_irqrestore(&ep->dev->lock, flags); @@ -1508,12 +1496,11 @@ static ssize_t registers_show(struct device *_dev, /* Main Control Registers */ t = scnprintf(next, size, "%s version " DRIVER_VERSION - ", chiprev %04x, dma %s\n\n" + ", chiprev %04x\n\n" "devinit %03x fifoctl %08x gadget '%s'\n" "pci irqenb0 %02x irqenb1 %08x " "irqstat0 %04x irqstat1 %08x\n", driver_name, dev->chiprev, - use_dma ? "enabled" : "disabled", readl(&dev->regs->devinit), readl(&dev->regs->fifoctl), s, @@ -1995,10 +1982,6 @@ static void usb_reset(struct net2280 *dev) static void usb_reinit_228x(struct net2280 *dev) { u32 tmp; - int init_dma; - - /* use_dma changes are ignored till next device re-init */ - init_dma = use_dma; /* basic endpoint init */ for (tmp = 0; tmp < 7; tmp++) { @@ -2010,8 +1993,7 @@ static void usb_reinit_228x(struct net2280 *dev) if (tmp > 0 && tmp <= 4) { ep->fifo_size = 1024; - if (init_dma) - ep->dma = &dev->dma[tmp - 1]; + ep->dma = &dev->dma[tmp - 1]; } else ep->fifo_size = 64; ep->regs = &dev->epregs[tmp]; @@ -2035,7 +2017,6 @@ static void usb_reinit_228x(struct net2280 *dev) static void usb_reinit_338x(struct net2280 *dev) { - int init_dma; int i; u32 tmp, val; u32 fsmvalue; @@ -2043,9 +2024,6 @@ static void usb_reinit_338x(struct net2280 *dev) static const u32 ep_reg_addr[9] = { 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0xC0 }; - /* use_dma changes are ignored till next device re-init */ - init_dma = use_dma; - /* basic endpoint init */ for (i = 0; i < dev->n_ep; i++) { struct net2280_ep *ep = &dev->ep[i]; @@ -2054,7 +2032,7 @@ static void usb_reinit_338x(struct net2280 *dev) ep->dev = dev; ep->num = i; - if (i > 0 && i <= 4 && init_dma) + if (i > 0 && i <= 4) ep->dma = &dev->dma[i - 1]; if (dev->enhanced_mode) { @@ -3606,8 +3584,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) ep_info(dev, "%s\n", driver_desc); ep_info(dev, "irq %d, pci mem %p, chip rev %04x\n", pdev->irq, base, dev->chiprev); - ep_info(dev, "version: " DRIVER_VERSION "; dma %s %s\n", - use_dma ? "enabled" : "disabled", + ep_info(dev, "version: " DRIVER_VERSION "; %s\n", dev->enhanced_mode ? "enhanced mode" : "legacy mode"); retval = device_create_file(&pdev->dev, &dev_attr_registers); if (retval) -- cgit v0.10.2 From cb52c698c6030c88733c87def5c359a09b76bc1e Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:50 +0100 Subject: usb: gadget: udc: net2280: Remove dma_started field Remove dma_started field from net2280_ep structure, since it is not used by any function. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 230b8d4..0340462 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -834,16 +834,12 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) static inline void resume_dma(struct net2280_ep *ep) { writel(readl(&ep->dma->dmactl) | BIT(DMA_ENABLE), &ep->dma->dmactl); - - ep->dma_started = true; } static inline void ep_stop_dma(struct net2280_ep *ep) { writel(readl(&ep->dma->dmactl) & ~BIT(DMA_ENABLE), &ep->dma->dmactl); spin_stop_dma(ep->dma); - - ep->dma_started = false; } static inline void diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index 03f1524..4c53b83 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -100,7 +100,7 @@ struct net2280_ep { dma_addr_t td_dma; /* of dummy */ struct net2280 *dev; unsigned long irqs; - unsigned is_halt:1, dma_started:1; + unsigned is_halt:1; /* analogous to a host-side qh */ struct list_head queue; -- cgit v0.10.2 From 61e72dc65a9a4937d351a4b67386a6bbf70a1146 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:51 +0100 Subject: usb: gadget: udc: net2280: Remove restart_dma inline function definition restart_dma is not used before it is declaration. Therefore we can remove this definition. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 0340462..e8c36db 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1043,8 +1043,6 @@ dma_done(struct net2280_ep *ep, struct net2280_request *req, u32 dmacount, done(ep, req, status); } -static void restart_dma(struct net2280_ep *ep); - static void scan_dma_completions(struct net2280_ep *ep) { /* only look at descriptors that were "naturally" retired, -- cgit v0.10.2 From cf8b1cdebab3c6fb01d37e62e14f9dd8cc99d09b Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:52 +0100 Subject: usb: gadget: udc: net2280: Code cleanout remove ep_stdrsp function ep_stdrsp was only called by handle_stat0_irqs_superspeed and with always the same flags. Remove the function and replace the call by the code inside the function, since it is very simple once the dead code is removed. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index e8c36db..653a8a2 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2668,46 +2668,6 @@ static void ep_stall(struct net2280_ep *ep, int stall) } } -static void ep_stdrsp(struct net2280_ep *ep, int value, int wedged) -{ - /* set/clear, then synch memory views with the device */ - if (value) { - ep->stopped = 1; - if (ep->num == 0) - ep->dev->protocol_stall = 1; - else { - if (ep->dma) - ep_stop_dma(ep); - ep_stall(ep, true); - } - - if (wedged) - ep->wedged = 1; - } else { - ep->stopped = 0; - ep->wedged = 0; - - ep_stall(ep, false); - - /* Flush the queue */ - if (!list_empty(&ep->queue)) { - struct net2280_request *req = - list_entry(ep->queue.next, struct net2280_request, - queue); - if (ep->dma) - resume_dma(ep); - else { - if (ep->is_in) - write_fifo(ep, &req->req); - else { - if (read_fifo(ep, req)) - done(ep, req, 0); - } - } - } - } -} - static void handle_stat0_irqs_superspeed(struct net2280 *dev, struct net2280_ep *ep, struct usb_ctrlrequest r) { @@ -2864,7 +2824,14 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, e = get_ep_by_addr(dev, w_index); if (!e || (w_value != USB_ENDPOINT_HALT)) goto do_stall3; - ep_stdrsp(e, true, false); + ep->stopped = 1; + if (ep->num == 0) + ep->dev->protocol_stall = 1; + else { + if (ep->dma) + ep_stop_dma(ep); + ep_stall(ep, true); + } allow_status_338x(ep); break; -- cgit v0.10.2 From 5d1b6840fd871b51765e7edee82876f760c46adf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:53 +0100 Subject: usb: gadget: udc: net2280: Remove field is_halt Field is_halt is never used by any function. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 653a8a2..48e8d82 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2643,7 +2643,6 @@ static void ep_stall(struct net2280_ep *ep, int stall) /* BIT(SET_NAK_PACKETS) | */ BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE), &ep->regs->ep_rsp); - ep->is_halt = 1; } else { if (dev->gadget.speed == USB_SPEED_SUPER) { /* @@ -2663,7 +2662,6 @@ static void ep_stall(struct net2280_ep *ep, int stall) writel(val, /* | BIT(CLEAR_NAK_PACKETS),*/ &ep->regs->ep_rsp); - ep->is_halt = 0; val = readl(&ep->regs->ep_rsp); } } @@ -2924,9 +2922,7 @@ static void handle_stat0_irqs(struct net2280 *dev, u32 stat) } ep->stopped = 0; dev->protocol_stall = 0; - if (dev->quirks & PLX_SUPERSPEED) - ep->is_halt = 0; - else{ + if (!(dev->quirks & PLX_SUPERSPEED)) { if (ep->dev->quirks & PLX_2280) tmp = BIT(FIFO_OVERFLOW) | BIT(FIFO_UNDERFLOW); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index 4c53b83..a16494a 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -100,7 +100,6 @@ struct net2280_ep { dma_addr_t td_dma; /* of dummy */ struct net2280 *dev; unsigned long irqs; - unsigned is_halt:1; /* analogous to a host-side qh */ struct list_head queue; -- cgit v0.10.2 From e0cbb04627556a953c6fe8d5ba42b9dda68146af Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:54 +0100 Subject: usb: gadget: udc: net2280: Remove function ep_stall irqs_superspeed calls ep_stall instead of set/clear_halt, due to a workaround for SS seqnum. Create a function with the workaround and call set/clear_halt instead. This way we can compare the code of super/normal speed and it is easier to follow the code. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 48e8d82..5ba5132 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2632,38 +2632,19 @@ restore_data_eps: return; } -static void ep_stall(struct net2280_ep *ep, int stall) +static void ep_clear_seqnum(struct net2280_ep *ep) { struct net2280 *dev = ep->dev; u32 val; static const u32 ep_pl[9] = { 0, 3, 4, 7, 8, 2, 5, 6, 9 }; - if (stall) { - writel(BIT(SET_ENDPOINT_HALT) | - /* BIT(SET_NAK_PACKETS) | */ - BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE), - &ep->regs->ep_rsp); - } else { - if (dev->gadget.speed == USB_SPEED_SUPER) { - /* - * Workaround for SS SeqNum not cleared via - * Endpoint Halt (Clear) bit. select endpoint - */ - val = readl(&dev->plregs->pl_ep_ctrl); - val = (val & ~0x1f) | ep_pl[ep->num]; - writel(val, &dev->plregs->pl_ep_ctrl); + val = readl(&dev->plregs->pl_ep_ctrl) & ~0x1f; + val |= ep_pl[ep->num]; + writel(val, &dev->plregs->pl_ep_ctrl); + val |= BIT(SEQUENCE_NUMBER_RESET); + writel(val, &dev->plregs->pl_ep_ctrl); - val |= BIT(SEQUENCE_NUMBER_RESET); - writel(val, &dev->plregs->pl_ep_ctrl); - } - val = readl(&ep->regs->ep_rsp); - val |= BIT(CLEAR_ENDPOINT_HALT) | - BIT(CLEAR_ENDPOINT_TOGGLE); - writel(val, - /* | BIT(CLEAR_NAK_PACKETS),*/ - &ep->regs->ep_rsp); - val = readl(&ep->regs->ep_rsp); - } + return; } static void handle_stat0_irqs_superspeed(struct net2280 *dev, @@ -2764,7 +2745,12 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, if (w_value != USB_ENDPOINT_HALT) goto do_stall3; ep_vdbg(dev, "%s clear halt\n", e->ep.name); - ep_stall(e, false); + /* + * Workaround for SS SeqNum not cleared via + * Endpoint Halt (Clear) bit. select endpoint + */ + ep_clear_seqnum(e); + clear_halt(e); if (!list_empty(&e->queue) && e->td_dma) restart_dma(e); allow_status(ep); @@ -2828,7 +2814,7 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, else { if (ep->dma) ep_stop_dma(ep); - ep_stall(ep, true); + set_halt(ep); } allow_status_338x(ep); break; @@ -2857,7 +2843,7 @@ do_stall3: r.bRequestType, r.bRequest, tmp); dev->protocol_stall = 1; /* TD 9.9 Halt Endpoint test. TD 9.22 Set feature test */ - ep_stall(ep, true); + set_halt(ep); } next_endpoints3: -- cgit v0.10.2 From e721c4575db77a543d0a28c56e95b84ee37a551c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:55 +0100 Subject: usb: gadget: udc: net2280: Merge abort_dma for 228x and 338x We can use the same function for both families of chips and also remove the ep_stop_dma() function. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 5ba5132..46b66b3 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -836,12 +836,6 @@ static inline void resume_dma(struct net2280_ep *ep) writel(readl(&ep->dma->dmactl) | BIT(DMA_ENABLE), &ep->dma->dmactl); } -static inline void ep_stop_dma(struct net2280_ep *ep) -{ - writel(readl(&ep->dma->dmactl) & ~BIT(DMA_ENABLE), &ep->dma->dmactl); - spin_stop_dma(ep->dma); -} - static inline void queue_dma(struct net2280_ep *ep, struct net2280_request *req, int valid) { @@ -1115,7 +1109,7 @@ static void restart_dma(struct net2280_ep *ep) start_dma(ep, req); } -static void abort_dma_228x(struct net2280_ep *ep) +static void abort_dma(struct net2280_ep *ep) { /* abort the current transfer */ if (likely(!list_empty(&ep->queue))) { @@ -1127,19 +1121,6 @@ static void abort_dma_228x(struct net2280_ep *ep) scan_dma_completions(ep); } -static void abort_dma_338x(struct net2280_ep *ep) -{ - writel(BIT(DMA_ABORT), &ep->dma->dmastat); - spin_stop_dma(ep->dma); -} - -static void abort_dma(struct net2280_ep *ep) -{ - if (ep->dev->quirks & PLX_LEGACY) - return abort_dma_228x(ep); - return abort_dma_338x(ep); -} - /* dequeue ALL requests */ static void nuke(struct net2280_ep *ep) { @@ -2813,7 +2794,7 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, ep->dev->protocol_stall = 1; else { if (ep->dma) - ep_stop_dma(ep); + abort_dma(ep); set_halt(ep); } allow_status_338x(ep); -- cgit v0.10.2 From 485f44d06b352a788d07e79a5744cc7420adc1e4 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:56 +0100 Subject: usb: gadget: udc: net2280: Clean function net2280_queue Do not duplicate the code for the else branch of the condition. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 46b66b3..ade4693 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -937,24 +937,12 @@ net2280_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) _req->actual = 0; /* kickstart this i/o queue? */ - if (list_empty(&ep->queue) && !ep->stopped) { - /* DMA request while EP halted */ - if (ep->dma && - (readl(&ep->regs->ep_rsp) & BIT(CLEAR_ENDPOINT_HALT)) && - (dev->quirks & PLX_SUPERSPEED)) { - int valid = 1; - if (ep->is_in) { - int expect; - expect = likely(req->req.zero || - ((req->req.length % - ep->ep.maxpacket) != 0)); - if (expect != ep->in_fifo_validate) - valid = 0; - } - queue_dma(ep, req, valid); - } + if (list_empty(&ep->queue) && !ep->stopped && + !((dev->quirks & PLX_SUPERSPEED) && ep->dma && + (readl(&ep->regs->ep_rsp) & BIT(CLEAR_ENDPOINT_HALT)))) { + /* use DMA if the endpoint supports it, else pio */ - else if (ep->dma) + if (ep->dma) start_dma(ep, req); else { /* maybe there's no control data, just status ack */ -- cgit v0.10.2 From 5517525e05b5d21c26e2b729a7e977f7d69714af Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:57 +0100 Subject: usb: gadget: udc: net2280: Improve patching of defect 7374 Once the defect 7374 is patched, there is no reason the keep reading the idx scratch register. Cache the content of the scratch idx register on device flag. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index ade4693..d4255f9 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1765,76 +1765,73 @@ static void defect7374_disable_data_eps(struct net2280 *dev) static void defect7374_enable_data_eps_zero(struct net2280 *dev) { u32 tmp = 0, tmp_reg; - u32 fsmvalue, scratch; + u32 scratch; int i; unsigned char ep_sel; scratch = get_idx_reg(dev->regs, SCRATCH); - fsmvalue = scratch & (0xf << DEFECT7374_FSM_FIELD); + + WARN_ON((scratch & (0xf << DEFECT7374_FSM_FIELD)) + == DEFECT7374_FSM_SS_CONTROL_READ); + scratch &= ~(0xf << DEFECT7374_FSM_FIELD); - /*See if firmware needs to set up for workaround*/ - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) { - ep_warn(dev, "Operate Defect 7374 workaround soft this time"); - ep_warn(dev, "It will operate on cold-reboot and SS connect"); - - /*GPEPs:*/ - tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_DIRECTION) | - (2 << OUT_ENDPOINT_TYPE) | (2 << IN_ENDPOINT_TYPE) | - ((dev->enhanced_mode) ? - BIT(OUT_ENDPOINT_ENABLE) : BIT(ENDPOINT_ENABLE)) | - BIT(IN_ENDPOINT_ENABLE)); - - for (i = 1; i < 5; i++) - writel(tmp, &dev->ep[i].cfg->ep_cfg); - - /* CSRIN, PCIIN, STATIN, RCIN*/ - tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_ENABLE)); - writel(tmp, &dev->dep[1].dep_cfg); - writel(tmp, &dev->dep[3].dep_cfg); - writel(tmp, &dev->dep[4].dep_cfg); - writel(tmp, &dev->dep[5].dep_cfg); - - /*Implemented for development and debug. - * Can be refined/tuned later.*/ - for (ep_sel = 0; ep_sel <= 21; ep_sel++) { - /* Select an endpoint for subsequent operations: */ - tmp_reg = readl(&dev->plregs->pl_ep_ctrl); - writel(((tmp_reg & ~0x1f) | ep_sel), - &dev->plregs->pl_ep_ctrl); - - if (ep_sel == 1) { - tmp = - (readl(&dev->plregs->pl_ep_ctrl) | - BIT(CLEAR_ACK_ERROR_CODE) | 0); - writel(tmp, &dev->plregs->pl_ep_ctrl); - continue; - } + ep_warn(dev, "Operate Defect 7374 workaround soft this time"); + ep_warn(dev, "It will operate on cold-reboot and SS connect"); - if (ep_sel == 0 || (ep_sel > 9 && ep_sel < 14) || - ep_sel == 18 || ep_sel == 20) - continue; + /*GPEPs:*/ + tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_DIRECTION) | + (2 << OUT_ENDPOINT_TYPE) | (2 << IN_ENDPOINT_TYPE) | + ((dev->enhanced_mode) ? + BIT(OUT_ENDPOINT_ENABLE) : BIT(ENDPOINT_ENABLE)) | + BIT(IN_ENDPOINT_ENABLE)); - tmp = (readl(&dev->plregs->pl_ep_cfg_4) | - BIT(NON_CTRL_IN_TOLERATE_BAD_DIR) | 0); - writel(tmp, &dev->plregs->pl_ep_cfg_4); + for (i = 1; i < 5; i++) + writel(tmp, &dev->ep[i].cfg->ep_cfg); - tmp = readl(&dev->plregs->pl_ep_ctrl) & - ~BIT(EP_INITIALIZED); - writel(tmp, &dev->plregs->pl_ep_ctrl); + /* CSRIN, PCIIN, STATIN, RCIN*/ + tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_ENABLE)); + writel(tmp, &dev->dep[1].dep_cfg); + writel(tmp, &dev->dep[3].dep_cfg); + writel(tmp, &dev->dep[4].dep_cfg); + writel(tmp, &dev->dep[5].dep_cfg); + /*Implemented for development and debug. + * Can be refined/tuned later.*/ + for (ep_sel = 0; ep_sel <= 21; ep_sel++) { + /* Select an endpoint for subsequent operations: */ + tmp_reg = readl(&dev->plregs->pl_ep_ctrl); + writel(((tmp_reg & ~0x1f) | ep_sel), + &dev->plregs->pl_ep_ctrl); + + if (ep_sel == 1) { + tmp = + (readl(&dev->plregs->pl_ep_ctrl) | + BIT(CLEAR_ACK_ERROR_CODE) | 0); + writel(tmp, &dev->plregs->pl_ep_ctrl); + continue; } - /* Set FSM to focus on the first Control Read: - * - Tip: Connection speed is known upon the first - * setup request.*/ - scratch |= DEFECT7374_FSM_WAITING_FOR_CONTROL_READ; - set_idx_reg(dev->regs, SCRATCH, scratch); + if (ep_sel == 0 || (ep_sel > 9 && ep_sel < 14) || + ep_sel == 18 || ep_sel == 20) + continue; + + tmp = (readl(&dev->plregs->pl_ep_cfg_4) | + BIT(NON_CTRL_IN_TOLERATE_BAD_DIR) | 0); + writel(tmp, &dev->plregs->pl_ep_cfg_4); + + tmp = readl(&dev->plregs->pl_ep_ctrl) & + ~BIT(EP_INITIALIZED); + writel(tmp, &dev->plregs->pl_ep_ctrl); - } else{ - ep_warn(dev, "Defect 7374 workaround soft will NOT operate"); - ep_warn(dev, "It will operate on cold-reboot and SS connect"); } + + /* Set FSM to focus on the first Control Read: + * - Tip: Connection speed is known upon the first + * setup request.*/ + scratch |= DEFECT7374_FSM_WAITING_FOR_CONTROL_READ; + set_idx_reg(dev->regs, SCRATCH, scratch); + } /* keeping it simple: @@ -1885,21 +1882,13 @@ static void usb_reset_228x(struct net2280 *dev) static void usb_reset_338x(struct net2280 *dev) { u32 tmp; - u32 fsmvalue; dev->gadget.speed = USB_SPEED_UNKNOWN; (void)readl(&dev->usb->usbctl); net2280_led_init(dev); - fsmvalue = get_idx_reg(dev->regs, SCRATCH) & - (0xf << DEFECT7374_FSM_FIELD); - - /* See if firmware needs to set up for workaround: */ - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) { - ep_info(dev, "%s: Defect 7374 FsmValue 0x%08x\n", __func__, - fsmvalue); - } else { + if (dev->bug7734_patched) { /* disable automatic responses, and irqs */ writel(0, &dev->usb->stdrsp); writel(0, &dev->regs->pciirqenb0); @@ -1916,7 +1905,7 @@ static void usb_reset_338x(struct net2280 *dev) writel(~0, &dev->regs->irqstat0), writel(~0, &dev->regs->irqstat1); - if (fsmvalue == DEFECT7374_FSM_SS_CONTROL_READ) { + if (dev->bug7734_patched) { /* reset, and enable pci */ tmp = readl(&dev->regs->devinit) | BIT(PCI_ENABLE) | @@ -1982,7 +1971,6 @@ static void usb_reinit_338x(struct net2280 *dev) { int i; u32 tmp, val; - u32 fsmvalue; static const u32 ne[9] = { 0, 1, 2, 3, 4, 1, 2, 3, 4 }; static const u32 ep_reg_addr[9] = { 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0xC0 }; @@ -2020,14 +2008,7 @@ static void usb_reinit_338x(struct net2280 *dev) dev->ep[0].stopped = 0; /* Link layer set up */ - fsmvalue = get_idx_reg(dev->regs, SCRATCH) & - (0xf << DEFECT7374_FSM_FIELD); - - /* See if driver needs to set up for workaround: */ - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) - ep_info(dev, "%s: Defect 7374 FsmValue %08x\n", - __func__, fsmvalue); - else { + if (dev->bug7734_patched) { tmp = readl(&dev->usb_ext->usbctl2) & ~(BIT(U1_ENABLE) | BIT(U2_ENABLE) | BIT(LTM_ENABLE)); writel(tmp, &dev->usb_ext->usbctl2); @@ -2134,15 +2115,8 @@ static void ep0_start_228x(struct net2280 *dev) static void ep0_start_338x(struct net2280 *dev) { - u32 fsmvalue; - fsmvalue = get_idx_reg(dev->regs, SCRATCH) & - (0xf << DEFECT7374_FSM_FIELD); - - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) - ep_info(dev, "%s: Defect 7374 FsmValue %08x\n", __func__, - fsmvalue); - else + if (dev->bug7734_patched) writel(BIT(CLEAR_NAK_OUT_PACKETS_MODE) | BIT(SET_EP_HIDE_STATUS_PHASE), &dev->epregs[0].ep_rsp); @@ -2230,7 +2204,7 @@ static int net2280_start(struct usb_gadget *_gadget, */ net2280_led_active(dev, 1); - if (dev->quirks & PLX_SUPERSPEED) + if ((dev->quirks & PLX_SUPERSPEED) && !dev->bug7734_patched) defect7374_enable_data_eps_zero(dev); ep0_start(dev); @@ -2552,6 +2526,7 @@ static void defect7374_workaround(struct net2280 *dev, struct usb_ctrlrequest r) * run after the next USB connection. */ scratch |= DEFECT7374_FSM_NON_SS_CONTROL_READ; + dev->bug7734_patched = 1; goto restore_data_eps; } @@ -2565,6 +2540,7 @@ static void defect7374_workaround(struct net2280 *dev, struct usb_ctrlrequest r) if ((state >= (ACK_GOOD_NORMAL << STATE)) && (state <= (ACK_GOOD_MORE_ACKS_TO_COME << STATE))) { scratch |= DEFECT7374_FSM_SS_CONTROL_READ; + dev->bug7734_patched = 1; break; } @@ -2904,7 +2880,7 @@ static void handle_stat0_irqs(struct net2280 *dev, u32 stat) cpu_to_le32s(&u.raw[0]); cpu_to_le32s(&u.raw[1]); - if (dev->quirks & PLX_SUPERSPEED) + if ((dev->quirks & PLX_SUPERSPEED) && !dev->bug7734_patched) defect7374_workaround(dev, u.r); tmp = 0; @@ -3418,9 +3394,12 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) fsmvalue = get_idx_reg(dev->regs, SCRATCH) & (0xf << DEFECT7374_FSM_FIELD); /* See if firmware needs to set up for workaround: */ - if (fsmvalue == DEFECT7374_FSM_SS_CONTROL_READ) + if (fsmvalue == DEFECT7374_FSM_SS_CONTROL_READ) { + dev->bug7734_patched = 1; writel(0, &dev->usb->usbctl); - } else{ + } else + dev->bug7734_patched = 0; + } else { dev->enhanced_mode = 0; dev->n_ep = 7; /* put into initial config, link up all endpoints */ diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index a16494a..c7c7981 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -165,7 +165,8 @@ struct net2280 { ltm_enable:1, wakeup_enable:1, selfpowered:1, - addressed_state:1; + addressed_state:1, + bug7734_patched:1; u16 chiprev; int enhanced_mode; int n_ep; -- cgit v0.10.2 From 5153c219e77985be2fb861814184729bb40b0987 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:58 +0100 Subject: usb: gadget: udc: net2280: Remove function resume_dma Function resume_dma is not used, remove it. The reason the compiler did not catch this dead code is the inline modifier. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index d4255f9..cc76181 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -831,11 +831,6 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) start_queue(ep, tmp, req->td_dma); } -static inline void resume_dma(struct net2280_ep *ep) -{ - writel(readl(&ep->dma->dmactl) | BIT(DMA_ENABLE), &ep->dma->dmactl); -} - static inline void queue_dma(struct net2280_ep *ep, struct net2280_request *req, int valid) { -- cgit v0.10.2 From 6897d4b2bafe189863b2fe448c4afde37844cdbf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:59 +0100 Subject: usb: gadget: udc: net2280: Declare allow_status_338x as inline The function is very simple, does not declare any variable and it is called in the irq path. The counterpart for net228x is already declared as inline. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index c7c7981..b31deb0 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -125,7 +125,7 @@ static inline void allow_status(struct net2280_ep *ep) ep->stopped = 1; } -static void allow_status_338x(struct net2280_ep *ep) +static inline void allow_status_338x(struct net2280_ep *ep) { /* * Control Status Phase Handshake was set by the chip when the setup -- cgit v0.10.2 From 43780aaa1c23c572ef57267d43d520affa7b4723 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:51:00 +0100 Subject: usb: gadget: udc: net2280: Simplify scan_dma_completions After fix superspeed dma_done was applied we can simplify the code by removing the duplicated dma_done and letting the function check if there are more completed dma transactions. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index cc76181..a978e6e 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1051,9 +1051,8 @@ static void scan_dma_completions(struct net2280_ep *ep) dma_done(ep, req, tmp, 0); break; } else if (!ep->is_in && - (req->req.length % ep->ep.maxpacket) != 0) { - if (ep->dev->quirks & PLX_SUPERSPEED) - return dma_done(ep, req, tmp, 0); + (req->req.length % ep->ep.maxpacket) && + !(ep->dev->quirks & PLX_SUPERSPEED)) { tmp = readl(&ep->regs->ep_stat); /* AVOID TROUBLE HERE by not issuing short reads from -- cgit v0.10.2 From d82f3db266f3be7f85de33905dba2a6caccb97f0 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:51:01 +0100 Subject: usb: gadget: udc: net2280: Move ASSERT_OUT_NAKING into out_flush ASSERT_OUT_NAKING was only called by out_flush and was hidden behind a ifdef. This patch moves the inline function into out_flush and remove the ifdef. The user can decide to print the debug message or not via dynamic printk Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index a978e6e..b1c253e 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -575,9 +575,15 @@ static void out_flush(struct net2280_ep *ep) u32 __iomem *statp; u32 tmp; - ASSERT_OUT_NAKING(ep); - statp = &ep->regs->ep_stat; + + tmp = readl(statp); + if (tmp & BIT(NAK_OUT_PACKETS)) { + ep_dbg(ep->dev, "%s %s %08x !NAK\n", + ep->ep.name, __func__, tmp); + writel(BIT(SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + } + writel(BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | BIT(DATA_PACKET_RECEIVED_INTERRUPT), statp); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index b31deb0..a307dce 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -356,23 +356,6 @@ static inline void start_out_naking(struct net2280_ep *ep) readl(&ep->regs->ep_rsp); } -#ifdef DEBUG -static inline void assert_out_naking(struct net2280_ep *ep, const char *where) -{ - u32 tmp = readl(&ep->regs->ep_stat); - - if ((tmp & BIT(NAK_OUT_PACKETS)) == 0) { - ep_dbg(ep->dev, "%s %s %08x !NAK\n", - ep->ep.name, where, tmp); - writel(BIT(SET_NAK_OUT_PACKETS), - &ep->regs->ep_rsp); - } -} -#define ASSERT_OUT_NAKING(ep) assert_out_naking(ep, __func__) -#else -#define ASSERT_OUT_NAKING(ep) do {} while (0) -#endif - static inline void stop_out_naking(struct net2280_ep *ep) { u32 tmp; -- cgit v0.10.2 From cb442ee1592d26815156201afa986a4ec3fb4bbf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:51:02 +0100 Subject: usb: gadget: udc: net2280: Re-enable dynamic debug messages Some debug messages were not build due to unconditional #if 0. These messages are very useful for debugging and the user can enable them on demand via dynamic debug. If they are not enabled the performance is not affected. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index b1c253e..d2fabdc 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -927,10 +927,8 @@ net2280_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) return ret; } -#if 0 ep_vdbg(dev, "%s queue req %p, len %d buf %p\n", _ep->name, _req, _req->length, _req->buf); -#endif spin_lock_irqsave(&dev->lock, flags); @@ -2287,10 +2285,10 @@ static void handle_ep_small(struct net2280_ep *ep) /* ack all, and handle what we care about */ t = readl(&ep->regs->ep_stat); ep->irqs++; -#if 0 + ep_vdbg(ep->dev, "%s ack ep_stat %08x, req %p\n", ep->ep.name, t, req ? &req->req : 0); -#endif + if (!ep->is_in || (ep->dev->quirks & PLX_2280)) writel(t & ~BIT(NAK_OUT_PACKETS), &ep->regs->ep_stat); else -- cgit v0.10.2 From 6b448af46e2a2fc9563fd86fb3b7cf06fcf2dd47 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 16 Dec 2014 11:51:44 +0100 Subject: drivers: usb: dwc2: remove 'force' parameter from kill_all_requests() This patch fixes in simpler way the bug described in [1] and [2]. It looks like DWC2 is the only UDC driver that doesn't force usb requests to complete in ep_disable() function. This causes described problem, because we have no guarantee that all requests will be completed before unbind of usb function. To fix this problem we force all requests of disabled endpoint to complete. Also currently running request is not handled. This allowed to simplify code of kill_all_requests() function, because 'force' parameter is always set to true, so we don't need it anymore. In s3c_hsotg_rx_data() we change function used to print message when active request is NULL from dev_warn() to dev_dbg(), because such situation is harmless for driver and now it can take place during normal endpoint disabling. [1] https://lkml.org/lkml/2014/12/9/283 [2] https://lkml.org/lkml/2014/12/12/360 Acked-by: Paul Zimmerman Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index a461406..1a8e0a6 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1305,7 +1305,7 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) u32 epctl = readl(hsotg->regs + DOEPCTL(ep_idx)); int ptr; - dev_warn(hsotg->dev, + dev_dbg(hsotg->dev, "%s: FIFO %d bytes on ep%d but no req (DXEPCTl=0x%08x)\n", __func__, size, ep_idx, epctl); @@ -1988,30 +1988,23 @@ static void s3c_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) * @hsotg: The device state. * @ep: The endpoint the requests may be on. * @result: The result code to use. - * @force: Force removal of any current requests * * Go through the requests on the given endpoint and mark them * completed with the given result code. */ static void kill_all_requests(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *ep, - int result, bool force) + int result) { struct s3c_hsotg_req *req, *treq; unsigned size; - list_for_each_entry_safe(req, treq, &ep->queue, queue) { - /* - * currently, we can't do much about an already - * running request on an in endpoint - */ - - if (ep->req == req && ep->dir_in && !force) - continue; + ep->req = NULL; + list_for_each_entry_safe(req, treq, &ep->queue, queue) s3c_hsotg_complete_request(hsotg, ep, req, result); - } + if (!hsotg->dedicated_fifos) return; size = (readl(hsotg->regs + DTXFSTS(ep->index)) & 0xffff) * 4; @@ -2036,7 +2029,7 @@ void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg) hsotg->connected = 0; for (ep = 0; ep < hsotg->num_of_eps; ep++) - kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN, true); + kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN); call_gadget(hsotg, disconnect); } @@ -2334,7 +2327,7 @@ irq_retry: msecs_to_jiffies(200))) { kill_all_requests(hsotg, &hsotg->eps[0], - -ECONNRESET, true); + -ECONNRESET); s3c_hsotg_core_init_disconnected(hsotg); s3c_hsotg_core_connect(hsotg); @@ -2588,7 +2581,7 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) spin_lock_irqsave(&hsotg->lock, flags); /* terminate all requests with shutdown */ - kill_all_requests(hsotg, hs_ep, -ESHUTDOWN, false); + kill_all_requests(hsotg, hs_ep, -ESHUTDOWN); hsotg->fifo_map &= ~(1<fifo_index); hs_ep->fifo_index = 0; -- cgit v0.10.2 From fdf80e78acbaa6a50531490b3a6ba027a52a69ad Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 24 Dec 2014 13:48:02 +0800 Subject: usb: phy: phy-mxs-usb: add power down and disable wakeup for .shutdown When we shut down the PHY, we need to power down all PHY's functions as well as disable wakeup, it is the opposite operation we do at .init. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index b9589f6..eaf94b0 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -293,6 +293,17 @@ static int mxs_phy_init(struct usb_phy *phy) static void mxs_phy_shutdown(struct usb_phy *phy) { struct mxs_phy *mxs_phy = to_mxs_phy(phy); + u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP | + BM_USBPHY_CTRL_ENDPDMCHG_WKUP | + BM_USBPHY_CTRL_ENIDCHG_WKUP | + BM_USBPHY_CTRL_ENAUTOSET_USBCLKS | + BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE | + BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD | + BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE | + BM_USBPHY_CTRL_ENAUTO_PWRON_PLL; + + writel(value, phy->io_priv + HW_USBPHY_CTRL_CLR); + writel(0xffffffff, phy->io_priv + HW_USBPHY_PWD); writel(BM_USBPHY_CTRL_CLKGATE, phy->io_priv + HW_USBPHY_CTRL_SET); -- cgit v0.10.2 From f78c0957674bda6687992058554c07683418ebc2 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 24 Dec 2014 13:48:03 +0800 Subject: usb: phy: phy-mxs-usb: do not depend on speed for disconnect notifier For some user cases, like plug out and replug in usb device during the system suspend, the speed negotiation will be error due to host doesn't know the device's disconnection, and it still hopes the high speed device, but the device backs to "powered" state which its high speed termination is not enabled, the usb core calls the PHY's disconnect notifier with "full speed", it will NOT take effect at all. If the usb core calls disconnect notifer, the port change must happen, so it is safe to disable high speed disconenct detector, since connect notifier will be called soon if the device is still connected on the port, and we will enable high speed disconnect detector at that time. Acked-by: Li Jun Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index eaf94b0..58cae78 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -370,7 +370,9 @@ static int mxs_phy_on_disconnect(struct usb_phy *phy, dev_dbg(phy->dev, "%s device has disconnected\n", (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); - if (speed == USB_SPEED_HIGH) + /* Sometimes, the speed is not high speed when the error occurs */ + if (readl(phy->io_priv + HW_USBPHY_CTRL) & + BM_USBPHY_CTRL_ENHOSTDISCONDETECT) writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, phy->io_priv + HW_USBPHY_CTRL_CLR); -- cgit v0.10.2 From 07673204777abe893092cc6e42ea04e5c0ac8cb7 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 30 Dec 2014 14:02:28 +0800 Subject: usb: phy: change some comments - Delete the OTG stuffs - .set_suspend is for controller, not for A-device or B-device. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index f499c23..bc91b5d 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -1,5 +1,5 @@ -/* USB OTG (On The Go) defines */ /* + * USB PHY defines * * These APIs may be used between USB controllers. USB device drivers * (for either host or peripheral roles) don't use these calls; they @@ -106,7 +106,7 @@ struct usb_phy { int (*set_power)(struct usb_phy *x, unsigned mA); - /* for non-OTG B devices: set transceiver into suspend mode */ + /* Set transceiver into suspend mode */ int (*set_suspend)(struct usb_phy *x, int suspend); -- cgit v0.10.2 From 6f15e2dc710cc604cc81f0ff596d83c332b5cbe8 Mon Sep 17 00:00:00 2001 From: Rickard Strandqvist Date: Thu, 1 Jan 2015 20:15:34 +0100 Subject: usb: phy: phy-fsl-usb: Remove some unused functions Removes some functions that are not used anywhere: fsl_otg_tick_timer() view_ulpi() This was partially found by using a static code analysis program called cppcheck. Signed-off-by: Rickard Strandqvist Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index ab38aa3..94eb292 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -107,19 +107,6 @@ static void (*_fsl_writel)(u32 v, unsigned __iomem *p); #define fsl_writel(val, addr) writel(val, addr) #endif /* CONFIG_PPC32 */ -/* Routines to access transceiver ULPI registers */ -u8 view_ulpi(u8 addr) -{ - u32 temp; - - temp = 0x40000000 | (addr << 16); - fsl_writel(temp, &usb_dr_regs->ulpiview); - udelay(1000); - while (temp & 0x40) - temp = fsl_readl(&usb_dr_regs->ulpiview); - return (le32_to_cpu(temp) & 0x0000ff00) >> 8; -} - int write_ulpi(u8 addr, u8 data) { u32 temp; @@ -460,28 +447,6 @@ static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) fsl_otg_del_timer(fsm, timer); } -/* - * Reduce timer count by 1, and find timeout conditions. - * Called by fsl_otg 1ms timer interrupt - */ -int fsl_otg_tick_timer(void) -{ - struct fsl_otg_timer *tmp_timer, *del_tmp; - int expired = 0; - - list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { - tmp_timer->count--; - /* check if timer expires */ - if (!tmp_timer->count) { - list_del(&tmp_timer->list); - tmp_timer->function(tmp_timer->data); - expired = 1; - } - } - - return expired; -} - /* Reset controller, not reset the bus */ void otg_reset_controller(void) { -- cgit v0.10.2 From 3280e67536f8a4d4adf8dcde10cb4c4b577c34f4 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:46:58 +0100 Subject: usb: atmel_usba_udc: Rework at91sam9rl errata handling at91sam9rl SoC has an erratum forcing us to toggle the BIAS on USB suspend/resume events. This specific handling is only activated when CONFIG_ARCH_AT91SAM9RL is set and this option is only set when building a non-DT kernel, which is problematic since non-DT support for at91sam9rl SoC has been removed. Rework the toggle_bias implementation to attach it to the "at91sam9rl-udc" compatible string. Add new compatible strings to avoid executing at91sam9rl erratum handling on other SoCs. Acked-by: Alexandre Belloni Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/atmel-usb.txt b/Documentation/devicetree/bindings/usb/atmel-usb.txt index bc2222c..38fee0f 100644 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ b/Documentation/devicetree/bindings/usb/atmel-usb.txt @@ -51,7 +51,10 @@ usb1: gadget@fffa4000 { Atmel High-Speed USB device controller Required properties: - - compatible: Should be "atmel,at91sam9rl-udc" + - compatible: Should be one of the following + "at91sam9rl-udc" + "at91sam9g45-udc" + "sama5d3-udc" - reg: Address and length of the register set for the device - interrupts: Should contain usba interrupt - ep childnode: To specify the number of endpoints and their properties. diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index ce88237..36fd34b 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -8,6 +8,7 @@ * published by the Free Software Foundation. */ #include +#include #include #include #include @@ -324,28 +325,12 @@ static int vbus_is_present(struct usba_udc *udc) return 1; } -#if defined(CONFIG_ARCH_AT91SAM9RL) - -#include - -static void toggle_bias(int is_on) -{ - unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); - - if (is_on) - at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); - else - at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); -} - -#else - -static void toggle_bias(int is_on) +static void toggle_bias(struct usba_udc *udc, int is_on) { + if (udc->errata && udc->errata->toggle_bias) + udc->errata->toggle_bias(udc, is_on); } -#endif /* CONFIG_ARCH_AT91SAM9RL */ - static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) { unsigned int transaction_len; @@ -1620,7 +1605,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) DBG(DBG_INT, "irq, status=%#08x\n", status); if (status & USBA_DET_SUSPEND) { - toggle_bias(0); + toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN @@ -1632,7 +1617,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) } if (status & USBA_WAKE_UP) { - toggle_bias(1); + toggle_bias(udc, 1); usba_writel(udc, INT_CLR, USBA_WAKE_UP); DBG(DBG_BUS, "Wake Up CPU detected\n"); } @@ -1736,13 +1721,13 @@ static irqreturn_t usba_vbus_irq(int irq, void *devid) vbus = vbus_is_present(udc); if (vbus != udc->vbus_prev) { if (vbus) { - toggle_bias(1); + toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); usba_writel(udc, INT_ENB, USBA_END_OF_RESET); } else { udc->gadget.speed = USB_SPEED_UNKNOWN; reset_all_endpoints(udc); - toggle_bias(0); + toggle_bias(udc, 0); usba_writel(udc, CTRL, USBA_DISABLE_MASK); if (udc->driver->disconnect) { spin_unlock(&udc->lock); @@ -1788,7 +1773,7 @@ static int atmel_usba_start(struct usb_gadget *gadget, /* If Vbus is present, enable the controller and wait for reset */ spin_lock_irqsave(&udc->lock, flags); if (vbus_is_present(udc) && udc->vbus_prev == 0) { - toggle_bias(1); + toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); usba_writel(udc, INT_ENB, USBA_END_OF_RESET); } @@ -1811,7 +1796,7 @@ static int atmel_usba_stop(struct usb_gadget *gadget) spin_unlock_irqrestore(&udc->lock, flags); /* This will also disable the DP pullup */ - toggle_bias(0); + toggle_bias(udc, 0); usba_writel(udc, CTRL, USBA_DISABLE_MASK); clk_disable_unprepare(udc->hclk); @@ -1823,6 +1808,29 @@ static int atmel_usba_stop(struct usb_gadget *gadget) } #ifdef CONFIG_OF +static void at91sam9rl_toggle_bias(struct usba_udc *udc, int is_on) +{ + unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); + + if (is_on) + at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); + else + at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); +} + +static const struct usba_udc_errata at91sam9rl_errata = { + .toggle_bias = at91sam9rl_toggle_bias, +}; + +static const struct of_device_id atmel_udc_dt_ids[] = { + { .compatible = "atmel,at91sam9rl-udc", .data = &at91sam9rl_errata }, + { .compatible = "atmel,at91sam9g45-udc" }, + { .compatible = "atmel,sama5d3-udc" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_udc_dt_ids); + static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, struct usba_udc *udc) { @@ -1830,10 +1838,17 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, const char *name; enum of_gpio_flags flags; struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; struct device_node *pp; int i, ret; struct usba_ep *eps, *ep; + match = of_match_node(atmel_udc_dt_ids, np); + if (!match) + return ERR_PTR(-EINVAL); + + udc->errata = match->data; + udc->num_ep = 0; udc->vbus_pin = of_get_named_gpio_flags(np, "atmel,vbus-gpio", 0, @@ -2024,7 +2039,7 @@ static int usba_udc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Unable to enable pclk, aborting.\n"); return ret; } - toggle_bias(0); + usba_writel(udc, CTRL, USBA_DISABLE_MASK); clk_disable_unprepare(pclk); @@ -2033,6 +2048,8 @@ static int usba_udc_probe(struct platform_device *pdev) else udc->usba_ep = usba_udc_pdata(pdev, udc); + toggle_bias(udc, 0); + if (IS_ERR(udc->usba_ep)) return PTR_ERR(udc->usba_ep); @@ -2092,15 +2109,6 @@ static int __exit usba_udc_remove(struct platform_device *pdev) return 0; } -#if defined(CONFIG_OF) -static const struct of_device_id atmel_udc_dt_ids[] = { - { .compatible = "atmel,at91sam9rl-udc" }, - { /* sentinel */ } -}; - -MODULE_DEVICE_TABLE(of, atmel_udc_dt_ids); -#endif - static struct platform_driver udc_driver = { .remove = __exit_p(usba_udc_remove), .driver = { diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index a70706e..456899e 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -304,6 +304,10 @@ struct usba_request { unsigned int mapped:1; }; +struct usba_udc_errata { + void (*toggle_bias)(struct usba_udc *udc, int is_on); +}; + struct usba_udc { /* Protect hw registers from concurrent modifications */ spinlock_t lock; @@ -314,6 +318,7 @@ struct usba_udc { struct usb_gadget gadget; struct usb_gadget_driver *driver; struct platform_device *pdev; + const struct usba_udc_errata *errata; int irq; int vbus_pin; int vbus_pin_inverted; -- cgit v0.10.2 From 258e2ddd634c20065d1c41290d10d2e5cf2f56e2 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:46:59 +0100 Subject: usb: atmel_usba_udc: Add at91sam9g45 and at91sam9x5 errata handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit at91sam9g45 and at91sam9x5 SoCs have an hardware bug forcing us to generate a pulse on the BIAS signal on "USB end of reset” and “USB end of resume" events. Acked-by: Alexandre Belloni Reported-by: Patrice VILCHEZ Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 36fd34b..55c8dde 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -331,6 +331,17 @@ static void toggle_bias(struct usba_udc *udc, int is_on) udc->errata->toggle_bias(udc, is_on); } +static void generate_bias_pulse(struct usba_udc *udc) +{ + if (!udc->bias_pulse_needed) + return; + + if (udc->errata && udc->errata->pulse_bias) + udc->errata->pulse_bias(udc); + + udc->bias_pulse_needed = false; +} + static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) { unsigned int transaction_len; @@ -1607,6 +1618,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_DET_SUSPEND) { toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); + udc->bias_pulse_needed = true; DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN && udc->driver && udc->driver->suspend) { @@ -1624,6 +1636,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_END_OF_RESUME) { usba_writel(udc, INT_CLR, USBA_END_OF_RESUME); + generate_bias_pulse(udc); DBG(DBG_BUS, "Resume detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN && udc->driver && udc->driver->resume) { @@ -1659,6 +1672,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) struct usba_ep *ep0; usba_writel(udc, INT_CLR, USBA_END_OF_RESET); + generate_bias_pulse(udc); reset_all_endpoints(udc); if (udc->gadget.speed != USB_SPEED_UNKNOWN && udc->driver) { @@ -1818,13 +1832,25 @@ static void at91sam9rl_toggle_bias(struct usba_udc *udc, int is_on) at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); } +static void at91sam9g45_pulse_bias(struct usba_udc *udc) +{ + unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); + + at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); + at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); +} + static const struct usba_udc_errata at91sam9rl_errata = { .toggle_bias = at91sam9rl_toggle_bias, }; +static const struct usba_udc_errata at91sam9g45_errata = { + .pulse_bias = at91sam9g45_pulse_bias, +}; + static const struct of_device_id atmel_udc_dt_ids[] = { { .compatible = "atmel,at91sam9rl-udc", .data = &at91sam9rl_errata }, - { .compatible = "atmel,at91sam9g45-udc" }, + { .compatible = "atmel,at91sam9g45-udc", .data = &at91sam9g45_errata }, { .compatible = "atmel,sama5d3-udc" }, { /* sentinel */ } }; diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 456899e..72b3537 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -306,6 +306,7 @@ struct usba_request { struct usba_udc_errata { void (*toggle_bias)(struct usba_udc *udc, int is_on); + void (*pulse_bias)(struct usba_udc *udc); }; struct usba_udc { @@ -326,6 +327,7 @@ struct usba_udc { struct clk *pclk; struct clk *hclk; struct usba_ep *usba_ep; + bool bias_pulse_needed; u16 devstatus; -- cgit v0.10.2 From 9870d895ad87efae130b2990db9ba265271f113b Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:47:01 +0100 Subject: usb: atmel_usba_udc: Mask status with enabled irqs Avoid interpreting useless status flags when we're not waiting for such events by masking the status variable with the interrupt enabled register value. Acked-by: Alexandre Belloni Reported-by: Patrice VILCHEZ Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 55c8dde..bc3a532 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -1612,12 +1612,14 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) spin_lock(&udc->lock); - status = usba_readl(udc, INT_STA); + status = usba_readl(udc, INT_STA) & usba_readl(udc, INT_ENB); DBG(DBG_INT, "irq, status=%#08x\n", status); if (status & USBA_DET_SUSPEND) { toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); + usba_writel(udc, INT_ENB, + usba_readl(udc, INT_ENB) | USBA_WAKE_UP); udc->bias_pulse_needed = true; DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN @@ -1631,6 +1633,8 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_WAKE_UP) { toggle_bias(udc, 1); usba_writel(udc, INT_CLR, USBA_WAKE_UP); + usba_writel(udc, INT_ENB, + usba_readl(udc, INT_ENB) & ~USBA_WAKE_UP); DBG(DBG_BUS, "Wake Up CPU detected\n"); } -- cgit v0.10.2 From e3a912a124c380db61eff762faa0547ea4c90eb4 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:47:02 +0100 Subject: usb: gadget: atmel_usba: Cache INT_ENB register value Cache INT_ENB register value in order to avoid uncached iomem access, and thus improve access time to INT_ENB value. Acked-by: Alexandre Belloni Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index bc3a532..6dfe17b 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -316,6 +316,17 @@ static inline void usba_cleanup_debugfs(struct usba_udc *udc) } #endif +static inline u32 usba_int_enb_get(struct usba_udc *udc) +{ + return udc->int_enb_cache; +} + +static inline void usba_int_enb_set(struct usba_udc *udc, u32 val) +{ + usba_writel(udc, INT_ENB, val); + udc->int_enb_cache = val; +} + static int vbus_is_present(struct usba_udc *udc) { if (gpio_is_valid(udc->vbus_pin)) @@ -597,16 +608,14 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) if (ep->can_dma) { u32 ctrl; - usba_writel(udc, INT_ENB, - (usba_readl(udc, INT_ENB) - | USBA_BF(EPT_INT, 1 << ep->index) - | USBA_BF(DMA_INT, 1 << ep->index))); + usba_int_enb_set(udc, usba_int_enb_get(udc) | + USBA_BF(EPT_INT, 1 << ep->index) | + USBA_BF(DMA_INT, 1 << ep->index)); ctrl = USBA_AUTO_VALID | USBA_INTDIS_DMA; usba_ep_writel(ep, CTL_ENB, ctrl); } else { - usba_writel(udc, INT_ENB, - (usba_readl(udc, INT_ENB) - | USBA_BF(EPT_INT, 1 << ep->index))); + usba_int_enb_set(udc, usba_int_enb_get(udc) | + USBA_BF(EPT_INT, 1 << ep->index)); } spin_unlock_irqrestore(&udc->lock, flags); @@ -614,7 +623,7 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) DBG(DBG_HW, "EPT_CFG%d after init: %#08lx\n", ep->index, (unsigned long)usba_ep_readl(ep, CFG)); DBG(DBG_HW, "INT_ENB after init: %#08lx\n", - (unsigned long)usba_readl(udc, INT_ENB)); + (unsigned long)usba_int_enb_get(udc)); return 0; } @@ -650,9 +659,8 @@ static int usba_ep_disable(struct usb_ep *_ep) usba_dma_readl(ep, STATUS); } usba_ep_writel(ep, CTL_DIS, USBA_EPT_ENABLE); - usba_writel(udc, INT_ENB, - usba_readl(udc, INT_ENB) - & ~USBA_BF(EPT_INT, 1 << ep->index)); + usba_int_enb_set(udc, usba_int_enb_get(udc) & + ~USBA_BF(EPT_INT, 1 << ep->index)); request_complete_list(ep, &req_list, -ESHUTDOWN); @@ -1606,20 +1614,20 @@ static void usba_dma_irq(struct usba_udc *udc, struct usba_ep *ep) static irqreturn_t usba_udc_irq(int irq, void *devid) { struct usba_udc *udc = devid; - u32 status; + u32 status, int_enb; u32 dma_status; u32 ep_status; spin_lock(&udc->lock); - status = usba_readl(udc, INT_STA) & usba_readl(udc, INT_ENB); + int_enb = usba_int_enb_get(udc); + status = usba_readl(udc, INT_STA) & int_enb; DBG(DBG_INT, "irq, status=%#08x\n", status); if (status & USBA_DET_SUSPEND) { toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); - usba_writel(udc, INT_ENB, - usba_readl(udc, INT_ENB) | USBA_WAKE_UP); + usba_int_enb_set(udc, int_enb | USBA_WAKE_UP); udc->bias_pulse_needed = true; DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN @@ -1633,8 +1641,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_WAKE_UP) { toggle_bias(udc, 1); usba_writel(udc, INT_CLR, USBA_WAKE_UP); - usba_writel(udc, INT_ENB, - usba_readl(udc, INT_ENB) & ~USBA_WAKE_UP); + usba_int_enb_set(udc, int_enb & ~USBA_WAKE_UP); DBG(DBG_BUS, "Wake Up CPU detected\n"); } @@ -1702,11 +1709,8 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) | USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE))); usba_ep_writel(ep0, CTL_ENB, USBA_EPT_ENABLE | USBA_RX_SETUP); - usba_writel(udc, INT_ENB, - (usba_readl(udc, INT_ENB) - | USBA_BF(EPT_INT, 1) - | USBA_DET_SUSPEND - | USBA_END_OF_RESUME)); + usba_int_enb_set(udc, int_enb | USBA_BF(EPT_INT, 1) | + USBA_DET_SUSPEND | USBA_END_OF_RESUME); /* * Unclear why we hit this irregularly, e.g. in usbtest, @@ -1741,7 +1745,7 @@ static irqreturn_t usba_vbus_irq(int irq, void *devid) if (vbus) { toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); - usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + usba_int_enb_set(udc, USBA_END_OF_RESET); } else { udc->gadget.speed = USB_SPEED_UNKNOWN; reset_all_endpoints(udc); @@ -1793,7 +1797,7 @@ static int atmel_usba_start(struct usb_gadget *gadget, if (vbus_is_present(udc) && udc->vbus_prev == 0) { toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); - usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + usba_int_enb_set(udc, USBA_END_OF_RESET); } spin_unlock_irqrestore(&udc->lock, flags); diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 72b3537..497cd18 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -334,6 +334,8 @@ struct usba_udc { u16 test_mode; int vbus_prev; + u32 int_enb_cache; + #ifdef CONFIG_USB_GADGET_DEBUG_FS struct dentry *debugfs_root; struct dentry *debugfs_regs; -- cgit v0.10.2 From e9f2cefb0cdc2aea8b70dbf68a3f78b88e57cf34 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sat, 6 Dec 2014 22:05:13 +0100 Subject: usb: phy: generic: migrate to gpio_desc Change internal gpio handling from integer gpios into gpio descriptors. This change only addresses the internal API and device-tree/ACPI, while the legacy platform data remains integer space based. This change is only build compile tested, and very prone to error. I leave this comment for now in the commit message so that this patch gets some testing as I'm pretty sure it's buggy. Signed-off-by: Robert Jarzmik Acked-by: Linus Walleij Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index f1b719b..d53928f 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -59,16 +59,8 @@ static int nop_set_suspend(struct usb_phy *x, int suspend) static void nop_reset_set(struct usb_phy_generic *nop, int asserted) { - int value; - - if (!gpio_is_valid(nop->gpio_reset)) - return; - - value = asserted; - if (nop->reset_active_low) - value = !value; - - gpio_set_value_cansleep(nop->gpio_reset, value); + if (nop->gpiod_reset) + gpiod_set_value(nop->gpiod_reset, asserted); if (!asserted) usleep_range(10000, 20000); @@ -143,35 +135,38 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, struct usb_phy_generic_platform_data *pdata) { enum usb_phy_type type = USB_PHY_TYPE_USB2; - int err; + int err = 0; u32 clk_rate = 0; bool needs_vcc = false; - nop->reset_active_low = true; /* default behaviour */ - if (dev->of_node) { struct device_node *node = dev->of_node; - enum of_gpio_flags flags = 0; if (of_property_read_u32(node, "clock-frequency", &clk_rate)) clk_rate = 0; needs_vcc = of_property_read_bool(node, "vcc-supply"); - nop->gpio_reset = of_get_named_gpio_flags(node, "reset-gpios", - 0, &flags); - if (nop->gpio_reset == -EPROBE_DEFER) - return -EPROBE_DEFER; - - nop->reset_active_low = flags & OF_GPIO_ACTIVE_LOW; - + nop->gpiod_reset = devm_gpiod_get(dev, "reset-gpios"); + err = PTR_ERR(nop->gpiod_reset); } else if (pdata) { type = pdata->type; clk_rate = pdata->clk_rate; needs_vcc = pdata->needs_vcc; - nop->gpio_reset = pdata->gpio_reset; - } else { - nop->gpio_reset = -1; + if (gpio_is_valid(gpio->gpio_reset)) { + err = devm_gpio_request_one(dev, pdata->gpio_reset, 0, + dev_name(dev)); + if (!err) + nop->gpiod_reset = + gpio_to_desc(pdata->gpio_reset); + } + } + + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (err) { + dev_err(dev, "Error requesting RESET GPIO\n"); + return err; } nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg), @@ -201,24 +196,6 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, return -EPROBE_DEFER; } - if (gpio_is_valid(nop->gpio_reset)) { - unsigned long gpio_flags; - - /* Assert RESET */ - if (nop->reset_active_low) - gpio_flags = GPIOF_OUT_INIT_LOW; - else - gpio_flags = GPIOF_OUT_INIT_HIGH; - - err = devm_gpio_request_one(dev, nop->gpio_reset, - gpio_flags, dev_name(dev)); - if (err) { - dev_err(dev, "Error requesting RESET GPIO %d\n", - nop->gpio_reset); - return err; - } - } - nop->dev = dev; nop->phy.dev = nop->dev; nop->phy.label = "nop-xceiv"; diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h index d8feacc..09924fd 100644 --- a/drivers/usb/phy/phy-generic.h +++ b/drivers/usb/phy/phy-generic.h @@ -2,14 +2,14 @@ #define _PHY_GENERIC_H_ #include +#include struct usb_phy_generic { struct usb_phy phy; struct device *dev; struct clk *clk; struct regulator *vcc; - int gpio_reset; - bool reset_active_low; + struct gpio_desc *gpiod_reset; }; int usb_gen_phy_init(struct usb_phy *phy); -- cgit v0.10.2 From 7bdea87a871f217a8a463f9203e6d0c702b7fda9 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sat, 6 Dec 2014 22:05:14 +0100 Subject: usb: phy: nop: device tree documentation for vbus Enhance the phy documentation by adding 2 new optional bindings : - the vbus gpio, which detects usb insertion - the vbus regulator, which provides current drawn from the usb cable Signed-off-by: Robert Jarzmik Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt index 8db5b33..5be01c8 100644 --- a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt +++ b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt @@ -17,6 +17,11 @@ Optional properties: - reset-gpios: Should specify the GPIO for reset. +- vbus-detect-gpio: should specify the GPIO detecting a VBus insertion + (see Documentation/devicetree/bindings/gpio/gpio.txt) +- vbus-regulator : should specifiy the regulator supplying current drawn from + the VBus line (see Documentation/devicetree/bindings/regulator/regulator.txt). + Example: hsusb1_phy { @@ -26,8 +31,11 @@ Example: clock-names = "main_clk"; vcc-supply = <&hsusb1_vcc_regulator>; reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; + vbus-detect-gpio = <&gpio2 13 GPIO_ACTIVE_HIGH>; + vbus-regulator = <&vbus_regulator>; }; hsusb1_phy is a NOP USB PHY device that gets its clock from an oscillator and expects that clock to be configured to 19.2MHz by the NOP PHY driver. hsusb1_vcc_regulator provides power to the PHY and GPIO 7 controls RESET. +GPIO 13 detects VBus insertion, and accordingly notifies the vbus-regulator. -- cgit v0.10.2 From 7acc9973e3c42de9926b28eec8ae3434dfdde3be Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sat, 6 Dec 2014 22:05:15 +0100 Subject: usb: phy: generic: add vbus support Add support for vbus detection and power supply. This code is more or less stolen from phy-gpio-vbus-usb.c, and aims at providing a detection mechanism for VBus (ie. usb cable plug) based on a GPIO line, and a power supply activation which draws current from the VBus. [ balbi@ti.com : fix build break ] Signed-off-by: Robert Jarzmik Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index d53928f..dd05254 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,9 @@ #include "phy-generic.h" +#define VBUS_IRQ_FLAGS \ + (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) + struct platform_device *usb_phy_generic_register(void) { return platform_device_register_simple("usb_phy_generic", @@ -66,6 +70,73 @@ static void nop_reset_set(struct usb_phy_generic *nop, int asserted) usleep_range(10000, 20000); } +/* interface to regulator framework */ +static void nop_set_vbus_draw(struct usb_phy_generic *nop, unsigned mA) +{ + struct regulator *vbus_draw = nop->vbus_draw; + int enabled; + int ret; + + if (!vbus_draw) + return; + + enabled = nop->vbus_draw_enabled; + if (mA) { + regulator_set_current_limit(vbus_draw, 0, 1000 * mA); + if (!enabled) { + ret = regulator_enable(vbus_draw); + if (ret < 0) + return; + nop->vbus_draw_enabled = 1; + } + } else { + if (enabled) { + ret = regulator_disable(vbus_draw); + if (ret < 0) + return; + nop->vbus_draw_enabled = 0; + } + } + nop->mA = mA; +} + + +static irqreturn_t nop_gpio_vbus_thread(int irq, void *data) +{ + struct usb_phy_generic *nop = data; + struct usb_otg *otg = nop->phy.otg; + int vbus, status; + + vbus = gpiod_get_value(nop->gpiod_vbus); + if ((vbus ^ nop->vbus) == 0) + return IRQ_HANDLED; + nop->vbus = vbus; + + if (vbus) { + status = USB_EVENT_VBUS; + otg->state = OTG_STATE_B_PERIPHERAL; + nop->phy.last_event = status; + usb_gadget_vbus_connect(otg->gadget); + + /* drawing a "unit load" is *always* OK, except for OTG */ + nop_set_vbus_draw(nop, 100); + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } else { + nop_set_vbus_draw(nop, 0); + + usb_gadget_vbus_disconnect(otg->gadget); + status = USB_EVENT_NONE; + otg->state = OTG_STATE_B_IDLE; + nop->phy.last_event = status; + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } + return IRQ_HANDLED; +} + int usb_gen_phy_init(struct usb_phy *phy) { struct usb_phy_generic *nop = dev_get_drvdata(phy->dev); @@ -149,17 +220,23 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, needs_vcc = of_property_read_bool(node, "vcc-supply"); nop->gpiod_reset = devm_gpiod_get(dev, "reset-gpios"); err = PTR_ERR(nop->gpiod_reset); + if (!err) { + nop->gpiod_vbus = devm_gpiod_get(dev, + "vbus-detect-gpio"); + err = PTR_ERR(nop->gpiod_vbus); + } } else if (pdata) { type = pdata->type; clk_rate = pdata->clk_rate; needs_vcc = pdata->needs_vcc; - if (gpio_is_valid(gpio->gpio_reset)) { + if (gpio_is_valid(pdata->gpio_reset)) { err = devm_gpio_request_one(dev, pdata->gpio_reset, 0, dev_name(dev)); if (!err) nop->gpiod_reset = gpio_to_desc(pdata->gpio_reset); } + nop->gpiod_vbus = pdata->gpiod_vbus; } if (err == -EPROBE_DEFER) @@ -224,6 +301,18 @@ static int usb_phy_generic_probe(struct platform_device *pdev) err = usb_phy_gen_create_phy(dev, nop, dev_get_platdata(&pdev->dev)); if (err) return err; + if (nop->gpiod_vbus) { + err = devm_request_threaded_irq(&pdev->dev, + gpiod_to_irq(nop->gpiod_vbus), + NULL, nop_gpio_vbus_thread, + VBUS_IRQ_FLAGS, "vbus_detect", + nop); + if (err) { + dev_err(&pdev->dev, "can't request irq %i, err: %d\n", + gpiod_to_irq(nop->gpiod_vbus), err); + return err; + } + } nop->phy.init = usb_gen_phy_init; nop->phy.shutdown = usb_gen_phy_shutdown; diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h index 09924fd..0d0eadd 100644 --- a/drivers/usb/phy/phy-generic.h +++ b/drivers/usb/phy/phy-generic.h @@ -3,6 +3,7 @@ #include #include +#include struct usb_phy_generic { struct usb_phy phy; @@ -10,6 +11,11 @@ struct usb_phy_generic { struct clk *clk; struct regulator *vcc; struct gpio_desc *gpiod_reset; + struct gpio_desc *gpiod_vbus; + struct regulator *vbus_draw; + bool vbus_draw_enabled; + unsigned long mA; + unsigned int vbus; }; int usb_gen_phy_init(struct usb_phy *phy); diff --git a/include/linux/usb/usb_phy_generic.h b/include/linux/usb/usb_phy_generic.h index 68adae8..c13632d5 100644 --- a/include/linux/usb/usb_phy_generic.h +++ b/include/linux/usb/usb_phy_generic.h @@ -2,6 +2,7 @@ #define __LINUX_USB_NOP_XCEIV_H #include +#include struct usb_phy_generic_platform_data { enum usb_phy_type type; @@ -11,6 +12,7 @@ struct usb_phy_generic_platform_data { unsigned int needs_vcc:1; unsigned int needs_reset:1; /* deprecated */ int gpio_reset; + struct gpio_desc *gpiod_vbus; }; #if IS_ENABLED(CONFIG_NOP_USB_XCEIV) -- cgit v0.10.2 From a7657a93d63d87399b53d15999e77973b4f02cf3 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Mon, 5 Jan 2015 10:44:44 +0100 Subject: tools: ffs-aio-example: add missing wMaxPacketSize for HS descs It's needed, to have more than 64 bytes of maxpacketsize. Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c index adc310a..1f44a29 100644 --- a/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c +++ b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c @@ -103,12 +103,14 @@ static const struct { .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), }, .bulk_source = { .bLength = sizeof(descriptors.hs_descs.bulk_source), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 2 | USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), }, }, }; -- cgit v0.10.2 From 67913bbd0b62d297520667cd3057aff5a5890ca6 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 10 Sep 2014 17:50:24 +0200 Subject: usb: gadget: f_fs: refactor and document __ffs_ep0_read_events better Instead of using variable length array, use a static length equal to the size of the ffs->ev.types array. This gets rid of a sparse warning: drivers/usb/gadget/function/f_fs.c:401:44: warning: Variable length array is used. and makes it more explicit that the array has a very tight upper size limit. Also add some more documentation about the ev.types array and how its size is limited and affects the rest of the code. Reported-by: Dan Carpenter Reported-by: Rohith Seelaboyina Signed-off-by: Michal Nazarewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 63314ed..a00ee97 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -390,17 +390,20 @@ done_spin: return ret; } +/* Called with ffs->ev.waitq.lock and ffs->mutex held, both released on exit. */ static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, size_t n) { /* - * We are holding ffs->ev.waitq.lock and ffs->mutex and we need - * to release them. + * n cannot be bigger than ffs->ev.count, which cannot be bigger than + * size of ffs->ev.types array (which is four) so that's how much space + * we reserve. */ - struct usb_functionfs_event events[n]; + struct usb_functionfs_event events[ARRAY_SIZE(ffs->ev.types)]; + const size_t size = n * sizeof *events; unsigned i = 0; - memset(events, 0, sizeof events); + memset(events, 0, size); do { events[i].type = ffs->ev.types[i]; @@ -410,19 +413,15 @@ static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, } } while (++i < n); - if (n < ffs->ev.count) { - ffs->ev.count -= n; + ffs->ev.count -= n; + if (ffs->ev.count) memmove(ffs->ev.types, ffs->ev.types + n, ffs->ev.count * sizeof *ffs->ev.types); - } else { - ffs->ev.count = 0; - } spin_unlock_irq(&ffs->ev.waitq.lock); mutex_unlock(&ffs->mutex); - return unlikely(__copy_to_user(buf, events, sizeof events)) - ? -EFAULT : sizeof events; + return unlikely(__copy_to_user(buf, events, size)) ? -EFAULT : size; } static ssize_t ffs_ep0_read(struct file *file, char __user *buf, @@ -2377,6 +2376,13 @@ static void __ffs_event_add(struct ffs_data *ffs, if (ffs->setup_state == FFS_SETUP_PENDING) ffs->setup_state = FFS_SETUP_CANCELLED; + /* + * Logic of this function guarantees that there are at most four pending + * evens on ffs->ev.types queue. This is important because the queue + * has space for four elements only and __ffs_ep0_read_events function + * depends on that limit as well. If more event types are added, those + * limits have to be revisited or guaranteed to still hold. + */ switch (type) { case FUNCTIONFS_RESUME: rem_type2 = FUNCTIONFS_SUSPEND; -- cgit v0.10.2 From 42c1ecff0e5bdd35659d143ad3d0e40e190b4050 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 12 Jan 2015 16:37:55 +0800 Subject: usb: gadget: pxa27x_udc: delete pullup operation at .udc_start and .udc_stop UDC core has already done it before .udc_stop and after .udc_start. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index c61a896..6a855fc 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -1809,7 +1809,6 @@ static int pxa27x_udc_start(struct usb_gadget *g, /* first hook up the driver ... */ udc->driver = driver; - dplus_pullup(udc, 1); if (!IS_ERR_OR_NULL(udc->transceiver)) { retval = otg_set_peripheral(udc->transceiver->otg, @@ -1862,7 +1861,6 @@ static int pxa27x_udc_stop(struct usb_gadget *g) stop_activity(udc, NULL); udc_disable(udc); - dplus_pullup(udc, 0); udc->driver = NULL; -- cgit v0.10.2 From 14d19d9f05184c38001be47b7567c8d53fce88ff Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 12 Jan 2015 16:37:56 +0800 Subject: usb: gadget: pxa27x_udc: delete pullup operation at .udc_start and .udc_stop UDC core has already done it before .udc_stop and after .udc_start. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c index 8550b2d..f6cbe66 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.c +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -1272,7 +1272,6 @@ static int pxa25x_udc_start(struct usb_gadget *g, goto bind_fail; } - pullup(dev); dump_state(dev); return 0; bind_fail: @@ -1339,7 +1338,6 @@ static int pxa25x_udc_stop(struct usb_gadget*g) local_irq_disable(); dev->pullup = 0; - pullup(dev); stop_activity(dev, NULL); local_irq_enable(); -- cgit v0.10.2 From 487d60cc09237d24f93ec2892cd8437ae689d7e0 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 12 Jan 2015 16:37:57 +0800 Subject: usb: gadget: mv_udc_core: delete pullup operation at .udc_start UDC core has already done it after .udc_start. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 253f3df..d32160d 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -1378,9 +1378,6 @@ static int mv_udc_start(struct usb_gadget *gadget, } } - /* pullup is always on */ - mv_udc_pullup(&udc->gadget, 1); - /* When boot with cable attached, there will be no vbus irq occurred */ if (udc->qwork) queue_work(udc->qwork, &udc->vbus_work); -- cgit v0.10.2 From f6c015922c1dc99c53fb2ca95361086e65897b7c Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:41 +0100 Subject: usb: dwc2: gadget: register gadget handle to the phy Bind peripheral controller to the phy on udc_start. Unbind on udc_stop. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 1a8e0a6..b5332f0 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2878,6 +2878,8 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget, } s3c_hsotg_phy_enable(hsotg); + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_peripheral(hsotg->uphy->otg, &hsotg->gadget); spin_lock_irqsave(&hsotg->lock, flags); s3c_hsotg_init(hsotg); @@ -2927,6 +2929,8 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget) spin_unlock_irqrestore(&hsotg->lock, flags); + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_peripheral(hsotg->uphy->otg, NULL); s3c_hsotg_phy_disable(hsotg); regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); -- cgit v0.10.2 From 6ff2e8326fb50aee80f93e12de367065cc089aae Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:42 +0100 Subject: usb: dwc2: gadget: mask fifo empty irq with dma When using DMA, keep fifo empty interrupt disabled. Otherwise core is flooded by interrupts. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b5332f0..843f3ee 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2170,8 +2170,8 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) * interrupts. */ - writel(((hsotg->dedicated_fifos) ? DIEPMSK_TXFIFOEMPTY | - DIEPMSK_INTKNTXFEMPMSK : 0) | + writel(((hsotg->dedicated_fifos && !using_dma(hsotg)) ? + DIEPMSK_TXFIFOEMPTY | DIEPMSK_INTKNTXFEMPMSK : 0) | DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK | DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK | DIEPMSK_INTKNEPMISMSK, -- cgit v0.10.2 From b787d75503fb6f68aea5220ca23bb0c02d14ae74 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:43 +0100 Subject: usb: dwc2: gadget: don't process XferCompl on setup packet Only process DOEPINT.XferCompl on data packet as DOEPINTn.SetUp can occur with or without DOEPINT.XferCompl. When DOEPINT.SetUp occurs with DOEPINT.XferCompl, only DOEPINT.SetUp needs to be handled. Moreover, ignore DOEPINT.XferCompl when it occurs with DOEPINT.StupPktRcvd as driver needs to wait for DOEPINT.SetUp to continue. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 843f3ee..45afbf8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1810,6 +1810,10 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", __func__, idx, dir_in ? "in" : "out", ints); + /* Don't process XferCompl interrupt if it is a setup packet */ + if (idx == 0 && (ints & (DXEPINT_SETUP | DXEPINT_SETUP_RCVD))) + ints &= ~DXEPINT_XFERCOMPL; + if (ints & DXEPINT_XFERCOMPL) { if (hs_ep->isochronous && hs_ep->interval == 1) { if (ctrl & DXEPCTL_EOFRNUM) diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 51248b9..d018ebe 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -541,6 +541,7 @@ #define DIEPINT(_a) HSOTG_REG(0x908 + ((_a) * 0x20)) #define DOEPINT(_a) HSOTG_REG(0xB08 + ((_a) * 0x20)) +#define DXEPINT_SETUP_RCVD (1 << 15) #define DXEPINT_INEPNAKEFF (1 << 6) #define DXEPINT_BACK2BACKSETUP (1 << 6) #define DXEPINT_INTKNEPMIS (1 << 5) -- cgit v0.10.2 From 3f95001db7998c350422708a437694dda07bc04e Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:44 +0100 Subject: usb: dwc2: gadget: don't embed ep0 buffers When using DMA, data of the previous setup packet can be read back from cache because ep0 and ctrl buffers are embedded in struct s3c_hsotg. Allocate buffers instead of embedding them. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Reviewed-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 0d2ee29..7db83d0 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -434,6 +434,9 @@ struct dwc2_hw_params { u32 snpsid; }; +/* Size of control and EP0 buffers */ +#define DWC2_CTRL_BUFF_SIZE 8 + /** * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic * and periodic schedules @@ -684,8 +687,8 @@ struct dwc2_hsotg { struct usb_request *ep0_reply; struct usb_request *ctrl_req; - u8 ep0_buff[8]; - u8 ctrl_buff[8]; + void *ep0_buff; + void *ctrl_buff; struct usb_gadget gadget; unsigned int enabled:1; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 45afbf8..35d346f 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3486,6 +3486,22 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) s3c_hsotg_hw_cfg(hsotg); s3c_hsotg_init(hsotg); + hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, + DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); + if (!hsotg->ctrl_buff) { + dev_err(dev, "failed to allocate ctrl request buff\n"); + ret = -ENOMEM; + goto err_supplies; + } + + hsotg->ep0_buff = devm_kzalloc(hsotg->dev, + DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); + if (!hsotg->ep0_buff) { + dev_err(dev, "failed to allocate ctrl reply buff\n"); + ret = -ENOMEM; + goto err_supplies; + } + ret = devm_request_irq(hsotg->dev, irq, s3c_hsotg_irq, IRQF_SHARED, dev_name(hsotg->dev), hsotg); if (ret < 0) { -- cgit v0.10.2 From c139ec27f9c77c7c215d7989f6d77e2dbaba63df Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:45 +0100 Subject: usb: dwc2: gadget: fix error path in dwc2_gadget_init In the error path, s3c_hsotg_phy_disable should be called after a call to s3c_hsotg_phy_enable is made. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 35d346f..a4d2c26 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3476,7 +3476,7 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) if (ret) { dev_err(dev, "failed to enable supplies: %d\n", ret); - goto err_supplies; + goto err_clk; } /* usb phy enable */ @@ -3510,7 +3510,7 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); dev_err(dev, "cannot claim IRQ for gadget\n"); - goto err_clk; + goto err_supplies; } /* hsotg->num_of_eps holds number of EPs other than ep0 */ -- cgit v0.10.2 From 5f05048e5473a0570b1db9c2828d2a469e8bbbee Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:46 +0100 Subject: usb: dwc2: gadget: write correct value in ahbcfg register HBstLen is GAHBCFG[4:1]. Use GAHBCFG_HBSTLEN_SHIFT to write burst- length at correct position. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index a4d2c26..b69b8fa8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2160,7 +2160,7 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) if (using_dma(hsotg)) writel(GAHBCFG_GLBL_INTR_EN | GAHBCFG_DMA_EN | - GAHBCFG_HBSTLEN_INCR4, + (GAHBCFG_HBSTLEN_INCR4 << GAHBCFG_HBSTLEN_SHIFT), hsotg->regs + GAHBCFG); else writel(((hsotg->dedicated_fifos) ? (GAHBCFG_NP_TXF_EMP_LVL | -- cgit v0.10.2 From f5090044763fd954a002a6ef466fcbc21033518f Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:47 +0100 Subject: usb: dwc2: gadget: don't erase gahbcfg register when enabling dma Do a read-modify-write instead of only setting DMAEn bit. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b69b8fa8..b98622f 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2827,8 +2827,8 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) writel(GUSBCFG_PHYIF16 | GUSBCFG_TOUTCAL(7) | (0x5 << 10), hsotg->regs + GUSBCFG); - writel(using_dma(hsotg) ? GAHBCFG_DMA_EN : 0x0, - hsotg->regs + GAHBCFG); + if (using_dma(hsotg)) + __orr32(hsotg->regs + GAHBCFG, GAHBCFG_DMA_EN); } /** -- cgit v0.10.2 From edd74be83047f439e9507cedbbbef0891f3a9a48 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:48 +0100 Subject: usb: dwc2: gadget: add device tree property to enable dma * Add an of specific function to parse device node properties. * Enable dma usage only if device tree property 'g_use_dma' is present. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 7db83d0..376a008 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -563,6 +563,7 @@ struct dwc2_hw_params { * @setup: NAK management for EP0 SETUP * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework + * @g_using_dma: Indicate if dma usage is enabled */ struct dwc2_hsotg { struct device *dev; @@ -696,6 +697,7 @@ struct dwc2_hsotg { unsigned int setup:1; unsigned long last_rst; struct s3c_hsotg_ep *eps; + u32 g_using_dma; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b98622f..c24cb3b 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -85,11 +85,11 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg); * a core reset. This means we either need to fix the gadgets to take * account of DMA alignment, or add bounce buffers (yuerk). * - * Until this issue is sorted out, we always return 'false'. + * g_using_dma is set depending on dts flag. */ static inline bool using_dma(struct dwc2_hsotg *hsotg) { - return false; /* support is not complete */ + return hsotg->g_using_dma; } /** @@ -3402,6 +3402,18 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) debugfs_remove(hsotg->debug_root); } +#ifdef CONFIG_OF +static void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) +{ + struct device_node *np = hsotg->dev->of_node; + + /* Enable dma if requested in device tree */ + hsotg->g_using_dma = of_property_read_bool(np, "g-use-dma"); +} +#else +static inline void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) { } +#endif + /** * dwc2_gadget_init - init function for gadget * @dwc2: The data structure for the DWC2 driver. @@ -3419,6 +3431,8 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) /* Set default UTMI width */ hsotg->phyif = GUSBCFG_PHYIF16; + s3c_hsotg_of_probe(hsotg); + /* * If platform probe couldn't find a generic PHY or an old style * USB PHY, fall back to pdata -- cgit v0.10.2 From b2f93ef30fc64dab53f64bcabe23d9377abf454e Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:49 +0100 Subject: Documentation: dt-bindings: add dt binding info for dwc2 g-use-dma Indicate if gadget driver must use dma for usb transfers. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/dwc2.txt b/Documentation/devicetree/bindings/usb/dwc2.txt index 482f815..a7a9cb6 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.txt +++ b/Documentation/devicetree/bindings/usb/dwc2.txt @@ -20,6 +20,7 @@ Optional properties: Refer to phy/phy-bindings.txt for generic phy consumer properties - dr_mode: shall be one of "host", "peripheral" and "otg" Refer to usb/generic.txt +- g-use-dma: enable dma usage in gadget driver. Example: -- cgit v0.10.2 From c6f5c050e2a7f6776ffa5594b6b2eedbf0137fe8 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:50 +0100 Subject: usb: dwc2: gadget: add bi-directional endpoint support GHWCFG1 provides hardware configuration of each endpoint. Use it to configure the endpoints instead of assuming all even endpoint are OUT and all odd endpoints are IN. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 376a008..7a0309d 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -696,7 +696,8 @@ struct dwc2_hsotg { unsigned int connected:1; unsigned int setup:1; unsigned long last_rst; - struct s3c_hsotg_ep *eps; + struct s3c_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; + struct s3c_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; u32 g_using_dma; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c24cb3b..530c246 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -65,6 +65,15 @@ static inline void __bic32(void __iomem *ptr, u32 val) writel(readl(ptr) & ~val, ptr); } +static inline struct s3c_hsotg_ep *index_to_ep(struct dwc2_hsotg *hsotg, + u32 ep_index, u32 dir_in) +{ + if (dir_in) + return hsotg->eps_in[ep_index]; + else + return hsotg->eps_out[ep_index]; +} + /* forward declaration of functions */ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg); @@ -819,7 +828,7 @@ static void s3c_hsotg_complete_oursetup(struct usb_ep *ep, static struct s3c_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, u32 windex) { - struct s3c_hsotg_ep *ep = &hsotg->eps[windex & 0x7F]; + struct s3c_hsotg_ep *ep; int dir = (windex & USB_DIR_IN) ? 1 : 0; int idx = windex & 0x7F; @@ -829,6 +838,8 @@ static struct s3c_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, if (idx > hsotg->num_of_eps) return NULL; + ep = index_to_ep(hsotg, idx, dir); + if (idx && ep->dir_in != dir) return NULL; @@ -889,7 +900,7 @@ static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, static int s3c_hsotg_process_req_status(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; struct s3c_hsotg_ep *ep; __le16 reply; int ret; @@ -960,7 +971,7 @@ static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; struct s3c_hsotg_req *hs_req; bool restart; bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); @@ -1040,7 +1051,7 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg); */ static void s3c_hsotg_stall_ep0(struct dwc2_hsotg *hsotg) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; u32 reg; u32 ctrl; @@ -1080,7 +1091,7 @@ static void s3c_hsotg_stall_ep0(struct dwc2_hsotg *hsotg) static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; int ret = 0; u32 dcfg; @@ -1201,9 +1212,9 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) return; } - hsotg->eps[0].dir_in = 0; + hsotg->eps_out[0]->dir_in = 0; - ret = s3c_hsotg_ep_queue(&hsotg->eps[0].ep, req, GFP_ATOMIC); + ret = s3c_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC); if (ret < 0) { dev_err(hsotg->dev, "%s: failed queue (%d)\n", __func__, ret); /* @@ -1293,7 +1304,7 @@ static void s3c_hsotg_complete_request(struct dwc2_hsotg *hsotg, */ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) { - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep_idx]; + struct s3c_hsotg_ep *hs_ep = hsotg->eps_out[ep_idx]; struct s3c_hsotg_req *hs_req = hs_ep->req; void __iomem *fifo = hsotg->regs + EPFIFO(ep_idx); int to_read; @@ -1367,13 +1378,14 @@ static void s3c_hsotg_send_zlp(struct dwc2_hsotg *hsotg, } if (req->req.length == 0) { - hsotg->eps[0].sent_zlp = 1; + hsotg->eps_out[0]->sent_zlp = 1; s3c_hsotg_enqueue_setup(hsotg); return; } - hsotg->eps[0].dir_in = 1; - hsotg->eps[0].sent_zlp = 1; + /* eps_out[0] is used in both directions */ + hsotg->eps_out[0]->dir_in = 1; + hsotg->eps_out[0]->sent_zlp = 1; dev_dbg(hsotg->dev, "sending zero-length packet\n"); @@ -1402,7 +1414,7 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum, bool was_setup) { u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[epnum]; + struct s3c_hsotg_ep *hs_ep = hsotg->eps_out[epnum]; struct s3c_hsotg_req *hs_req = hs_ep->req; struct usb_request *req = &hs_req->req; unsigned size_left = DXEPTSIZ_XFERSIZE_GET(epsize); @@ -1591,14 +1603,18 @@ static u32 s3c_hsotg_ep0_mps(unsigned int mps) * the hardware control registers to reflect this. */ static void s3c_hsotg_set_ep_maxpacket(struct dwc2_hsotg *hsotg, - unsigned int ep, unsigned int mps) + unsigned int ep, unsigned int mps, unsigned int dir_in) { - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep]; + struct s3c_hsotg_ep *hs_ep; void __iomem *regs = hsotg->regs; u32 mpsval; u32 mcval; u32 reg; + hs_ep = index_to_ep(hsotg, ep, dir_in); + if (!hs_ep) + return; + if (ep == 0) { /* EP0 is a special case */ mpsval = s3c_hsotg_ep0_mps(mps); @@ -1617,17 +1633,12 @@ static void s3c_hsotg_set_ep_maxpacket(struct dwc2_hsotg *hsotg, hs_ep->ep.maxpacket = mpsval; } - /* - * update both the in and out endpoint controldir_ registers, even - * if one of the directions may not be in use. - */ - - reg = readl(regs + DIEPCTL(ep)); - reg &= ~DXEPCTL_MPS_MASK; - reg |= mpsval; - writel(reg, regs + DIEPCTL(ep)); - - if (ep) { + if (dir_in) { + reg = readl(regs + DIEPCTL(ep)); + reg &= ~DXEPCTL_MPS_MASK; + reg |= mpsval; + writel(reg, regs + DIEPCTL(ep)); + } else { reg = readl(regs + DOEPCTL(ep)); reg &= ~DXEPCTL_MPS_MASK; reg |= mpsval; @@ -1727,7 +1738,7 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, } /* Finish ZLP handling for IN EP0 transactions */ - if (hsotg->eps[0].sent_zlp) { + if (hsotg->eps_out[0]->sent_zlp) { dev_dbg(hsotg->dev, "zlp packet received\n"); s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); return; @@ -1794,7 +1805,7 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, int dir_in) { - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[idx]; + struct s3c_hsotg_ep *hs_ep = index_to_ep(hsotg, idx, dir_in); u32 epint_reg = dir_in ? DIEPINT(idx) : DOEPINT(idx); u32 epctl_reg = dir_in ? DIEPCTL(idx) : DOEPCTL(idx); u32 epsiz_reg = dir_in ? DIEPTSIZ(idx) : DOEPTSIZ(idx); @@ -1807,6 +1818,12 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, /* Clear endpoint interrupts */ writel(ints, hsotg->regs + epint_reg); + if (!hs_ep) { + dev_err(hsotg->dev, "%s:Interrupt for unconfigured ep%d(%s)\n", + __func__, idx, dir_in ? "in" : "out"); + return; + } + dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", __func__, idx, dir_in ? "in" : "out", ints); @@ -1973,9 +1990,15 @@ static void s3c_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) if (ep0_mps) { int i; - s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps); - for (i = 1; i < hsotg->num_of_eps; i++) - s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps); + /* Initialize ep0 for both in and out directions */ + s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps, 1); + s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps, 0); + for (i = 1; i < hsotg->num_of_eps; i++) { + if (hsotg->eps_in[i]) + s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps, 1); + if (hsotg->eps_out[i]) + s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps, 0); + } } /* ensure after enumeration our EP0 is active */ @@ -2032,8 +2055,15 @@ void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg) return; hsotg->connected = 0; - for (ep = 0; ep < hsotg->num_of_eps; ep++) - kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN); + + for (ep = 0; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + kill_all_requests(hsotg, hsotg->eps_in[ep], + -ESHUTDOWN); + if (hsotg->eps_out[ep]) + kill_all_requests(hsotg, hsotg->eps_out[ep], + -ESHUTDOWN); + } call_gadget(hsotg, disconnect); } @@ -2050,9 +2080,11 @@ static void s3c_hsotg_irq_fifoempty(struct dwc2_hsotg *hsotg, bool periodic) int epno, ret; /* look through for any more data to transmit */ - for (epno = 0; epno < hsotg->num_of_eps; epno++) { - ep = &hsotg->eps[epno]; + ep = index_to_ep(hsotg, epno, 1); + + if (!ep) + continue; if (!ep->dir_in) continue; @@ -2227,13 +2259,13 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | DXEPTSIZ_XFERSIZE(8), hsotg->regs + DOEPTSIZ0); - writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + writel(s3c_hsotg_ep0_mps(hsotg->eps_out[0]->ep.maxpacket) | DXEPCTL_CNAK | DXEPCTL_EPENA | DXEPCTL_USBACTEP, hsotg->regs + DOEPCTL0); /* enable, but don't activate EP0in */ - writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + writel(s3c_hsotg_ep0_mps(hsotg->eps_out[0]->ep.maxpacket) | DXEPCTL_USBACTEP, hsotg->regs + DIEPCTL0); s3c_hsotg_enqueue_setup(hsotg); @@ -2330,7 +2362,7 @@ irq_retry: if (time_after(jiffies, hsotg->last_rst + msecs_to_jiffies(200))) { - kill_all_requests(hsotg, &hsotg->eps[0], + kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); s3c_hsotg_core_init_disconnected(hsotg); @@ -2479,7 +2511,7 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, epctrl |= DXEPCTL_SNAK; /* update the endpoint state */ - s3c_hsotg_set_ep_maxpacket(hsotg, hs_ep->index, mps); + s3c_hsotg_set_ep_maxpacket(hsotg, hs_ep->index, mps, dir_in); /* default, set to non-periodic */ hs_ep->isochronous = 0; @@ -2576,7 +2608,7 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) dev_dbg(hsotg->dev, "%s(ep %p)\n", __func__, ep); - if (ep == &hsotg->eps[0].ep) { + if (ep == &hsotg->eps_out[0]->ep) { dev_err(hsotg->dev, "%s: called for ep0\n", __func__); return -EINVAL; } @@ -2675,40 +2707,39 @@ static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) return 0; } - /* write both IN and OUT control registers */ - - epreg = DIEPCTL(index); - epctl = readl(hs->regs + epreg); - - if (value) { - epctl |= DXEPCTL_STALL + DXEPCTL_SNAK; - if (epctl & DXEPCTL_EPENA) - epctl |= DXEPCTL_EPDIS; + if (hs_ep->dir_in) { + epreg = DIEPCTL(index); + epctl = readl(hs->regs + epreg); + + if (value) { + epctl |= DXEPCTL_STALL + DXEPCTL_SNAK; + if (epctl & DXEPCTL_EPENA) + epctl |= DXEPCTL_EPDIS; + } else { + epctl &= ~DXEPCTL_STALL; + xfertype = epctl & DXEPCTL_EPTYPE_MASK; + if (xfertype == DXEPCTL_EPTYPE_BULK || + xfertype == DXEPCTL_EPTYPE_INTERRUPT) + epctl |= DXEPCTL_SETD0PID; + } + writel(epctl, hs->regs + epreg); } else { - epctl &= ~DXEPCTL_STALL; - xfertype = epctl & DXEPCTL_EPTYPE_MASK; - if (xfertype == DXEPCTL_EPTYPE_BULK || - xfertype == DXEPCTL_EPTYPE_INTERRUPT) - epctl |= DXEPCTL_SETD0PID; - } - - writel(epctl, hs->regs + epreg); - epreg = DOEPCTL(index); - epctl = readl(hs->regs + epreg); + epreg = DOEPCTL(index); + epctl = readl(hs->regs + epreg); - if (value) - epctl |= DXEPCTL_STALL; - else { - epctl &= ~DXEPCTL_STALL; - xfertype = epctl & DXEPCTL_EPTYPE_MASK; - if (xfertype == DXEPCTL_EPTYPE_BULK || - xfertype == DXEPCTL_EPTYPE_INTERRUPT) - epctl |= DXEPCTL_SETD0PID; + if (value) + epctl |= DXEPCTL_STALL; + else { + epctl &= ~DXEPCTL_STALL; + xfertype = epctl & DXEPCTL_EPTYPE_MASK; + if (xfertype == DXEPCTL_EPTYPE_BULK || + xfertype == DXEPCTL_EPTYPE_INTERRUPT) + epctl |= DXEPCTL_SETD0PID; + } + writel(epctl, hs->regs + epreg); } - writel(epctl, hs->regs + epreg); - hs_ep->halted = value; return 0; @@ -2922,8 +2953,12 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget) mutex_lock(&hsotg->init_mutex); /* all endpoints should be shutdown */ - for (ep = 1; ep < hsotg->num_of_eps; ep++) - s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + for (ep = 1; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); + } spin_lock_irqsave(&hsotg->lock, flags); @@ -3009,19 +3044,19 @@ static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { */ static void s3c_hsotg_initep(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, - int epnum) + int epnum, + bool dir_in) { char *dir; if (epnum == 0) dir = ""; - else if ((epnum % 2) == 0) { - dir = "out"; - } else { + else if (dir_in) dir = "in"; - hs_ep->dir_in = 1; - } + else + dir = "out"; + hs_ep->dir_in = dir_in; hs_ep->index = epnum; snprintf(hs_ep->name, sizeof(hs_ep->name), "ep%d%s", epnum, dir); @@ -3045,8 +3080,10 @@ static void s3c_hsotg_initep(struct dwc2_hsotg *hsotg, if (using_dma(hsotg)) { u32 next = DXEPCTL_NEXTEP((epnum + 1) % 15); - writel(next, hsotg->regs + DIEPCTL(epnum)); - writel(next, hsotg->regs + DOEPCTL(epnum)); + if (dir_in) + writel(next, hsotg->regs + DIEPCTL(epnum)); + else + writel(next, hsotg->regs + DOEPCTL(epnum)); } } @@ -3056,24 +3093,56 @@ static void s3c_hsotg_initep(struct dwc2_hsotg *hsotg, * * Read the USB core HW configuration registers */ -static void s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) +static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) { - u32 cfg2, cfg3, cfg4; + u32 cfg; + u32 ep_type; + u32 i; + /* check hardware configuration */ - cfg2 = readl(hsotg->regs + 0x48); - hsotg->num_of_eps = (cfg2 >> 10) & 0xF; + cfg = readl(hsotg->regs + GHWCFG2); + hsotg->num_of_eps = (cfg >> 10) & 0xF; + /* Add ep0 */ + hsotg->num_of_eps++; - cfg3 = readl(hsotg->regs + 0x4C); - hsotg->fifo_mem = (cfg3 >> 16); + hsotg->eps_in[0] = devm_kzalloc(hsotg->dev, sizeof(struct s3c_hsotg_ep), + GFP_KERNEL); + if (!hsotg->eps_in[0]) + return -ENOMEM; + /* Same s3c_hsotg_ep is used in both directions for ep0 */ + hsotg->eps_out[0] = hsotg->eps_in[0]; + + cfg = readl(hsotg->regs + GHWCFG1); + for (i = 1; i < hsotg->num_of_eps; i++, cfg >>= 2) { + ep_type = cfg & 3; + /* Direction in or both */ + if (!(ep_type & 2)) { + hsotg->eps_in[i] = devm_kzalloc(hsotg->dev, + sizeof(struct s3c_hsotg_ep), GFP_KERNEL); + if (!hsotg->eps_in[i]) + return -ENOMEM; + } + /* Direction out or both */ + if (!(ep_type & 1)) { + hsotg->eps_out[i] = devm_kzalloc(hsotg->dev, + sizeof(struct s3c_hsotg_ep), GFP_KERNEL); + if (!hsotg->eps_out[i]) + return -ENOMEM; + } + } + + cfg = readl(hsotg->regs + GHWCFG3); + hsotg->fifo_mem = (cfg >> 16); - cfg4 = readl(hsotg->regs + 0x50); - hsotg->dedicated_fifos = (cfg4 >> 25) & 1; + cfg = readl(hsotg->regs + GHWCFG4); + hsotg->dedicated_fifos = (cfg >> 25) & 1; dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", hsotg->num_of_eps, hsotg->dedicated_fifos ? "dedicated" : "shared", hsotg->fifo_mem); + return 0; } /** @@ -3368,17 +3437,33 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) if (IS_ERR(hsotg->debug_fifo)) dev_err(hsotg->dev, "%s: failed to create fifo\n", __func__); - /* create one file for each endpoint */ - + /* Create one file for each out endpoint */ for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { - struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; + struct s3c_hsotg_ep *ep; - ep->debugfs = debugfs_create_file(ep->name, 0444, - root, ep, &ep_fops); + ep = hsotg->eps_out[epidx]; + if (ep) { + ep->debugfs = debugfs_create_file(ep->name, 0444, + root, ep, &ep_fops); - if (IS_ERR(ep->debugfs)) - dev_err(hsotg->dev, "failed to create %s debug file\n", - ep->name); + if (IS_ERR(ep->debugfs)) + dev_err(hsotg->dev, "failed to create %s debug file\n", + ep->name); + } + } + /* Create one file for each in endpoint. EP0 is handled with out eps */ + for (epidx = 1; epidx < hsotg->num_of_eps; epidx++) { + struct s3c_hsotg_ep *ep; + + ep = hsotg->eps_in[epidx]; + if (ep) { + ep->debugfs = debugfs_create_file(ep->name, 0444, + root, ep, &ep_fops); + + if (IS_ERR(ep->debugfs)) + dev_err(hsotg->dev, "failed to create %s debug file\n", + ep->name); + } } } @@ -3393,8 +3478,10 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) unsigned epidx; for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { - struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; - debugfs_remove(ep->debugfs); + if (hsotg->eps_in[epidx]) + debugfs_remove(hsotg->eps_in[epidx]->debugfs); + if (hsotg->eps_out[epidx]) + debugfs_remove(hsotg->eps_out[epidx]->debugfs); } debugfs_remove(hsotg->debug_file); @@ -3423,7 +3510,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) { struct device *dev = hsotg->dev; struct s3c_hsotg_plat *plat = dev->platform_data; - struct s3c_hsotg_ep *eps; int epnum; int ret; int i; @@ -3497,7 +3583,12 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) s3c_hsotg_phy_enable(hsotg); s3c_hsotg_corereset(hsotg); - s3c_hsotg_hw_cfg(hsotg); + ret = s3c_hsotg_hw_cfg(hsotg); + if (ret) { + dev_err(hsotg->dev, "Hardware configuration failed: %d\n", ret); + goto err_clk; + } + s3c_hsotg_init(hsotg); hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, @@ -3535,33 +3626,30 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) goto err_supplies; } - eps = kcalloc(hsotg->num_of_eps + 1, sizeof(struct s3c_hsotg_ep), - GFP_KERNEL); - if (!eps) { - ret = -ENOMEM; - goto err_supplies; - } - - hsotg->eps = eps; - /* setup endpoint information */ INIT_LIST_HEAD(&hsotg->gadget.ep_list); - hsotg->gadget.ep0 = &hsotg->eps[0].ep; + hsotg->gadget.ep0 = &hsotg->eps_out[0]->ep; /* allocate EP0 request */ - hsotg->ctrl_req = s3c_hsotg_ep_alloc_request(&hsotg->eps[0].ep, + hsotg->ctrl_req = s3c_hsotg_ep_alloc_request(&hsotg->eps_out[0]->ep, GFP_KERNEL); if (!hsotg->ctrl_req) { dev_err(dev, "failed to allocate ctrl req\n"); ret = -ENOMEM; - goto err_ep_mem; + goto err_supplies; } /* initialise the endpoints now the core has been initialised */ - for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) - s3c_hsotg_initep(hsotg, &hsotg->eps[epnum], epnum); + for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) { + if (hsotg->eps_in[epnum]) + s3c_hsotg_initep(hsotg, hsotg->eps_in[epnum], + epnum, 1); + if (hsotg->eps_out[epnum]) + s3c_hsotg_initep(hsotg, hsotg->eps_out[epnum], + epnum, 0); + } /* disable power and clock */ s3c_hsotg_phy_disable(hsotg); @@ -3570,12 +3658,12 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) hsotg->supplies); if (ret) { dev_err(dev, "failed to disable supplies: %d\n", ret); - goto err_ep_mem; + goto err_supplies; } ret = usb_add_gadget_udc(dev, &hsotg->gadget); if (ret) - goto err_ep_mem; + goto err_supplies; s3c_hsotg_create_debug(hsotg); @@ -3583,8 +3671,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) return 0; -err_ep_mem: - kfree(eps); err_supplies: s3c_hsotg_phy_disable(hsotg); err_clk: @@ -3630,8 +3716,12 @@ int s3c_hsotg_suspend(struct dwc2_hsotg *hsotg) s3c_hsotg_phy_disable(hsotg); - for (ep = 0; ep < hsotg->num_of_eps; ep++) - s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + for (ep = 0; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); + } ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); -- cgit v0.10.2 From cec87f1d2686057654e4565b8eaab4f539e9afca Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:51 +0100 Subject: usb: dwc2: gadget: check interrupts for all endpoints Current code does not check endpoint 15 interrupt. Use number of endpoint configured in hardware instead of the hardcoded value. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 530c246..b7879fd 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2337,12 +2337,14 @@ irq_retry: dev_dbg(hsotg->dev, "%s: daint=%08x\n", __func__, daint); - for (ep = 0; ep < 15 && daint_out; ep++, daint_out >>= 1) { + for (ep = 0; ep < hsotg->num_of_eps && daint_out; + ep++, daint_out >>= 1) { if (daint_out & 1) s3c_hsotg_epint(hsotg, ep, 0); } - for (ep = 0; ep < 15 && daint_in; ep++, daint_in >>= 1) { + for (ep = 0; ep < hsotg->num_of_eps && daint_in; + ep++, daint_in >>= 1) { if (daint_in & 1) s3c_hsotg_epint(hsotg, ep, 1); } -- cgit v0.10.2 From 0a176279db6817cf865ed4c685e4a414be8226fd Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:52 +0100 Subject: usb: dwc2: gadget: configure fifos from device tree As fifo size can vary between SOCs, add possibility to configure them from device tree. Fifo sizes used by the legacy driver will be used If they are not provided by the device tree. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Reviewed-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 7a0309d..df6a64f 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -193,6 +193,13 @@ enum dwc2_lx_state { DWC2_L3, /* Off state */ }; +/* + * Gadget periodic tx fifo sizes as used by legacy driver + * EP0 is not included + */ +#define DWC2_G_P_LEGACY_TX_FIFO_SIZE {256, 256, 256, 256, 768, 768, 768, \ + 768, 0, 0, 0, 0, 0, 0, 0} + /** * struct dwc2_core_params - Parameters for configuring the core * @@ -564,6 +571,9 @@ struct dwc2_hw_params { * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework * @g_using_dma: Indicate if dma usage is enabled + * @g_rx_fifo_sz: Contains rx fifo size value + * @g_np_g_tx_fifo_sz: Contains Non-Periodic tx fifo size value + * @g_tx_fifo_sz: Contains tx fifo size value per endpoints */ struct dwc2_hsotg { struct device *dev; @@ -699,6 +709,9 @@ struct dwc2_hsotg { struct s3c_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; struct s3c_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; u32 g_using_dma; + u32 g_rx_fifo_sz; + u32 g_np_g_tx_fifo_sz; + u32 g_tx_fifo_sz[MAX_EPS_CHANNELS]; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b7879fd..c43ce7e 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -174,15 +174,14 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) { unsigned int ep; unsigned int addr; - unsigned int size; int timeout; u32 val; - /* set FIFO sizes to 2048/1024 */ - - writel(2048, hsotg->regs + GRXFSIZ); - writel((2048 << FIFOSIZE_STARTADDR_SHIFT) | - (1024 << FIFOSIZE_DEPTH_SHIFT), hsotg->regs + GNPTXFSIZ); + /* set RX/NPTX FIFO sizes */ + writel(hsotg->g_rx_fifo_sz, hsotg->regs + GRXFSIZ); + writel((hsotg->g_rx_fifo_sz << FIFOSIZE_STARTADDR_SHIFT) | + (hsotg->g_np_g_tx_fifo_sz << FIFOSIZE_DEPTH_SHIFT), + hsotg->regs + GNPTXFSIZ); /* * arange all the rest of the TX FIFOs, as some versions of this @@ -192,35 +191,21 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) */ /* start at the end of the GNPTXFSIZ, rounded up */ - addr = 2048 + 1024; + addr = hsotg->g_rx_fifo_sz + hsotg->g_np_g_tx_fifo_sz; /* - * Because we have not enough memory to have each TX FIFO of size at - * least 3072 bytes (the maximum single packet size), we create four - * FIFOs of lenght 1024, and four of length 3072 bytes, and assing + * Configure fifos sizes from provided configuration and assign * them to endpoints dynamically according to maxpacket size value of * given endpoint. */ - - /* 256*4=1024 bytes FIFO length */ - size = 256; - for (ep = 1; ep <= 4; ep++) { - val = addr; - val |= size << FIFOSIZE_DEPTH_SHIFT; - WARN_ONCE(addr + size > hsotg->fifo_mem, - "insufficient fifo memory"); - addr += size; - - writel(val, hsotg->regs + DPTXFSIZN(ep)); - } - /* 768*4=3072 bytes FIFO length */ - size = 768; - for (ep = 5; ep <= 8; ep++) { + for (ep = 1; ep < MAX_EPS_CHANNELS; ep++) { + if (!hsotg->g_tx_fifo_sz[ep]) + continue; val = addr; - val |= size << FIFOSIZE_DEPTH_SHIFT; - WARN_ONCE(addr + size > hsotg->fifo_mem, + val |= hsotg->g_tx_fifo_sz[ep] << FIFOSIZE_DEPTH_SHIFT; + WARN_ONCE(addr + hsotg->g_tx_fifo_sz[ep] > hsotg->fifo_mem, "insufficient fifo memory"); - addr += size; + addr += hsotg->g_tx_fifo_sz[ep]; writel(val, hsotg->regs + DPTXFSIZN(ep)); } @@ -3495,9 +3480,42 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) static void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) { struct device_node *np = hsotg->dev->of_node; + u32 len = 0; + u32 i = 0; /* Enable dma if requested in device tree */ hsotg->g_using_dma = of_property_read_bool(np, "g-use-dma"); + + /* + * Register TX periodic fifo size per endpoint. + * EP0 is excluded since it has no fifo configuration. + */ + if (!of_find_property(np, "g-tx-fifo-size", &len)) + goto rx_fifo; + + len /= sizeof(u32); + + /* Read tx fifo sizes other than ep0 */ + if (of_property_read_u32_array(np, "g-tx-fifo-size", + &hsotg->g_tx_fifo_sz[1], len)) + goto rx_fifo; + + /* Add ep0 */ + len++; + + /* Make remaining TX fifos unavailable */ + if (len < MAX_EPS_CHANNELS) { + for (i = len; i < MAX_EPS_CHANNELS; i++) + hsotg->g_tx_fifo_sz[i] = 0; + } + +rx_fifo: + /* Register RX fifo size */ + of_property_read_u32(np, "g-rx-fifo-size", &hsotg->g_rx_fifo_sz); + + /* Register NPTX fifo size */ + of_property_read_u32(np, "g-np-tx-fifo-size", + &hsotg->g_np_g_tx_fifo_sz); } #else static inline void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) { } @@ -3515,12 +3533,26 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) int epnum; int ret; int i; + u32 p_tx_fifo[] = DWC2_G_P_LEGACY_TX_FIFO_SIZE; /* Set default UTMI width */ hsotg->phyif = GUSBCFG_PHYIF16; s3c_hsotg_of_probe(hsotg); + /* Initialize to legacy fifo configuration values */ + hsotg->g_rx_fifo_sz = 2048; + hsotg->g_np_g_tx_fifo_sz = 1024; + memcpy(&hsotg->g_tx_fifo_sz[1], p_tx_fifo, sizeof(p_tx_fifo)); + /* Device tree specific probe */ + s3c_hsotg_of_probe(hsotg); + /* Dump fifo information */ + dev_dbg(dev, "NonPeriodic TXFIFO size: %d\n", + hsotg->g_np_g_tx_fifo_sz); + dev_dbg(dev, "RXFIFO size: %d\n", hsotg->g_rx_fifo_sz); + for (i = 0; i < MAX_EPS_CHANNELS; i++) + dev_dbg(dev, "Periodic TXFIFO%2d size: %d\n", i, + hsotg->g_tx_fifo_sz[i]); /* * If platform probe couldn't find a generic PHY or an old style * USB PHY, fall back to pdata -- cgit v0.10.2 From bb8693a98ebbe863401108d1cc4f8c0bc72e1900 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:53 +0100 Subject: Documentation: dt-bindings: add dt binding info for dwc2 fifo resizing Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/dwc2.txt b/Documentation/devicetree/bindings/usb/dwc2.txt index a7a9cb6..fd132cb 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.txt +++ b/Documentation/devicetree/bindings/usb/dwc2.txt @@ -21,6 +21,9 @@ Refer to phy/phy-bindings.txt for generic phy consumer properties - dr_mode: shall be one of "host", "peripheral" and "otg" Refer to usb/generic.txt - g-use-dma: enable dma usage in gadget driver. +- g-rx-fifo-size: size of rx fifo size in gadget mode. +- g-np-tx-fifo-size: size of non-periodic tx fifo size in gadget mode. +- g-tx-fifo-size: size of periodic tx fifo per endpoint (except ep0) in gadget mode. Example: -- cgit v0.10.2 From ea5a8774a273c62047abb3cb5cca69c8368d05da Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:54 +0100 Subject: usb: dwc2: gadget: remove unused members from hsotg_req These members are only occupying space. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index df6a64f..db5348c 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -158,14 +158,10 @@ struct s3c_hsotg_ep { * struct s3c_hsotg_req - data transfer request * @req: The USB gadget request * @queue: The list of requests for the endpoint this is queued for. - * @in_progress: Has already had size/packets written to core - * @mapped: DMA buffer for this request has been mapped via dma_map_single(). */ struct s3c_hsotg_req { struct usb_request req; struct list_head queue; - unsigned char in_progress; - unsigned char mapped; }; #if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) -- cgit v0.10.2 From 364f8e933869699167db935e13b41b1d92ab369d Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:55 +0100 Subject: usb: dwc2: gadget: fix debug loop limits < 15 check doesn't show debug information for endpoint 15. It is possible to have less than 15 endpoints so use limit provided by hardware configuration. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c43ce7e..7dfda95 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3156,14 +3156,14 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg) /* show periodic fifo settings */ - for (idx = 1; idx <= 15; idx++) { + for (idx = 1; idx < hsotg->num_of_eps; idx++) { val = readl(regs + DPTXFSIZN(idx)); dev_info(dev, "DPTx[%d] FSize=%d, StAddr=0x%08x\n", idx, val >> FIFOSIZE_DEPTH_SHIFT, val & FIFOSIZE_STARTADDR_MASK); } - for (idx = 0; idx < 15; idx++) { + for (idx = 0; idx < hsotg->num_of_eps; idx++) { dev_info(dev, "ep%d-in: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", idx, readl(regs + DIEPCTL(idx)), @@ -3221,7 +3221,7 @@ static int state_show(struct seq_file *seq, void *v) seq_puts(seq, "\nEndpoint status:\n"); - for (idx = 0; idx < 15; idx++) { + for (idx = 0; idx < hsotg->num_of_eps; idx++) { u32 in, out; in = readl(regs + DIEPCTL(idx)); @@ -3280,7 +3280,7 @@ static int fifo_show(struct seq_file *seq, void *v) seq_puts(seq, "\nPeriodic TXFIFOs:\n"); - for (idx = 1; idx <= 15; idx++) { + for (idx = 1; idx < hsotg->num_of_eps; idx++) { val = readl(regs + DPTXFSIZN(idx)); seq_printf(seq, "\tDPTXFIFO%2d: Size %d, Start 0x%08x\n", idx, -- cgit v0.10.2 From 5f2196bd7c72cbead9d47da32816dd69a06b692e Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:56 +0100 Subject: usb: dwc2: gadget: consider all tx fifos When matching tx fifo to endpoint, consider all fifos instead of hard limiting to 8 Moreover, print error in case no fifo could be found. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 7dfda95..cd8880d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2540,7 +2540,7 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, */ if (dir_in && hsotg->dedicated_fifos) { size = hs_ep->ep.maxpacket*hs_ep->mc; - for (i = 1; i <= 8; ++i) { + for (i = 1; i < hsotg->num_of_eps; ++i) { if (hsotg->fifo_map & (1<regs + DPTXFSIZN(i)); @@ -2554,7 +2554,9 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, hs_ep->fifo_size = val; break; } - if (i == 8) { + if (i == hsotg->num_of_eps) { + dev_err(hsotg->dev, + "%s: No suitable fifo found\n", __func__); ret = -ENOMEM; goto error; } -- cgit v0.10.2 From 1141ea01d5faa82f4910b4a8f896d10490860f24 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:57 +0100 Subject: usb: dwc2: gadget: kill requests after disabling ep kill_all_requests() can flush the fifo. Call it after disabling the endpoint. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index cd8880d..3dda17e 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2605,8 +2605,6 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); spin_lock_irqsave(&hsotg->lock, flags); - /* terminate all requests with shutdown */ - kill_all_requests(hsotg, hs_ep, -ESHUTDOWN); hsotg->fifo_map &= ~(1<fifo_index); hs_ep->fifo_index = 0; @@ -2623,6 +2621,9 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) /* disable endpoint interrupts */ s3c_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 0); + /* terminate all requests with shutdown */ + kill_all_requests(hsotg, hs_ep, -ESHUTDOWN); + spin_unlock_irqrestore(&hsotg->lock, flags); return 0; } -- cgit v0.10.2 From fe0b94abcdf6d80bd575e91baf1e0f85a4978d49 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:58 +0100 Subject: usb: dwc2: gadget: manage ep0 state in software Manage ep0 state in software to add handling of status OUT stage. Just toggling hsotg->setup in s3c_hsotg_handle_outdone leaves it in wrong state in 2-stage control transfers. Moreover, ensure that for setup-packet s3c_hsotg_handle_outdone is called either from SetupDone or OutDone but not both. Dwc2 ip v3.00a generates both SetupDone and OutDone on setup packets. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index db5348c..e963aef 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -196,6 +196,15 @@ enum dwc2_lx_state { #define DWC2_G_P_LEGACY_TX_FIFO_SIZE {256, 256, 256, 256, 768, 768, 768, \ 768, 0, 0, 0, 0, 0, 0, 0} +/* Gadget ep0 states */ +enum dwc2_ep0_state { + DWC2_EP0_SETUP, + DWC2_EP0_DATA_IN, + DWC2_EP0_DATA_OUT, + DWC2_EP0_STATUS_IN, + DWC2_EP0_STATUS_OUT, +}; + /** * struct dwc2_core_params - Parameters for configuring the core * @@ -563,7 +572,7 @@ struct dwc2_hw_params { * @ep0_buff: Buffer for EP0 reply data, if needed. * @ctrl_buff: Buffer for EP0 control requests. * @ctrl_req: Request for EP0 control packets. - * @setup: NAK management for EP0 SETUP + * @ep0_state: EP0 control transfers state * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework * @g_using_dma: Indicate if dma usage is enabled @@ -696,11 +705,11 @@ struct dwc2_hsotg { struct usb_request *ctrl_req; void *ep0_buff; void *ctrl_buff; + enum dwc2_ep0_state ep0_state; struct usb_gadget gadget; unsigned int enabled:1; unsigned int connected:1; - unsigned int setup:1; unsigned long last_rst; struct s3c_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; struct s3c_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 3dda17e..c3977a8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -638,15 +638,12 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ ctrl |= DXEPCTL_USBACTEP; - dev_dbg(hsotg->dev, "setup req:%d\n", hsotg->setup); + dev_dbg(hsotg->dev, "ep0 state:%d\n", hsotg->ep0_state); /* For Setup request do not clear NAK */ - if (hsotg->setup && index == 0) - hsotg->setup = 0; - else + if (!(index == 0 && hsotg->ep0_state == DWC2_EP0_SETUP)) ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ - dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); writel(ctrl, hsotg->regs + epctrl_reg); @@ -865,8 +862,6 @@ static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, if (length) memcpy(req->buf, buff, length); - else - ep->sent_zlp = 1; ret = s3c_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC); if (ret) { @@ -1080,26 +1075,20 @@ static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, int ret = 0; u32 dcfg; - ep0->sent_zlp = 0; - dev_dbg(hsotg->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", ctrl->bRequest, ctrl->bRequestType, ctrl->wValue, ctrl->wLength); - /* - * record the direction of the request, for later use when enquing - * packets onto EP0. - */ - - ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; - dev_dbg(hsotg->dev, "ctrl: dir_in=%d\n", ep0->dir_in); - - /* - * if we've no data with this request, then the last part of the - * transaction is going to implicitly be IN. - */ - if (ctrl->wLength == 0) + if (ctrl->wLength == 0) { + ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_STATUS_IN; + } else if (ctrl->bRequestType & USB_DIR_IN) { ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_DATA_IN; + } else { + ep0->dir_in = 0; + hsotg->ep0_state = DWC2_EP0_DATA_OUT; + } if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (ctrl->bRequest) { @@ -1198,6 +1187,8 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } hsotg->eps_out[0]->dir_in = 0; + hsotg->eps_out[0]->sent_zlp = 0; + hsotg->ep0_state = DWC2_EP0_SETUP; ret = s3c_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC); if (ret < 0) { @@ -1209,6 +1200,27 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } } +static void s3c_hsotg_program_zlp(struct dwc2_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep) +{ + u32 ctrl; + u8 index = hs_ep->index; + u32 epctl_reg = hs_ep->dir_in ? DIEPCTL(index) : DOEPCTL(index); + u32 epsiz_reg = hs_ep->dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); + + dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", index); + + writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | + DXEPTSIZ_XFERSIZE(0), hsotg->regs + + epsiz_reg); + + ctrl = readl(hsotg->regs + epctl_reg); + ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ + ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ + ctrl |= DXEPCTL_USBACTEP; + writel(ctrl, hsotg->regs + epctl_reg); +} + /** * s3c_hsotg_complete_request - complete a request given to us * @hsotg: The device state. @@ -1341,9 +1353,9 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) } /** - * s3c_hsotg_send_zlp - send zero-length packet on control endpoint + * s3c_hsotg_ep0_zlp - send/receive zero-length packet on control endpoint * @hsotg: The device instance - * @req: The request currently on this endpoint + * @dir_in: If IN zlp * * Generate a zero-length IN packet request for terminating a SETUP * transaction. @@ -1352,51 +1364,25 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) * currently believed that we do not need to wait for any space in * the TxFIFO. */ -static void s3c_hsotg_send_zlp(struct dwc2_hsotg *hsotg, - struct s3c_hsotg_req *req) +static void s3c_hsotg_ep0_zlp(struct dwc2_hsotg *hsotg, bool dir_in) { - u32 ctrl; - - if (!req) { - dev_warn(hsotg->dev, "%s: no request?\n", __func__); - return; - } - - if (req->req.length == 0) { - hsotg->eps_out[0]->sent_zlp = 1; - s3c_hsotg_enqueue_setup(hsotg); - return; - } - /* eps_out[0] is used in both directions */ - hsotg->eps_out[0]->dir_in = 1; - hsotg->eps_out[0]->sent_zlp = 1; - - dev_dbg(hsotg->dev, "sending zero-length packet\n"); - - /* issue a zero-sized packet to terminate this */ - writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | - DXEPTSIZ_XFERSIZE(0), hsotg->regs + DIEPTSIZ(0)); + hsotg->eps_out[0]->dir_in = dir_in; + hsotg->ep0_state = dir_in ? DWC2_EP0_STATUS_IN : DWC2_EP0_STATUS_OUT; - ctrl = readl(hsotg->regs + DIEPCTL0); - ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ - ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ - ctrl |= DXEPCTL_USBACTEP; - writel(ctrl, hsotg->regs + DIEPCTL0); + s3c_hsotg_program_zlp(hsotg, hsotg->eps_out[0]); } /** * s3c_hsotg_handle_outdone - handle receiving OutDone/SetupDone from RXFIFO * @hsotg: The device instance * @epnum: The endpoint received from - * @was_setup: Set if processing a SetupDone event. * * The RXFIFO has delivered an OutDone event, which means that the data * transfer for an OUT endpoint has been completed, either by a short * packet or by the finish of a transfer. */ -static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, - int epnum, bool was_setup) +static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum) { u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); struct s3c_hsotg_ep *hs_ep = hsotg->eps_out[epnum]; @@ -1410,6 +1396,13 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, return; } + if (epnum == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_OUT) { + dev_dbg(hsotg->dev, "zlp packet received\n"); + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + s3c_hsotg_enqueue_setup(hsotg); + return; + } + if (using_dma(hsotg)) { unsigned size_done; @@ -1432,12 +1425,6 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, if (req->actual < req->length && size_left == 0) { s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); return; - } else if (epnum == 0) { - /* - * After was_setup = 1 => - * set CNAK for non Setup requests - */ - hsotg->setup = was_setup ? 0 : 1; } if (req->actual < req->length && req->short_not_ok) { @@ -1450,13 +1437,10 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, */ } - if (epnum == 0) { - /* - * Condition req->complete != s3c_hsotg_complete_setup says: - * send ZLP when we have an asynchronous request from gadget - */ - if (!was_setup && req->complete != s3c_hsotg_complete_setup) - s3c_hsotg_send_zlp(hsotg, hs_req); + if (epnum == 0 && hsotg->ep0_state == DWC2_EP0_DATA_OUT) { + /* Move to STATUS IN */ + s3c_hsotg_ep0_zlp(hsotg, true); + return; } s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, result); @@ -1522,7 +1506,7 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) s3c_hsotg_read_frameno(hsotg)); if (!using_dma(hsotg)) - s3c_hsotg_handle_outdone(hsotg, epnum, false); + s3c_hsotg_handle_outdone(hsotg, epnum); break; case GRXSTS_PKTSTS_SETUPDONE: @@ -1530,8 +1514,13 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) "SetupDone (Frame=0x%08x, DOPEPCTL=0x%08x)\n", s3c_hsotg_read_frameno(hsotg), readl(hsotg->regs + DOEPCTL(0))); - - s3c_hsotg_handle_outdone(hsotg, epnum, true); + /* + * Call s3c_hsotg_handle_outdone here if it was not called from + * GRXSTS_PKTSTS_OUTDONE. That is, if the core didn't + * generate GRXSTS_PKTSTS_OUTDONE for setup packet. + */ + if (hsotg->ep0_state == DWC2_EP0_SETUP) + s3c_hsotg_handle_outdone(hsotg, epnum); break; case GRXSTS_PKTSTS_OUTRX: @@ -1544,6 +1533,8 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) s3c_hsotg_read_frameno(hsotg), readl(hsotg->regs + DOEPCTL(0))); + WARN_ON(hsotg->ep0_state != DWC2_EP0_SETUP); + s3c_hsotg_rx_data(hsotg, epnum, size); break; @@ -1723,9 +1714,10 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, } /* Finish ZLP handling for IN EP0 transactions */ - if (hsotg->eps_out[0]->sent_zlp) { - dev_dbg(hsotg->dev, "zlp packet received\n"); + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_IN) { + dev_dbg(hsotg->dev, "zlp packet sent\n"); s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + s3c_hsotg_enqueue_setup(hsotg); return; } @@ -1767,7 +1759,7 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, !(hs_req->req.length % hs_ep->ep.maxpacket)) { dev_dbg(hsotg->dev, "ep0 zlp IN packet sent\n"); - s3c_hsotg_send_zlp(hsotg, hs_req); + s3c_hsotg_program_zlp(hsotg, hs_ep); return; } @@ -1775,8 +1767,16 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, if (!size_left && hs_req->req.actual < hs_req->req.length) { dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); - } else - s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + return; + } + + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_DATA_IN) { + /* Move to STATUS OUT */ + s3c_hsotg_ep0_zlp(hsotg, false); + return; + } + + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); } /** @@ -1845,7 +1845,7 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, * as we ignore the RXFIFO. */ - s3c_hsotg_handle_outdone(hsotg, idx, false); + s3c_hsotg_handle_outdone(hsotg, idx); } } @@ -1884,7 +1884,7 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, if (dir_in) WARN_ON_ONCE(1); else - s3c_hsotg_handle_outdone(hsotg, 0, true); + s3c_hsotg_handle_outdone(hsotg, 0); } } -- cgit v0.10.2 From f71b5e2533debe564f21c068aaabc42c6293d18b Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:59 +0100 Subject: usb: dwc2: gadget: fix zero length packet transfers According to programming guide, zero length packet should be programmed on its own and should not be counted in DIEPTSIZ.PktCnt with other packets. For ep0, this is the zlp for DATA IN stage (if required) and not for the STATUS stage. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c3977a8..992a2eb 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -598,14 +598,15 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, else epsize = 0; - if (index != 0 && ureq->zero) { - /* - * test for the packets being exactly right for the - * transfer - */ - - if (length == (packets * hs_ep->ep.maxpacket)) - packets++; + /* + * zero length packet should be programmed on its own and should not + * be counted in DIEPTSIZ.PktCnt with other packets. + */ + if (dir_in && ureq->zero && !continuing) { + /* Test if zlp is actually required. */ + if ((ureq->length >= hs_ep->ep.maxpacket) && + !(ureq->length % hs_ep->ep.maxpacket)) + hs_ep->sent_zlp = 1; } epsize |= DXEPTSIZ_PKTCNT(packets); @@ -857,7 +858,11 @@ static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, req->buf = hsotg->ep0_buff; req->length = length; - req->zero = 1; /* always do zero-length final transfer */ + /* + * zero flag is for sending zlp in DATA IN stage. It has no impact on + * STATUS stage. + */ + req->zero = 0; req->complete = s3c_hsotg_complete_oursetup; if (length) @@ -1744,32 +1749,20 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, dev_dbg(hsotg->dev, "req->length:%d req->actual:%d req->zero:%d\n", hs_req->req.length, hs_req->req.actual, hs_req->req.zero); - /* - * Check if dealing with Maximum Packet Size(MPS) IN transfer at EP0 - * When sent data is a multiple MPS size (e.g. 64B ,128B ,192B - * ,256B ... ), after last MPS sized packet send IN ZLP packet to - * inform the host that no more data is available. - * The state of req.zero member is checked to be sure that the value to - * send is smaller than wValue expected from host. - * Check req.length to NOT send another ZLP when the current one is - * under completion (the one for which this completion has been called). - */ - if (hs_req->req.length && hs_ep->index == 0 && hs_req->req.zero && - hs_req->req.length == hs_req->req.actual && - !(hs_req->req.length % hs_ep->ep.maxpacket)) { - - dev_dbg(hsotg->dev, "ep0 zlp IN packet sent\n"); - s3c_hsotg_program_zlp(hsotg, hs_ep); - - return; - } - if (!size_left && hs_req->req.actual < hs_req->req.length) { dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); return; } + /* Zlp for all endpoints, for ep0 only in DATA IN stage */ + if (hs_ep->sent_zlp) { + s3c_hsotg_program_zlp(hsotg, hs_ep); + hs_ep->sent_zlp = 0; + /* transfer will be completed on next complete interrupt */ + return; + } + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_DATA_IN) { /* Move to STATUS OUT */ s3c_hsotg_ep0_zlp(hsotg, false); -- cgit v0.10.2 From 1a0ed863eef7ea6af63afd60ca4fa16edf6b4244 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:00 +0100 Subject: usb: dwc2: gadget: dont warn if endpoint is not enabled The warning is probably good but it has false positives in both dma and non-dma cases. So its not very helpful in either. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 992a2eb..6d76e28 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -678,7 +678,7 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, /* check ep is enabled */ if (!(readl(hsotg->regs + epctrl_reg) & DXEPCTL_EPENA)) - dev_warn(hsotg->dev, + dev_dbg(hsotg->dev, "ep%d: failed to become enabled (DXEPCTL=0x%08x)?\n", index, readl(hsotg->regs + epctrl_reg)); -- cgit v0.10.2 From 48b20bcb3b2e9b15be1ebb9b574b003cffb033d5 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:01 +0100 Subject: usb: dwc2: gadget: don't block after fifo flush timeout Can't stay in the loop forever. Break it after timeout. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 6d76e28..e3bb525 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -230,6 +230,7 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) dev_err(hsotg->dev, "%s: timeout flushing fifos (GRSTCTL=%08x)\n", __func__, val); + break; } udelay(1); -- cgit v0.10.2 From 83d982234ee65900521e763161f409b29f89b8f6 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:02 +0100 Subject: usb: dwc2: gadget: add vbus_session support Enable phy driver to report vbus session. This allows us to remove D+ pullup when vbus is not present. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index e3bb525..65ec0f2 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3009,11 +3009,35 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) return 0; } +static int s3c_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + + dev_dbg(hsotg->dev, "%s: is_active: %d\n", __func__, is_active); + spin_lock_irqsave(&hsotg->lock, flags); + + if (is_active) { + /* Kill any ep0 requests as controller will be reinitialized */ + kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); + s3c_hsotg_core_init_disconnected(hsotg); + if (hsotg->enabled) + s3c_hsotg_core_connect(hsotg); + } else { + s3c_hsotg_core_disconnect(hsotg); + s3c_hsotg_disconnect(hsotg); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; +} + static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { .get_frame = s3c_hsotg_gadget_getframe, .udc_start = s3c_hsotg_udc_start, .udc_stop = s3c_hsotg_udc_stop, .pullup = s3c_hsotg_pullup, + .vbus_session = s3c_hsotg_vbus_session, }; /** -- cgit v0.10.2 From 8a20fa457eb5a8d247fd82d4bd3c04225fd2f253 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:03 +0100 Subject: usb: dwc2: gadget: rename sent_zlp to send_zlp This flag is set before sending the zlp. So use present tense instead of the past tense. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index e963aef..f09b3de 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -108,7 +108,7 @@ struct s3c_hsotg_req; * @halted: Set if the endpoint has been halted. * @periodic: Set if this is a periodic ep, such as Interrupt * @isochronous: Set if this is a isochronous ep - * @sent_zlp: Set if we've sent a zero-length packet. + * @send_zlp: Set if we need to send a zero-length packet. * @total_data: The total number of data bytes done. * @fifo_size: The size of the FIFO (for periodic IN endpoints) * @fifo_load: The amount of data loaded into the FIFO (periodic IN) @@ -149,7 +149,7 @@ struct s3c_hsotg_ep { unsigned int halted:1; unsigned int periodic:1; unsigned int isochronous:1; - unsigned int sent_zlp:1; + unsigned int send_zlp:1; char name[10]; }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 65ec0f2..5fdc3f5 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -607,7 +607,7 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, /* Test if zlp is actually required. */ if ((ureq->length >= hs_ep->ep.maxpacket) && !(ureq->length % hs_ep->ep.maxpacket)) - hs_ep->sent_zlp = 1; + hs_ep->send_zlp = 1; } epsize |= DXEPTSIZ_PKTCNT(packets); @@ -1193,7 +1193,7 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } hsotg->eps_out[0]->dir_in = 0; - hsotg->eps_out[0]->sent_zlp = 0; + hsotg->eps_out[0]->send_zlp = 0; hsotg->ep0_state = DWC2_EP0_SETUP; ret = s3c_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC); @@ -1757,9 +1757,9 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, } /* Zlp for all endpoints, for ep0 only in DATA IN stage */ - if (hs_ep->sent_zlp) { + if (hs_ep->send_zlp) { s3c_hsotg_program_zlp(hsotg, hs_ep); - hs_ep->sent_zlp = 0; + hs_ep->send_zlp = 0; /* transfer will be completed on next complete interrupt */ return; } -- cgit v0.10.2 From ca4c55ad89edb54b1eb04fe2d4c6915c9f8d69b9 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:04 +0100 Subject: usb: dwc2: gadget: pick smallest acceptable fifo Current algorithm picks the first fifo which is equal to or greater than the required size. This can result in bigger fifos assigned to endpoints with smaller maxps. Change the algorithm to pick the smallest fifo which is greater than or equal to the required size. Moreover, only use signed variables when required. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5fdc3f5..54e6d15 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2439,12 +2439,12 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, struct s3c_hsotg_ep *hs_ep = our_ep(ep); struct dwc2_hsotg *hsotg = hs_ep->parent; unsigned long flags; - int index = hs_ep->index; + unsigned int index = hs_ep->index; u32 epctrl_reg; u32 epctrl; u32 mps; - int dir_in; - int i, val, size; + unsigned int dir_in; + unsigned int i, val, size; int ret = 0; dev_dbg(hsotg->dev, @@ -2533,6 +2533,8 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, * a unique tx-fifo even if it is non-periodic. */ if (dir_in && hsotg->dedicated_fifos) { + u32 fifo_index = 0; + u32 fifo_size = UINT_MAX; size = hs_ep->ep.maxpacket*hs_ep->mc; for (i = 1; i < hsotg->num_of_eps; ++i) { if (hsotg->fifo_map & (1<> FIFOSIZE_DEPTH_SHIFT)*4; if (val < size) continue; - hsotg->fifo_map |= 1<fifo_index = i; - hs_ep->fifo_size = val; - break; + /* Search for smallest acceptable fifo */ + if (val < fifo_size) { + fifo_size = val; + fifo_index = i; + } } - if (i == hsotg->num_of_eps) { + if (!fifo_index) { dev_err(hsotg->dev, "%s: No suitable fifo found\n", __func__); ret = -ENOMEM; goto error; } + hsotg->fifo_map |= 1 << fifo_index; + epctrl |= DXEPCTL_TXFNUM(fifo_index); + hs_ep->fifo_index = fifo_index; + hs_ep->fifo_size = fifo_size; } /* for non control endpoints, set PID to D0 */ -- cgit v0.10.2 From 4556e12c9cf732343224308866dad2a12887dcd6 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:05 +0100 Subject: usb: dwc2: gadget: fix fifo allocation leak When selecting different alt setting, s3c_hsotg_ep_enable can be called with fifo already allocated. Allocate fifo again only if required and after deallocating the previous fifo. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 54e6d15..de5da92 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2528,11 +2528,22 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, break; } + /* If fifo is already allocated for this ep */ + if (hs_ep->fifo_index) { + size = hs_ep->ep.maxpacket * hs_ep->mc; + /* If bigger fifo is required deallocate current one */ + if (size > hs_ep->fifo_size) { + hsotg->fifo_map &= ~(1 << hs_ep->fifo_index); + hs_ep->fifo_index = 0; + hs_ep->fifo_size = 0; + } + } + /* * if the hardware has dedicated fifos, we must give each IN EP * a unique tx-fifo even if it is non-periodic. */ - if (dir_in && hsotg->dedicated_fifos) { + if (dir_in && hsotg->dedicated_fifos && !hs_ep->fifo_index) { u32 fifo_index = 0; u32 fifo_size = UINT_MAX; size = hs_ep->ep.maxpacket*hs_ep->mc; -- cgit v0.10.2 From 7fcbc95c1fa4d6e3f4645f9552d358f07ab93ad1 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:06 +0100 Subject: usb: dwc2: gadget: reset fifo_map when initializing fifos After all endpoints are disabled, fifo_map should have reached 0. Its a bug if if didn't, so warn about it and reset it to 0 so that driver can continue using all the fifos. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index de5da92..a1fd86f 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -177,6 +177,10 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) int timeout; u32 val; + /* Reset fifo map if not correctly cleared during previous session */ + WARN_ON(hsotg->fifo_map); + hsotg->fifo_map = 0; + /* set RX/NPTX FIFO sizes */ writel(hsotg->g_rx_fifo_sz, hsotg->regs + GRXFSIZ); writel((hsotg->g_rx_fifo_sz << FIFOSIZE_STARTADDR_SHIFT) | -- cgit v0.10.2 From 6d13673e6bc5b666913e92ca59518a449da3b4a9 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:07 +0100 Subject: usb: dwc2: gadget: fix pullup handling Gadget must be informed about disconnection when pullup is removed. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index a1fd86f..2719d48 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3018,6 +3018,7 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) s3c_hsotg_core_connect(hsotg); } else { s3c_hsotg_core_disconnect(hsotg); + s3c_hsotg_disconnect(hsotg); hsotg->enabled = 0; clk_disable(hsotg->clk); } -- cgit v0.10.2 From 596d696a5d0e066f2ed4bd6b0e40dedd52513c7c Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:08 +0100 Subject: usb: dwc2: gadget: add vbus_draw support This callback informs the driver about the total amount of current it is allowed to draw. Share this information with the phy so that current limits can be set for charging for example. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 2719d48..f477709 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3053,12 +3053,29 @@ static int s3c_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) return 0; } +/** + * s3c_hsotg_vbus_draw - report bMaxPower field + * @gadget: The usb gadget state + * @mA: Amount of current + * + * Report how much power the device may consume to the phy. + */ +static int s3c_hsotg_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + + if (IS_ERR_OR_NULL(hsotg->uphy)) + return -ENOTSUPP; + return usb_phy_set_power(hsotg->uphy, mA); +} + static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { .get_frame = s3c_hsotg_gadget_getframe, .udc_start = s3c_hsotg_udc_start, .udc_stop = s3c_hsotg_udc_stop, .pullup = s3c_hsotg_pullup, .vbus_session = s3c_hsotg_vbus_session, + .vbus_draw = s3c_hsotg_vbus_draw, }; /** -- cgit v0.10.2 From 1b7a66b4d3399da5e0b13cf90fb4bd33b7197ff2 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:09 +0100 Subject: usb: dwc2: gadget: force gadget initialization in dev mode When booting with id pin grounded, dwc2 default to host mode. Thus, force device mode prior initializing gadget part. Else fifo init will fail since fifo values are not correct in host mode. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f477709..df9080d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3675,6 +3675,19 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) /* usb phy enable */ s3c_hsotg_phy_enable(hsotg); + /* + * 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); + s3c_hsotg_corereset(hsotg); ret = s3c_hsotg_hw_cfg(hsotg); if (ret) { @@ -3684,6 +3697,9 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) s3c_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 v0.10.2 From 6d713c1531638df8d459d248a89948318cbeec4c Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:10 +0100 Subject: usb: dwc2: gadget: report disconnection after reset If usb bus is reset without a physical disconnection, all endpoints will remain open. Call s3c_hsotg_disconnect() from reset handler to report a disconnect to gadget framework. hsotg->connected is checked in s3c_hsotg_disconnect() before processing disconnect. In some cases, USBRst is seen before EnumDone and after it as well. So move setting of hsotg->connected to set-address to avoid reporting extra disconnection in this case. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index df9080d..882a1a8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1103,6 +1103,7 @@ static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (ctrl->bRequest) { case USB_REQ_SET_ADDRESS: + hsotg->connected = 1; dcfg = readl(hsotg->regs + DCFG); dcfg &= ~DCFG_DEVADDR_MASK; dcfg |= (le16_to_cpu(ctrl->wValue) << @@ -2305,7 +2306,6 @@ irq_retry: writel(GINTSTS_ENUMDONE, hsotg->regs + GINTSTS); s3c_hsotg_irq_enumdone(hsotg); - hsotg->connected = 1; } if (gintsts & (GINTSTS_OEPINT | GINTSTS_IEPINT)) { @@ -2343,6 +2343,9 @@ irq_retry: writel(GINTSTS_USBRST, hsotg->regs + GINTSTS); + /* Report disconnection if it is not already done. */ + s3c_hsotg_disconnect(hsotg); + if (usb_status & GOTGCTL_BSESVLD) { if (time_after(jiffies, hsotg->last_rst + msecs_to_jiffies(200))) { -- cgit v0.10.2 From d7b3968dbcbb620acf92164752031fa276917db9 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Tue, 16 Dec 2014 01:42:13 +0300 Subject: usb: renesas_usbhs: add OTG ID signal sensing On the Renesas R8A7791 SoC based boards there's MAX3355 USB OTG chip and mini-AB USB connector corresponding to USB port 0 driven either by EHCI/OHCI or Renesas USBHS gadget controller. And we'd like the host/gadget drivers to work based on the cable type connected. An 'extcon' driver for MAX3355 has been written, so we only need to bind to it via device tree which I'm doing in this patch. (Perhaps, it would also make sense to use OTG HNP when the USBHS host mode is active and a B-cable is connected but I don't have access to host-capable USBHS, so wouldn't be able to test it.) Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 76d14f5..4cf77d3 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -363,6 +363,7 @@ static void usbhsc_hotplug(struct usbhs_priv *priv) struct usbhs_mod *mod = usbhs_mod_get_current(priv); int id; int enable; + int cable; int ret; /* @@ -376,6 +377,16 @@ static void usbhsc_hotplug(struct usbhs_priv *priv) id = usbhs_platform_call(priv, get_id, pdev); if (enable && !mod) { + if (priv->edev) { + cable = extcon_get_cable_state(priv->edev, "USB-HOST"); + if ((cable > 0 && id != USBHS_HOST) || + (!cable && id != USBHS_GADGET)) { + dev_info(&pdev->dev, + "USB cable plugged in doesn't match the selected role!\n"); + return; + } + } + ret = usbhs_mod_change(priv, id); if (ret < 0) return; @@ -514,6 +525,12 @@ static int usbhs_probe(struct platform_device *pdev) if (IS_ERR(priv->base)) return PTR_ERR(priv->base); + if (of_property_read_bool(pdev->dev.of_node, "extcon")) { + priv->edev = extcon_get_edev_by_phandle(&pdev->dev, 0); + if (IS_ERR(priv->edev)) + return PTR_ERR(priv->edev); + } + /* * care platform info */ diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index 0427cdd..fc96e92 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -17,6 +17,7 @@ #ifndef RENESAS_USB_DRIVER_H #define RENESAS_USB_DRIVER_H +#include #include #include @@ -254,6 +255,8 @@ struct usbhs_priv { struct delayed_work notify_hotplug_work; struct platform_device *pdev; + struct extcon_dev *edev; + spinlock_t lock; u32 flags; -- cgit v0.10.2 From 3703cfe78295b1ccbad091871ccc0fa0e5d0589f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 1 Dec 2014 16:09:28 +0800 Subject: usb: gadget: uac1: struct gaudio is useless for struct f_uac1_opts Since we call gaudio_cleanup at f_audio_free, the f_uac1_opts doesn't need to use gaudio any more. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index d8b4af6..9cf2252 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -674,7 +674,6 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst); audio->card.gadget = c->cdev->gadget; - audio_opts->card = &audio->card; /* set up ASLA audio devices */ if (!audio_opts->bound) { status = gaudio_setup(&audio->card); diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h index f8b17fe..fe386df 100644 --- a/drivers/usb/gadget/function/u_uac1.h +++ b/drivers/usb/gadget/function/u_uac1.h @@ -70,7 +70,6 @@ struct f_uac1_opts { unsigned fn_play_alloc:1; unsigned fn_cap_alloc:1; unsigned fn_cntl_alloc:1; - struct gaudio *card; struct mutex lock; int refcnt; }; -- cgit v0.10.2 From c76abecc42b63f70d33f9e266c62002c2b1234d1 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 1 Dec 2014 16:09:29 +0800 Subject: usb: gadget: u_uac1: fix one code style problem Fix one code style problem. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/u_uac1.c b/drivers/usb/gadget/function/u_uac1.c index 53842a1..c78c841 100644 --- a/drivers/usb/gadget/function/u_uac1.c +++ b/drivers/usb/gadget/function/u_uac1.c @@ -308,8 +308,7 @@ int gaudio_setup(struct gaudio *card) */ void gaudio_cleanup(struct gaudio *the_card) { - if (the_card) { + if (the_card) gaudio_close_snd_dev(the_card); - } } -- cgit v0.10.2 From fc12c68b4ff2904c3322b7a12089f0b9808e2383 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Tue, 13 Jan 2015 10:54:58 +0100 Subject: usb: gadget: net2280: Dont use 0 as NULL pointer Fix sparse warning Fixes: cb442ee1592d2681 (usb: gadget: udc: net2280: Re-enable dynamic debug messages) Reported-by: kbuild test robot Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index d2fabdc..6411ed8 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2287,7 +2287,7 @@ static void handle_ep_small(struct net2280_ep *ep) ep->irqs++; ep_vdbg(ep->dev, "%s ack ep_stat %08x, req %p\n", - ep->ep.name, t, req ? &req->req : 0); + ep->ep.name, t, req ? &req->req : NULL); if (!ep->is_in || (ep->dev->quirks & PLX_2280)) writel(t & ~BIT(NAK_OUT_PACKETS), &ep->regs->ep_stat); -- cgit v0.10.2 From 969678c09877fd1116be6ae304e59a27658226c2 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 13 Jan 2015 14:56:28 +0100 Subject: tools: ffs-aio-example: use endpoint addresses from descriptors This makes examples more platform independent and more compatible with USB standard, as endpoint addresses in given interface may differ between hardware platforms or even between configurations in single USB device. Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/test.c b/tools/usb/ffs-aio-example/multibuff/host_app/test.c index daa3abe..2cbcce6 100644 --- a/tools/usb/ffs-aio-example/multibuff/host_app/test.c +++ b/tools/usb/ffs-aio-example/multibuff/host_app/test.c @@ -33,11 +33,6 @@ #define VENDOR 0x1d6b #define PRODUCT 0x0105 -/* endpoints indexes */ - -#define EP_BULK_IN (1 | LIBUSB_ENDPOINT_IN) -#define EP_BULK_OUT (2 | LIBUSB_ENDPOINT_OUT) - #define BUF_LEN 8192 /* @@ -159,14 +154,21 @@ void test_exit(struct test_state *state) int main(void) { struct test_state state; + struct libusb_config_descriptor *conf; + struct libusb_interface_descriptor const *iface; + unsigned char addr; if (test_init(&state)) return 1; + libusb_get_config_descriptor(state.found, 0, &conf); + iface = &conf->interface[0].altsetting[0]; + addr = iface->endpoint[0].bEndpointAddress; + while (1) { static unsigned char buffer[BUF_LEN]; int bytes; - libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN, + libusb_bulk_transfer(state.handle, addr, buffer, BUF_LEN, &bytes, 500); } test_exit(&state); diff --git a/tools/usb/ffs-aio-example/simple/host_app/test.c b/tools/usb/ffs-aio-example/simple/host_app/test.c index acd6332..aed86ff 100644 --- a/tools/usb/ffs-aio-example/simple/host_app/test.c +++ b/tools/usb/ffs-aio-example/simple/host_app/test.c @@ -33,11 +33,6 @@ #define VENDOR 0x1d6b #define PRODUCT 0x0105 -/* endpoints indexes */ - -#define EP_BULK_IN (1 | LIBUSB_ENDPOINT_IN) -#define EP_BULK_OUT (2 | LIBUSB_ENDPOINT_OUT) - #define BUF_LEN 8192 /* @@ -159,16 +154,24 @@ void test_exit(struct test_state *state) int main(void) { struct test_state state; + struct libusb_config_descriptor *conf; + struct libusb_interface_descriptor const *iface; + unsigned char in_addr, out_addr; if (test_init(&state)) return 1; + libusb_get_config_descriptor(state.found, 0, &conf); + iface = &conf->interface[0].altsetting[0]; + in_addr = iface->endpoint[0].bEndpointAddress; + out_addr = iface->endpoint[1].bEndpointAddress; + while (1) { static unsigned char buffer[BUF_LEN]; int bytes; - libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN, + libusb_bulk_transfer(state.handle, in_addr, buffer, BUF_LEN, &bytes, 500); - libusb_bulk_transfer(state.handle, EP_BULK_OUT, buffer, BUF_LEN, + libusb_bulk_transfer(state.handle, out_addr, buffer, BUF_LEN, &bytes, 500); } test_exit(&state); -- cgit v0.10.2 From f093a2d465371d106384559a3ac0abf701be820e Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Tue, 13 Jan 2015 16:55:38 +0800 Subject: usb: gadget: uvc: to_uvcg_control_header() can be static drivers/usb/gadget/function/uvc_configfs.c:46:28: sparse: symbol 'to_uvcg_control_header' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:138:25: sparse: symbol 'uvcg_control_header_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:164:6: sparse: symbol 'uvcg_control_header_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:721:20: sparse: symbol 'to_uvcg_format' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:798:30: sparse: symbol 'to_uvcg_streaming_header' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:950:25: sparse: symbol 'uvcg_streaming_header_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:976:6: sparse: symbol 'uvcg_streaming_header_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1020:19: sparse: symbol 'to_uvcg_frame' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1265:25: sparse: symbol 'uvcg_frame_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1315:6: sparse: symbol 'uvcg_frame_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1338:26: sparse: symbol 'to_uvcg_uncompressed' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1548:25: sparse: symbol 'uvcg_uncompressed_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1586:6: sparse: symbol 'uvcg_uncompressed_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1610:19: sparse: symbol 'to_uvcg_mjpeg' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1761:25: sparse: symbol 'uvcg_mjpeg_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1793:6: sparse: symbol 'uvcg_mjpeg_drop' was not declared. Should it be static? Signed-off-by: Fengguang Wu Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 33d92ab..f69f47a 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -43,7 +43,7 @@ struct uvcg_control_header { unsigned linked; }; -struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) +static struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) { return container_of(item, struct uvcg_control_header, item); } @@ -135,7 +135,7 @@ static struct configfs_attribute *uvcg_control_header_attrs[] = { NULL, }; -struct config_item_type uvcg_control_header_type = { +static struct config_item_type uvcg_control_header_type = { .ct_item_ops = &uvcg_control_header_item_ops, .ct_attrs = uvcg_control_header_attrs, .ct_owner = THIS_MODULE, @@ -161,7 +161,7 @@ static struct config_item *uvcg_control_header_make(struct config_group *group, return &h->item; } -void uvcg_control_header_drop(struct config_group *group, +static void uvcg_control_header_drop(struct config_group *group, struct config_item *item) { struct uvcg_control_header *h = to_uvcg_control_header(item); @@ -718,7 +718,7 @@ struct uvcg_format { __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; }; -struct uvcg_format *to_uvcg_format(struct config_item *item) +static struct uvcg_format *to_uvcg_format(struct config_item *item) { return container_of(to_config_group(item), struct uvcg_format, group); } @@ -795,7 +795,7 @@ struct uvcg_streaming_header { unsigned num_fmt; }; -struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) +static struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) { return container_of(item, struct uvcg_streaming_header, item); } @@ -947,7 +947,7 @@ static struct configfs_attribute *uvcg_streaming_header_attrs[] = { NULL, }; -struct config_item_type uvcg_streaming_header_type = { +static struct config_item_type uvcg_streaming_header_type = { .ct_item_ops = &uvcg_streaming_header_item_ops, .ct_attrs = uvcg_streaming_header_attrs, .ct_owner = THIS_MODULE, @@ -973,7 +973,7 @@ static struct config_item return &h->item; } -void uvcg_streaming_header_drop(struct config_group *group, +static void uvcg_streaming_header_drop(struct config_group *group, struct config_item *item) { struct uvcg_streaming_header *h = to_uvcg_streaming_header(item); @@ -1017,7 +1017,7 @@ struct uvcg_frame { struct config_item item; }; -struct uvcg_frame *to_uvcg_frame(struct config_item *item) +static struct uvcg_frame *to_uvcg_frame(struct config_item *item) { return container_of(item, struct uvcg_frame, item); } @@ -1262,7 +1262,7 @@ static struct configfs_attribute *uvcg_frame_attrs[] = { NULL, }; -struct config_item_type uvcg_frame_type = { +static struct config_item_type uvcg_frame_type = { .ct_item_ops = &uvcg_frame_item_ops, .ct_attrs = uvcg_frame_attrs, .ct_owner = THIS_MODULE, @@ -1312,7 +1312,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, return &h->item; } -void uvcg_frame_drop(struct config_group *group, struct config_item *item) +static void uvcg_frame_drop(struct config_group *group, struct config_item *item) { struct uvcg_frame *h = to_uvcg_frame(item); struct uvcg_format *fmt; @@ -1335,7 +1335,7 @@ struct uvcg_uncompressed { struct uvc_format_uncompressed desc; }; -struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) +static struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) { return container_of( container_of(to_config_group(item), struct uvcg_format, group), @@ -1545,7 +1545,7 @@ static struct configfs_attribute *uvcg_uncompressed_attrs[] = { NULL, }; -struct config_item_type uvcg_uncompressed_type = { +static struct config_item_type uvcg_uncompressed_type = { .ct_item_ops = &uvcg_uncompressed_item_ops, .ct_group_ops = &uvcg_uncompressed_group_ops, .ct_attrs = uvcg_uncompressed_attrs, @@ -1583,7 +1583,7 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, return &h->fmt.group; } -void uvcg_uncompressed_drop(struct config_group *group, +static void uvcg_uncompressed_drop(struct config_group *group, struct config_item *item) { struct uvcg_uncompressed *h = to_uvcg_uncompressed(item); @@ -1607,7 +1607,7 @@ struct uvcg_mjpeg { struct uvc_format_mjpeg desc; }; -struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) +static struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) { return container_of( container_of(to_config_group(item), struct uvcg_format, group), @@ -1758,7 +1758,7 @@ static struct configfs_attribute *uvcg_mjpeg_attrs[] = { NULL, }; -struct config_item_type uvcg_mjpeg_type = { +static struct config_item_type uvcg_mjpeg_type = { .ct_item_ops = &uvcg_mjpeg_item_ops, .ct_group_ops = &uvcg_mjpeg_group_ops, .ct_attrs = uvcg_mjpeg_attrs, @@ -1790,7 +1790,7 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, return &h->fmt.group; } -void uvcg_mjpeg_drop(struct config_group *group, +static void uvcg_mjpeg_drop(struct config_group *group, struct config_item *item) { struct uvcg_mjpeg *h = to_uvcg_mjpeg(item); -- cgit v0.10.2 From d3fdcc78b2d6f1338fa9abea2d37bc10da7f71b8 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 12 Jan 2015 14:20:12 +0200 Subject: usb: dwc3: pci: remove registration of NOP PHYs None of the PCI platforms need the NOP transceivers, and since we can now live without the PHYs, removing registration of the platform devices for them. Acked-by: Huang Rui Signed-off-by: Heikki Krogerus Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 7c4faf7..82a439b 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -22,9 +22,6 @@ #include #include -#include -#include - #include "platform_data.h" /* FIXME define these in */ @@ -37,65 +34,8 @@ struct dwc3_pci { struct device *dev; struct platform_device *dwc3; - struct platform_device *usb2_phy; - struct platform_device *usb3_phy; }; -static int dwc3_pci_register_phys(struct dwc3_pci *glue) -{ - struct usb_phy_generic_platform_data pdata; - struct platform_device *pdev; - int ret; - - memset(&pdata, 0x00, sizeof(pdata)); - - pdev = platform_device_alloc("usb_phy_generic", 0); - if (!pdev) - return -ENOMEM; - - glue->usb2_phy = pdev; - pdata.type = USB_PHY_TYPE_USB2; - pdata.gpio_reset = -1; - - ret = platform_device_add_data(glue->usb2_phy, &pdata, sizeof(pdata)); - if (ret) - goto err1; - - pdev = platform_device_alloc("usb_phy_generic", 1); - if (!pdev) { - ret = -ENOMEM; - goto err1; - } - - glue->usb3_phy = pdev; - pdata.type = USB_PHY_TYPE_USB3; - - ret = platform_device_add_data(glue->usb3_phy, &pdata, sizeof(pdata)); - if (ret) - goto err2; - - ret = platform_device_add(glue->usb2_phy); - if (ret) - goto err2; - - ret = platform_device_add(glue->usb3_phy); - if (ret) - goto err3; - - return 0; - -err3: - platform_device_del(glue->usb2_phy); - -err2: - platform_device_put(glue->usb3_phy); - -err1: - platform_device_put(glue->usb2_phy); - - return ret; -} - static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) { @@ -122,12 +62,6 @@ static int dwc3_pci_probe(struct pci_dev *pci, pci_set_master(pci); - ret = dwc3_pci_register_phys(glue); - if (ret) { - dev_err(dev, "couldn't register PHYs\n"); - return ret; - } - dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO); if (!dwc3) { dev_err(dev, "couldn't allocate dwc3 device\n"); @@ -207,8 +141,6 @@ static void dwc3_pci_remove(struct pci_dev *pci) struct dwc3_pci *glue = pci_get_drvdata(pci); platform_device_unregister(glue->dwc3); - platform_device_unregister(glue->usb2_phy); - platform_device_unregister(glue->usb3_phy); } static const struct pci_device_id dwc3_pci_id_table[] = { -- cgit v0.10.2 From 3b44ed90cdb26ce3a06a1b41c5fcbb01c7d63b3c Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 12 Jan 2015 14:20:13 +0200 Subject: usb: dwc3: pci: rely on default PM callbacks from PCI driver utility There is nothing specific being done in the suspend and resume callbacks that is not already taken care of in PCI driver core, so dropping the functions. Acked-by: Huang Rui Signed-off-by: Heikki Krogerus Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 82a439b..5c471d2 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -156,45 +156,11 @@ static const struct pci_device_id dwc3_pci_id_table[] = { }; MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); -#ifdef CONFIG_PM_SLEEP -static int dwc3_pci_suspend(struct device *dev) -{ - struct pci_dev *pci = to_pci_dev(dev); - - pci_disable_device(pci); - - return 0; -} - -static int dwc3_pci_resume(struct device *dev) -{ - struct pci_dev *pci = to_pci_dev(dev); - int ret; - - ret = pci_enable_device(pci); - if (ret) { - dev_err(dev, "can't re-enable device --> %d\n", ret); - return ret; - } - - pci_set_master(pci); - - return 0; -} -#endif /* CONFIG_PM_SLEEP */ - -static const struct dev_pm_ops dwc3_pci_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(dwc3_pci_suspend, dwc3_pci_resume) -}; - static struct pci_driver dwc3_pci_driver = { .name = "dwc3-pci", .id_table = dwc3_pci_id_table, .probe = dwc3_pci_probe, .remove = dwc3_pci_remove, - .driver = { - .pm = &dwc3_pci_dev_pm_ops, - }, }; MODULE_AUTHOR("Felipe Balbi "); -- cgit v0.10.2 From 2cd9ddf77e0da465da4535b685587338260eca99 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 12 Jan 2015 14:20:14 +0200 Subject: usb: dwc3: pci: code cleanup Removing a few items that are not needed anymore and adding separate function for quirks. Acked-by: Huang Rui Signed-off-by: Heikki Krogerus Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 5c471d2..5f5fa98 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -31,28 +31,50 @@ #define PCI_DEVICE_ID_INTEL_MRFLD 0x119e #define PCI_DEVICE_ID_INTEL_BSW 0x22B7 -struct dwc3_pci { - struct device *dev; - struct platform_device *dwc3; -}; +static int dwc3_pci_quirks(struct pci_dev *pdev) +{ + if (pdev->vendor == PCI_VENDOR_ID_AMD && + pdev->device == PCI_DEVICE_ID_AMD_NL_USB) { + struct dwc3_platform_data pdata; + + memset(&pdata, 0, sizeof(pdata)); + + pdata.has_lpm_erratum = true; + pdata.lpm_nyet_threshold = 0xf; + + pdata.u2exit_lfps_quirk = true; + pdata.u2ss_inp3_quirk = true; + pdata.req_p1p2p3_quirk = true; + pdata.del_p1p2p3_quirk = true; + pdata.del_phy_power_chg_quirk = true; + pdata.lfps_filter_quirk = true; + pdata.rx_detect_poll_quirk = true; + + pdata.tx_de_emphasis_quirk = true; + pdata.tx_de_emphasis = 1; + + /* + * FIXME these quirks should be removed when AMD NL + * taps out + */ + pdata.disable_scramble_quirk = true; + pdata.dis_u3_susphy_quirk = true; + pdata.dis_u2_susphy_quirk = true; + + return platform_device_add_data(pci_get_drvdata(pdev), &pdata, + sizeof(pdata)); + } + + return 0; +} static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) { struct resource res[2]; struct platform_device *dwc3; - struct dwc3_pci *glue; int ret; struct device *dev = &pci->dev; - struct dwc3_platform_data dwc3_pdata; - - memset(&dwc3_pdata, 0x00, sizeof(dwc3_pdata)); - - glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); - if (!glue) - return -ENOMEM; - - glue->dev = dev; ret = pcim_enable_device(pci); if (ret) { @@ -79,68 +101,34 @@ static int dwc3_pci_probe(struct pci_dev *pci, res[1].name = "dwc_usb3"; res[1].flags = IORESOURCE_IRQ; - if (pci->vendor == PCI_VENDOR_ID_AMD && - pci->device == PCI_DEVICE_ID_AMD_NL_USB) { - dwc3_pdata.has_lpm_erratum = true; - dwc3_pdata.lpm_nyet_threshold = 0xf; - - dwc3_pdata.u2exit_lfps_quirk = true; - dwc3_pdata.u2ss_inp3_quirk = true; - dwc3_pdata.req_p1p2p3_quirk = true; - dwc3_pdata.del_p1p2p3_quirk = true; - dwc3_pdata.del_phy_power_chg_quirk = true; - dwc3_pdata.lfps_filter_quirk = true; - dwc3_pdata.rx_detect_poll_quirk = true; - - dwc3_pdata.tx_de_emphasis_quirk = true; - dwc3_pdata.tx_de_emphasis = 1; - - /* - * FIXME these quirks should be removed when AMD NL - * taps out - */ - dwc3_pdata.disable_scramble_quirk = true; - dwc3_pdata.dis_u3_susphy_quirk = true; - dwc3_pdata.dis_u2_susphy_quirk = true; - } - ret = platform_device_add_resources(dwc3, res, ARRAY_SIZE(res)); if (ret) { dev_err(dev, "couldn't add resources to dwc3 device\n"); return ret; } - pci_set_drvdata(pci, glue); - - ret = platform_device_add_data(dwc3, &dwc3_pdata, sizeof(dwc3_pdata)); + pci_set_drvdata(pci, dwc3); + ret = dwc3_pci_quirks(pci); if (ret) - goto err3; - - dma_set_coherent_mask(&dwc3->dev, dev->coherent_dma_mask); + goto err; - dwc3->dev.dma_mask = dev->dma_mask; - dwc3->dev.dma_parms = dev->dma_parms; dwc3->dev.parent = dev; - glue->dwc3 = dwc3; ret = platform_device_add(dwc3); if (ret) { dev_err(dev, "failed to register dwc3 device\n"); - goto err3; + goto err; } return 0; - -err3: +err: platform_device_put(dwc3); return ret; } static void dwc3_pci_remove(struct pci_dev *pci) { - struct dwc3_pci *glue = pci_get_drvdata(pci); - - platform_device_unregister(glue->dwc3); + platform_device_unregister(pci_get_drvdata(pci)); } static const struct pci_device_id dwc3_pci_id_table[] = { -- cgit v0.10.2 From 18d6b32fca3841f7cd9479b4024abd8a9b299281 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Thu, 18 Dec 2014 09:55:10 +0100 Subject: usb: gadget: f_fs: add "no_disconnect" mode Since we can compose gadgets from many functions, there is the problem related to gadget breakage while FunctionFS daemon being closed. FFS function is userspace code so there is no way to know when it will close files (it doesn't matter what is the reason of this situation, it can be daemon logic, program breakage, process kill or any other). So when we have another function in gadget which, for example, sends some amount of data, does some software update or implements some real-time functionality, we may want to keep the gadget connected despite FFS function is no longer functional. We can't just remove one of functions from gadget since it has been enumerated, so the only way to keep entire gadget working is to make broken FFS function deactivated but still visible to host. For this purpose this patch introduces "no_disconnect" mode. It can be enabled by setting mount option "no_disconnect=1", and results with defering function disconnect to the moment of reopen ep0 file or filesystem unmount. After closing all endpoint files, FunctionFS is set to state FFS_DEACTIVATED. When ffs->state == FFS_DEACTIVATED: - function is still bound and visible to host, - setup requests are automatically stalled, - transfers on other endpoints are refused, - epfiles, except ep0, are deleted from the filesystem, - opening ep0 causes the function to be closed, and then FunctionFS is ready for descriptors and string write, - altsetting change causes the function to be closed - we want to keep function alive until another functions are potentialy used, altsetting change means that another configuration is being selected or USB cable was unplugged, which indicates that we don't need to stay longer in FFS_DEACTIVATED state - unmounting of the FunctionFS instance causes the function to be closed. Tested-by: David Cohen Acked-by: Michal Nazarewicz Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index a00ee97..e78a2c6 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -605,6 +605,8 @@ static unsigned int ffs_ep0_poll(struct file *file, poll_table *wait) } case FFS_CLOSING: break; + case FFS_DEACTIVATED: + break; } mutex_unlock(&ffs->mutex); @@ -1179,6 +1181,7 @@ struct ffs_sb_fill_data { struct ffs_file_perms perms; umode_t root_mode; const char *dev_name; + bool no_disconnect; struct ffs_data *ffs_data; }; @@ -1249,6 +1252,12 @@ static int ffs_fs_parse_opts(struct ffs_sb_fill_data *data, char *opts) /* Interpret option */ switch (eq - opts) { + case 13: + if (!memcmp(opts, "no_disconnect", 13)) + data->no_disconnect = !!value; + else + goto invalid; + break; case 5: if (!memcmp(opts, "rmode", 5)) data->root_mode = (value & 0555) | S_IFDIR; @@ -1313,6 +1322,7 @@ ffs_fs_mount(struct file_system_type *t, int flags, .gid = GLOBAL_ROOT_GID, }, .root_mode = S_IFDIR | 0500, + .no_disconnect = false, }; struct dentry *rv; int ret; @@ -1329,6 +1339,7 @@ ffs_fs_mount(struct file_system_type *t, int flags, if (unlikely(!ffs)) return ERR_PTR(-ENOMEM); ffs->file_perms = data.perms; + ffs->no_disconnect = data.no_disconnect; ffs->dev_name = kstrdup(dev_name, GFP_KERNEL); if (unlikely(!ffs->dev_name)) { @@ -1360,6 +1371,7 @@ ffs_fs_kill_sb(struct super_block *sb) kill_litter_super(sb); if (sb->s_fs_info) { ffs_release_dev(sb->s_fs_info); + ffs_data_closed(sb->s_fs_info); ffs_data_put(sb->s_fs_info); } } @@ -1416,7 +1428,11 @@ static void ffs_data_opened(struct ffs_data *ffs) ENTER(); atomic_inc(&ffs->ref); - atomic_inc(&ffs->opened); + if (atomic_add_return(1, &ffs->opened) == 1 && + ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + } } static void ffs_data_put(struct ffs_data *ffs) @@ -1438,6 +1454,21 @@ static void ffs_data_closed(struct ffs_data *ffs) ENTER(); if (atomic_dec_and_test(&ffs->opened)) { + if (ffs->no_disconnect) { + ffs->state = FFS_DEACTIVATED; + if (ffs->epfiles) { + ffs_epfiles_destroy(ffs->epfiles, + ffs->eps_count); + ffs->epfiles = NULL; + } + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); + } else { + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + } + } + if (atomic_read(&ffs->opened) < 0) { ffs->state = FFS_CLOSING; ffs_data_reset(ffs); } @@ -1615,7 +1646,6 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) kfree(epfiles); } - static void ffs_func_eps_disable(struct ffs_function *func) { struct ffs_ep *ep = func->eps; @@ -1628,10 +1658,12 @@ static void ffs_func_eps_disable(struct ffs_function *func) /* pending requests get nuked */ if (likely(ep->ep)) usb_ep_disable(ep->ep); - epfile->ep = NULL; - ++ep; - ++epfile; + + if (epfile) { + epfile->ep = NULL; + ++epfile; + } } while (--count); spin_unlock_irqrestore(&func->ffs->eps_lock, flags); } @@ -2894,6 +2926,13 @@ static int ffs_func_bind(struct usb_configuration *c, /* Other USB function hooks *************************************************/ +static void ffs_reset_work(struct work_struct *work) +{ + struct ffs_data *ffs = container_of(work, + struct ffs_data, reset_work); + ffs_data_reset(ffs); +} + static int ffs_func_set_alt(struct usb_function *f, unsigned interface, unsigned alt) { @@ -2910,6 +2949,13 @@ static int ffs_func_set_alt(struct usb_function *f, if (ffs->func) ffs_func_eps_disable(ffs->func); + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return -ENODEV; + } + if (ffs->state != FFS_ACTIVE) return -ENODEV; diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index cd128e3..284a1f0 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -19,6 +19,7 @@ #include #include #include +#include #ifdef VERBOSE_DEBUG #ifndef pr_vdebug @@ -93,6 +94,26 @@ enum ffs_state { FFS_ACTIVE, /* + * Function is visible to host, but it's not functional. All + * setup requests are stalled and transfers on another endpoints + * are refused. All epfiles, except ep0, are deleted so there + * is no way to perform any operations on them. + * + * This state is set after closing all functionfs files, when + * mount parameter "no_disconnect=1" has been set. Function will + * remain in deactivated state until filesystem is umounted or + * ep0 is opened again. In the second case functionfs state will + * be reset, and it will be ready for descriptors and strings + * writing. + * + * This is useful only when functionfs is composed to gadget + * with another function which can perform some critical + * operations, and it's strongly desired to have this operations + * completed, even after functionfs files closure. + */ + FFS_DEACTIVATED, + + /* * All endpoints have been closed. This state is also set if * we encounter an unrecoverable error. The only * unrecoverable error is situation when after reading strings @@ -251,6 +272,9 @@ struct ffs_data { kgid_t gid; } file_perms; + bool no_disconnect; + struct work_struct reset_work; + /* * The endpoint files, filled by ffs_epfiles_create(), * destroyed by ffs_epfiles_destroy(). -- cgit v0.10.2 From fe198e34a44ce7f27e39c303d62a754129938194 Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Wed, 14 Jan 2015 16:08:38 +0100 Subject: usb: musb: debugfs: improve copy_from_user() argument While the code is correct and functions well, it's still a bit misleading to add the reference operator in from of the buf argument. This patch simply removes that operator in order to make use of buf slightly better to the eyes. Signed-off-by: Markus Pargmann Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_debugfs.c b/drivers/usb/musb/musb_debugfs.c index ad3701a..bb13e04 100644 --- a/drivers/usb/musb/musb_debugfs.c +++ b/drivers/usb/musb/musb_debugfs.c @@ -194,7 +194,7 @@ static ssize_t musb_test_mode_write(struct file *file, memset(buf, 0x00, sizeof(buf)); - if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) return -EFAULT; if (!strncmp(buf, "force host", 9)) -- cgit v0.10.2 From 46dc0fc4befac07e71621d122a72194d2ae61c36 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 4 Jan 2015 18:45:37 +0100 Subject: USB: option: clean up blacklist handling Clean up the unnecessarily complicated blacklist implementation somewhat. Signed-off-by: Johan Hovold diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index efdcee1..f0c0c53 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -507,18 +507,10 @@ static void option_instat_callback(struct urb *urb); #define VIATELECOM_VENDOR_ID 0x15eb #define VIATELECOM_PRODUCT_CDS7 0x0001 -/* some devices interfaces need special handling due to a number of reasons */ -enum option_blacklist_reason { - OPTION_BLACKLIST_NONE = 0, - OPTION_BLACKLIST_SENDSETUP = 1, - OPTION_BLACKLIST_RESERVED_IF = 2 -}; - -#define MAX_BL_NUM 11 struct option_blacklist_info { - /* bitfield of interface numbers for OPTION_BLACKLIST_SENDSETUP */ + /* bitmask of interface numbers blacklisted for send_setup */ const unsigned long sendsetup; - /* bitfield of interface numbers for OPTION_BLACKLIST_RESERVED_IF */ + /* bitmask of interface numbers that are reserved */ const unsigned long reserved; }; @@ -1822,36 +1814,13 @@ struct option_private { module_usb_serial_driver(serial_drivers, option_ids); -static bool is_blacklisted(const u8 ifnum, enum option_blacklist_reason reason, - const struct option_blacklist_info *blacklist) -{ - unsigned long num; - const unsigned long *intf_list; - - if (blacklist) { - if (reason == OPTION_BLACKLIST_SENDSETUP) - intf_list = &blacklist->sendsetup; - else if (reason == OPTION_BLACKLIST_RESERVED_IF) - intf_list = &blacklist->reserved; - else { - BUG_ON(reason); - return false; - } - - for_each_set_bit(num, intf_list, MAX_BL_NUM + 1) { - if (num == ifnum) - return true; - } - } - return false; -} - static int option_probe(struct usb_serial *serial, const struct usb_device_id *id) { struct usb_interface_descriptor *iface_desc = &serial->interface->cur_altsetting->desc; struct usb_device_descriptor *dev_desc = &serial->dev->descriptor; + const struct option_blacklist_info *blacklist; /* Never bind to the CD-Rom emulation interface */ if (iface_desc->bInterfaceClass == 0x08) @@ -1862,10 +1831,9 @@ static int option_probe(struct usb_serial *serial, * the same class/subclass/protocol as the serial interfaces. Look at * the Windows driver .INF files for reserved interface numbers. */ - if (is_blacklisted( - iface_desc->bInterfaceNumber, - OPTION_BLACKLIST_RESERVED_IF, - (const struct option_blacklist_info *) id->driver_info)) + blacklist = (void *)id->driver_info; + if (blacklist && test_bit(iface_desc->bInterfaceNumber, + &blacklist->reserved)) return -ENODEV; /* * Don't bind network interface on Samsung GT-B3730, it is handled by @@ -1876,8 +1844,8 @@ static int option_probe(struct usb_serial *serial, iface_desc->bInterfaceClass != USB_CLASS_CDC_DATA) return -ENODEV; - /* Store device id so we can use it during attach. */ - usb_set_serial_data(serial, (void *)id); + /* Store the blacklist info so we can use it during attach. */ + usb_set_serial_data(serial, (void *)blacklist); return 0; } @@ -1885,7 +1853,7 @@ static int option_probe(struct usb_serial *serial, static int option_attach(struct usb_serial *serial) { struct usb_interface_descriptor *iface_desc; - const struct usb_device_id *id; + const struct option_blacklist_info *blacklist; struct usb_wwan_intf_private *data; struct option_private *priv; @@ -1899,16 +1867,16 @@ static int option_attach(struct usb_serial *serial) return -ENOMEM; } - /* Retrieve device id stored at probe. */ - id = usb_get_serial_data(serial); + /* Retrieve blacklist info stored at probe. */ + blacklist = usb_get_serial_data(serial); + iface_desc = &serial->interface->cur_altsetting->desc; priv->bInterfaceNumber = iface_desc->bInterfaceNumber; data->private = priv; - if (!is_blacklisted(iface_desc->bInterfaceNumber, - OPTION_BLACKLIST_SENDSETUP, - (struct option_blacklist_info *)id->driver_info)) { + if (!blacklist || !test_bit(iface_desc->bInterfaceNumber, + &blacklist->sendsetup)) { data->send_setup = option_send_setup; } spin_lock_init(&data->susp_lock); -- cgit v0.10.2 From 3e264ffc831e70c4a3ecc8c2eeb5bfbc1c8a30d3 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 19 Jan 2015 13:32:58 +0000 Subject: USB: mos7840: remove unused code There is old, unused code that is #defined out by the use of NOTMOS7840 and NOTMCS7840 - these are not defined anywhere. If NOTMOS7840 is defined then the code will break on null pointer dereferences on mos7840_port. So the code is currently unused, and broken anyway, so why not just remove it. Signed-off-by: Colin Ian King Signed-off-by: Johan Hovold diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 220b4be..e4473a9 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -1309,35 +1309,6 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *current_position = data; unsigned char *data1; -#ifdef NOTMOS7840 - Data = 0x00; - status = mos7840_get_uart_reg(port, LINE_CONTROL_REGISTER, &Data); - mos7840_port->shadowLCR = Data; - dev_dbg(&port->dev, "%s: LINE_CONTROL_REGISTER is %x\n", __func__, Data); - dev_dbg(&port->dev, "%s: mos7840_port->shadowLCR is %x\n", __func__, mos7840_port->shadowLCR); - - /* Data = 0x03; */ - /* status = mos7840_set_uart_reg(port,LINE_CONTROL_REGISTER,Data); */ - /* mos7840_port->shadowLCR=Data;//Need to add later */ - - Data |= SERIAL_LCR_DLAB; /* data latch enable in LCR 0x80 */ - status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); - - /* Data = 0x0c; */ - /* status = mos7840_set_uart_reg(port,DIVISOR_LATCH_LSB,Data); */ - Data = 0x00; - status = mos7840_get_uart_reg(port, DIVISOR_LATCH_LSB, &Data); - dev_dbg(&port->dev, "%s: DLL value is %x\n", __func__, Data); - - Data = 0x0; - status = mos7840_get_uart_reg(port, DIVISOR_LATCH_MSB, &Data); - dev_dbg(&port->dev, "%s: DLM value is %x\n", __func__, Data); - - Data = Data & ~SERIAL_LCR_DLAB; - dev_dbg(&port->dev, "%s: mos7840_port->shadowLCR is %x\n", __func__, mos7840_port->shadowLCR); - status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); -#endif - if (mos7840_port_paranoia_check(port, __func__)) return -1; @@ -1614,37 +1585,6 @@ static int mos7840_calc_baud_rate_divisor(struct usb_serial_port *port, *clk_sel_val = 0x70; } return 0; - -#ifdef NOTMCS7840 - - for (i = 0; i < ARRAY_SIZE(mos7840_divisor_table); i++) { - if (mos7840_divisor_table[i].BaudRate == baudrate) { - *divisor = mos7840_divisor_table[i].Divisor; - return 0; - } - } - - /* After trying for all the standard baud rates * - * Try calculating the divisor for this baud rate */ - - if (baudrate > 75 && baudrate < 230400) { - /* get the divisor */ - custom = (__u16) (230400L / baudrate); - - /* Check for round off */ - round1 = (__u16) (2304000L / baudrate); - round = (__u16) (round1 - (custom * 10)); - if (round > 4) - custom++; - *divisor = custom; - - dev_dbg(&port->dev, " Baud %d = %d\n", baudrate, custom); - return 0; - } - - dev_dbg(&port->dev, "%s", " Baud calculation Failed...\n"); - return -1; -#endif } /***************************************************************************** -- cgit v0.10.2 From df90f8381972369ff33580e5c4b3c3eea91cc91b Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 14 Jan 2015 23:59:48 +0300 Subject: usb: gadget: uvc: fix some error codes We're basically saying ERR_CAST(NULL) and PTR_ERR(NULL) here, which is nonsensical. Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index f69f47a..ff30588 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -148,7 +148,7 @@ static struct config_item *uvcg_control_header_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->desc.bLength = UVC_DT_HEADER_SIZE(1); h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -840,7 +840,7 @@ static int uvcg_streaming_header_allow_link(struct config_item *src, format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL); if (!format_ptr) { - ret = PTR_ERR(format_ptr); + ret = -ENOMEM; goto out; } ret = 0; @@ -960,7 +960,7 @@ static struct config_item h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&h->formats); h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -1278,7 +1278,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->frame.b_descriptor_type = USB_DT_CS_INTERFACE; h->frame.b_frame_index = 1; @@ -1563,7 +1563,7 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->desc.bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE; h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -1772,7 +1772,7 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->desc.bLength = UVC_DT_FORMAT_MJPEG_SIZE; h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -2124,7 +2124,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, count += 2; /* color_matching, NULL */ *class_array = kcalloc(count, sizeof(void *), GFP_KERNEL); if (!*class_array) { - ret = PTR_ERR(*class_array); + ret = -ENOMEM; goto unlock; } -- cgit v0.10.2 From ceeb010ba2534f0b78efde6b3aa7d208148df92f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 15 Jan 2015 00:02:18 +0300 Subject: usb: gadget: uvc: remove an impossible condition "num" is a u32 so "(num > 0xFFFFFFFF)" is never true. Also the range is already checked in kstrtou32(). Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index ff30588..6d9c681 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1156,8 +1156,6 @@ static inline int __uvcg_fill_frm_intrv(char *buf, void *priv) ret = kstrtou32(buf, 0, &num); if (ret) return ret; - if (num > 0xFFFFFFFF) - return -EINVAL; interv = priv; **interv = cpu_to_le32(num); -- cgit v0.10.2 From c5b2dc68a7a7e31d8e88b3bf9d45bd9e78e9fb78 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 15 Jan 2015 00:03:08 +0300 Subject: usb: gadget: uvc: memory leak in uvcg_frame_make() We need to add a kfree(h) on an error path. Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 6d9c681..09028d2 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1300,6 +1300,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, h->fmt_type = UVCG_MJPEG; } else { mutex_unlock(&opts->lock); + kfree(h); return ERR_PTR(-EINVAL); } ++fmt->num_frames; -- cgit v0.10.2 From 3c4c733ca9e3f88e3107b6e23b9789bf7769e1f4 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 15 Jan 2015 00:06:35 +0300 Subject: usb: gadget: uvc: cleanup UVCG_FRAME_ATTR macro 1) Change "conv" an "vnoc" to "to_cpu_endian" to "to_little_endian". 2) No need to check the "limit" because that is already handled in kstrtoXX so delete that parameter along with the check. 3) By using a "bits" parameter, we can combine the "uxx" parameter and the "str2u" parameters. 4) The kstrtou##bits() conversion does not need to be done under the mutex so move it to the start of the function. 5) Change the name of "identity_conv" to "noop_conversion". Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 09028d2..cc2a613 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1030,7 +1030,7 @@ static struct configfs_item_operations uvcg_frame_item_ops = { .store_attribute = uvcg_frame_attr_store, }; -#define UVCG_FRAME_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +#define UVCG_FRAME_ATTR(cname, aname, to_cpu_endian, to_little_endian, bits) \ static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\ { \ struct f_uvc_opts *opts; \ @@ -1044,7 +1044,7 @@ static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(f->frame.cname)); \ + result = sprintf(page, "%d\n", to_cpu_endian(f->frame.cname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1059,7 +1059,11 @@ static ssize_t uvcg_frame_##cname##_store(struct uvcg_frame *f, \ struct uvcg_format *fmt; \ struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ int ret; \ - uxx num; \ + u##bits num; \ + \ + ret = kstrtou##bits(page, 0, &num); \ + if (ret) \ + return ret; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ @@ -1073,15 +1077,7 @@ static ssize_t uvcg_frame_##cname##_store(struct uvcg_frame *f, \ goto end; \ } \ \ - ret = str2u(page, 0, &num); \ - if (ret) \ - goto end; \ - \ - if (num > limit) { \ - ret = -EINVAL; \ - goto end; \ - } \ - f->frame.cname = vnoc(num); \ + f->frame.cname = to_little_endian(num); \ ret = len; \ end: \ mutex_unlock(&opts->lock); \ @@ -1095,24 +1091,20 @@ static struct uvcg_frame_attribute \ uvcg_frame_##cname##_show, \ uvcg_frame_##cname##_store) -#define identity_conv(x) (x) +#define noop_conversion(x) (x) -UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, identity_conv, kstrtou8, u8, - identity_conv, 0xFF); -UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, kstrtou16, u16, cpu_to_le16, - 0xFFFF); -UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, kstrtou16, u16, cpu_to_le16, - 0xFFFF); -UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, kstrtou32, u32, - cpu_to_le32, 0xFFFFFFFF); -UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, kstrtou32, u32, - cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, noop_conversion, + noop_conversion, 8); +UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, cpu_to_le16, 16); +UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, cpu_to_le16, 16); +UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, cpu_to_le32, 32); +UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, cpu_to_le32, 32); UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, - le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); + le32_to_cpu, cpu_to_le32, 32); UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, - le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); + le32_to_cpu, cpu_to_le32, 32); -#undef identity_conv +#undef noop_conversion #undef UVCG_FRAME_ATTR -- cgit v0.10.2 From da89dba1a0c69ed01e33ebf41327b90993d08492 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 15 Jan 2015 02:45:12 +0000 Subject: usb: phy: make GPIOs optional for the generic phy The use of GPIOs should be optional for the generic phy, otherwise the Altera SOCFPGA platform at least is broken. Fixes breakage caused by a combination of e9f2cefb0cd "usb: phy: generic: migrate to gpio_desc" and 135b3c4304d "usb: dwc2: platform: add generic PHY framework support". Reviewed-by: Robert Jarzmik Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index dd05254..9a826ff 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -218,10 +218,10 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, clk_rate = 0; needs_vcc = of_property_read_bool(node, "vcc-supply"); - nop->gpiod_reset = devm_gpiod_get(dev, "reset-gpios"); + nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset-gpios"); err = PTR_ERR(nop->gpiod_reset); if (!err) { - nop->gpiod_vbus = devm_gpiod_get(dev, + nop->gpiod_vbus = devm_gpiod_get_optional(dev, "vbus-detect-gpio"); err = PTR_ERR(nop->gpiod_vbus); } -- cgit v0.10.2 From 27f387059bedc3c4b3eb5b4c8bfe229dba59ce24 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 15 Jan 2015 13:40:04 +0200 Subject: usb: gadget: ethernet: re-use %pM specifier to print MAC Instead of custom approach the patch converts code to use %pM specifier to print MAC. Signed-off-by: Andy Shevchenko Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index 6e6f876..f1fd777 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -729,9 +729,7 @@ static int get_ether_addr_str(u8 dev_addr[ETH_ALEN], char *str, int len) if (len < 18) return -EINVAL; - snprintf(str, len, "%02x:%02x:%02x:%02x:%02x:%02x", - dev_addr[0], dev_addr[1], dev_addr[2], - dev_addr[3], dev_addr[4], dev_addr[5]); + snprintf(str, len, "%pM", dev_addr); return 18; } -- cgit v0.10.2 From b0bb9bb6ce01e37e47db04a31fd93dbe2d2be3d3 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 15 Jan 2015 19:21:46 +0000 Subject: Revert "usb: dwc2: add bus suspend/resume for dwc2" This reverts commit 0cf884e819e05437287a668b9bfcc198bab6329c. Even after applying the follow-on patch at https://patchwork.kernel.org/patch/5325111 there are still problems with device connect on the Altera SOCFPGA platform at least. One possible fix would be to add a whitelist to enable suspend/resume on platforms where it does work correctly. Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index a0cd9db..755e16b 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1473,30 +1473,6 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) } } -static void dwc2_port_resume(struct dwc2_hsotg *hsotg) -{ - u32 hprt0; - - /* After clear the Stop PHY clock bit, we should wait for a moment - * for PLL work stable with clock output. - */ - writel(0, hsotg->regs + PCGCTL); - usleep_range(2000, 4000); - - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_RES; - writel(hprt0, hsotg->regs + HPRT0); - hprt0 &= ~HPRT0_SUSP; - /* according to USB2.0 Spec 7.1.7.7, the host must send the resume - * signal for at least 20ms - */ - usleep_range(20000, 25000); - - hprt0 &= ~HPRT0_RES; - writel(hprt0, hsotg->regs + HPRT0); - hsotg->lx_state = DWC2_L0; -} - /* Handles hub class-specific requests */ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, u16 wvalue, u16 windex, char *buf, u16 wlength) @@ -1542,7 +1518,17 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, case USB_PORT_FEAT_SUSPEND: dev_dbg(hsotg->dev, "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); - dwc2_port_resume(hsotg); + writel(0, hsotg->regs + PCGCTL); + usleep_range(20000, 40000); + + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + writel(hprt0, hsotg->regs + HPRT0); + hprt0 &= ~HPRT0_SUSP; + usleep_range(100000, 150000); + + hprt0 &= ~HPRT0_RES; + writel(hprt0, hsotg->regs + HPRT0); break; case USB_PORT_FEAT_POWER: @@ -2315,55 +2301,6 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) usleep_range(1000, 3000); } -static int _dwc2_hcd_suspend(struct usb_hcd *hcd) -{ - struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - u32 hprt0; - - if (!((hsotg->op_state == OTG_STATE_B_HOST) || - (hsotg->op_state == OTG_STATE_A_HOST))) - return 0; - - /* TODO: We get into suspend from 'on' state, maybe we need to do - * something if we get here from DWC2_L1(LPM sleep) state one day. - */ - if (hsotg->lx_state != DWC2_L0) - return 0; - - hprt0 = dwc2_read_hprt0(hsotg); - if (hprt0 & HPRT0_CONNSTS) { - dwc2_port_suspend(hsotg, 1); - } else { - u32 pcgctl = readl(hsotg->regs + PCGCTL); - - pcgctl |= PCGCTL_STOPPCLK; - writel(pcgctl, hsotg->regs + PCGCTL); - } - - return 0; -} - -static int _dwc2_hcd_resume(struct usb_hcd *hcd) -{ - struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - u32 hprt0; - - if (!((hsotg->op_state == OTG_STATE_B_HOST) || - (hsotg->op_state == OTG_STATE_A_HOST))) - return 0; - - if (hsotg->lx_state != DWC2_L2) - return 0; - - hprt0 = dwc2_read_hprt0(hsotg); - if ((hprt0 & HPRT0_CONNSTS) && (hprt0 & HPRT0_SUSP)) - dwc2_port_resume(hsotg); - else - writel(0, hsotg->regs + PCGCTL); - - return 0; -} - /* Returns the current frame number */ static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd) { @@ -2734,9 +2671,6 @@ static struct hc_driver dwc2_hc_driver = { .hub_status_data = _dwc2_hcd_hub_status_data, .hub_control = _dwc2_hcd_hub_control, .clear_tt_buffer_complete = _dwc2_hcd_clear_tt_buffer_complete, - - .bus_suspend = _dwc2_hcd_suspend, - .bus_resume = _dwc2_hcd_resume, }; /* -- cgit v0.10.2 From 16272ae77e0f167af70eabd476059137d64569a8 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 15 Jan 2015 20:14:10 +0000 Subject: MAINTAINERS: update maintainer entry for dwc2 driver Update the MAINTAINERS entry for the dwc2 driver to show John Youn as the new maintainer Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi diff --git a/MAINTAINERS b/MAINTAINERS index 2fa3853..97de006 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3028,7 +3028,7 @@ S: Maintained F: drivers/platform/x86/dell-wmi.c DESIGNWARE USB2 DRD IP DRIVER -M: Paul Zimmerman +M: John Youn L: linux-usb@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb.git S: Maintained -- cgit v0.10.2 From 06ab8b04d232c80cb4b23984290ce413058cc975 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Fri, 16 Jan 2015 15:14:27 +0100 Subject: usb: gadget: uvc: preserve the address passed to kfree() __uvcg_fill_strm() called from __uvcg_iter_stream_cls() might have advanced the "data" even if __uvcg_iter_stream_cls() returns an error, so use a backup copy as an argument to kfree(). Acked-by: Laurent Pinchart Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index cc2a613..49f25e8 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -2086,7 +2086,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; struct uvc_descriptor_header ***class_array, **cl_arr; struct uvcg_streaming_header *target_hdr; - void *data; + void *data, *data_save; size_t size = 0, count = 0; int ret = -EINVAL; @@ -2119,7 +2119,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, goto unlock; } - data = kzalloc(size, GFP_KERNEL); + data = data_save = kzalloc(size, GFP_KERNEL); if (!data) { kfree(*class_array); *class_array = NULL; @@ -2132,7 +2132,11 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, if (ret) { kfree(*class_array); *class_array = NULL; - kfree(data); + /* + * __uvcg_fill_strm() called from __uvcg_iter_stream_cls() + * might have advanced the "data", so use a backup copy + */ + kfree(data_save); goto unlock; } *cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching; -- cgit v0.10.2 From 3d040de802d82fa744f599d8a886dda3462fb6e6 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:54:15 +0300 Subject: usb: dwc2: hcd: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 755e16b..c3e66f0 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1608,7 +1608,9 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, hub_desc->bDescLength = 9; hub_desc->bDescriptorType = 0x29; hub_desc->bNbrPorts = 1; - hub_desc->wHubCharacteristics = cpu_to_le16(0x08); + hub_desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_COMMON_LPSM | + HUB_CHAR_INDV_PORT_OCPM); hub_desc->bPwrOn2PwrGood = 1; hub_desc->bHubContrCurrent = 0; hub_desc->u.hs.DeviceRemovable[0] = 0; -- cgit v0.10.2 From d906d61cf8a3080b3bf7fb2e12efc2b645d514d4 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:55:55 +0300 Subject: usb: dummy_hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Acked-by: Alan Stern Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 9c59880..270c1ec 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -1924,7 +1924,9 @@ ss_hub_descriptor(struct usb_hub_descriptor *desc) memset(desc, 0, sizeof *desc); desc->bDescriptorType = 0x2a; desc->bDescLength = 12; - desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = 1; desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ desc->u.ss.DeviceRemovable = 0xffff; @@ -1935,7 +1937,9 @@ static inline void hub_descriptor(struct usb_hub_descriptor *desc) memset(desc, 0, sizeof *desc); desc->bDescriptorType = 0x29; desc->bDescLength = 9; - desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = 1; desc->u.hs.DeviceRemovable[0] = 0xff; desc->u.hs.DeviceRemovable[1] = 0xff; -- cgit v0.10.2 From 6e7384320ffa28a3732513f51fc56119003edbd3 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Tue, 13 Jan 2015 14:23:19 +0200 Subject: phy: ti-pipe3: Disable clocks on system suspend On system suspend, the runtime_suspend() driver hook doesn't get called for USB phy and so the clocks are not disabled in the driver. This causes the L3INIT_960M_GFCLK and L3INIT_480M_GFCLK to remain active on the DRA7 platform while in system suspend. In case of pcie-phy, the runtime_suspend hook gets called after the suspend hook so we introduce a flag phy->enabled to keep track if our clocks are enabled or not to prevent multiple enable/disables. Add suspend/resume hooks to the driver. Move enabling/disabling clock code into helper functions. Reported-by: Nishant Menon Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 465de2c..8c85468 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -28,6 +28,7 @@ #include #include #include +#include #define PLL_STATUS 0x00000004 #define PLL_GO 0x00000008 @@ -82,6 +83,8 @@ struct ti_pipe3 { struct clk *refclk; struct clk *div_clk; struct pipe3_dpll_map *dpll_map; + bool enabled; + spinlock_t lock; /* serialize clock enable/disable */ }; static struct pipe3_dpll_map dpll_map_usb[] = { @@ -307,6 +310,7 @@ static int ti_pipe3_probe(struct platform_device *pdev) return -ENOMEM; phy->dev = &pdev->dev; + spin_lock_init(&phy->lock); if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { match = of_match_device(of_match_ptr(ti_pipe3_id_table), @@ -427,24 +431,14 @@ static int ti_pipe3_remove(struct platform_device *pdev) #ifdef CONFIG_PM -static int ti_pipe3_runtime_suspend(struct device *dev) +static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy) { - struct ti_pipe3 *phy = dev_get_drvdata(dev); - - if (!IS_ERR(phy->wkupclk)) - clk_disable_unprepare(phy->wkupclk); - if (!IS_ERR(phy->refclk)) - clk_disable_unprepare(phy->refclk); - if (!IS_ERR(phy->div_clk)) - clk_disable_unprepare(phy->div_clk); - - return 0; -} + int ret = 0; + unsigned long flags; -static int ti_pipe3_runtime_resume(struct device *dev) -{ - u32 ret = 0; - struct ti_pipe3 *phy = dev_get_drvdata(dev); + spin_lock_irqsave(&phy->lock, flags); + if (phy->enabled) + goto err1; if (!IS_ERR(phy->refclk)) { ret = clk_prepare_enable(phy->refclk); @@ -469,6 +463,9 @@ static int ti_pipe3_runtime_resume(struct device *dev) goto err3; } } + + phy->enabled = true; + spin_unlock_irqrestore(&phy->lock, flags); return 0; err3: @@ -480,19 +477,77 @@ err2: clk_disable_unprepare(phy->refclk); err1: + spin_unlock_irqrestore(&phy->lock, flags); + return ret; +} + +static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy) +{ + unsigned long flags; + + spin_lock_irqsave(&phy->lock, flags); + if (!phy->enabled) { + spin_unlock_irqrestore(&phy->lock, flags); + return; + } + + if (!IS_ERR(phy->wkupclk)) + clk_disable_unprepare(phy->wkupclk); + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); + if (!IS_ERR(phy->div_clk)) + clk_disable_unprepare(phy->div_clk); + phy->enabled = false; + spin_unlock_irqrestore(&phy->lock, flags); +} + +static int ti_pipe3_runtime_suspend(struct device *dev) +{ + struct ti_pipe3 *phy = dev_get_drvdata(dev); + + ti_pipe3_disable_clocks(phy); + return 0; +} + +static int ti_pipe3_runtime_resume(struct device *dev) +{ + struct ti_pipe3 *phy = dev_get_drvdata(dev); + int ret = 0; + + ret = ti_pipe3_enable_clocks(phy); return ret; } +static int ti_pipe3_suspend(struct device *dev) +{ + struct ti_pipe3 *phy = dev_get_drvdata(dev); + + ti_pipe3_disable_clocks(phy); + return 0; +} + +static int ti_pipe3_resume(struct device *dev) +{ + struct ti_pipe3 *phy = dev_get_drvdata(dev); + int ret; + + ret = ti_pipe3_enable_clocks(phy); + if (ret) + return ret; + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + return 0; +} +#endif + static const struct dev_pm_ops ti_pipe3_pm_ops = { SET_RUNTIME_PM_OPS(ti_pipe3_runtime_suspend, ti_pipe3_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(ti_pipe3_suspend, ti_pipe3_resume) }; -#define DEV_PM_OPS (&ti_pipe3_pm_ops) -#else -#define DEV_PM_OPS NULL -#endif - #ifdef CONFIG_OF static const struct of_device_id ti_pipe3_id_table[] = { { @@ -520,7 +575,7 @@ static struct platform_driver ti_pipe3_driver = { .remove = ti_pipe3_remove, .driver = { .name = "ti-pipe3", - .pm = DEV_PM_OPS, + .pm = &ti_pipe3_pm_ops, .of_match_table = of_match_ptr(ti_pipe3_id_table), }, }; -- cgit v0.10.2 From 7f33912d2978796662473f1f5e1fcfc387717e15 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Tue, 13 Jan 2015 14:23:20 +0200 Subject: phy: ti-pipe3: Fix SATA across suspend/resume Failed test case: Boot without SATA drive connected. Suspend/resume the board and then connect SATA drive. It fails to enumerate. Due to Errata i783 "SATA Lockup After SATA DPLL Unlock/Relock" we can't allow SATA DPLL to be in the unlocked state. The SATA refclk (sata_ref_clk) is the source of the SATA_DPLL. This clock is being controlled only by the AHCI SATA driver and is shut off during system suspend (if the SATA drive was not already attached) causing the SATA DPLL to be unlocked and so causing errata i783. To prevent sata_ref_clk from being disabled, we add the control of this clock to the SATA PHY driver and prevent it from being disabled. This also fixes the issue of SATA not working on OMAP5/DRA7 when AHCI platform driver is built as a module. NOTE: Device tree changes also required for OMAP5 & DRA7. Signed-off-by: Roger Quadros Signed-off-by: Kishon Vijay Abraham I diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c index 8c85468..95c88f9 100644 --- a/drivers/phy/phy-ti-pipe3.c +++ b/drivers/phy/phy-ti-pipe3.c @@ -85,6 +85,8 @@ struct ti_pipe3 { struct pipe3_dpll_map *dpll_map; bool enabled; spinlock_t lock; /* serialize clock enable/disable */ + /* the below flag is needed specifically for SATA */ + bool refclk_enabled; }; static struct pipe3_dpll_map dpll_map_usb[] = { @@ -337,21 +339,24 @@ static int ti_pipe3_probe(struct platform_device *pdev) } } + phy->refclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->refclk)) { + dev_err(&pdev->dev, "unable to get refclk\n"); + /* older DTBs have missing refclk in SATA PHY + * so don't bail out in case of SATA PHY. + */ + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) + return PTR_ERR(phy->refclk); + } + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); if (IS_ERR(phy->wkupclk)) { dev_err(&pdev->dev, "unable to get wkupclk\n"); return PTR_ERR(phy->wkupclk); } - - phy->refclk = devm_clk_get(phy->dev, "refclk"); - if (IS_ERR(phy->refclk)) { - dev_err(&pdev->dev, "unable to get refclk\n"); - return PTR_ERR(phy->refclk); - } } else { phy->wkupclk = ERR_PTR(-ENODEV); - phy->refclk = ERR_PTR(-ENODEV); } if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { @@ -430,6 +435,29 @@ static int ti_pipe3_remove(struct platform_device *pdev) } #ifdef CONFIG_PM +static int ti_pipe3_enable_refclk(struct ti_pipe3 *phy) +{ + if (!IS_ERR(phy->refclk) && !phy->refclk_enabled) { + int ret; + + ret = clk_prepare_enable(phy->refclk); + if (ret) { + dev_err(phy->dev, "Failed to enable refclk %d\n", ret); + return ret; + } + phy->refclk_enabled = true; + } + + return 0; +} + +static void ti_pipe3_disable_refclk(struct ti_pipe3 *phy) +{ + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); + + phy->refclk_enabled = false; +} static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy) { @@ -440,13 +468,9 @@ static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy) if (phy->enabled) goto err1; - if (!IS_ERR(phy->refclk)) { - ret = clk_prepare_enable(phy->refclk); - if (ret) { - dev_err(phy->dev, "Failed to enable refclk %d\n", ret); - goto err1; - } - } + ret = ti_pipe3_enable_refclk(phy); + if (ret) + goto err1; if (!IS_ERR(phy->wkupclk)) { ret = clk_prepare_enable(phy->wkupclk); @@ -476,6 +500,7 @@ err2: if (!IS_ERR(phy->refclk)) clk_disable_unprepare(phy->refclk); + ti_pipe3_disable_refclk(phy); err1: spin_unlock_irqrestore(&phy->lock, flags); return ret; @@ -493,8 +518,9 @@ static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy) if (!IS_ERR(phy->wkupclk)) clk_disable_unprepare(phy->wkupclk); - if (!IS_ERR(phy->refclk)) - clk_disable_unprepare(phy->refclk); + /* Don't disable refclk for SATA PHY due to Errata i783 */ + if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) + ti_pipe3_disable_refclk(phy); if (!IS_ERR(phy->div_clk)) clk_disable_unprepare(phy->div_clk); phy->enabled = false; -- cgit v0.10.2 From 147fd9d6bbb6b4bf1c320089494c0cb1bf1636a2 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 14 Jan 2015 09:10:00 +0800 Subject: phy: fix return value check in armada375_usb_phy_probe() In case of error, the function devm_ioremap_resource() returns ERR_PTR() and never returns NULL. The NULL test in the return value check should be replaced with IS_ERR(). Signed-off-by: Wei Yongjun Signed-off-by: Kishon Vijay Abraham I diff --git a/drivers/phy/phy-armada375-usb2.c b/drivers/phy/phy-armada375-usb2.c index ac7d99d..7c99ca2 100644 --- a/drivers/phy/phy-armada375-usb2.c +++ b/drivers/phy/phy-armada375-usb2.c @@ -118,8 +118,8 @@ static int armada375_usb_phy_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); usb_cluster_base = devm_ioremap_resource(&pdev->dev, res); - if (!usb_cluster_base) - return -ENOMEM; + if (IS_ERR(usb_cluster_base)) + return PTR_ERR(usb_cluster_base); phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops); if (IS_ERR(phy)) { -- cgit v0.10.2 From de6e0f84bebc0daadca46e735f4b2be3e006d05d Mon Sep 17 00:00:00 2001 From: Gabriel FERNANDEZ Date: Wed, 14 Jan 2015 10:54:57 +0100 Subject: phy: miphy28lp: Pass sysconfig register offsets via syscfg dt property. Based on Arnds review comments here https://lkml.org/lkml/2014/11/13/161, update the miphy28lp phy driver to access sysconfig register offsets via syscfg dt property. This is because the reg property should not be mixing address spaces like it does currently for miphy28lp. This change then also aligns us to how other platforms such as keystone and bcm7445 pass there syscon offsets via DT. I have updated the miphy28lp phy driver same way as Peter's implementation. Signed-off-by: Gabriel Fernandez Signed-off-by: Kishon Vijay Abraham I diff --git a/Documentation/devicetree/bindings/phy/phy-miphy28lp.txt b/Documentation/devicetree/bindings/phy/phy-miphy28lp.txt index 46a135d..89caa88 100644 --- a/Documentation/devicetree/bindings/phy/phy-miphy28lp.txt +++ b/Documentation/devicetree/bindings/phy/phy-miphy28lp.txt @@ -26,6 +26,7 @@ Required properties (port (child) node): filled in "reg". It can also contain the offset of the system configuration registers used as glue-logic to setup the device for SATA/PCIe or USB3 devices. +- st,syscfg : Offset of the parent configuration register. - resets : phandle to the parent reset controller. - reset-names : Associated name must be "miphy-sw-rst". @@ -54,18 +55,12 @@ example: phy_port0: port@9b22000 { reg = <0x9b22000 0xff>, <0x9b09000 0xff>, - <0x9b04000 0xff>, - <0x114 0x4>, /* sysctrl MiPHY cntrl */ - <0x818 0x4>, /* sysctrl MiPHY status*/ - <0xe0 0x4>, /* sysctrl PCIe */ - <0xec 0x4>; /* sysctrl SATA */ + <0x9b04000 0xff>; reg-names = "sata-up", "pcie-up", - "pipew", - "miphy-ctrl-glue", - "miphy-status-glue", - "pcie-glue", - "sata-glue"; + "pipew"; + + st,syscfg = <0x114 0x818 0xe0 0xec>; #phy-cells = <1>; st,osc-rdy; reset-names = "miphy-sw-rst"; @@ -75,18 +70,13 @@ example: phy_port1: port@9b2a000 { reg = <0x9b2a000 0xff>, <0x9b19000 0xff>, - <0x9b14000 0xff>, - <0x118 0x4>, - <0x81c 0x4>, - <0xe4 0x4>, - <0xf0 0x4>; + <0x9b14000 0xff>; reg-names = "sata-up", "pcie-up", - "pipew", - "miphy-ctrl-glue", - "miphy-status-glue", - "pcie-glue", - "sata-glue"; + "pipew"; + + st,syscfg = <0x118 0x81c 0xe4 0xf0>; + #phy-cells = <1>; st,osc-force-ext; reset-names = "miphy-sw-rst"; @@ -95,13 +85,12 @@ example: phy_port2: port@8f95000 { reg = <0x8f95000 0xff>, - <0x8f90000 0xff>, - <0x11c 0x4>, - <0x820 0x4>; + <0x8f90000 0xff>; reg-names = "pipew", - "usb3-up", - "miphy-ctrl-glue", - "miphy-status-glue"; + "usb3-up"; + + st,syscfg = <0x11c 0x820>; + #phy-cells = <1>; reset-names = "miphy-sw-rst"; resets = <&softreset STIH407_MIPHY2_SOFTRESET>; @@ -125,4 +114,4 @@ example: Macro definitions for the supported miphy configuration can be found in: -include/dt-bindings/phy/phy-miphy28lp.h +include/dt-bindings/phy/phy.h diff --git a/drivers/phy/phy-miphy28lp.c b/drivers/phy/phy-miphy28lp.c index 27fa62c..9b2848e 100644 --- a/drivers/phy/phy-miphy28lp.c +++ b/drivers/phy/phy-miphy28lp.c @@ -194,6 +194,14 @@ #define MIPHY_SATA_BANK_NB 3 #define MIPHY_PCIE_BANK_NB 2 +enum { + SYSCFG_CTRL, + SYSCFG_STATUS, + SYSCFG_PCI, + SYSCFG_SATA, + SYSCFG_REG_MAX, +}; + struct miphy28lp_phy { struct phy *phy; struct miphy28lp_dev *phydev; @@ -211,10 +219,7 @@ struct miphy28lp_phy { u32 sata_gen; /* Sysconfig registers offsets needed to configure the device */ - u32 syscfg_miphy_ctrl; - u32 syscfg_miphy_status; - u32 syscfg_pci; - u32 syscfg_sata; + u32 syscfg_reg[SYSCFG_REG_MAX]; u8 type; }; @@ -834,12 +839,12 @@ static int miphy_osc_is_ready(struct miphy28lp_phy *miphy_phy) if (!miphy_phy->osc_rdy) return 0; - if (!miphy_phy->syscfg_miphy_status) + if (!miphy_phy->syscfg_reg[SYSCFG_STATUS]) return -EINVAL; do { - regmap_read(miphy_dev->regmap, miphy_phy->syscfg_miphy_status, - &val); + regmap_read(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_STATUS], &val); if ((val & MIPHY_OSC_RDY) != MIPHY_OSC_RDY) cpu_relax(); @@ -888,7 +893,7 @@ static int miphy28lp_setup(struct miphy28lp_phy *miphy_phy, u32 miphy_val) int err; struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - if (!miphy_phy->syscfg_miphy_ctrl) + if (!miphy_phy->syscfg_reg[SYSCFG_CTRL]) return -EINVAL; err = reset_control_assert(miphy_phy->miphy_rst); @@ -900,7 +905,8 @@ static int miphy28lp_setup(struct miphy28lp_phy *miphy_phy, u32 miphy_val) if (miphy_phy->osc_force_ext) miphy_val |= MIPHY_OSC_FORCE_EXT; - regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_miphy_ctrl, + regmap_update_bits(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_CTRL], MIPHY_CTRL_MASK, miphy_val); err = reset_control_deassert(miphy_phy->miphy_rst); @@ -917,8 +923,9 @@ static int miphy28lp_init_sata(struct miphy28lp_phy *miphy_phy) struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; int err, sata_conf = SATA_CTRL_SELECT_SATA; - if ((!miphy_phy->syscfg_sata) || (!miphy_phy->syscfg_pci) - || (!miphy_phy->base)) + if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) || + (!miphy_phy->syscfg_reg[SYSCFG_PCI]) || + (!miphy_phy->base)) return -EINVAL; dev_info(miphy_dev->dev, "sata-up mode, addr 0x%p\n", miphy_phy->base); @@ -926,10 +933,11 @@ static int miphy28lp_init_sata(struct miphy28lp_phy *miphy_phy) /* Configure the glue-logic */ sata_conf |= ((miphy_phy->sata_gen - SATA_GEN1) << SATA_SPDMODE); - regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_sata, + regmap_update_bits(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_SATA], SATA_CTRL_MASK, sata_conf); - regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_pci, + regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI], PCIE_CTRL_MASK, SATA_CTRL_SELECT_PCIE); /* MiPHY path and clocking init */ @@ -951,17 +959,19 @@ static int miphy28lp_init_pcie(struct miphy28lp_phy *miphy_phy) struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; int err; - if ((!miphy_phy->syscfg_sata) || (!miphy_phy->syscfg_pci) + if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) || + (!miphy_phy->syscfg_reg[SYSCFG_PCI]) || (!miphy_phy->base) || (!miphy_phy->pipebase)) return -EINVAL; dev_info(miphy_dev->dev, "pcie-up mode, addr 0x%p\n", miphy_phy->base); /* Configure the glue-logic */ - regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_sata, + regmap_update_bits(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_SATA], SATA_CTRL_MASK, SATA_CTRL_SELECT_PCIE); - regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_pci, + regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI], PCIE_CTRL_MASK, SYSCFG_PCIE_PCIE_VAL); /* MiPHY path and clocking init */ @@ -1156,7 +1166,8 @@ static int miphy28lp_probe_resets(struct device_node *node, static int miphy28lp_of_probe(struct device_node *np, struct miphy28lp_phy *miphy_phy) { - struct resource res; + int i; + u32 ctrlreg; miphy_phy->osc_force_ext = of_property_read_bool(np, "st,osc-force-ext"); @@ -1175,18 +1186,10 @@ static int miphy28lp_of_probe(struct device_node *np, if (!miphy_phy->sata_gen) miphy_phy->sata_gen = SATA_GEN1; - if (!miphy28lp_get_resource_byname(np, "miphy-ctrl-glue", &res)) - miphy_phy->syscfg_miphy_ctrl = res.start; - - if (!miphy28lp_get_resource_byname(np, "miphy-status-glue", &res)) - miphy_phy->syscfg_miphy_status = res.start; - - if (!miphy28lp_get_resource_byname(np, "pcie-glue", &res)) - miphy_phy->syscfg_pci = res.start; - - if (!miphy28lp_get_resource_byname(np, "sata-glue", &res)) - miphy_phy->syscfg_sata = res.start; - + for (i = 0; i < SYSCFG_REG_MAX; i++) { + if (!of_property_read_u32_index(np, "st,syscfg", i, &ctrlreg)) + miphy_phy->syscfg_reg[i] = ctrlreg; + } return 0; } -- cgit v0.10.2 From 5402d927ae96bd1413ecc6bac966fd8b6d3b8159 Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Fri, 16 Jan 2015 16:04:11 +0100 Subject: phy: phy-stih407-usb: Pass sysconfig register offsets via syscfg property. Based on Arnds review comments here https://lkml.org/lkml/2014/11/13/161, update the phy driver to not use the reg property to access the sysconfig register offsets. This is because other phy's (miphy28, miphy365) have a combination of memory mapped registers and sysconfig control regs, and we shouldn't be mixing address spaces in the reg property. In addition we would ideally like the sysconfig offsets to be passed via DT in a uniform way. This new method will also allow us to support devices which have sysconfig registers in different banks more easily and it is also analagous to how keystone and bcm7745 platforms pass there syscon offsets in DT. This breaks DT compatibility, but this platform is considered WIP, and is only used by a few developers who are upstreaming support for it. Signed-off-by: Peter Griffin Reviewed-by: Arnd Bergmann Signed-off-by: Maxime Coquelin Signed-off-by: Kishon Vijay Abraham I diff --git a/Documentation/devicetree/bindings/phy/phy-stih407-usb.txt b/Documentation/devicetree/bindings/phy/phy-stih407-usb.txt index 1ef8228..de6a706 100644 --- a/Documentation/devicetree/bindings/phy/phy-stih407-usb.txt +++ b/Documentation/devicetree/bindings/phy/phy-stih407-usb.txt @@ -5,10 +5,7 @@ host controllers (when controlling usb2/1.1 devices) available on STiH407 SoC fa Required properties: - compatible : should be "st,stih407-usb2-phy" -- reg : contain the offset and length of the system configuration registers - used as glue logic to control & parameter phy -- reg-names : the names of the system configuration registers in "reg", should be "param" and "reg" -- st,syscfg : sysconfig register to manage phy parameter at driver level +- st,syscfg : phandle of sysconfig bank plus integer array containing phyparam and phyctrl register offsets - resets : list of phandle and reset specifier pairs. There should be two entries, one for the whole phy and one for the port - reset-names : list of reset signal names. Should be "global" and "port" @@ -19,11 +16,8 @@ Example: usb2_picophy0: usbpicophy@f8 { compatible = "st,stih407-usb2-phy"; - reg = <0xf8 0x04>, /* syscfg 5062 */ - <0xf4 0x04>; /* syscfg 5061 */ - reg-names = "param", "ctrl"; #phy-cells = <0>; - st,syscfg = <&syscfg_core>; + st,syscfg = <&syscfg_core 0x100 0xf4>; resets = <&softreset STIH407_PICOPHY_SOFTRESET>, <&picophyreset STIH407_PICOPHY0_RESET>; reset-names = "global", "port"; diff --git a/drivers/phy/phy-stih407-usb.c b/drivers/phy/phy-stih407-usb.c index 74f0fab..1d5ae5f 100644 --- a/drivers/phy/phy-stih407-usb.c +++ b/drivers/phy/phy-stih407-usb.c @@ -22,6 +22,9 @@ #include #include +#define PHYPARAM_REG 1 +#define PHYCTRL_REG 2 + /* Default PHY_SEL and REFCLKSEL configuration */ #define STIH407_USB_PICOPHY_CTRL_PORT_CONF 0x6 #define STIH407_USB_PICOPHY_CTRL_PORT_MASK 0x1f @@ -93,7 +96,7 @@ static int stih407_usb2_picophy_probe(struct platform_device *pdev) struct device_node *np = dev->of_node; struct phy_provider *phy_provider; struct phy *phy; - struct resource *res; + int ret; phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL); if (!phy_dev) @@ -123,19 +126,19 @@ static int stih407_usb2_picophy_probe(struct platform_device *pdev) return PTR_ERR(phy_dev->regmap); } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ctrl"); - if (!res) { - dev_err(dev, "No ctrl reg found\n"); - return -ENXIO; + ret = of_property_read_u32_index(np, "st,syscfg", PHYPARAM_REG, + &phy_dev->param); + if (ret) { + dev_err(dev, "can't get phyparam offset (%d)\n", ret); + return ret; } - phy_dev->ctrl = res->start; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "param"); - if (!res) { - dev_err(dev, "No param reg found\n"); - return -ENXIO; + ret = of_property_read_u32_index(np, "st,syscfg", PHYCTRL_REG, + &phy_dev->ctrl); + if (ret) { + dev_err(dev, "can't get phyctrl offset (%d)\n", ret); + return ret; } - phy_dev->param = res->start; phy = devm_phy_create(dev, NULL, &stih407_usb2_picophy_data); if (IS_ERR(phy)) { -- cgit v0.10.2 From 3eb598ded1cfb0dc337d4e82cb274e583856338f Mon Sep 17 00:00:00 2001 From: Rickard Strandqvist Date: Sun, 11 Jan 2015 23:23:25 +0100 Subject: uwb: lc-dev: Remove unused function Remove the function uwb_dev_addr_bcast() that is not used anywhere. This was partially found by using a static code analysis program called cppcheck. Signed-off-by: Rickard Strandqvist Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/uwb/lc-dev.c b/drivers/uwb/lc-dev.c index 8c7cfab..7203358 100644 --- a/drivers/uwb/lc-dev.c +++ b/drivers/uwb/lc-dev.c @@ -43,13 +43,6 @@ static inline void uwb_mac_addr_init(struct uwb_mac_addr *addr) memset(&addr->data, 0xff, sizeof(addr->data)); } -/* @returns !0 if a device @addr is a broadcast address */ -static inline int uwb_dev_addr_bcast(const struct uwb_dev_addr *addr) -{ - static const struct uwb_dev_addr bcast = { .data = { 0xff, 0xff } }; - return !uwb_dev_addr_cmp(addr, &bcast); -} - /* * Add callback @new to be called when an event occurs in @rc. */ -- cgit v0.10.2 From 368fbc34c4602accd1ba76d03b4cce07c9c90cf7 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 12 Jan 2015 01:47:49 +0300 Subject: fhci-hub: no need to overwrite byte 3 of the Hub Descriptor The code handling Get Hub Descriptor request copies data from root_hub_des[], then overwrites byte 3 with real 'wHubCharacteristics' value. There's no need to overwrite, we can substitute the real value right into root_hub_des[3]. Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/fhci-hub.c b/drivers/usb/host/fhci-hub.c index 6af2512..4d061a2 100644 --- a/drivers/usb/host/fhci-hub.c +++ b/drivers/usb/host/fhci-hub.c @@ -32,8 +32,8 @@ static u8 root_hub_des[] = { 0x09, /* blength */ 0x29, /* bDescriptorType;hub-descriptor */ 0x01, /* bNbrPorts */ - 0x00, /* wHubCharacteristics */ - 0x00, + 0x11, /* wHubCharacteristics */ + 0x00, /* per-port power, no overcurrent */ 0x01, /* bPwrOn2pwrGood;2ms */ 0x00, /* bHubContrCurrent;0mA */ 0x00, /* DeviceRemoveable */ @@ -272,7 +272,6 @@ int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case GetHubDescriptor: memcpy(buf, root_hub_des, sizeof(root_hub_des)); - buf[3] = 0x11; /* per-port power, no ovrcrnt */ len = (buf[0] < wLength) ? buf[0] : wLength; break; case GetHubStatus: -- cgit v0.10.2 From a5790e9e834bb48cb681620ec680203edb536242 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 12 Jan 2015 01:49:01 +0300 Subject: fhci-hub: kill 'len' variable in fhci_hub_control() The 'len' variable in fhci_hub_control() is write-only, so totally useless. Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/fhci-hub.c b/drivers/usb/host/fhci-hub.c index 4d061a2..a081a78 100644 --- a/drivers/usb/host/fhci-hub.c +++ b/drivers/usb/host/fhci-hub.c @@ -208,7 +208,6 @@ int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, { struct fhci_hcd *fhci = hcd_to_fhci(hcd); int retval = 0; - int len = 0; struct usb_hub_status *hub_status; struct usb_port_status *port_status; unsigned long flags; @@ -272,7 +271,6 @@ int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case GetHubDescriptor: memcpy(buf, root_hub_des, sizeof(root_hub_des)); - len = (buf[0] < wLength) ? buf[0] : wLength; break; case GetHubStatus: hub_status = (struct usb_hub_status *)buf; @@ -280,7 +278,6 @@ int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, cpu_to_le16(fhci->vroot_hub->hub.wHubStatus); hub_status->wHubChange = cpu_to_le16(fhci->vroot_hub->hub.wHubChange); - len = 4; break; case GetPortStatus: port_status = (struct usb_port_status *)buf; @@ -288,7 +285,6 @@ int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, cpu_to_le16(fhci->vroot_hub->port.wPortStatus); port_status->wPortChange = cpu_to_le16(fhci->vroot_hub->port.wPortChange); - len = 4; break; case SetHubFeature: switch (wValue) { -- cgit v0.10.2 From c99e76c55f68eaa0c307ba25803c4e59c2fca1ca Mon Sep 17 00:00:00 2001 From: Andreas Herrmann Date: Mon, 12 Jan 2015 16:05:52 +0100 Subject: USB: host: Introduce flag to enable use of 64-bit dma_mask for ehci-platform ehci-octeon driver used a 64-bit dma_mask. With removal of ehci-octeon and usage of ehci-platform ehci dma_mask is now limited to 32 bits (coerced in ehci_platform_probe). Provide a flag in ehci platform data to allow use of 64 bits for dma_mask. Cc: David Daney Cc: Alex Smith Signed-off-by: Andreas Herrmann Tested-by: Aaro Koskinen Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/arch/mips/cavium-octeon/octeon-platform.c b/arch/mips/cavium-octeon/octeon-platform.c index eea60b6..12410a2 100644 --- a/arch/mips/cavium-octeon/octeon-platform.c +++ b/arch/mips/cavium-octeon/octeon-platform.c @@ -310,6 +310,7 @@ static struct usb_ehci_pdata octeon_ehci_pdata = { #ifdef __BIG_ENDIAN .big_endian_mmio = 1, #endif + .dma_mask_64 = 1, .power_on = octeon_ehci_power_on, .power_off = octeon_ehci_power_off, }; @@ -331,8 +332,6 @@ static void __init octeon_ehci_hw_start(struct device *dev) octeon2_usb_clocks_stop(); } -static u64 octeon_ehci_dma_mask = DMA_BIT_MASK(64); - static int __init octeon_ehci_device_init(void) { struct platform_device *pd; @@ -347,7 +346,6 @@ static int __init octeon_ehci_device_init(void) if (!pd) return 0; - pd->dev.dma_mask = &octeon_ehci_dma_mask; pd->dev.platform_data = &octeon_ehci_pdata; octeon_ehci_hw_start(&pd->dev); diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 28aae64..63f2622 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -155,7 +155,8 @@ static int ehci_platform_probe(struct platform_device *dev) if (!pdata) pdata = &ehci_platform_defaults; - err = dma_coerce_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)); + err = dma_coerce_mask_and_coherent(&dev->dev, + pdata->dma_mask_64 ? DMA_BIT_MASK(64) : DMA_BIT_MASK(32)); if (err) return err; diff --git a/include/linux/usb/ehci_pdriver.h b/include/linux/usb/ehci_pdriver.h index 6287b39..db0431b 100644 --- a/include/linux/usb/ehci_pdriver.h +++ b/include/linux/usb/ehci_pdriver.h @@ -48,6 +48,7 @@ struct usb_ehci_pdata { unsigned big_endian_mmio:1; unsigned no_io_watchdog:1; unsigned reset_on_resume:1; + unsigned dma_mask_64:1; /* Turn on all power and clocks */ int (*power_on)(struct platform_device *pdev); -- cgit v0.10.2 From 9636c37843c9355c1ab6adcd8491186cbdc3b950 Mon Sep 17 00:00:00 2001 From: Chris Rorvick Date: Wed, 14 Jan 2015 21:52:28 -0600 Subject: usb: Fix typo in `struct usb_host_interface' comment The descriptor member `bNumEndpoints' is plural. Signed-off-by: Chris Rorvick Signed-off-by: Greg Kroah-Hartman diff --git a/include/linux/usb.h b/include/linux/usb.h index f89c24a..4add566 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -82,7 +82,7 @@ struct usb_host_interface { int extralen; unsigned char *extra; /* Extra descriptors */ - /* array of desc.bNumEndpoint endpoints associated with this + /* array of desc.bNumEndpoints endpoints associated with this * interface setting. these will be in no particular order. */ struct usb_host_endpoint *endpoint; -- cgit v0.10.2 From 524134d422316a59d5464ccbc12036bbe90c5563 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 21 Jan 2015 14:02:43 -0500 Subject: USB: don't cancel queued resets when unbinding drivers The USB stack provides a mechanism for drivers to request an asynchronous device reset (usb_queue_reset_device()). The mechanism uses a work item (reset_ws) embedded in the usb_interface structure used by the driver, and the reset is carried out by a work queue routine. The asynchronous reset can race with driver unbinding. When this happens, we try to cancel the queued reset before unbinding the driver, on the theory that the driver won't care about any resets once it is unbound. However, thanks to the fact that lockdep now tracks work queue accesses, this can provoke a lockdep warning in situations where the device reset causes another interface's driver to be unbound; see http://marc.info/?l=linux-usb&m=141893165203776&w=2 for an example. The reason is that the work routine for reset_ws in one interface calls cancel_queued_work() for the reset_ws in another interface. Lockdep thinks this might lead to a work routine trying to cancel itself. The simplest solution is not to cancel queued resets when unbinding drivers. This means we now need to acquire a reference to the usb_interface when queuing a reset_ws work item and to drop the reference when the work routine finishes. We also need to make sure that the usb_interface structure doesn't outlive its parent usb_device; this means acquiring and dropping a reference when the interface is created and destroyed. In addition, cancelling a queued reset can fail (if the device is in the middle of an earlier reset), and this can cause usb_reset_device() to try to rebind an interface that has been deallocated (see http://marc.info/?l=linux-usb&m=142175717016628&w=2 for details). Acquiring the extra references prevents this failure. Signed-off-by: Alan Stern Reported-by: Russell King - ARM Linux Reported-by: Olivier Sobrie Tested-by: Olivier Sobrie Cc: stable # 3.19 Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 874dec3..c76ec97 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -275,21 +275,6 @@ static int usb_unbind_device(struct device *dev) return 0; } -/* - * Cancel any pending scheduled resets - * - * [see usb_queue_reset_device()] - * - * Called after unconfiguring / when releasing interfaces. See - * comments in __usb_queue_reset_device() regarding - * udev->reset_running. - */ -static void usb_cancel_queued_reset(struct usb_interface *iface) -{ - if (iface->reset_running == 0) - cancel_work_sync(&iface->reset_ws); -} - /* called from driver core with dev locked */ static int usb_probe_interface(struct device *dev) { @@ -380,7 +365,6 @@ static int usb_probe_interface(struct device *dev) usb_set_intfdata(intf, NULL); intf->needs_remote_wakeup = 0; intf->condition = USB_INTERFACE_UNBOUND; - usb_cancel_queued_reset(intf); /* If the LPM disable succeeded, balance the ref counts. */ if (!lpm_disable_error) @@ -425,7 +409,6 @@ static int usb_unbind_interface(struct device *dev) usb_disable_interface(udev, intf, false); driver->disconnect(intf); - usb_cancel_queued_reset(intf); /* Free streams */ for (i = 0, j = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index aeb50bb..b4bfa3a 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5589,26 +5589,19 @@ EXPORT_SYMBOL_GPL(usb_reset_device); * possible; depending on how the driver attached to each interface * handles ->pre_reset(), the second reset might happen or not. * - * - If a driver is unbound and it had a pending reset, the reset will - * be cancelled. + * - If the reset is delayed so long that the interface is unbound from + * its driver, the reset will be skipped. * - * - This function can be called during .probe() or .disconnect() - * times. On return from .disconnect(), any pending resets will be - * cancelled. - * - * There is no no need to lock/unlock the @reset_ws as schedule_work() - * does its own. - * - * NOTE: We don't do any reference count tracking because it is not - * needed. The lifecycle of the work_struct is tied to the - * usb_interface. Before destroying the interface we cancel the - * work_struct, so the fact that work_struct is queued and or - * running means the interface (and thus, the device) exist and - * are referenced. + * - This function can be called during .probe(). It can also be called + * during .disconnect(), but doing so is pointless because the reset + * will not occur. If you really want to reset the device during + * .disconnect(), call usb_reset_device() directly -- but watch out + * for nested unbinding issues! */ void usb_queue_reset_device(struct usb_interface *iface) { - schedule_work(&iface->reset_ws); + if (schedule_work(&iface->reset_ws)) + usb_get_intf(iface); } EXPORT_SYMBOL_GPL(usb_queue_reset_device); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index f7b7713..f368d20 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1551,6 +1551,7 @@ static void usb_release_interface(struct device *dev) altsetting_to_usb_interface_cache(intf->altsetting); kref_put(&intfc->ref, usb_release_interface_cache); + usb_put_dev(interface_to_usbdev(intf)); kfree(intf); } @@ -1626,24 +1627,6 @@ static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev, /* * Internal function to queue a device reset - * - * This is initialized into the workstruct in 'struct - * usb_device->reset_ws' that is launched by - * message.c:usb_set_configuration() when initializing each 'struct - * usb_interface'. - * - * It is safe to get the USB device without reference counts because - * the life cycle of @iface is bound to the life cycle of @udev. Then, - * this function will be ran only if @iface is alive (and before - * freeing it any scheduled instances of it will have been cancelled). - * - * We need to set a flag (usb_dev->reset_running) because when we call - * the reset, the interfaces might be unbound. The current interface - * cannot try to remove the queued work as it would cause a deadlock - * (you cannot remove your work from within your executing - * workqueue). This flag lets it know, so that - * usb_cancel_queued_reset() doesn't try to do it. - * * See usb_queue_reset_device() for more details */ static void __usb_queue_reset_device(struct work_struct *ws) @@ -1655,11 +1638,10 @@ static void __usb_queue_reset_device(struct work_struct *ws) rc = usb_lock_device_for_reset(udev, iface); if (rc >= 0) { - iface->reset_running = 1; usb_reset_device(udev); - iface->reset_running = 0; usb_unlock_device(udev); } + usb_put_intf(iface); /* Undo _get_ in usb_queue_reset_device() */ } @@ -1854,6 +1836,7 @@ free_interfaces: dev_set_name(&intf->dev, "%d-%s:%d.%d", dev->bus->busnum, dev->devpath, configuration, alt->desc.bInterfaceNumber); + usb_get_dev(dev); } kfree(new_interfaces); diff --git a/include/linux/usb.h b/include/linux/usb.h index 4add566..7ee1b5c 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -127,10 +127,6 @@ enum usb_interface_condition { * to the sysfs representation for that device. * @pm_usage_cnt: PM usage counter for this interface * @reset_ws: Used for scheduling resets from atomic context. - * @reset_running: set to 1 if the interface is currently running a - * queued reset so that usb_cancel_queued_reset() doesn't try to - * remove from the workqueue when running inside the worker - * thread. See __usb_queue_reset_device(). * @resetting_device: USB core reset the device, so use alt setting 0 as * current; needs bandwidth alloc after reset. * @@ -181,7 +177,6 @@ struct usb_interface { unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */ unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */ unsigned needs_binding:1; /* needs delayed unbind/rebind */ - unsigned reset_running:1; unsigned resetting_device:1; /* true: bandwidth alloc after reset */ struct device dev; /* interface specific device info */ -- cgit v0.10.2 From 99705092a83f0202d2b64c1b754a831464c5ce90 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 16 Jan 2015 17:54:01 +0200 Subject: xhci: Print hcc params, version and quirks on init To help debugging xhci problems. Signed-off-by: Hans de Goede Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 1b0c91f..15d8c22 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -4904,6 +4904,10 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) if (retval) goto error; xhci_dbg(xhci, "Called HCD init\n"); + + xhci_info(xhci, "hcc params 0x%08x hci version 0x%x quirks 0x%08x\n", + xhci->hcc_params, xhci->hci_version, xhci->quirks); + return 0; error: kfree(xhci); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index e23f31d..9745147 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1605,6 +1605,8 @@ static inline struct usb_hcd *xhci_to_hcd(struct xhci_hcd *xhci) dev_warn(xhci_to_hcd(xhci)->self.controller , fmt , ## args) #define xhci_warn_ratelimited(xhci, fmt, args...) \ dev_warn_ratelimited(xhci_to_hcd(xhci)->self.controller , fmt , ## args) +#define xhci_info(xhci, fmt, args...) \ + dev_info(xhci_to_hcd(xhci)->self.controller , fmt , ## args) /* * Registers should always be accessed with double word or quad word accesses. -- cgit v0.10.2 From a6134136d938ed9298f15e865e4a035f9c0eeb9c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 16 Jan 2015 17:54:02 +0200 Subject: xhci: Silence "xHCI xhci_drop_endpoint called with disabled ep ..." messages When re-applying the configuration after a successful usb device reset, xhci_discover_or_reset_device has already dropped the endpoints, and free-ed the rings. The endpoints already being dropped is expected, and should not lead to warnings. Use the fact that the rings are also free-ed in this scenario to detect this, and suppress the "xHCI xhci_drop_endpoint called with disabled ep ..." message in this case. Signed-off-by: Hans de Goede -- Changes in v2: Move the ring check to only guard the xhci_warn, so as to avoid side-effects in case we have a scenario where the rings are free-ed, but the endpoint is not yet dropped. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 15d8c22..ec8ac16 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1673,8 +1673,10 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, cpu_to_le32(EP_STATE_DISABLED)) || le32_to_cpu(ctrl_ctx->drop_flags) & xhci_get_endpoint_flag(&ep->desc)) { - xhci_warn(xhci, "xHCI %s called with disabled ep %p\n", - __func__, ep); + /* Do not warn when called after a usb_device_reset */ + if (xhci->devs[udev->slot_id]->eps[ep_index].ring != NULL) + xhci_warn(xhci, "xHCI %s called with disabled ep %p\n", + __func__, ep); return 0; } -- cgit v0.10.2 From 3a2359ee7d00d7746e4e71a15e06d06a5b376f58 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:25:44 +0300 Subject: ehci-hub: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 118edb7..87cf86f 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -700,15 +700,15 @@ ehci_hub_descriptor ( memset(&desc->u.hs.DeviceRemovable[0], 0, temp); memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - temp = 0x0008; /* per-port overcurrent reporting */ + temp = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ if (HCS_PPC (ehci->hcs_params)) - temp |= 0x0001; /* per-port power control */ + temp |= HUB_CHAR_INDV_PORT_LPSM; /* per-port power control */ else - temp |= 0x0002; /* no power switching */ + temp |= HUB_CHAR_NO_LPSM; /* no power switching */ #if 0 // re-enable when we support USB_PORT_FEAT_INDICATOR below. if (HCS_INDICATOR (ehci->hcs_params)) - temp |= 0x0080; /* per-port indicators (LEDs) */ + temp |= HUB_CHAR_PORTIND; /* per-port indicators (LEDs) */ #endif desc->wHubCharacteristics = cpu_to_le16(temp); } -- cgit v0.10.2 From 14de3a30437b2a755bd3a292ffcc7604504664e1 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:26:50 +0300 Subject: fhci-hub: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/fhci-hub.c b/drivers/usb/host/fhci-hub.c index a081a78..70116a6 100644 --- a/drivers/usb/host/fhci-hub.c +++ b/drivers/usb/host/fhci-hub.c @@ -32,7 +32,7 @@ static u8 root_hub_des[] = { 0x09, /* blength */ 0x29, /* bDescriptorType;hub-descriptor */ 0x01, /* bNbrPorts */ - 0x11, /* wHubCharacteristics */ + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM, /* wHubCharacteristics */ 0x00, /* per-port power, no overcurrent */ 0x01, /* bPwrOn2pwrGood;2ms */ 0x00, /* bHubContrCurrent;0mA */ -- cgit v0.10.2 From 7538bd620146418ce0cc25e14adfde2bfa489078 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:29:03 +0300 Subject: fotg210-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index ecf02b26..475b21f 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -1521,8 +1521,8 @@ fotg210_hub_descriptor( memset(&desc->u.hs.DeviceRemovable[0], 0, temp); memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - temp = 0x0008; /* per-port overcurrent reporting */ - temp |= 0x0002; /* no power switching */ + temp = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ + temp |= HUB_CHAR_NO_LPSM; /* no power switching */ desc->wHubCharacteristics = cpu_to_le16(temp); } -- cgit v0.10.2 From 4925fd9d7df6fa4d2ad8830266c9962395d7b66f Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:30:11 +0300 Subject: fusbh200-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/fusbh200-hcd.c b/drivers/usb/host/fusbh200-hcd.c index 664d2aa..a83eefe 100644 --- a/drivers/usb/host/fusbh200-hcd.c +++ b/drivers/usb/host/fusbh200-hcd.c @@ -1479,8 +1479,8 @@ fusbh200_hub_descriptor ( memset(&desc->u.hs.DeviceRemovable[0], 0, temp); memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - temp = 0x0008; /* per-port overcurrent reporting */ - temp |= 0x0002; /* no power switching */ + temp = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ + temp |= HUB_CHAR_NO_LPSM; /* no power switching */ desc->wHubCharacteristics = cpu_to_le16(temp); } -- cgit v0.10.2 From 4fbdb33678829d39501e1ff950940d9090d4730a Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:31:41 +0300 Subject: imx21-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/imx21-hcd.c b/drivers/usb/host/imx21-hcd.c index eb4efba..6a2ad55 100644 --- a/drivers/usb/host/imx21-hcd.c +++ b/drivers/usb/host/imx21-hcd.c @@ -1482,9 +1482,8 @@ static int get_hub_descriptor(struct usb_hcd *hcd, desc->bDescLength = 9; desc->bPwrOn2PwrGood = 0; desc->wHubCharacteristics = (__force __u16) cpu_to_le16( - 0x0002 | /* No power switching */ - 0x0010 | /* No over current protection */ - 0); + HUB_CHAR_NO_LPSM | /* No power switching */ + HUB_CHAR_NO_OCPM); /* No over current protection */ desc->u.hs.DeviceRemovable[0] = 1 << 1; desc->u.hs.DeviceRemovable[1] = ~0; -- cgit v0.10.2 From f3c4140dc9c7a1719eba3537eee576050174f93c Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:33:26 +0300 Subject: isp116x-hcd: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 31c9c4d..113d0cc 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -948,7 +948,10 @@ static void isp116x_hub_descriptor(struct isp116x *isp116x, desc->bHubContrCurrent = 0; desc->bNbrPorts = (u8) (reg & 0x3); /* Power switching, device type, overcurrent. */ - desc->wHubCharacteristics = cpu_to_le16((u16) ((reg >> 8) & 0x1f)); + desc->wHubCharacteristics = cpu_to_le16((u16) ((reg >> 8) & + (HUB_CHAR_LPSM | + HUB_CHAR_COMPOUND | + HUB_CHAR_OCPM))); desc->bPwrOn2PwrGood = (u8) ((reg >> 24) & 0xff); /* ports removable, and legacy PortPwrCtrlMask */ desc->u.hs.DeviceRemovable[0] = 0; -- cgit v0.10.2 From 662fc2a6a3d0720b3e4f00fec22ee214cd1204c3 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:34:59 +0300 Subject: isp1362-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . While at it, avoid duplicate computation when calling DBG(). Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/isp1362-hcd.c b/drivers/usb/host/isp1362-hcd.c index 75e5876..b32ab60 100644 --- a/drivers/usb/host/isp1362-hcd.c +++ b/drivers/usb/host/isp1362-hcd.c @@ -1543,8 +1543,12 @@ static void isp1362_hub_descriptor(struct isp1362_hcd *isp1362_hcd, desc->bHubContrCurrent = 0; desc->bNbrPorts = reg & 0x3; /* Power switching, device type, overcurrent. */ - desc->wHubCharacteristics = cpu_to_le16((reg >> 8) & 0x1f); - DBG(0, "%s: hubcharacteristics = %02x\n", __func__, cpu_to_le16((reg >> 8) & 0x1f)); + desc->wHubCharacteristics = cpu_to_le16((reg >> 8) & + (HUB_CHAR_LPSM | + HUB_CHAR_COMPOUND | + HUB_CHAR_OCPM)); + DBG(0, "%s: hubcharacteristics = %02x\n", __func__, + desc->wHubCharacteristics); desc->bPwrOn2PwrGood = (reg >> 24) & 0xff; /* ports removable, and legacy PortPwrCtrlMask */ desc->u.hs.DeviceRemovable[0] = desc->bNbrPorts == 1 ? 1 << 1 : 3 << 1; -- cgit v0.10.2 From cc581c12ac1c1a3340949a60bffd993d8c60d965 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:36:35 +0300 Subject: isp1760-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 79261d5..dbba455 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -1796,13 +1796,13 @@ static void isp1760_hub_descriptor(struct isp1760_hcd *priv, memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); /* per-port overcurrent reporting */ - temp = 0x0008; + temp = HUB_CHAR_INDV_PORT_OCPM; if (HCS_PPC(priv->hcs_params)) /* per-port power control */ - temp |= 0x0001; + temp |= HUB_CHAR_INDV_PORT_LPSM; else /* no power switching */ - temp |= 0x0002; + temp |= HUB_CHAR_NO_LPSM; desc->wHubCharacteristics = cpu_to_le16(temp); } -- cgit v0.10.2 From 2e48c4668da6fe8a3f1af67f8cd7d9b927698e62 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:38:22 +0300 Subject: max3421-hcd: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index aaaff94..a98833c 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -1661,7 +1661,8 @@ hub_descriptor(struct usb_hub_descriptor *desc) */ desc->bDescriptorType = 0x29; /* hub descriptor */ desc->bDescLength = 9; - desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->wHubCharacteristics = cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = 1; } -- cgit v0.10.2 From a9c49bcd0a4fe68e78eb202a00ba0d6c6361449c Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:39:44 +0300 Subject: ohci-at91: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . While at it, stop setting already set HUB_CHAR_INDV_PORT_LPSM once again. Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index dc9e4e6..e95e41c 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -347,11 +347,13 @@ static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, */ desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM); - desc->wHubCharacteristics |= cpu_to_le16(0x0001); + desc->wHubCharacteristics |= + cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM); if (pdata->overcurrent_supported) { desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM); - desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001); + desc->wHubCharacteristics |= + cpu_to_le16(HUB_CHAR_INDV_PORT_OCPM); } dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n", -- cgit v0.10.2 From db42a4c9a053e12f408fa13d5475180f33a4bc0b Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:41:37 +0300 Subject: ohci-hub: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . While at it, fix the indentation. Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 0aa17c9..fe2aedd 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -544,15 +544,15 @@ ohci_hub_descriptor ( temp = 1 + (ohci->num_ports / 8); desc->bDescLength = 7 + 2 * temp; - temp = 0; + temp = HUB_CHAR_COMMON_LPSM | HUB_CHAR_COMMON_OCPM; if (rh & RH_A_NPS) /* no power switching? */ - temp |= 0x0002; + temp |= HUB_CHAR_NO_LPSM; if (rh & RH_A_PSM) /* per-port power switching? */ - temp |= 0x0001; + temp |= HUB_CHAR_INDV_PORT_LPSM; if (rh & RH_A_NOCP) /* no overcurrent reporting? */ - temp |= 0x0010; + temp |= HUB_CHAR_NO_OCPM; else if (rh & RH_A_OCPM) /* per-port overcurrent reporting? */ - temp |= 0x0008; + temp |= HUB_CHAR_INDV_PORT_OCPM; desc->wHubCharacteristics = cpu_to_le16(temp); /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ -- cgit v0.10.2 From dd709764dcb3fcb6776a33b999296e534777ec59 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:44:12 +0300 Subject: ohci-s3c2410: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . While at it, stop setting already set HUB_CHAR_INDV_PORT_LPSM once again. Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index 095113e..7a1919c 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -249,14 +249,14 @@ static int ohci_s3c2410_hub_control( */ desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM); - desc->wHubCharacteristics |= cpu_to_le16(0x0001); + desc->wHubCharacteristics |= cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM); if (info->enable_oc) { desc->wHubCharacteristics &= ~cpu_to_le16( HUB_CHAR_OCPM); desc->wHubCharacteristics |= cpu_to_le16( - 0x0008 | - 0x0001); + HUB_CHAR_INDV_PORT_OCPM); } dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n", -- cgit v0.10.2 From 85943f3438041abb6bf9fc0ffb52a9530ffe75a8 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:45:41 +0300 Subject: oxu210hp-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index ea0ecbc..ef7efb2 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -457,11 +457,11 @@ static void ehci_hub_descriptor(struct oxu_hcd *oxu, memset(&desc->u.hs.DeviceRemovable[0], 0, temp); memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - temp = 0x0008; /* per-port overcurrent reporting */ + temp = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ if (HCS_PPC(oxu->hcs_params)) - temp |= 0x0001; /* per-port power control */ + temp |= HUB_CHAR_INDV_PORT_LPSM; /* per-port power control */ else - temp |= 0x0002; /* no power switching */ + temp |= HUB_CHAR_NO_LPSM; /* no power switching */ desc->wHubCharacteristics = (__force __u16)cpu_to_le16(temp); } -- cgit v0.10.2 From e70480fd8b0acc0c4d762cea69530438deaf3582 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:46:58 +0300 Subject: r8a66597-hcd: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index a048b8e..bdc82fe 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2141,7 +2141,8 @@ static void r8a66597_hub_descriptor(struct r8a66597 *r8a66597, desc->bNbrPorts = r8a66597->max_root_hub; desc->bDescLength = 9; desc->bPwrOn2PwrGood = 0; - desc->wHubCharacteristics = cpu_to_le16(0x0011); + desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM); desc->u.hs.DeviceRemovable[0] = ((1 << r8a66597->max_root_hub) - 1) << 1; desc->u.hs.DeviceRemovable[1] = ~0; -- cgit v0.10.2 From c25af1d67f67fb05b494351d31b7cf6d67174872 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:48:21 +0300 Subject: sl811-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index cef3140..4f4ba1e 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -1103,12 +1103,12 @@ sl811h_hub_descriptor ( desc->bPwrOn2PwrGood = sl811->board->potpg; if (!desc->bPwrOn2PwrGood) desc->bPwrOn2PwrGood = 10; - temp = 0x0001; + temp = HUB_CHAR_INDV_PORT_LPSM; } else - temp = 0x0002; + temp = HUB_CHAR_NO_LPSM; /* no overcurrent errors detection/handling */ - temp |= 0x0010; + temp |= HUB_CHAR_NO_OCPM; desc->wHubCharacteristics = cpu_to_le16(temp); -- cgit v0.10.2 From a7cf697d3adc062d75180001ba58f5368c11e4a3 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:49:59 +0300 Subject: u132-hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c index 2894e54..ad97e8a 100644 --- a/drivers/usb/host/u132-hcd.c +++ b/drivers/usb/host/u132-hcd.c @@ -2590,15 +2590,15 @@ static int u132_roothub_descriptor(struct u132 *u132, desc->bNbrPorts = u132->num_ports; temp = 1 + (u132->num_ports / 8); desc->bDescLength = 7 + 2 * temp; - temp = 0; + temp = HUB_CHAR_COMMON_LPSM | HUB_CHAR_COMMON_OCPM; if (rh_a & RH_A_NPS) - temp |= 0x0002; + temp |= HUB_CHAR_NO_LPSM; if (rh_a & RH_A_PSM) - temp |= 0x0001; + temp |= HUB_CHAR_INDV_PORT_LPSM; if (rh_a & RH_A_NOCP) - temp |= 0x0010; + temp |= HUB_CHAR_NO_OCPM; else if (rh_a & RH_A_OCPM) - temp |= 0x0008; + temp |= HUB_CHAR_INDV_PORT_OCPM; desc->wHubCharacteristics = cpu_to_le16(temp); retval = u132_read_pcimem(u132, roothub.b, &rh_b); if (retval) -- cgit v0.10.2 From 265bf230ab583c45344f93a061046509d5a4ab71 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:54:15 +0300 Subject: dwc2: hcd: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index a0cd9db..c5fd43d 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1622,7 +1622,9 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, hub_desc->bDescLength = 9; hub_desc->bDescriptorType = 0x29; hub_desc->bNbrPorts = 1; - hub_desc->wHubCharacteristics = cpu_to_le16(0x08); + hub_desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_COMMON_LPSM | + HUB_CHAR_INDV_PORT_OCPM); hub_desc->bPwrOn2PwrGood = 1; hub_desc->bHubContrCurrent = 0; hub_desc->u.hs.DeviceRemovable[0] = 0; -- cgit v0.10.2 From 2345f84c6ac65ef546978c1b1f741526f9cbd342 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:55:55 +0300 Subject: dummy_hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 9c59880..270c1ec 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -1924,7 +1924,9 @@ ss_hub_descriptor(struct usb_hub_descriptor *desc) memset(desc, 0, sizeof *desc); desc->bDescriptorType = 0x2a; desc->bDescLength = 12; - desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = 1; desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ desc->u.ss.DeviceRemovable = 0xffff; @@ -1935,7 +1937,9 @@ static inline void hub_descriptor(struct usb_hub_descriptor *desc) memset(desc, 0, sizeof *desc); desc->bDescriptorType = 0x29; desc->bDescLength = 9; - desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = 1; desc->u.hs.DeviceRemovable[0] = 0xff; desc->u.hs.DeviceRemovable[1] = 0xff; -- cgit v0.10.2 From 211f25dc9dc72aafa813654eaf7ff7107a2f6c6a Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:57:18 +0300 Subject: musb_virthub: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index b072420..b79e5be 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -349,9 +349,10 @@ int musb_hub_control( desc->bDescriptorType = 0x29; desc->bNbrPorts = 1; desc->wHubCharacteristics = cpu_to_le16( - 0x0001 /* per-port power switching */ - | 0x0010 /* no overcurrent reporting */ - ); + HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */ + + | HUB_CHAR_NO_OCPM /* no overcurrent reporting */ + ); desc->bPwrOn2PwrGood = 5; /* msec/2 */ desc->bHubContrCurrent = 0; -- cgit v0.10.2 From a1837d15d2782be38472919b273a114a56d91dde Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:59:09 +0300 Subject: renesas_usbhs: mod_host: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c index f0d3231..96eead6 100644 --- a/drivers/usb/renesas_usbhs/mod_host.c +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -1234,7 +1234,8 @@ static int __usbhsh_hub_get_status(struct usbhsh_hpriv *hpriv, desc->bNbrPorts = roothub_id; desc->bDescLength = 9; desc->bPwrOn2PwrGood = 0; - desc->wHubCharacteristics = cpu_to_le16(0x0011); + desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM); desc->u.hs.DeviceRemovable[0] = (roothub_id << 1); desc->u.hs.DeviceRemovable[1] = ~0; dev_dbg(dev, "%s :: GetHubDescriptor\n", __func__); -- cgit v0.10.2 From 1cc9af8711613668d23b60f0c3d648a1e3c4ddc8 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 02:00:56 +0300 Subject: usbip: vhci_hcd: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Acked-by: Valentina Manea Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 1ae9d40..11f6f61 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -218,7 +218,8 @@ static inline void hub_descriptor(struct usb_hub_descriptor *desc) memset(desc, 0, sizeof(*desc)); desc->bDescriptorType = 0x29; desc->bDescLength = 9; - desc->wHubCharacteristics = (__constant_cpu_to_le16(0x0001)); + desc->wHubCharacteristics = __constant_cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = VHCI_NPORTS; desc->u.hs.DeviceRemovable[0] = 0xff; desc->u.hs.DeviceRemovable[1] = 0xff; -- cgit v0.10.2 From 6d2a5663297a0489e7ade3303bba37e544da66e5 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 02:02:24 +0300 Subject: wusbcore: rh: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c index fe8bc77..aa5af81 100644 --- a/drivers/usb/wusbcore/rh.c +++ b/drivers/usb/wusbcore/rh.c @@ -185,9 +185,9 @@ static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue, descr->bDescriptorType = 0x29; /* HUB type */ descr->bNbrPorts = wusbhc->ports_max; descr->wHubCharacteristics = cpu_to_le16( - 0x00 /* All ports power at once */ + HUB_CHAR_COMMON_LPSM /* All ports power at once */ | 0x00 /* not part of compound device */ - | 0x10 /* No overcurrent protection */ + | HUB_CHAR_NO_OCPM /* No overcurrent protection */ | 0x00 /* 8 FS think time FIXME ?? */ | 0x00); /* No port indicators */ descr->bPwrOn2PwrGood = 0; -- cgit v0.10.2 From 85cd690d7e08159e8e4d737317a5e8a96140d687 Mon Sep 17 00:00:00 2001 From: Arun Ramamurthy Date: Mon, 19 Jan 2015 16:05:30 -0800 Subject: usb: ohci-platform: add support for multiple phys per controller Added support for cases where one controller is connected to multiple phys Signed-off-by: Arun Ramamurthy Reviewed-by: Ray Jui Reviewed-by: Scott Branden Tested-by: Scott Branden Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 9c06b01..185ceee 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -38,7 +38,8 @@ struct ohci_platform_priv { struct clk *clks[OHCI_MAX_CLKS]; struct reset_control *rst; - struct phy *phy; + struct phy **phys; + int num_phys; }; static const char hcd_name[] = "ohci-platform"; @@ -47,7 +48,7 @@ static int ohci_platform_power_on(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); - int clk, ret; + int clk, ret, phy_num; for (clk = 0; clk < OHCI_MAX_CLKS && priv->clks[clk]; clk++) { ret = clk_prepare_enable(priv->clks[clk]); @@ -55,20 +56,28 @@ static int ohci_platform_power_on(struct platform_device *dev) goto err_disable_clks; } - if (priv->phy) { - ret = phy_init(priv->phy); - if (ret) - goto err_disable_clks; - - ret = phy_power_on(priv->phy); - if (ret) - goto err_exit_phy; + for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { + if (priv->phys[phy_num]) { + ret = phy_init(priv->phys[phy_num]); + if (ret) + goto err_exit_phy; + ret = phy_power_on(priv->phys[phy_num]); + if (ret) { + phy_exit(priv->phys[phy_num]); + goto err_exit_phy; + } + } } return 0; err_exit_phy: - phy_exit(priv->phy); + while (--phy_num >= 0) { + if (priv->phys[phy_num]) { + phy_power_off(priv->phys[phy_num]); + phy_exit(priv->phys[phy_num]); + } + } err_disable_clks: while (--clk >= 0) clk_disable_unprepare(priv->clks[clk]); @@ -80,11 +89,13 @@ static void ohci_platform_power_off(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); - int clk; + int clk, phy_num; - if (priv->phy) { - phy_power_off(priv->phy); - phy_exit(priv->phy); + for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { + if (priv->phys[phy_num]) { + phy_power_off(priv->phys[phy_num]); + phy_exit(priv->phys[phy_num]); + } } for (clk = OHCI_MAX_CLKS - 1; clk >= 0; clk--) @@ -112,7 +123,8 @@ static int ohci_platform_probe(struct platform_device *dev) struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); struct ohci_platform_priv *priv; struct ohci_hcd *ohci; - int err, irq, clk = 0; + const char *phy_name; + int err, irq, phy_num, clk = 0; if (usb_disabled()) return -ENODEV; @@ -160,12 +172,38 @@ static int ohci_platform_probe(struct platform_device *dev) of_property_read_u32(dev->dev.of_node, "num-ports", &ohci->num_ports); - priv->phy = devm_phy_get(&dev->dev, "usb"); - if (IS_ERR(priv->phy)) { - err = PTR_ERR(priv->phy); - if (err == -EPROBE_DEFER) - goto err_put_hcd; - priv->phy = NULL; + priv->num_phys = of_count_phandle_with_args(dev->dev.of_node, + "phys", "#phy-cells"); + priv->num_phys = priv->num_phys > 0 ? priv->num_phys : 1; + + priv->phys = devm_kcalloc(&dev->dev, priv->num_phys, + sizeof(struct phy *), GFP_KERNEL); + if (!priv->phys) + return -ENOMEM; + + for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { + err = of_property_read_string_index( + dev->dev.of_node, + "phy-names", phy_num, + &phy_name); + + if (err < 0) { + if (priv->num_phys > 1) { + dev_err(&dev->dev, "phy-names not provided"); + goto err_put_hcd; + } else + phy_name = "usb"; + } + + priv->phys[phy_num] = devm_phy_get(&dev->dev, + phy_name); + if (IS_ERR(priv->phys[phy_num])) { + err = PTR_ERR(priv->phys[phy_num]); + if ((priv->num_phys > 1) || + (err == -EPROBE_DEFER)) + goto err_put_hcd; + priv->phys[phy_num] = NULL; + } } for (clk = 0; clk < OHCI_MAX_CLKS; clk++) { -- cgit v0.10.2 From 7e7a0e67f2c2104fb7515008fb2ba72ffb10b493 Mon Sep 17 00:00:00 2001 From: Arun Ramamurthy Date: Mon, 19 Jan 2015 16:05:29 -0800 Subject: usb: ehci-platform: add support for multiple phys per controller Added support for cases where one controller is connected to multiple phys. Signed-off-by: Arun Ramamurthy Reviewed-by: Ray Jui Reviewed-by: Scott Branden Tested-by: Scott Branden Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 63f2622..d8a75a5 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -43,7 +43,8 @@ struct ehci_platform_priv { struct clk *clks[EHCI_MAX_CLKS]; struct reset_control *rst; - struct phy *phy; + struct phy **phys; + int num_phys; }; static const char hcd_name[] = "ehci-platform"; @@ -78,7 +79,7 @@ static int ehci_platform_power_on(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); - int clk, ret; + int clk, ret, phy_num; for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) { ret = clk_prepare_enable(priv->clks[clk]); @@ -86,20 +87,28 @@ static int ehci_platform_power_on(struct platform_device *dev) goto err_disable_clks; } - if (priv->phy) { - ret = phy_init(priv->phy); - if (ret) - goto err_disable_clks; - - ret = phy_power_on(priv->phy); - if (ret) - goto err_exit_phy; + for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { + if (priv->phys[phy_num]) { + ret = phy_init(priv->phys[phy_num]); + if (ret) + goto err_exit_phy; + ret = phy_power_on(priv->phys[phy_num]); + if (ret) { + phy_exit(priv->phys[phy_num]); + goto err_exit_phy; + } + } } return 0; err_exit_phy: - phy_exit(priv->phy); + while (--phy_num >= 0) { + if (priv->phys[phy_num]) { + phy_power_off(priv->phys[phy_num]); + phy_exit(priv->phys[phy_num]); + } + } err_disable_clks: while (--clk >= 0) clk_disable_unprepare(priv->clks[clk]); @@ -111,11 +120,13 @@ static void ehci_platform_power_off(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); - int clk; + int clk, phy_num; - if (priv->phy) { - phy_power_off(priv->phy); - phy_exit(priv->phy); + for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { + if (priv->phys[phy_num]) { + phy_power_off(priv->phys[phy_num]); + phy_exit(priv->phys[phy_num]); + } } for (clk = EHCI_MAX_CLKS - 1; clk >= 0; clk--) @@ -143,7 +154,8 @@ static int ehci_platform_probe(struct platform_device *dev) struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); struct ehci_platform_priv *priv; struct ehci_hcd *ehci; - int err, irq, clk = 0; + const char *phy_name; + int err, irq, phy_num, clk = 0; if (usb_disabled()) return -ENODEV; @@ -190,12 +202,38 @@ static int ehci_platform_probe(struct platform_device *dev) "needs-reset-on-resume")) pdata->reset_on_resume = 1; - priv->phy = devm_phy_get(&dev->dev, "usb"); - if (IS_ERR(priv->phy)) { - err = PTR_ERR(priv->phy); - if (err == -EPROBE_DEFER) - goto err_put_hcd; - priv->phy = NULL; + priv->num_phys = of_count_phandle_with_args(dev->dev.of_node, + "phys", "#phy-cells"); + priv->num_phys = priv->num_phys > 0 ? priv->num_phys : 1; + + priv->phys = devm_kcalloc(&dev->dev, priv->num_phys, + sizeof(struct phy *), GFP_KERNEL); + if (!priv->phys) + return -ENOMEM; + + for (phy_num = 0; phy_num < priv->num_phys; phy_num++) { + err = of_property_read_string_index( + dev->dev.of_node, + "phy-names", phy_num, + &phy_name); + + if (err < 0) { + if (priv->num_phys > 1) { + dev_err(&dev->dev, "phy-names not provided"); + goto err_put_hcd; + } else + phy_name = "usb"; + } + + priv->phys[phy_num] = devm_phy_get(&dev->dev, + phy_name); + if (IS_ERR(priv->phys[phy_num])) { + err = PTR_ERR(priv->phys[phy_num]); + if ((priv->num_phys > 1) || + (err == -EPROBE_DEFER)) + goto err_put_hcd; + priv->phys[phy_num] = NULL; + } } for (clk = 0; clk < EHCI_MAX_CLKS; clk++) { -- cgit v0.10.2 From fbaecff06a7db4defa899a664fe2758e5161b39d Mon Sep 17 00:00:00 2001 From: Deepak Das Date: Wed, 21 Jan 2015 23:39:58 +0530 Subject: usb: core: hub: modify hub reset logic in hub driver Currently if port power is turned off by user on hub port using USBDEVFS then port power is turned back ON by hub driver. This commit modifies hub reset logic in hub_port_connect() to prevent hub driver from turning back the port power ON if port is not owned by kernel. Signed-off-by: Deepak Das Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b4bfa3a..3e9c4d4 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4655,9 +4655,13 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, if (!(portstatus & USB_PORT_STAT_CONNECTION) || test_bit(port1, hub->removed_bits)) { - /* maybe switch power back on (e.g. root hub was reset) */ + /* + * maybe switch power back on (e.g. root hub was reset) + * but only if the port isn't owned by someone else. + */ if (hub_is_port_power_switchable(hub) - && !port_is_power_on(hub, portstatus)) + && !port_is_power_on(hub, portstatus) + && !port_dev->port_owner) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); if (portstatus & USB_PORT_STAT_ENABLE) -- cgit v0.10.2 From ca2c1dc04b3fe8846d691adecfd65978b7ed3008 Mon Sep 17 00:00:00 2001 From: Sylvain Rochet Date: Tue, 20 Jan 2015 14:38:59 +0100 Subject: USB: host: ehci-atmel: Add suspend/resume support This patch add suspend/resume support for Atmel EHCI, mostly about disabling and unpreparing clocks so USB PLL is stopped before entering sleep state. Signed-off-by: Sylvain Rochet Acked-by: Boris Brezillon Acked-by: Alan Stern Acked-by: Nicolas Ferre Acked-by: Alexandre Belloni Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c index 56a8850..5a15e3d 100644 --- a/drivers/usb/host/ehci-atmel.c +++ b/drivers/usb/host/ehci-atmel.c @@ -37,6 +37,8 @@ static int clocked; static void atmel_start_clock(void) { + if (clocked) + return; if (IS_ENABLED(CONFIG_COMMON_CLK)) { clk_set_rate(uclk, 48000000); clk_prepare_enable(uclk); @@ -48,6 +50,8 @@ static void atmel_start_clock(void) static void atmel_stop_clock(void) { + if (!clocked) + return; clk_disable_unprepare(fclk); clk_disable_unprepare(iclk); if (IS_ENABLED(CONFIG_COMMON_CLK)) @@ -174,6 +178,29 @@ static int ehci_atmel_drv_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int ehci_atmel_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + int ret; + + ret = ehci_suspend(hcd, false); + if (ret) + return ret; + + atmel_stop_clock(); + return 0; +} + +static int ehci_atmel_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + atmel_start_clock(); + return ehci_resume(hcd, false); +} +#endif + #ifdef CONFIG_OF static const struct of_device_id atmel_ehci_dt_ids[] = { { .compatible = "atmel,at91sam9g45-ehci" }, @@ -183,12 +210,16 @@ static const struct of_device_id atmel_ehci_dt_ids[] = { MODULE_DEVICE_TABLE(of, atmel_ehci_dt_ids); #endif +static SIMPLE_DEV_PM_OPS(ehci_atmel_pm_ops, ehci_atmel_drv_suspend, + ehci_atmel_drv_resume); + static struct platform_driver ehci_atmel_driver = { .probe = ehci_atmel_drv_probe, .remove = ehci_atmel_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "atmel-ehci", + .pm = &ehci_atmel_pm_ops, .of_match_table = of_match_ptr(atmel_ehci_dt_ids), }, }; -- cgit v0.10.2 From 94ac0e5d29f0f5ba04961b64f879d05d1507cbe5 Mon Sep 17 00:00:00 2001 From: Sylvain Rochet Date: Tue, 20 Jan 2015 14:39:00 +0100 Subject: USB: host: ohci-at91: Use struct dev_pm_ops instead of struct platform_driver This patch replace struct platform_driver.{resume,suspend} PM bindings to a new struct dev_pm_ops. Signed-off-by: Sylvain Rochet Acked-by: Boris Brezillon Acked-by: Alan Stern Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index e95e41c..8fad2ca 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -595,11 +595,11 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev) #ifdef CONFIG_PM static int -ohci_hcd_at91_drv_suspend(struct platform_device *pdev, pm_message_t mesg) +ohci_hcd_at91_drv_suspend(struct device *dev) { - struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct usb_hcd *hcd = dev_get_drvdata(dev); struct ohci_hcd *ohci = hcd_to_ohci(hcd); - bool do_wakeup = device_may_wakeup(&pdev->dev); + bool do_wakeup = device_may_wakeup(dev); int ret; if (do_wakeup) @@ -631,11 +631,11 @@ ohci_hcd_at91_drv_suspend(struct platform_device *pdev, pm_message_t mesg) return ret; } -static int ohci_hcd_at91_drv_resume(struct platform_device *pdev) +static int ohci_hcd_at91_drv_resume(struct device *dev) { - struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct usb_hcd *hcd = dev_get_drvdata(dev); - if (device_may_wakeup(&pdev->dev)) + if (device_may_wakeup(dev)) disable_irq_wake(hcd->irq); if (!clocked) @@ -644,19 +644,18 @@ static int ohci_hcd_at91_drv_resume(struct platform_device *pdev) ohci_resume(hcd, false); return 0; } -#else -#define ohci_hcd_at91_drv_suspend NULL -#define ohci_hcd_at91_drv_resume NULL #endif +static SIMPLE_DEV_PM_OPS(ohci_hcd_at91_pm_ops, ohci_hcd_at91_drv_suspend, + ohci_hcd_at91_drv_resume); + static struct platform_driver ohci_hcd_at91_driver = { .probe = ohci_hcd_at91_drv_probe, .remove = ohci_hcd_at91_drv_remove, .shutdown = usb_hcd_platform_shutdown, - .suspend = ohci_hcd_at91_drv_suspend, - .resume = ohci_hcd_at91_drv_resume, .driver = { .name = "at91_ohci", + .pm = &ohci_hcd_at91_pm_ops, .of_match_table = of_match_ptr(at91_ohci_dt_ids), }, }; -- cgit v0.10.2 From 7baddacbe1bc7186268aea0697400197c836f682 Mon Sep 17 00:00:00 2001 From: Sylvain Rochet Date: Tue, 20 Jan 2015 14:39:01 +0100 Subject: USB: host: ehci-atmel: Move global variables to private struct This patch move Atmel EHCI global variables (clocks ptr and clocked boolean) to private struct atmel_ehci_priv, stored in ehci->priv. Signed-off-by: Sylvain Rochet Acked-by: Boris Brezillon Acked-by: Alan Stern Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c index 5a15e3d..663f790 100644 --- a/drivers/usb/host/ehci-atmel.c +++ b/drivers/usb/host/ehci-atmel.c @@ -27,48 +27,66 @@ #define DRIVER_DESC "EHCI Atmel driver" static const char hcd_name[] = "ehci-atmel"; -static struct hc_driver __read_mostly ehci_atmel_hc_driver; /* interface and function clocks */ -static struct clk *iclk, *fclk, *uclk; -static int clocked; +#define hcd_to_atmel_ehci_priv(h) \ + ((struct atmel_ehci_priv *)hcd_to_ehci(h)->priv) + +struct atmel_ehci_priv { + struct clk *iclk; + struct clk *fclk; + struct clk *uclk; + bool clocked; +}; + +static struct hc_driver __read_mostly ehci_atmel_hc_driver; + +static const struct ehci_driver_overrides ehci_atmel_drv_overrides __initconst = { + .extra_priv_size = sizeof(struct atmel_ehci_priv), +}; /*-------------------------------------------------------------------------*/ -static void atmel_start_clock(void) +static void atmel_start_clock(struct atmel_ehci_priv *atmel_ehci) { - if (clocked) + if (atmel_ehci->clocked) return; if (IS_ENABLED(CONFIG_COMMON_CLK)) { - clk_set_rate(uclk, 48000000); - clk_prepare_enable(uclk); + clk_set_rate(atmel_ehci->uclk, 48000000); + clk_prepare_enable(atmel_ehci->uclk); } - clk_prepare_enable(iclk); - clk_prepare_enable(fclk); - clocked = 1; + clk_prepare_enable(atmel_ehci->iclk); + clk_prepare_enable(atmel_ehci->fclk); + atmel_ehci->clocked = true; } -static void atmel_stop_clock(void) +static void atmel_stop_clock(struct atmel_ehci_priv *atmel_ehci) { - if (!clocked) + if (!atmel_ehci->clocked) return; - clk_disable_unprepare(fclk); - clk_disable_unprepare(iclk); + clk_disable_unprepare(atmel_ehci->fclk); + clk_disable_unprepare(atmel_ehci->iclk); if (IS_ENABLED(CONFIG_COMMON_CLK)) - clk_disable_unprepare(uclk); - clocked = 0; + clk_disable_unprepare(atmel_ehci->uclk); + atmel_ehci->clocked = false; } static void atmel_start_ehci(struct platform_device *pdev) { + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); + dev_dbg(&pdev->dev, "start\n"); - atmel_start_clock(); + atmel_start_clock(atmel_ehci); } static void atmel_stop_ehci(struct platform_device *pdev) { + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); + dev_dbg(&pdev->dev, "stop\n"); - atmel_stop_clock(); + atmel_stop_clock(atmel_ehci); } /*-------------------------------------------------------------------------*/ @@ -79,6 +97,7 @@ static int ehci_atmel_drv_probe(struct platform_device *pdev) const struct hc_driver *driver = &ehci_atmel_hc_driver; struct resource *res; struct ehci_hcd *ehci; + struct atmel_ehci_priv *atmel_ehci; int irq; int retval; @@ -109,6 +128,7 @@ static int ehci_atmel_drv_probe(struct platform_device *pdev) retval = -ENOMEM; goto fail_create_hcd; } + atmel_ehci = hcd_to_atmel_ehci_priv(hcd); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); hcd->regs = devm_ioremap_resource(&pdev->dev, res); @@ -120,23 +140,23 @@ static int ehci_atmel_drv_probe(struct platform_device *pdev) hcd->rsrc_start = res->start; hcd->rsrc_len = resource_size(res); - iclk = devm_clk_get(&pdev->dev, "ehci_clk"); - if (IS_ERR(iclk)) { + atmel_ehci->iclk = devm_clk_get(&pdev->dev, "ehci_clk"); + if (IS_ERR(atmel_ehci->iclk)) { dev_err(&pdev->dev, "Error getting interface clock\n"); retval = -ENOENT; goto fail_request_resource; } - fclk = devm_clk_get(&pdev->dev, "uhpck"); - if (IS_ERR(fclk)) { + atmel_ehci->fclk = devm_clk_get(&pdev->dev, "uhpck"); + if (IS_ERR(atmel_ehci->fclk)) { dev_err(&pdev->dev, "Error getting function clock\n"); retval = -ENOENT; goto fail_request_resource; } if (IS_ENABLED(CONFIG_COMMON_CLK)) { - uclk = devm_clk_get(&pdev->dev, "usb_clk"); - if (IS_ERR(uclk)) { + atmel_ehci->uclk = devm_clk_get(&pdev->dev, "usb_clk"); + if (IS_ERR(atmel_ehci->uclk)) { dev_err(&pdev->dev, "failed to get uclk\n"); - retval = PTR_ERR(uclk); + retval = PTR_ERR(atmel_ehci->uclk); goto fail_request_resource; } } @@ -173,7 +193,6 @@ static int ehci_atmel_drv_remove(struct platform_device *pdev) usb_put_hcd(hcd); atmel_stop_ehci(pdev); - fclk = iclk = NULL; return 0; } @@ -182,21 +201,23 @@ static int ehci_atmel_drv_remove(struct platform_device *pdev) static int ehci_atmel_drv_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); int ret; ret = ehci_suspend(hcd, false); if (ret) return ret; - atmel_stop_clock(); + atmel_stop_clock(atmel_ehci); return 0; } static int ehci_atmel_drv_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); - atmel_start_clock(); + atmel_start_clock(atmel_ehci); return ehci_resume(hcd, false); } #endif @@ -230,7 +251,7 @@ static int __init ehci_atmel_init(void) return -ENODEV; pr_info("%s: " DRIVER_DESC "\n", hcd_name); - ehci_init_driver(&ehci_atmel_hc_driver, NULL); + ehci_init_driver(&ehci_atmel_hc_driver, &ehci_atmel_drv_overrides); return platform_driver_register(&ehci_atmel_driver); } module_init(ehci_atmel_init); -- cgit v0.10.2 From 1b207459f7ab5f130f44b89ad07f3e8769ea5c17 Mon Sep 17 00:00:00 2001 From: Sylvain Rochet Date: Tue, 20 Jan 2015 14:39:02 +0100 Subject: USB: host: ohci-at91: Move global variables to private struct This patch move AT91 OHCI global variables (clocks ptr and clocked boolean) to private struct ohci_at91_priv, stored in ohci->priv. Signed-off-by: Sylvain Rochet Acked-by: Boris Brezillon Acked-by: Alan Stern Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 8fad2ca..eb7457f 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -33,7 +33,16 @@ for ((index) = 0; (index) < AT91_MAX_USBH_PORTS; (index)++) /* interface, function and usb clocks; sometimes also an AHB clock */ -static struct clk *iclk, *fclk, *uclk, *hclk; +#define hcd_to_ohci_at91_priv(h) \ + ((struct ohci_at91_priv *)hcd_to_ohci(h)->priv) + +struct ohci_at91_priv { + struct clk *iclk; + struct clk *fclk; + struct clk *uclk; + struct clk *hclk; + bool clocked; +}; /* interface and function clocks; sometimes also an AHB clock */ #define DRIVER_DESC "OHCI Atmel driver" @@ -41,45 +50,49 @@ static struct clk *iclk, *fclk, *uclk, *hclk; static const char hcd_name[] = "ohci-atmel"; static struct hc_driver __read_mostly ohci_at91_hc_driver; -static int clocked; + +static const struct ohci_driver_overrides ohci_at91_drv_overrides __initconst = { + .extra_priv_size = sizeof(struct ohci_at91_priv), +}; extern int usb_disabled(void); /*-------------------------------------------------------------------------*/ -static void at91_start_clock(void) +static void at91_start_clock(struct ohci_at91_priv *ohci_at91) { if (IS_ENABLED(CONFIG_COMMON_CLK)) { - clk_set_rate(uclk, 48000000); - clk_prepare_enable(uclk); + clk_set_rate(ohci_at91->uclk, 48000000); + clk_prepare_enable(ohci_at91->uclk); } - clk_prepare_enable(hclk); - clk_prepare_enable(iclk); - clk_prepare_enable(fclk); - clocked = 1; + clk_prepare_enable(ohci_at91->hclk); + clk_prepare_enable(ohci_at91->iclk); + clk_prepare_enable(ohci_at91->fclk); + ohci_at91->clocked = true; } -static void at91_stop_clock(void) +static void at91_stop_clock(struct ohci_at91_priv *ohci_at91) { - clk_disable_unprepare(fclk); - clk_disable_unprepare(iclk); - clk_disable_unprepare(hclk); + clk_disable_unprepare(ohci_at91->fclk); + clk_disable_unprepare(ohci_at91->iclk); + clk_disable_unprepare(ohci_at91->hclk); if (IS_ENABLED(CONFIG_COMMON_CLK)) - clk_disable_unprepare(uclk); - clocked = 0; + clk_disable_unprepare(ohci_at91->uclk); + ohci_at91->clocked = false; } static void at91_start_hc(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct ohci_regs __iomem *regs = hcd->regs; + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); dev_dbg(&pdev->dev, "start\n"); /* * Start the USB clocks. */ - at91_start_clock(); + at91_start_clock(ohci_at91); /* * The USB host controller must remain in reset. @@ -91,6 +104,7 @@ static void at91_stop_hc(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct ohci_regs __iomem *regs = hcd->regs; + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); dev_dbg(&pdev->dev, "stop\n"); @@ -102,7 +116,7 @@ static void at91_stop_hc(struct platform_device *pdev) /* * Stop the USB clocks. */ - at91_stop_clock(); + at91_stop_clock(ohci_at91); } @@ -129,6 +143,7 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, struct ohci_hcd *ohci; int retval; struct usb_hcd *hcd = NULL; + struct ohci_at91_priv *ohci_at91; struct device *dev = &pdev->dev; struct resource *res; int irq; @@ -142,6 +157,7 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, hcd = usb_create_hcd(driver, dev, "at91"); if (!hcd) return -ENOMEM; + ohci_at91 = hcd_to_ohci_at91_priv(hcd); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); hcd->regs = devm_ioremap_resource(dev, res); @@ -152,29 +168,29 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, hcd->rsrc_start = res->start; hcd->rsrc_len = resource_size(res); - iclk = devm_clk_get(dev, "ohci_clk"); - if (IS_ERR(iclk)) { + ohci_at91->iclk = devm_clk_get(dev, "ohci_clk"); + if (IS_ERR(ohci_at91->iclk)) { dev_err(dev, "failed to get ohci_clk\n"); - retval = PTR_ERR(iclk); + retval = PTR_ERR(ohci_at91->iclk); goto err; } - fclk = devm_clk_get(dev, "uhpck"); - if (IS_ERR(fclk)) { + ohci_at91->fclk = devm_clk_get(dev, "uhpck"); + if (IS_ERR(ohci_at91->fclk)) { dev_err(dev, "failed to get uhpck\n"); - retval = PTR_ERR(fclk); + retval = PTR_ERR(ohci_at91->fclk); goto err; } - hclk = devm_clk_get(dev, "hclk"); - if (IS_ERR(hclk)) { + ohci_at91->hclk = devm_clk_get(dev, "hclk"); + if (IS_ERR(ohci_at91->hclk)) { dev_err(dev, "failed to get hclk\n"); - retval = PTR_ERR(hclk); + retval = PTR_ERR(ohci_at91->hclk); goto err; } if (IS_ENABLED(CONFIG_COMMON_CLK)) { - uclk = devm_clk_get(dev, "usb_clk"); - if (IS_ERR(uclk)) { + ohci_at91->uclk = devm_clk_get(dev, "usb_clk"); + if (IS_ERR(ohci_at91->uclk)) { dev_err(dev, "failed to get uclk\n"); - retval = PTR_ERR(uclk); + retval = PTR_ERR(ohci_at91->uclk); goto err; } } @@ -599,6 +615,7 @@ ohci_hcd_at91_drv_suspend(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); bool do_wakeup = device_may_wakeup(dev); int ret; @@ -625,7 +642,7 @@ ohci_hcd_at91_drv_suspend(struct device *dev) /* flush the writes */ (void) ohci_readl (ohci, &ohci->regs->control); - at91_stop_clock(); + at91_stop_clock(ohci_at91); } return ret; @@ -634,12 +651,13 @@ ohci_hcd_at91_drv_suspend(struct device *dev) static int ohci_hcd_at91_drv_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); if (device_may_wakeup(dev)) disable_irq_wake(hcd->irq); - if (!clocked) - at91_start_clock(); + if (!ohci_at91->clocked) + at91_start_clock(ohci_at91); ohci_resume(hcd, false); return 0; @@ -666,7 +684,7 @@ static int __init ohci_at91_init(void) return -ENODEV; pr_info("%s: " DRIVER_DESC "\n", hcd_name); - ohci_init_driver(&ohci_at91_hc_driver, NULL); + ohci_init_driver(&ohci_at91_hc_driver, &ohci_at91_drv_overrides); /* * The Atmel HW has some unusual quirks, which require Atmel-specific -- cgit v0.10.2 From 3b3394aff79770681fd9a5a02cd4b70518899b78 Mon Sep 17 00:00:00 2001 From: Sylvain Rochet Date: Tue, 20 Jan 2015 14:39:03 +0100 Subject: USB: host: ohci-at91: usb_hcd_at91_probe(), remove useless stack initialisation struct usb_hcd *hcd = NULL; ... hcd = usb_create_hcd(driver, dev, "at91"); This patch remove *hcd useless initialisation Signed-off-by: Sylvain Rochet Acked-by: Boris Brezillon Acked-by: Alan Stern Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index eb7457f..e2b8b7b 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -142,7 +142,7 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, struct at91_usbh_data *board; struct ohci_hcd *ohci; int retval; - struct usb_hcd *hcd = NULL; + struct usb_hcd *hcd; struct ohci_at91_priv *ohci_at91; struct device *dev = &pdev->dev; struct resource *res; -- cgit v0.10.2 From 1cee6b8d00427fd993c2e2b8acfd08410b053af3 Mon Sep 17 00:00:00 2001 From: Sylvain Rochet Date: Tue, 20 Jan 2015 14:39:04 +0100 Subject: USB: host: ohci-at91: Fix wake-up support This device needs to be continuously clocked to provide wake up support, previously, if STANDBY target were chosen the device were enable_irq_wake()-prepared and clock still active and if MEM target were chosen the device were also enable_irq_wake()-prepared but not clocked anymore, which is wrong. Now, if STANDBY target is chosen the device is still clocked with wake up support enabled, which were the previous default and if MEM target is chosen the device is declocked with wake up support disabled. Signed-off-by: Sylvain Rochet Acked-by: Boris Brezillon Acked-by: Alan Stern Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index e2b8b7b..7cce85a 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -42,6 +42,7 @@ struct ohci_at91_priv { struct clk *uclk; struct clk *hclk; bool clocked; + bool wakeup; /* Saved wake-up state for resume */ }; /* interface and function clocks; sometimes also an AHB clock */ @@ -61,6 +62,8 @@ extern int usb_disabled(void); static void at91_start_clock(struct ohci_at91_priv *ohci_at91) { + if (ohci_at91->clocked) + return; if (IS_ENABLED(CONFIG_COMMON_CLK)) { clk_set_rate(ohci_at91->uclk, 48000000); clk_prepare_enable(ohci_at91->uclk); @@ -73,6 +76,8 @@ static void at91_start_clock(struct ohci_at91_priv *ohci_at91) static void at91_stop_clock(struct ohci_at91_priv *ohci_at91) { + if (!ohci_at91->clocked) + return; clk_disable_unprepare(ohci_at91->fclk); clk_disable_unprepare(ohci_at91->iclk); clk_disable_unprepare(ohci_at91->hclk); @@ -616,15 +621,22 @@ ohci_hcd_at91_drv_suspend(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); struct ohci_hcd *ohci = hcd_to_ohci(hcd); struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); - bool do_wakeup = device_may_wakeup(dev); int ret; - if (do_wakeup) + /* + * Disable wakeup if we are going to sleep with slow clock mode + * enabled. + */ + ohci_at91->wakeup = device_may_wakeup(dev) + && !at91_suspend_entering_slow_clock(); + + if (ohci_at91->wakeup) enable_irq_wake(hcd->irq); - ret = ohci_suspend(hcd, do_wakeup); + ret = ohci_suspend(hcd, ohci_at91->wakeup); if (ret) { - disable_irq_wake(hcd->irq); + if (ohci_at91->wakeup) + disable_irq_wake(hcd->irq); return ret; } /* @@ -634,7 +646,7 @@ ohci_hcd_at91_drv_suspend(struct device *dev) * * REVISIT: some boards will be able to turn VBUS off... */ - if (at91_suspend_entering_slow_clock()) { + if (!ohci_at91->wakeup) { ohci->hc_control = ohci_readl(ohci, &ohci->regs->control); ohci->hc_control &= OHCI_CTRL_RWC; ohci_writel(ohci, ohci->hc_control, &ohci->regs->control); @@ -653,11 +665,10 @@ static int ohci_hcd_at91_drv_resume(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); - if (device_may_wakeup(dev)) + if (ohci_at91->wakeup) disable_irq_wake(hcd->irq); - if (!ohci_at91->clocked) - at91_start_clock(ohci_at91); + at91_start_clock(ohci_at91); ohci_resume(hcd, false); return 0; -- cgit v0.10.2 From a6f0331236fa75afba14bbcf6668d42cebb55c43 Mon Sep 17 00:00:00 2001 From: Lennart Sorensen Date: Wed, 21 Jan 2015 15:24:27 -0500 Subject: USB: cp210x: add ID for RUGGEDCOM USB Serial Console Added the USB serial console device ID for Siemens Ruggedcom devices which have a USB port for their serial console. Signed-off-by: Len Sorensen Cc: stable Signed-off-by: Johan Hovold diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index f4c56fc..f40c856 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -56,6 +56,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x0846, 0x1100) }, /* NetGear Managed Switch M4100 series, M5300 series, M7100 series */ { USB_DEVICE(0x08e6, 0x5501) }, /* Gemalto Prox-PU/CU contactless smartcard reader */ { USB_DEVICE(0x08FD, 0x000A) }, /* Digianswer A/S , ZigBee/802.15.4 MAC Device */ + { USB_DEVICE(0x0908, 0x01FF) }, /* Siemens RUGGEDCOM USB Serial Console */ { USB_DEVICE(0x0BED, 0x1100) }, /* MEI (TM) Cashflow-SC Bill/Voucher Acceptor */ { USB_DEVICE(0x0BED, 0x1101) }, /* MEI series 2000 Combo Acceptor */ { USB_DEVICE(0x0FCF, 0x1003) }, /* Dynastream ANT development board */ -- cgit v0.10.2 From 32357605ce7bcd36cb6215a21cad12cd8f51b74e Mon Sep 17 00:00:00 2001 From: Sharon Dvir Date: Thu, 22 Jan 2015 12:15:25 +0200 Subject: USB: Add missing word to comment in mod_devicetable.h The documentation of match_flags in struct usb_device_id said: 'Bit mask controlling of the other fields are used to match against new devices.' Changed to: 'Bit mask controlling which of the other fields are used to match against new devices.' By adding the word 'which' and editing the next lines to not exceed 80 chars. Signed-off-by: Sharon Dvir Signed-off-by: Greg Kroah-Hartman diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 745def8..470a240 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -53,9 +53,9 @@ struct ieee1394_device_id { /** * struct usb_device_id - identifies USB devices for probing and hotplugging - * @match_flags: Bit mask controlling of the other fields are used to match - * against new devices. Any field except for driver_info may be used, - * although some only make sense in conjunction with other fields. + * @match_flags: Bit mask controlling which of the other fields are used to + * match against new devices. Any field except for driver_info may be + * used, although some only make sense in conjunction with other fields. * This is usually set by a USB_DEVICE_*() macro, which sets all * other fields in this structure except for driver_info. * @idVendor: USB vendor ID for a device; numbers are assigned -- cgit v0.10.2 From c509ba6e48d394aa7fe0f04c38493967c1268317 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:57:18 +0300 Subject: usb: musb: virthub: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index b072420..662de58 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -349,9 +349,9 @@ int musb_hub_control( desc->bDescriptorType = 0x29; desc->bNbrPorts = 1; desc->wHubCharacteristics = cpu_to_le16( - 0x0001 /* per-port power switching */ - | 0x0010 /* no overcurrent reporting */ - ); + HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */ + | HUB_CHAR_NO_OCPM /* no overcurrent reporting */ + ); desc->bPwrOn2PwrGood = 5; /* msec/2 */ desc->bHubContrCurrent = 0; -- cgit v0.10.2 From 16eb1a67f3e6f8b90474b882fe210df229da7573 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:59:09 +0300 Subject: usb: renesas_usbhs: mod_host: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c index f0d3231..96eead6 100644 --- a/drivers/usb/renesas_usbhs/mod_host.c +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -1234,7 +1234,8 @@ static int __usbhsh_hub_get_status(struct usbhsh_hpriv *hpriv, desc->bNbrPorts = roothub_id; desc->bDescLength = 9; desc->bPwrOn2PwrGood = 0; - desc->wHubCharacteristics = cpu_to_le16(0x0011); + desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM); desc->u.hs.DeviceRemovable[0] = (roothub_id << 1); desc->u.hs.DeviceRemovable[1] = ~0; dev_dbg(dev, "%s :: GetHubDescriptor\n", __func__); -- cgit v0.10.2 From 6e3f53ab5bd3092e195b290802208a39261fdf26 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 19 Jan 2015 12:53:16 +0900 Subject: usb: renesas_usbhs: add usbhsf_dma_init_pdev() function To add support for requesting DT DMA in the future, this patch adds usbhsf_dma_init_pdev() function. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index f46271c..48e31b9 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -1054,10 +1054,8 @@ static void usbhsf_dma_quit(struct usbhs_priv *priv, struct usbhs_fifo *fifo) fifo->rx_chan = NULL; } -static void usbhsf_dma_init(struct usbhs_priv *priv, - struct usbhs_fifo *fifo) +static void usbhsf_dma_init_pdev(struct usbhs_fifo *fifo) { - struct device *dev = usbhs_priv_to_dev(priv); dma_cap_mask_t mask; dma_cap_zero(mask); @@ -1069,6 +1067,14 @@ static void usbhsf_dma_init(struct usbhs_priv *priv, dma_cap_set(DMA_SLAVE, mask); fifo->rx_chan = dma_request_channel(mask, usbhsf_dma_filter, &fifo->rx_slave); +} + +static void usbhsf_dma_init(struct usbhs_priv *priv, + struct usbhs_fifo *fifo) +{ + struct device *dev = usbhs_priv_to_dev(priv); + + usbhsf_dma_init_pdev(fifo); if (fifo->tx_chan || fifo->rx_chan) dev_dbg(dev, "enable DMAEngine (%s%s%s)\n", -- cgit v0.10.2 From abd2dbf6bb1b5f3a03a8c76b1a8879da1dd30caa Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 19 Jan 2015 12:53:17 +0900 Subject: usb: renesas_usbhs: add support for requesting DT DMA This patch adds dma_request_slave_channel_reason() calling to request dma slave channels for multiplatform environment. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt index b08c903..61b045b 100644 --- a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt +++ b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt @@ -14,6 +14,8 @@ Optional properties: function should be enabled - phys: phandle + phy specifier pair - phy-names: must be "usb" + - dmas: Must contain a list of references to DMA specifiers. + - dma-names : Must contain a list of DMA names, "tx" or "rx". Example: usbhs: usb@e6590000 { diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index 48e31b9..4c086b1 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -1069,12 +1069,21 @@ static void usbhsf_dma_init_pdev(struct usbhs_fifo *fifo) &fifo->rx_slave); } +static void usbhsf_dma_init_dt(struct device *dev, struct usbhs_fifo *fifo) +{ + fifo->tx_chan = dma_request_slave_channel_reason(dev, "tx"); + fifo->rx_chan = dma_request_slave_channel_reason(dev, "rx"); +} + static void usbhsf_dma_init(struct usbhs_priv *priv, struct usbhs_fifo *fifo) { struct device *dev = usbhs_priv_to_dev(priv); - usbhsf_dma_init_pdev(fifo); + if (dev->of_node) + usbhsf_dma_init_dt(dev, fifo); + else + usbhsf_dma_init_pdev(fifo); if (fifo->tx_chan || fifo->rx_chan) dev_dbg(dev, "enable DMAEngine (%s%s%s)\n", -- cgit v0.10.2 From 0149b07a9e28b0d8bd2fc1c238ffe7d530c2673f Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Mon, 26 Jan 2015 16:22:06 -0600 Subject: usb: musb: cppi41: correct the macro name EP_MODE_AUTOREG_* The macro EP_MODE_AUTOREG_* should be called EP_MODE_AUTOREQ_*, as they are used for register AUTOREQ. Signed-off-by: Bin Liu Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index c39a16a..6af10e7 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -9,9 +9,9 @@ #define RNDIS_REG(x) (0x80 + ((x - 1) * 4)) -#define EP_MODE_AUTOREG_NONE 0 -#define EP_MODE_AUTOREG_ALL_NEOP 1 -#define EP_MODE_AUTOREG_ALWAYS 3 +#define EP_MODE_AUTOREQ_NONE 0 +#define EP_MODE_AUTOREQ_ALL_NEOP 1 +#define EP_MODE_AUTOREQ_ALWAYS 3 #define EP_MODE_DMA_TRANSPARENT 0 #define EP_MODE_DMA_RNDIS 1 @@ -404,19 +404,19 @@ static bool cppi41_configure_channel(struct dma_channel *channel, /* auto req */ cppi41_set_autoreq_mode(cppi41_channel, - EP_MODE_AUTOREG_ALL_NEOP); + EP_MODE_AUTOREQ_ALL_NEOP); } else { musb_writel(musb->ctrl_base, RNDIS_REG(cppi41_channel->port_num), 0); cppi41_set_dma_mode(cppi41_channel, EP_MODE_DMA_TRANSPARENT); cppi41_set_autoreq_mode(cppi41_channel, - EP_MODE_AUTOREG_NONE); + EP_MODE_AUTOREQ_NONE); } } else { /* fallback mode */ cppi41_set_dma_mode(cppi41_channel, EP_MODE_DMA_TRANSPARENT); - cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREG_NONE); + cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE); len = min_t(u32, packet_sz, len); } cppi41_channel->prog_len = len; -- cgit v0.10.2 From cb83df77f3ec151d68a1b6be957207e6fc7b7f50 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Mon, 26 Jan 2015 16:22:07 -0600 Subject: usb: musb: cppi41: improve rx channel abort routine 1. set AUTOREQ to NONE at the beginning of teardown; 2. add delay for dma pipeline to drain; 3. Do not set USB_TDOWN bit for RX teardown. The CPPI hw has an issue that when tearing down a RX channel, if another RX channel is receiving data, the CPPI will lockup. To workaround the issue, do not set the CPPI TD bit. The steps before this point ensures the CPPI channel will be torn down properly. Signed-off-by: Bin Liu Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index 6af10e7..be84562 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -549,10 +549,15 @@ static int cppi41_dma_channel_abort(struct dma_channel *channel) csr &= ~MUSB_TXCSR_DMAENAB; musb_writew(epio, MUSB_TXCSR, csr); } else { + cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE); + csr = musb_readw(epio, MUSB_RXCSR); csr &= ~(MUSB_RXCSR_H_REQPKT | MUSB_RXCSR_DMAENAB); musb_writew(epio, MUSB_RXCSR, csr); + /* wait to drain cppi dma pipe line */ + udelay(50); + csr = musb_readw(epio, MUSB_RXCSR); if (csr & MUSB_RXCSR_RXPKTRDY) { csr |= MUSB_RXCSR_FLUSHFIFO; @@ -566,13 +571,14 @@ static int cppi41_dma_channel_abort(struct dma_channel *channel) tdbit <<= 16; do { - musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); + if (is_tx) + musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); ret = dmaengine_terminate_all(cppi41_channel->dc); } while (ret == -EAGAIN); - musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); - if (is_tx) { + musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); + csr = musb_readw(epio, MUSB_TXCSR); if (csr & MUSB_TXCSR_TXPKTRDY) { csr |= MUSB_TXCSR_FLUSHFIFO; -- cgit v0.10.2 From df6738d0d23321da834e9025845e26e09b0f6615 Mon Sep 17 00:00:00 2001 From: Mario Schuknecht Date: Mon, 26 Jan 2015 20:30:27 +0100 Subject: usb: gadget: Fix os desc test USB vendor type is encoded in field bmRequestType. Make test USB_TYPE_VENDOR with bRequestType instead of bRequest. Signed-off-by: Mario Schuknecht Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 6178353..13adfd1 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1655,7 +1655,7 @@ unknown: * OS descriptors handling */ if (cdev->use_os_string && cdev->os_desc_config && - (ctrl->bRequest & USB_TYPE_VENDOR) && + (ctrl->bRequestType & USB_TYPE_VENDOR) && ctrl->bRequest == cdev->b_vendor_code) { struct usb_request *req; struct usb_configuration *os_desc_cfg; -- cgit v0.10.2 From acba23fec527012e901636e4ba091ee25461c943 Mon Sep 17 00:00:00 2001 From: Mario Schuknecht Date: Mon, 26 Jan 2015 20:40:21 +0100 Subject: usb: gadget: f_fs: Fix loop variable Use if-loop variable 'epfile' instead of start variable 'epfiles'. Now the correct endpoint file name is stored. Signed-off-by: Mario Schuknecht Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index e78a2c6..14e44d7 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1611,10 +1611,10 @@ static int ffs_epfiles_create(struct ffs_data *ffs) mutex_init(&epfile->mutex); init_waitqueue_head(&epfile->wait); if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) - sprintf(epfiles->name, "ep%02x", ffs->eps_addrmap[i]); + sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); else - sprintf(epfiles->name, "ep%u", i); - epfile->dentry = ffs_sb_create_file(ffs->sb, epfiles->name, + sprintf(epfile->name, "ep%u", i); + epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name, epfile, &ffs_epfile_operations); if (unlikely(!epfile->dentry)) { -- cgit v0.10.2 From 5e33f6fdf735cda1d4580fe6f1878da05718fe73 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Fri, 23 Jan 2015 13:41:01 +0100 Subject: usb: gadget: ffs: add eventfd notification about ffs events Add eventfd which notifies userspace about ep0 events and AIO completion events. It simplifies using of FunctionFS with event loop, because now we need to poll on single file (instead of polling on ep0 and eventfd's supplied to AIO layer). FunctionFS eventfd is not triggered if another eventfd is supplied to AIO layer (in AIO request). It can be useful, for example, when we want to handle AIO transations for chosen endpoint in separate thread. Signed-off-by: Robert Baldyga Acked-by: Michal Nazarewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 14e44d7..af98b09 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "u_fs.h" #include "u_f.h" @@ -153,6 +154,8 @@ struct ffs_io_data { struct usb_ep *ep; struct usb_request *req; + + struct ffs_data *ffs; }; struct ffs_desc_helper { @@ -674,6 +677,9 @@ static void ffs_user_copy_worker(struct work_struct *work) aio_complete(io_data->kiocb, ret, ret); + if (io_data->ffs->ffs_eventfd && !io_data->kiocb->ki_eventfd) + eventfd_signal(io_data->ffs->ffs_eventfd, 1); + usb_ep_free_request(io_data->ep, io_data->req); io_data->kiocb->private = NULL; @@ -827,6 +833,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) io_data->buf = data; io_data->ep = ep->ep; io_data->req = req; + io_data->ffs = epfile->ffs; req->context = io_data; req->complete = ffs_epfile_async_io_complete; @@ -1510,6 +1517,9 @@ static void ffs_data_clear(struct ffs_data *ffs) if (ffs->epfiles) ffs_epfiles_destroy(ffs->epfiles, ffs->eps_count); + if (ffs->ffs_eventfd) + eventfd_ctx_put(ffs->ffs_eventfd); + kfree(ffs->raw_descs_data); kfree(ffs->raw_strings); kfree(ffs->stringtabs); @@ -2169,7 +2179,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, FUNCTIONFS_HAS_HS_DESC | FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_MS_OS_DESC | - FUNCTIONFS_VIRTUAL_ADDR)) { + FUNCTIONFS_VIRTUAL_ADDR | + FUNCTIONFS_EVENTFD)) { ret = -ENOSYS; goto error; } @@ -2180,6 +2191,20 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, goto error; } + if (flags & FUNCTIONFS_EVENTFD) { + if (len < 4) + goto error; + ffs->ffs_eventfd = + eventfd_ctx_fdget((int)get_unaligned_le32(data)); + if (IS_ERR(ffs->ffs_eventfd)) { + ret = PTR_ERR(ffs->ffs_eventfd); + ffs->ffs_eventfd = NULL; + goto error; + } + data += 4; + len -= 4; + } + /* Read fs_count, hs_count and ss_count (if present) */ for (i = 0; i < 3; ++i) { if (!(flags & (1 << i))) { @@ -2454,6 +2479,8 @@ static void __ffs_event_add(struct ffs_data *ffs, pr_vdebug("adding event %d\n", type); ffs->ev.types[ffs->ev.count++] = type; wake_up_locked(&ffs->ev.waitq); + if (ffs->ffs_eventfd) + eventfd_signal(ffs->ffs_eventfd, 1); } static void ffs_event_add(struct ffs_data *ffs, diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 284a1f0..6013985 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -272,6 +272,7 @@ struct ffs_data { kgid_t gid; } file_perms; + struct eventfd_ctx *ffs_eventfd; bool no_disconnect; struct work_struct reset_work; diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 295ba29..108dd79 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -20,6 +20,7 @@ enum functionfs_flags { FUNCTIONFS_HAS_SS_DESC = 4, FUNCTIONFS_HAS_MS_OS_DESC = 8, FUNCTIONFS_VIRTUAL_ADDR = 16, + FUNCTIONFS_EVENTFD = 32, }; /* Descriptor of an non-audio endpoint */ -- cgit v0.10.2 From 9fdd84d23c76825f3fc3c0782ea433c52d41971a Mon Sep 17 00:00:00 2001 From: Asaf Vertz Date: Wed, 21 Jan 2015 11:06:34 +0200 Subject: usb: gadget: zero: fix format string warnings Fixed the following warnings (reported by cppcheck): [drivers/usb/gadget/function/f_sourcesink.c:1217]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1261]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1305]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1349]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1393]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1437]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1476]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1520]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1564]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1608]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int' Reviewed-by: Amit Virdi Signed-off-by: Asaf Vertz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index 80be25b..e07c50c 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -1214,7 +1214,7 @@ static ssize_t f_ss_opts_pattern_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->pattern); + result = sprintf(page, "%u", opts->pattern); mutex_unlock(&opts->lock); return result; @@ -1258,7 +1258,7 @@ static ssize_t f_ss_opts_isoc_interval_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_interval); + result = sprintf(page, "%u", opts->isoc_interval); mutex_unlock(&opts->lock); return result; @@ -1302,7 +1302,7 @@ static ssize_t f_ss_opts_isoc_maxpacket_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_maxpacket); + result = sprintf(page, "%u", opts->isoc_maxpacket); mutex_unlock(&opts->lock); return result; @@ -1346,7 +1346,7 @@ static ssize_t f_ss_opts_isoc_mult_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_mult); + result = sprintf(page, "%u", opts->isoc_mult); mutex_unlock(&opts->lock); return result; @@ -1390,7 +1390,7 @@ static ssize_t f_ss_opts_isoc_maxburst_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_maxburst); + result = sprintf(page, "%u", opts->isoc_maxburst); mutex_unlock(&opts->lock); return result; @@ -1434,7 +1434,7 @@ static ssize_t f_ss_opts_bulk_buflen_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->bulk_buflen); + result = sprintf(page, "%u", opts->bulk_buflen); mutex_unlock(&opts->lock); return result; @@ -1473,7 +1473,7 @@ static ssize_t f_ss_opts_int_interval_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_interval); + result = sprintf(page, "%u", opts->int_interval); mutex_unlock(&opts->lock); return result; @@ -1517,7 +1517,7 @@ static ssize_t f_ss_opts_int_maxpacket_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_maxpacket); + result = sprintf(page, "%u", opts->int_maxpacket); mutex_unlock(&opts->lock); return result; @@ -1561,7 +1561,7 @@ static ssize_t f_ss_opts_int_mult_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_mult); + result = sprintf(page, "%u", opts->int_mult); mutex_unlock(&opts->lock); return result; @@ -1605,7 +1605,7 @@ static ssize_t f_ss_opts_int_maxburst_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_maxburst); + result = sprintf(page, "%u", opts->int_maxburst); mutex_unlock(&opts->lock); return result; -- cgit v0.10.2 From 7eb42c6e808a0a18b8493268d06e05c1654fbbb2 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:40 +0200 Subject: usb: isp1760: Use the gpio descriptor API Switching to the managed gpio descriptor API simplifies both error and cleanup code paths (by removing the need to free the gpio) and runtime code (by removing manual handling of the active low flag). It also permits handling the reset gpio entirely from within the HCD code, sharing it between the different glue layers. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 395649f..e4a9424 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -11,6 +11,7 @@ * (c) 2011 Arvid Brodin * */ +#include #include #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #include "isp1760-hcd.h" @@ -57,7 +57,7 @@ struct isp1760_hcd { unsigned long next_statechange; unsigned int devflags; - int rst_gpio; + struct gpio_desc *rst_gpio; }; static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) @@ -444,15 +444,10 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) u32 scratch, hwmode; /* low-level chip reset */ - if (gpio_is_valid(priv->rst_gpio)) { - unsigned int rst_lvl; - - rst_lvl = (priv->devflags & - ISP1760_FLAG_RESET_ACTIVE_HIGH) ? 1 : 0; - - gpio_set_value(priv->rst_gpio, rst_lvl); + if (priv->rst_gpio) { + gpiod_set_value_cansleep(priv->rst_gpio, 1); mdelay(50); - gpio_set_value(priv->rst_gpio, !rst_lvl); + gpiod_set_value_cansleep(priv->rst_gpio, 0); } /* Setup HW Mode Control: This assumes a level active-low interrupt */ @@ -2215,7 +2210,6 @@ void deinit_kmem_cache(void) struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, - int rst_gpio, struct device *dev, const char *busname, unsigned int devflags) { @@ -2235,7 +2229,13 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, priv = hcd_to_priv(hcd); priv->devflags = devflags; - priv->rst_gpio = rst_gpio; + + priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); + if (IS_ERR(priv->rst_gpio)) { + ret = PTR_ERR(priv->rst_gpio); + goto err_put; + } + init_memory(priv); hcd->regs = ioremap(res_start, res_len); if (!hcd->regs) { diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 33dc79c..fda0f2d 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -4,7 +4,6 @@ /* exports for if */ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, - int rst_gpio, struct device *dev, const char *busname, unsigned int devflags); int init_kmem_once(void); @@ -127,7 +126,6 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, #define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ #define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ #define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ -#define ISP1760_FLAG_RESET_ACTIVE_HIGH 0x80000000 /* RESET GPIO active high */ /* chip memory management */ struct memory_chunk { diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 09254a4..7e8e7f6 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -24,7 +24,6 @@ #include #include #include -#include #endif #ifdef CONFIG_PCI @@ -34,7 +33,6 @@ #if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) struct isp1760 { struct usb_hcd *hcd; - int rst_gpio; }; static int of_isp1760_probe(struct platform_device *dev) @@ -47,7 +45,6 @@ static int of_isp1760_probe(struct platform_device *dev) resource_size_t res_len; int ret; unsigned int devflags = 0; - enum of_gpio_flags gpio_flags; u32 bus_width = 0; drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); @@ -94,36 +91,17 @@ static int of_isp1760_probe(struct platform_device *dev) if (of_get_property(dp, "dreq-polarity", NULL) != NULL) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - drvdata->rst_gpio = of_get_gpio_flags(dp, 0, &gpio_flags); - if (gpio_is_valid(drvdata->rst_gpio)) { - ret = gpio_request(drvdata->rst_gpio, dev_name(&dev->dev)); - if (!ret) { - if (!(gpio_flags & OF_GPIO_ACTIVE_LOW)) { - devflags |= ISP1760_FLAG_RESET_ACTIVE_HIGH; - gpio_direction_output(drvdata->rst_gpio, 0); - } else { - gpio_direction_output(drvdata->rst_gpio, 1); - } - } else { - drvdata->rst_gpio = ret; - } - } - drvdata->hcd = isp1760_register(memory.start, res_len, virq, - IRQF_SHARED, drvdata->rst_gpio, - &dev->dev, dev_name(&dev->dev), - devflags); + IRQF_SHARED, &dev->dev, + dev_name(&dev->dev), devflags); if (IS_ERR(drvdata->hcd)) { ret = PTR_ERR(drvdata->hcd); - goto free_gpio; + goto release_reg; } platform_set_drvdata(dev, drvdata); return ret; -free_gpio: - if (gpio_is_valid(drvdata->rst_gpio)) - gpio_free(drvdata->rst_gpio); release_reg: release_mem_region(memory.start, res_len); free_data: @@ -140,9 +118,6 @@ static int of_isp1760_remove(struct platform_device *dev) release_mem_region(drvdata->hcd->rsrc_start, drvdata->hcd->rsrc_len); usb_put_hcd(drvdata->hcd); - if (gpio_is_valid(drvdata->rst_gpio)) - gpio_free(drvdata->rst_gpio); - kfree(drvdata); return 0; } @@ -279,8 +254,8 @@ static int isp1761_pci_probe(struct pci_dev *dev, dev->dev.dma_mask = NULL; hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, -ENOENT, &dev->dev, dev_name(&dev->dev), - devflags); + IRQF_SHARED, &dev->dev, dev_name(&dev->dev), + devflags); if (IS_ERR(hcd)) { ret_status = -ENODEV; goto cleanup3; @@ -392,8 +367,8 @@ static int isp1760_plat_probe(struct platform_device *pdev) } hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, - irqflags, -ENOENT, - &pdev->dev, dev_name(&pdev->dev), devflags); + irqflags, &pdev->dev, dev_name(&pdev->dev), + devflags); platform_set_drvdata(pdev, hcd); -- cgit v0.10.2 From 8a8b96f487e8b7ba0a9384df8e7ecc514152114c Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:41 +0200 Subject: usb: isp1760: Remove isp1760 glue structure The structure is allocated for the sole purpose of holding a pointer to the HCD and being stored in platform device driver data. Store the HCD pointer directly instead. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 7e8e7f6..b5bcb99 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -31,13 +31,9 @@ #endif #if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) -struct isp1760 { - struct usb_hcd *hcd; -}; - static int of_isp1760_probe(struct platform_device *dev) { - struct isp1760 *drvdata; + struct usb_hcd *hcd; struct device_node *dp = dev->dev.of_node; struct resource *res; struct resource memory; @@ -47,23 +43,15 @@ static int of_isp1760_probe(struct platform_device *dev) unsigned int devflags = 0; u32 bus_width = 0; - drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - ret = of_address_to_resource(dp, 0, &memory); - if (ret) { - ret = -ENXIO; - goto free_data; - } + if (ret) + return -ENXIO; res_len = resource_size(&memory); res = request_mem_region(memory.start, res_len, dev_name(&dev->dev)); - if (!res) { - ret = -EBUSY; - goto free_data; - } + if (!res) + return -EBUSY; virq = irq_of_parse_and_map(dp, 0); if (!virq) { @@ -91,34 +79,30 @@ static int of_isp1760_probe(struct platform_device *dev) if (of_get_property(dp, "dreq-polarity", NULL) != NULL) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - drvdata->hcd = isp1760_register(memory.start, res_len, virq, - IRQF_SHARED, &dev->dev, - dev_name(&dev->dev), devflags); - if (IS_ERR(drvdata->hcd)) { - ret = PTR_ERR(drvdata->hcd); + hcd = isp1760_register(memory.start, res_len, virq, IRQF_SHARED, + &dev->dev, dev_name(&dev->dev), devflags); + if (IS_ERR(hcd)) { + ret = PTR_ERR(hcd); goto release_reg; } - platform_set_drvdata(dev, drvdata); + platform_set_drvdata(dev, hcd); return ret; release_reg: release_mem_region(memory.start, res_len); -free_data: - kfree(drvdata); return ret; } static int of_isp1760_remove(struct platform_device *dev) { - struct isp1760 *drvdata = platform_get_drvdata(dev); + struct usb_hcd *hcd = platform_get_drvdata(dev); - usb_remove_hcd(drvdata->hcd); - iounmap(drvdata->hcd->regs); - release_mem_region(drvdata->hcd->rsrc_start, drvdata->hcd->rsrc_len); - usb_put_hcd(drvdata->hcd); + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); - kfree(drvdata); return 0; } -- cgit v0.10.2 From fcead8431d245af312afcbcbfbfc34a0fec77cd9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:42 +0200 Subject: usb: isp1760: Retrieve pdev memory resource from hcd at remove time The platform driver remove function needs to release the memory resource requested at probe time. Instead of retrieving the resource from the platform device, retrieve it from the usb_hcd. This mimics the behaviour of the PCI and OF glues, and will make it easier to share code between all three glue layers. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b5bcb99..025edf2 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -373,15 +373,11 @@ out: static int isp1760_plat_remove(struct platform_device *pdev) { - struct resource *mem_res; - resource_size_t mem_size; struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); - mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - mem_size = resource_size(mem_res); - release_mem_region(mem_res->start, mem_size); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); -- cgit v0.10.2 From 5324713c998d6615d858606188fe69a23d1de9d8 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:43 +0200 Subject: usb: isp1760: Unmap I/O registers at platform device removal The I/O registers are mapped in the HCD code but must be unmapped by glue code. The OF and PCI glue code unmap them correctly but the platform glue code is missing an iounmap() call. Fix it. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 025edf2..730caa1 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -376,9 +376,8 @@ static int isp1760_plat_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); - + iounmap(hcd->regs); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); return 0; -- cgit v0.10.2 From c3cc40ccea2cd3533a90d915b40f304843642df9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:44 +0200 Subject: usb: isp1760: Merge platform and OF glue codes Both handle platform devices, merge them. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 730caa1..3db98da 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -12,121 +12,18 @@ #include #include #include +#include #include +#include #include #include #include "isp1760-hcd.h" -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) -#include -#include -#include -#include -#include -#endif - #ifdef CONFIG_PCI #include #endif -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) -static int of_isp1760_probe(struct platform_device *dev) -{ - struct usb_hcd *hcd; - struct device_node *dp = dev->dev.of_node; - struct resource *res; - struct resource memory; - int virq; - resource_size_t res_len; - int ret; - unsigned int devflags = 0; - u32 bus_width = 0; - - ret = of_address_to_resource(dp, 0, &memory); - if (ret) - return -ENXIO; - - res_len = resource_size(&memory); - - res = request_mem_region(memory.start, res_len, dev_name(&dev->dev)); - if (!res) - return -EBUSY; - - virq = irq_of_parse_and_map(dp, 0); - if (!virq) { - ret = -ENODEV; - goto release_reg; - } - - if (of_device_is_compatible(dp, "nxp,usb-isp1761")) - devflags |= ISP1760_FLAG_ISP1761; - - /* Some systems wire up only 16 of the 32 data lines */ - of_property_read_u32(dp, "bus-width", &bus_width); - if (bus_width == 16) - devflags |= ISP1760_FLAG_BUS_WIDTH_16; - - if (of_get_property(dp, "port1-otg", NULL) != NULL) - devflags |= ISP1760_FLAG_OTG_EN; - - if (of_get_property(dp, "analog-oc", NULL) != NULL) - devflags |= ISP1760_FLAG_ANALOG_OC; - - if (of_get_property(dp, "dack-polarity", NULL) != NULL) - devflags |= ISP1760_FLAG_DACK_POL_HIGH; - - if (of_get_property(dp, "dreq-polarity", NULL) != NULL) - devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - - hcd = isp1760_register(memory.start, res_len, virq, IRQF_SHARED, - &dev->dev, dev_name(&dev->dev), devflags); - if (IS_ERR(hcd)) { - ret = PTR_ERR(hcd); - goto release_reg; - } - - platform_set_drvdata(dev, hcd); - return ret; - -release_reg: - release_mem_region(memory.start, res_len); - return ret; -} - -static int of_isp1760_remove(struct platform_device *dev) -{ - struct usb_hcd *hcd = platform_get_drvdata(dev); - - usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); - - return 0; -} - -static const struct of_device_id of_isp1760_match[] = { - { - .compatible = "nxp,usb-isp1760", - }, - { - .compatible = "nxp,usb-isp1761", - }, - { }, -}; -MODULE_DEVICE_TABLE(of, of_isp1760_match); - -static struct platform_driver isp1760_of_driver = { - .driver = { - .name = "nxp-isp1760", - .of_match_table = of_isp1760_match, - }, - .probe = of_isp1760_probe, - .remove = of_isp1760_remove, -}; -#endif - #ifdef CONFIG_PCI static int isp1761_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) @@ -304,26 +201,23 @@ static struct pci_driver isp1761_pci_driver = { static int isp1760_plat_probe(struct platform_device *pdev) { - int ret = 0; - struct usb_hcd *hcd; + unsigned long irqflags = IRQF_SHARED; + unsigned int devflags = 0; struct resource *mem_res; struct resource *irq_res; resource_size_t mem_size; - struct isp1760_platform_data *priv = dev_get_platdata(&pdev->dev); - unsigned int devflags = 0; - unsigned long irqflags = IRQF_SHARED; + struct usb_hcd *hcd; + int ret; mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem_res) { pr_warning("isp1760: Memory resource not available\n"); - ret = -ENODEV; - goto out; + return -ENODEV; } mem_size = resource_size(mem_res); if (!request_mem_region(mem_res->start, mem_size, "isp1760")) { pr_warning("isp1760: Cannot reserve the memory resource\n"); - ret = -EBUSY; - goto out; + return -EBUSY; } irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); @@ -335,39 +229,63 @@ static int isp1760_plat_probe(struct platform_device *pdev) irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; - if (priv) { - if (priv->is_isp1761) + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + struct device_node *dp = pdev->dev.of_node; + u32 bus_width = 0; + + if (of_device_is_compatible(dp, "nxp,usb-isp1761")) devflags |= ISP1760_FLAG_ISP1761; - if (priv->bus_width_16) + + /* Some systems wire up only 16 of the 32 data lines */ + of_property_read_u32(dp, "bus-width", &bus_width); + if (bus_width == 16) devflags |= ISP1760_FLAG_BUS_WIDTH_16; - if (priv->port1_otg) + + if (of_property_read_bool(dp, "port1-otg")) devflags |= ISP1760_FLAG_OTG_EN; - if (priv->analog_oc) + + if (of_property_read_bool(dp, "analog-oc")) devflags |= ISP1760_FLAG_ANALOG_OC; - if (priv->dack_polarity_high) + + if (of_property_read_bool(dp, "dack-polarity")) devflags |= ISP1760_FLAG_DACK_POL_HIGH; - if (priv->dreq_polarity_high) + + if (of_property_read_bool(dp, "dreq-polarity")) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } else if (dev_get_platdata(&pdev->dev)) { + struct isp1760_platform_data *pdata = + dev_get_platdata(&pdev->dev); + + if (pdata->is_isp1761) + devflags |= ISP1760_FLAG_ISP1761; + if (pdata->bus_width_16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + if (pdata->port1_otg) + devflags |= ISP1760_FLAG_OTG_EN; + if (pdata->analog_oc) + devflags |= ISP1760_FLAG_ANALOG_OC; + if (pdata->dack_polarity_high) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + if (pdata->dreq_polarity_high) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; } hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, irqflags, &pdev->dev, dev_name(&pdev->dev), devflags); - - platform_set_drvdata(pdev, hcd); - if (IS_ERR(hcd)) { pr_warning("isp1760: Failed to register the HCD device\n"); - ret = -ENODEV; + ret = PTR_ERR(hcd); goto cleanup; } + platform_set_drvdata(pdev, hcd); + pr_info("ISP1760 USB device initialised\n"); - return ret; + return 0; cleanup: release_mem_region(mem_res->start, mem_size); -out: return ret; } @@ -383,11 +301,21 @@ static int isp1760_plat_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id isp1760_of_match[] = { + { .compatible = "nxp,usb-isp1760", }, + { .compatible = "nxp,usb-isp1761", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isp1760_of_match); +#endif + static struct platform_driver isp1760_plat_driver = { .probe = isp1760_plat_probe, .remove = isp1760_plat_remove, .driver = { .name = "isp1760", + .of_match_table = of_match_ptr(isp1760_of_match), }, }; @@ -400,11 +328,6 @@ static int __init isp1760_init(void) ret = platform_driver_register(&isp1760_plat_driver); if (!ret) any_ret = 0; -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) - ret = platform_driver_register(&isp1760_of_driver); - if (!ret) - any_ret = 0; -#endif #ifdef CONFIG_PCI ret = pci_register_driver(&isp1761_pci_driver); if (!ret) @@ -420,9 +343,6 @@ module_init(isp1760_init); static void __exit isp1760_exit(void) { platform_driver_unregister(&isp1760_plat_driver); -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) - platform_driver_unregister(&isp1760_of_driver); -#endif #ifdef CONFIG_PCI pci_unregister_driver(&isp1761_pci_driver); #endif -- cgit v0.10.2 From 10c73f09dc9be7c7373483f9965dd535c5788f09 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:45 +0200 Subject: usb: isp1760: Move removal cleanup code to isp1760-hcd.c The removal cleanup code is duplicated between the different bus glues. Move it to a central location. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index e4a9424..7498727 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2263,6 +2263,16 @@ err_put: return ERR_PTR(ret); } +void isp1760_unregister(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); MODULE_AUTHOR("Sebastian Siewior "); MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index fda0f2d..ea13a58 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -6,6 +6,8 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, struct device *dev, const char *busname, unsigned int devflags); +void isp1760_unregister(struct device *dev); + int init_kmem_once(void); void deinit_kmem_cache(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 3db98da..b96c62f 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -160,14 +160,7 @@ cleanup1: static void isp1761_pci_remove(struct pci_dev *dev) { - struct usb_hcd *hcd; - - hcd = pci_get_drvdata(dev); - - usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); + isp1760_unregister(&dev->dev); pci_disable_device(dev); } @@ -291,12 +284,7 @@ cleanup: static int isp1760_plat_remove(struct platform_device *pdev) { - struct usb_hcd *hcd = platform_get_drvdata(pdev); - - usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); + isp1760_unregister(&pdev->dev); return 0; } -- cgit v0.10.2 From 57f068bedca8b92157d09f971f2fb3adb76f8f96 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:46 +0200 Subject: usb: isp1760: Manage device driver data in common code Don't duplicate *_set_drvdata calls in glue code. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 7498727..19fbd69 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2252,6 +2252,8 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, goto err_unmap; device_wakeup_enable(hcd->self.controller); + dev_set_drvdata(dev, hcd); + return hcd; err_unmap: diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b96c62f..64eaf5d 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -146,7 +146,6 @@ static int isp1761_pci_probe(struct pci_dev *dev, iounmap(iobase); release_mem_region(nxp_pci_io_base, iolength); - pci_set_drvdata(dev, hcd); return 0; cleanup3: @@ -272,8 +271,6 @@ static int isp1760_plat_probe(struct platform_device *pdev) goto cleanup; } - platform_set_drvdata(pdev, hcd); - pr_info("ISP1760 USB device initialised\n"); return 0; -- cgit v0.10.2 From f0bdbb0ec6be7465eecbdbea058c027485a0d966 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:47 +0200 Subject: usb: isp1760: Don't expose hcd to glue code from isp1760_register The glue code probe functions don't need to access the hcd structure anymore. Modify isp1760_register to return an integer error code instead of the hcd pointer. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 19fbd69..4d6e50b 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -60,6 +60,9 @@ struct isp1760_hcd { struct gpio_desc *rst_gpio; }; +typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd); + static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) { return (struct isp1760_hcd *) (hcd->hcd_priv); @@ -2208,24 +2211,23 @@ void deinit_kmem_cache(void) kmem_cache_destroy(urb_listitem_cachep); } -struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, - int irq, unsigned long irqflags, - struct device *dev, const char *busname, - unsigned int devflags) +int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, + unsigned long irqflags, struct device *dev, + const char *busname, unsigned int devflags) { struct usb_hcd *hcd; struct isp1760_hcd *priv; int ret; if (usb_disabled()) - return ERR_PTR(-ENODEV); + return -ENODEV; /* prevent usb-core allocating DMA pages */ dev->dma_mask = NULL; hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); if (!hcd) - return ERR_PTR(-ENOMEM); + return -ENOMEM; priv = hcd_to_priv(hcd); priv->devflags = devflags; @@ -2254,7 +2256,7 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, dev_set_drvdata(dev, hcd); - return hcd; + return 0; err_unmap: iounmap(hcd->regs); @@ -2262,7 +2264,7 @@ err_unmap: err_put: usb_put_hcd(hcd); - return ERR_PTR(ret); + return ret; } void isp1760_unregister(struct device *dev) diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index ea13a58..372d2e5 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -2,10 +2,9 @@ #define _ISP1760_HCD_H_ /* exports for if */ -struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, - int irq, unsigned long irqflags, - struct device *dev, const char *busname, - unsigned int devflags); +int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, + unsigned long irqflags, struct device *dev, + const char *busname, unsigned int devflags); void isp1760_unregister(struct device *dev); int init_kmem_once(void); @@ -112,9 +111,6 @@ struct slotinfo { }; -typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct isp1760_qtd *qtd); - /* * Device flags that can vary from board to board. All of these * indicate the most "atypical" case, so that a devflags of 0 is diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 64eaf5d..03243b0 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -31,7 +31,6 @@ static int isp1761_pci_probe(struct pci_dev *dev, u8 latency, limit; __u32 reg_data; int retry_count; - struct usb_hcd *hcd; unsigned int devflags = 0; int ret_status = 0; @@ -134,13 +133,11 @@ static int isp1761_pci_probe(struct pci_dev *dev, writel(reg_data, iobase + PLX_INT_CSR_REG); dev->dev.dma_mask = NULL; - hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, &dev->dev, dev_name(&dev->dev), - devflags); - if (IS_ERR(hcd)) { - ret_status = -ENODEV; + ret_status = isp1760_register(pci_mem_phy0, memlength, dev->irq, + IRQF_SHARED, &dev->dev, + dev_name(&dev->dev), devflags); + if (ret_status < 0) goto cleanup3; - } /* done with PLX IO access */ iounmap(iobase); @@ -198,7 +195,6 @@ static int isp1760_plat_probe(struct platform_device *pdev) struct resource *mem_res; struct resource *irq_res; resource_size_t mem_size; - struct usb_hcd *hcd; int ret; mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -262,14 +258,11 @@ static int isp1760_plat_probe(struct platform_device *pdev) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; } - hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, + ret = isp1760_register(mem_res->start, mem_size, irq_res->start, irqflags, &pdev->dev, dev_name(&pdev->dev), devflags); - if (IS_ERR(hcd)) { - pr_warning("isp1760: Failed to register the HCD device\n"); - ret = PTR_ERR(hcd); + if (ret < 0) goto cleanup; - } pr_info("ISP1760 USB device initialised\n"); return 0; -- cgit v0.10.2 From 30573751daf60835ad1dba8b040b6a345f49120a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:48 +0200 Subject: usb: isp1760: Fix indentation in probe error path Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 4d6e50b..aa894a1 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2259,12 +2259,12 @@ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, return 0; err_unmap: - iounmap(hcd->regs); + iounmap(hcd->regs); err_put: - usb_put_hcd(hcd); + usb_put_hcd(hcd); - return ret; + return ret; } void isp1760_unregister(struct device *dev) -- cgit v0.10.2 From 5a6356ac62c8fa732e260123feb73655f7d919e6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:49 +0200 Subject: usb: isp1760: Prefix init_kmem_once and deinit_kmem_cache with isp1760_ The two functions are specific to the driver but have very generic names, subject to collisions. Rename them. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index aa894a1..0ae1719 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2177,7 +2177,7 @@ static const struct hc_driver isp1760_hc_driver = { .clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete, }; -int __init init_kmem_once(void) +int __init isp1760_init_kmem_once(void) { urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | @@ -2204,7 +2204,7 @@ int __init init_kmem_once(void) return 0; } -void deinit_kmem_cache(void) +void isp1760_deinit_kmem_cache(void) { kmem_cache_destroy(qtd_cachep); kmem_cache_destroy(qh_cachep); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 372d2e5..f97c9d6 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -7,8 +7,8 @@ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, const char *busname, unsigned int devflags); void isp1760_unregister(struct device *dev); -int init_kmem_once(void); -void deinit_kmem_cache(void); +int isp1760_init_kmem_once(void); +void isp1760_deinit_kmem_cache(void); /* EHCI capability registers */ #define HC_CAPLENGTH 0x00 diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 03243b0..e268b20 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -301,7 +301,7 @@ static int __init isp1760_init(void) { int ret, any_ret = -ENODEV; - init_kmem_once(); + isp1760_init_kmem_once(); ret = platform_driver_register(&isp1760_plat_driver); if (!ret) @@ -313,7 +313,7 @@ static int __init isp1760_init(void) #endif if (any_ret) - deinit_kmem_cache(); + isp1760_deinit_kmem_cache(); return any_ret; } module_init(isp1760_init); @@ -324,6 +324,6 @@ static void __exit isp1760_exit(void) #ifdef CONFIG_PCI pci_unregister_driver(&isp1761_pci_driver); #endif - deinit_kmem_cache(); + isp1760_deinit_kmem_cache(); } module_exit(isp1760_exit); -- cgit v0.10.2 From d69292a8f5eed074412b70bb59b745fc17213661 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:50 +0200 Subject: usb: isp1760: Remove busname argument to isp1760_register The argument is unused, remove it. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 0ae1719..9ba3023 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2213,7 +2213,7 @@ void isp1760_deinit_kmem_cache(void) int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, struct device *dev, - const char *busname, unsigned int devflags) + unsigned int devflags) { struct usb_hcd *hcd; struct isp1760_hcd *priv; diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index f97c9d6..cc65602 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -4,7 +4,7 @@ /* exports for if */ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, struct device *dev, - const char *busname, unsigned int devflags); + unsigned int devflags); void isp1760_unregister(struct device *dev); int isp1760_init_kmem_once(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index e268b20..ce572cc 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -134,8 +134,7 @@ static int isp1761_pci_probe(struct pci_dev *dev, dev->dev.dma_mask = NULL; ret_status = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, &dev->dev, - dev_name(&dev->dev), devflags); + IRQF_SHARED, &dev->dev, devflags); if (ret_status < 0) goto cleanup3; @@ -259,8 +258,7 @@ static int isp1760_plat_probe(struct platform_device *pdev) } ret = isp1760_register(mem_res->start, mem_size, irq_res->start, - irqflags, &pdev->dev, dev_name(&pdev->dev), - devflags); + irqflags, &pdev->dev, devflags); if (ret < 0) goto cleanup; -- cgit v0.10.2 From 4942e00e5582dcb45d432c35d47980ced72cdb8e Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:51 +0200 Subject: usb: isp1760: Pass resource pointer to isp1760_register The function takes quite a few arguments, passing the resource pointer instead of the start address and length simplifies it a bit. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 9ba3023..e99dafa 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2211,9 +2211,8 @@ void isp1760_deinit_kmem_cache(void) kmem_cache_destroy(urb_listitem_cachep); } -int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, - unsigned long irqflags, struct device *dev, - unsigned int devflags) +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) { struct usb_hcd *hcd; struct isp1760_hcd *priv; @@ -2239,15 +2238,15 @@ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, } init_memory(priv); - hcd->regs = ioremap(res_start, res_len); + hcd->regs = ioremap(mem->start, resource_size(mem)); if (!hcd->regs) { ret = -EIO; goto err_put; } hcd->irq = irq; - hcd->rsrc_start = res_start; - hcd->rsrc_len = res_len; + hcd->rsrc_start = mem->start; + hcd->rsrc_len = resource_size(mem); ret = usb_add_hcd(hcd, irq, irqflags); if (ret) diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index cc65602..7fc510f 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -2,9 +2,8 @@ #define _ISP1760_HCD_H_ /* exports for if */ -int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, - unsigned long irqflags, struct device *dev, - unsigned int devflags); +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); void isp1760_unregister(struct device *dev); int isp1760_init_kmem_once(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index ce572cc..3a9b4f6 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -133,8 +133,8 @@ static int isp1761_pci_probe(struct pci_dev *dev, writel(reg_data, iobase + PLX_INT_CSR_REG); dev->dev.dma_mask = NULL; - ret_status = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, &dev->dev, devflags); + ret_status = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, + &dev->dev, devflags); if (ret_status < 0) goto cleanup3; @@ -257,8 +257,8 @@ static int isp1760_plat_probe(struct platform_device *pdev) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; } - ret = isp1760_register(mem_res->start, mem_size, irq_res->start, - irqflags, &pdev->dev, devflags); + ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, + devflags); if (ret < 0) goto cleanup; -- cgit v0.10.2 From 10feee144664a87a07f75756f1ee577ce17616c4 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:52 +0200 Subject: usb: isp1760: Use the managed devm_ioremap_resource() API This simplifies error and remove code paths. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index e99dafa..2e38efe 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2234,14 +2234,14 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); if (IS_ERR(priv->rst_gpio)) { ret = PTR_ERR(priv->rst_gpio); - goto err_put; + goto error; } init_memory(priv); - hcd->regs = ioremap(mem->start, resource_size(mem)); - if (!hcd->regs) { - ret = -EIO; - goto err_put; + hcd->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto error; } hcd->irq = irq; @@ -2250,19 +2250,15 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, ret = usb_add_hcd(hcd, irq, irqflags); if (ret) - goto err_unmap; + goto error; device_wakeup_enable(hcd->self.controller); dev_set_drvdata(dev, hcd); return 0; -err_unmap: - iounmap(hcd->regs); - -err_put: +error: usb_put_hcd(hcd); - return ret; } @@ -2271,8 +2267,6 @@ void isp1760_unregister(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); } diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 3a9b4f6..dbb287c 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -85,8 +85,9 @@ static int isp1761_pci_probe(struct pci_dev *dev, chip_addr = ioremap_nocache(pci_mem_phy0,memlength); if (!chip_addr) { printk(KERN_ERR "Error ioremap failed\n"); + release_mem_region(pci_mem_phy0, memlength); ret_status = -ENOMEM; - goto cleanup3; + goto cleanup2; } /* bad pci latencies can contribute to overruns */ @@ -114,6 +115,7 @@ static int isp1761_pci_probe(struct pci_dev *dev, } iounmap(chip_addr); + release_mem_region(pci_mem_phy0, memlength); /* Host Controller presence is detected by writing to scratch register * and reading back and checking the contents are same or not @@ -121,7 +123,7 @@ static int isp1761_pci_probe(struct pci_dev *dev, if (reg_data != 0xFACE) { dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); ret_status = -ENOMEM; - goto cleanup3; + goto cleanup2; } pci_set_master(dev); @@ -132,20 +134,14 @@ static int isp1761_pci_probe(struct pci_dev *dev, reg_data |= 0x900; writel(reg_data, iobase + PLX_INT_CSR_REG); - dev->dev.dma_mask = NULL; - ret_status = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, - &dev->dev, devflags); - if (ret_status < 0) - goto cleanup3; - /* done with PLX IO access */ iounmap(iobase); release_mem_region(nxp_pci_io_base, iolength); - return 0; + dev->dev.dma_mask = NULL; + return isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, + &dev->dev, devflags); -cleanup3: - release_mem_region(pci_mem_phy0, memlength); cleanup2: iounmap(iobase); cleanup1: @@ -193,25 +189,14 @@ static int isp1760_plat_probe(struct platform_device *pdev) unsigned int devflags = 0; struct resource *mem_res; struct resource *irq_res; - resource_size_t mem_size; int ret; mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem_res) { - pr_warning("isp1760: Memory resource not available\n"); - return -ENODEV; - } - mem_size = resource_size(mem_res); - if (!request_mem_region(mem_res->start, mem_size, "isp1760")) { - pr_warning("isp1760: Cannot reserve the memory resource\n"); - return -EBUSY; - } irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!irq_res) { pr_warning("isp1760: IRQ resource not available\n"); - ret = -ENODEV; - goto cleanup; + return -ENODEV; } irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; @@ -260,14 +245,10 @@ static int isp1760_plat_probe(struct platform_device *pdev) ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, devflags); if (ret < 0) - goto cleanup; + return ret; pr_info("ISP1760 USB device initialised\n"); return 0; - -cleanup: - release_mem_region(mem_res->start, mem_size); - return ret; } static int isp1760_plat_remove(struct platform_device *pdev) -- cgit v0.10.2 From c770e00122a939f4ec9eeb663b6be6a131065926 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:53 +0200 Subject: usb: isp1760: Refactor PCI initialization code Move the initialization code out of the PCI probe function to a new function in order to simplify and fix error handling. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index dbb287c..7c03320 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -25,69 +25,34 @@ #endif #ifdef CONFIG_PCI -static int isp1761_pci_probe(struct pci_dev *dev, - const struct pci_device_id *id) +static int isp1761_pci_init(struct pci_dev *dev) { + resource_size_t mem_start; + resource_size_t mem_length; + u8 __iomem *iobase; u8 latency, limit; - __u32 reg_data; int retry_count; - unsigned int devflags = 0; - int ret_status = 0; - - resource_size_t pci_mem_phy0; - resource_size_t memlength; + u32 reg_data; - u8 __iomem *chip_addr; - u8 __iomem *iobase; - resource_size_t nxp_pci_io_base; - resource_size_t iolength; - - if (usb_disabled()) - return -ENODEV; - - if (pci_enable_device(dev) < 0) - return -ENODEV; - - if (!dev->irq) - return -ENODEV; - - /* Grab the PLX PCI mem maped port start address we need */ - nxp_pci_io_base = pci_resource_start(dev, 0); - iolength = pci_resource_len(dev, 0); - - if (!request_mem_region(nxp_pci_io_base, iolength, "ISP1761 IO MEM")) { - printk(KERN_ERR "request region #1\n"); - return -EBUSY; - } - - iobase = ioremap_nocache(nxp_pci_io_base, iolength); - if (!iobase) { - printk(KERN_ERR "ioremap #1\n"); - ret_status = -ENOMEM; - goto cleanup1; - } /* Grab the PLX PCI shared memory of the ISP 1761 we need */ - pci_mem_phy0 = pci_resource_start(dev, 3); - memlength = pci_resource_len(dev, 3); - if (memlength < 0xffff) { + mem_start = pci_resource_start(dev, 3); + mem_length = pci_resource_len(dev, 3); + if (mem_length < 0xffff) { printk(KERN_ERR "memory length for this resource is wrong\n"); - ret_status = -ENOMEM; - goto cleanup2; + return -ENOMEM; } - if (!request_mem_region(pci_mem_phy0, memlength, "ISP-PCI")) { + if (!request_mem_region(mem_start, mem_length, "ISP-PCI")) { printk(KERN_ERR "host controller already in use\n"); - ret_status = -EBUSY; - goto cleanup2; + return -EBUSY; } /* map available memory */ - chip_addr = ioremap_nocache(pci_mem_phy0,memlength); - if (!chip_addr) { + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { printk(KERN_ERR "Error ioremap failed\n"); - release_mem_region(pci_mem_phy0, memlength); - ret_status = -ENOMEM; - goto cleanup2; + release_mem_region(mem_start, mem_length); + return -ENOMEM; } /* bad pci latencies can contribute to overruns */ @@ -108,25 +73,38 @@ static int isp1761_pci_probe(struct pci_dev *dev, /*by default host is in 16bit mode, so * io operations at this stage must be 16 bit * */ - writel(0xface, chip_addr + HC_SCRATCH_REG); + writel(0xface, iobase + HC_SCRATCH_REG); udelay(100); - reg_data = readl(chip_addr + HC_SCRATCH_REG) & 0x0000ffff; + reg_data = readl(iobase + HC_SCRATCH_REG) & 0x0000ffff; retry_count--; } - iounmap(chip_addr); - release_mem_region(pci_mem_phy0, memlength); + iounmap(iobase); + release_mem_region(mem_start, mem_length); /* Host Controller presence is detected by writing to scratch register * and reading back and checking the contents are same or not */ if (reg_data != 0xFACE) { dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); - ret_status = -ENOMEM; - goto cleanup2; + return -ENOMEM; } - pci_set_master(dev); + /* Grab the PLX PCI mem maped port start address we need */ + mem_start = pci_resource_start(dev, 0); + mem_length = pci_resource_len(dev, 0); + + if (!request_mem_region(mem_start, mem_length, "ISP1761 IO MEM")) { + printk(KERN_ERR "request region #1\n"); + return -EBUSY; + } + + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "ioremap #1\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } /* configure PLX PCI chip to pass interrupts */ #define PLX_INT_CSR_REG 0x68 @@ -136,17 +114,43 @@ static int isp1761_pci_probe(struct pci_dev *dev, /* done with PLX IO access */ iounmap(iobase); - release_mem_region(nxp_pci_io_base, iolength); + release_mem_region(mem_start, mem_length); + + return 0; +} + +static int isp1761_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + unsigned int devflags = 0; + int ret; + + if (usb_disabled()) + return -ENODEV; + + if (!dev->irq) + return -ENODEV; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + ret = isp1761_pci_init(dev); + if (ret < 0) + goto error; + + pci_set_master(dev); dev->dev.dma_mask = NULL; - return isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, - &dev->dev, devflags); + ret = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, + &dev->dev, devflags); + if (ret < 0) + goto error; -cleanup2: - iounmap(iobase); -cleanup1: - release_mem_region(nxp_pci_io_base, iolength); - return ret_status; + return 0; + +error: + pci_disable_device(dev); + return ret; } static void isp1761_pci_remove(struct pci_dev *dev) -- cgit v0.10.2 From cdd36e872c55603af60a9dc1f9e4b3de31ffc21a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:54 +0200 Subject: usb: isp1760: Decouple usb_hdc and isp1760_priv Allocate the driver private data structure manually instead of using the usb_hcd private space. This will allow skipping hcd registration for the isp1761 when used in device mode only. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 2e38efe..55c0add 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -40,6 +40,8 @@ enum queue_head_types { }; struct isp1760_hcd { + struct usb_hcd *hcd; + u32 hcs_params; spinlock_t lock; struct slotinfo atl_slots[32]; @@ -65,7 +67,7 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) { - return (struct isp1760_hcd *) (hcd->hcd_priv); + return *(struct isp1760_hcd **)hcd->hcd_priv; } /* Section 2.2 Host Controller Capability Registers */ @@ -2161,7 +2163,7 @@ static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd, static const struct hc_driver isp1760_hc_driver = { .description = "isp1760-hcd", .product_desc = "NXP ISP1760 USB Host Controller", - .hcd_priv_size = sizeof(struct isp1760_hcd), + .hcd_priv_size = sizeof(struct isp1760_hcd *), .irq = isp1760_irq, .flags = HCD_MEMORY | HCD_USB2, .reset = isp1760_hc_setup, @@ -2214,13 +2216,17 @@ void isp1760_deinit_kmem_cache(void) int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) { - struct usb_hcd *hcd; + struct usb_hcd *hcd = NULL; struct isp1760_hcd *priv; int ret; if (usb_disabled()) return -ENODEV; + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + /* prevent usb-core allocating DMA pages */ dev->dma_mask = NULL; @@ -2228,7 +2234,9 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (!hcd) return -ENOMEM; - priv = hcd_to_priv(hcd); + priv->hcd = hcd; + *(struct isp1760_hcd **)hcd->hcd_priv = priv; + priv->devflags = devflags; priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); @@ -2253,7 +2261,7 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, goto error; device_wakeup_enable(hcd->self.controller); - dev_set_drvdata(dev, hcd); + dev_set_drvdata(dev, priv); return 0; @@ -2264,7 +2272,8 @@ error: void isp1760_unregister(struct device *dev) { - struct usb_hcd *hcd = dev_get_drvdata(dev); + struct isp1760_hcd *priv = dev_get_drvdata(dev); + struct usb_hcd *hcd = priv->hcd; usb_remove_hcd(hcd); usb_put_hcd(hcd); -- cgit v0.10.2 From ea0b1fabc77420261c57c4ac4f0ea384f5a8c438 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:55 +0200 Subject: usb: isp1760: Prefix driver data structures with isp1760_ The slotinfo and memory_chunk structures are specific to the driver and defined in a header file. Prefix them with isp1760_ to avoid namespace clashes. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 55c0add..99f56c6 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -44,11 +44,11 @@ struct isp1760_hcd { u32 hcs_params; spinlock_t lock; - struct slotinfo atl_slots[32]; + struct isp1760_slotinfo atl_slots[32]; int atl_done_map; - struct slotinfo int_slots[32]; + struct isp1760_slotinfo int_slots[32]; int int_done_map; - struct memory_chunk memory_pool[BLOCKS]; + struct isp1760_memory_chunk memory_pool[BLOCKS]; struct list_head qh_list[QH_END]; /* periodic schedule support */ @@ -743,8 +743,9 @@ static void qtd_free(struct isp1760_qtd *qtd) } static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, - struct slotinfo *slots, struct isp1760_qtd *qtd, - struct isp1760_qh *qh, struct ptd *ptd) + struct isp1760_slotinfo *slots, + struct isp1760_qtd *qtd, struct isp1760_qh *qh, + struct ptd *ptd) { struct isp1760_hcd *priv = hcd_to_priv(hcd); int skip_map; @@ -857,7 +858,7 @@ static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) { struct isp1760_hcd *priv = hcd_to_priv(hcd); int ptd_offset; - struct slotinfo *slots; + struct isp1760_slotinfo *slots; int curr_slot, free_slot; int n; struct ptd ptd; @@ -1097,7 +1098,7 @@ static void handle_done_ptds(struct usb_hcd *hcd) struct isp1760_qh *qh; int slot; int state; - struct slotinfo *slots; + struct isp1760_slotinfo *slots; u32 ptd_offset; struct isp1760_qtd *qtd; int modified; diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 7fc510f..3056bcd 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -103,7 +103,7 @@ struct ptd { #define ATL_PTD_OFFSET 0x0c00 #define PAYLOAD_OFFSET 0x1000 -struct slotinfo { +struct isp1760_slotinfo { struct isp1760_qh *qh; struct isp1760_qtd *qtd; unsigned long timestamp; @@ -125,7 +125,7 @@ struct slotinfo { #define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ /* chip memory management */ -struct memory_chunk { +struct isp1760_memory_chunk { unsigned int start; unsigned int size; unsigned int free; -- cgit v0.10.2 From e19c99e7592e06b6fdf558aa8877b671f8cf0329 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:56 +0200 Subject: usb: isp1760: Reorganize header files The isp1760-rhcd.h header contains PTD constants and structures only useful for the HCD implementation. It also contains register definitions that will be needed by common code when implementing support for the ISP1761 device controller, but doesn't contain the isp1760_hcd structure definition that will also be used by common code. Move definitions to the right location and create an isp1760-regs.h to store register definitions. No change other than moving definitions and modifying indentation is performed. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 99f56c6..50434cc 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -27,41 +27,12 @@ #include #include "isp1760-hcd.h" +#include "isp1760-regs.h" static struct kmem_cache *qtd_cachep; static struct kmem_cache *qh_cachep; static struct kmem_cache *urb_listitem_cachep; -enum queue_head_types { - QH_CONTROL, - QH_BULK, - QH_INTERRUPT, - QH_END -}; - -struct isp1760_hcd { - struct usb_hcd *hcd; - - u32 hcs_params; - spinlock_t lock; - struct isp1760_slotinfo atl_slots[32]; - int atl_done_map; - struct isp1760_slotinfo int_slots[32]; - int int_done_map; - struct isp1760_memory_chunk memory_pool[BLOCKS]; - struct list_head qh_list[QH_END]; - - /* periodic schedule support */ -#define DEFAULT_I_TDPS 1024 - unsigned periodic_size; - unsigned i_thresh; - unsigned long reset_done; - unsigned long next_statechange; - unsigned int devflags; - - struct gpio_desc *rst_gpio; -}; - typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, struct isp1760_qtd *qtd); @@ -70,32 +41,79 @@ static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) return *(struct isp1760_hcd **)hcd->hcd_priv; } -/* Section 2.2 Host Controller Capability Registers */ -#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ -#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ -#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ -#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ -#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ -#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ -#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ - -/* Section 2.3 Host Controller Operational Registers */ -#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ -#define CMD_RESET (1<<1) /* reset HC not bus */ -#define CMD_RUN (1<<0) /* start/stop HC */ -#define STS_PCD (1<<2) /* port change detect */ -#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ - -#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ -#define PORT_POWER (1<<12) /* true: has power (see PPC) */ -#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ -#define PORT_RESET (1<<8) /* reset port */ -#define PORT_SUSPEND (1<<7) /* suspend port */ -#define PORT_RESUME (1<<6) /* resume it */ -#define PORT_PE (1<<2) /* port enable */ -#define PORT_CSC (1<<1) /* connect status change */ -#define PORT_CONNECT (1<<0) /* device connected */ -#define PORT_RWC_BITS (PORT_CSC) +/* urb state*/ +#define DELETE_URB (0x0008) +#define NO_TRANSFER_ACTIVE (0xffffffff) + +/* Philips Proprietary Transfer Descriptor (PTD) */ +typedef __u32 __bitwise __dw; +struct ptd { + __dw dw0; + __dw dw1; + __dw dw2; + __dw dw3; + __dw dw4; + __dw dw5; + __dw dw6; + __dw dw7; +}; +#define PTD_OFFSET 0x0400 +#define ISO_PTD_OFFSET 0x0400 +#define INT_PTD_OFFSET 0x0800 +#define ATL_PTD_OFFSET 0x0c00 +#define PAYLOAD_OFFSET 0x1000 + + +/* ATL */ +/* DW0 */ +#define DW0_VALID_BIT 1 +#define FROM_DW0_VALID(x) ((x) & 0x01) +#define TO_DW0_LENGTH(x) (((u32) x) << 3) +#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) +#define TO_DW0_MULTI(x) (((u32) x) << 29) +#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) +/* DW1 */ +#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) +#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) +#define DW1_TRANS_BULK ((u32) 2 << 12) +#define DW1_TRANS_INT ((u32) 3 << 12) +#define DW1_TRANS_SPLIT ((u32) 1 << 14) +#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) +#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) +#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) +/* DW2 */ +#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) +#define TO_DW2_RL(x) ((x) << 25) +#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) +/* DW3 */ +#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) +#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) +#define TO_DW3_NAKCOUNT(x) ((x) << 19) +#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) +#define TO_DW3_CERR(x) ((x) << 23) +#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) +#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) +#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) +#define TO_DW3_PING(x) ((x) << 26) +#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) +#define DW3_ERROR_BIT (1 << 28) +#define DW3_BABBLE_BIT (1 << 29) +#define DW3_HALT_BIT (1 << 30) +#define DW3_ACTIVE_BIT (1 << 31) +#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) + +#define INT_UNDERRUN (1 << 2) +#define INT_BABBLE (1 << 1) +#define INT_EXACT (1 << 0) + +#define SETUP_PID (2) +#define IN_PID (1) +#define OUT_PID (0) + +/* Errata 1 */ +#define RL_COUNTER (0) +#define NAK_COUNTER (0) +#define ERR_COUNTER (2) struct isp1760_qtd { u8 packet_type; diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 3056bcd..44486c8 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -1,114 +1,31 @@ #ifndef _ISP1760_HCD_H_ #define _ISP1760_HCD_H_ -/* exports for if */ -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); -void isp1760_unregister(struct device *dev); +#include -int isp1760_init_kmem_once(void); -void isp1760_deinit_kmem_cache(void); +struct gpio_desc; +struct isp1760_qh; +struct isp1760_qtd; +struct resource; +struct usb_hcd; -/* EHCI capability registers */ -#define HC_CAPLENGTH 0x00 -#define HC_HCSPARAMS 0x04 -#define HC_HCCPARAMS 0x08 - -/* EHCI operational registers */ -#define HC_USBCMD 0x20 -#define HC_USBSTS 0x24 -#define HC_FRINDEX 0x2c -#define HC_CONFIGFLAG 0x60 -#define HC_PORTSC1 0x64 -#define HC_ISO_PTD_DONEMAP_REG 0x130 -#define HC_ISO_PTD_SKIPMAP_REG 0x134 -#define HC_ISO_PTD_LASTPTD_REG 0x138 -#define HC_INT_PTD_DONEMAP_REG 0x140 -#define HC_INT_PTD_SKIPMAP_REG 0x144 -#define HC_INT_PTD_LASTPTD_REG 0x148 -#define HC_ATL_PTD_DONEMAP_REG 0x150 -#define HC_ATL_PTD_SKIPMAP_REG 0x154 -#define HC_ATL_PTD_LASTPTD_REG 0x158 - -/* Configuration Register */ -#define HC_HW_MODE_CTRL 0x300 -#define ALL_ATX_RESET (1 << 31) -#define HW_ANA_DIGI_OC (1 << 15) -#define HW_DATA_BUS_32BIT (1 << 8) -#define HW_DACK_POL_HIGH (1 << 6) -#define HW_DREQ_POL_HIGH (1 << 5) -#define HW_INTR_HIGH_ACT (1 << 2) -#define HW_INTR_EDGE_TRIG (1 << 1) -#define HW_GLOBAL_INTR_EN (1 << 0) - -#define HC_CHIP_ID_REG 0x304 -#define HC_SCRATCH_REG 0x308 - -#define HC_RESET_REG 0x30c -#define SW_RESET_RESET_HC (1 << 1) -#define SW_RESET_RESET_ALL (1 << 0) - -#define HC_BUFFER_STATUS_REG 0x334 -#define ISO_BUF_FILL (1 << 2) -#define INT_BUF_FILL (1 << 1) -#define ATL_BUF_FILL (1 << 0) - -#define HC_MEMORY_REG 0x33c -#define ISP_BANK(x) ((x) << 16) - -#define HC_PORT1_CTRL 0x374 -#define PORT1_POWER (3 << 3) -#define PORT1_INIT1 (1 << 7) -#define PORT1_INIT2 (1 << 23) -#define HW_OTG_CTRL_SET 0x374 -#define HW_OTG_CTRL_CLR 0x376 - -/* Interrupt Register */ -#define HC_INTERRUPT_REG 0x310 - -#define HC_INTERRUPT_ENABLE 0x314 -#define HC_ISO_INT (1 << 9) -#define HC_ATL_INT (1 << 8) -#define HC_INTL_INT (1 << 7) -#define HC_EOT_INT (1 << 3) -#define HC_SOT_INT (1 << 1) -#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) - -#define HC_ISO_IRQ_MASK_OR_REG 0x318 -#define HC_INT_IRQ_MASK_OR_REG 0x31C -#define HC_ATL_IRQ_MASK_OR_REG 0x320 -#define HC_ISO_IRQ_MASK_AND_REG 0x324 -#define HC_INT_IRQ_MASK_AND_REG 0x328 -#define HC_ATL_IRQ_MASK_AND_REG 0x32C - -/* urb state*/ -#define DELETE_URB (0x0008) -#define NO_TRANSFER_ACTIVE (0xffffffff) - -/* Philips Proprietary Transfer Descriptor (PTD) */ -typedef __u32 __bitwise __dw; -struct ptd { - __dw dw0; - __dw dw1; - __dw dw2; - __dw dw3; - __dw dw4; - __dw dw5; - __dw dw6; - __dw dw7; -}; -#define PTD_OFFSET 0x0400 -#define ISO_PTD_OFFSET 0x0400 -#define INT_PTD_OFFSET 0x0800 -#define ATL_PTD_OFFSET 0x0c00 -#define PAYLOAD_OFFSET 0x1000 +/* + * 60kb divided in: + * - 32 blocks @ 256 bytes + * - 20 blocks @ 1024 bytes + * - 4 blocks @ 8192 bytes + */ -struct isp1760_slotinfo { - struct isp1760_qh *qh; - struct isp1760_qtd *qtd; - unsigned long timestamp; -}; +#define BLOCK_1_NUM 32 +#define BLOCK_2_NUM 20 +#define BLOCK_3_NUM 4 +#define BLOCK_1_SIZE 256 +#define BLOCK_2_SIZE 1024 +#define BLOCK_3_SIZE 8192 +#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) +#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE +#define PAYLOAD_AREA_SIZE 0xf000 /* * Device flags that can vary from board to board. All of these @@ -124,6 +41,12 @@ struct isp1760_slotinfo { #define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ #define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ +struct isp1760_slotinfo { + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + unsigned long timestamp; +}; + /* chip memory management */ struct isp1760_memory_chunk { unsigned int start; @@ -131,73 +54,42 @@ struct isp1760_memory_chunk { unsigned int free; }; -/* - * 60kb divided in: - * - 32 blocks @ 256 bytes - * - 20 blocks @ 1024 bytes - * - 4 blocks @ 8192 bytes - */ +enum isp1760_queue_head_types { + QH_CONTROL, + QH_BULK, + QH_INTERRUPT, + QH_END +}; -#define BLOCK_1_NUM 32 -#define BLOCK_2_NUM 20 -#define BLOCK_3_NUM 4 +struct isp1760_hcd { + struct usb_hcd *hcd; + + u32 hcs_params; + spinlock_t lock; + struct isp1760_slotinfo atl_slots[32]; + int atl_done_map; + struct isp1760_slotinfo int_slots[32]; + int int_done_map; + struct isp1760_memory_chunk memory_pool[BLOCKS]; + struct list_head qh_list[QH_END]; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 + unsigned periodic_size; + unsigned i_thresh; + unsigned long reset_done; + unsigned long next_statechange; + unsigned int devflags; + + struct gpio_desc *rst_gpio; +}; -#define BLOCK_1_SIZE 256 -#define BLOCK_2_SIZE 1024 -#define BLOCK_3_SIZE 8192 -#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) -#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE -#define PAYLOAD_AREA_SIZE 0xf000 +/* exports for if */ +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_unregister(struct device *dev); -/* ATL */ -/* DW0 */ -#define DW0_VALID_BIT 1 -#define FROM_DW0_VALID(x) ((x) & 0x01) -#define TO_DW0_LENGTH(x) (((u32) x) << 3) -#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) -#define TO_DW0_MULTI(x) (((u32) x) << 29) -#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) -/* DW1 */ -#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) -#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) -#define DW1_TRANS_BULK ((u32) 2 << 12) -#define DW1_TRANS_INT ((u32) 3 << 12) -#define DW1_TRANS_SPLIT ((u32) 1 << 14) -#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) -#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) -#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) -/* DW2 */ -#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) -#define TO_DW2_RL(x) ((x) << 25) -#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) -/* DW3 */ -#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) -#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) -#define TO_DW3_NAKCOUNT(x) ((x) << 19) -#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) -#define TO_DW3_CERR(x) ((x) << 23) -#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) -#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) -#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) -#define TO_DW3_PING(x) ((x) << 26) -#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) -#define DW3_ERROR_BIT (1 << 28) -#define DW3_BABBLE_BIT (1 << 29) -#define DW3_HALT_BIT (1 << 30) -#define DW3_ACTIVE_BIT (1 << 31) -#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) - -#define INT_UNDERRUN (1 << 2) -#define INT_BABBLE (1 << 1) -#define INT_EXACT (1 << 0) - -#define SETUP_PID (2) -#define IN_PID (1) -#define OUT_PID (0) - -/* Errata 1 */ -#define RL_COUNTER (0) -#define NAK_COUNTER (0) -#define ERR_COUNTER (2) +int isp1760_init_kmem_once(void); +void isp1760_deinit_kmem_cache(void); #endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 7c03320..b1591c6c 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -19,6 +19,7 @@ #include #include "isp1760-hcd.h" +#include "isp1760-regs.h" #ifdef CONFIG_PCI #include diff --git a/drivers/usb/host/isp1760-regs.h b/drivers/usb/host/isp1760-regs.h new file mode 100644 index 0000000..c2a3900 --- /dev/null +++ b/drivers/usb/host/isp1760-regs.h @@ -0,0 +1,120 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * 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 _ISP1760_REGS_H_ +#define _ISP1760_REGS_H_ + +/* EHCI capability registers */ +#define HC_CAPLENGTH 0x000 +#define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) /* bits 31:16 */ + +#define HC_HCSPARAMS 0x004 +#define HCS_INDICATOR(p) ((p) & (1 << 16)) /* true: has port indicators */ +#define HCS_PPC(p) ((p) & (1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p) >> 0) & 0xf) /* bits 3:0, ports on HC */ + +#define HC_HCCPARAMS 0x008 +#define HCC_ISOC_CACHE(p) ((p) & (1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p) >> 4) & 0x7) /* bits 6:4, uframes cached */ + +/* EHCI operational registers */ +#define HC_USBCMD 0x020 +#define CMD_LRESET (1 << 7) /* partial reset (no ports, etc) */ +#define CMD_RESET (1 << 1) /* reset HC not bus */ +#define CMD_RUN (1 << 0) /* start/stop HC */ + +#define HC_USBSTS 0x024 +#define STS_PCD (1 << 2) /* port change detect */ + +#define HC_FRINDEX 0x02c + +#define HC_CONFIGFLAG 0x060 +#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */ + +#define HC_PORTSC1 0x064 +#define PORT_OWNER (1 << 13) /* true: companion hc owns this port */ +#define PORT_POWER (1 << 12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ +#define PORT_RESET (1 << 8) /* reset port */ +#define PORT_SUSPEND (1 << 7) /* suspend port */ +#define PORT_RESUME (1 << 6) /* resume it */ +#define PORT_PE (1 << 2) /* port enable */ +#define PORT_CSC (1 << 1) /* connect status change */ +#define PORT_CONNECT (1 << 0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC) + +#define HC_ISO_PTD_DONEMAP_REG 0x130 +#define HC_ISO_PTD_SKIPMAP_REG 0x134 +#define HC_ISO_PTD_LASTPTD_REG 0x138 +#define HC_INT_PTD_DONEMAP_REG 0x140 +#define HC_INT_PTD_SKIPMAP_REG 0x144 +#define HC_INT_PTD_LASTPTD_REG 0x148 +#define HC_ATL_PTD_DONEMAP_REG 0x150 +#define HC_ATL_PTD_SKIPMAP_REG 0x154 +#define HC_ATL_PTD_LASTPTD_REG 0x158 + +/* Configuration Register */ +#define HC_HW_MODE_CTRL 0x300 +#define ALL_ATX_RESET (1 << 31) +#define HW_ANA_DIGI_OC (1 << 15) +#define HW_DATA_BUS_32BIT (1 << 8) +#define HW_DACK_POL_HIGH (1 << 6) +#define HW_DREQ_POL_HIGH (1 << 5) +#define HW_INTR_HIGH_ACT (1 << 2) +#define HW_INTR_EDGE_TRIG (1 << 1) +#define HW_GLOBAL_INTR_EN (1 << 0) + +#define HC_CHIP_ID_REG 0x304 +#define HC_SCRATCH_REG 0x308 + +#define HC_RESET_REG 0x30c +#define SW_RESET_RESET_HC (1 << 1) +#define SW_RESET_RESET_ALL (1 << 0) + +#define HC_BUFFER_STATUS_REG 0x334 +#define ISO_BUF_FILL (1 << 2) +#define INT_BUF_FILL (1 << 1) +#define ATL_BUF_FILL (1 << 0) + +#define HC_MEMORY_REG 0x33c +#define ISP_BANK(x) ((x) << 16) + +#define HC_PORT1_CTRL 0x374 +#define PORT1_POWER (3 << 3) +#define PORT1_INIT1 (1 << 7) +#define PORT1_INIT2 (1 << 23) +#define HW_OTG_CTRL_SET 0x374 +#define HW_OTG_CTRL_CLR 0x376 + +/* Interrupt Register */ +#define HC_INTERRUPT_REG 0x310 + +#define HC_INTERRUPT_ENABLE 0x314 +#define HC_ISO_INT (1 << 9) +#define HC_ATL_INT (1 << 8) +#define HC_INTL_INT (1 << 7) +#define HC_EOT_INT (1 << 3) +#define HC_SOT_INT (1 << 1) +#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) + +#define HC_ISO_IRQ_MASK_OR_REG 0x318 +#define HC_INT_IRQ_MASK_OR_REG 0x31c +#define HC_ATL_IRQ_MASK_OR_REG 0x320 +#define HC_ISO_IRQ_MASK_AND_REG 0x324 +#define HC_INT_IRQ_MASK_AND_REG 0x328 +#define HC_ATL_IRQ_MASK_AND_REG 0x32c + +#endif -- cgit v0.10.2 From 4b1a577d41c99f2aa548e8de3effe1033d9ca40b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:57 +0200 Subject: usb: isp1760: Move core code to isp1760-core.c Move core device initialization to a central location in order to share it with the device mode implementation. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index d6216a4..4dea9b1 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -5,7 +5,7 @@ # tell define_trace.h where to find the xhci trace header CFLAGS_xhci-trace.o := -I$(src) -isp1760-y := isp1760-hcd.o isp1760-if.o +isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o fhci-y := fhci-hcd.o fhci-hub.o fhci-q.o fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c new file mode 100644 index 0000000..d38efa0 --- /dev/null +++ b/drivers/usb/host/isp1760-core.c @@ -0,0 +1,65 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * 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 "isp1760-core.h" +#include "isp1760-hcd.h" + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) +{ + struct isp1760_device *isp; + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* prevent usb-core allocating DMA pages */ + dev->dma_mask = NULL; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(isp->regs)) + return PTR_ERR(isp->regs); + + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, irqflags, + dev, devflags); + if (ret < 0) + return ret; + + dev_set_drvdata(dev, isp); + + return 0; +} + +void isp1760_unregister(struct device *dev) +{ + struct isp1760_device *isp = dev_get_drvdata(dev); + + isp1760_hcd_unregister(&isp->hcd); +} + +MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); +MODULE_AUTHOR("Sebastian Siewior "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h new file mode 100644 index 0000000..0caeb11 --- /dev/null +++ b/drivers/usb/host/isp1760-core.h @@ -0,0 +1,33 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * 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 _ISP1760_CORE_H_ +#define _ISP1760_CORE_H_ + +#include + +#include "isp1760-hcd.h" + +struct isp1760_device { + void __iomem *regs; + + struct isp1760_hcd hcd; +}; + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_unregister(struct device *dev); + +#endif diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 50434cc..0cf620b 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2232,30 +2232,20 @@ void isp1760_deinit_kmem_cache(void) kmem_cache_destroy(urb_listitem_cachep); } -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags) +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) { - struct usb_hcd *hcd = NULL; - struct isp1760_hcd *priv; + struct usb_hcd *hcd; int ret; - if (usb_disabled()) - return -ENODEV; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - /* prevent usb-core allocating DMA pages */ - dev->dma_mask = NULL; - hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); if (!hcd) return -ENOMEM; - priv->hcd = hcd; *(struct isp1760_hcd **)hcd->hcd_priv = priv; + priv->hcd = hcd; priv->devflags = devflags; priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); @@ -2265,22 +2255,17 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, } init_memory(priv); - hcd->regs = devm_ioremap_resource(dev, mem); - if (IS_ERR(hcd->regs)) { - ret = PTR_ERR(hcd->regs); - goto error; - } hcd->irq = irq; + hcd->regs = regs; hcd->rsrc_start = mem->start; hcd->rsrc_len = resource_size(mem); ret = usb_add_hcd(hcd, irq, irqflags); if (ret) goto error; - device_wakeup_enable(hcd->self.controller); - dev_set_drvdata(dev, priv); + device_wakeup_enable(hcd->self.controller); return 0; @@ -2289,15 +2274,8 @@ error: return ret; } -void isp1760_unregister(struct device *dev) +void isp1760_hcd_unregister(struct isp1760_hcd *priv) { - struct isp1760_hcd *priv = dev_get_drvdata(dev); - struct usb_hcd *hcd = priv->hcd; - - usb_remove_hcd(hcd); - usb_put_hcd(hcd); + usb_remove_hcd(priv->hcd); + usb_put_hcd(priv->hcd); } - -MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); -MODULE_AUTHOR("Sebastian Siewior "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 44486c8..dcd2232 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -84,10 +84,10 @@ struct isp1760_hcd { struct gpio_desc *rst_gpio; }; -/* exports for if */ -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); -void isp1760_unregister(struct device *dev); +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_hcd_unregister(struct isp1760_hcd *priv); int isp1760_init_kmem_once(void); void isp1760_deinit_kmem_cache(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b1591c6c..f2a3990 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -18,7 +18,7 @@ #include #include -#include "isp1760-hcd.h" +#include "isp1760-core.h" #include "isp1760-regs.h" #ifdef CONFIG_PCI -- cgit v0.10.2 From 667c45c2f159d3c4e1d592df42ffbc7d4d73e07b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:58 +0200 Subject: usb: isp1760: Set IRQF_SHARED flag in core code The IRQF_SHARED flag needs to be set regardless of the bus type. Don't require glue code to set it manually. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index d38efa0..35278a8 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -43,8 +43,8 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (IS_ERR(isp->regs)) return PTR_ERR(isp->regs); - ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, irqflags, - dev, devflags); + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, + irqflags | IRQF_SHARED, dev, devflags); if (ret < 0) return ret; diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index f2a3990..c2a94c9 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -142,8 +142,8 @@ static int isp1761_pci_probe(struct pci_dev *dev, pci_set_master(dev); dev->dev.dma_mask = NULL; - ret = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, - &dev->dev, devflags); + ret = isp1760_register(&dev->resource[3], dev->irq, 0, &dev->dev, + devflags); if (ret < 0) goto error; @@ -190,7 +190,7 @@ static struct pci_driver isp1761_pci_driver = { static int isp1760_plat_probe(struct platform_device *pdev) { - unsigned long irqflags = IRQF_SHARED; + unsigned long irqflags; unsigned int devflags = 0; struct resource *mem_res; struct resource *irq_res; @@ -203,8 +203,7 @@ static int isp1760_plat_probe(struct platform_device *pdev) pr_warning("isp1760: IRQ resource not available\n"); return -ENODEV; } - - irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; + irqflags = irq_res->flags & IRQF_TRIGGER_MASK; if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { struct device_node *dp = pdev->dev.of_node; -- cgit v0.10.2 From 5171446a3aec607c4f94a32758f51a68bc627fe3 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:59 +0200 Subject: usb: isp1760: Initialize the bus interface in core code Although the corresponding register is part of the HCD register space, processor bus initialization is not specific to the HCD. To prepare for device controller support, move bus interface initialization to core code. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index 35278a8..e840a1d 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -13,7 +13,8 @@ * version 2 as published by the Free Software Foundation. */ -#include +#include +#include #include #include #include @@ -22,6 +23,54 @@ #include "isp1760-core.h" #include "isp1760-hcd.h" +#include "isp1760-regs.h" + +static void isp1760_init_core(struct isp1760_device *isp) +{ + u32 hwmode; + + /* Low-level chip reset */ + if (isp->rst_gpio) { + gpiod_set_value_cansleep(isp->rst_gpio, 1); + mdelay(50); + gpiod_set_value_cansleep(isp->rst_gpio, 0); + } + + /* + * Reset the host controller, including the CPU interface + * configuration. + */ + isp1760_write32(isp->regs, HC_RESET_REG, SW_RESET_RESET_ALL); + msleep(100); + + /* Setup HW Mode Control: This assumes a level active-low interrupt */ + hwmode = HW_DATA_BUS_32BIT; + + if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_16) + hwmode &= ~HW_DATA_BUS_32BIT; + if (isp->devflags & ISP1760_FLAG_ANALOG_OC) + hwmode |= HW_ANA_DIGI_OC; + if (isp->devflags & ISP1760_FLAG_DACK_POL_HIGH) + hwmode |= HW_DACK_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_DREQ_POL_HIGH) + hwmode |= HW_DREQ_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_INTR_POL_HIGH) + hwmode |= HW_INTR_HIGH_ACT; + if (isp->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) + hwmode |= HW_INTR_EDGE_TRIG; + + /* + * We have to set this first in case we're in 16-bit mode. + * Write it twice to ensure correct upper bits if switching + * to 16-bit mode. + */ + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + + dev_info(isp->dev, "bus width: %u, oc: %s\n", + isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, + isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); +} int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) @@ -39,12 +88,21 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (!isp) return -ENOMEM; + isp->dev = dev; + isp->devflags = devflags; + + isp->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); + if (IS_ERR(isp->rst_gpio)) + return PTR_ERR(isp->rst_gpio); + isp->regs = devm_ioremap_resource(dev, mem); if (IS_ERR(isp->regs)) return PTR_ERR(isp->regs); + isp1760_init_core(isp); + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, - irqflags | IRQF_SHARED, dev, devflags); + irqflags | IRQF_SHARED, dev); if (ret < 0) return ret; diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h index 0caeb11..cd4a0f3 100644 --- a/drivers/usb/host/isp1760-core.h +++ b/drivers/usb/host/isp1760-core.h @@ -20,8 +20,29 @@ #include "isp1760-hcd.h" +struct device; +struct gpio_desc; + +/* + * Device flags that can vary from board to board. All of these + * indicate the most "atypical" case, so that a devflags of 0 is + * a sane default configuration. + */ +#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ +#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ +#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ +#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ +#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ +#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ +#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ +#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ + struct isp1760_device { + struct device *dev; + void __iomem *regs; + unsigned int devflags; + struct gpio_desc *rst_gpio; struct isp1760_hcd hcd; }; @@ -30,4 +51,14 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags); void isp1760_unregister(struct device *dev); +static inline u32 isp1760_read32(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void isp1760_write32(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + #endif diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 0cf620b..5309d73 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -26,6 +26,7 @@ #include #include +#include "isp1760-core.h" #include "isp1760-hcd.h" #include "isp1760-regs.h" @@ -160,12 +161,12 @@ struct urb_listitem { */ static u32 reg_read32(void __iomem *base, u32 reg) { - return readl(base + reg); + return isp1760_read32(base, reg); } static void reg_write32(void __iomem *base, u32 reg, u32 val) { - writel(val, base + reg); + isp1760_write32(base, reg, val); } /* @@ -466,37 +467,6 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) int result; u32 scratch, hwmode; - /* low-level chip reset */ - if (priv->rst_gpio) { - gpiod_set_value_cansleep(priv->rst_gpio, 1); - mdelay(50); - gpiod_set_value_cansleep(priv->rst_gpio, 0); - } - - /* Setup HW Mode Control: This assumes a level active-low interrupt */ - hwmode = HW_DATA_BUS_32BIT; - - if (priv->devflags & ISP1760_FLAG_BUS_WIDTH_16) - hwmode &= ~HW_DATA_BUS_32BIT; - if (priv->devflags & ISP1760_FLAG_ANALOG_OC) - hwmode |= HW_ANA_DIGI_OC; - if (priv->devflags & ISP1760_FLAG_DACK_POL_HIGH) - hwmode |= HW_DACK_POL_HIGH; - if (priv->devflags & ISP1760_FLAG_DREQ_POL_HIGH) - hwmode |= HW_DREQ_POL_HIGH; - if (priv->devflags & ISP1760_FLAG_INTR_POL_HIGH) - hwmode |= HW_INTR_HIGH_ACT; - if (priv->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) - hwmode |= HW_INTR_EDGE_TRIG; - - /* - * We have to set this first in case we're in 16-bit mode. - * Write it twice to ensure correct upper bits if switching - * to 16-bit mode. - */ - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); - reg_write32(hcd->regs, HC_SCRATCH_REG, 0xdeadbabe); /* Change bus pattern */ scratch = reg_read32(hcd->regs, HC_CHIP_ID_REG); @@ -506,31 +476,27 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) return -ENODEV; } - /* pre reset */ + /* + * The RESET_HC bit in the SW_RESET register is supposed to reset the + * host controller without touching the CPU interface registers, but at + * least on the ISP1761 it seems to behave as the RESET_ALL bit and + * reset the whole device. We thus can't use it here, so let's reset + * the host controller through the EHCI USB Command register. The device + * has been reset in core code anyway, so this shouldn't matter. + */ reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, 0); reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - /* reset */ - reg_write32(hcd->regs, HC_RESET_REG, SW_RESET_RESET_ALL); - mdelay(100); - - reg_write32(hcd->regs, HC_RESET_REG, SW_RESET_RESET_HC); - mdelay(100); - result = ehci_reset(hcd); if (result) return result; /* Step 11 passed */ - dev_info(hcd->self.controller, "bus width: %d, oc: %s\n", - (priv->devflags & ISP1760_FLAG_BUS_WIDTH_16) ? - 16 : 32, (priv->devflags & ISP1760_FLAG_ANALOG_OC) ? - "analog" : "digital"); - /* ATL reset */ + hwmode = reg_read32(hcd->regs, HC_HW_MODE_CTRL) & ~ALL_ATX_RESET; reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); mdelay(10); reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); @@ -2234,7 +2200,7 @@ void isp1760_deinit_kmem_cache(void) int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags) + struct device *dev) { struct usb_hcd *hcd; int ret; @@ -2246,13 +2212,6 @@ int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, *(struct isp1760_hcd **)hcd->hcd_priv = priv; priv->hcd = hcd; - priv->devflags = devflags; - - priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); - if (IS_ERR(priv->rst_gpio)) { - ret = PTR_ERR(priv->rst_gpio); - goto error; - } init_memory(priv); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index dcd2232..df7ea36 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -3,7 +3,6 @@ #include -struct gpio_desc; struct isp1760_qh; struct isp1760_qtd; struct resource; @@ -27,20 +26,6 @@ struct usb_hcd; #define MAX_PAYLOAD_SIZE BLOCK_3_SIZE #define PAYLOAD_AREA_SIZE 0xf000 -/* - * Device flags that can vary from board to board. All of these - * indicate the most "atypical" case, so that a devflags of 0 is - * a sane default configuration. - */ -#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ -#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ -#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ -#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ -#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ -#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ -#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ -#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ - struct isp1760_slotinfo { struct isp1760_qh *qh; struct isp1760_qtd *qtd; @@ -79,14 +64,11 @@ struct isp1760_hcd { unsigned i_thresh; unsigned long reset_done; unsigned long next_statechange; - unsigned int devflags; - - struct gpio_desc *rst_gpio; }; int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); + struct device *dev); void isp1760_hcd_unregister(struct isp1760_hcd *priv); int isp1760_init_kmem_once(void); -- cgit v0.10.2 From 9a66e13290f16be59ac38e1955e15e8929076fc6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:00 +0200 Subject: usb: isp1760: Move PORT1 configuration to core code Configuring the mode of operation of port 1 doesn't belong to the HCD code, as it's related to the soon to come UDC support. Move the configuration to core code. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index e840a1d..1cba3e0 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -67,6 +67,14 @@ static void isp1760_init_core(struct isp1760_device *isp) isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + /* + * PORT 1 Control register of the ISP1760 is the OTG control register on + * ISP1761. Since there is no OTG or device controller support in this + * driver, we use port 1 as a "normal" USB host port on both chips. + */ + isp1760_write32(isp->regs, HC_PORT1_CTRL, PORT1_POWER | PORT1_INIT2); + usleep_range(10000, 11000); + dev_info(isp->dev, "bus width: %u, oc: %s\n", isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 5309d73..568446c 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -503,15 +503,6 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) reg_write32(hcd->regs, HC_INTERRUPT_ENABLE, INTERRUPT_ENABLE_MASK); - /* - * PORT 1 Control register of the ISP1760 is the OTG control - * register on ISP1761. Since there is no OTG or device controller - * support in this driver, we use port 1 as a "normal" USB host port on - * both chips. - */ - reg_write32(hcd->regs, HC_PORT1_CTRL, PORT1_POWER | PORT1_INIT2); - mdelay(10); - priv->hcs_params = reg_read32(hcd->regs, HC_HCSPARAMS); return priv_init(hcd); -- cgit v0.10.2 From 0316ca6319b98e485325be98a47d08fed07ead43 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:01 +0200 Subject: usb: isp1760: Add device controller support The ISP1761 is a dual-mode host and device controller backward compatible on the host side with the ISP1760. Add support for the device controller. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index b8e213e..c9152e2 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -109,6 +109,13 @@ config USB_GR_UDC Select this to support Aeroflex Gaisler GRUSBDC cores from the GRLIB VHDL IP core library. +config USB_ISP1761_UDC + boolean "NXP ISP1761 USB Device Controller" + depends on USB_ISP1760_HCD + help + The NXP ISP1761 is a dual-role high-speed USB host and device + controller. + config USB_OMAP tristate "OMAP USB Device Controller" depends on ARCH_OMAP1 diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 4dea9b1..67d3f18 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -6,6 +6,7 @@ CFLAGS_xhci-trace.o := -I$(src) isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o +isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o fhci-y := fhci-hcd.o fhci-hub.o fhci-q.o fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index 1cba3e0..727e90a 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -24,9 +24,11 @@ #include "isp1760-core.h" #include "isp1760-hcd.h" #include "isp1760-regs.h" +#include "isp1760-udc.h" static void isp1760_init_core(struct isp1760_device *isp) { + u32 otgctrl; u32 hwmode; /* Low-level chip reset */ @@ -60,6 +62,17 @@ static void isp1760_init_core(struct isp1760_device *isp) hwmode |= HW_INTR_EDGE_TRIG; /* + * The ISP1761 has a dedicated DC IRQ line but supports sharing the HC + * IRQ line for both the host and device controllers. Hardcode IRQ + * sharing for now and disable the DC interrupts globally to avoid + * spurious interrupts during HCD registration. + */ + if (isp->devflags & ISP1760_FLAG_ISP1761) { + isp1760_write32(isp->regs, DC_MODE, 0); + hwmode |= HW_COMN_IRQ; + } + + /* * We have to set this first in case we're in 16-bit mode. * Write it twice to ensure correct upper bits if switching * to 16-bit mode. @@ -68,18 +81,33 @@ static void isp1760_init_core(struct isp1760_device *isp) isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); /* - * PORT 1 Control register of the ISP1760 is the OTG control register on - * ISP1761. Since there is no OTG or device controller support in this - * driver, we use port 1 as a "normal" USB host port on both chips. + * PORT 1 Control register of the ISP1760 is the OTG control register + * on ISP1761. + * + * TODO: Really support OTG. For now we configure port 1 in device mode + * when OTG is requested. */ - isp1760_write32(isp->regs, HC_PORT1_CTRL, PORT1_POWER | PORT1_INIT2); - usleep_range(10000, 11000); + if ((isp->devflags & ISP1760_FLAG_ISP1761) && + (isp->devflags & ISP1760_FLAG_OTG_EN)) + otgctrl = ((HW_DM_PULLDOWN | HW_DP_PULLDOWN) << 16) + | HW_OTG_DISABLE; + else + otgctrl = (HW_SW_SEL_HC_DC << 16) + | (HW_VBUS_DRV | HW_SEL_CP_EXT); + + isp1760_write32(isp->regs, HC_PORT1_CTRL, otgctrl); dev_info(isp->dev, "bus width: %u, oc: %s\n", isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); } +void isp1760_set_pullup(struct isp1760_device *isp, bool enable) +{ + isp1760_write32(isp->regs, HW_OTG_CTRL_SET, + enable ? HW_DP_PULLUP : HW_DP_PULLUP << 16); +} + int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) { @@ -114,6 +142,15 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (ret < 0) return ret; + if (devflags & ISP1760_FLAG_ISP1761) { + ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | + IRQF_DISABLED); + if (ret < 0) { + isp1760_hcd_unregister(&isp->hcd); + return ret; + } + } + dev_set_drvdata(dev, isp); return 0; @@ -123,6 +160,9 @@ void isp1760_unregister(struct device *dev) { struct isp1760_device *isp = dev_get_drvdata(dev); + if (isp->devflags & ISP1760_FLAG_ISP1761) + isp1760_udc_unregister(isp); + isp1760_hcd_unregister(&isp->hcd); } diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h index cd4a0f3..c70f836 100644 --- a/drivers/usb/host/isp1760-core.h +++ b/drivers/usb/host/isp1760-core.h @@ -19,6 +19,7 @@ #include #include "isp1760-hcd.h" +#include "isp1760-udc.h" struct device; struct gpio_desc; @@ -45,12 +46,15 @@ struct isp1760_device { struct gpio_desc *rst_gpio; struct isp1760_hcd hcd; + struct isp1760_udc udc; }; int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags); void isp1760_unregister(struct device *dev); +void isp1760_set_pullup(struct isp1760_device *isp, bool enable); + static inline u32 isp1760_read32(void __iomem *base, u32 reg) { return readl(base + reg); diff --git a/drivers/usb/host/isp1760-regs.h b/drivers/usb/host/isp1760-regs.h index c2a3900..b67095c 100644 --- a/drivers/usb/host/isp1760-regs.h +++ b/drivers/usb/host/isp1760-regs.h @@ -16,6 +16,10 @@ #ifndef _ISP1760_REGS_H_ #define _ISP1760_REGS_H_ +/* ----------------------------------------------------------------------------- + * Host Controller + */ + /* EHCI capability registers */ #define HC_CAPLENGTH 0x000 #define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ @@ -70,6 +74,9 @@ #define HC_HW_MODE_CTRL 0x300 #define ALL_ATX_RESET (1 << 31) #define HW_ANA_DIGI_OC (1 << 15) +#define HW_DEV_DMA (1 << 11) +#define HW_COMN_IRQ (1 << 10) +#define HW_COMN_DMA (1 << 9) #define HW_DATA_BUS_32BIT (1 << 8) #define HW_DACK_POL_HIGH (1 << 6) #define HW_DREQ_POL_HIGH (1 << 5) @@ -98,6 +105,17 @@ #define PORT1_INIT2 (1 << 23) #define HW_OTG_CTRL_SET 0x374 #define HW_OTG_CTRL_CLR 0x376 +#define HW_OTG_DISABLE (1 << 10) +#define HW_OTG_SE0_EN (1 << 9) +#define HW_BDIS_ACON_EN (1 << 8) +#define HW_SW_SEL_HC_DC (1 << 7) +#define HW_VBUS_CHRG (1 << 6) +#define HW_VBUS_DISCHRG (1 << 5) +#define HW_VBUS_DRV (1 << 4) +#define HW_SEL_CP_EXT (1 << 3) +#define HW_DM_PULLDOWN (1 << 2) +#define HW_DP_PULLDOWN (1 << 1) +#define HW_DP_PULLUP (1 << 0) /* Interrupt Register */ #define HC_INTERRUPT_REG 0x310 @@ -117,4 +135,96 @@ #define HC_INT_IRQ_MASK_AND_REG 0x328 #define HC_ATL_IRQ_MASK_AND_REG 0x32c +/* ----------------------------------------------------------------------------- + * Peripheral Controller + */ + +/* Initialization Registers */ +#define DC_ADDRESS 0x0200 +#define DC_DEVEN (1 << 7) + +#define DC_MODE 0x020c +#define DC_DMACLKON (1 << 9) +#define DC_VBUSSTAT (1 << 8) +#define DC_CLKAON (1 << 7) +#define DC_SNDRSU (1 << 6) +#define DC_GOSUSP (1 << 5) +#define DC_SFRESET (1 << 4) +#define DC_GLINTENA (1 << 3) +#define DC_WKUPCS (1 << 2) + +#define DC_INTCONF 0x0210 +#define DC_CDBGMOD_ACK_NAK (0 << 6) +#define DC_CDBGMOD_ACK (1 << 6) +#define DC_CDBGMOD_ACK_1NAK (2 << 6) +#define DC_DDBGMODIN_ACK_NAK (0 << 4) +#define DC_DDBGMODIN_ACK (1 << 4) +#define DC_DDBGMODIN_ACK_1NAK (2 << 4) +#define DC_DDBGMODOUT_ACK_NYET_NAK (0 << 2) +#define DC_DDBGMODOUT_ACK_NYET (1 << 2) +#define DC_DDBGMODOUT_ACK_NYET_1NAK (2 << 2) +#define DC_INTLVL (1 << 1) +#define DC_INTPOL (1 << 0) + +#define DC_DEBUG 0x0212 +#define DC_INTENABLE 0x0214 +#define DC_IEPTX(n) (1 << (11 + 2 * (n))) +#define DC_IEPRX(n) (1 << (10 + 2 * (n))) +#define DC_IEPRXTX(n) (3 << (10 + 2 * (n))) +#define DC_IEP0SETUP (1 << 8) +#define DC_IEVBUS (1 << 7) +#define DC_IEDMA (1 << 6) +#define DC_IEHS_STA (1 << 5) +#define DC_IERESM (1 << 4) +#define DC_IESUSP (1 << 3) +#define DC_IEPSOF (1 << 2) +#define DC_IESOF (1 << 1) +#define DC_IEBRST (1 << 0) + +/* Data Flow Registers */ +#define DC_EPINDEX 0x022c +#define DC_EP0SETUP (1 << 5) +#define DC_ENDPIDX(n) ((n) << 1) +#define DC_EPDIR (1 << 0) + +#define DC_CTRLFUNC 0x0228 +#define DC_CLBUF (1 << 4) +#define DC_VENDP (1 << 3) +#define DC_DSEN (1 << 2) +#define DC_STATUS (1 << 1) +#define DC_STALL (1 << 0) + +#define DC_DATAPORT 0x0220 +#define DC_BUFLEN 0x021c +#define DC_DATACOUNT_MASK 0xffff +#define DC_BUFSTAT 0x021e +#define DC_EPMAXPKTSZ 0x0204 + +#define DC_EPTYPE 0x0208 +#define DC_NOEMPKT (1 << 4) +#define DC_EPENABLE (1 << 3) +#define DC_DBLBUF (1 << 2) +#define DC_ENDPTYP_ISOC (1 << 0) +#define DC_ENDPTYP_BULK (2 << 0) +#define DC_ENDPTYP_INTERRUPT (3 << 0) + +/* DMA Registers */ +#define DC_DMACMD 0x0230 +#define DC_DMATXCOUNT 0x0234 +#define DC_DMACONF 0x0238 +#define DC_DMAHW 0x023c +#define DC_DMAINTREASON 0x0250 +#define DC_DMAINTEN 0x0254 +#define DC_DMAEP 0x0258 +#define DC_DMABURSTCOUNT 0x0264 + +/* General Registers */ +#define DC_INTERRUPT 0x0218 +#define DC_CHIPID 0x0270 +#define DC_FRAMENUM 0x0274 +#define DC_SCRATCH 0x0278 +#define DC_UNLOCKDEV 0x027c +#define DC_INTPULSEWIDTH 0x0280 +#define DC_TESTMODE 0x0284 + #endif diff --git a/drivers/usb/host/isp1760-udc.c b/drivers/usb/host/isp1760-udc.c new file mode 100644 index 0000000..6bfda30 --- /dev/null +++ b/drivers/usb/host/isp1760-udc.c @@ -0,0 +1,1495 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * 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 +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +#define ISP1760_VBUS_POLL_INTERVAL msecs_to_jiffies(500) + +struct isp1760_request { + struct usb_request req; + struct list_head queue; + struct isp1760_ep *ep; + unsigned int packet_size; +}; + +static inline struct isp1760_udc *gadget_to_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct isp1760_udc, gadget); +} + +static inline struct isp1760_ep *ep_to_udc_ep(struct usb_ep *ep) +{ + return container_of(ep, struct isp1760_ep, ep); +} + +static inline struct isp1760_request *req_to_udc_req(struct usb_request *req) +{ + return container_of(req, struct isp1760_request, req); +} + +static inline u32 isp1760_udc_read(struct isp1760_udc *udc, u16 reg) +{ + return isp1760_read32(udc->regs, reg); +} + +static inline void isp1760_udc_write(struct isp1760_udc *udc, u16 reg, u32 val) +{ + isp1760_write32(udc->regs, reg, val); +} + +/* ----------------------------------------------------------------------------- + * Endpoint Management + */ + +static struct isp1760_ep *isp1760_udc_find_ep(struct isp1760_udc *udc, + u16 index) +{ + unsigned int i; + + if (index == 0) + return &udc->ep[0]; + + for (i = 1; i < ARRAY_SIZE(udc->ep); ++i) { + if (udc->ep[i].addr == index) + return udc->ep[i].desc ? &udc->ep[i] : NULL; + } + + return NULL; +} + +static void __isp1760_udc_select_ep(struct isp1760_ep *ep, int dir) +{ + isp1760_udc_write(ep->udc, DC_EPINDEX, + DC_ENDPIDX(ep->addr & USB_ENDPOINT_NUMBER_MASK) | + (dir == USB_DIR_IN ? DC_EPDIR : 0)); +} + +/** + * isp1760_udc_select_ep - Select an endpoint for register access + * @ep: The endpoint + * + * The ISP1761 endpoint registers are banked. This function selects the target + * endpoint for banked register access. The selection remains valid until the + * next call to this function, the next direct access to the EPINDEX register + * or the next reset, whichever comes first. + * + * Called with the UDC spinlock held. + */ +static void isp1760_udc_select_ep(struct isp1760_ep *ep) +{ + __isp1760_udc_select_ep(ep, ep->addr & USB_ENDPOINT_DIR_MASK); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_ctrl_send_status(struct isp1760_ep *ep, int dir) +{ + struct isp1760_udc *udc = ep->udc; + + /* + * Proceed to the status stage. The status stage data packet flows in + * the direction opposite to the data stage data packets, we thus need + * to select the OUT/IN endpoint for IN/OUT transfers. + */ + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | + (dir == USB_DIR_IN ? 0 : DC_EPDIR)); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STATUS); + + /* + * The hardware will terminate the request automatically and go back to + * the setup stage without notifying us. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; +} + +/* Called without the UDC spinlock held. */ +static void isp1760_udc_request_complete(struct isp1760_ep *ep, + struct isp1760_request *req, + int status) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "completing request %p with status %d\n", + req, status); + + req->ep = NULL; + req->req.status = status; + req->req.complete(&ep->ep, &req->req); + + spin_lock_irqsave(&udc->lock, flags); + + /* + * When completing control OUT requests, move to the status stage after + * calling the request complete callback. This gives the gadget an + * opportunity to stall the control transfer if needed. + */ + if (status == 0 && ep->addr == 0 && udc->ep0_dir == USB_DIR_OUT) + isp1760_udc_ctrl_send_status(ep, USB_DIR_OUT); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_ctrl_send_stall(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "%s(ep%02x)\n", __func__, ep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + /* Stall both the IN and OUT endpoints. */ + __isp1760_udc_select_ep(ep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + + /* A protocol stall completes the control transaction. */ + udc->ep0_state = ISP1760_CTRL_SETUP; + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Data Endpoints + */ + +/* Called with the UDC spinlock held. */ +static bool isp1760_udc_receive(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + unsigned int len; + u32 *buf; + int i; + + isp1760_udc_select_ep(ep); + len = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + + dev_dbg(udc->isp->dev, "%s: received %u bytes (%u/%u done)\n", + __func__, len, req->req.actual, req->req.length); + + len = min(len, req->req.length - req->req.actual); + + if (!len) { + /* + * There's no data to be read from the FIFO, acknowledge the RX + * interrupt by clearing the buffer. + * + * TODO: What if another packet arrives in the meantime ? The + * datasheet doesn't clearly document how this should be + * handled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + return false; + } + + buf = req->req.buf + req->req.actual; + + /* + * Make sure not to read more than one extra byte, otherwise data from + * the next packet might be removed from the FIFO. + */ + for (i = len; i > 2; i -= 4, ++buf) + *buf = le32_to_cpu(isp1760_udc_read(udc, DC_DATAPORT)); + if (i > 0) + *(u16 *)buf = le16_to_cpu(readw(udc->regs + DC_DATAPORT)); + + req->req.actual += len; + + /* + * TODO: The short_not_ok flag isn't supported yet, but isn't used by + * any gadget driver either. + */ + + dev_dbg(udc->isp->dev, + "%s: req %p actual/length %u/%u maxpacket %u packet size %u\n", + __func__, req, req->req.actual, req->req.length, ep->maxpacket, + len); + + ep->rx_pending = false; + + /* + * Complete the request if all data has been received or if a short + * packet has been received. + */ + if (req->req.actual == req->req.length || len < ep->maxpacket) { + list_del(&req->queue); + return true; + } + + return false; +} + +static void isp1760_udc_transmit(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + u32 *buf = req->req.buf + req->req.actual; + int i; + + req->packet_size = min(req->req.length - req->req.actual, + ep->maxpacket); + + dev_dbg(udc->isp->dev, "%s: transferring %u bytes (%u/%u done)\n", + __func__, req->packet_size, req->req.actual, + req->req.length); + + __isp1760_udc_select_ep(ep, USB_DIR_IN); + + if (req->packet_size) + isp1760_udc_write(udc, DC_BUFLEN, req->packet_size); + + /* + * Make sure not to write more than one extra byte, otherwise extra data + * will stay in the FIFO and will be transmitted during the next control + * request. The endpoint control CLBUF bit is supposed to allow flushing + * the FIFO for this kind of conditions, but doesn't seem to work. + */ + for (i = req->packet_size; i > 2; i -= 4, ++buf) + isp1760_udc_write(udc, DC_DATAPORT, cpu_to_le32(*buf)); + if (i > 0) + writew(cpu_to_le16(*(u16 *)buf), udc->regs + DC_DATAPORT); + + if (ep->addr == 0) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + if (!req->packet_size) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_VENDP); +} + +static void isp1760_ep_rx_ready(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *req; + bool complete; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_OUT) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: invalid ep0 state %u\n", __func__, + udc->ep0_state); + return; + } + + if (ep->addr != 0 && !ep->desc) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + ep->addr); + return; + } + + if (list_empty(&ep->queue)) { + ep->rx_pending = true; + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x (%p) has no request queued\n", + __func__, ep->addr, ep); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + complete = isp1760_udc_receive(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, req, 0); +} + +static void isp1760_ep_tx_complete(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *complete = NULL; + struct isp1760_request *req; + bool need_zlp; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_IN) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "TX IRQ: invalid endpoint state %u\n", + udc->ep0_state); + return; + } + + if (list_empty(&ep->queue)) { + /* + * This can happen for the control endpoint when the reply to + * the GET_STATUS IN control request is sent directly by the + * setup IRQ handler. Just proceed to the status stage. + */ + if (ep->addr == 0) { + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + spin_unlock(&udc->lock); + return; + } + + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x has no request queued\n", + __func__, ep->addr); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + req->req.actual += req->packet_size; + + need_zlp = req->req.actual == req->req.length && + !(req->req.length % ep->maxpacket) && + req->packet_size && req->req.zero; + + dev_dbg(udc->isp->dev, + "TX IRQ: req %p actual/length %u/%u maxpacket %u packet size %u zero %u need zlp %u\n", + req, req->req.actual, req->req.length, ep->maxpacket, + req->packet_size, req->req.zero, need_zlp); + + /* + * Complete the request if all data has been sent and we don't need to + * transmit a zero length packet. + */ + if (req->req.actual == req->req.length && !need_zlp) { + complete = req; + list_del(&req->queue); + + if (ep->addr == 0) + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + + if (!list_empty(&ep->queue)) + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + else + req = NULL; + } + + /* + * Transmit the next packet or start the next request, if any. + * + * TODO: If the endpoint is stalled the next request shouldn't be + * started, but what about the next packet ? + */ + if (req) + isp1760_udc_transmit(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, complete, 0); +} + +static int __isp1760_udc_set_halt(struct isp1760_ep *ep, bool halt) +{ + struct isp1760_udc *udc = ep->udc; + + dev_dbg(udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + halt ? "set" : "clear", ep->addr); + + if (ep->desc && usb_endpoint_xfer_isoc(ep->desc)) { + dev_dbg(udc->isp->dev, "%s: ep%02x is isochronous\n", __func__, + ep->addr); + return -EINVAL; + } + + isp1760_udc_select_ep(ep); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + + if (ep->addr == 0) { + /* When halting the control endpoint, stall both IN and OUT. */ + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + } else if (!halt) { + /* Reset the data PID by cycling the endpoint enable bit. */ + u16 eptype = isp1760_udc_read(udc, DC_EPTYPE); + + isp1760_udc_write(udc, DC_EPTYPE, eptype & ~DC_EPENABLE); + isp1760_udc_write(udc, DC_EPTYPE, eptype); + + /* + * Disabling the endpoint emptied the transmit FIFO, fill it + * again if a request is pending. + * + * TODO: Does the gadget framework require synchronizatino with + * the TX IRQ handler ? + */ + if ((ep->addr & USB_DIR_IN) && !list_empty(&ep->queue)) { + struct isp1760_request *req; + + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + isp1760_udc_transmit(ep, req); + } + } + + ep->halted = halt; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Control Endpoint + */ + +static int isp1760_udc_get_status(struct isp1760_udc *udc, + const struct usb_ctrlrequest *req) +{ + struct isp1760_ep *ep; + u16 status; + + if (req->wLength != cpu_to_le16(2) || req->wValue != cpu_to_le16(0)) + return -EINVAL; + + switch (req->bRequestType) { + case USB_DIR_IN | USB_RECIP_DEVICE: + status = udc->devstatus; + break; + + case USB_DIR_IN | USB_RECIP_INTERFACE: + status = 0; + break; + + case USB_DIR_IN | USB_RECIP_ENDPOINT: + ep = isp1760_udc_find_ep(udc, le16_to_cpu(req->wIndex)); + if (!ep) + return -EINVAL; + + status = 0; + if (ep->halted) + status |= 1 << USB_ENDPOINT_HALT; + break; + + default: + return -EINVAL; + } + + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | DC_EPDIR); + isp1760_udc_write(udc, DC_BUFLEN, 2); + + writew(cpu_to_le16(status), udc->regs + DC_DATAPORT); + + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + + dev_dbg(udc->isp->dev, "%s: status 0x%04x\n", __func__, status); + + return 0; +} + +static int isp1760_udc_set_address(struct isp1760_udc *udc, u16 addr) +{ + if (addr > 127) { + dev_dbg(udc->isp->dev, "invalid device address %u\n", addr); + return -EINVAL; + } + + if (udc->gadget.state != USB_STATE_DEFAULT && + udc->gadget.state != USB_STATE_ADDRESS) { + dev_dbg(udc->isp->dev, "can't set address in state %u\n", + udc->gadget.state); + return -EINVAL; + } + + usb_gadget_set_state(&udc->gadget, addr ? USB_STATE_ADDRESS : + USB_STATE_DEFAULT); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN | addr); + + spin_lock(&udc->lock); + isp1760_udc_ctrl_send_status(&udc->ep[0], USB_DIR_OUT); + spin_unlock(&udc->lock); + + return 0; +} + +static bool isp1760_ep0_setup_standard(struct isp1760_udc *udc, + struct usb_ctrlrequest *req) +{ + bool stall; + + switch (req->bRequest) { + case USB_REQ_GET_STATUS: + return isp1760_udc_get_status(udc, req); + + case USB_REQ_CLEAR_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup feature. */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + /* + * If the endpoint is wedged only the gadget can clear + * the halt feature. Pretend success in that case, but + * keep the endpoint halted. + */ + if (!ep->wedged) + stall = __isp1760_udc_set_halt(ep, false); + else + stall = false; + + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup and test mode features */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + stall = __isp1760_udc_set_halt(ep, true); + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_ADDRESS: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + return isp1760_udc_set_address(udc, le16_to_cpu(req->wValue)); + + case USB_REQ_SET_CONFIGURATION: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + if (udc->gadget.state != USB_STATE_ADDRESS && + udc->gadget.state != USB_STATE_CONFIGURED) + return true; + + stall = udc->driver->setup(&udc->gadget, req) < 0; + if (stall) + return true; + + usb_gadget_set_state(&udc->gadget, req->wValue ? + USB_STATE_CONFIGURED : USB_STATE_ADDRESS); + + /* + * SET_CONFIGURATION (and SET_INTERFACE) must reset the halt + * feature on all endpoints. There is however no need to do so + * explicitly here as the gadget driver will disable and + * reenable endpoints, clearing the halt feature. + */ + return false; + + default: + return udc->driver->setup(&udc->gadget, req) < 0; + } +} + +static void isp1760_ep0_setup(struct isp1760_udc *udc) +{ + union { + struct usb_ctrlrequest r; + u32 data[2]; + } req; + unsigned int count; + bool stall = false; + + spin_lock(&udc->lock); + + isp1760_udc_write(udc, DC_EPINDEX, DC_EP0SETUP); + + count = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + if (count != sizeof(req)) { + spin_unlock(&udc->lock); + + dev_err(udc->isp->dev, "invalid length %u for setup packet\n", + count); + + isp1760_udc_ctrl_send_stall(&udc->ep[0]); + return; + } + + req.data[0] = isp1760_udc_read(udc, DC_DATAPORT); + req.data[1] = isp1760_udc_read(udc, DC_DATAPORT); + + if (udc->ep0_state != ISP1760_CTRL_SETUP) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "unexpected SETUP packet\n"); + return; + } + + /* Move to the data stage. */ + if (!req.r.wLength) + udc->ep0_state = ISP1760_CTRL_STATUS; + else if (req.r.bRequestType & USB_DIR_IN) + udc->ep0_state = ISP1760_CTRL_DATA_IN; + else + udc->ep0_state = ISP1760_CTRL_DATA_OUT; + + udc->ep0_dir = req.r.bRequestType & USB_DIR_IN; + udc->ep0_length = le16_to_cpu(req.r.wLength); + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, + "%s: bRequestType 0x%02x bRequest 0x%02x wValue 0x%04x wIndex 0x%04x wLength 0x%04x\n", + __func__, req.r.bRequestType, req.r.bRequest, + le16_to_cpu(req.r.wValue), le16_to_cpu(req.r.wIndex), + le16_to_cpu(req.r.wLength)); + + if ((req.r.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + stall = isp1760_ep0_setup_standard(udc, &req.r); + else + stall = udc->driver->setup(&udc->gadget, &req.r) < 0; + + if (stall) + isp1760_udc_ctrl_send_stall(&udc->ep[0]); +} + +/* ----------------------------------------------------------------------------- + * Gadget Endpoint Operations + */ + +static int isp1760_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + unsigned int type; + + dev_dbg(uep->udc->isp->dev, "%s\n", __func__); + + /* + * Validate the descriptor. The control endpoint can't be enabled + * manually. + */ + if (desc->bDescriptorType != USB_DT_ENDPOINT || + desc->bEndpointAddress == 0 || + desc->bEndpointAddress != uep->addr || + le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket) { + dev_dbg(udc->isp->dev, + "%s: invalid descriptor type %u addr %02x ep addr %02x max packet size %u/%u\n", + __func__, desc->bDescriptorType, + desc->bEndpointAddress, uep->addr, + le16_to_cpu(desc->wMaxPacketSize), ep->maxpacket); + return -EINVAL; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_ISOC: + type = DC_ENDPTYP_ISOC; + break; + case USB_ENDPOINT_XFER_BULK: + type = DC_ENDPTYP_BULK; + break; + case USB_ENDPOINT_XFER_INT: + type = DC_ENDPTYP_INTERRUPT; + break; + case USB_ENDPOINT_XFER_CONTROL: + default: + dev_dbg(udc->isp->dev, "%s: control endpoints unsupported\n", + __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + + uep->desc = desc; + uep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + uep->rx_pending = false; + uep->halted = false; + uep->wedged = false; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPMAXPKTSZ, uep->maxpacket); + isp1760_udc_write(udc, DC_BUFLEN, uep->maxpacket); + isp1760_udc_write(udc, DC_EPTYPE, DC_EPENABLE | type); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int isp1760_ep_disable(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + struct isp1760_request *req, *nreq; + LIST_HEAD(req_list); + unsigned long flags; + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + spin_lock_irqsave(&udc->lock, flags); + + if (!uep->desc) { + dev_dbg(udc->isp->dev, "%s: endpoint not enabled\n", __func__); + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + uep->desc = NULL; + uep->maxpacket = 0; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPTYPE, 0); + + /* TODO Synchronize with the IRQ handler */ + + list_splice_init(&uep->queue, &req_list); + + spin_unlock_irqrestore(&udc->lock, flags); + + list_for_each_entry_safe(req, nreq, &req_list, queue) { + list_del(&req->queue); + isp1760_udc_request_complete(uep, req, -ESHUTDOWN); + } + + return 0; +} + +static struct usb_request *isp1760_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct isp1760_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + + return &req->req; +} + +static void isp1760_ep_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + + kfree(req); +} + +static int isp1760_ep_queue(struct usb_ep *ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + bool complete = false; + unsigned long flags; + int ret = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + dev_dbg(udc->isp->dev, + "%s: req %p (%u bytes%s) ep %p(0x%02x)\n", __func__, _req, + _req->length, _req->zero ? " (zlp)" : "", uep, uep->addr); + + req->ep = uep; + + if (uep->addr == 0) { + if (_req->length != udc->ep0_length && + udc->ep0_state != ISP1760_CTRL_DATA_IN) { + dev_dbg(udc->isp->dev, + "%s: invalid length %u for req %p\n", + __func__, _req->length, req); + ret = -EINVAL; + goto done; + } + + switch (udc->ep0_state) { + case ISP1760_CTRL_DATA_IN: + dev_dbg(udc->isp->dev, "%s: transmitting req %p\n", + __func__, req); + + list_add_tail(&req->queue, &uep->queue); + isp1760_udc_transmit(uep, req); + break; + + case ISP1760_CTRL_DATA_OUT: + list_add_tail(&req->queue, &uep->queue); + __isp1760_udc_select_ep(uep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + break; + + case ISP1760_CTRL_STATUS: + complete = true; + break; + + default: + dev_dbg(udc->isp->dev, "%s: invalid ep0 state\n", + __func__); + ret = -EINVAL; + break; + } + } else if (uep->desc) { + bool empty = list_empty(&uep->queue); + + list_add_tail(&req->queue, &uep->queue); + if ((uep->addr & USB_DIR_IN) && !uep->halted && empty) + isp1760_udc_transmit(uep, req); + else if (!(uep->addr & USB_DIR_IN) && uep->rx_pending) + complete = isp1760_udc_receive(uep, req); + } else { + dev_dbg(udc->isp->dev, + "%s: can't queue request to disabled ep%02x\n", + __func__, uep->addr); + ret = -ESHUTDOWN; + } + +done: + if (ret < 0) + req->ep = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + + if (complete) + isp1760_udc_request_complete(uep, req, 0); + + return ret; +} + +static int isp1760_ep_dequeue(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + dev_dbg(uep->udc->isp->dev, "%s(ep%02x)\n", __func__, uep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + if (req->ep != uep) + req = NULL; + else + list_del(&req->queue); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (!req) + return -EINVAL; + + isp1760_udc_request_complete(uep, req, -ECONNRESET); + return 0; +} + +static int __isp1760_ep_set_halt(struct isp1760_ep *uep, bool stall, bool wedge) +{ + struct isp1760_udc *udc = uep->udc; + int ret; + + if (!uep->addr) { + /* + * Halting the control endpoint is only valid as a delayed error + * response to a SETUP packet. Make sure EP0 is in the right + * stage and that the gadget isn't trying to clear the halt + * condition. + */ + if (WARN_ON(udc->ep0_state == ISP1760_CTRL_SETUP || !stall || + wedge)) { + return -EINVAL; + } + } + + if (uep->addr && !uep->desc) { + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + uep->addr); + return -EINVAL; + } + + if (uep->addr & USB_DIR_IN) { + /* Refuse to halt IN endpoints with active transfers. */ + if (!list_empty(&uep->queue)) { + dev_dbg(udc->isp->dev, + "%s: ep%02x has request pending\n", __func__, + uep->addr); + return -EAGAIN; + } + } + + ret = __isp1760_udc_set_halt(uep, stall); + if (ret < 0) + return ret; + + if (!uep->addr) { + /* + * Stalling EP0 completes the control transaction, move back to + * the SETUP state. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; + return 0; + } + + if (wedge) + uep->wedged = true; + else if (!stall) + uep->wedged = false; + + return 0; +} + +static int isp1760_ep_set_halt(struct usb_ep *ep, int value) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + value ? "set" : "clear", uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, value, false); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static int isp1760_ep_set_wedge(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: set wedge on ep%02x)\n", __func__, + uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, true, true); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static void isp1760_ep_fifo_flush(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + isp1760_udc_select_ep(uep); + + /* + * Set the CLBUF bit twice to flush both buffers in case double + * buffering is enabled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static const struct usb_ep_ops isp1760_ep_ops = { + .enable = isp1760_ep_enable, + .disable = isp1760_ep_disable, + .alloc_request = isp1760_ep_alloc_request, + .free_request = isp1760_ep_free_request, + .queue = isp1760_ep_queue, + .dequeue = isp1760_ep_dequeue, + .set_halt = isp1760_ep_set_halt, + .set_wedge = isp1760_ep_set_wedge, + .fifo_flush = isp1760_ep_fifo_flush, +}; + +/* ----------------------------------------------------------------------------- + * Device States + */ + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_connect(struct isp1760_udc *udc) +{ + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + mod_timer(&udc->vbus_timer, jiffies + ISP1760_VBUS_POLL_INTERVAL); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_disconnect(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_POWERED) + return; + + dev_dbg(udc->isp->dev, "Device disconnected in state %u\n", + udc->gadget.state); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + if (udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + del_timer(&udc->vbus_timer); + + /* TODO Reset all endpoints ? */ +} + +static void isp1760_udc_init_hw(struct isp1760_udc *udc) +{ + /* + * The device controller currently shares its interrupt with the host + * controller, the DC_IRQ polarity and signaling mode are ignored. Set + * the to active-low level-triggered. + * + * Configure the control, in and out pipes to generate interrupts on + * ACK tokens only (and NYET for the out pipe). The default + * configuration also generates an interrupt on the first NACK token. + */ + isp1760_udc_write(udc, DC_INTCONF, DC_CDBGMOD_ACK | DC_DDBGMODIN_ACK | + DC_DDBGMODOUT_ACK_NYET); + + isp1760_udc_write(udc, DC_INTENABLE, DC_IEPRXTX(7) | DC_IEPRXTX(6) | + DC_IEPRXTX(5) | DC_IEPRXTX(4) | DC_IEPRXTX(3) | + DC_IEPRXTX(2) | DC_IEPRXTX(1) | DC_IEPRXTX(0) | + DC_IEP0SETUP | DC_IEVBUS | DC_IERESM | DC_IESUSP | + DC_IEHS_STA | DC_IEBRST); + + if (udc->connected) + isp1760_set_pullup(udc->isp, true); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN); +} + +static void isp1760_udc_reset(struct isp1760_udc *udc) +{ + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* + * The bus reset has reset most registers to their default value, + * reinitialize the UDC hardware. + */ + isp1760_udc_init_hw(udc); + + udc->ep0_state = ISP1760_CTRL_SETUP; + udc->gadget.speed = USB_SPEED_FULL; + + usb_gadget_udc_reset(&udc->gadget, udc->driver); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_suspend(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void isp1760_udc_resume(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* ----------------------------------------------------------------------------- + * Gadget Operations + */ + +static int isp1760_udc_get_frame(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + return isp1760_udc_read(udc, DC_FRAMENUM) & ((1 << 11) - 1); +} + +static int isp1760_udc_wakeup(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + return -ENOTSUPP; +} + +static int isp1760_udc_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static int isp1760_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + isp1760_set_pullup(udc->isp, is_on); + udc->connected = is_on; + + return 0; +} + +static int isp1760_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + /* The hardware doesn't support low speed. */ + if (driver->max_speed < USB_SPEED_FULL) { + dev_err(udc->isp->dev, "Invalid gadget driver\n"); + return -EINVAL; + } + + spin_lock(&udc->lock); + + if (udc->driver) { + dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); + spin_unlock(&udc->lock); + return -EBUSY; + } + + udc->driver = driver; + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, "starting UDC with driver %s\n", + driver->function); + + udc->devstatus = 0; + udc->connected = true; + + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + /* DMA isn't supported yet, don't enable the DMA clock. */ + isp1760_udc_write(udc, DC_MODE, DC_GLINTENA); + + isp1760_udc_init_hw(udc); + + dev_dbg(udc->isp->dev, "UDC started with driver %s\n", + driver->function); + + return 0; +} + +static int isp1760_udc_stop(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + del_timer_sync(&udc->vbus_timer); + + isp1760_udc_write(udc, DC_MODE, 0); + + spin_lock(&udc->lock); + udc->driver = NULL; + spin_unlock(&udc->lock); + + return 0; +} + +static struct usb_gadget_ops isp1760_udc_ops = { + .get_frame = isp1760_udc_get_frame, + .wakeup = isp1760_udc_wakeup, + .set_selfpowered = isp1760_udc_set_selfpowered, + .pullup = isp1760_udc_pullup, + .udc_start = isp1760_udc_start, + .udc_stop = isp1760_udc_stop, +}; + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t isp1760_udc_irq(int irq, void *dev) +{ + struct isp1760_udc *udc = dev; + unsigned int i; + u32 status; + + status = isp1760_udc_read(udc, DC_INTERRUPT) + & isp1760_udc_read(udc, DC_INTENABLE); + isp1760_udc_write(udc, DC_INTERRUPT, status); + + if (status & DC_IEVBUS) { + dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); + /* The VBUS interrupt is only triggered when VBUS appears. */ + spin_lock(&udc->lock); + isp1760_udc_connect(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEBRST) { + dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); + + isp1760_udc_reset(udc); + } + + for (i = 0; i <= 7; ++i) { + struct isp1760_ep *ep = &udc->ep[i*2]; + + if (status & DC_IEPTX(i)) { + dev_dbg(udc->isp->dev, "%s(EPTX%u)\n", __func__, i); + isp1760_ep_tx_complete(ep); + } + + if (status & DC_IEPRX(i)) { + dev_dbg(udc->isp->dev, "%s(EPRX%u)\n", __func__, i); + isp1760_ep_rx_ready(i ? ep - 1 : ep); + } + } + + if (status & DC_IEP0SETUP) { + dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); + + isp1760_ep0_setup(udc); + } + + if (status & DC_IERESM) { + dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); + isp1760_udc_resume(udc); + } + + if (status & DC_IESUSP) { + dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); + + spin_lock(&udc->lock); + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else + isp1760_udc_suspend(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEHS_STA) { + dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); + udc->gadget.speed = USB_SPEED_HIGH; + } + + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static void isp1760_udc_vbus_poll(unsigned long data) +{ + struct isp1760_udc *udc = (struct isp1760_udc *)data; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else if (udc->gadget.state >= USB_STATE_POWERED) + mod_timer(&udc->vbus_timer, + jiffies + ISP1760_VBUS_POLL_INTERVAL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Registration + */ + +static void isp1760_udc_init_eps(struct isp1760_udc *udc) +{ + unsigned int i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (i = 0; i < ARRAY_SIZE(udc->ep); ++i) { + struct isp1760_ep *ep = &udc->ep[i]; + unsigned int ep_num = (i + 1) / 2; + bool is_in = !(i & 1); + + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + ep->addr = (ep_num && is_in ? USB_DIR_IN : USB_DIR_OUT) + | ep_num; + ep->desc = NULL; + + sprintf(ep->name, "ep%u%s", ep_num, + ep_num ? (is_in ? "in" : "out") : ""); + + ep->ep.ops = &isp1760_ep_ops; + ep->ep.name = ep->name; + + /* + * Hardcode the maximum packet sizes for now, to 64 bytes for + * the control endpoint and 512 bytes for all other endpoints. + * This fits in the 8kB FIFO without double-buffering. + */ + if (ep_num == 0) { + ep->ep.maxpacket = 64; + ep->maxpacket = 64; + udc->gadget.ep0 = &ep->ep; + } else { + ep->ep.maxpacket = 512; + ep->maxpacket = 0; + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } +} + +static int isp1760_udc_init(struct isp1760_udc *udc) +{ + u16 scratch; + u32 chipid; + + /* + * Check that the controller is present by writing to the scratch + * register, modifying the bus pattern by reading from the chip ID + * register, and reading the scratch register value back. The chip ID + * and scratch register contents must match the expected values. + */ + isp1760_udc_write(udc, DC_SCRATCH, 0xbabe); + chipid = isp1760_udc_read(udc, DC_CHIPID); + scratch = isp1760_udc_read(udc, DC_SCRATCH); + + if (scratch != 0xbabe) { + dev_err(udc->isp->dev, + "udc: scratch test failed (0x%04x/0x%08x)\n", + scratch, chipid); + return -ENODEV; + } + + if (chipid != 0x00011582) { + dev_err(udc->isp->dev, "udc: invalid chip ID 0x%08x\n", chipid); + return -ENODEV; + } + + /* Reset the device controller. */ + isp1760_udc_write(udc, DC_MODE, DC_SFRESET); + usleep_range(10000, 11000); + isp1760_udc_write(udc, DC_MODE, 0); + usleep_range(10000, 11000); + + return 0; +} + +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + struct isp1760_udc *udc = &isp->udc; + const char *devname; + int ret; + + udc->irq = -1; + udc->isp = isp; + udc->regs = isp->regs; + + spin_lock_init(&udc->lock); + setup_timer(&udc->vbus_timer, isp1760_udc_vbus_poll, + (unsigned long)udc); + + ret = isp1760_udc_init(udc); + if (ret < 0) + return ret; + + devname = dev_name(isp->dev); + udc->irqname = kmalloc(strlen(devname) + 7, GFP_KERNEL); + if (!udc->irqname) + return -ENOMEM; + + sprintf(udc->irqname, "%s (udc)", devname); + + ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | IRQF_DISABLED | + irqflags, udc->irqname, udc); + if (ret < 0) + goto error; + + udc->irq = irq; + + /* + * Initialize the gadget static fields and register its device. Gadget + * fields that vary during the life time of the gadget are initialized + * by the UDC core. + */ + udc->gadget.ops = &isp1760_udc_ops; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.name = "isp1761_udc"; + + isp1760_udc_init_eps(udc); + + ret = usb_add_gadget_udc(isp->dev, &udc->gadget); + if (ret < 0) + goto error; + + return 0; + +error: + if (udc->irq >= 0) + free_irq(udc->irq, udc); + kfree(udc->irqname); + + return ret; +} + +void isp1760_udc_unregister(struct isp1760_device *isp) +{ + struct isp1760_udc *udc = &isp->udc; + + usb_del_gadget_udc(&udc->gadget); + + free_irq(udc->irq, udc); + kfree(udc->irqname); +} diff --git a/drivers/usb/host/isp1760-udc.h b/drivers/usb/host/isp1760-udc.h new file mode 100644 index 0000000..4af6ba6 --- /dev/null +++ b/drivers/usb/host/isp1760-udc.h @@ -0,0 +1,106 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * 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 _ISP1760_UDC_H_ +#define _ISP1760_UDC_H_ + +#include +#include +#include +#include +#include + +struct isp1760_device; +struct isp1760_udc; + +enum isp1760_ctrl_state { + ISP1760_CTRL_SETUP, /* Waiting for a SETUP transaction */ + ISP1760_CTRL_DATA_IN, /* Setup received, data IN stage */ + ISP1760_CTRL_DATA_OUT, /* Setup received, data OUT stage */ + ISP1760_CTRL_STATUS, /* 0-length request in status stage */ +}; + +struct isp1760_ep { + struct isp1760_udc *udc; + struct usb_ep ep; + + struct list_head queue; + + unsigned int addr; + unsigned int maxpacket; + char name[7]; + + const struct usb_endpoint_descriptor *desc; + + bool rx_pending; + bool halted; + bool wedged; +}; + +/** + * struct isp1760_udc - UDC state information + * irq: IRQ number + * irqname: IRQ name (as passed to request_irq) + * regs: Base address of the UDC registers + * driver: Gadget driver + * gadget: Gadget device + * lock: Protects driver, vbus_timer, ep, ep0_*, DC_EPINDEX register + * ep: Array of endpoints + * ep0_state: Control request state for endpoint 0 + * ep0_dir: Direction of the current control request + * ep0_length: Length of the current control request + * connected: Tracks gadget driver bus connection state + */ +struct isp1760_udc { +#if CONFIG_USB_ISP1761_UDC + struct isp1760_device *isp; + + int irq; + char *irqname; + void __iomem *regs; + + struct usb_gadget_driver *driver; + struct usb_gadget gadget; + + spinlock_t lock; + struct timer_list vbus_timer; + + struct isp1760_ep ep[15]; + + enum isp1760_ctrl_state ep0_state; + u8 ep0_dir; + u16 ep0_length; + + bool connected; + + unsigned int devstatus; +#endif +}; + +#if CONFIG_USB_ISP1761_UDC +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags); +void isp1760_udc_unregister(struct isp1760_device *isp); +#else +static inline int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + return 0; +} + +static inline void isp1760_udc_unregister(struct isp1760_device *isp) +{ +} +#endif + +#endif -- cgit v0.10.2 From 7ef077a8ad3557f030d0407c4f56c5a0cf1e418a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:02 +0200 Subject: usb: isp1760: Move driver from drivers/usb/host/ to drivers/usb/isp1760/ Now that this is DRD, it doesn't make sense to keep it under drivers/usb/host. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index ae481c3..8ed451d 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -104,6 +104,8 @@ source "drivers/usb/dwc2/Kconfig" source "drivers/usb/chipidea/Kconfig" +source "drivers/usb/isp1760/Kconfig" + comment "USB port drivers" if USB diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index d7be717..2f1e2aa 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_USB) += core/ obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_DWC2) += dwc2/ +obj-$(CONFIG_USB_ISP1760) += isp1760/ obj-$(CONFIG_USB_MON) += mon/ @@ -23,7 +24,6 @@ obj-$(CONFIG_USB_ISP1362_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ obj-$(CONFIG_USB_HWA_HCD) += host/ -obj-$(CONFIG_USB_ISP1760_HCD) += host/ obj-$(CONFIG_USB_IMX21_HCD) += host/ obj-$(CONFIG_USB_FSL_MPH_DR_OF) += host/ obj-$(CONFIG_USB_FUSBH200_HCD) += host/ diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index c9152e2..b8e213e 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -109,13 +109,6 @@ config USB_GR_UDC Select this to support Aeroflex Gaisler GRUSBDC cores from the GRLIB VHDL IP core library. -config USB_ISP1761_UDC - boolean "NXP ISP1761 USB Device Controller" - depends on USB_ISP1760_HCD - help - The NXP ISP1761 is a dual-role high-speed USB host and device - controller. - config USB_OMAP tristate "OMAP USB Device Controller" depends on ARCH_OMAP1 diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index fafc628..3de291b6 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -331,20 +331,6 @@ config USB_ISP116X_HCD To compile this driver as a module, choose M here: the module will be called isp116x-hcd. -config USB_ISP1760_HCD - tristate "ISP 1760 HCD support" - ---help--- - The ISP1760 chip is a USB 2.0 host controller. - - This driver does not support isochronous transfers or OTG. - This USB controller is usually attached to a non-DMA-Master - capable bus. NXP's eval kit brings this chip on PCI card - where the chip itself is behind a PLB to simulate such - a bus. - - To compile this driver as a module, choose M here: the - module will be called isp1760. - config USB_ISP1362_HCD tristate "ISP1362 HCD support" ---help--- diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 67d3f18..65b0b6a 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -5,9 +5,6 @@ # tell define_trace.h where to find the xhci trace header CFLAGS_xhci-trace.o := -I$(src) -isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o -isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o - fhci-y := fhci-hcd.o fhci-hub.o fhci-q.o fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o @@ -70,7 +67,6 @@ obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o -obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c deleted file mode 100644 index 727e90a..0000000 --- a/drivers/usb/host/isp1760-core.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * Copyright 2014 Laurent Pinchart - * Copyright 2007 Sebastian Siewior - * - * Contacts: - * Sebastian Siewior - * Laurent Pinchart - * - * 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 - -#include "isp1760-core.h" -#include "isp1760-hcd.h" -#include "isp1760-regs.h" -#include "isp1760-udc.h" - -static void isp1760_init_core(struct isp1760_device *isp) -{ - u32 otgctrl; - u32 hwmode; - - /* Low-level chip reset */ - if (isp->rst_gpio) { - gpiod_set_value_cansleep(isp->rst_gpio, 1); - mdelay(50); - gpiod_set_value_cansleep(isp->rst_gpio, 0); - } - - /* - * Reset the host controller, including the CPU interface - * configuration. - */ - isp1760_write32(isp->regs, HC_RESET_REG, SW_RESET_RESET_ALL); - msleep(100); - - /* Setup HW Mode Control: This assumes a level active-low interrupt */ - hwmode = HW_DATA_BUS_32BIT; - - if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_16) - hwmode &= ~HW_DATA_BUS_32BIT; - if (isp->devflags & ISP1760_FLAG_ANALOG_OC) - hwmode |= HW_ANA_DIGI_OC; - if (isp->devflags & ISP1760_FLAG_DACK_POL_HIGH) - hwmode |= HW_DACK_POL_HIGH; - if (isp->devflags & ISP1760_FLAG_DREQ_POL_HIGH) - hwmode |= HW_DREQ_POL_HIGH; - if (isp->devflags & ISP1760_FLAG_INTR_POL_HIGH) - hwmode |= HW_INTR_HIGH_ACT; - if (isp->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) - hwmode |= HW_INTR_EDGE_TRIG; - - /* - * The ISP1761 has a dedicated DC IRQ line but supports sharing the HC - * IRQ line for both the host and device controllers. Hardcode IRQ - * sharing for now and disable the DC interrupts globally to avoid - * spurious interrupts during HCD registration. - */ - if (isp->devflags & ISP1760_FLAG_ISP1761) { - isp1760_write32(isp->regs, DC_MODE, 0); - hwmode |= HW_COMN_IRQ; - } - - /* - * We have to set this first in case we're in 16-bit mode. - * Write it twice to ensure correct upper bits if switching - * to 16-bit mode. - */ - isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); - isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); - - /* - * PORT 1 Control register of the ISP1760 is the OTG control register - * on ISP1761. - * - * TODO: Really support OTG. For now we configure port 1 in device mode - * when OTG is requested. - */ - if ((isp->devflags & ISP1760_FLAG_ISP1761) && - (isp->devflags & ISP1760_FLAG_OTG_EN)) - otgctrl = ((HW_DM_PULLDOWN | HW_DP_PULLDOWN) << 16) - | HW_OTG_DISABLE; - else - otgctrl = (HW_SW_SEL_HC_DC << 16) - | (HW_VBUS_DRV | HW_SEL_CP_EXT); - - isp1760_write32(isp->regs, HC_PORT1_CTRL, otgctrl); - - dev_info(isp->dev, "bus width: %u, oc: %s\n", - isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, - isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); -} - -void isp1760_set_pullup(struct isp1760_device *isp, bool enable) -{ - isp1760_write32(isp->regs, HW_OTG_CTRL_SET, - enable ? HW_DP_PULLUP : HW_DP_PULLUP << 16); -} - -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags) -{ - struct isp1760_device *isp; - int ret; - - if (usb_disabled()) - return -ENODEV; - - /* prevent usb-core allocating DMA pages */ - dev->dma_mask = NULL; - - isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); - if (!isp) - return -ENOMEM; - - isp->dev = dev; - isp->devflags = devflags; - - isp->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); - if (IS_ERR(isp->rst_gpio)) - return PTR_ERR(isp->rst_gpio); - - isp->regs = devm_ioremap_resource(dev, mem); - if (IS_ERR(isp->regs)) - return PTR_ERR(isp->regs); - - isp1760_init_core(isp); - - ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, - irqflags | IRQF_SHARED, dev); - if (ret < 0) - return ret; - - if (devflags & ISP1760_FLAG_ISP1761) { - ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | - IRQF_DISABLED); - if (ret < 0) { - isp1760_hcd_unregister(&isp->hcd); - return ret; - } - } - - dev_set_drvdata(dev, isp); - - return 0; -} - -void isp1760_unregister(struct device *dev) -{ - struct isp1760_device *isp = dev_get_drvdata(dev); - - if (isp->devflags & ISP1760_FLAG_ISP1761) - isp1760_udc_unregister(isp); - - isp1760_hcd_unregister(&isp->hcd); -} - -MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); -MODULE_AUTHOR("Sebastian Siewior "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h deleted file mode 100644 index c70f836..0000000 --- a/drivers/usb/host/isp1760-core.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * Copyright 2014 Laurent Pinchart - * Copyright 2007 Sebastian Siewior - * - * Contacts: - * Sebastian Siewior - * Laurent Pinchart - * - * 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 _ISP1760_CORE_H_ -#define _ISP1760_CORE_H_ - -#include - -#include "isp1760-hcd.h" -#include "isp1760-udc.h" - -struct device; -struct gpio_desc; - -/* - * Device flags that can vary from board to board. All of these - * indicate the most "atypical" case, so that a devflags of 0 is - * a sane default configuration. - */ -#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ -#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ -#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ -#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ -#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ -#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ -#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ -#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ - -struct isp1760_device { - struct device *dev; - - void __iomem *regs; - unsigned int devflags; - struct gpio_desc *rst_gpio; - - struct isp1760_hcd hcd; - struct isp1760_udc udc; -}; - -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); -void isp1760_unregister(struct device *dev); - -void isp1760_set_pullup(struct isp1760_device *isp, bool enable); - -static inline u32 isp1760_read32(void __iomem *base, u32 reg) -{ - return readl(base + reg); -} - -static inline void isp1760_write32(void __iomem *base, u32 reg, u32 val) -{ - writel(val, base + reg); -} - -#endif diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c deleted file mode 100644 index 568446c..0000000 --- a/drivers/usb/host/isp1760-hcd.c +++ /dev/null @@ -1,2231 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * However, the code might contain some bugs. What doesn't work for sure is: - * - ISO - * - OTG - e The interrupt line is configured as active low, level. - * - * (c) 2007 Sebastian Siewior - * - * (c) 2011 Arvid Brodin - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isp1760-core.h" -#include "isp1760-hcd.h" -#include "isp1760-regs.h" - -static struct kmem_cache *qtd_cachep; -static struct kmem_cache *qh_cachep; -static struct kmem_cache *urb_listitem_cachep; - -typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct isp1760_qtd *qtd); - -static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) -{ - return *(struct isp1760_hcd **)hcd->hcd_priv; -} - -/* urb state*/ -#define DELETE_URB (0x0008) -#define NO_TRANSFER_ACTIVE (0xffffffff) - -/* Philips Proprietary Transfer Descriptor (PTD) */ -typedef __u32 __bitwise __dw; -struct ptd { - __dw dw0; - __dw dw1; - __dw dw2; - __dw dw3; - __dw dw4; - __dw dw5; - __dw dw6; - __dw dw7; -}; -#define PTD_OFFSET 0x0400 -#define ISO_PTD_OFFSET 0x0400 -#define INT_PTD_OFFSET 0x0800 -#define ATL_PTD_OFFSET 0x0c00 -#define PAYLOAD_OFFSET 0x1000 - - -/* ATL */ -/* DW0 */ -#define DW0_VALID_BIT 1 -#define FROM_DW0_VALID(x) ((x) & 0x01) -#define TO_DW0_LENGTH(x) (((u32) x) << 3) -#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) -#define TO_DW0_MULTI(x) (((u32) x) << 29) -#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) -/* DW1 */ -#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) -#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) -#define DW1_TRANS_BULK ((u32) 2 << 12) -#define DW1_TRANS_INT ((u32) 3 << 12) -#define DW1_TRANS_SPLIT ((u32) 1 << 14) -#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) -#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) -#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) -/* DW2 */ -#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) -#define TO_DW2_RL(x) ((x) << 25) -#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) -/* DW3 */ -#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) -#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) -#define TO_DW3_NAKCOUNT(x) ((x) << 19) -#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) -#define TO_DW3_CERR(x) ((x) << 23) -#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) -#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) -#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) -#define TO_DW3_PING(x) ((x) << 26) -#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) -#define DW3_ERROR_BIT (1 << 28) -#define DW3_BABBLE_BIT (1 << 29) -#define DW3_HALT_BIT (1 << 30) -#define DW3_ACTIVE_BIT (1 << 31) -#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) - -#define INT_UNDERRUN (1 << 2) -#define INT_BABBLE (1 << 1) -#define INT_EXACT (1 << 0) - -#define SETUP_PID (2) -#define IN_PID (1) -#define OUT_PID (0) - -/* Errata 1 */ -#define RL_COUNTER (0) -#define NAK_COUNTER (0) -#define ERR_COUNTER (2) - -struct isp1760_qtd { - u8 packet_type; - void *data_buffer; - u32 payload_addr; - - /* the rest is HCD-private */ - struct list_head qtd_list; - struct urb *urb; - size_t length; - size_t actual_length; - - /* QTD_ENQUEUED: waiting for transfer (inactive) */ - /* QTD_PAYLOAD_ALLOC: chip mem has been allocated for payload */ - /* QTD_XFER_STARTED: valid ptd has been written to isp176x - only - interrupt handler may touch this qtd! */ - /* QTD_XFER_COMPLETE: payload has been transferred successfully */ - /* QTD_RETIRE: transfer error/abort qtd */ -#define QTD_ENQUEUED 0 -#define QTD_PAYLOAD_ALLOC 1 -#define QTD_XFER_STARTED 2 -#define QTD_XFER_COMPLETE 3 -#define QTD_RETIRE 4 - u32 status; -}; - -/* Queue head, one for each active endpoint */ -struct isp1760_qh { - struct list_head qh_list; - struct list_head qtd_list; - u32 toggle; - u32 ping; - int slot; - int tt_buffer_dirty; /* See USB2.0 spec section 11.17.5 */ -}; - -struct urb_listitem { - struct list_head urb_list; - struct urb *urb; -}; - -/* - * Access functions for isp176x registers (addresses 0..0x03FF). - */ -static u32 reg_read32(void __iomem *base, u32 reg) -{ - return isp1760_read32(base, reg); -} - -static void reg_write32(void __iomem *base, u32 reg, u32 val) -{ - isp1760_write32(base, reg, val); -} - -/* - * Access functions for isp176x memory (offset >= 0x0400). - * - * bank_reads8() reads memory locations prefetched by an earlier write to - * HC_MEMORY_REG (see isp176x datasheet). Unless you want to do fancy multi- - * bank optimizations, you should use the more generic mem_reads8() below. - * - * For access to ptd memory, use the specialized ptd_read() and ptd_write() - * below. - * - * These functions copy via MMIO data to/from the device. memcpy_{to|from}io() - * doesn't quite work because some people have to enforce 32-bit access - */ -static void bank_reads8(void __iomem *src_base, u32 src_offset, u32 bank_addr, - __u32 *dst, u32 bytes) -{ - __u32 __iomem *src; - u32 val; - __u8 *src_byteptr; - __u8 *dst_byteptr; - - src = src_base + (bank_addr | src_offset); - - if (src_offset < PAYLOAD_OFFSET) { - while (bytes >= 4) { - *dst = le32_to_cpu(__raw_readl(src)); - bytes -= 4; - src++; - dst++; - } - } else { - while (bytes >= 4) { - *dst = __raw_readl(src); - bytes -= 4; - src++; - dst++; - } - } - - if (!bytes) - return; - - /* in case we have 3, 2 or 1 by left. The dst buffer may not be fully - * allocated. - */ - if (src_offset < PAYLOAD_OFFSET) - val = le32_to_cpu(__raw_readl(src)); - else - val = __raw_readl(src); - - dst_byteptr = (void *) dst; - src_byteptr = (void *) &val; - while (bytes > 0) { - *dst_byteptr = *src_byteptr; - dst_byteptr++; - src_byteptr++; - bytes--; - } -} - -static void mem_reads8(void __iomem *src_base, u32 src_offset, void *dst, - u32 bytes) -{ - reg_write32(src_base, HC_MEMORY_REG, src_offset + ISP_BANK(0)); - ndelay(90); - bank_reads8(src_base, src_offset, ISP_BANK(0), dst, bytes); -} - -static void mem_writes8(void __iomem *dst_base, u32 dst_offset, - __u32 const *src, u32 bytes) -{ - __u32 __iomem *dst; - - dst = dst_base + dst_offset; - - if (dst_offset < PAYLOAD_OFFSET) { - while (bytes >= 4) { - __raw_writel(cpu_to_le32(*src), dst); - bytes -= 4; - src++; - dst++; - } - } else { - while (bytes >= 4) { - __raw_writel(*src, dst); - bytes -= 4; - src++; - dst++; - } - } - - if (!bytes) - return; - /* in case we have 3, 2 or 1 bytes left. The buffer is allocated and the - * extra bytes should not be read by the HW. - */ - - if (dst_offset < PAYLOAD_OFFSET) - __raw_writel(cpu_to_le32(*src), dst); - else - __raw_writel(*src, dst); -} - -/* - * Read and write ptds. 'ptd_offset' should be one of ISO_PTD_OFFSET, - * INT_PTD_OFFSET, and ATL_PTD_OFFSET. 'slot' should be less than 32. - */ -static void ptd_read(void __iomem *base, u32 ptd_offset, u32 slot, - struct ptd *ptd) -{ - reg_write32(base, HC_MEMORY_REG, - ISP_BANK(0) + ptd_offset + slot*sizeof(*ptd)); - ndelay(90); - bank_reads8(base, ptd_offset + slot*sizeof(*ptd), ISP_BANK(0), - (void *) ptd, sizeof(*ptd)); -} - -static void ptd_write(void __iomem *base, u32 ptd_offset, u32 slot, - struct ptd *ptd) -{ - mem_writes8(base, ptd_offset + slot*sizeof(*ptd) + sizeof(ptd->dw0), - &ptd->dw1, 7*sizeof(ptd->dw1)); - /* Make sure dw0 gets written last (after other dw's and after payload) - since it contains the enable bit */ - wmb(); - mem_writes8(base, ptd_offset + slot*sizeof(*ptd), &ptd->dw0, - sizeof(ptd->dw0)); -} - - -/* memory management of the 60kb on the chip from 0x1000 to 0xffff */ -static void init_memory(struct isp1760_hcd *priv) -{ - int i, curr; - u32 payload_addr; - - payload_addr = PAYLOAD_OFFSET; - for (i = 0; i < BLOCK_1_NUM; i++) { - priv->memory_pool[i].start = payload_addr; - priv->memory_pool[i].size = BLOCK_1_SIZE; - priv->memory_pool[i].free = 1; - payload_addr += priv->memory_pool[i].size; - } - - curr = i; - for (i = 0; i < BLOCK_2_NUM; i++) { - priv->memory_pool[curr + i].start = payload_addr; - priv->memory_pool[curr + i].size = BLOCK_2_SIZE; - priv->memory_pool[curr + i].free = 1; - payload_addr += priv->memory_pool[curr + i].size; - } - - curr = i; - for (i = 0; i < BLOCK_3_NUM; i++) { - priv->memory_pool[curr + i].start = payload_addr; - priv->memory_pool[curr + i].size = BLOCK_3_SIZE; - priv->memory_pool[curr + i].free = 1; - payload_addr += priv->memory_pool[curr + i].size; - } - - WARN_ON(payload_addr - priv->memory_pool[0].start > PAYLOAD_AREA_SIZE); -} - -static void alloc_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int i; - - WARN_ON(qtd->payload_addr); - - if (!qtd->length) - return; - - for (i = 0; i < BLOCKS; i++) { - if (priv->memory_pool[i].size >= qtd->length && - priv->memory_pool[i].free) { - priv->memory_pool[i].free = 0; - qtd->payload_addr = priv->memory_pool[i].start; - return; - } - } -} - -static void free_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int i; - - if (!qtd->payload_addr) - return; - - for (i = 0; i < BLOCKS; i++) { - if (priv->memory_pool[i].start == qtd->payload_addr) { - WARN_ON(priv->memory_pool[i].free); - priv->memory_pool[i].free = 1; - qtd->payload_addr = 0; - return; - } - } - - dev_err(hcd->self.controller, "%s: Invalid pointer: %08x\n", - __func__, qtd->payload_addr); - WARN_ON(1); - qtd->payload_addr = 0; -} - -static int handshake(struct usb_hcd *hcd, u32 reg, - u32 mask, u32 done, int usec) -{ - u32 result; - - do { - result = reg_read32(hcd->regs, reg); - if (result == ~0) - return -ENODEV; - result &= mask; - if (result == done) - return 0; - udelay(1); - usec--; - } while (usec > 0); - return -ETIMEDOUT; -} - -/* reset a non-running (STS_HALT == 1) controller */ -static int ehci_reset(struct usb_hcd *hcd) -{ - int retval; - struct isp1760_hcd *priv = hcd_to_priv(hcd); - - u32 command = reg_read32(hcd->regs, HC_USBCMD); - - command |= CMD_RESET; - reg_write32(hcd->regs, HC_USBCMD, command); - hcd->state = HC_STATE_HALT; - priv->next_statechange = jiffies; - retval = handshake(hcd, HC_USBCMD, - CMD_RESET, 0, 250 * 1000); - return retval; -} - -static struct isp1760_qh *qh_alloc(gfp_t flags) -{ - struct isp1760_qh *qh; - - qh = kmem_cache_zalloc(qh_cachep, flags); - if (!qh) - return NULL; - - INIT_LIST_HEAD(&qh->qh_list); - INIT_LIST_HEAD(&qh->qtd_list); - qh->slot = -1; - - return qh; -} - -static void qh_free(struct isp1760_qh *qh) -{ - WARN_ON(!list_empty(&qh->qtd_list)); - WARN_ON(qh->slot > -1); - kmem_cache_free(qh_cachep, qh); -} - -/* one-time init, only for memory state */ -static int priv_init(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 hcc_params; - int i; - - spin_lock_init(&priv->lock); - - for (i = 0; i < QH_END; i++) - INIT_LIST_HEAD(&priv->qh_list[i]); - - /* - * hw default: 1K periodic list heads, one per frame. - * periodic_size can shrink by USBCMD update if hcc_params allows. - */ - priv->periodic_size = DEFAULT_I_TDPS; - - /* controllers may cache some of the periodic schedule ... */ - hcc_params = reg_read32(hcd->regs, HC_HCCPARAMS); - /* full frame cache */ - if (HCC_ISOC_CACHE(hcc_params)) - priv->i_thresh = 8; - else /* N microframes cached */ - priv->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); - - return 0; -} - -static int isp1760_hc_setup(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int result; - u32 scratch, hwmode; - - reg_write32(hcd->regs, HC_SCRATCH_REG, 0xdeadbabe); - /* Change bus pattern */ - scratch = reg_read32(hcd->regs, HC_CHIP_ID_REG); - scratch = reg_read32(hcd->regs, HC_SCRATCH_REG); - if (scratch != 0xdeadbabe) { - dev_err(hcd->self.controller, "Scratch test failed.\n"); - return -ENODEV; - } - - /* - * The RESET_HC bit in the SW_RESET register is supposed to reset the - * host controller without touching the CPU interface registers, but at - * least on the ISP1761 it seems to behave as the RESET_ALL bit and - * reset the whole device. We thus can't use it here, so let's reset - * the host controller through the EHCI USB Command register. The device - * has been reset in core code anyway, so this shouldn't matter. - */ - reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, 0); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - - result = ehci_reset(hcd); - if (result) - return result; - - /* Step 11 passed */ - - /* ATL reset */ - hwmode = reg_read32(hcd->regs, HC_HW_MODE_CTRL) & ~ALL_ATX_RESET; - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); - mdelay(10); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); - - reg_write32(hcd->regs, HC_INTERRUPT_ENABLE, INTERRUPT_ENABLE_MASK); - - priv->hcs_params = reg_read32(hcd->regs, HC_HCSPARAMS); - - return priv_init(hcd); -} - -static u32 base_to_chip(u32 base) -{ - return ((base - 0x400) >> 3); -} - -static int last_qtd_of_urb(struct isp1760_qtd *qtd, struct isp1760_qh *qh) -{ - struct urb *urb; - - if (list_is_last(&qtd->qtd_list, &qh->qtd_list)) - return 1; - - urb = qtd->urb; - qtd = list_entry(qtd->qtd_list.next, typeof(*qtd), qtd_list); - return (qtd->urb != urb); -} - -/* magic numbers that can affect system performance */ -#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ -#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ -#define EHCI_TUNE_RL_TT 0 -#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ -#define EHCI_TUNE_MULT_TT 1 -#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ - -static void create_ptd_atl(struct isp1760_qh *qh, - struct isp1760_qtd *qtd, struct ptd *ptd) -{ - u32 maxpacket; - u32 multi; - u32 rl = RL_COUNTER; - u32 nak = NAK_COUNTER; - - memset(ptd, 0, sizeof(*ptd)); - - /* according to 3.6.2, max packet len can not be > 0x400 */ - maxpacket = usb_maxpacket(qtd->urb->dev, qtd->urb->pipe, - usb_pipeout(qtd->urb->pipe)); - multi = 1 + ((maxpacket >> 11) & 0x3); - maxpacket &= 0x7ff; - - /* DW0 */ - ptd->dw0 = DW0_VALID_BIT; - ptd->dw0 |= TO_DW0_LENGTH(qtd->length); - ptd->dw0 |= TO_DW0_MAXPACKET(maxpacket); - ptd->dw0 |= TO_DW0_ENDPOINT(usb_pipeendpoint(qtd->urb->pipe)); - - /* DW1 */ - ptd->dw1 = usb_pipeendpoint(qtd->urb->pipe) >> 1; - ptd->dw1 |= TO_DW1_DEVICE_ADDR(usb_pipedevice(qtd->urb->pipe)); - ptd->dw1 |= TO_DW1_PID_TOKEN(qtd->packet_type); - - if (usb_pipebulk(qtd->urb->pipe)) - ptd->dw1 |= DW1_TRANS_BULK; - else if (usb_pipeint(qtd->urb->pipe)) - ptd->dw1 |= DW1_TRANS_INT; - - if (qtd->urb->dev->speed != USB_SPEED_HIGH) { - /* split transaction */ - - ptd->dw1 |= DW1_TRANS_SPLIT; - if (qtd->urb->dev->speed == USB_SPEED_LOW) - ptd->dw1 |= DW1_SE_USB_LOSPEED; - - ptd->dw1 |= TO_DW1_PORT_NUM(qtd->urb->dev->ttport); - ptd->dw1 |= TO_DW1_HUB_NUM(qtd->urb->dev->tt->hub->devnum); - - /* SE bit for Split INT transfers */ - if (usb_pipeint(qtd->urb->pipe) && - (qtd->urb->dev->speed == USB_SPEED_LOW)) - ptd->dw1 |= 2 << 16; - - rl = 0; - nak = 0; - } else { - ptd->dw0 |= TO_DW0_MULTI(multi); - if (usb_pipecontrol(qtd->urb->pipe) || - usb_pipebulk(qtd->urb->pipe)) - ptd->dw3 |= TO_DW3_PING(qh->ping); - } - /* DW2 */ - ptd->dw2 = 0; - ptd->dw2 |= TO_DW2_DATA_START_ADDR(base_to_chip(qtd->payload_addr)); - ptd->dw2 |= TO_DW2_RL(rl); - - /* DW3 */ - ptd->dw3 |= TO_DW3_NAKCOUNT(nak); - ptd->dw3 |= TO_DW3_DATA_TOGGLE(qh->toggle); - if (usb_pipecontrol(qtd->urb->pipe)) { - if (qtd->data_buffer == qtd->urb->setup_packet) - ptd->dw3 &= ~TO_DW3_DATA_TOGGLE(1); - else if (last_qtd_of_urb(qtd, qh)) - ptd->dw3 |= TO_DW3_DATA_TOGGLE(1); - } - - ptd->dw3 |= DW3_ACTIVE_BIT; - /* Cerr */ - ptd->dw3 |= TO_DW3_CERR(ERR_COUNTER); -} - -static void transform_add_int(struct isp1760_qh *qh, - struct isp1760_qtd *qtd, struct ptd *ptd) -{ - u32 usof; - u32 period; - - /* - * Most of this is guessing. ISP1761 datasheet is quite unclear, and - * the algorithm from the original Philips driver code, which was - * pretty much used in this driver before as well, is quite horrendous - * and, i believe, incorrect. The code below follows the datasheet and - * USB2.0 spec as far as I can tell, and plug/unplug seems to be much - * more reliable this way (fingers crossed...). - */ - - if (qtd->urb->dev->speed == USB_SPEED_HIGH) { - /* urb->interval is in units of microframes (1/8 ms) */ - period = qtd->urb->interval >> 3; - - if (qtd->urb->interval > 4) - usof = 0x01; /* One bit set => - interval 1 ms * uFrame-match */ - else if (qtd->urb->interval > 2) - usof = 0x22; /* Two bits set => interval 1/2 ms */ - else if (qtd->urb->interval > 1) - usof = 0x55; /* Four bits set => interval 1/4 ms */ - else - usof = 0xff; /* All bits set => interval 1/8 ms */ - } else { - /* urb->interval is in units of frames (1 ms) */ - period = qtd->urb->interval; - usof = 0x0f; /* Execute Start Split on any of the - four first uFrames */ - - /* - * First 8 bits in dw5 is uSCS and "specifies which uSOF the - * complete split needs to be sent. Valid only for IN." Also, - * "All bits can be set to one for every transfer." (p 82, - * ISP1761 data sheet.) 0x1c is from Philips driver. Where did - * that number come from? 0xff seems to work fine... - */ - /* ptd->dw5 = 0x1c; */ - ptd->dw5 = 0xff; /* Execute Complete Split on any uFrame */ - } - - period = period >> 1;/* Ensure equal or shorter period than requested */ - period &= 0xf8; /* Mask off too large values and lowest unused 3 bits */ - - ptd->dw2 |= period; - ptd->dw4 = usof; -} - -static void create_ptd_int(struct isp1760_qh *qh, - struct isp1760_qtd *qtd, struct ptd *ptd) -{ - create_ptd_atl(qh, qtd, ptd); - transform_add_int(qh, qtd, ptd); -} - -static void isp1760_urb_done(struct usb_hcd *hcd, struct urb *urb) -__releases(priv->lock) -__acquires(priv->lock) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - - if (!urb->unlinked) { - if (urb->status == -EINPROGRESS) - urb->status = 0; - } - - if (usb_pipein(urb->pipe) && usb_pipetype(urb->pipe) != PIPE_CONTROL) { - void *ptr; - for (ptr = urb->transfer_buffer; - ptr < urb->transfer_buffer + urb->transfer_buffer_length; - ptr += PAGE_SIZE) - flush_dcache_page(virt_to_page(ptr)); - } - - /* complete() can reenter this HCD */ - usb_hcd_unlink_urb_from_ep(hcd, urb); - spin_unlock(&priv->lock); - usb_hcd_giveback_urb(hcd, urb, urb->status); - spin_lock(&priv->lock); -} - -static struct isp1760_qtd *qtd_alloc(gfp_t flags, struct urb *urb, - u8 packet_type) -{ - struct isp1760_qtd *qtd; - - qtd = kmem_cache_zalloc(qtd_cachep, flags); - if (!qtd) - return NULL; - - INIT_LIST_HEAD(&qtd->qtd_list); - qtd->urb = urb; - qtd->packet_type = packet_type; - qtd->status = QTD_ENQUEUED; - qtd->actual_length = 0; - - return qtd; -} - -static void qtd_free(struct isp1760_qtd *qtd) -{ - WARN_ON(qtd->payload_addr); - kmem_cache_free(qtd_cachep, qtd); -} - -static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, - struct isp1760_slotinfo *slots, - struct isp1760_qtd *qtd, struct isp1760_qh *qh, - struct ptd *ptd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int skip_map; - - WARN_ON((slot < 0) || (slot > 31)); - WARN_ON(qtd->length && !qtd->payload_addr); - WARN_ON(slots[slot].qtd); - WARN_ON(slots[slot].qh); - WARN_ON(qtd->status != QTD_PAYLOAD_ALLOC); - - /* Make sure done map has not triggered from some unlinked transfer */ - if (ptd_offset == ATL_PTD_OFFSET) { - priv->atl_done_map |= reg_read32(hcd->regs, - HC_ATL_PTD_DONEMAP_REG); - priv->atl_done_map &= ~(1 << slot); - } else { - priv->int_done_map |= reg_read32(hcd->regs, - HC_INT_PTD_DONEMAP_REG); - priv->int_done_map &= ~(1 << slot); - } - - qh->slot = slot; - qtd->status = QTD_XFER_STARTED; - slots[slot].timestamp = jiffies; - slots[slot].qtd = qtd; - slots[slot].qh = qh; - ptd_write(hcd->regs, ptd_offset, slot, ptd); - - if (ptd_offset == ATL_PTD_OFFSET) { - skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); - skip_map &= ~(1 << qh->slot); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); - } else { - skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); - skip_map &= ~(1 << qh->slot); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); - } -} - -static int is_short_bulk(struct isp1760_qtd *qtd) -{ - return (usb_pipebulk(qtd->urb->pipe) && - (qtd->actual_length < qtd->length)); -} - -static void collect_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct list_head *urb_list) -{ - int last_qtd; - struct isp1760_qtd *qtd, *qtd_next; - struct urb_listitem *urb_listitem; - - list_for_each_entry_safe(qtd, qtd_next, &qh->qtd_list, qtd_list) { - if (qtd->status < QTD_XFER_COMPLETE) - break; - - last_qtd = last_qtd_of_urb(qtd, qh); - - if ((!last_qtd) && (qtd->status == QTD_RETIRE)) - qtd_next->status = QTD_RETIRE; - - if (qtd->status == QTD_XFER_COMPLETE) { - if (qtd->actual_length) { - switch (qtd->packet_type) { - case IN_PID: - mem_reads8(hcd->regs, qtd->payload_addr, - qtd->data_buffer, - qtd->actual_length); - /* Fall through (?) */ - case OUT_PID: - qtd->urb->actual_length += - qtd->actual_length; - /* Fall through ... */ - case SETUP_PID: - break; - } - } - - if (is_short_bulk(qtd)) { - if (qtd->urb->transfer_flags & URB_SHORT_NOT_OK) - qtd->urb->status = -EREMOTEIO; - if (!last_qtd) - qtd_next->status = QTD_RETIRE; - } - } - - if (qtd->payload_addr) - free_mem(hcd, qtd); - - if (last_qtd) { - if ((qtd->status == QTD_RETIRE) && - (qtd->urb->status == -EINPROGRESS)) - qtd->urb->status = -EPIPE; - /* Defer calling of urb_done() since it releases lock */ - urb_listitem = kmem_cache_zalloc(urb_listitem_cachep, - GFP_ATOMIC); - if (unlikely(!urb_listitem)) - break; /* Try again on next call */ - urb_listitem->urb = qtd->urb; - list_add_tail(&urb_listitem->urb_list, urb_list); - } - - list_del(&qtd->qtd_list); - qtd_free(qtd); - } -} - -#define ENQUEUE_DEPTH 2 -static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int ptd_offset; - struct isp1760_slotinfo *slots; - int curr_slot, free_slot; - int n; - struct ptd ptd; - struct isp1760_qtd *qtd; - - if (unlikely(list_empty(&qh->qtd_list))) { - WARN_ON(1); - return; - } - - /* Make sure this endpoint's TT buffer is clean before queueing ptds */ - if (qh->tt_buffer_dirty) - return; - - if (usb_pipeint(list_entry(qh->qtd_list.next, struct isp1760_qtd, - qtd_list)->urb->pipe)) { - ptd_offset = INT_PTD_OFFSET; - slots = priv->int_slots; - } else { - ptd_offset = ATL_PTD_OFFSET; - slots = priv->atl_slots; - } - - free_slot = -1; - for (curr_slot = 0; curr_slot < 32; curr_slot++) { - if ((free_slot == -1) && (slots[curr_slot].qtd == NULL)) - free_slot = curr_slot; - if (slots[curr_slot].qh == qh) - break; - } - - n = 0; - list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { - if (qtd->status == QTD_ENQUEUED) { - WARN_ON(qtd->payload_addr); - alloc_mem(hcd, qtd); - if ((qtd->length) && (!qtd->payload_addr)) - break; - - if ((qtd->length) && - ((qtd->packet_type == SETUP_PID) || - (qtd->packet_type == OUT_PID))) { - mem_writes8(hcd->regs, qtd->payload_addr, - qtd->data_buffer, qtd->length); - } - - qtd->status = QTD_PAYLOAD_ALLOC; - } - - if (qtd->status == QTD_PAYLOAD_ALLOC) { -/* - if ((curr_slot > 31) && (free_slot == -1)) - dev_dbg(hcd->self.controller, "%s: No slot " - "available for transfer\n", __func__); -*/ - /* Start xfer for this endpoint if not already done */ - if ((curr_slot > 31) && (free_slot > -1)) { - if (usb_pipeint(qtd->urb->pipe)) - create_ptd_int(qh, qtd, &ptd); - else - create_ptd_atl(qh, qtd, &ptd); - - start_bus_transfer(hcd, ptd_offset, free_slot, - slots, qtd, qh, &ptd); - curr_slot = free_slot; - } - - n++; - if (n >= ENQUEUE_DEPTH) - break; - } - } -} - -static void schedule_ptds(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv; - struct isp1760_qh *qh, *qh_next; - struct list_head *ep_queue; - LIST_HEAD(urb_list); - struct urb_listitem *urb_listitem, *urb_listitem_next; - int i; - - if (!hcd) { - WARN_ON(1); - return; - } - - priv = hcd_to_priv(hcd); - - /* - * check finished/retired xfers, transfer payloads, call urb_done() - */ - for (i = 0; i < QH_END; i++) { - ep_queue = &priv->qh_list[i]; - list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) { - collect_qtds(hcd, qh, &urb_list); - if (list_empty(&qh->qtd_list)) - list_del(&qh->qh_list); - } - } - - list_for_each_entry_safe(urb_listitem, urb_listitem_next, &urb_list, - urb_list) { - isp1760_urb_done(hcd, urb_listitem->urb); - kmem_cache_free(urb_listitem_cachep, urb_listitem); - } - - /* - * Schedule packets for transfer. - * - * According to USB2.0 specification: - * - * 1st prio: interrupt xfers, up to 80 % of bandwidth - * 2nd prio: control xfers - * 3rd prio: bulk xfers - * - * ... but let's use a simpler scheme here (mostly because ISP1761 doc - * is very unclear on how to prioritize traffic): - * - * 1) Enqueue any queued control transfers, as long as payload chip mem - * and PTD ATL slots are available. - * 2) Enqueue any queued INT transfers, as long as payload chip mem - * and PTD INT slots are available. - * 3) Enqueue any queued bulk transfers, as long as payload chip mem - * and PTD ATL slots are available. - * - * Use double buffering (ENQUEUE_DEPTH==2) as a compromise between - * conservation of chip mem and performance. - * - * I'm sure this scheme could be improved upon! - */ - for (i = 0; i < QH_END; i++) { - ep_queue = &priv->qh_list[i]; - list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) - enqueue_qtds(hcd, qh); - } -} - -#define PTD_STATE_QTD_DONE 1 -#define PTD_STATE_QTD_RELOAD 2 -#define PTD_STATE_URB_RETIRE 3 - -static int check_int_transfer(struct usb_hcd *hcd, struct ptd *ptd, - struct urb *urb) -{ - __dw dw4; - int i; - - dw4 = ptd->dw4; - dw4 >>= 8; - - /* FIXME: ISP1761 datasheet does not say what to do with these. Do we - need to handle these errors? Is it done in hardware? */ - - if (ptd->dw3 & DW3_HALT_BIT) { - - urb->status = -EPROTO; /* Default unknown error */ - - for (i = 0; i < 8; i++) { - switch (dw4 & 0x7) { - case INT_UNDERRUN: - dev_dbg(hcd->self.controller, "%s: underrun " - "during uFrame %d\n", - __func__, i); - urb->status = -ECOMM; /* Could not write data */ - break; - case INT_EXACT: - dev_dbg(hcd->self.controller, "%s: transaction " - "error during uFrame %d\n", - __func__, i); - urb->status = -EPROTO; /* timeout, bad CRC, PID - error etc. */ - break; - case INT_BABBLE: - dev_dbg(hcd->self.controller, "%s: babble " - "error during uFrame %d\n", - __func__, i); - urb->status = -EOVERFLOW; - break; - } - dw4 >>= 3; - } - - return PTD_STATE_URB_RETIRE; - } - - return PTD_STATE_QTD_DONE; -} - -static int check_atl_transfer(struct usb_hcd *hcd, struct ptd *ptd, - struct urb *urb) -{ - WARN_ON(!ptd); - if (ptd->dw3 & DW3_HALT_BIT) { - if (ptd->dw3 & DW3_BABBLE_BIT) - urb->status = -EOVERFLOW; - else if (FROM_DW3_CERR(ptd->dw3)) - urb->status = -EPIPE; /* Stall */ - else if (ptd->dw3 & DW3_ERROR_BIT) - urb->status = -EPROTO; /* XactErr */ - else - urb->status = -EPROTO; /* Unknown */ -/* - dev_dbg(hcd->self.controller, "%s: ptd error:\n" - " dw0: %08x dw1: %08x dw2: %08x dw3: %08x\n" - " dw4: %08x dw5: %08x dw6: %08x dw7: %08x\n", - __func__, - ptd->dw0, ptd->dw1, ptd->dw2, ptd->dw3, - ptd->dw4, ptd->dw5, ptd->dw6, ptd->dw7); -*/ - return PTD_STATE_URB_RETIRE; - } - - if ((ptd->dw3 & DW3_ERROR_BIT) && (ptd->dw3 & DW3_ACTIVE_BIT)) { - /* Transfer Error, *but* active and no HALT -> reload */ - dev_dbg(hcd->self.controller, "PID error; reloading ptd\n"); - return PTD_STATE_QTD_RELOAD; - } - - if (!FROM_DW3_NAKCOUNT(ptd->dw3) && (ptd->dw3 & DW3_ACTIVE_BIT)) { - /* - * NAKs are handled in HW by the chip. Usually if the - * device is not able to send data fast enough. - * This happens mostly on slower hardware. - */ - return PTD_STATE_QTD_RELOAD; - } - - return PTD_STATE_QTD_DONE; -} - -static void handle_done_ptds(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - struct ptd ptd; - struct isp1760_qh *qh; - int slot; - int state; - struct isp1760_slotinfo *slots; - u32 ptd_offset; - struct isp1760_qtd *qtd; - int modified; - int skip_map; - - skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); - priv->int_done_map &= ~skip_map; - skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); - priv->atl_done_map &= ~skip_map; - - modified = priv->int_done_map || priv->atl_done_map; - - while (priv->int_done_map || priv->atl_done_map) { - if (priv->int_done_map) { - /* INT ptd */ - slot = __ffs(priv->int_done_map); - priv->int_done_map &= ~(1 << slot); - slots = priv->int_slots; - /* This should not trigger, and could be removed if - noone have any problems with it triggering: */ - if (!slots[slot].qh) { - WARN_ON(1); - continue; - } - ptd_offset = INT_PTD_OFFSET; - ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); - state = check_int_transfer(hcd, &ptd, - slots[slot].qtd->urb); - } else { - /* ATL ptd */ - slot = __ffs(priv->atl_done_map); - priv->atl_done_map &= ~(1 << slot); - slots = priv->atl_slots; - /* This should not trigger, and could be removed if - noone have any problems with it triggering: */ - if (!slots[slot].qh) { - WARN_ON(1); - continue; - } - ptd_offset = ATL_PTD_OFFSET; - ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); - state = check_atl_transfer(hcd, &ptd, - slots[slot].qtd->urb); - } - - qtd = slots[slot].qtd; - slots[slot].qtd = NULL; - qh = slots[slot].qh; - slots[slot].qh = NULL; - qh->slot = -1; - - WARN_ON(qtd->status != QTD_XFER_STARTED); - - switch (state) { - case PTD_STATE_QTD_DONE: - if ((usb_pipeint(qtd->urb->pipe)) && - (qtd->urb->dev->speed != USB_SPEED_HIGH)) - qtd->actual_length = - FROM_DW3_SCS_NRBYTESTRANSFERRED(ptd.dw3); - else - qtd->actual_length = - FROM_DW3_NRBYTESTRANSFERRED(ptd.dw3); - - qtd->status = QTD_XFER_COMPLETE; - if (list_is_last(&qtd->qtd_list, &qh->qtd_list) || - is_short_bulk(qtd)) - qtd = NULL; - else - qtd = list_entry(qtd->qtd_list.next, - typeof(*qtd), qtd_list); - - qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); - qh->ping = FROM_DW3_PING(ptd.dw3); - break; - - case PTD_STATE_QTD_RELOAD: /* QTD_RETRY, for atls only */ - qtd->status = QTD_PAYLOAD_ALLOC; - ptd.dw0 |= DW0_VALID_BIT; - /* RL counter = ERR counter */ - ptd.dw3 &= ~TO_DW3_NAKCOUNT(0xf); - ptd.dw3 |= TO_DW3_NAKCOUNT(FROM_DW2_RL(ptd.dw2)); - ptd.dw3 &= ~TO_DW3_CERR(3); - ptd.dw3 |= TO_DW3_CERR(ERR_COUNTER); - qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); - qh->ping = FROM_DW3_PING(ptd.dw3); - break; - - case PTD_STATE_URB_RETIRE: - qtd->status = QTD_RETIRE; - if ((qtd->urb->dev->speed != USB_SPEED_HIGH) && - (qtd->urb->status != -EPIPE) && - (qtd->urb->status != -EREMOTEIO)) { - qh->tt_buffer_dirty = 1; - if (usb_hub_clear_tt_buffer(qtd->urb)) - /* Clear failed; let's hope things work - anyway */ - qh->tt_buffer_dirty = 0; - } - qtd = NULL; - qh->toggle = 0; - qh->ping = 0; - break; - - default: - WARN_ON(1); - continue; - } - - if (qtd && (qtd->status == QTD_PAYLOAD_ALLOC)) { - if (slots == priv->int_slots) { - if (state == PTD_STATE_QTD_RELOAD) - dev_err(hcd->self.controller, - "%s: PTD_STATE_QTD_RELOAD on " - "interrupt packet\n", __func__); - if (state != PTD_STATE_QTD_RELOAD) - create_ptd_int(qh, qtd, &ptd); - } else { - if (state != PTD_STATE_QTD_RELOAD) - create_ptd_atl(qh, qtd, &ptd); - } - - start_bus_transfer(hcd, ptd_offset, slot, slots, qtd, - qh, &ptd); - } - } - - if (modified) - schedule_ptds(hcd); -} - -static irqreturn_t isp1760_irq(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 imask; - irqreturn_t irqret = IRQ_NONE; - - spin_lock(&priv->lock); - - if (!(hcd->state & HC_STATE_RUNNING)) - goto leave; - - imask = reg_read32(hcd->regs, HC_INTERRUPT_REG); - if (unlikely(!imask)) - goto leave; - reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ - - priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); - priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); - - handle_done_ptds(hcd); - - irqret = IRQ_HANDLED; -leave: - spin_unlock(&priv->lock); - - return irqret; -} - -/* - * Workaround for problem described in chip errata 2: - * - * Sometimes interrupts are not generated when ATL (not INT?) completion occurs. - * One solution suggested in the errata is to use SOF interrupts _instead_of_ - * ATL done interrupts (the "instead of" might be important since it seems - * enabling ATL interrupts also causes the chip to sometimes - rarely - "forget" - * to set the PTD's done bit in addition to not generating an interrupt!). - * - * So if we use SOF + ATL interrupts, we sometimes get stale PTDs since their - * done bit is not being set. This is bad - it blocks the endpoint until reboot. - * - * If we use SOF interrupts only, we get latency between ptd completion and the - * actual handling. This is very noticeable in testusb runs which takes several - * minutes longer without ATL interrupts. - * - * A better solution is to run the code below every SLOT_CHECK_PERIOD ms. If it - * finds active ATL slots which are older than SLOT_TIMEOUT ms, it checks the - * slot's ACTIVE and VALID bits. If these are not set, the ptd is considered - * completed and its done map bit is set. - * - * The values of SLOT_TIMEOUT and SLOT_CHECK_PERIOD have been arbitrarily chosen - * not to cause too much lag when this HW bug occurs, while still hopefully - * ensuring that the check does not falsely trigger. - */ -#define SLOT_TIMEOUT 300 -#define SLOT_CHECK_PERIOD 200 -static struct timer_list errata2_timer; - -static void errata2_function(unsigned long data) -{ - struct usb_hcd *hcd = (struct usb_hcd *) data; - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int slot; - struct ptd ptd; - unsigned long spinflags; - - spin_lock_irqsave(&priv->lock, spinflags); - - for (slot = 0; slot < 32; slot++) - if (priv->atl_slots[slot].qh && time_after(jiffies, - priv->atl_slots[slot].timestamp + - SLOT_TIMEOUT * HZ / 1000)) { - ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); - if (!FROM_DW0_VALID(ptd.dw0) && - !FROM_DW3_ACTIVE(ptd.dw3)) - priv->atl_done_map |= 1 << slot; - } - - if (priv->atl_done_map) - handle_done_ptds(hcd); - - spin_unlock_irqrestore(&priv->lock, spinflags); - - errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; - add_timer(&errata2_timer); -} - -static int isp1760_run(struct usb_hcd *hcd) -{ - int retval; - u32 temp; - u32 command; - u32 chipid; - - hcd->uses_new_polling = 1; - - hcd->state = HC_STATE_RUNNING; - - /* Set PTD interrupt AND & OR maps */ - reg_write32(hcd->regs, HC_ATL_IRQ_MASK_AND_REG, 0); - reg_write32(hcd->regs, HC_ATL_IRQ_MASK_OR_REG, 0xffffffff); - reg_write32(hcd->regs, HC_INT_IRQ_MASK_AND_REG, 0); - reg_write32(hcd->regs, HC_INT_IRQ_MASK_OR_REG, 0xffffffff); - reg_write32(hcd->regs, HC_ISO_IRQ_MASK_AND_REG, 0); - reg_write32(hcd->regs, HC_ISO_IRQ_MASK_OR_REG, 0xffffffff); - /* step 23 passed */ - - temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp | HW_GLOBAL_INTR_EN); - - command = reg_read32(hcd->regs, HC_USBCMD); - command &= ~(CMD_LRESET|CMD_RESET); - command |= CMD_RUN; - reg_write32(hcd->regs, HC_USBCMD, command); - - retval = handshake(hcd, HC_USBCMD, CMD_RUN, CMD_RUN, 250 * 1000); - if (retval) - return retval; - - /* - * XXX - * Spec says to write FLAG_CF as last config action, priv code grabs - * the semaphore while doing so. - */ - down_write(&ehci_cf_port_reset_rwsem); - reg_write32(hcd->regs, HC_CONFIGFLAG, FLAG_CF); - - retval = handshake(hcd, HC_CONFIGFLAG, FLAG_CF, FLAG_CF, 250 * 1000); - up_write(&ehci_cf_port_reset_rwsem); - if (retval) - return retval; - - init_timer(&errata2_timer); - errata2_timer.function = errata2_function; - errata2_timer.data = (unsigned long) hcd; - errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; - add_timer(&errata2_timer); - - chipid = reg_read32(hcd->regs, HC_CHIP_ID_REG); - dev_info(hcd->self.controller, "USB ISP %04x HW rev. %d started\n", - chipid & 0xffff, chipid >> 16); - - /* PTD Register Init Part 2, Step 28 */ - - /* Setup registers controlling PTD checking */ - reg_write32(hcd->regs, HC_ATL_PTD_LASTPTD_REG, 0x80000000); - reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); - reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0xffffffff); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff); - reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff); - reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, - ATL_BUF_FILL | INT_BUF_FILL); - - /* GRR this is run-once init(), being done every time the HC starts. - * So long as they're part of class devices, we can't do it init() - * since the class device isn't created that early. - */ - return 0; -} - -static int qtd_fill(struct isp1760_qtd *qtd, void *databuffer, size_t len) -{ - qtd->data_buffer = databuffer; - - if (len > MAX_PAYLOAD_SIZE) - len = MAX_PAYLOAD_SIZE; - qtd->length = len; - - return qtd->length; -} - -static void qtd_list_free(struct list_head *qtd_list) -{ - struct isp1760_qtd *qtd, *qtd_next; - - list_for_each_entry_safe(qtd, qtd_next, qtd_list, qtd_list) { - list_del(&qtd->qtd_list); - qtd_free(qtd); - } -} - -/* - * Packetize urb->transfer_buffer into list of packets of size wMaxPacketSize. - * Also calculate the PID type (SETUP/IN/OUT) for each packet. - */ -#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) -static void packetize_urb(struct usb_hcd *hcd, - struct urb *urb, struct list_head *head, gfp_t flags) -{ - struct isp1760_qtd *qtd; - void *buf; - int len, maxpacketsize; - u8 packet_type; - - /* - * URBs map to sequences of QTDs: one logical transaction - */ - - if (!urb->transfer_buffer && urb->transfer_buffer_length) { - /* XXX This looks like usb storage / SCSI bug */ - dev_err(hcd->self.controller, - "buf is null, dma is %08lx len is %d\n", - (long unsigned)urb->transfer_dma, - urb->transfer_buffer_length); - WARN_ON(1); - } - - if (usb_pipein(urb->pipe)) - packet_type = IN_PID; - else - packet_type = OUT_PID; - - if (usb_pipecontrol(urb->pipe)) { - qtd = qtd_alloc(flags, urb, SETUP_PID); - if (!qtd) - goto cleanup; - qtd_fill(qtd, urb->setup_packet, sizeof(struct usb_ctrlrequest)); - list_add_tail(&qtd->qtd_list, head); - - /* for zero length DATA stages, STATUS is always IN */ - if (urb->transfer_buffer_length == 0) - packet_type = IN_PID; - } - - maxpacketsize = max_packet(usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe))); - - /* - * buffer gets wrapped in one or more qtds; - * last one may be "short" (including zero len) - * and may serve as a control status ack - */ - buf = urb->transfer_buffer; - len = urb->transfer_buffer_length; - - for (;;) { - int this_qtd_len; - - qtd = qtd_alloc(flags, urb, packet_type); - if (!qtd) - goto cleanup; - this_qtd_len = qtd_fill(qtd, buf, len); - list_add_tail(&qtd->qtd_list, head); - - len -= this_qtd_len; - buf += this_qtd_len; - - if (len <= 0) - break; - } - - /* - * control requests may need a terminating data "status" ack; - * bulk ones may need a terminating short packet (zero length). - */ - if (urb->transfer_buffer_length != 0) { - int one_more = 0; - - if (usb_pipecontrol(urb->pipe)) { - one_more = 1; - if (packet_type == IN_PID) - packet_type = OUT_PID; - else - packet_type = IN_PID; - } else if (usb_pipebulk(urb->pipe) - && (urb->transfer_flags & URB_ZERO_PACKET) - && !(urb->transfer_buffer_length % - maxpacketsize)) { - one_more = 1; - } - if (one_more) { - qtd = qtd_alloc(flags, urb, packet_type); - if (!qtd) - goto cleanup; - - /* never any data in such packets */ - qtd_fill(qtd, NULL, 0); - list_add_tail(&qtd->qtd_list, head); - } - } - - return; - -cleanup: - qtd_list_free(head); -} - -static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - struct list_head *ep_queue; - struct isp1760_qh *qh, *qhit; - unsigned long spinflags; - LIST_HEAD(new_qtds); - int retval; - int qh_in_queue; - - switch (usb_pipetype(urb->pipe)) { - case PIPE_CONTROL: - ep_queue = &priv->qh_list[QH_CONTROL]; - break; - case PIPE_BULK: - ep_queue = &priv->qh_list[QH_BULK]; - break; - case PIPE_INTERRUPT: - if (urb->interval < 0) - return -EINVAL; - /* FIXME: Check bandwidth */ - ep_queue = &priv->qh_list[QH_INTERRUPT]; - break; - case PIPE_ISOCHRONOUS: - dev_err(hcd->self.controller, "%s: isochronous USB packets " - "not yet supported\n", - __func__); - return -EPIPE; - default: - dev_err(hcd->self.controller, "%s: unknown pipe type\n", - __func__); - return -EPIPE; - } - - if (usb_pipein(urb->pipe)) - urb->actual_length = 0; - - packetize_urb(hcd, urb, &new_qtds, mem_flags); - if (list_empty(&new_qtds)) - return -ENOMEM; - - retval = 0; - spin_lock_irqsave(&priv->lock, spinflags); - - if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { - retval = -ESHUTDOWN; - qtd_list_free(&new_qtds); - goto out; - } - retval = usb_hcd_link_urb_to_ep(hcd, urb); - if (retval) { - qtd_list_free(&new_qtds); - goto out; - } - - qh = urb->ep->hcpriv; - if (qh) { - qh_in_queue = 0; - list_for_each_entry(qhit, ep_queue, qh_list) { - if (qhit == qh) { - qh_in_queue = 1; - break; - } - } - if (!qh_in_queue) - list_add_tail(&qh->qh_list, ep_queue); - } else { - qh = qh_alloc(GFP_ATOMIC); - if (!qh) { - retval = -ENOMEM; - usb_hcd_unlink_urb_from_ep(hcd, urb); - qtd_list_free(&new_qtds); - goto out; - } - list_add_tail(&qh->qh_list, ep_queue); - urb->ep->hcpriv = qh; - } - - list_splice_tail(&new_qtds, &qh->qtd_list); - schedule_ptds(hcd); - -out: - spin_unlock_irqrestore(&priv->lock, spinflags); - return retval; -} - -static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, - struct isp1760_qh *qh) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int skip_map; - - WARN_ON(qh->slot == -1); - - /* We need to forcefully reclaim the slot since some transfers never - return, e.g. interrupt transfers and NAKed bulk transfers. */ - if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) { - skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); - skip_map |= (1 << qh->slot); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); - priv->atl_slots[qh->slot].qh = NULL; - priv->atl_slots[qh->slot].qtd = NULL; - } else { - skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); - skip_map |= (1 << qh->slot); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); - priv->int_slots[qh->slot].qh = NULL; - priv->int_slots[qh->slot].qtd = NULL; - } - - qh->slot = -1; -} - -/* - * Retire the qtds beginning at 'qtd' and belonging all to the same urb, killing - * any active transfer belonging to the urb in the process. - */ -static void dequeue_urb_from_qtd(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct isp1760_qtd *qtd) -{ - struct urb *urb; - int urb_was_running; - - urb = qtd->urb; - urb_was_running = 0; - list_for_each_entry_from(qtd, &qh->qtd_list, qtd_list) { - if (qtd->urb != urb) - break; - - if (qtd->status >= QTD_XFER_STARTED) - urb_was_running = 1; - if (last_qtd_of_urb(qtd, qh) && - (qtd->status >= QTD_XFER_COMPLETE)) - urb_was_running = 0; - - if (qtd->status == QTD_XFER_STARTED) - kill_transfer(hcd, urb, qh); - qtd->status = QTD_RETIRE; - } - - if ((urb->dev->speed != USB_SPEED_HIGH) && urb_was_running) { - qh->tt_buffer_dirty = 1; - if (usb_hub_clear_tt_buffer(urb)) - /* Clear failed; let's hope things work anyway */ - qh->tt_buffer_dirty = 0; - } -} - -static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, - int status) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - unsigned long spinflags; - struct isp1760_qh *qh; - struct isp1760_qtd *qtd; - int retval = 0; - - spin_lock_irqsave(&priv->lock, spinflags); - retval = usb_hcd_check_unlink_urb(hcd, urb, status); - if (retval) - goto out; - - qh = urb->ep->hcpriv; - if (!qh) { - retval = -EINVAL; - goto out; - } - - list_for_each_entry(qtd, &qh->qtd_list, qtd_list) - if (qtd->urb == urb) { - dequeue_urb_from_qtd(hcd, qh, qtd); - list_move(&qtd->qtd_list, &qh->qtd_list); - break; - } - - urb->status = status; - schedule_ptds(hcd); - -out: - spin_unlock_irqrestore(&priv->lock, spinflags); - return retval; -} - -static void isp1760_endpoint_disable(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - unsigned long spinflags; - struct isp1760_qh *qh, *qh_iter; - int i; - - spin_lock_irqsave(&priv->lock, spinflags); - - qh = ep->hcpriv; - if (!qh) - goto out; - - WARN_ON(!list_empty(&qh->qtd_list)); - - for (i = 0; i < QH_END; i++) - list_for_each_entry(qh_iter, &priv->qh_list[i], qh_list) - if (qh_iter == qh) { - list_del(&qh_iter->qh_list); - i = QH_END; - break; - } - qh_free(qh); - ep->hcpriv = NULL; - - schedule_ptds(hcd); - -out: - spin_unlock_irqrestore(&priv->lock, spinflags); -} - -static int isp1760_hub_status_data(struct usb_hcd *hcd, char *buf) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 temp, status = 0; - u32 mask; - int retval = 1; - unsigned long flags; - - /* if !PM, root hub timers won't get shut down ... */ - if (!HC_IS_RUNNING(hcd->state)) - return 0; - - /* init status to no-changes */ - buf[0] = 0; - mask = PORT_CSC; - - spin_lock_irqsave(&priv->lock, flags); - temp = reg_read32(hcd->regs, HC_PORTSC1); - - if (temp & PORT_OWNER) { - if (temp & PORT_CSC) { - temp &= ~PORT_CSC; - reg_write32(hcd->regs, HC_PORTSC1, temp); - goto done; - } - } - - /* - * Return status information even for ports with OWNER set. - * Otherwise hub_wq wouldn't see the disconnect event when a - * high-speed device is switched over to the companion - * controller by the user. - */ - - if ((temp & mask) != 0 - || ((temp & PORT_RESUME) != 0 - && time_after_eq(jiffies, - priv->reset_done))) { - buf [0] |= 1 << (0 + 1); - status = STS_PCD; - } - /* FIXME autosuspend idle root hubs */ -done: - spin_unlock_irqrestore(&priv->lock, flags); - return status ? retval : 0; -} - -static void isp1760_hub_descriptor(struct isp1760_hcd *priv, - struct usb_hub_descriptor *desc) -{ - int ports = HCS_N_PORTS(priv->hcs_params); - u16 temp; - - desc->bDescriptorType = 0x29; - /* priv 1.0, 2.3.9 says 20ms max */ - desc->bPwrOn2PwrGood = 10; - desc->bHubContrCurrent = 0; - - desc->bNbrPorts = ports; - temp = 1 + (ports / 8); - desc->bDescLength = 7 + 2 * temp; - - /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ - memset(&desc->u.hs.DeviceRemovable[0], 0, temp); - memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - - /* per-port overcurrent reporting */ - temp = 0x0008; - if (HCS_PPC(priv->hcs_params)) - /* per-port power control */ - temp |= 0x0001; - else - /* no power switching */ - temp |= 0x0002; - desc->wHubCharacteristics = cpu_to_le16(temp); -} - -#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) - -static int check_reset_complete(struct usb_hcd *hcd, int index, - int port_status) -{ - if (!(port_status & PORT_CONNECT)) - return port_status; - - /* if reset finished and it's still not enabled -- handoff */ - if (!(port_status & PORT_PE)) { - - dev_info(hcd->self.controller, - "port %d full speed --> companion\n", - index + 1); - - port_status |= PORT_OWNER; - port_status &= ~PORT_RWC_BITS; - reg_write32(hcd->regs, HC_PORTSC1, port_status); - - } else - dev_info(hcd->self.controller, "port %d high speed\n", - index + 1); - - return port_status; -} - -static int isp1760_hub_control(struct usb_hcd *hcd, u16 typeReq, - u16 wValue, u16 wIndex, char *buf, u16 wLength) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int ports = HCS_N_PORTS(priv->hcs_params); - u32 temp, status; - unsigned long flags; - int retval = 0; - unsigned selector; - - /* - * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. - * HCS_INDICATOR may say we can change LEDs to off/amber/green. - * (track current state ourselves) ... blink for diagnostics, - * power, "this is the one", etc. EHCI spec supports this. - */ - - spin_lock_irqsave(&priv->lock, flags); - switch (typeReq) { - case ClearHubFeature: - switch (wValue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* no hub-wide feature/status flags */ - break; - default: - goto error; - } - break; - case ClearPortFeature: - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - temp = reg_read32(hcd->regs, HC_PORTSC1); - - /* - * Even if OWNER is set, so the port is owned by the - * companion controller, hub_wq needs to be able to clear - * the port-change status bits (especially - * USB_PORT_STAT_C_CONNECTION). - */ - - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_PE); - break; - case USB_PORT_FEAT_C_ENABLE: - /* XXX error? */ - break; - case USB_PORT_FEAT_SUSPEND: - if (temp & PORT_RESET) - goto error; - - if (temp & PORT_SUSPEND) { - if ((temp & PORT_PE) == 0) - goto error; - /* resume signaling for 20 msec */ - temp &= ~(PORT_RWC_BITS); - reg_write32(hcd->regs, HC_PORTSC1, - temp | PORT_RESUME); - priv->reset_done = jiffies + - msecs_to_jiffies(20); - } - break; - case USB_PORT_FEAT_C_SUSPEND: - /* we auto-clear this feature */ - break; - case USB_PORT_FEAT_POWER: - if (HCS_PPC(priv->hcs_params)) - reg_write32(hcd->regs, HC_PORTSC1, - temp & ~PORT_POWER); - break; - case USB_PORT_FEAT_C_CONNECTION: - reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_CSC); - break; - case USB_PORT_FEAT_C_OVER_CURRENT: - /* XXX error ?*/ - break; - case USB_PORT_FEAT_C_RESET: - /* GetPortStatus clears reset */ - break; - default: - goto error; - } - reg_read32(hcd->regs, HC_USBCMD); - break; - case GetHubDescriptor: - isp1760_hub_descriptor(priv, (struct usb_hub_descriptor *) - buf); - break; - case GetHubStatus: - /* no hub-wide feature/status flags */ - memset(buf, 0, 4); - break; - case GetPortStatus: - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - status = 0; - temp = reg_read32(hcd->regs, HC_PORTSC1); - - /* wPortChange bits */ - if (temp & PORT_CSC) - status |= USB_PORT_STAT_C_CONNECTION << 16; - - - /* whoever resumes must GetPortStatus to complete it!! */ - if (temp & PORT_RESUME) { - dev_err(hcd->self.controller, "Port resume should be skipped.\n"); - - /* Remote Wakeup received? */ - if (!priv->reset_done) { - /* resume signaling for 20 msec */ - priv->reset_done = jiffies - + msecs_to_jiffies(20); - /* check the port again */ - mod_timer(&hcd->rh_timer, priv->reset_done); - } - - /* resume completed? */ - else if (time_after_eq(jiffies, - priv->reset_done)) { - status |= USB_PORT_STAT_C_SUSPEND << 16; - priv->reset_done = 0; - - /* stop resume signaling */ - temp = reg_read32(hcd->regs, HC_PORTSC1); - reg_write32(hcd->regs, HC_PORTSC1, - temp & ~(PORT_RWC_BITS | PORT_RESUME)); - retval = handshake(hcd, HC_PORTSC1, - PORT_RESUME, 0, 2000 /* 2msec */); - if (retval != 0) { - dev_err(hcd->self.controller, - "port %d resume error %d\n", - wIndex + 1, retval); - goto error; - } - temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); - } - } - - /* whoever resets must GetPortStatus to complete it!! */ - if ((temp & PORT_RESET) - && time_after_eq(jiffies, - priv->reset_done)) { - status |= USB_PORT_STAT_C_RESET << 16; - priv->reset_done = 0; - - /* force reset to complete */ - reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_RESET); - /* REVISIT: some hardware needs 550+ usec to clear - * this bit; seems too long to spin routinely... - */ - retval = handshake(hcd, HC_PORTSC1, - PORT_RESET, 0, 750); - if (retval != 0) { - dev_err(hcd->self.controller, "port %d reset error %d\n", - wIndex + 1, retval); - goto error; - } - - /* see what we found out */ - temp = check_reset_complete(hcd, wIndex, - reg_read32(hcd->regs, HC_PORTSC1)); - } - /* - * Even if OWNER is set, there's no harm letting hub_wq - * see the wPortStatus values (they should all be 0 except - * for PORT_POWER anyway). - */ - - if (temp & PORT_OWNER) - dev_err(hcd->self.controller, "PORT_OWNER is set\n"); - - if (temp & PORT_CONNECT) { - status |= USB_PORT_STAT_CONNECTION; - /* status may be from integrated TT */ - status |= USB_PORT_STAT_HIGH_SPEED; - } - if (temp & PORT_PE) - status |= USB_PORT_STAT_ENABLE; - if (temp & (PORT_SUSPEND|PORT_RESUME)) - status |= USB_PORT_STAT_SUSPEND; - if (temp & PORT_RESET) - status |= USB_PORT_STAT_RESET; - if (temp & PORT_POWER) - status |= USB_PORT_STAT_POWER; - - put_unaligned(cpu_to_le32(status), (__le32 *) buf); - break; - case SetHubFeature: - switch (wValue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* no hub-wide feature/status flags */ - break; - default: - goto error; - } - break; - case SetPortFeature: - selector = wIndex >> 8; - wIndex &= 0xff; - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - temp = reg_read32(hcd->regs, HC_PORTSC1); - if (temp & PORT_OWNER) - break; - -/* temp &= ~PORT_RWC_BITS; */ - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_PE); - break; - - case USB_PORT_FEAT_SUSPEND: - if ((temp & PORT_PE) == 0 - || (temp & PORT_RESET) != 0) - goto error; - - reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_SUSPEND); - break; - case USB_PORT_FEAT_POWER: - if (HCS_PPC(priv->hcs_params)) - reg_write32(hcd->regs, HC_PORTSC1, - temp | PORT_POWER); - break; - case USB_PORT_FEAT_RESET: - if (temp & PORT_RESUME) - goto error; - /* line status bits may report this as low speed, - * which can be fine if this root hub has a - * transaction translator built in. - */ - if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT - && PORT_USB11(temp)) { - temp |= PORT_OWNER; - } else { - temp |= PORT_RESET; - temp &= ~PORT_PE; - - /* - * caller must wait, then call GetPortStatus - * usb 2.0 spec says 50 ms resets on root - */ - priv->reset_done = jiffies + - msecs_to_jiffies(50); - } - reg_write32(hcd->regs, HC_PORTSC1, temp); - break; - default: - goto error; - } - reg_read32(hcd->regs, HC_USBCMD); - break; - - default: -error: - /* "stall" on error */ - retval = -EPIPE; - } - spin_unlock_irqrestore(&priv->lock, flags); - return retval; -} - -static int isp1760_get_frame(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 fr; - - fr = reg_read32(hcd->regs, HC_FRINDEX); - return (fr >> 3) % priv->periodic_size; -} - -static void isp1760_stop(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 temp; - - del_timer(&errata2_timer); - - isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, - NULL, 0); - mdelay(20); - - spin_lock_irq(&priv->lock); - ehci_reset(hcd); - /* Disable IRQ */ - temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); - spin_unlock_irq(&priv->lock); - - reg_write32(hcd->regs, HC_CONFIGFLAG, 0); -} - -static void isp1760_shutdown(struct usb_hcd *hcd) -{ - u32 command, temp; - - isp1760_stop(hcd); - temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); - - command = reg_read32(hcd->regs, HC_USBCMD); - command &= ~CMD_RUN; - reg_write32(hcd->regs, HC_USBCMD, command); -} - -static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - struct isp1760_qh *qh = ep->hcpriv; - unsigned long spinflags; - - if (!qh) - return; - - spin_lock_irqsave(&priv->lock, spinflags); - qh->tt_buffer_dirty = 0; - schedule_ptds(hcd); - spin_unlock_irqrestore(&priv->lock, spinflags); -} - - -static const struct hc_driver isp1760_hc_driver = { - .description = "isp1760-hcd", - .product_desc = "NXP ISP1760 USB Host Controller", - .hcd_priv_size = sizeof(struct isp1760_hcd *), - .irq = isp1760_irq, - .flags = HCD_MEMORY | HCD_USB2, - .reset = isp1760_hc_setup, - .start = isp1760_run, - .stop = isp1760_stop, - .shutdown = isp1760_shutdown, - .urb_enqueue = isp1760_urb_enqueue, - .urb_dequeue = isp1760_urb_dequeue, - .endpoint_disable = isp1760_endpoint_disable, - .get_frame_number = isp1760_get_frame, - .hub_status_data = isp1760_hub_status_data, - .hub_control = isp1760_hub_control, - .clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete, -}; - -int __init isp1760_init_kmem_once(void) -{ - urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", - sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | - SLAB_MEM_SPREAD, NULL); - - if (!urb_listitem_cachep) - return -ENOMEM; - - qtd_cachep = kmem_cache_create("isp1760_qtd", - sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY | - SLAB_MEM_SPREAD, NULL); - - if (!qtd_cachep) - return -ENOMEM; - - qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), - 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL); - - if (!qh_cachep) { - kmem_cache_destroy(qtd_cachep); - return -ENOMEM; - } - - return 0; -} - -void isp1760_deinit_kmem_cache(void) -{ - kmem_cache_destroy(qtd_cachep); - kmem_cache_destroy(qh_cachep); - kmem_cache_destroy(urb_listitem_cachep); -} - -int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, - struct resource *mem, int irq, unsigned long irqflags, - struct device *dev) -{ - struct usb_hcd *hcd; - int ret; - - hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); - if (!hcd) - return -ENOMEM; - - *(struct isp1760_hcd **)hcd->hcd_priv = priv; - - priv->hcd = hcd; - - init_memory(priv); - - hcd->irq = irq; - hcd->regs = regs; - hcd->rsrc_start = mem->start; - hcd->rsrc_len = resource_size(mem); - - ret = usb_add_hcd(hcd, irq, irqflags); - if (ret) - goto error; - - device_wakeup_enable(hcd->self.controller); - - return 0; - -error: - usb_put_hcd(hcd); - return ret; -} - -void isp1760_hcd_unregister(struct isp1760_hcd *priv) -{ - usb_remove_hcd(priv->hcd); - usb_put_hcd(priv->hcd); -} diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h deleted file mode 100644 index df7ea36..0000000 --- a/drivers/usb/host/isp1760-hcd.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef _ISP1760_HCD_H_ -#define _ISP1760_HCD_H_ - -#include - -struct isp1760_qh; -struct isp1760_qtd; -struct resource; -struct usb_hcd; - -/* - * 60kb divided in: - * - 32 blocks @ 256 bytes - * - 20 blocks @ 1024 bytes - * - 4 blocks @ 8192 bytes - */ - -#define BLOCK_1_NUM 32 -#define BLOCK_2_NUM 20 -#define BLOCK_3_NUM 4 - -#define BLOCK_1_SIZE 256 -#define BLOCK_2_SIZE 1024 -#define BLOCK_3_SIZE 8192 -#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) -#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE -#define PAYLOAD_AREA_SIZE 0xf000 - -struct isp1760_slotinfo { - struct isp1760_qh *qh; - struct isp1760_qtd *qtd; - unsigned long timestamp; -}; - -/* chip memory management */ -struct isp1760_memory_chunk { - unsigned int start; - unsigned int size; - unsigned int free; -}; - -enum isp1760_queue_head_types { - QH_CONTROL, - QH_BULK, - QH_INTERRUPT, - QH_END -}; - -struct isp1760_hcd { - struct usb_hcd *hcd; - - u32 hcs_params; - spinlock_t lock; - struct isp1760_slotinfo atl_slots[32]; - int atl_done_map; - struct isp1760_slotinfo int_slots[32]; - int int_done_map; - struct isp1760_memory_chunk memory_pool[BLOCKS]; - struct list_head qh_list[QH_END]; - - /* periodic schedule support */ -#define DEFAULT_I_TDPS 1024 - unsigned periodic_size; - unsigned i_thresh; - unsigned long reset_done; - unsigned long next_statechange; -}; - -int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, - struct resource *mem, int irq, unsigned long irqflags, - struct device *dev); -void isp1760_hcd_unregister(struct isp1760_hcd *priv); - -int isp1760_init_kmem_once(void); -void isp1760_deinit_kmem_cache(void); - -#endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c deleted file mode 100644 index c2a94c9..0000000 --- a/drivers/usb/host/isp1760-if.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Glue code for the ISP1760 driver and bus - * Currently there is support for - * - OpenFirmware - * - PCI - * - PDEV (generic platform device centralized driver model) - * - * (c) 2007 Sebastian Siewior - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isp1760-core.h" -#include "isp1760-regs.h" - -#ifdef CONFIG_PCI -#include -#endif - -#ifdef CONFIG_PCI -static int isp1761_pci_init(struct pci_dev *dev) -{ - resource_size_t mem_start; - resource_size_t mem_length; - u8 __iomem *iobase; - u8 latency, limit; - int retry_count; - u32 reg_data; - - /* Grab the PLX PCI shared memory of the ISP 1761 we need */ - mem_start = pci_resource_start(dev, 3); - mem_length = pci_resource_len(dev, 3); - if (mem_length < 0xffff) { - printk(KERN_ERR "memory length for this resource is wrong\n"); - return -ENOMEM; - } - - if (!request_mem_region(mem_start, mem_length, "ISP-PCI")) { - printk(KERN_ERR "host controller already in use\n"); - return -EBUSY; - } - - /* map available memory */ - iobase = ioremap_nocache(mem_start, mem_length); - if (!iobase) { - printk(KERN_ERR "Error ioremap failed\n"); - release_mem_region(mem_start, mem_length); - return -ENOMEM; - } - - /* bad pci latencies can contribute to overruns */ - pci_read_config_byte(dev, PCI_LATENCY_TIMER, &latency); - if (latency) { - pci_read_config_byte(dev, PCI_MAX_LAT, &limit); - if (limit && limit < latency) - pci_write_config_byte(dev, PCI_LATENCY_TIMER, limit); - } - - /* Try to check whether we can access Scratch Register of - * Host Controller or not. The initial PCI access is retried until - * local init for the PCI bridge is completed - */ - retry_count = 20; - reg_data = 0; - while ((reg_data != 0xFACE) && retry_count) { - /*by default host is in 16bit mode, so - * io operations at this stage must be 16 bit - * */ - writel(0xface, iobase + HC_SCRATCH_REG); - udelay(100); - reg_data = readl(iobase + HC_SCRATCH_REG) & 0x0000ffff; - retry_count--; - } - - iounmap(iobase); - release_mem_region(mem_start, mem_length); - - /* Host Controller presence is detected by writing to scratch register - * and reading back and checking the contents are same or not - */ - if (reg_data != 0xFACE) { - dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); - return -ENOMEM; - } - - /* Grab the PLX PCI mem maped port start address we need */ - mem_start = pci_resource_start(dev, 0); - mem_length = pci_resource_len(dev, 0); - - if (!request_mem_region(mem_start, mem_length, "ISP1761 IO MEM")) { - printk(KERN_ERR "request region #1\n"); - return -EBUSY; - } - - iobase = ioremap_nocache(mem_start, mem_length); - if (!iobase) { - printk(KERN_ERR "ioremap #1\n"); - release_mem_region(mem_start, mem_length); - return -ENOMEM; - } - - /* configure PLX PCI chip to pass interrupts */ -#define PLX_INT_CSR_REG 0x68 - reg_data = readl(iobase + PLX_INT_CSR_REG); - reg_data |= 0x900; - writel(reg_data, iobase + PLX_INT_CSR_REG); - - /* done with PLX IO access */ - iounmap(iobase); - release_mem_region(mem_start, mem_length); - - return 0; -} - -static int isp1761_pci_probe(struct pci_dev *dev, - const struct pci_device_id *id) -{ - unsigned int devflags = 0; - int ret; - - if (usb_disabled()) - return -ENODEV; - - if (!dev->irq) - return -ENODEV; - - if (pci_enable_device(dev) < 0) - return -ENODEV; - - ret = isp1761_pci_init(dev); - if (ret < 0) - goto error; - - pci_set_master(dev); - - dev->dev.dma_mask = NULL; - ret = isp1760_register(&dev->resource[3], dev->irq, 0, &dev->dev, - devflags); - if (ret < 0) - goto error; - - return 0; - -error: - pci_disable_device(dev); - return ret; -} - -static void isp1761_pci_remove(struct pci_dev *dev) -{ - isp1760_unregister(&dev->dev); - - pci_disable_device(dev); -} - -static void isp1761_pci_shutdown(struct pci_dev *dev) -{ - printk(KERN_ERR "ips1761_pci_shutdown\n"); -} - -static const struct pci_device_id isp1760_plx [] = { - { - .class = PCI_CLASS_BRIDGE_OTHER << 8, - .class_mask = ~0, - .vendor = PCI_VENDOR_ID_PLX, - .device = 0x5406, - .subvendor = PCI_VENDOR_ID_PLX, - .subdevice = 0x9054, - }, - { } -}; -MODULE_DEVICE_TABLE(pci, isp1760_plx); - -static struct pci_driver isp1761_pci_driver = { - .name = "isp1760", - .id_table = isp1760_plx, - .probe = isp1761_pci_probe, - .remove = isp1761_pci_remove, - .shutdown = isp1761_pci_shutdown, -}; -#endif - -static int isp1760_plat_probe(struct platform_device *pdev) -{ - unsigned long irqflags; - unsigned int devflags = 0; - struct resource *mem_res; - struct resource *irq_res; - int ret; - - mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!irq_res) { - pr_warning("isp1760: IRQ resource not available\n"); - return -ENODEV; - } - irqflags = irq_res->flags & IRQF_TRIGGER_MASK; - - if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { - struct device_node *dp = pdev->dev.of_node; - u32 bus_width = 0; - - if (of_device_is_compatible(dp, "nxp,usb-isp1761")) - devflags |= ISP1760_FLAG_ISP1761; - - /* Some systems wire up only 16 of the 32 data lines */ - of_property_read_u32(dp, "bus-width", &bus_width); - if (bus_width == 16) - devflags |= ISP1760_FLAG_BUS_WIDTH_16; - - if (of_property_read_bool(dp, "port1-otg")) - devflags |= ISP1760_FLAG_OTG_EN; - - if (of_property_read_bool(dp, "analog-oc")) - devflags |= ISP1760_FLAG_ANALOG_OC; - - if (of_property_read_bool(dp, "dack-polarity")) - devflags |= ISP1760_FLAG_DACK_POL_HIGH; - - if (of_property_read_bool(dp, "dreq-polarity")) - devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - } else if (dev_get_platdata(&pdev->dev)) { - struct isp1760_platform_data *pdata = - dev_get_platdata(&pdev->dev); - - if (pdata->is_isp1761) - devflags |= ISP1760_FLAG_ISP1761; - if (pdata->bus_width_16) - devflags |= ISP1760_FLAG_BUS_WIDTH_16; - if (pdata->port1_otg) - devflags |= ISP1760_FLAG_OTG_EN; - if (pdata->analog_oc) - devflags |= ISP1760_FLAG_ANALOG_OC; - if (pdata->dack_polarity_high) - devflags |= ISP1760_FLAG_DACK_POL_HIGH; - if (pdata->dreq_polarity_high) - devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - } - - ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, - devflags); - if (ret < 0) - return ret; - - pr_info("ISP1760 USB device initialised\n"); - return 0; -} - -static int isp1760_plat_remove(struct platform_device *pdev) -{ - isp1760_unregister(&pdev->dev); - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id isp1760_of_match[] = { - { .compatible = "nxp,usb-isp1760", }, - { .compatible = "nxp,usb-isp1761", }, - { }, -}; -MODULE_DEVICE_TABLE(of, isp1760_of_match); -#endif - -static struct platform_driver isp1760_plat_driver = { - .probe = isp1760_plat_probe, - .remove = isp1760_plat_remove, - .driver = { - .name = "isp1760", - .of_match_table = of_match_ptr(isp1760_of_match), - }, -}; - -static int __init isp1760_init(void) -{ - int ret, any_ret = -ENODEV; - - isp1760_init_kmem_once(); - - ret = platform_driver_register(&isp1760_plat_driver); - if (!ret) - any_ret = 0; -#ifdef CONFIG_PCI - ret = pci_register_driver(&isp1761_pci_driver); - if (!ret) - any_ret = 0; -#endif - - if (any_ret) - isp1760_deinit_kmem_cache(); - return any_ret; -} -module_init(isp1760_init); - -static void __exit isp1760_exit(void) -{ - platform_driver_unregister(&isp1760_plat_driver); -#ifdef CONFIG_PCI - pci_unregister_driver(&isp1761_pci_driver); -#endif - isp1760_deinit_kmem_cache(); -} -module_exit(isp1760_exit); diff --git a/drivers/usb/host/isp1760-regs.h b/drivers/usb/host/isp1760-regs.h deleted file mode 100644 index b67095c..0000000 --- a/drivers/usb/host/isp1760-regs.h +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * Copyright 2014 Laurent Pinchart - * Copyright 2007 Sebastian Siewior - * - * Contacts: - * Sebastian Siewior - * Laurent Pinchart - * - * 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 _ISP1760_REGS_H_ -#define _ISP1760_REGS_H_ - -/* ----------------------------------------------------------------------------- - * Host Controller - */ - -/* EHCI capability registers */ -#define HC_CAPLENGTH 0x000 -#define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ -#define HC_VERSION(p) (((p) >> 16) & 0xffff) /* bits 31:16 */ - -#define HC_HCSPARAMS 0x004 -#define HCS_INDICATOR(p) ((p) & (1 << 16)) /* true: has port indicators */ -#define HCS_PPC(p) ((p) & (1 << 4)) /* true: port power control */ -#define HCS_N_PORTS(p) (((p) >> 0) & 0xf) /* bits 3:0, ports on HC */ - -#define HC_HCCPARAMS 0x008 -#define HCC_ISOC_CACHE(p) ((p) & (1 << 7)) /* true: can cache isoc frame */ -#define HCC_ISOC_THRES(p) (((p) >> 4) & 0x7) /* bits 6:4, uframes cached */ - -/* EHCI operational registers */ -#define HC_USBCMD 0x020 -#define CMD_LRESET (1 << 7) /* partial reset (no ports, etc) */ -#define CMD_RESET (1 << 1) /* reset HC not bus */ -#define CMD_RUN (1 << 0) /* start/stop HC */ - -#define HC_USBSTS 0x024 -#define STS_PCD (1 << 2) /* port change detect */ - -#define HC_FRINDEX 0x02c - -#define HC_CONFIGFLAG 0x060 -#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */ - -#define HC_PORTSC1 0x064 -#define PORT_OWNER (1 << 13) /* true: companion hc owns this port */ -#define PORT_POWER (1 << 12) /* true: has power (see PPC) */ -#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ -#define PORT_RESET (1 << 8) /* reset port */ -#define PORT_SUSPEND (1 << 7) /* suspend port */ -#define PORT_RESUME (1 << 6) /* resume it */ -#define PORT_PE (1 << 2) /* port enable */ -#define PORT_CSC (1 << 1) /* connect status change */ -#define PORT_CONNECT (1 << 0) /* device connected */ -#define PORT_RWC_BITS (PORT_CSC) - -#define HC_ISO_PTD_DONEMAP_REG 0x130 -#define HC_ISO_PTD_SKIPMAP_REG 0x134 -#define HC_ISO_PTD_LASTPTD_REG 0x138 -#define HC_INT_PTD_DONEMAP_REG 0x140 -#define HC_INT_PTD_SKIPMAP_REG 0x144 -#define HC_INT_PTD_LASTPTD_REG 0x148 -#define HC_ATL_PTD_DONEMAP_REG 0x150 -#define HC_ATL_PTD_SKIPMAP_REG 0x154 -#define HC_ATL_PTD_LASTPTD_REG 0x158 - -/* Configuration Register */ -#define HC_HW_MODE_CTRL 0x300 -#define ALL_ATX_RESET (1 << 31) -#define HW_ANA_DIGI_OC (1 << 15) -#define HW_DEV_DMA (1 << 11) -#define HW_COMN_IRQ (1 << 10) -#define HW_COMN_DMA (1 << 9) -#define HW_DATA_BUS_32BIT (1 << 8) -#define HW_DACK_POL_HIGH (1 << 6) -#define HW_DREQ_POL_HIGH (1 << 5) -#define HW_INTR_HIGH_ACT (1 << 2) -#define HW_INTR_EDGE_TRIG (1 << 1) -#define HW_GLOBAL_INTR_EN (1 << 0) - -#define HC_CHIP_ID_REG 0x304 -#define HC_SCRATCH_REG 0x308 - -#define HC_RESET_REG 0x30c -#define SW_RESET_RESET_HC (1 << 1) -#define SW_RESET_RESET_ALL (1 << 0) - -#define HC_BUFFER_STATUS_REG 0x334 -#define ISO_BUF_FILL (1 << 2) -#define INT_BUF_FILL (1 << 1) -#define ATL_BUF_FILL (1 << 0) - -#define HC_MEMORY_REG 0x33c -#define ISP_BANK(x) ((x) << 16) - -#define HC_PORT1_CTRL 0x374 -#define PORT1_POWER (3 << 3) -#define PORT1_INIT1 (1 << 7) -#define PORT1_INIT2 (1 << 23) -#define HW_OTG_CTRL_SET 0x374 -#define HW_OTG_CTRL_CLR 0x376 -#define HW_OTG_DISABLE (1 << 10) -#define HW_OTG_SE0_EN (1 << 9) -#define HW_BDIS_ACON_EN (1 << 8) -#define HW_SW_SEL_HC_DC (1 << 7) -#define HW_VBUS_CHRG (1 << 6) -#define HW_VBUS_DISCHRG (1 << 5) -#define HW_VBUS_DRV (1 << 4) -#define HW_SEL_CP_EXT (1 << 3) -#define HW_DM_PULLDOWN (1 << 2) -#define HW_DP_PULLDOWN (1 << 1) -#define HW_DP_PULLUP (1 << 0) - -/* Interrupt Register */ -#define HC_INTERRUPT_REG 0x310 - -#define HC_INTERRUPT_ENABLE 0x314 -#define HC_ISO_INT (1 << 9) -#define HC_ATL_INT (1 << 8) -#define HC_INTL_INT (1 << 7) -#define HC_EOT_INT (1 << 3) -#define HC_SOT_INT (1 << 1) -#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) - -#define HC_ISO_IRQ_MASK_OR_REG 0x318 -#define HC_INT_IRQ_MASK_OR_REG 0x31c -#define HC_ATL_IRQ_MASK_OR_REG 0x320 -#define HC_ISO_IRQ_MASK_AND_REG 0x324 -#define HC_INT_IRQ_MASK_AND_REG 0x328 -#define HC_ATL_IRQ_MASK_AND_REG 0x32c - -/* ----------------------------------------------------------------------------- - * Peripheral Controller - */ - -/* Initialization Registers */ -#define DC_ADDRESS 0x0200 -#define DC_DEVEN (1 << 7) - -#define DC_MODE 0x020c -#define DC_DMACLKON (1 << 9) -#define DC_VBUSSTAT (1 << 8) -#define DC_CLKAON (1 << 7) -#define DC_SNDRSU (1 << 6) -#define DC_GOSUSP (1 << 5) -#define DC_SFRESET (1 << 4) -#define DC_GLINTENA (1 << 3) -#define DC_WKUPCS (1 << 2) - -#define DC_INTCONF 0x0210 -#define DC_CDBGMOD_ACK_NAK (0 << 6) -#define DC_CDBGMOD_ACK (1 << 6) -#define DC_CDBGMOD_ACK_1NAK (2 << 6) -#define DC_DDBGMODIN_ACK_NAK (0 << 4) -#define DC_DDBGMODIN_ACK (1 << 4) -#define DC_DDBGMODIN_ACK_1NAK (2 << 4) -#define DC_DDBGMODOUT_ACK_NYET_NAK (0 << 2) -#define DC_DDBGMODOUT_ACK_NYET (1 << 2) -#define DC_DDBGMODOUT_ACK_NYET_1NAK (2 << 2) -#define DC_INTLVL (1 << 1) -#define DC_INTPOL (1 << 0) - -#define DC_DEBUG 0x0212 -#define DC_INTENABLE 0x0214 -#define DC_IEPTX(n) (1 << (11 + 2 * (n))) -#define DC_IEPRX(n) (1 << (10 + 2 * (n))) -#define DC_IEPRXTX(n) (3 << (10 + 2 * (n))) -#define DC_IEP0SETUP (1 << 8) -#define DC_IEVBUS (1 << 7) -#define DC_IEDMA (1 << 6) -#define DC_IEHS_STA (1 << 5) -#define DC_IERESM (1 << 4) -#define DC_IESUSP (1 << 3) -#define DC_IEPSOF (1 << 2) -#define DC_IESOF (1 << 1) -#define DC_IEBRST (1 << 0) - -/* Data Flow Registers */ -#define DC_EPINDEX 0x022c -#define DC_EP0SETUP (1 << 5) -#define DC_ENDPIDX(n) ((n) << 1) -#define DC_EPDIR (1 << 0) - -#define DC_CTRLFUNC 0x0228 -#define DC_CLBUF (1 << 4) -#define DC_VENDP (1 << 3) -#define DC_DSEN (1 << 2) -#define DC_STATUS (1 << 1) -#define DC_STALL (1 << 0) - -#define DC_DATAPORT 0x0220 -#define DC_BUFLEN 0x021c -#define DC_DATACOUNT_MASK 0xffff -#define DC_BUFSTAT 0x021e -#define DC_EPMAXPKTSZ 0x0204 - -#define DC_EPTYPE 0x0208 -#define DC_NOEMPKT (1 << 4) -#define DC_EPENABLE (1 << 3) -#define DC_DBLBUF (1 << 2) -#define DC_ENDPTYP_ISOC (1 << 0) -#define DC_ENDPTYP_BULK (2 << 0) -#define DC_ENDPTYP_INTERRUPT (3 << 0) - -/* DMA Registers */ -#define DC_DMACMD 0x0230 -#define DC_DMATXCOUNT 0x0234 -#define DC_DMACONF 0x0238 -#define DC_DMAHW 0x023c -#define DC_DMAINTREASON 0x0250 -#define DC_DMAINTEN 0x0254 -#define DC_DMAEP 0x0258 -#define DC_DMABURSTCOUNT 0x0264 - -/* General Registers */ -#define DC_INTERRUPT 0x0218 -#define DC_CHIPID 0x0270 -#define DC_FRAMENUM 0x0274 -#define DC_SCRATCH 0x0278 -#define DC_UNLOCKDEV 0x027c -#define DC_INTPULSEWIDTH 0x0280 -#define DC_TESTMODE 0x0284 - -#endif diff --git a/drivers/usb/host/isp1760-udc.c b/drivers/usb/host/isp1760-udc.c deleted file mode 100644 index 6bfda30..0000000 --- a/drivers/usb/host/isp1760-udc.c +++ /dev/null @@ -1,1495 +0,0 @@ -/* - * Driver for the NXP ISP1761 device controller - * - * Copyright 2014 Ideas on Board Oy - * - * Contacts: - * Laurent Pinchart - * - * 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 -#include - -#include "isp1760-core.h" -#include "isp1760-regs.h" -#include "isp1760-udc.h" - -#define ISP1760_VBUS_POLL_INTERVAL msecs_to_jiffies(500) - -struct isp1760_request { - struct usb_request req; - struct list_head queue; - struct isp1760_ep *ep; - unsigned int packet_size; -}; - -static inline struct isp1760_udc *gadget_to_udc(struct usb_gadget *gadget) -{ - return container_of(gadget, struct isp1760_udc, gadget); -} - -static inline struct isp1760_ep *ep_to_udc_ep(struct usb_ep *ep) -{ - return container_of(ep, struct isp1760_ep, ep); -} - -static inline struct isp1760_request *req_to_udc_req(struct usb_request *req) -{ - return container_of(req, struct isp1760_request, req); -} - -static inline u32 isp1760_udc_read(struct isp1760_udc *udc, u16 reg) -{ - return isp1760_read32(udc->regs, reg); -} - -static inline void isp1760_udc_write(struct isp1760_udc *udc, u16 reg, u32 val) -{ - isp1760_write32(udc->regs, reg, val); -} - -/* ----------------------------------------------------------------------------- - * Endpoint Management - */ - -static struct isp1760_ep *isp1760_udc_find_ep(struct isp1760_udc *udc, - u16 index) -{ - unsigned int i; - - if (index == 0) - return &udc->ep[0]; - - for (i = 1; i < ARRAY_SIZE(udc->ep); ++i) { - if (udc->ep[i].addr == index) - return udc->ep[i].desc ? &udc->ep[i] : NULL; - } - - return NULL; -} - -static void __isp1760_udc_select_ep(struct isp1760_ep *ep, int dir) -{ - isp1760_udc_write(ep->udc, DC_EPINDEX, - DC_ENDPIDX(ep->addr & USB_ENDPOINT_NUMBER_MASK) | - (dir == USB_DIR_IN ? DC_EPDIR : 0)); -} - -/** - * isp1760_udc_select_ep - Select an endpoint for register access - * @ep: The endpoint - * - * The ISP1761 endpoint registers are banked. This function selects the target - * endpoint for banked register access. The selection remains valid until the - * next call to this function, the next direct access to the EPINDEX register - * or the next reset, whichever comes first. - * - * Called with the UDC spinlock held. - */ -static void isp1760_udc_select_ep(struct isp1760_ep *ep) -{ - __isp1760_udc_select_ep(ep, ep->addr & USB_ENDPOINT_DIR_MASK); -} - -/* Called with the UDC spinlock held. */ -static void isp1760_udc_ctrl_send_status(struct isp1760_ep *ep, int dir) -{ - struct isp1760_udc *udc = ep->udc; - - /* - * Proceed to the status stage. The status stage data packet flows in - * the direction opposite to the data stage data packets, we thus need - * to select the OUT/IN endpoint for IN/OUT transfers. - */ - isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | - (dir == USB_DIR_IN ? 0 : DC_EPDIR)); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_STATUS); - - /* - * The hardware will terminate the request automatically and go back to - * the setup stage without notifying us. - */ - udc->ep0_state = ISP1760_CTRL_SETUP; -} - -/* Called without the UDC spinlock held. */ -static void isp1760_udc_request_complete(struct isp1760_ep *ep, - struct isp1760_request *req, - int status) -{ - struct isp1760_udc *udc = ep->udc; - unsigned long flags; - - dev_dbg(ep->udc->isp->dev, "completing request %p with status %d\n", - req, status); - - req->ep = NULL; - req->req.status = status; - req->req.complete(&ep->ep, &req->req); - - spin_lock_irqsave(&udc->lock, flags); - - /* - * When completing control OUT requests, move to the status stage after - * calling the request complete callback. This gives the gadget an - * opportunity to stall the control transfer if needed. - */ - if (status == 0 && ep->addr == 0 && udc->ep0_dir == USB_DIR_OUT) - isp1760_udc_ctrl_send_status(ep, USB_DIR_OUT); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -static void isp1760_udc_ctrl_send_stall(struct isp1760_ep *ep) -{ - struct isp1760_udc *udc = ep->udc; - unsigned long flags; - - dev_dbg(ep->udc->isp->dev, "%s(ep%02x)\n", __func__, ep->addr); - - spin_lock_irqsave(&udc->lock, flags); - - /* Stall both the IN and OUT endpoints. */ - __isp1760_udc_select_ep(ep, USB_DIR_OUT); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); - __isp1760_udc_select_ep(ep, USB_DIR_IN); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); - - /* A protocol stall completes the control transaction. */ - udc->ep0_state = ISP1760_CTRL_SETUP; - - spin_unlock_irqrestore(&udc->lock, flags); -} - -/* ----------------------------------------------------------------------------- - * Data Endpoints - */ - -/* Called with the UDC spinlock held. */ -static bool isp1760_udc_receive(struct isp1760_ep *ep, - struct isp1760_request *req) -{ - struct isp1760_udc *udc = ep->udc; - unsigned int len; - u32 *buf; - int i; - - isp1760_udc_select_ep(ep); - len = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; - - dev_dbg(udc->isp->dev, "%s: received %u bytes (%u/%u done)\n", - __func__, len, req->req.actual, req->req.length); - - len = min(len, req->req.length - req->req.actual); - - if (!len) { - /* - * There's no data to be read from the FIFO, acknowledge the RX - * interrupt by clearing the buffer. - * - * TODO: What if another packet arrives in the meantime ? The - * datasheet doesn't clearly document how this should be - * handled. - */ - isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); - return false; - } - - buf = req->req.buf + req->req.actual; - - /* - * Make sure not to read more than one extra byte, otherwise data from - * the next packet might be removed from the FIFO. - */ - for (i = len; i > 2; i -= 4, ++buf) - *buf = le32_to_cpu(isp1760_udc_read(udc, DC_DATAPORT)); - if (i > 0) - *(u16 *)buf = le16_to_cpu(readw(udc->regs + DC_DATAPORT)); - - req->req.actual += len; - - /* - * TODO: The short_not_ok flag isn't supported yet, but isn't used by - * any gadget driver either. - */ - - dev_dbg(udc->isp->dev, - "%s: req %p actual/length %u/%u maxpacket %u packet size %u\n", - __func__, req, req->req.actual, req->req.length, ep->maxpacket, - len); - - ep->rx_pending = false; - - /* - * Complete the request if all data has been received or if a short - * packet has been received. - */ - if (req->req.actual == req->req.length || len < ep->maxpacket) { - list_del(&req->queue); - return true; - } - - return false; -} - -static void isp1760_udc_transmit(struct isp1760_ep *ep, - struct isp1760_request *req) -{ - struct isp1760_udc *udc = ep->udc; - u32 *buf = req->req.buf + req->req.actual; - int i; - - req->packet_size = min(req->req.length - req->req.actual, - ep->maxpacket); - - dev_dbg(udc->isp->dev, "%s: transferring %u bytes (%u/%u done)\n", - __func__, req->packet_size, req->req.actual, - req->req.length); - - __isp1760_udc_select_ep(ep, USB_DIR_IN); - - if (req->packet_size) - isp1760_udc_write(udc, DC_BUFLEN, req->packet_size); - - /* - * Make sure not to write more than one extra byte, otherwise extra data - * will stay in the FIFO and will be transmitted during the next control - * request. The endpoint control CLBUF bit is supposed to allow flushing - * the FIFO for this kind of conditions, but doesn't seem to work. - */ - for (i = req->packet_size; i > 2; i -= 4, ++buf) - isp1760_udc_write(udc, DC_DATAPORT, cpu_to_le32(*buf)); - if (i > 0) - writew(cpu_to_le16(*(u16 *)buf), udc->regs + DC_DATAPORT); - - if (ep->addr == 0) - isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); - if (!req->packet_size) - isp1760_udc_write(udc, DC_CTRLFUNC, DC_VENDP); -} - -static void isp1760_ep_rx_ready(struct isp1760_ep *ep) -{ - struct isp1760_udc *udc = ep->udc; - struct isp1760_request *req; - bool complete; - - spin_lock(&udc->lock); - - if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_OUT) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: invalid ep0 state %u\n", __func__, - udc->ep0_state); - return; - } - - if (ep->addr != 0 && !ep->desc) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, - ep->addr); - return; - } - - if (list_empty(&ep->queue)) { - ep->rx_pending = true; - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: ep%02x (%p) has no request queued\n", - __func__, ep->addr, ep); - return; - } - - req = list_first_entry(&ep->queue, struct isp1760_request, - queue); - complete = isp1760_udc_receive(ep, req); - - spin_unlock(&udc->lock); - - if (complete) - isp1760_udc_request_complete(ep, req, 0); -} - -static void isp1760_ep_tx_complete(struct isp1760_ep *ep) -{ - struct isp1760_udc *udc = ep->udc; - struct isp1760_request *complete = NULL; - struct isp1760_request *req; - bool need_zlp; - - spin_lock(&udc->lock); - - if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_IN) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "TX IRQ: invalid endpoint state %u\n", - udc->ep0_state); - return; - } - - if (list_empty(&ep->queue)) { - /* - * This can happen for the control endpoint when the reply to - * the GET_STATUS IN control request is sent directly by the - * setup IRQ handler. Just proceed to the status stage. - */ - if (ep->addr == 0) { - isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); - spin_unlock(&udc->lock); - return; - } - - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: ep%02x has no request queued\n", - __func__, ep->addr); - return; - } - - req = list_first_entry(&ep->queue, struct isp1760_request, - queue); - req->req.actual += req->packet_size; - - need_zlp = req->req.actual == req->req.length && - !(req->req.length % ep->maxpacket) && - req->packet_size && req->req.zero; - - dev_dbg(udc->isp->dev, - "TX IRQ: req %p actual/length %u/%u maxpacket %u packet size %u zero %u need zlp %u\n", - req, req->req.actual, req->req.length, ep->maxpacket, - req->packet_size, req->req.zero, need_zlp); - - /* - * Complete the request if all data has been sent and we don't need to - * transmit a zero length packet. - */ - if (req->req.actual == req->req.length && !need_zlp) { - complete = req; - list_del(&req->queue); - - if (ep->addr == 0) - isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); - - if (!list_empty(&ep->queue)) - req = list_first_entry(&ep->queue, - struct isp1760_request, queue); - else - req = NULL; - } - - /* - * Transmit the next packet or start the next request, if any. - * - * TODO: If the endpoint is stalled the next request shouldn't be - * started, but what about the next packet ? - */ - if (req) - isp1760_udc_transmit(ep, req); - - spin_unlock(&udc->lock); - - if (complete) - isp1760_udc_request_complete(ep, complete, 0); -} - -static int __isp1760_udc_set_halt(struct isp1760_ep *ep, bool halt) -{ - struct isp1760_udc *udc = ep->udc; - - dev_dbg(udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, - halt ? "set" : "clear", ep->addr); - - if (ep->desc && usb_endpoint_xfer_isoc(ep->desc)) { - dev_dbg(udc->isp->dev, "%s: ep%02x is isochronous\n", __func__, - ep->addr); - return -EINVAL; - } - - isp1760_udc_select_ep(ep); - isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); - - if (ep->addr == 0) { - /* When halting the control endpoint, stall both IN and OUT. */ - __isp1760_udc_select_ep(ep, USB_DIR_IN); - isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); - } else if (!halt) { - /* Reset the data PID by cycling the endpoint enable bit. */ - u16 eptype = isp1760_udc_read(udc, DC_EPTYPE); - - isp1760_udc_write(udc, DC_EPTYPE, eptype & ~DC_EPENABLE); - isp1760_udc_write(udc, DC_EPTYPE, eptype); - - /* - * Disabling the endpoint emptied the transmit FIFO, fill it - * again if a request is pending. - * - * TODO: Does the gadget framework require synchronizatino with - * the TX IRQ handler ? - */ - if ((ep->addr & USB_DIR_IN) && !list_empty(&ep->queue)) { - struct isp1760_request *req; - - req = list_first_entry(&ep->queue, - struct isp1760_request, queue); - isp1760_udc_transmit(ep, req); - } - } - - ep->halted = halt; - - return 0; -} - -/* ----------------------------------------------------------------------------- - * Control Endpoint - */ - -static int isp1760_udc_get_status(struct isp1760_udc *udc, - const struct usb_ctrlrequest *req) -{ - struct isp1760_ep *ep; - u16 status; - - if (req->wLength != cpu_to_le16(2) || req->wValue != cpu_to_le16(0)) - return -EINVAL; - - switch (req->bRequestType) { - case USB_DIR_IN | USB_RECIP_DEVICE: - status = udc->devstatus; - break; - - case USB_DIR_IN | USB_RECIP_INTERFACE: - status = 0; - break; - - case USB_DIR_IN | USB_RECIP_ENDPOINT: - ep = isp1760_udc_find_ep(udc, le16_to_cpu(req->wIndex)); - if (!ep) - return -EINVAL; - - status = 0; - if (ep->halted) - status |= 1 << USB_ENDPOINT_HALT; - break; - - default: - return -EINVAL; - } - - isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | DC_EPDIR); - isp1760_udc_write(udc, DC_BUFLEN, 2); - - writew(cpu_to_le16(status), udc->regs + DC_DATAPORT); - - isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); - - dev_dbg(udc->isp->dev, "%s: status 0x%04x\n", __func__, status); - - return 0; -} - -static int isp1760_udc_set_address(struct isp1760_udc *udc, u16 addr) -{ - if (addr > 127) { - dev_dbg(udc->isp->dev, "invalid device address %u\n", addr); - return -EINVAL; - } - - if (udc->gadget.state != USB_STATE_DEFAULT && - udc->gadget.state != USB_STATE_ADDRESS) { - dev_dbg(udc->isp->dev, "can't set address in state %u\n", - udc->gadget.state); - return -EINVAL; - } - - usb_gadget_set_state(&udc->gadget, addr ? USB_STATE_ADDRESS : - USB_STATE_DEFAULT); - - isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN | addr); - - spin_lock(&udc->lock); - isp1760_udc_ctrl_send_status(&udc->ep[0], USB_DIR_OUT); - spin_unlock(&udc->lock); - - return 0; -} - -static bool isp1760_ep0_setup_standard(struct isp1760_udc *udc, - struct usb_ctrlrequest *req) -{ - bool stall; - - switch (req->bRequest) { - case USB_REQ_GET_STATUS: - return isp1760_udc_get_status(udc, req); - - case USB_REQ_CLEAR_FEATURE: - switch (req->bRequestType) { - case USB_DIR_OUT | USB_RECIP_DEVICE: { - /* TODO: Handle remote wakeup feature. */ - return true; - } - - case USB_DIR_OUT | USB_RECIP_ENDPOINT: { - u16 index = le16_to_cpu(req->wIndex); - struct isp1760_ep *ep; - - if (req->wLength != cpu_to_le16(0) || - req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) - return true; - - ep = isp1760_udc_find_ep(udc, index); - if (!ep) - return true; - - spin_lock(&udc->lock); - - /* - * If the endpoint is wedged only the gadget can clear - * the halt feature. Pretend success in that case, but - * keep the endpoint halted. - */ - if (!ep->wedged) - stall = __isp1760_udc_set_halt(ep, false); - else - stall = false; - - if (!stall) - isp1760_udc_ctrl_send_status(&udc->ep[0], - USB_DIR_OUT); - - spin_unlock(&udc->lock); - return stall; - } - - default: - return true; - } - break; - - case USB_REQ_SET_FEATURE: - switch (req->bRequestType) { - case USB_DIR_OUT | USB_RECIP_DEVICE: { - /* TODO: Handle remote wakeup and test mode features */ - return true; - } - - case USB_DIR_OUT | USB_RECIP_ENDPOINT: { - u16 index = le16_to_cpu(req->wIndex); - struct isp1760_ep *ep; - - if (req->wLength != cpu_to_le16(0) || - req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) - return true; - - ep = isp1760_udc_find_ep(udc, index); - if (!ep) - return true; - - spin_lock(&udc->lock); - - stall = __isp1760_udc_set_halt(ep, true); - if (!stall) - isp1760_udc_ctrl_send_status(&udc->ep[0], - USB_DIR_OUT); - - spin_unlock(&udc->lock); - return stall; - } - - default: - return true; - } - break; - - case USB_REQ_SET_ADDRESS: - if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) - return true; - - return isp1760_udc_set_address(udc, le16_to_cpu(req->wValue)); - - case USB_REQ_SET_CONFIGURATION: - if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) - return true; - - if (udc->gadget.state != USB_STATE_ADDRESS && - udc->gadget.state != USB_STATE_CONFIGURED) - return true; - - stall = udc->driver->setup(&udc->gadget, req) < 0; - if (stall) - return true; - - usb_gadget_set_state(&udc->gadget, req->wValue ? - USB_STATE_CONFIGURED : USB_STATE_ADDRESS); - - /* - * SET_CONFIGURATION (and SET_INTERFACE) must reset the halt - * feature on all endpoints. There is however no need to do so - * explicitly here as the gadget driver will disable and - * reenable endpoints, clearing the halt feature. - */ - return false; - - default: - return udc->driver->setup(&udc->gadget, req) < 0; - } -} - -static void isp1760_ep0_setup(struct isp1760_udc *udc) -{ - union { - struct usb_ctrlrequest r; - u32 data[2]; - } req; - unsigned int count; - bool stall = false; - - spin_lock(&udc->lock); - - isp1760_udc_write(udc, DC_EPINDEX, DC_EP0SETUP); - - count = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; - if (count != sizeof(req)) { - spin_unlock(&udc->lock); - - dev_err(udc->isp->dev, "invalid length %u for setup packet\n", - count); - - isp1760_udc_ctrl_send_stall(&udc->ep[0]); - return; - } - - req.data[0] = isp1760_udc_read(udc, DC_DATAPORT); - req.data[1] = isp1760_udc_read(udc, DC_DATAPORT); - - if (udc->ep0_state != ISP1760_CTRL_SETUP) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "unexpected SETUP packet\n"); - return; - } - - /* Move to the data stage. */ - if (!req.r.wLength) - udc->ep0_state = ISP1760_CTRL_STATUS; - else if (req.r.bRequestType & USB_DIR_IN) - udc->ep0_state = ISP1760_CTRL_DATA_IN; - else - udc->ep0_state = ISP1760_CTRL_DATA_OUT; - - udc->ep0_dir = req.r.bRequestType & USB_DIR_IN; - udc->ep0_length = le16_to_cpu(req.r.wLength); - - spin_unlock(&udc->lock); - - dev_dbg(udc->isp->dev, - "%s: bRequestType 0x%02x bRequest 0x%02x wValue 0x%04x wIndex 0x%04x wLength 0x%04x\n", - __func__, req.r.bRequestType, req.r.bRequest, - le16_to_cpu(req.r.wValue), le16_to_cpu(req.r.wIndex), - le16_to_cpu(req.r.wLength)); - - if ((req.r.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) - stall = isp1760_ep0_setup_standard(udc, &req.r); - else - stall = udc->driver->setup(&udc->gadget, &req.r) < 0; - - if (stall) - isp1760_udc_ctrl_send_stall(&udc->ep[0]); -} - -/* ----------------------------------------------------------------------------- - * Gadget Endpoint Operations - */ - -static int isp1760_ep_enable(struct usb_ep *ep, - const struct usb_endpoint_descriptor *desc) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - unsigned long flags; - unsigned int type; - - dev_dbg(uep->udc->isp->dev, "%s\n", __func__); - - /* - * Validate the descriptor. The control endpoint can't be enabled - * manually. - */ - if (desc->bDescriptorType != USB_DT_ENDPOINT || - desc->bEndpointAddress == 0 || - desc->bEndpointAddress != uep->addr || - le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket) { - dev_dbg(udc->isp->dev, - "%s: invalid descriptor type %u addr %02x ep addr %02x max packet size %u/%u\n", - __func__, desc->bDescriptorType, - desc->bEndpointAddress, uep->addr, - le16_to_cpu(desc->wMaxPacketSize), ep->maxpacket); - return -EINVAL; - } - - switch (usb_endpoint_type(desc)) { - case USB_ENDPOINT_XFER_ISOC: - type = DC_ENDPTYP_ISOC; - break; - case USB_ENDPOINT_XFER_BULK: - type = DC_ENDPTYP_BULK; - break; - case USB_ENDPOINT_XFER_INT: - type = DC_ENDPTYP_INTERRUPT; - break; - case USB_ENDPOINT_XFER_CONTROL: - default: - dev_dbg(udc->isp->dev, "%s: control endpoints unsupported\n", - __func__); - return -EINVAL; - } - - spin_lock_irqsave(&udc->lock, flags); - - uep->desc = desc; - uep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); - uep->rx_pending = false; - uep->halted = false; - uep->wedged = false; - - isp1760_udc_select_ep(uep); - isp1760_udc_write(udc, DC_EPMAXPKTSZ, uep->maxpacket); - isp1760_udc_write(udc, DC_BUFLEN, uep->maxpacket); - isp1760_udc_write(udc, DC_EPTYPE, DC_EPENABLE | type); - - spin_unlock_irqrestore(&udc->lock, flags); - - return 0; -} - -static int isp1760_ep_disable(struct usb_ep *ep) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - struct isp1760_request *req, *nreq; - LIST_HEAD(req_list); - unsigned long flags; - - dev_dbg(udc->isp->dev, "%s\n", __func__); - - spin_lock_irqsave(&udc->lock, flags); - - if (!uep->desc) { - dev_dbg(udc->isp->dev, "%s: endpoint not enabled\n", __func__); - spin_unlock_irqrestore(&udc->lock, flags); - return -EINVAL; - } - - uep->desc = NULL; - uep->maxpacket = 0; - - isp1760_udc_select_ep(uep); - isp1760_udc_write(udc, DC_EPTYPE, 0); - - /* TODO Synchronize with the IRQ handler */ - - list_splice_init(&uep->queue, &req_list); - - spin_unlock_irqrestore(&udc->lock, flags); - - list_for_each_entry_safe(req, nreq, &req_list, queue) { - list_del(&req->queue); - isp1760_udc_request_complete(uep, req, -ESHUTDOWN); - } - - return 0; -} - -static struct usb_request *isp1760_ep_alloc_request(struct usb_ep *ep, - gfp_t gfp_flags) -{ - struct isp1760_request *req; - - req = kzalloc(sizeof(*req), gfp_flags); - - return &req->req; -} - -static void isp1760_ep_free_request(struct usb_ep *ep, struct usb_request *_req) -{ - struct isp1760_request *req = req_to_udc_req(_req); - - kfree(req); -} - -static int isp1760_ep_queue(struct usb_ep *ep, struct usb_request *_req, - gfp_t gfp_flags) -{ - struct isp1760_request *req = req_to_udc_req(_req); - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - bool complete = false; - unsigned long flags; - int ret = 0; - - _req->status = -EINPROGRESS; - _req->actual = 0; - - spin_lock_irqsave(&udc->lock, flags); - - dev_dbg(udc->isp->dev, - "%s: req %p (%u bytes%s) ep %p(0x%02x)\n", __func__, _req, - _req->length, _req->zero ? " (zlp)" : "", uep, uep->addr); - - req->ep = uep; - - if (uep->addr == 0) { - if (_req->length != udc->ep0_length && - udc->ep0_state != ISP1760_CTRL_DATA_IN) { - dev_dbg(udc->isp->dev, - "%s: invalid length %u for req %p\n", - __func__, _req->length, req); - ret = -EINVAL; - goto done; - } - - switch (udc->ep0_state) { - case ISP1760_CTRL_DATA_IN: - dev_dbg(udc->isp->dev, "%s: transmitting req %p\n", - __func__, req); - - list_add_tail(&req->queue, &uep->queue); - isp1760_udc_transmit(uep, req); - break; - - case ISP1760_CTRL_DATA_OUT: - list_add_tail(&req->queue, &uep->queue); - __isp1760_udc_select_ep(uep, USB_DIR_OUT); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); - break; - - case ISP1760_CTRL_STATUS: - complete = true; - break; - - default: - dev_dbg(udc->isp->dev, "%s: invalid ep0 state\n", - __func__); - ret = -EINVAL; - break; - } - } else if (uep->desc) { - bool empty = list_empty(&uep->queue); - - list_add_tail(&req->queue, &uep->queue); - if ((uep->addr & USB_DIR_IN) && !uep->halted && empty) - isp1760_udc_transmit(uep, req); - else if (!(uep->addr & USB_DIR_IN) && uep->rx_pending) - complete = isp1760_udc_receive(uep, req); - } else { - dev_dbg(udc->isp->dev, - "%s: can't queue request to disabled ep%02x\n", - __func__, uep->addr); - ret = -ESHUTDOWN; - } - -done: - if (ret < 0) - req->ep = NULL; - - spin_unlock_irqrestore(&udc->lock, flags); - - if (complete) - isp1760_udc_request_complete(uep, req, 0); - - return ret; -} - -static int isp1760_ep_dequeue(struct usb_ep *ep, struct usb_request *_req) -{ - struct isp1760_request *req = req_to_udc_req(_req); - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - unsigned long flags; - - dev_dbg(uep->udc->isp->dev, "%s(ep%02x)\n", __func__, uep->addr); - - spin_lock_irqsave(&udc->lock, flags); - - if (req->ep != uep) - req = NULL; - else - list_del(&req->queue); - - spin_unlock_irqrestore(&udc->lock, flags); - - if (!req) - return -EINVAL; - - isp1760_udc_request_complete(uep, req, -ECONNRESET); - return 0; -} - -static int __isp1760_ep_set_halt(struct isp1760_ep *uep, bool stall, bool wedge) -{ - struct isp1760_udc *udc = uep->udc; - int ret; - - if (!uep->addr) { - /* - * Halting the control endpoint is only valid as a delayed error - * response to a SETUP packet. Make sure EP0 is in the right - * stage and that the gadget isn't trying to clear the halt - * condition. - */ - if (WARN_ON(udc->ep0_state == ISP1760_CTRL_SETUP || !stall || - wedge)) { - return -EINVAL; - } - } - - if (uep->addr && !uep->desc) { - dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, - uep->addr); - return -EINVAL; - } - - if (uep->addr & USB_DIR_IN) { - /* Refuse to halt IN endpoints with active transfers. */ - if (!list_empty(&uep->queue)) { - dev_dbg(udc->isp->dev, - "%s: ep%02x has request pending\n", __func__, - uep->addr); - return -EAGAIN; - } - } - - ret = __isp1760_udc_set_halt(uep, stall); - if (ret < 0) - return ret; - - if (!uep->addr) { - /* - * Stalling EP0 completes the control transaction, move back to - * the SETUP state. - */ - udc->ep0_state = ISP1760_CTRL_SETUP; - return 0; - } - - if (wedge) - uep->wedged = true; - else if (!stall) - uep->wedged = false; - - return 0; -} - -static int isp1760_ep_set_halt(struct usb_ep *ep, int value) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - unsigned long flags; - int ret; - - dev_dbg(uep->udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, - value ? "set" : "clear", uep->addr); - - spin_lock_irqsave(&uep->udc->lock, flags); - ret = __isp1760_ep_set_halt(uep, value, false); - spin_unlock_irqrestore(&uep->udc->lock, flags); - - return ret; -} - -static int isp1760_ep_set_wedge(struct usb_ep *ep) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - unsigned long flags; - int ret; - - dev_dbg(uep->udc->isp->dev, "%s: set wedge on ep%02x)\n", __func__, - uep->addr); - - spin_lock_irqsave(&uep->udc->lock, flags); - ret = __isp1760_ep_set_halt(uep, true, true); - spin_unlock_irqrestore(&uep->udc->lock, flags); - - return ret; -} - -static void isp1760_ep_fifo_flush(struct usb_ep *ep) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - - isp1760_udc_select_ep(uep); - - /* - * Set the CLBUF bit twice to flush both buffers in case double - * buffering is enabled. - */ - isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -static const struct usb_ep_ops isp1760_ep_ops = { - .enable = isp1760_ep_enable, - .disable = isp1760_ep_disable, - .alloc_request = isp1760_ep_alloc_request, - .free_request = isp1760_ep_free_request, - .queue = isp1760_ep_queue, - .dequeue = isp1760_ep_dequeue, - .set_halt = isp1760_ep_set_halt, - .set_wedge = isp1760_ep_set_wedge, - .fifo_flush = isp1760_ep_fifo_flush, -}; - -/* ----------------------------------------------------------------------------- - * Device States - */ - -/* Called with the UDC spinlock held. */ -static void isp1760_udc_connect(struct isp1760_udc *udc) -{ - usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); - mod_timer(&udc->vbus_timer, jiffies + ISP1760_VBUS_POLL_INTERVAL); -} - -/* Called with the UDC spinlock held. */ -static void isp1760_udc_disconnect(struct isp1760_udc *udc) -{ - if (udc->gadget.state < USB_STATE_POWERED) - return; - - dev_dbg(udc->isp->dev, "Device disconnected in state %u\n", - udc->gadget.state); - - udc->gadget.speed = USB_SPEED_UNKNOWN; - usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); - - if (udc->driver->disconnect) - udc->driver->disconnect(&udc->gadget); - - del_timer(&udc->vbus_timer); - - /* TODO Reset all endpoints ? */ -} - -static void isp1760_udc_init_hw(struct isp1760_udc *udc) -{ - /* - * The device controller currently shares its interrupt with the host - * controller, the DC_IRQ polarity and signaling mode are ignored. Set - * the to active-low level-triggered. - * - * Configure the control, in and out pipes to generate interrupts on - * ACK tokens only (and NYET for the out pipe). The default - * configuration also generates an interrupt on the first NACK token. - */ - isp1760_udc_write(udc, DC_INTCONF, DC_CDBGMOD_ACK | DC_DDBGMODIN_ACK | - DC_DDBGMODOUT_ACK_NYET); - - isp1760_udc_write(udc, DC_INTENABLE, DC_IEPRXTX(7) | DC_IEPRXTX(6) | - DC_IEPRXTX(5) | DC_IEPRXTX(4) | DC_IEPRXTX(3) | - DC_IEPRXTX(2) | DC_IEPRXTX(1) | DC_IEPRXTX(0) | - DC_IEP0SETUP | DC_IEVBUS | DC_IERESM | DC_IESUSP | - DC_IEHS_STA | DC_IEBRST); - - if (udc->connected) - isp1760_set_pullup(udc->isp, true); - - isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN); -} - -static void isp1760_udc_reset(struct isp1760_udc *udc) -{ - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - - /* - * The bus reset has reset most registers to their default value, - * reinitialize the UDC hardware. - */ - isp1760_udc_init_hw(udc); - - udc->ep0_state = ISP1760_CTRL_SETUP; - udc->gadget.speed = USB_SPEED_FULL; - - usb_gadget_udc_reset(&udc->gadget, udc->driver); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -static void isp1760_udc_suspend(struct isp1760_udc *udc) -{ - if (udc->gadget.state < USB_STATE_DEFAULT) - return; - - if (udc->driver->suspend) - udc->driver->suspend(&udc->gadget); -} - -static void isp1760_udc_resume(struct isp1760_udc *udc) -{ - if (udc->gadget.state < USB_STATE_DEFAULT) - return; - - if (udc->driver->resume) - udc->driver->resume(&udc->gadget); -} - -/* ----------------------------------------------------------------------------- - * Gadget Operations - */ - -static int isp1760_udc_get_frame(struct usb_gadget *gadget) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - return isp1760_udc_read(udc, DC_FRAMENUM) & ((1 << 11) - 1); -} - -static int isp1760_udc_wakeup(struct usb_gadget *gadget) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - dev_dbg(udc->isp->dev, "%s\n", __func__); - return -ENOTSUPP; -} - -static int isp1760_udc_set_selfpowered(struct usb_gadget *gadget, - int is_selfpowered) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - if (is_selfpowered) - udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; - else - udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); - - return 0; -} - -static int isp1760_udc_pullup(struct usb_gadget *gadget, int is_on) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - isp1760_set_pullup(udc->isp, is_on); - udc->connected = is_on; - - return 0; -} - -static int isp1760_udc_start(struct usb_gadget *gadget, - struct usb_gadget_driver *driver) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - /* The hardware doesn't support low speed. */ - if (driver->max_speed < USB_SPEED_FULL) { - dev_err(udc->isp->dev, "Invalid gadget driver\n"); - return -EINVAL; - } - - spin_lock(&udc->lock); - - if (udc->driver) { - dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); - spin_unlock(&udc->lock); - return -EBUSY; - } - - udc->driver = driver; - - spin_unlock(&udc->lock); - - dev_dbg(udc->isp->dev, "starting UDC with driver %s\n", - driver->function); - - udc->devstatus = 0; - udc->connected = true; - - usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); - - /* DMA isn't supported yet, don't enable the DMA clock. */ - isp1760_udc_write(udc, DC_MODE, DC_GLINTENA); - - isp1760_udc_init_hw(udc); - - dev_dbg(udc->isp->dev, "UDC started with driver %s\n", - driver->function); - - return 0; -} - -static int isp1760_udc_stop(struct usb_gadget *gadget) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - dev_dbg(udc->isp->dev, "%s\n", __func__); - - del_timer_sync(&udc->vbus_timer); - - isp1760_udc_write(udc, DC_MODE, 0); - - spin_lock(&udc->lock); - udc->driver = NULL; - spin_unlock(&udc->lock); - - return 0; -} - -static struct usb_gadget_ops isp1760_udc_ops = { - .get_frame = isp1760_udc_get_frame, - .wakeup = isp1760_udc_wakeup, - .set_selfpowered = isp1760_udc_set_selfpowered, - .pullup = isp1760_udc_pullup, - .udc_start = isp1760_udc_start, - .udc_stop = isp1760_udc_stop, -}; - -/* ----------------------------------------------------------------------------- - * Interrupt Handling - */ - -static irqreturn_t isp1760_udc_irq(int irq, void *dev) -{ - struct isp1760_udc *udc = dev; - unsigned int i; - u32 status; - - status = isp1760_udc_read(udc, DC_INTERRUPT) - & isp1760_udc_read(udc, DC_INTENABLE); - isp1760_udc_write(udc, DC_INTERRUPT, status); - - if (status & DC_IEVBUS) { - dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); - /* The VBUS interrupt is only triggered when VBUS appears. */ - spin_lock(&udc->lock); - isp1760_udc_connect(udc); - spin_unlock(&udc->lock); - } - - if (status & DC_IEBRST) { - dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); - - isp1760_udc_reset(udc); - } - - for (i = 0; i <= 7; ++i) { - struct isp1760_ep *ep = &udc->ep[i*2]; - - if (status & DC_IEPTX(i)) { - dev_dbg(udc->isp->dev, "%s(EPTX%u)\n", __func__, i); - isp1760_ep_tx_complete(ep); - } - - if (status & DC_IEPRX(i)) { - dev_dbg(udc->isp->dev, "%s(EPRX%u)\n", __func__, i); - isp1760_ep_rx_ready(i ? ep - 1 : ep); - } - } - - if (status & DC_IEP0SETUP) { - dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); - - isp1760_ep0_setup(udc); - } - - if (status & DC_IERESM) { - dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); - isp1760_udc_resume(udc); - } - - if (status & DC_IESUSP) { - dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); - - spin_lock(&udc->lock); - if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) - isp1760_udc_disconnect(udc); - else - isp1760_udc_suspend(udc); - spin_unlock(&udc->lock); - } - - if (status & DC_IEHS_STA) { - dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); - udc->gadget.speed = USB_SPEED_HIGH; - } - - return status ? IRQ_HANDLED : IRQ_NONE; -} - -static void isp1760_udc_vbus_poll(unsigned long data) -{ - struct isp1760_udc *udc = (struct isp1760_udc *)data; - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - - if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) - isp1760_udc_disconnect(udc); - else if (udc->gadget.state >= USB_STATE_POWERED) - mod_timer(&udc->vbus_timer, - jiffies + ISP1760_VBUS_POLL_INTERVAL); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -/* ----------------------------------------------------------------------------- - * Registration - */ - -static void isp1760_udc_init_eps(struct isp1760_udc *udc) -{ - unsigned int i; - - INIT_LIST_HEAD(&udc->gadget.ep_list); - - for (i = 0; i < ARRAY_SIZE(udc->ep); ++i) { - struct isp1760_ep *ep = &udc->ep[i]; - unsigned int ep_num = (i + 1) / 2; - bool is_in = !(i & 1); - - ep->udc = udc; - - INIT_LIST_HEAD(&ep->queue); - - ep->addr = (ep_num && is_in ? USB_DIR_IN : USB_DIR_OUT) - | ep_num; - ep->desc = NULL; - - sprintf(ep->name, "ep%u%s", ep_num, - ep_num ? (is_in ? "in" : "out") : ""); - - ep->ep.ops = &isp1760_ep_ops; - ep->ep.name = ep->name; - - /* - * Hardcode the maximum packet sizes for now, to 64 bytes for - * the control endpoint and 512 bytes for all other endpoints. - * This fits in the 8kB FIFO without double-buffering. - */ - if (ep_num == 0) { - ep->ep.maxpacket = 64; - ep->maxpacket = 64; - udc->gadget.ep0 = &ep->ep; - } else { - ep->ep.maxpacket = 512; - ep->maxpacket = 0; - list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); - } - } -} - -static int isp1760_udc_init(struct isp1760_udc *udc) -{ - u16 scratch; - u32 chipid; - - /* - * Check that the controller is present by writing to the scratch - * register, modifying the bus pattern by reading from the chip ID - * register, and reading the scratch register value back. The chip ID - * and scratch register contents must match the expected values. - */ - isp1760_udc_write(udc, DC_SCRATCH, 0xbabe); - chipid = isp1760_udc_read(udc, DC_CHIPID); - scratch = isp1760_udc_read(udc, DC_SCRATCH); - - if (scratch != 0xbabe) { - dev_err(udc->isp->dev, - "udc: scratch test failed (0x%04x/0x%08x)\n", - scratch, chipid); - return -ENODEV; - } - - if (chipid != 0x00011582) { - dev_err(udc->isp->dev, "udc: invalid chip ID 0x%08x\n", chipid); - return -ENODEV; - } - - /* Reset the device controller. */ - isp1760_udc_write(udc, DC_MODE, DC_SFRESET); - usleep_range(10000, 11000); - isp1760_udc_write(udc, DC_MODE, 0); - usleep_range(10000, 11000); - - return 0; -} - -int isp1760_udc_register(struct isp1760_device *isp, int irq, - unsigned long irqflags) -{ - struct isp1760_udc *udc = &isp->udc; - const char *devname; - int ret; - - udc->irq = -1; - udc->isp = isp; - udc->regs = isp->regs; - - spin_lock_init(&udc->lock); - setup_timer(&udc->vbus_timer, isp1760_udc_vbus_poll, - (unsigned long)udc); - - ret = isp1760_udc_init(udc); - if (ret < 0) - return ret; - - devname = dev_name(isp->dev); - udc->irqname = kmalloc(strlen(devname) + 7, GFP_KERNEL); - if (!udc->irqname) - return -ENOMEM; - - sprintf(udc->irqname, "%s (udc)", devname); - - ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | IRQF_DISABLED | - irqflags, udc->irqname, udc); - if (ret < 0) - goto error; - - udc->irq = irq; - - /* - * Initialize the gadget static fields and register its device. Gadget - * fields that vary during the life time of the gadget are initialized - * by the UDC core. - */ - udc->gadget.ops = &isp1760_udc_ops; - udc->gadget.speed = USB_SPEED_UNKNOWN; - udc->gadget.max_speed = USB_SPEED_HIGH; - udc->gadget.name = "isp1761_udc"; - - isp1760_udc_init_eps(udc); - - ret = usb_add_gadget_udc(isp->dev, &udc->gadget); - if (ret < 0) - goto error; - - return 0; - -error: - if (udc->irq >= 0) - free_irq(udc->irq, udc); - kfree(udc->irqname); - - return ret; -} - -void isp1760_udc_unregister(struct isp1760_device *isp) -{ - struct isp1760_udc *udc = &isp->udc; - - usb_del_gadget_udc(&udc->gadget); - - free_irq(udc->irq, udc); - kfree(udc->irqname); -} diff --git a/drivers/usb/host/isp1760-udc.h b/drivers/usb/host/isp1760-udc.h deleted file mode 100644 index 4af6ba6..0000000 --- a/drivers/usb/host/isp1760-udc.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Driver for the NXP ISP1761 device controller - * - * Copyright 2014 Ideas on Board Oy - * - * Contacts: - * Laurent Pinchart - * - * 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 _ISP1760_UDC_H_ -#define _ISP1760_UDC_H_ - -#include -#include -#include -#include -#include - -struct isp1760_device; -struct isp1760_udc; - -enum isp1760_ctrl_state { - ISP1760_CTRL_SETUP, /* Waiting for a SETUP transaction */ - ISP1760_CTRL_DATA_IN, /* Setup received, data IN stage */ - ISP1760_CTRL_DATA_OUT, /* Setup received, data OUT stage */ - ISP1760_CTRL_STATUS, /* 0-length request in status stage */ -}; - -struct isp1760_ep { - struct isp1760_udc *udc; - struct usb_ep ep; - - struct list_head queue; - - unsigned int addr; - unsigned int maxpacket; - char name[7]; - - const struct usb_endpoint_descriptor *desc; - - bool rx_pending; - bool halted; - bool wedged; -}; - -/** - * struct isp1760_udc - UDC state information - * irq: IRQ number - * irqname: IRQ name (as passed to request_irq) - * regs: Base address of the UDC registers - * driver: Gadget driver - * gadget: Gadget device - * lock: Protects driver, vbus_timer, ep, ep0_*, DC_EPINDEX register - * ep: Array of endpoints - * ep0_state: Control request state for endpoint 0 - * ep0_dir: Direction of the current control request - * ep0_length: Length of the current control request - * connected: Tracks gadget driver bus connection state - */ -struct isp1760_udc { -#if CONFIG_USB_ISP1761_UDC - struct isp1760_device *isp; - - int irq; - char *irqname; - void __iomem *regs; - - struct usb_gadget_driver *driver; - struct usb_gadget gadget; - - spinlock_t lock; - struct timer_list vbus_timer; - - struct isp1760_ep ep[15]; - - enum isp1760_ctrl_state ep0_state; - u8 ep0_dir; - u16 ep0_length; - - bool connected; - - unsigned int devstatus; -#endif -}; - -#if CONFIG_USB_ISP1761_UDC -int isp1760_udc_register(struct isp1760_device *isp, int irq, - unsigned long irqflags); -void isp1760_udc_unregister(struct isp1760_device *isp); -#else -static inline int isp1760_udc_register(struct isp1760_device *isp, int irq, - unsigned long irqflags) -{ - return 0; -} - -static inline void isp1760_udc_unregister(struct isp1760_device *isp) -{ -} -#endif - -#endif diff --git a/drivers/usb/isp1760/Kconfig b/drivers/usb/isp1760/Kconfig new file mode 100644 index 0000000..c09ab8f --- /dev/null +++ b/drivers/usb/isp1760/Kconfig @@ -0,0 +1,22 @@ +config USB_ISP1760 + tristate "NXP ISP 1760/1761 support" + depends on USB + help + Say Y or M here if your system as an ISP1760 USB host controller + or an ISP1761 USB dual-role controller. + + This driver does not support isochronous transfers or OTG. + This USB controller is usually attached to a non-DMA-Master + capable bus. NXP's eval kit brings this chip on PCI card + where the chip itself is behind a PLB to simulate such + a bus. + + To compile this driver as a module, choose M here: the + module will be called isp1760. + +config USB_ISP1761_UDC + boolean "NXP ISP1761 USB Device Controller" + depends on USB_ISP1760 && USB_GADGET + help + The NXP ISP1761 is a dual-role high-speed USB host and device + controller. diff --git a/drivers/usb/isp1760/Makefile b/drivers/usb/isp1760/Makefile new file mode 100644 index 0000000..698ccb0 --- /dev/null +++ b/drivers/usb/isp1760/Makefile @@ -0,0 +1,4 @@ +isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o +isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o + +obj-$(CONFIG_USB_ISP1760) += isp1760.o diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c new file mode 100644 index 0000000..727e90a --- /dev/null +++ b/drivers/usb/isp1760/isp1760-core.c @@ -0,0 +1,171 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * 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 + +#include "isp1760-core.h" +#include "isp1760-hcd.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +static void isp1760_init_core(struct isp1760_device *isp) +{ + u32 otgctrl; + u32 hwmode; + + /* Low-level chip reset */ + if (isp->rst_gpio) { + gpiod_set_value_cansleep(isp->rst_gpio, 1); + mdelay(50); + gpiod_set_value_cansleep(isp->rst_gpio, 0); + } + + /* + * Reset the host controller, including the CPU interface + * configuration. + */ + isp1760_write32(isp->regs, HC_RESET_REG, SW_RESET_RESET_ALL); + msleep(100); + + /* Setup HW Mode Control: This assumes a level active-low interrupt */ + hwmode = HW_DATA_BUS_32BIT; + + if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_16) + hwmode &= ~HW_DATA_BUS_32BIT; + if (isp->devflags & ISP1760_FLAG_ANALOG_OC) + hwmode |= HW_ANA_DIGI_OC; + if (isp->devflags & ISP1760_FLAG_DACK_POL_HIGH) + hwmode |= HW_DACK_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_DREQ_POL_HIGH) + hwmode |= HW_DREQ_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_INTR_POL_HIGH) + hwmode |= HW_INTR_HIGH_ACT; + if (isp->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) + hwmode |= HW_INTR_EDGE_TRIG; + + /* + * The ISP1761 has a dedicated DC IRQ line but supports sharing the HC + * IRQ line for both the host and device controllers. Hardcode IRQ + * sharing for now and disable the DC interrupts globally to avoid + * spurious interrupts during HCD registration. + */ + if (isp->devflags & ISP1760_FLAG_ISP1761) { + isp1760_write32(isp->regs, DC_MODE, 0); + hwmode |= HW_COMN_IRQ; + } + + /* + * We have to set this first in case we're in 16-bit mode. + * Write it twice to ensure correct upper bits if switching + * to 16-bit mode. + */ + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + + /* + * PORT 1 Control register of the ISP1760 is the OTG control register + * on ISP1761. + * + * TODO: Really support OTG. For now we configure port 1 in device mode + * when OTG is requested. + */ + if ((isp->devflags & ISP1760_FLAG_ISP1761) && + (isp->devflags & ISP1760_FLAG_OTG_EN)) + otgctrl = ((HW_DM_PULLDOWN | HW_DP_PULLDOWN) << 16) + | HW_OTG_DISABLE; + else + otgctrl = (HW_SW_SEL_HC_DC << 16) + | (HW_VBUS_DRV | HW_SEL_CP_EXT); + + isp1760_write32(isp->regs, HC_PORT1_CTRL, otgctrl); + + dev_info(isp->dev, "bus width: %u, oc: %s\n", + isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, + isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); +} + +void isp1760_set_pullup(struct isp1760_device *isp, bool enable) +{ + isp1760_write32(isp->regs, HW_OTG_CTRL_SET, + enable ? HW_DP_PULLUP : HW_DP_PULLUP << 16); +} + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) +{ + struct isp1760_device *isp; + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* prevent usb-core allocating DMA pages */ + dev->dma_mask = NULL; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->dev = dev; + isp->devflags = devflags; + + isp->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); + if (IS_ERR(isp->rst_gpio)) + return PTR_ERR(isp->rst_gpio); + + isp->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(isp->regs)) + return PTR_ERR(isp->regs); + + isp1760_init_core(isp); + + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, + irqflags | IRQF_SHARED, dev); + if (ret < 0) + return ret; + + if (devflags & ISP1760_FLAG_ISP1761) { + ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | + IRQF_DISABLED); + if (ret < 0) { + isp1760_hcd_unregister(&isp->hcd); + return ret; + } + } + + dev_set_drvdata(dev, isp); + + return 0; +} + +void isp1760_unregister(struct device *dev) +{ + struct isp1760_device *isp = dev_get_drvdata(dev); + + if (isp->devflags & ISP1760_FLAG_ISP1761) + isp1760_udc_unregister(isp); + + isp1760_hcd_unregister(&isp->hcd); +} + +MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); +MODULE_AUTHOR("Sebastian Siewior "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/isp1760/isp1760-core.h b/drivers/usb/isp1760/isp1760-core.h new file mode 100644 index 0000000..c70f836 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-core.h @@ -0,0 +1,68 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * 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 _ISP1760_CORE_H_ +#define _ISP1760_CORE_H_ + +#include + +#include "isp1760-hcd.h" +#include "isp1760-udc.h" + +struct device; +struct gpio_desc; + +/* + * Device flags that can vary from board to board. All of these + * indicate the most "atypical" case, so that a devflags of 0 is + * a sane default configuration. + */ +#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ +#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ +#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ +#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ +#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ +#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ +#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ +#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ + +struct isp1760_device { + struct device *dev; + + void __iomem *regs; + unsigned int devflags; + struct gpio_desc *rst_gpio; + + struct isp1760_hcd hcd; + struct isp1760_udc udc; +}; + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_unregister(struct device *dev); + +void isp1760_set_pullup(struct isp1760_device *isp, bool enable); + +static inline u32 isp1760_read32(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void isp1760_write32(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +#endif diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c new file mode 100644 index 0000000..568446c --- /dev/null +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -0,0 +1,2231 @@ +/* + * Driver for the NXP ISP1760 chip + * + * However, the code might contain some bugs. What doesn't work for sure is: + * - ISO + * - OTG + e The interrupt line is configured as active low, level. + * + * (c) 2007 Sebastian Siewior + * + * (c) 2011 Arvid Brodin + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-hcd.h" +#include "isp1760-regs.h" + +static struct kmem_cache *qtd_cachep; +static struct kmem_cache *qh_cachep; +static struct kmem_cache *urb_listitem_cachep; + +typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd); + +static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) +{ + return *(struct isp1760_hcd **)hcd->hcd_priv; +} + +/* urb state*/ +#define DELETE_URB (0x0008) +#define NO_TRANSFER_ACTIVE (0xffffffff) + +/* Philips Proprietary Transfer Descriptor (PTD) */ +typedef __u32 __bitwise __dw; +struct ptd { + __dw dw0; + __dw dw1; + __dw dw2; + __dw dw3; + __dw dw4; + __dw dw5; + __dw dw6; + __dw dw7; +}; +#define PTD_OFFSET 0x0400 +#define ISO_PTD_OFFSET 0x0400 +#define INT_PTD_OFFSET 0x0800 +#define ATL_PTD_OFFSET 0x0c00 +#define PAYLOAD_OFFSET 0x1000 + + +/* ATL */ +/* DW0 */ +#define DW0_VALID_BIT 1 +#define FROM_DW0_VALID(x) ((x) & 0x01) +#define TO_DW0_LENGTH(x) (((u32) x) << 3) +#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) +#define TO_DW0_MULTI(x) (((u32) x) << 29) +#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) +/* DW1 */ +#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) +#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) +#define DW1_TRANS_BULK ((u32) 2 << 12) +#define DW1_TRANS_INT ((u32) 3 << 12) +#define DW1_TRANS_SPLIT ((u32) 1 << 14) +#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) +#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) +#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) +/* DW2 */ +#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) +#define TO_DW2_RL(x) ((x) << 25) +#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) +/* DW3 */ +#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) +#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) +#define TO_DW3_NAKCOUNT(x) ((x) << 19) +#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) +#define TO_DW3_CERR(x) ((x) << 23) +#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) +#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) +#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) +#define TO_DW3_PING(x) ((x) << 26) +#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) +#define DW3_ERROR_BIT (1 << 28) +#define DW3_BABBLE_BIT (1 << 29) +#define DW3_HALT_BIT (1 << 30) +#define DW3_ACTIVE_BIT (1 << 31) +#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) + +#define INT_UNDERRUN (1 << 2) +#define INT_BABBLE (1 << 1) +#define INT_EXACT (1 << 0) + +#define SETUP_PID (2) +#define IN_PID (1) +#define OUT_PID (0) + +/* Errata 1 */ +#define RL_COUNTER (0) +#define NAK_COUNTER (0) +#define ERR_COUNTER (2) + +struct isp1760_qtd { + u8 packet_type; + void *data_buffer; + u32 payload_addr; + + /* the rest is HCD-private */ + struct list_head qtd_list; + struct urb *urb; + size_t length; + size_t actual_length; + + /* QTD_ENQUEUED: waiting for transfer (inactive) */ + /* QTD_PAYLOAD_ALLOC: chip mem has been allocated for payload */ + /* QTD_XFER_STARTED: valid ptd has been written to isp176x - only + interrupt handler may touch this qtd! */ + /* QTD_XFER_COMPLETE: payload has been transferred successfully */ + /* QTD_RETIRE: transfer error/abort qtd */ +#define QTD_ENQUEUED 0 +#define QTD_PAYLOAD_ALLOC 1 +#define QTD_XFER_STARTED 2 +#define QTD_XFER_COMPLETE 3 +#define QTD_RETIRE 4 + u32 status; +}; + +/* Queue head, one for each active endpoint */ +struct isp1760_qh { + struct list_head qh_list; + struct list_head qtd_list; + u32 toggle; + u32 ping; + int slot; + int tt_buffer_dirty; /* See USB2.0 spec section 11.17.5 */ +}; + +struct urb_listitem { + struct list_head urb_list; + struct urb *urb; +}; + +/* + * Access functions for isp176x registers (addresses 0..0x03FF). + */ +static u32 reg_read32(void __iomem *base, u32 reg) +{ + return isp1760_read32(base, reg); +} + +static void reg_write32(void __iomem *base, u32 reg, u32 val) +{ + isp1760_write32(base, reg, val); +} + +/* + * Access functions for isp176x memory (offset >= 0x0400). + * + * bank_reads8() reads memory locations prefetched by an earlier write to + * HC_MEMORY_REG (see isp176x datasheet). Unless you want to do fancy multi- + * bank optimizations, you should use the more generic mem_reads8() below. + * + * For access to ptd memory, use the specialized ptd_read() and ptd_write() + * below. + * + * These functions copy via MMIO data to/from the device. memcpy_{to|from}io() + * doesn't quite work because some people have to enforce 32-bit access + */ +static void bank_reads8(void __iomem *src_base, u32 src_offset, u32 bank_addr, + __u32 *dst, u32 bytes) +{ + __u32 __iomem *src; + u32 val; + __u8 *src_byteptr; + __u8 *dst_byteptr; + + src = src_base + (bank_addr | src_offset); + + if (src_offset < PAYLOAD_OFFSET) { + while (bytes >= 4) { + *dst = le32_to_cpu(__raw_readl(src)); + bytes -= 4; + src++; + dst++; + } + } else { + while (bytes >= 4) { + *dst = __raw_readl(src); + bytes -= 4; + src++; + dst++; + } + } + + if (!bytes) + return; + + /* in case we have 3, 2 or 1 by left. The dst buffer may not be fully + * allocated. + */ + if (src_offset < PAYLOAD_OFFSET) + val = le32_to_cpu(__raw_readl(src)); + else + val = __raw_readl(src); + + dst_byteptr = (void *) dst; + src_byteptr = (void *) &val; + while (bytes > 0) { + *dst_byteptr = *src_byteptr; + dst_byteptr++; + src_byteptr++; + bytes--; + } +} + +static void mem_reads8(void __iomem *src_base, u32 src_offset, void *dst, + u32 bytes) +{ + reg_write32(src_base, HC_MEMORY_REG, src_offset + ISP_BANK(0)); + ndelay(90); + bank_reads8(src_base, src_offset, ISP_BANK(0), dst, bytes); +} + +static void mem_writes8(void __iomem *dst_base, u32 dst_offset, + __u32 const *src, u32 bytes) +{ + __u32 __iomem *dst; + + dst = dst_base + dst_offset; + + if (dst_offset < PAYLOAD_OFFSET) { + while (bytes >= 4) { + __raw_writel(cpu_to_le32(*src), dst); + bytes -= 4; + src++; + dst++; + } + } else { + while (bytes >= 4) { + __raw_writel(*src, dst); + bytes -= 4; + src++; + dst++; + } + } + + if (!bytes) + return; + /* in case we have 3, 2 or 1 bytes left. The buffer is allocated and the + * extra bytes should not be read by the HW. + */ + + if (dst_offset < PAYLOAD_OFFSET) + __raw_writel(cpu_to_le32(*src), dst); + else + __raw_writel(*src, dst); +} + +/* + * Read and write ptds. 'ptd_offset' should be one of ISO_PTD_OFFSET, + * INT_PTD_OFFSET, and ATL_PTD_OFFSET. 'slot' should be less than 32. + */ +static void ptd_read(void __iomem *base, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + reg_write32(base, HC_MEMORY_REG, + ISP_BANK(0) + ptd_offset + slot*sizeof(*ptd)); + ndelay(90); + bank_reads8(base, ptd_offset + slot*sizeof(*ptd), ISP_BANK(0), + (void *) ptd, sizeof(*ptd)); +} + +static void ptd_write(void __iomem *base, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + mem_writes8(base, ptd_offset + slot*sizeof(*ptd) + sizeof(ptd->dw0), + &ptd->dw1, 7*sizeof(ptd->dw1)); + /* Make sure dw0 gets written last (after other dw's and after payload) + since it contains the enable bit */ + wmb(); + mem_writes8(base, ptd_offset + slot*sizeof(*ptd), &ptd->dw0, + sizeof(ptd->dw0)); +} + + +/* memory management of the 60kb on the chip from 0x1000 to 0xffff */ +static void init_memory(struct isp1760_hcd *priv) +{ + int i, curr; + u32 payload_addr; + + payload_addr = PAYLOAD_OFFSET; + for (i = 0; i < BLOCK_1_NUM; i++) { + priv->memory_pool[i].start = payload_addr; + priv->memory_pool[i].size = BLOCK_1_SIZE; + priv->memory_pool[i].free = 1; + payload_addr += priv->memory_pool[i].size; + } + + curr = i; + for (i = 0; i < BLOCK_2_NUM; i++) { + priv->memory_pool[curr + i].start = payload_addr; + priv->memory_pool[curr + i].size = BLOCK_2_SIZE; + priv->memory_pool[curr + i].free = 1; + payload_addr += priv->memory_pool[curr + i].size; + } + + curr = i; + for (i = 0; i < BLOCK_3_NUM; i++) { + priv->memory_pool[curr + i].start = payload_addr; + priv->memory_pool[curr + i].size = BLOCK_3_SIZE; + priv->memory_pool[curr + i].free = 1; + payload_addr += priv->memory_pool[curr + i].size; + } + + WARN_ON(payload_addr - priv->memory_pool[0].start > PAYLOAD_AREA_SIZE); +} + +static void alloc_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int i; + + WARN_ON(qtd->payload_addr); + + if (!qtd->length) + return; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].size >= qtd->length && + priv->memory_pool[i].free) { + priv->memory_pool[i].free = 0; + qtd->payload_addr = priv->memory_pool[i].start; + return; + } + } +} + +static void free_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int i; + + if (!qtd->payload_addr) + return; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].start == qtd->payload_addr) { + WARN_ON(priv->memory_pool[i].free); + priv->memory_pool[i].free = 1; + qtd->payload_addr = 0; + return; + } + } + + dev_err(hcd->self.controller, "%s: Invalid pointer: %08x\n", + __func__, qtd->payload_addr); + WARN_ON(1); + qtd->payload_addr = 0; +} + +static int handshake(struct usb_hcd *hcd, u32 reg, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = reg_read32(hcd->regs, reg); + if (result == ~0) + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct usb_hcd *hcd) +{ + int retval; + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + u32 command = reg_read32(hcd->regs, HC_USBCMD); + + command |= CMD_RESET; + reg_write32(hcd->regs, HC_USBCMD, command); + hcd->state = HC_STATE_HALT; + priv->next_statechange = jiffies; + retval = handshake(hcd, HC_USBCMD, + CMD_RESET, 0, 250 * 1000); + return retval; +} + +static struct isp1760_qh *qh_alloc(gfp_t flags) +{ + struct isp1760_qh *qh; + + qh = kmem_cache_zalloc(qh_cachep, flags); + if (!qh) + return NULL; + + INIT_LIST_HEAD(&qh->qh_list); + INIT_LIST_HEAD(&qh->qtd_list); + qh->slot = -1; + + return qh; +} + +static void qh_free(struct isp1760_qh *qh) +{ + WARN_ON(!list_empty(&qh->qtd_list)); + WARN_ON(qh->slot > -1); + kmem_cache_free(qh_cachep, qh); +} + +/* one-time init, only for memory state */ +static int priv_init(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 hcc_params; + int i; + + spin_lock_init(&priv->lock); + + for (i = 0; i < QH_END; i++) + INIT_LIST_HEAD(&priv->qh_list[i]); + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + priv->periodic_size = DEFAULT_I_TDPS; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = reg_read32(hcd->regs, HC_HCCPARAMS); + /* full frame cache */ + if (HCC_ISOC_CACHE(hcc_params)) + priv->i_thresh = 8; + else /* N microframes cached */ + priv->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + return 0; +} + +static int isp1760_hc_setup(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int result; + u32 scratch, hwmode; + + reg_write32(hcd->regs, HC_SCRATCH_REG, 0xdeadbabe); + /* Change bus pattern */ + scratch = reg_read32(hcd->regs, HC_CHIP_ID_REG); + scratch = reg_read32(hcd->regs, HC_SCRATCH_REG); + if (scratch != 0xdeadbabe) { + dev_err(hcd->self.controller, "Scratch test failed.\n"); + return -ENODEV; + } + + /* + * The RESET_HC bit in the SW_RESET register is supposed to reset the + * host controller without touching the CPU interface registers, but at + * least on the ISP1761 it seems to behave as the RESET_ALL bit and + * reset the whole device. We thus can't use it here, so let's reset + * the host controller through the EHCI USB Command register. The device + * has been reset in core code anyway, so this shouldn't matter. + */ + reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, 0); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + + result = ehci_reset(hcd); + if (result) + return result; + + /* Step 11 passed */ + + /* ATL reset */ + hwmode = reg_read32(hcd->regs, HC_HW_MODE_CTRL) & ~ALL_ATX_RESET; + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); + mdelay(10); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); + + reg_write32(hcd->regs, HC_INTERRUPT_ENABLE, INTERRUPT_ENABLE_MASK); + + priv->hcs_params = reg_read32(hcd->regs, HC_HCSPARAMS); + + return priv_init(hcd); +} + +static u32 base_to_chip(u32 base) +{ + return ((base - 0x400) >> 3); +} + +static int last_qtd_of_urb(struct isp1760_qtd *qtd, struct isp1760_qh *qh) +{ + struct urb *urb; + + if (list_is_last(&qtd->qtd_list, &qh->qtd_list)) + return 1; + + urb = qtd->urb; + qtd = list_entry(qtd->qtd_list.next, typeof(*qtd), qtd_list); + return (qtd->urb != urb); +} + +/* magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +static void create_ptd_atl(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + u32 maxpacket; + u32 multi; + u32 rl = RL_COUNTER; + u32 nak = NAK_COUNTER; + + memset(ptd, 0, sizeof(*ptd)); + + /* according to 3.6.2, max packet len can not be > 0x400 */ + maxpacket = usb_maxpacket(qtd->urb->dev, qtd->urb->pipe, + usb_pipeout(qtd->urb->pipe)); + multi = 1 + ((maxpacket >> 11) & 0x3); + maxpacket &= 0x7ff; + + /* DW0 */ + ptd->dw0 = DW0_VALID_BIT; + ptd->dw0 |= TO_DW0_LENGTH(qtd->length); + ptd->dw0 |= TO_DW0_MAXPACKET(maxpacket); + ptd->dw0 |= TO_DW0_ENDPOINT(usb_pipeendpoint(qtd->urb->pipe)); + + /* DW1 */ + ptd->dw1 = usb_pipeendpoint(qtd->urb->pipe) >> 1; + ptd->dw1 |= TO_DW1_DEVICE_ADDR(usb_pipedevice(qtd->urb->pipe)); + ptd->dw1 |= TO_DW1_PID_TOKEN(qtd->packet_type); + + if (usb_pipebulk(qtd->urb->pipe)) + ptd->dw1 |= DW1_TRANS_BULK; + else if (usb_pipeint(qtd->urb->pipe)) + ptd->dw1 |= DW1_TRANS_INT; + + if (qtd->urb->dev->speed != USB_SPEED_HIGH) { + /* split transaction */ + + ptd->dw1 |= DW1_TRANS_SPLIT; + if (qtd->urb->dev->speed == USB_SPEED_LOW) + ptd->dw1 |= DW1_SE_USB_LOSPEED; + + ptd->dw1 |= TO_DW1_PORT_NUM(qtd->urb->dev->ttport); + ptd->dw1 |= TO_DW1_HUB_NUM(qtd->urb->dev->tt->hub->devnum); + + /* SE bit for Split INT transfers */ + if (usb_pipeint(qtd->urb->pipe) && + (qtd->urb->dev->speed == USB_SPEED_LOW)) + ptd->dw1 |= 2 << 16; + + rl = 0; + nak = 0; + } else { + ptd->dw0 |= TO_DW0_MULTI(multi); + if (usb_pipecontrol(qtd->urb->pipe) || + usb_pipebulk(qtd->urb->pipe)) + ptd->dw3 |= TO_DW3_PING(qh->ping); + } + /* DW2 */ + ptd->dw2 = 0; + ptd->dw2 |= TO_DW2_DATA_START_ADDR(base_to_chip(qtd->payload_addr)); + ptd->dw2 |= TO_DW2_RL(rl); + + /* DW3 */ + ptd->dw3 |= TO_DW3_NAKCOUNT(nak); + ptd->dw3 |= TO_DW3_DATA_TOGGLE(qh->toggle); + if (usb_pipecontrol(qtd->urb->pipe)) { + if (qtd->data_buffer == qtd->urb->setup_packet) + ptd->dw3 &= ~TO_DW3_DATA_TOGGLE(1); + else if (last_qtd_of_urb(qtd, qh)) + ptd->dw3 |= TO_DW3_DATA_TOGGLE(1); + } + + ptd->dw3 |= DW3_ACTIVE_BIT; + /* Cerr */ + ptd->dw3 |= TO_DW3_CERR(ERR_COUNTER); +} + +static void transform_add_int(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + u32 usof; + u32 period; + + /* + * Most of this is guessing. ISP1761 datasheet is quite unclear, and + * the algorithm from the original Philips driver code, which was + * pretty much used in this driver before as well, is quite horrendous + * and, i believe, incorrect. The code below follows the datasheet and + * USB2.0 spec as far as I can tell, and plug/unplug seems to be much + * more reliable this way (fingers crossed...). + */ + + if (qtd->urb->dev->speed == USB_SPEED_HIGH) { + /* urb->interval is in units of microframes (1/8 ms) */ + period = qtd->urb->interval >> 3; + + if (qtd->urb->interval > 4) + usof = 0x01; /* One bit set => + interval 1 ms * uFrame-match */ + else if (qtd->urb->interval > 2) + usof = 0x22; /* Two bits set => interval 1/2 ms */ + else if (qtd->urb->interval > 1) + usof = 0x55; /* Four bits set => interval 1/4 ms */ + else + usof = 0xff; /* All bits set => interval 1/8 ms */ + } else { + /* urb->interval is in units of frames (1 ms) */ + period = qtd->urb->interval; + usof = 0x0f; /* Execute Start Split on any of the + four first uFrames */ + + /* + * First 8 bits in dw5 is uSCS and "specifies which uSOF the + * complete split needs to be sent. Valid only for IN." Also, + * "All bits can be set to one for every transfer." (p 82, + * ISP1761 data sheet.) 0x1c is from Philips driver. Where did + * that number come from? 0xff seems to work fine... + */ + /* ptd->dw5 = 0x1c; */ + ptd->dw5 = 0xff; /* Execute Complete Split on any uFrame */ + } + + period = period >> 1;/* Ensure equal or shorter period than requested */ + period &= 0xf8; /* Mask off too large values and lowest unused 3 bits */ + + ptd->dw2 |= period; + ptd->dw4 = usof; +} + +static void create_ptd_int(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + create_ptd_atl(qh, qtd, ptd); + transform_add_int(qh, qtd, ptd); +} + +static void isp1760_urb_done(struct usb_hcd *hcd, struct urb *urb) +__releases(priv->lock) +__acquires(priv->lock) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (!urb->unlinked) { + if (urb->status == -EINPROGRESS) + urb->status = 0; + } + + if (usb_pipein(urb->pipe) && usb_pipetype(urb->pipe) != PIPE_CONTROL) { + void *ptr; + for (ptr = urb->transfer_buffer; + ptr < urb->transfer_buffer + urb->transfer_buffer_length; + ptr += PAGE_SIZE) + flush_dcache_page(virt_to_page(ptr)); + } + + /* complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock(&priv->lock); + usb_hcd_giveback_urb(hcd, urb, urb->status); + spin_lock(&priv->lock); +} + +static struct isp1760_qtd *qtd_alloc(gfp_t flags, struct urb *urb, + u8 packet_type) +{ + struct isp1760_qtd *qtd; + + qtd = kmem_cache_zalloc(qtd_cachep, flags); + if (!qtd) + return NULL; + + INIT_LIST_HEAD(&qtd->qtd_list); + qtd->urb = urb; + qtd->packet_type = packet_type; + qtd->status = QTD_ENQUEUED; + qtd->actual_length = 0; + + return qtd; +} + +static void qtd_free(struct isp1760_qtd *qtd) +{ + WARN_ON(qtd->payload_addr); + kmem_cache_free(qtd_cachep, qtd); +} + +static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, + struct isp1760_slotinfo *slots, + struct isp1760_qtd *qtd, struct isp1760_qh *qh, + struct ptd *ptd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + + WARN_ON((slot < 0) || (slot > 31)); + WARN_ON(qtd->length && !qtd->payload_addr); + WARN_ON(slots[slot].qtd); + WARN_ON(slots[slot].qh); + WARN_ON(qtd->status != QTD_PAYLOAD_ALLOC); + + /* Make sure done map has not triggered from some unlinked transfer */ + if (ptd_offset == ATL_PTD_OFFSET) { + priv->atl_done_map |= reg_read32(hcd->regs, + HC_ATL_PTD_DONEMAP_REG); + priv->atl_done_map &= ~(1 << slot); + } else { + priv->int_done_map |= reg_read32(hcd->regs, + HC_INT_PTD_DONEMAP_REG); + priv->int_done_map &= ~(1 << slot); + } + + qh->slot = slot; + qtd->status = QTD_XFER_STARTED; + slots[slot].timestamp = jiffies; + slots[slot].qtd = qtd; + slots[slot].qh = qh; + ptd_write(hcd->regs, ptd_offset, slot, ptd); + + if (ptd_offset == ATL_PTD_OFFSET) { + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + } else { + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + } +} + +static int is_short_bulk(struct isp1760_qtd *qtd) +{ + return (usb_pipebulk(qtd->urb->pipe) && + (qtd->actual_length < qtd->length)); +} + +static void collect_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct list_head *urb_list) +{ + int last_qtd; + struct isp1760_qtd *qtd, *qtd_next; + struct urb_listitem *urb_listitem; + + list_for_each_entry_safe(qtd, qtd_next, &qh->qtd_list, qtd_list) { + if (qtd->status < QTD_XFER_COMPLETE) + break; + + last_qtd = last_qtd_of_urb(qtd, qh); + + if ((!last_qtd) && (qtd->status == QTD_RETIRE)) + qtd_next->status = QTD_RETIRE; + + if (qtd->status == QTD_XFER_COMPLETE) { + if (qtd->actual_length) { + switch (qtd->packet_type) { + case IN_PID: + mem_reads8(hcd->regs, qtd->payload_addr, + qtd->data_buffer, + qtd->actual_length); + /* Fall through (?) */ + case OUT_PID: + qtd->urb->actual_length += + qtd->actual_length; + /* Fall through ... */ + case SETUP_PID: + break; + } + } + + if (is_short_bulk(qtd)) { + if (qtd->urb->transfer_flags & URB_SHORT_NOT_OK) + qtd->urb->status = -EREMOTEIO; + if (!last_qtd) + qtd_next->status = QTD_RETIRE; + } + } + + if (qtd->payload_addr) + free_mem(hcd, qtd); + + if (last_qtd) { + if ((qtd->status == QTD_RETIRE) && + (qtd->urb->status == -EINPROGRESS)) + qtd->urb->status = -EPIPE; + /* Defer calling of urb_done() since it releases lock */ + urb_listitem = kmem_cache_zalloc(urb_listitem_cachep, + GFP_ATOMIC); + if (unlikely(!urb_listitem)) + break; /* Try again on next call */ + urb_listitem->urb = qtd->urb; + list_add_tail(&urb_listitem->urb_list, urb_list); + } + + list_del(&qtd->qtd_list); + qtd_free(qtd); + } +} + +#define ENQUEUE_DEPTH 2 +static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int ptd_offset; + struct isp1760_slotinfo *slots; + int curr_slot, free_slot; + int n; + struct ptd ptd; + struct isp1760_qtd *qtd; + + if (unlikely(list_empty(&qh->qtd_list))) { + WARN_ON(1); + return; + } + + /* Make sure this endpoint's TT buffer is clean before queueing ptds */ + if (qh->tt_buffer_dirty) + return; + + if (usb_pipeint(list_entry(qh->qtd_list.next, struct isp1760_qtd, + qtd_list)->urb->pipe)) { + ptd_offset = INT_PTD_OFFSET; + slots = priv->int_slots; + } else { + ptd_offset = ATL_PTD_OFFSET; + slots = priv->atl_slots; + } + + free_slot = -1; + for (curr_slot = 0; curr_slot < 32; curr_slot++) { + if ((free_slot == -1) && (slots[curr_slot].qtd == NULL)) + free_slot = curr_slot; + if (slots[curr_slot].qh == qh) + break; + } + + n = 0; + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { + if (qtd->status == QTD_ENQUEUED) { + WARN_ON(qtd->payload_addr); + alloc_mem(hcd, qtd); + if ((qtd->length) && (!qtd->payload_addr)) + break; + + if ((qtd->length) && + ((qtd->packet_type == SETUP_PID) || + (qtd->packet_type == OUT_PID))) { + mem_writes8(hcd->regs, qtd->payload_addr, + qtd->data_buffer, qtd->length); + } + + qtd->status = QTD_PAYLOAD_ALLOC; + } + + if (qtd->status == QTD_PAYLOAD_ALLOC) { +/* + if ((curr_slot > 31) && (free_slot == -1)) + dev_dbg(hcd->self.controller, "%s: No slot " + "available for transfer\n", __func__); +*/ + /* Start xfer for this endpoint if not already done */ + if ((curr_slot > 31) && (free_slot > -1)) { + if (usb_pipeint(qtd->urb->pipe)) + create_ptd_int(qh, qtd, &ptd); + else + create_ptd_atl(qh, qtd, &ptd); + + start_bus_transfer(hcd, ptd_offset, free_slot, + slots, qtd, qh, &ptd); + curr_slot = free_slot; + } + + n++; + if (n >= ENQUEUE_DEPTH) + break; + } + } +} + +static void schedule_ptds(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv; + struct isp1760_qh *qh, *qh_next; + struct list_head *ep_queue; + LIST_HEAD(urb_list); + struct urb_listitem *urb_listitem, *urb_listitem_next; + int i; + + if (!hcd) { + WARN_ON(1); + return; + } + + priv = hcd_to_priv(hcd); + + /* + * check finished/retired xfers, transfer payloads, call urb_done() + */ + for (i = 0; i < QH_END; i++) { + ep_queue = &priv->qh_list[i]; + list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) { + collect_qtds(hcd, qh, &urb_list); + if (list_empty(&qh->qtd_list)) + list_del(&qh->qh_list); + } + } + + list_for_each_entry_safe(urb_listitem, urb_listitem_next, &urb_list, + urb_list) { + isp1760_urb_done(hcd, urb_listitem->urb); + kmem_cache_free(urb_listitem_cachep, urb_listitem); + } + + /* + * Schedule packets for transfer. + * + * According to USB2.0 specification: + * + * 1st prio: interrupt xfers, up to 80 % of bandwidth + * 2nd prio: control xfers + * 3rd prio: bulk xfers + * + * ... but let's use a simpler scheme here (mostly because ISP1761 doc + * is very unclear on how to prioritize traffic): + * + * 1) Enqueue any queued control transfers, as long as payload chip mem + * and PTD ATL slots are available. + * 2) Enqueue any queued INT transfers, as long as payload chip mem + * and PTD INT slots are available. + * 3) Enqueue any queued bulk transfers, as long as payload chip mem + * and PTD ATL slots are available. + * + * Use double buffering (ENQUEUE_DEPTH==2) as a compromise between + * conservation of chip mem and performance. + * + * I'm sure this scheme could be improved upon! + */ + for (i = 0; i < QH_END; i++) { + ep_queue = &priv->qh_list[i]; + list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) + enqueue_qtds(hcd, qh); + } +} + +#define PTD_STATE_QTD_DONE 1 +#define PTD_STATE_QTD_RELOAD 2 +#define PTD_STATE_URB_RETIRE 3 + +static int check_int_transfer(struct usb_hcd *hcd, struct ptd *ptd, + struct urb *urb) +{ + __dw dw4; + int i; + + dw4 = ptd->dw4; + dw4 >>= 8; + + /* FIXME: ISP1761 datasheet does not say what to do with these. Do we + need to handle these errors? Is it done in hardware? */ + + if (ptd->dw3 & DW3_HALT_BIT) { + + urb->status = -EPROTO; /* Default unknown error */ + + for (i = 0; i < 8; i++) { + switch (dw4 & 0x7) { + case INT_UNDERRUN: + dev_dbg(hcd->self.controller, "%s: underrun " + "during uFrame %d\n", + __func__, i); + urb->status = -ECOMM; /* Could not write data */ + break; + case INT_EXACT: + dev_dbg(hcd->self.controller, "%s: transaction " + "error during uFrame %d\n", + __func__, i); + urb->status = -EPROTO; /* timeout, bad CRC, PID + error etc. */ + break; + case INT_BABBLE: + dev_dbg(hcd->self.controller, "%s: babble " + "error during uFrame %d\n", + __func__, i); + urb->status = -EOVERFLOW; + break; + } + dw4 >>= 3; + } + + return PTD_STATE_URB_RETIRE; + } + + return PTD_STATE_QTD_DONE; +} + +static int check_atl_transfer(struct usb_hcd *hcd, struct ptd *ptd, + struct urb *urb) +{ + WARN_ON(!ptd); + if (ptd->dw3 & DW3_HALT_BIT) { + if (ptd->dw3 & DW3_BABBLE_BIT) + urb->status = -EOVERFLOW; + else if (FROM_DW3_CERR(ptd->dw3)) + urb->status = -EPIPE; /* Stall */ + else if (ptd->dw3 & DW3_ERROR_BIT) + urb->status = -EPROTO; /* XactErr */ + else + urb->status = -EPROTO; /* Unknown */ +/* + dev_dbg(hcd->self.controller, "%s: ptd error:\n" + " dw0: %08x dw1: %08x dw2: %08x dw3: %08x\n" + " dw4: %08x dw5: %08x dw6: %08x dw7: %08x\n", + __func__, + ptd->dw0, ptd->dw1, ptd->dw2, ptd->dw3, + ptd->dw4, ptd->dw5, ptd->dw6, ptd->dw7); +*/ + return PTD_STATE_URB_RETIRE; + } + + if ((ptd->dw3 & DW3_ERROR_BIT) && (ptd->dw3 & DW3_ACTIVE_BIT)) { + /* Transfer Error, *but* active and no HALT -> reload */ + dev_dbg(hcd->self.controller, "PID error; reloading ptd\n"); + return PTD_STATE_QTD_RELOAD; + } + + if (!FROM_DW3_NAKCOUNT(ptd->dw3) && (ptd->dw3 & DW3_ACTIVE_BIT)) { + /* + * NAKs are handled in HW by the chip. Usually if the + * device is not able to send data fast enough. + * This happens mostly on slower hardware. + */ + return PTD_STATE_QTD_RELOAD; + } + + return PTD_STATE_QTD_DONE; +} + +static void handle_done_ptds(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct ptd ptd; + struct isp1760_qh *qh; + int slot; + int state; + struct isp1760_slotinfo *slots; + u32 ptd_offset; + struct isp1760_qtd *qtd; + int modified; + int skip_map; + + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + priv->int_done_map &= ~skip_map; + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + priv->atl_done_map &= ~skip_map; + + modified = priv->int_done_map || priv->atl_done_map; + + while (priv->int_done_map || priv->atl_done_map) { + if (priv->int_done_map) { + /* INT ptd */ + slot = __ffs(priv->int_done_map); + priv->int_done_map &= ~(1 << slot); + slots = priv->int_slots; + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); + continue; + } + ptd_offset = INT_PTD_OFFSET; + ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); + state = check_int_transfer(hcd, &ptd, + slots[slot].qtd->urb); + } else { + /* ATL ptd */ + slot = __ffs(priv->atl_done_map); + priv->atl_done_map &= ~(1 << slot); + slots = priv->atl_slots; + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); + continue; + } + ptd_offset = ATL_PTD_OFFSET; + ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); + state = check_atl_transfer(hcd, &ptd, + slots[slot].qtd->urb); + } + + qtd = slots[slot].qtd; + slots[slot].qtd = NULL; + qh = slots[slot].qh; + slots[slot].qh = NULL; + qh->slot = -1; + + WARN_ON(qtd->status != QTD_XFER_STARTED); + + switch (state) { + case PTD_STATE_QTD_DONE: + if ((usb_pipeint(qtd->urb->pipe)) && + (qtd->urb->dev->speed != USB_SPEED_HIGH)) + qtd->actual_length = + FROM_DW3_SCS_NRBYTESTRANSFERRED(ptd.dw3); + else + qtd->actual_length = + FROM_DW3_NRBYTESTRANSFERRED(ptd.dw3); + + qtd->status = QTD_XFER_COMPLETE; + if (list_is_last(&qtd->qtd_list, &qh->qtd_list) || + is_short_bulk(qtd)) + qtd = NULL; + else + qtd = list_entry(qtd->qtd_list.next, + typeof(*qtd), qtd_list); + + qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); + qh->ping = FROM_DW3_PING(ptd.dw3); + break; + + case PTD_STATE_QTD_RELOAD: /* QTD_RETRY, for atls only */ + qtd->status = QTD_PAYLOAD_ALLOC; + ptd.dw0 |= DW0_VALID_BIT; + /* RL counter = ERR counter */ + ptd.dw3 &= ~TO_DW3_NAKCOUNT(0xf); + ptd.dw3 |= TO_DW3_NAKCOUNT(FROM_DW2_RL(ptd.dw2)); + ptd.dw3 &= ~TO_DW3_CERR(3); + ptd.dw3 |= TO_DW3_CERR(ERR_COUNTER); + qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); + qh->ping = FROM_DW3_PING(ptd.dw3); + break; + + case PTD_STATE_URB_RETIRE: + qtd->status = QTD_RETIRE; + if ((qtd->urb->dev->speed != USB_SPEED_HIGH) && + (qtd->urb->status != -EPIPE) && + (qtd->urb->status != -EREMOTEIO)) { + qh->tt_buffer_dirty = 1; + if (usb_hub_clear_tt_buffer(qtd->urb)) + /* Clear failed; let's hope things work + anyway */ + qh->tt_buffer_dirty = 0; + } + qtd = NULL; + qh->toggle = 0; + qh->ping = 0; + break; + + default: + WARN_ON(1); + continue; + } + + if (qtd && (qtd->status == QTD_PAYLOAD_ALLOC)) { + if (slots == priv->int_slots) { + if (state == PTD_STATE_QTD_RELOAD) + dev_err(hcd->self.controller, + "%s: PTD_STATE_QTD_RELOAD on " + "interrupt packet\n", __func__); + if (state != PTD_STATE_QTD_RELOAD) + create_ptd_int(qh, qtd, &ptd); + } else { + if (state != PTD_STATE_QTD_RELOAD) + create_ptd_atl(qh, qtd, &ptd); + } + + start_bus_transfer(hcd, ptd_offset, slot, slots, qtd, + qh, &ptd); + } + } + + if (modified) + schedule_ptds(hcd); +} + +static irqreturn_t isp1760_irq(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 imask; + irqreturn_t irqret = IRQ_NONE; + + spin_lock(&priv->lock); + + if (!(hcd->state & HC_STATE_RUNNING)) + goto leave; + + imask = reg_read32(hcd->regs, HC_INTERRUPT_REG); + if (unlikely(!imask)) + goto leave; + reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ + + priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); + priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); + + handle_done_ptds(hcd); + + irqret = IRQ_HANDLED; +leave: + spin_unlock(&priv->lock); + + return irqret; +} + +/* + * Workaround for problem described in chip errata 2: + * + * Sometimes interrupts are not generated when ATL (not INT?) completion occurs. + * One solution suggested in the errata is to use SOF interrupts _instead_of_ + * ATL done interrupts (the "instead of" might be important since it seems + * enabling ATL interrupts also causes the chip to sometimes - rarely - "forget" + * to set the PTD's done bit in addition to not generating an interrupt!). + * + * So if we use SOF + ATL interrupts, we sometimes get stale PTDs since their + * done bit is not being set. This is bad - it blocks the endpoint until reboot. + * + * If we use SOF interrupts only, we get latency between ptd completion and the + * actual handling. This is very noticeable in testusb runs which takes several + * minutes longer without ATL interrupts. + * + * A better solution is to run the code below every SLOT_CHECK_PERIOD ms. If it + * finds active ATL slots which are older than SLOT_TIMEOUT ms, it checks the + * slot's ACTIVE and VALID bits. If these are not set, the ptd is considered + * completed and its done map bit is set. + * + * The values of SLOT_TIMEOUT and SLOT_CHECK_PERIOD have been arbitrarily chosen + * not to cause too much lag when this HW bug occurs, while still hopefully + * ensuring that the check does not falsely trigger. + */ +#define SLOT_TIMEOUT 300 +#define SLOT_CHECK_PERIOD 200 +static struct timer_list errata2_timer; + +static void errata2_function(unsigned long data) +{ + struct usb_hcd *hcd = (struct usb_hcd *) data; + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int slot; + struct ptd ptd; + unsigned long spinflags; + + spin_lock_irqsave(&priv->lock, spinflags); + + for (slot = 0; slot < 32; slot++) + if (priv->atl_slots[slot].qh && time_after(jiffies, + priv->atl_slots[slot].timestamp + + SLOT_TIMEOUT * HZ / 1000)) { + ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); + if (!FROM_DW0_VALID(ptd.dw0) && + !FROM_DW3_ACTIVE(ptd.dw3)) + priv->atl_done_map |= 1 << slot; + } + + if (priv->atl_done_map) + handle_done_ptds(hcd); + + spin_unlock_irqrestore(&priv->lock, spinflags); + + errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; + add_timer(&errata2_timer); +} + +static int isp1760_run(struct usb_hcd *hcd) +{ + int retval; + u32 temp; + u32 command; + u32 chipid; + + hcd->uses_new_polling = 1; + + hcd->state = HC_STATE_RUNNING; + + /* Set PTD interrupt AND & OR maps */ + reg_write32(hcd->regs, HC_ATL_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_ATL_IRQ_MASK_OR_REG, 0xffffffff); + reg_write32(hcd->regs, HC_INT_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_INT_IRQ_MASK_OR_REG, 0xffffffff); + reg_write32(hcd->regs, HC_ISO_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_ISO_IRQ_MASK_OR_REG, 0xffffffff); + /* step 23 passed */ + + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp | HW_GLOBAL_INTR_EN); + + command = reg_read32(hcd->regs, HC_USBCMD); + command &= ~(CMD_LRESET|CMD_RESET); + command |= CMD_RUN; + reg_write32(hcd->regs, HC_USBCMD, command); + + retval = handshake(hcd, HC_USBCMD, CMD_RUN, CMD_RUN, 250 * 1000); + if (retval) + return retval; + + /* + * XXX + * Spec says to write FLAG_CF as last config action, priv code grabs + * the semaphore while doing so. + */ + down_write(&ehci_cf_port_reset_rwsem); + reg_write32(hcd->regs, HC_CONFIGFLAG, FLAG_CF); + + retval = handshake(hcd, HC_CONFIGFLAG, FLAG_CF, FLAG_CF, 250 * 1000); + up_write(&ehci_cf_port_reset_rwsem); + if (retval) + return retval; + + init_timer(&errata2_timer); + errata2_timer.function = errata2_function; + errata2_timer.data = (unsigned long) hcd; + errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; + add_timer(&errata2_timer); + + chipid = reg_read32(hcd->regs, HC_CHIP_ID_REG); + dev_info(hcd->self.controller, "USB ISP %04x HW rev. %d started\n", + chipid & 0xffff, chipid >> 16); + + /* PTD Register Init Part 2, Step 28 */ + + /* Setup registers controlling PTD checking */ + reg_write32(hcd->regs, HC_ATL_PTD_LASTPTD_REG, 0x80000000); + reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); + reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, + ATL_BUF_FILL | INT_BUF_FILL); + + /* GRR this is run-once init(), being done every time the HC starts. + * So long as they're part of class devices, we can't do it init() + * since the class device isn't created that early. + */ + return 0; +} + +static int qtd_fill(struct isp1760_qtd *qtd, void *databuffer, size_t len) +{ + qtd->data_buffer = databuffer; + + if (len > MAX_PAYLOAD_SIZE) + len = MAX_PAYLOAD_SIZE; + qtd->length = len; + + return qtd->length; +} + +static void qtd_list_free(struct list_head *qtd_list) +{ + struct isp1760_qtd *qtd, *qtd_next; + + list_for_each_entry_safe(qtd, qtd_next, qtd_list, qtd_list) { + list_del(&qtd->qtd_list); + qtd_free(qtd); + } +} + +/* + * Packetize urb->transfer_buffer into list of packets of size wMaxPacketSize. + * Also calculate the PID type (SETUP/IN/OUT) for each packet. + */ +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) +static void packetize_urb(struct usb_hcd *hcd, + struct urb *urb, struct list_head *head, gfp_t flags) +{ + struct isp1760_qtd *qtd; + void *buf; + int len, maxpacketsize; + u8 packet_type; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + + if (!urb->transfer_buffer && urb->transfer_buffer_length) { + /* XXX This looks like usb storage / SCSI bug */ + dev_err(hcd->self.controller, + "buf is null, dma is %08lx len is %d\n", + (long unsigned)urb->transfer_dma, + urb->transfer_buffer_length); + WARN_ON(1); + } + + if (usb_pipein(urb->pipe)) + packet_type = IN_PID; + else + packet_type = OUT_PID; + + if (usb_pipecontrol(urb->pipe)) { + qtd = qtd_alloc(flags, urb, SETUP_PID); + if (!qtd) + goto cleanup; + qtd_fill(qtd, urb->setup_packet, sizeof(struct usb_ctrlrequest)); + list_add_tail(&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (urb->transfer_buffer_length == 0) + packet_type = IN_PID; + } + + maxpacketsize = max_packet(usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe))); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + buf = urb->transfer_buffer; + len = urb->transfer_buffer_length; + + for (;;) { + int this_qtd_len; + + qtd = qtd_alloc(flags, urb, packet_type); + if (!qtd) + goto cleanup; + this_qtd_len = qtd_fill(qtd, buf, len); + list_add_tail(&qtd->qtd_list, head); + + len -= this_qtd_len; + buf += this_qtd_len; + + if (len <= 0) + break; + } + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (urb->transfer_buffer_length != 0) { + int one_more = 0; + + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + if (packet_type == IN_PID) + packet_type = OUT_PID; + else + packet_type = IN_PID; + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % + maxpacketsize)) { + one_more = 1; + } + if (one_more) { + qtd = qtd_alloc(flags, urb, packet_type); + if (!qtd) + goto cleanup; + + /* never any data in such packets */ + qtd_fill(qtd, NULL, 0); + list_add_tail(&qtd->qtd_list, head); + } + } + + return; + +cleanup: + qtd_list_free(head); +} + +static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct list_head *ep_queue; + struct isp1760_qh *qh, *qhit; + unsigned long spinflags; + LIST_HEAD(new_qtds); + int retval; + int qh_in_queue; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ep_queue = &priv->qh_list[QH_CONTROL]; + break; + case PIPE_BULK: + ep_queue = &priv->qh_list[QH_BULK]; + break; + case PIPE_INTERRUPT: + if (urb->interval < 0) + return -EINVAL; + /* FIXME: Check bandwidth */ + ep_queue = &priv->qh_list[QH_INTERRUPT]; + break; + case PIPE_ISOCHRONOUS: + dev_err(hcd->self.controller, "%s: isochronous USB packets " + "not yet supported\n", + __func__); + return -EPIPE; + default: + dev_err(hcd->self.controller, "%s: unknown pipe type\n", + __func__); + return -EPIPE; + } + + if (usb_pipein(urb->pipe)) + urb->actual_length = 0; + + packetize_urb(hcd, urb, &new_qtds, mem_flags); + if (list_empty(&new_qtds)) + return -ENOMEM; + + retval = 0; + spin_lock_irqsave(&priv->lock, spinflags); + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + retval = -ESHUTDOWN; + qtd_list_free(&new_qtds); + goto out; + } + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) { + qtd_list_free(&new_qtds); + goto out; + } + + qh = urb->ep->hcpriv; + if (qh) { + qh_in_queue = 0; + list_for_each_entry(qhit, ep_queue, qh_list) { + if (qhit == qh) { + qh_in_queue = 1; + break; + } + } + if (!qh_in_queue) + list_add_tail(&qh->qh_list, ep_queue); + } else { + qh = qh_alloc(GFP_ATOMIC); + if (!qh) { + retval = -ENOMEM; + usb_hcd_unlink_urb_from_ep(hcd, urb); + qtd_list_free(&new_qtds); + goto out; + } + list_add_tail(&qh->qh_list, ep_queue); + urb->ep->hcpriv = qh; + } + + list_splice_tail(&new_qtds, &qh->qtd_list); + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); + return retval; +} + +static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, + struct isp1760_qh *qh) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + + WARN_ON(qh->slot == -1); + + /* We need to forcefully reclaim the slot since some transfers never + return, e.g. interrupt transfers and NAKed bulk transfers. */ + if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) { + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + priv->atl_slots[qh->slot].qh = NULL; + priv->atl_slots[qh->slot].qtd = NULL; + } else { + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + priv->int_slots[qh->slot].qh = NULL; + priv->int_slots[qh->slot].qtd = NULL; + } + + qh->slot = -1; +} + +/* + * Retire the qtds beginning at 'qtd' and belonging all to the same urb, killing + * any active transfer belonging to the urb in the process. + */ +static void dequeue_urb_from_qtd(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd) +{ + struct urb *urb; + int urb_was_running; + + urb = qtd->urb; + urb_was_running = 0; + list_for_each_entry_from(qtd, &qh->qtd_list, qtd_list) { + if (qtd->urb != urb) + break; + + if (qtd->status >= QTD_XFER_STARTED) + urb_was_running = 1; + if (last_qtd_of_urb(qtd, qh) && + (qtd->status >= QTD_XFER_COMPLETE)) + urb_was_running = 0; + + if (qtd->status == QTD_XFER_STARTED) + kill_transfer(hcd, urb, qh); + qtd->status = QTD_RETIRE; + } + + if ((urb->dev->speed != USB_SPEED_HIGH) && urb_was_running) { + qh->tt_buffer_dirty = 1; + if (usb_hub_clear_tt_buffer(urb)) + /* Clear failed; let's hope things work anyway */ + qh->tt_buffer_dirty = 0; + } +} + +static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + int retval = 0; + + spin_lock_irqsave(&priv->lock, spinflags); + retval = usb_hcd_check_unlink_urb(hcd, urb, status); + if (retval) + goto out; + + qh = urb->ep->hcpriv; + if (!qh) { + retval = -EINVAL; + goto out; + } + + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) + if (qtd->urb == urb) { + dequeue_urb_from_qtd(hcd, qh, qtd); + list_move(&qtd->qtd_list, &qh->qtd_list); + break; + } + + urb->status = status; + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); + return retval; +} + +static void isp1760_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; + struct isp1760_qh *qh, *qh_iter; + int i; + + spin_lock_irqsave(&priv->lock, spinflags); + + qh = ep->hcpriv; + if (!qh) + goto out; + + WARN_ON(!list_empty(&qh->qtd_list)); + + for (i = 0; i < QH_END; i++) + list_for_each_entry(qh_iter, &priv->qh_list[i], qh_list) + if (qh_iter == qh) { + list_del(&qh_iter->qh_list); + i = QH_END; + break; + } + qh_free(qh); + ep->hcpriv = NULL; + + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); +} + +static int isp1760_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 temp, status = 0; + u32 mask; + int retval = 1; + unsigned long flags; + + /* if !PM, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + mask = PORT_CSC; + + spin_lock_irqsave(&priv->lock, flags); + temp = reg_read32(hcd->regs, HC_PORTSC1); + + if (temp & PORT_OWNER) { + if (temp & PORT_CSC) { + temp &= ~PORT_CSC; + reg_write32(hcd->regs, HC_PORTSC1, temp); + goto done; + } + } + + /* + * Return status information even for ports with OWNER set. + * Otherwise hub_wq wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if ((temp & mask) != 0 + || ((temp & PORT_RESUME) != 0 + && time_after_eq(jiffies, + priv->reset_done))) { + buf [0] |= 1 << (0 + 1); + status = STS_PCD; + } + /* FIXME autosuspend idle root hubs */ +done: + spin_unlock_irqrestore(&priv->lock, flags); + return status ? retval : 0; +} + +static void isp1760_hub_descriptor(struct isp1760_hcd *priv, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(priv->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + /* priv 1.0, 2.3.9 says 20ms max */ + desc->bPwrOn2PwrGood = 10; + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + /* per-port overcurrent reporting */ + temp = 0x0008; + if (HCS_PPC(priv->hcs_params)) + /* per-port power control */ + temp |= 0x0001; + else + /* no power switching */ + temp |= 0x0002; + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) + +static int check_reset_complete(struct usb_hcd *hcd, int index, + int port_status) +{ + if (!(port_status & PORT_CONNECT)) + return port_status; + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + + dev_info(hcd->self.controller, + "port %d full speed --> companion\n", + index + 1); + + port_status |= PORT_OWNER; + port_status &= ~PORT_RWC_BITS; + reg_write32(hcd->regs, HC_PORTSC1, port_status); + + } else + dev_info(hcd->self.controller, "port %d high speed\n", + index + 1); + + return port_status; +} + +static int isp1760_hub_control(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int ports = HCS_N_PORTS(priv->hcs_params); + u32 temp, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave(&priv->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = reg_read32(hcd->regs, HC_PORTSC1); + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, hub_wq needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_PE); + break; + case USB_PORT_FEAT_C_ENABLE: + /* XXX error? */ + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + + if (temp & PORT_SUSPEND) { + if ((temp & PORT_PE) == 0) + goto error; + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS); + reg_write32(hcd->regs, HC_PORTSC1, + temp | PORT_RESUME); + priv->reset_done = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(priv->hcs_params)) + reg_write32(hcd->regs, HC_PORTSC1, + temp & ~PORT_POWER); + break; + case USB_PORT_FEAT_C_CONNECTION: + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_CSC); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + /* XXX error ?*/ + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + reg_read32(hcd->regs, HC_USBCMD); + break; + case GetHubDescriptor: + isp1760_hub_descriptor(priv, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = reg_read32(hcd->regs, HC_PORTSC1); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + dev_err(hcd->self.controller, "Port resume should be skipped.\n"); + + /* Remote Wakeup received? */ + if (!priv->reset_done) { + /* resume signaling for 20 msec */ + priv->reset_done = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&hcd->rh_timer, priv->reset_done); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + priv->reset_done)) { + status |= USB_PORT_STAT_C_SUSPEND << 16; + priv->reset_done = 0; + + /* stop resume signaling */ + temp = reg_read32(hcd->regs, HC_PORTSC1); + reg_write32(hcd->regs, HC_PORTSC1, + temp & ~(PORT_RWC_BITS | PORT_RESUME)); + retval = handshake(hcd, HC_PORTSC1, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + dev_err(hcd->self.controller, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + priv->reset_done)) { + status |= USB_PORT_STAT_C_RESET << 16; + priv->reset_done = 0; + + /* force reset to complete */ + reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_RESET); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(hcd, HC_PORTSC1, + PORT_RESET, 0, 750); + if (retval != 0) { + dev_err(hcd->self.controller, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(hcd, wIndex, + reg_read32(hcd->regs, HC_PORTSC1)); + } + /* + * Even if OWNER is set, there's no harm letting hub_wq + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_OWNER) + dev_err(hcd->self.controller, "PORT_OWNER is set\n"); + + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + /* status may be from integrated TT */ + status |= USB_PORT_STAT_HIGH_SPEED; + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= USB_PORT_STAT_SUSPEND; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (temp & PORT_POWER) + status |= USB_PORT_STAT_POWER; + + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = reg_read32(hcd->regs, HC_PORTSC1); + if (temp & PORT_OWNER) + break; + +/* temp &= ~PORT_RWC_BITS; */ + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_PE); + break; + + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_SUSPEND); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(priv->hcs_params)) + reg_write32(hcd->regs, HC_PORTSC1, + temp | PORT_POWER); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && PORT_USB11(temp)) { + temp |= PORT_OWNER; + } else { + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + priv->reset_done = jiffies + + msecs_to_jiffies(50); + } + reg_write32(hcd->regs, HC_PORTSC1, temp); + break; + default: + goto error; + } + reg_read32(hcd->regs, HC_USBCMD); + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&priv->lock, flags); + return retval; +} + +static int isp1760_get_frame(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 fr; + + fr = reg_read32(hcd->regs, HC_FRINDEX); + return (fr >> 3) % priv->periodic_size; +} + +static void isp1760_stop(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 temp; + + del_timer(&errata2_timer); + + isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, + NULL, 0); + mdelay(20); + + spin_lock_irq(&priv->lock); + ehci_reset(hcd); + /* Disable IRQ */ + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); + spin_unlock_irq(&priv->lock); + + reg_write32(hcd->regs, HC_CONFIGFLAG, 0); +} + +static void isp1760_shutdown(struct usb_hcd *hcd) +{ + u32 command, temp; + + isp1760_stop(hcd); + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); + + command = reg_read32(hcd->regs, HC_USBCMD); + command &= ~CMD_RUN; + reg_write32(hcd->regs, HC_USBCMD, command); +} + +static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct isp1760_qh *qh = ep->hcpriv; + unsigned long spinflags; + + if (!qh) + return; + + spin_lock_irqsave(&priv->lock, spinflags); + qh->tt_buffer_dirty = 0; + schedule_ptds(hcd); + spin_unlock_irqrestore(&priv->lock, spinflags); +} + + +static const struct hc_driver isp1760_hc_driver = { + .description = "isp1760-hcd", + .product_desc = "NXP ISP1760 USB Host Controller", + .hcd_priv_size = sizeof(struct isp1760_hcd *), + .irq = isp1760_irq, + .flags = HCD_MEMORY | HCD_USB2, + .reset = isp1760_hc_setup, + .start = isp1760_run, + .stop = isp1760_stop, + .shutdown = isp1760_shutdown, + .urb_enqueue = isp1760_urb_enqueue, + .urb_dequeue = isp1760_urb_dequeue, + .endpoint_disable = isp1760_endpoint_disable, + .get_frame_number = isp1760_get_frame, + .hub_status_data = isp1760_hub_status_data, + .hub_control = isp1760_hub_control, + .clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete, +}; + +int __init isp1760_init_kmem_once(void) +{ + urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", + sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | + SLAB_MEM_SPREAD, NULL); + + if (!urb_listitem_cachep) + return -ENOMEM; + + qtd_cachep = kmem_cache_create("isp1760_qtd", + sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY | + SLAB_MEM_SPREAD, NULL); + + if (!qtd_cachep) + return -ENOMEM; + + qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), + 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL); + + if (!qh_cachep) { + kmem_cache_destroy(qtd_cachep); + return -ENOMEM; + } + + return 0; +} + +void isp1760_deinit_kmem_cache(void) +{ + kmem_cache_destroy(qtd_cachep); + kmem_cache_destroy(qh_cachep); + kmem_cache_destroy(urb_listitem_cachep); +} + +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev) +{ + struct usb_hcd *hcd; + int ret; + + hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + + *(struct isp1760_hcd **)hcd->hcd_priv = priv; + + priv->hcd = hcd; + + init_memory(priv); + + hcd->irq = irq; + hcd->regs = regs; + hcd->rsrc_start = mem->start; + hcd->rsrc_len = resource_size(mem); + + ret = usb_add_hcd(hcd, irq, irqflags); + if (ret) + goto error; + + device_wakeup_enable(hcd->self.controller); + + return 0; + +error: + usb_put_hcd(hcd); + return ret; +} + +void isp1760_hcd_unregister(struct isp1760_hcd *priv) +{ + usb_remove_hcd(priv->hcd); + usb_put_hcd(priv->hcd); +} diff --git a/drivers/usb/isp1760/isp1760-hcd.h b/drivers/usb/isp1760/isp1760-hcd.h new file mode 100644 index 0000000..df7ea36 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-hcd.h @@ -0,0 +1,77 @@ +#ifndef _ISP1760_HCD_H_ +#define _ISP1760_HCD_H_ + +#include + +struct isp1760_qh; +struct isp1760_qtd; +struct resource; +struct usb_hcd; + +/* + * 60kb divided in: + * - 32 blocks @ 256 bytes + * - 20 blocks @ 1024 bytes + * - 4 blocks @ 8192 bytes + */ + +#define BLOCK_1_NUM 32 +#define BLOCK_2_NUM 20 +#define BLOCK_3_NUM 4 + +#define BLOCK_1_SIZE 256 +#define BLOCK_2_SIZE 1024 +#define BLOCK_3_SIZE 8192 +#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) +#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE +#define PAYLOAD_AREA_SIZE 0xf000 + +struct isp1760_slotinfo { + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + unsigned long timestamp; +}; + +/* chip memory management */ +struct isp1760_memory_chunk { + unsigned int start; + unsigned int size; + unsigned int free; +}; + +enum isp1760_queue_head_types { + QH_CONTROL, + QH_BULK, + QH_INTERRUPT, + QH_END +}; + +struct isp1760_hcd { + struct usb_hcd *hcd; + + u32 hcs_params; + spinlock_t lock; + struct isp1760_slotinfo atl_slots[32]; + int atl_done_map; + struct isp1760_slotinfo int_slots[32]; + int int_done_map; + struct isp1760_memory_chunk memory_pool[BLOCKS]; + struct list_head qh_list[QH_END]; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 + unsigned periodic_size; + unsigned i_thresh; + unsigned long reset_done; + unsigned long next_statechange; +}; + +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev); +void isp1760_hcd_unregister(struct isp1760_hcd *priv); + +int isp1760_init_kmem_once(void); +void isp1760_deinit_kmem_cache(void); + +#endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/isp1760/isp1760-if.c b/drivers/usb/isp1760/isp1760-if.c new file mode 100644 index 0000000..c2a94c9 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-if.c @@ -0,0 +1,312 @@ +/* + * Glue code for the ISP1760 driver and bus + * Currently there is support for + * - OpenFirmware + * - PCI + * - PDEV (generic platform device centralized driver model) + * + * (c) 2007 Sebastian Siewior + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" + +#ifdef CONFIG_PCI +#include +#endif + +#ifdef CONFIG_PCI +static int isp1761_pci_init(struct pci_dev *dev) +{ + resource_size_t mem_start; + resource_size_t mem_length; + u8 __iomem *iobase; + u8 latency, limit; + int retry_count; + u32 reg_data; + + /* Grab the PLX PCI shared memory of the ISP 1761 we need */ + mem_start = pci_resource_start(dev, 3); + mem_length = pci_resource_len(dev, 3); + if (mem_length < 0xffff) { + printk(KERN_ERR "memory length for this resource is wrong\n"); + return -ENOMEM; + } + + if (!request_mem_region(mem_start, mem_length, "ISP-PCI")) { + printk(KERN_ERR "host controller already in use\n"); + return -EBUSY; + } + + /* map available memory */ + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "Error ioremap failed\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } + + /* bad pci latencies can contribute to overruns */ + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &latency); + if (latency) { + pci_read_config_byte(dev, PCI_MAX_LAT, &limit); + if (limit && limit < latency) + pci_write_config_byte(dev, PCI_LATENCY_TIMER, limit); + } + + /* Try to check whether we can access Scratch Register of + * Host Controller or not. The initial PCI access is retried until + * local init for the PCI bridge is completed + */ + retry_count = 20; + reg_data = 0; + while ((reg_data != 0xFACE) && retry_count) { + /*by default host is in 16bit mode, so + * io operations at this stage must be 16 bit + * */ + writel(0xface, iobase + HC_SCRATCH_REG); + udelay(100); + reg_data = readl(iobase + HC_SCRATCH_REG) & 0x0000ffff; + retry_count--; + } + + iounmap(iobase); + release_mem_region(mem_start, mem_length); + + /* Host Controller presence is detected by writing to scratch register + * and reading back and checking the contents are same or not + */ + if (reg_data != 0xFACE) { + dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); + return -ENOMEM; + } + + /* Grab the PLX PCI mem maped port start address we need */ + mem_start = pci_resource_start(dev, 0); + mem_length = pci_resource_len(dev, 0); + + if (!request_mem_region(mem_start, mem_length, "ISP1761 IO MEM")) { + printk(KERN_ERR "request region #1\n"); + return -EBUSY; + } + + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "ioremap #1\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } + + /* configure PLX PCI chip to pass interrupts */ +#define PLX_INT_CSR_REG 0x68 + reg_data = readl(iobase + PLX_INT_CSR_REG); + reg_data |= 0x900; + writel(reg_data, iobase + PLX_INT_CSR_REG); + + /* done with PLX IO access */ + iounmap(iobase); + release_mem_region(mem_start, mem_length); + + return 0; +} + +static int isp1761_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + unsigned int devflags = 0; + int ret; + + if (usb_disabled()) + return -ENODEV; + + if (!dev->irq) + return -ENODEV; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + ret = isp1761_pci_init(dev); + if (ret < 0) + goto error; + + pci_set_master(dev); + + dev->dev.dma_mask = NULL; + ret = isp1760_register(&dev->resource[3], dev->irq, 0, &dev->dev, + devflags); + if (ret < 0) + goto error; + + return 0; + +error: + pci_disable_device(dev); + return ret; +} + +static void isp1761_pci_remove(struct pci_dev *dev) +{ + isp1760_unregister(&dev->dev); + + pci_disable_device(dev); +} + +static void isp1761_pci_shutdown(struct pci_dev *dev) +{ + printk(KERN_ERR "ips1761_pci_shutdown\n"); +} + +static const struct pci_device_id isp1760_plx [] = { + { + .class = PCI_CLASS_BRIDGE_OTHER << 8, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x5406, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = 0x9054, + }, + { } +}; +MODULE_DEVICE_TABLE(pci, isp1760_plx); + +static struct pci_driver isp1761_pci_driver = { + .name = "isp1760", + .id_table = isp1760_plx, + .probe = isp1761_pci_probe, + .remove = isp1761_pci_remove, + .shutdown = isp1761_pci_shutdown, +}; +#endif + +static int isp1760_plat_probe(struct platform_device *pdev) +{ + unsigned long irqflags; + unsigned int devflags = 0; + struct resource *mem_res; + struct resource *irq_res; + int ret; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq_res) { + pr_warning("isp1760: IRQ resource not available\n"); + return -ENODEV; + } + irqflags = irq_res->flags & IRQF_TRIGGER_MASK; + + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + struct device_node *dp = pdev->dev.of_node; + u32 bus_width = 0; + + if (of_device_is_compatible(dp, "nxp,usb-isp1761")) + devflags |= ISP1760_FLAG_ISP1761; + + /* Some systems wire up only 16 of the 32 data lines */ + of_property_read_u32(dp, "bus-width", &bus_width); + if (bus_width == 16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + + if (of_property_read_bool(dp, "port1-otg")) + devflags |= ISP1760_FLAG_OTG_EN; + + if (of_property_read_bool(dp, "analog-oc")) + devflags |= ISP1760_FLAG_ANALOG_OC; + + if (of_property_read_bool(dp, "dack-polarity")) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + + if (of_property_read_bool(dp, "dreq-polarity")) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } else if (dev_get_platdata(&pdev->dev)) { + struct isp1760_platform_data *pdata = + dev_get_platdata(&pdev->dev); + + if (pdata->is_isp1761) + devflags |= ISP1760_FLAG_ISP1761; + if (pdata->bus_width_16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + if (pdata->port1_otg) + devflags |= ISP1760_FLAG_OTG_EN; + if (pdata->analog_oc) + devflags |= ISP1760_FLAG_ANALOG_OC; + if (pdata->dack_polarity_high) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + if (pdata->dreq_polarity_high) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } + + ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, + devflags); + if (ret < 0) + return ret; + + pr_info("ISP1760 USB device initialised\n"); + return 0; +} + +static int isp1760_plat_remove(struct platform_device *pdev) +{ + isp1760_unregister(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id isp1760_of_match[] = { + { .compatible = "nxp,usb-isp1760", }, + { .compatible = "nxp,usb-isp1761", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isp1760_of_match); +#endif + +static struct platform_driver isp1760_plat_driver = { + .probe = isp1760_plat_probe, + .remove = isp1760_plat_remove, + .driver = { + .name = "isp1760", + .of_match_table = of_match_ptr(isp1760_of_match), + }, +}; + +static int __init isp1760_init(void) +{ + int ret, any_ret = -ENODEV; + + isp1760_init_kmem_once(); + + ret = platform_driver_register(&isp1760_plat_driver); + if (!ret) + any_ret = 0; +#ifdef CONFIG_PCI + ret = pci_register_driver(&isp1761_pci_driver); + if (!ret) + any_ret = 0; +#endif + + if (any_ret) + isp1760_deinit_kmem_cache(); + return any_ret; +} +module_init(isp1760_init); + +static void __exit isp1760_exit(void) +{ + platform_driver_unregister(&isp1760_plat_driver); +#ifdef CONFIG_PCI + pci_unregister_driver(&isp1761_pci_driver); +#endif + isp1760_deinit_kmem_cache(); +} +module_exit(isp1760_exit); diff --git a/drivers/usb/isp1760/isp1760-regs.h b/drivers/usb/isp1760/isp1760-regs.h new file mode 100644 index 0000000..b67095c --- /dev/null +++ b/drivers/usb/isp1760/isp1760-regs.h @@ -0,0 +1,230 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * 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 _ISP1760_REGS_H_ +#define _ISP1760_REGS_H_ + +/* ----------------------------------------------------------------------------- + * Host Controller + */ + +/* EHCI capability registers */ +#define HC_CAPLENGTH 0x000 +#define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) /* bits 31:16 */ + +#define HC_HCSPARAMS 0x004 +#define HCS_INDICATOR(p) ((p) & (1 << 16)) /* true: has port indicators */ +#define HCS_PPC(p) ((p) & (1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p) >> 0) & 0xf) /* bits 3:0, ports on HC */ + +#define HC_HCCPARAMS 0x008 +#define HCC_ISOC_CACHE(p) ((p) & (1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p) >> 4) & 0x7) /* bits 6:4, uframes cached */ + +/* EHCI operational registers */ +#define HC_USBCMD 0x020 +#define CMD_LRESET (1 << 7) /* partial reset (no ports, etc) */ +#define CMD_RESET (1 << 1) /* reset HC not bus */ +#define CMD_RUN (1 << 0) /* start/stop HC */ + +#define HC_USBSTS 0x024 +#define STS_PCD (1 << 2) /* port change detect */ + +#define HC_FRINDEX 0x02c + +#define HC_CONFIGFLAG 0x060 +#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */ + +#define HC_PORTSC1 0x064 +#define PORT_OWNER (1 << 13) /* true: companion hc owns this port */ +#define PORT_POWER (1 << 12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ +#define PORT_RESET (1 << 8) /* reset port */ +#define PORT_SUSPEND (1 << 7) /* suspend port */ +#define PORT_RESUME (1 << 6) /* resume it */ +#define PORT_PE (1 << 2) /* port enable */ +#define PORT_CSC (1 << 1) /* connect status change */ +#define PORT_CONNECT (1 << 0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC) + +#define HC_ISO_PTD_DONEMAP_REG 0x130 +#define HC_ISO_PTD_SKIPMAP_REG 0x134 +#define HC_ISO_PTD_LASTPTD_REG 0x138 +#define HC_INT_PTD_DONEMAP_REG 0x140 +#define HC_INT_PTD_SKIPMAP_REG 0x144 +#define HC_INT_PTD_LASTPTD_REG 0x148 +#define HC_ATL_PTD_DONEMAP_REG 0x150 +#define HC_ATL_PTD_SKIPMAP_REG 0x154 +#define HC_ATL_PTD_LASTPTD_REG 0x158 + +/* Configuration Register */ +#define HC_HW_MODE_CTRL 0x300 +#define ALL_ATX_RESET (1 << 31) +#define HW_ANA_DIGI_OC (1 << 15) +#define HW_DEV_DMA (1 << 11) +#define HW_COMN_IRQ (1 << 10) +#define HW_COMN_DMA (1 << 9) +#define HW_DATA_BUS_32BIT (1 << 8) +#define HW_DACK_POL_HIGH (1 << 6) +#define HW_DREQ_POL_HIGH (1 << 5) +#define HW_INTR_HIGH_ACT (1 << 2) +#define HW_INTR_EDGE_TRIG (1 << 1) +#define HW_GLOBAL_INTR_EN (1 << 0) + +#define HC_CHIP_ID_REG 0x304 +#define HC_SCRATCH_REG 0x308 + +#define HC_RESET_REG 0x30c +#define SW_RESET_RESET_HC (1 << 1) +#define SW_RESET_RESET_ALL (1 << 0) + +#define HC_BUFFER_STATUS_REG 0x334 +#define ISO_BUF_FILL (1 << 2) +#define INT_BUF_FILL (1 << 1) +#define ATL_BUF_FILL (1 << 0) + +#define HC_MEMORY_REG 0x33c +#define ISP_BANK(x) ((x) << 16) + +#define HC_PORT1_CTRL 0x374 +#define PORT1_POWER (3 << 3) +#define PORT1_INIT1 (1 << 7) +#define PORT1_INIT2 (1 << 23) +#define HW_OTG_CTRL_SET 0x374 +#define HW_OTG_CTRL_CLR 0x376 +#define HW_OTG_DISABLE (1 << 10) +#define HW_OTG_SE0_EN (1 << 9) +#define HW_BDIS_ACON_EN (1 << 8) +#define HW_SW_SEL_HC_DC (1 << 7) +#define HW_VBUS_CHRG (1 << 6) +#define HW_VBUS_DISCHRG (1 << 5) +#define HW_VBUS_DRV (1 << 4) +#define HW_SEL_CP_EXT (1 << 3) +#define HW_DM_PULLDOWN (1 << 2) +#define HW_DP_PULLDOWN (1 << 1) +#define HW_DP_PULLUP (1 << 0) + +/* Interrupt Register */ +#define HC_INTERRUPT_REG 0x310 + +#define HC_INTERRUPT_ENABLE 0x314 +#define HC_ISO_INT (1 << 9) +#define HC_ATL_INT (1 << 8) +#define HC_INTL_INT (1 << 7) +#define HC_EOT_INT (1 << 3) +#define HC_SOT_INT (1 << 1) +#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) + +#define HC_ISO_IRQ_MASK_OR_REG 0x318 +#define HC_INT_IRQ_MASK_OR_REG 0x31c +#define HC_ATL_IRQ_MASK_OR_REG 0x320 +#define HC_ISO_IRQ_MASK_AND_REG 0x324 +#define HC_INT_IRQ_MASK_AND_REG 0x328 +#define HC_ATL_IRQ_MASK_AND_REG 0x32c + +/* ----------------------------------------------------------------------------- + * Peripheral Controller + */ + +/* Initialization Registers */ +#define DC_ADDRESS 0x0200 +#define DC_DEVEN (1 << 7) + +#define DC_MODE 0x020c +#define DC_DMACLKON (1 << 9) +#define DC_VBUSSTAT (1 << 8) +#define DC_CLKAON (1 << 7) +#define DC_SNDRSU (1 << 6) +#define DC_GOSUSP (1 << 5) +#define DC_SFRESET (1 << 4) +#define DC_GLINTENA (1 << 3) +#define DC_WKUPCS (1 << 2) + +#define DC_INTCONF 0x0210 +#define DC_CDBGMOD_ACK_NAK (0 << 6) +#define DC_CDBGMOD_ACK (1 << 6) +#define DC_CDBGMOD_ACK_1NAK (2 << 6) +#define DC_DDBGMODIN_ACK_NAK (0 << 4) +#define DC_DDBGMODIN_ACK (1 << 4) +#define DC_DDBGMODIN_ACK_1NAK (2 << 4) +#define DC_DDBGMODOUT_ACK_NYET_NAK (0 << 2) +#define DC_DDBGMODOUT_ACK_NYET (1 << 2) +#define DC_DDBGMODOUT_ACK_NYET_1NAK (2 << 2) +#define DC_INTLVL (1 << 1) +#define DC_INTPOL (1 << 0) + +#define DC_DEBUG 0x0212 +#define DC_INTENABLE 0x0214 +#define DC_IEPTX(n) (1 << (11 + 2 * (n))) +#define DC_IEPRX(n) (1 << (10 + 2 * (n))) +#define DC_IEPRXTX(n) (3 << (10 + 2 * (n))) +#define DC_IEP0SETUP (1 << 8) +#define DC_IEVBUS (1 << 7) +#define DC_IEDMA (1 << 6) +#define DC_IEHS_STA (1 << 5) +#define DC_IERESM (1 << 4) +#define DC_IESUSP (1 << 3) +#define DC_IEPSOF (1 << 2) +#define DC_IESOF (1 << 1) +#define DC_IEBRST (1 << 0) + +/* Data Flow Registers */ +#define DC_EPINDEX 0x022c +#define DC_EP0SETUP (1 << 5) +#define DC_ENDPIDX(n) ((n) << 1) +#define DC_EPDIR (1 << 0) + +#define DC_CTRLFUNC 0x0228 +#define DC_CLBUF (1 << 4) +#define DC_VENDP (1 << 3) +#define DC_DSEN (1 << 2) +#define DC_STATUS (1 << 1) +#define DC_STALL (1 << 0) + +#define DC_DATAPORT 0x0220 +#define DC_BUFLEN 0x021c +#define DC_DATACOUNT_MASK 0xffff +#define DC_BUFSTAT 0x021e +#define DC_EPMAXPKTSZ 0x0204 + +#define DC_EPTYPE 0x0208 +#define DC_NOEMPKT (1 << 4) +#define DC_EPENABLE (1 << 3) +#define DC_DBLBUF (1 << 2) +#define DC_ENDPTYP_ISOC (1 << 0) +#define DC_ENDPTYP_BULK (2 << 0) +#define DC_ENDPTYP_INTERRUPT (3 << 0) + +/* DMA Registers */ +#define DC_DMACMD 0x0230 +#define DC_DMATXCOUNT 0x0234 +#define DC_DMACONF 0x0238 +#define DC_DMAHW 0x023c +#define DC_DMAINTREASON 0x0250 +#define DC_DMAINTEN 0x0254 +#define DC_DMAEP 0x0258 +#define DC_DMABURSTCOUNT 0x0264 + +/* General Registers */ +#define DC_INTERRUPT 0x0218 +#define DC_CHIPID 0x0270 +#define DC_FRAMENUM 0x0274 +#define DC_SCRATCH 0x0278 +#define DC_UNLOCKDEV 0x027c +#define DC_INTPULSEWIDTH 0x0280 +#define DC_TESTMODE 0x0284 + +#endif diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c new file mode 100644 index 0000000..6bfda30 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -0,0 +1,1495 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * 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 +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +#define ISP1760_VBUS_POLL_INTERVAL msecs_to_jiffies(500) + +struct isp1760_request { + struct usb_request req; + struct list_head queue; + struct isp1760_ep *ep; + unsigned int packet_size; +}; + +static inline struct isp1760_udc *gadget_to_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct isp1760_udc, gadget); +} + +static inline struct isp1760_ep *ep_to_udc_ep(struct usb_ep *ep) +{ + return container_of(ep, struct isp1760_ep, ep); +} + +static inline struct isp1760_request *req_to_udc_req(struct usb_request *req) +{ + return container_of(req, struct isp1760_request, req); +} + +static inline u32 isp1760_udc_read(struct isp1760_udc *udc, u16 reg) +{ + return isp1760_read32(udc->regs, reg); +} + +static inline void isp1760_udc_write(struct isp1760_udc *udc, u16 reg, u32 val) +{ + isp1760_write32(udc->regs, reg, val); +} + +/* ----------------------------------------------------------------------------- + * Endpoint Management + */ + +static struct isp1760_ep *isp1760_udc_find_ep(struct isp1760_udc *udc, + u16 index) +{ + unsigned int i; + + if (index == 0) + return &udc->ep[0]; + + for (i = 1; i < ARRAY_SIZE(udc->ep); ++i) { + if (udc->ep[i].addr == index) + return udc->ep[i].desc ? &udc->ep[i] : NULL; + } + + return NULL; +} + +static void __isp1760_udc_select_ep(struct isp1760_ep *ep, int dir) +{ + isp1760_udc_write(ep->udc, DC_EPINDEX, + DC_ENDPIDX(ep->addr & USB_ENDPOINT_NUMBER_MASK) | + (dir == USB_DIR_IN ? DC_EPDIR : 0)); +} + +/** + * isp1760_udc_select_ep - Select an endpoint for register access + * @ep: The endpoint + * + * The ISP1761 endpoint registers are banked. This function selects the target + * endpoint for banked register access. The selection remains valid until the + * next call to this function, the next direct access to the EPINDEX register + * or the next reset, whichever comes first. + * + * Called with the UDC spinlock held. + */ +static void isp1760_udc_select_ep(struct isp1760_ep *ep) +{ + __isp1760_udc_select_ep(ep, ep->addr & USB_ENDPOINT_DIR_MASK); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_ctrl_send_status(struct isp1760_ep *ep, int dir) +{ + struct isp1760_udc *udc = ep->udc; + + /* + * Proceed to the status stage. The status stage data packet flows in + * the direction opposite to the data stage data packets, we thus need + * to select the OUT/IN endpoint for IN/OUT transfers. + */ + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | + (dir == USB_DIR_IN ? 0 : DC_EPDIR)); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STATUS); + + /* + * The hardware will terminate the request automatically and go back to + * the setup stage without notifying us. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; +} + +/* Called without the UDC spinlock held. */ +static void isp1760_udc_request_complete(struct isp1760_ep *ep, + struct isp1760_request *req, + int status) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "completing request %p with status %d\n", + req, status); + + req->ep = NULL; + req->req.status = status; + req->req.complete(&ep->ep, &req->req); + + spin_lock_irqsave(&udc->lock, flags); + + /* + * When completing control OUT requests, move to the status stage after + * calling the request complete callback. This gives the gadget an + * opportunity to stall the control transfer if needed. + */ + if (status == 0 && ep->addr == 0 && udc->ep0_dir == USB_DIR_OUT) + isp1760_udc_ctrl_send_status(ep, USB_DIR_OUT); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_ctrl_send_stall(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "%s(ep%02x)\n", __func__, ep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + /* Stall both the IN and OUT endpoints. */ + __isp1760_udc_select_ep(ep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + + /* A protocol stall completes the control transaction. */ + udc->ep0_state = ISP1760_CTRL_SETUP; + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Data Endpoints + */ + +/* Called with the UDC spinlock held. */ +static bool isp1760_udc_receive(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + unsigned int len; + u32 *buf; + int i; + + isp1760_udc_select_ep(ep); + len = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + + dev_dbg(udc->isp->dev, "%s: received %u bytes (%u/%u done)\n", + __func__, len, req->req.actual, req->req.length); + + len = min(len, req->req.length - req->req.actual); + + if (!len) { + /* + * There's no data to be read from the FIFO, acknowledge the RX + * interrupt by clearing the buffer. + * + * TODO: What if another packet arrives in the meantime ? The + * datasheet doesn't clearly document how this should be + * handled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + return false; + } + + buf = req->req.buf + req->req.actual; + + /* + * Make sure not to read more than one extra byte, otherwise data from + * the next packet might be removed from the FIFO. + */ + for (i = len; i > 2; i -= 4, ++buf) + *buf = le32_to_cpu(isp1760_udc_read(udc, DC_DATAPORT)); + if (i > 0) + *(u16 *)buf = le16_to_cpu(readw(udc->regs + DC_DATAPORT)); + + req->req.actual += len; + + /* + * TODO: The short_not_ok flag isn't supported yet, but isn't used by + * any gadget driver either. + */ + + dev_dbg(udc->isp->dev, + "%s: req %p actual/length %u/%u maxpacket %u packet size %u\n", + __func__, req, req->req.actual, req->req.length, ep->maxpacket, + len); + + ep->rx_pending = false; + + /* + * Complete the request if all data has been received or if a short + * packet has been received. + */ + if (req->req.actual == req->req.length || len < ep->maxpacket) { + list_del(&req->queue); + return true; + } + + return false; +} + +static void isp1760_udc_transmit(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + u32 *buf = req->req.buf + req->req.actual; + int i; + + req->packet_size = min(req->req.length - req->req.actual, + ep->maxpacket); + + dev_dbg(udc->isp->dev, "%s: transferring %u bytes (%u/%u done)\n", + __func__, req->packet_size, req->req.actual, + req->req.length); + + __isp1760_udc_select_ep(ep, USB_DIR_IN); + + if (req->packet_size) + isp1760_udc_write(udc, DC_BUFLEN, req->packet_size); + + /* + * Make sure not to write more than one extra byte, otherwise extra data + * will stay in the FIFO and will be transmitted during the next control + * request. The endpoint control CLBUF bit is supposed to allow flushing + * the FIFO for this kind of conditions, but doesn't seem to work. + */ + for (i = req->packet_size; i > 2; i -= 4, ++buf) + isp1760_udc_write(udc, DC_DATAPORT, cpu_to_le32(*buf)); + if (i > 0) + writew(cpu_to_le16(*(u16 *)buf), udc->regs + DC_DATAPORT); + + if (ep->addr == 0) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + if (!req->packet_size) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_VENDP); +} + +static void isp1760_ep_rx_ready(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *req; + bool complete; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_OUT) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: invalid ep0 state %u\n", __func__, + udc->ep0_state); + return; + } + + if (ep->addr != 0 && !ep->desc) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + ep->addr); + return; + } + + if (list_empty(&ep->queue)) { + ep->rx_pending = true; + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x (%p) has no request queued\n", + __func__, ep->addr, ep); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + complete = isp1760_udc_receive(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, req, 0); +} + +static void isp1760_ep_tx_complete(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *complete = NULL; + struct isp1760_request *req; + bool need_zlp; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_IN) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "TX IRQ: invalid endpoint state %u\n", + udc->ep0_state); + return; + } + + if (list_empty(&ep->queue)) { + /* + * This can happen for the control endpoint when the reply to + * the GET_STATUS IN control request is sent directly by the + * setup IRQ handler. Just proceed to the status stage. + */ + if (ep->addr == 0) { + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + spin_unlock(&udc->lock); + return; + } + + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x has no request queued\n", + __func__, ep->addr); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + req->req.actual += req->packet_size; + + need_zlp = req->req.actual == req->req.length && + !(req->req.length % ep->maxpacket) && + req->packet_size && req->req.zero; + + dev_dbg(udc->isp->dev, + "TX IRQ: req %p actual/length %u/%u maxpacket %u packet size %u zero %u need zlp %u\n", + req, req->req.actual, req->req.length, ep->maxpacket, + req->packet_size, req->req.zero, need_zlp); + + /* + * Complete the request if all data has been sent and we don't need to + * transmit a zero length packet. + */ + if (req->req.actual == req->req.length && !need_zlp) { + complete = req; + list_del(&req->queue); + + if (ep->addr == 0) + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + + if (!list_empty(&ep->queue)) + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + else + req = NULL; + } + + /* + * Transmit the next packet or start the next request, if any. + * + * TODO: If the endpoint is stalled the next request shouldn't be + * started, but what about the next packet ? + */ + if (req) + isp1760_udc_transmit(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, complete, 0); +} + +static int __isp1760_udc_set_halt(struct isp1760_ep *ep, bool halt) +{ + struct isp1760_udc *udc = ep->udc; + + dev_dbg(udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + halt ? "set" : "clear", ep->addr); + + if (ep->desc && usb_endpoint_xfer_isoc(ep->desc)) { + dev_dbg(udc->isp->dev, "%s: ep%02x is isochronous\n", __func__, + ep->addr); + return -EINVAL; + } + + isp1760_udc_select_ep(ep); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + + if (ep->addr == 0) { + /* When halting the control endpoint, stall both IN and OUT. */ + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + } else if (!halt) { + /* Reset the data PID by cycling the endpoint enable bit. */ + u16 eptype = isp1760_udc_read(udc, DC_EPTYPE); + + isp1760_udc_write(udc, DC_EPTYPE, eptype & ~DC_EPENABLE); + isp1760_udc_write(udc, DC_EPTYPE, eptype); + + /* + * Disabling the endpoint emptied the transmit FIFO, fill it + * again if a request is pending. + * + * TODO: Does the gadget framework require synchronizatino with + * the TX IRQ handler ? + */ + if ((ep->addr & USB_DIR_IN) && !list_empty(&ep->queue)) { + struct isp1760_request *req; + + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + isp1760_udc_transmit(ep, req); + } + } + + ep->halted = halt; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Control Endpoint + */ + +static int isp1760_udc_get_status(struct isp1760_udc *udc, + const struct usb_ctrlrequest *req) +{ + struct isp1760_ep *ep; + u16 status; + + if (req->wLength != cpu_to_le16(2) || req->wValue != cpu_to_le16(0)) + return -EINVAL; + + switch (req->bRequestType) { + case USB_DIR_IN | USB_RECIP_DEVICE: + status = udc->devstatus; + break; + + case USB_DIR_IN | USB_RECIP_INTERFACE: + status = 0; + break; + + case USB_DIR_IN | USB_RECIP_ENDPOINT: + ep = isp1760_udc_find_ep(udc, le16_to_cpu(req->wIndex)); + if (!ep) + return -EINVAL; + + status = 0; + if (ep->halted) + status |= 1 << USB_ENDPOINT_HALT; + break; + + default: + return -EINVAL; + } + + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | DC_EPDIR); + isp1760_udc_write(udc, DC_BUFLEN, 2); + + writew(cpu_to_le16(status), udc->regs + DC_DATAPORT); + + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + + dev_dbg(udc->isp->dev, "%s: status 0x%04x\n", __func__, status); + + return 0; +} + +static int isp1760_udc_set_address(struct isp1760_udc *udc, u16 addr) +{ + if (addr > 127) { + dev_dbg(udc->isp->dev, "invalid device address %u\n", addr); + return -EINVAL; + } + + if (udc->gadget.state != USB_STATE_DEFAULT && + udc->gadget.state != USB_STATE_ADDRESS) { + dev_dbg(udc->isp->dev, "can't set address in state %u\n", + udc->gadget.state); + return -EINVAL; + } + + usb_gadget_set_state(&udc->gadget, addr ? USB_STATE_ADDRESS : + USB_STATE_DEFAULT); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN | addr); + + spin_lock(&udc->lock); + isp1760_udc_ctrl_send_status(&udc->ep[0], USB_DIR_OUT); + spin_unlock(&udc->lock); + + return 0; +} + +static bool isp1760_ep0_setup_standard(struct isp1760_udc *udc, + struct usb_ctrlrequest *req) +{ + bool stall; + + switch (req->bRequest) { + case USB_REQ_GET_STATUS: + return isp1760_udc_get_status(udc, req); + + case USB_REQ_CLEAR_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup feature. */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + /* + * If the endpoint is wedged only the gadget can clear + * the halt feature. Pretend success in that case, but + * keep the endpoint halted. + */ + if (!ep->wedged) + stall = __isp1760_udc_set_halt(ep, false); + else + stall = false; + + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup and test mode features */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + stall = __isp1760_udc_set_halt(ep, true); + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_ADDRESS: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + return isp1760_udc_set_address(udc, le16_to_cpu(req->wValue)); + + case USB_REQ_SET_CONFIGURATION: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + if (udc->gadget.state != USB_STATE_ADDRESS && + udc->gadget.state != USB_STATE_CONFIGURED) + return true; + + stall = udc->driver->setup(&udc->gadget, req) < 0; + if (stall) + return true; + + usb_gadget_set_state(&udc->gadget, req->wValue ? + USB_STATE_CONFIGURED : USB_STATE_ADDRESS); + + /* + * SET_CONFIGURATION (and SET_INTERFACE) must reset the halt + * feature on all endpoints. There is however no need to do so + * explicitly here as the gadget driver will disable and + * reenable endpoints, clearing the halt feature. + */ + return false; + + default: + return udc->driver->setup(&udc->gadget, req) < 0; + } +} + +static void isp1760_ep0_setup(struct isp1760_udc *udc) +{ + union { + struct usb_ctrlrequest r; + u32 data[2]; + } req; + unsigned int count; + bool stall = false; + + spin_lock(&udc->lock); + + isp1760_udc_write(udc, DC_EPINDEX, DC_EP0SETUP); + + count = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + if (count != sizeof(req)) { + spin_unlock(&udc->lock); + + dev_err(udc->isp->dev, "invalid length %u for setup packet\n", + count); + + isp1760_udc_ctrl_send_stall(&udc->ep[0]); + return; + } + + req.data[0] = isp1760_udc_read(udc, DC_DATAPORT); + req.data[1] = isp1760_udc_read(udc, DC_DATAPORT); + + if (udc->ep0_state != ISP1760_CTRL_SETUP) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "unexpected SETUP packet\n"); + return; + } + + /* Move to the data stage. */ + if (!req.r.wLength) + udc->ep0_state = ISP1760_CTRL_STATUS; + else if (req.r.bRequestType & USB_DIR_IN) + udc->ep0_state = ISP1760_CTRL_DATA_IN; + else + udc->ep0_state = ISP1760_CTRL_DATA_OUT; + + udc->ep0_dir = req.r.bRequestType & USB_DIR_IN; + udc->ep0_length = le16_to_cpu(req.r.wLength); + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, + "%s: bRequestType 0x%02x bRequest 0x%02x wValue 0x%04x wIndex 0x%04x wLength 0x%04x\n", + __func__, req.r.bRequestType, req.r.bRequest, + le16_to_cpu(req.r.wValue), le16_to_cpu(req.r.wIndex), + le16_to_cpu(req.r.wLength)); + + if ((req.r.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + stall = isp1760_ep0_setup_standard(udc, &req.r); + else + stall = udc->driver->setup(&udc->gadget, &req.r) < 0; + + if (stall) + isp1760_udc_ctrl_send_stall(&udc->ep[0]); +} + +/* ----------------------------------------------------------------------------- + * Gadget Endpoint Operations + */ + +static int isp1760_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + unsigned int type; + + dev_dbg(uep->udc->isp->dev, "%s\n", __func__); + + /* + * Validate the descriptor. The control endpoint can't be enabled + * manually. + */ + if (desc->bDescriptorType != USB_DT_ENDPOINT || + desc->bEndpointAddress == 0 || + desc->bEndpointAddress != uep->addr || + le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket) { + dev_dbg(udc->isp->dev, + "%s: invalid descriptor type %u addr %02x ep addr %02x max packet size %u/%u\n", + __func__, desc->bDescriptorType, + desc->bEndpointAddress, uep->addr, + le16_to_cpu(desc->wMaxPacketSize), ep->maxpacket); + return -EINVAL; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_ISOC: + type = DC_ENDPTYP_ISOC; + break; + case USB_ENDPOINT_XFER_BULK: + type = DC_ENDPTYP_BULK; + break; + case USB_ENDPOINT_XFER_INT: + type = DC_ENDPTYP_INTERRUPT; + break; + case USB_ENDPOINT_XFER_CONTROL: + default: + dev_dbg(udc->isp->dev, "%s: control endpoints unsupported\n", + __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + + uep->desc = desc; + uep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + uep->rx_pending = false; + uep->halted = false; + uep->wedged = false; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPMAXPKTSZ, uep->maxpacket); + isp1760_udc_write(udc, DC_BUFLEN, uep->maxpacket); + isp1760_udc_write(udc, DC_EPTYPE, DC_EPENABLE | type); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int isp1760_ep_disable(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + struct isp1760_request *req, *nreq; + LIST_HEAD(req_list); + unsigned long flags; + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + spin_lock_irqsave(&udc->lock, flags); + + if (!uep->desc) { + dev_dbg(udc->isp->dev, "%s: endpoint not enabled\n", __func__); + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + uep->desc = NULL; + uep->maxpacket = 0; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPTYPE, 0); + + /* TODO Synchronize with the IRQ handler */ + + list_splice_init(&uep->queue, &req_list); + + spin_unlock_irqrestore(&udc->lock, flags); + + list_for_each_entry_safe(req, nreq, &req_list, queue) { + list_del(&req->queue); + isp1760_udc_request_complete(uep, req, -ESHUTDOWN); + } + + return 0; +} + +static struct usb_request *isp1760_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct isp1760_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + + return &req->req; +} + +static void isp1760_ep_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + + kfree(req); +} + +static int isp1760_ep_queue(struct usb_ep *ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + bool complete = false; + unsigned long flags; + int ret = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + dev_dbg(udc->isp->dev, + "%s: req %p (%u bytes%s) ep %p(0x%02x)\n", __func__, _req, + _req->length, _req->zero ? " (zlp)" : "", uep, uep->addr); + + req->ep = uep; + + if (uep->addr == 0) { + if (_req->length != udc->ep0_length && + udc->ep0_state != ISP1760_CTRL_DATA_IN) { + dev_dbg(udc->isp->dev, + "%s: invalid length %u for req %p\n", + __func__, _req->length, req); + ret = -EINVAL; + goto done; + } + + switch (udc->ep0_state) { + case ISP1760_CTRL_DATA_IN: + dev_dbg(udc->isp->dev, "%s: transmitting req %p\n", + __func__, req); + + list_add_tail(&req->queue, &uep->queue); + isp1760_udc_transmit(uep, req); + break; + + case ISP1760_CTRL_DATA_OUT: + list_add_tail(&req->queue, &uep->queue); + __isp1760_udc_select_ep(uep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + break; + + case ISP1760_CTRL_STATUS: + complete = true; + break; + + default: + dev_dbg(udc->isp->dev, "%s: invalid ep0 state\n", + __func__); + ret = -EINVAL; + break; + } + } else if (uep->desc) { + bool empty = list_empty(&uep->queue); + + list_add_tail(&req->queue, &uep->queue); + if ((uep->addr & USB_DIR_IN) && !uep->halted && empty) + isp1760_udc_transmit(uep, req); + else if (!(uep->addr & USB_DIR_IN) && uep->rx_pending) + complete = isp1760_udc_receive(uep, req); + } else { + dev_dbg(udc->isp->dev, + "%s: can't queue request to disabled ep%02x\n", + __func__, uep->addr); + ret = -ESHUTDOWN; + } + +done: + if (ret < 0) + req->ep = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + + if (complete) + isp1760_udc_request_complete(uep, req, 0); + + return ret; +} + +static int isp1760_ep_dequeue(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + dev_dbg(uep->udc->isp->dev, "%s(ep%02x)\n", __func__, uep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + if (req->ep != uep) + req = NULL; + else + list_del(&req->queue); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (!req) + return -EINVAL; + + isp1760_udc_request_complete(uep, req, -ECONNRESET); + return 0; +} + +static int __isp1760_ep_set_halt(struct isp1760_ep *uep, bool stall, bool wedge) +{ + struct isp1760_udc *udc = uep->udc; + int ret; + + if (!uep->addr) { + /* + * Halting the control endpoint is only valid as a delayed error + * response to a SETUP packet. Make sure EP0 is in the right + * stage and that the gadget isn't trying to clear the halt + * condition. + */ + if (WARN_ON(udc->ep0_state == ISP1760_CTRL_SETUP || !stall || + wedge)) { + return -EINVAL; + } + } + + if (uep->addr && !uep->desc) { + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + uep->addr); + return -EINVAL; + } + + if (uep->addr & USB_DIR_IN) { + /* Refuse to halt IN endpoints with active transfers. */ + if (!list_empty(&uep->queue)) { + dev_dbg(udc->isp->dev, + "%s: ep%02x has request pending\n", __func__, + uep->addr); + return -EAGAIN; + } + } + + ret = __isp1760_udc_set_halt(uep, stall); + if (ret < 0) + return ret; + + if (!uep->addr) { + /* + * Stalling EP0 completes the control transaction, move back to + * the SETUP state. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; + return 0; + } + + if (wedge) + uep->wedged = true; + else if (!stall) + uep->wedged = false; + + return 0; +} + +static int isp1760_ep_set_halt(struct usb_ep *ep, int value) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + value ? "set" : "clear", uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, value, false); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static int isp1760_ep_set_wedge(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: set wedge on ep%02x)\n", __func__, + uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, true, true); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static void isp1760_ep_fifo_flush(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + isp1760_udc_select_ep(uep); + + /* + * Set the CLBUF bit twice to flush both buffers in case double + * buffering is enabled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static const struct usb_ep_ops isp1760_ep_ops = { + .enable = isp1760_ep_enable, + .disable = isp1760_ep_disable, + .alloc_request = isp1760_ep_alloc_request, + .free_request = isp1760_ep_free_request, + .queue = isp1760_ep_queue, + .dequeue = isp1760_ep_dequeue, + .set_halt = isp1760_ep_set_halt, + .set_wedge = isp1760_ep_set_wedge, + .fifo_flush = isp1760_ep_fifo_flush, +}; + +/* ----------------------------------------------------------------------------- + * Device States + */ + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_connect(struct isp1760_udc *udc) +{ + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + mod_timer(&udc->vbus_timer, jiffies + ISP1760_VBUS_POLL_INTERVAL); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_disconnect(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_POWERED) + return; + + dev_dbg(udc->isp->dev, "Device disconnected in state %u\n", + udc->gadget.state); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + if (udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + del_timer(&udc->vbus_timer); + + /* TODO Reset all endpoints ? */ +} + +static void isp1760_udc_init_hw(struct isp1760_udc *udc) +{ + /* + * The device controller currently shares its interrupt with the host + * controller, the DC_IRQ polarity and signaling mode are ignored. Set + * the to active-low level-triggered. + * + * Configure the control, in and out pipes to generate interrupts on + * ACK tokens only (and NYET for the out pipe). The default + * configuration also generates an interrupt on the first NACK token. + */ + isp1760_udc_write(udc, DC_INTCONF, DC_CDBGMOD_ACK | DC_DDBGMODIN_ACK | + DC_DDBGMODOUT_ACK_NYET); + + isp1760_udc_write(udc, DC_INTENABLE, DC_IEPRXTX(7) | DC_IEPRXTX(6) | + DC_IEPRXTX(5) | DC_IEPRXTX(4) | DC_IEPRXTX(3) | + DC_IEPRXTX(2) | DC_IEPRXTX(1) | DC_IEPRXTX(0) | + DC_IEP0SETUP | DC_IEVBUS | DC_IERESM | DC_IESUSP | + DC_IEHS_STA | DC_IEBRST); + + if (udc->connected) + isp1760_set_pullup(udc->isp, true); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN); +} + +static void isp1760_udc_reset(struct isp1760_udc *udc) +{ + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* + * The bus reset has reset most registers to their default value, + * reinitialize the UDC hardware. + */ + isp1760_udc_init_hw(udc); + + udc->ep0_state = ISP1760_CTRL_SETUP; + udc->gadget.speed = USB_SPEED_FULL; + + usb_gadget_udc_reset(&udc->gadget, udc->driver); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_suspend(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void isp1760_udc_resume(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* ----------------------------------------------------------------------------- + * Gadget Operations + */ + +static int isp1760_udc_get_frame(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + return isp1760_udc_read(udc, DC_FRAMENUM) & ((1 << 11) - 1); +} + +static int isp1760_udc_wakeup(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + return -ENOTSUPP; +} + +static int isp1760_udc_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static int isp1760_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + isp1760_set_pullup(udc->isp, is_on); + udc->connected = is_on; + + return 0; +} + +static int isp1760_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + /* The hardware doesn't support low speed. */ + if (driver->max_speed < USB_SPEED_FULL) { + dev_err(udc->isp->dev, "Invalid gadget driver\n"); + return -EINVAL; + } + + spin_lock(&udc->lock); + + if (udc->driver) { + dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); + spin_unlock(&udc->lock); + return -EBUSY; + } + + udc->driver = driver; + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, "starting UDC with driver %s\n", + driver->function); + + udc->devstatus = 0; + udc->connected = true; + + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + /* DMA isn't supported yet, don't enable the DMA clock. */ + isp1760_udc_write(udc, DC_MODE, DC_GLINTENA); + + isp1760_udc_init_hw(udc); + + dev_dbg(udc->isp->dev, "UDC started with driver %s\n", + driver->function); + + return 0; +} + +static int isp1760_udc_stop(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + del_timer_sync(&udc->vbus_timer); + + isp1760_udc_write(udc, DC_MODE, 0); + + spin_lock(&udc->lock); + udc->driver = NULL; + spin_unlock(&udc->lock); + + return 0; +} + +static struct usb_gadget_ops isp1760_udc_ops = { + .get_frame = isp1760_udc_get_frame, + .wakeup = isp1760_udc_wakeup, + .set_selfpowered = isp1760_udc_set_selfpowered, + .pullup = isp1760_udc_pullup, + .udc_start = isp1760_udc_start, + .udc_stop = isp1760_udc_stop, +}; + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t isp1760_udc_irq(int irq, void *dev) +{ + struct isp1760_udc *udc = dev; + unsigned int i; + u32 status; + + status = isp1760_udc_read(udc, DC_INTERRUPT) + & isp1760_udc_read(udc, DC_INTENABLE); + isp1760_udc_write(udc, DC_INTERRUPT, status); + + if (status & DC_IEVBUS) { + dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); + /* The VBUS interrupt is only triggered when VBUS appears. */ + spin_lock(&udc->lock); + isp1760_udc_connect(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEBRST) { + dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); + + isp1760_udc_reset(udc); + } + + for (i = 0; i <= 7; ++i) { + struct isp1760_ep *ep = &udc->ep[i*2]; + + if (status & DC_IEPTX(i)) { + dev_dbg(udc->isp->dev, "%s(EPTX%u)\n", __func__, i); + isp1760_ep_tx_complete(ep); + } + + if (status & DC_IEPRX(i)) { + dev_dbg(udc->isp->dev, "%s(EPRX%u)\n", __func__, i); + isp1760_ep_rx_ready(i ? ep - 1 : ep); + } + } + + if (status & DC_IEP0SETUP) { + dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); + + isp1760_ep0_setup(udc); + } + + if (status & DC_IERESM) { + dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); + isp1760_udc_resume(udc); + } + + if (status & DC_IESUSP) { + dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); + + spin_lock(&udc->lock); + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else + isp1760_udc_suspend(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEHS_STA) { + dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); + udc->gadget.speed = USB_SPEED_HIGH; + } + + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static void isp1760_udc_vbus_poll(unsigned long data) +{ + struct isp1760_udc *udc = (struct isp1760_udc *)data; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else if (udc->gadget.state >= USB_STATE_POWERED) + mod_timer(&udc->vbus_timer, + jiffies + ISP1760_VBUS_POLL_INTERVAL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Registration + */ + +static void isp1760_udc_init_eps(struct isp1760_udc *udc) +{ + unsigned int i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (i = 0; i < ARRAY_SIZE(udc->ep); ++i) { + struct isp1760_ep *ep = &udc->ep[i]; + unsigned int ep_num = (i + 1) / 2; + bool is_in = !(i & 1); + + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + ep->addr = (ep_num && is_in ? USB_DIR_IN : USB_DIR_OUT) + | ep_num; + ep->desc = NULL; + + sprintf(ep->name, "ep%u%s", ep_num, + ep_num ? (is_in ? "in" : "out") : ""); + + ep->ep.ops = &isp1760_ep_ops; + ep->ep.name = ep->name; + + /* + * Hardcode the maximum packet sizes for now, to 64 bytes for + * the control endpoint and 512 bytes for all other endpoints. + * This fits in the 8kB FIFO without double-buffering. + */ + if (ep_num == 0) { + ep->ep.maxpacket = 64; + ep->maxpacket = 64; + udc->gadget.ep0 = &ep->ep; + } else { + ep->ep.maxpacket = 512; + ep->maxpacket = 0; + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } +} + +static int isp1760_udc_init(struct isp1760_udc *udc) +{ + u16 scratch; + u32 chipid; + + /* + * Check that the controller is present by writing to the scratch + * register, modifying the bus pattern by reading from the chip ID + * register, and reading the scratch register value back. The chip ID + * and scratch register contents must match the expected values. + */ + isp1760_udc_write(udc, DC_SCRATCH, 0xbabe); + chipid = isp1760_udc_read(udc, DC_CHIPID); + scratch = isp1760_udc_read(udc, DC_SCRATCH); + + if (scratch != 0xbabe) { + dev_err(udc->isp->dev, + "udc: scratch test failed (0x%04x/0x%08x)\n", + scratch, chipid); + return -ENODEV; + } + + if (chipid != 0x00011582) { + dev_err(udc->isp->dev, "udc: invalid chip ID 0x%08x\n", chipid); + return -ENODEV; + } + + /* Reset the device controller. */ + isp1760_udc_write(udc, DC_MODE, DC_SFRESET); + usleep_range(10000, 11000); + isp1760_udc_write(udc, DC_MODE, 0); + usleep_range(10000, 11000); + + return 0; +} + +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + struct isp1760_udc *udc = &isp->udc; + const char *devname; + int ret; + + udc->irq = -1; + udc->isp = isp; + udc->regs = isp->regs; + + spin_lock_init(&udc->lock); + setup_timer(&udc->vbus_timer, isp1760_udc_vbus_poll, + (unsigned long)udc); + + ret = isp1760_udc_init(udc); + if (ret < 0) + return ret; + + devname = dev_name(isp->dev); + udc->irqname = kmalloc(strlen(devname) + 7, GFP_KERNEL); + if (!udc->irqname) + return -ENOMEM; + + sprintf(udc->irqname, "%s (udc)", devname); + + ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | IRQF_DISABLED | + irqflags, udc->irqname, udc); + if (ret < 0) + goto error; + + udc->irq = irq; + + /* + * Initialize the gadget static fields and register its device. Gadget + * fields that vary during the life time of the gadget are initialized + * by the UDC core. + */ + udc->gadget.ops = &isp1760_udc_ops; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.name = "isp1761_udc"; + + isp1760_udc_init_eps(udc); + + ret = usb_add_gadget_udc(isp->dev, &udc->gadget); + if (ret < 0) + goto error; + + return 0; + +error: + if (udc->irq >= 0) + free_irq(udc->irq, udc); + kfree(udc->irqname); + + return ret; +} + +void isp1760_udc_unregister(struct isp1760_device *isp) +{ + struct isp1760_udc *udc = &isp->udc; + + usb_del_gadget_udc(&udc->gadget); + + free_irq(udc->irq, udc); + kfree(udc->irqname); +} diff --git a/drivers/usb/isp1760/isp1760-udc.h b/drivers/usb/isp1760/isp1760-udc.h new file mode 100644 index 0000000..4af6ba6 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-udc.h @@ -0,0 +1,106 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * 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 _ISP1760_UDC_H_ +#define _ISP1760_UDC_H_ + +#include +#include +#include +#include +#include + +struct isp1760_device; +struct isp1760_udc; + +enum isp1760_ctrl_state { + ISP1760_CTRL_SETUP, /* Waiting for a SETUP transaction */ + ISP1760_CTRL_DATA_IN, /* Setup received, data IN stage */ + ISP1760_CTRL_DATA_OUT, /* Setup received, data OUT stage */ + ISP1760_CTRL_STATUS, /* 0-length request in status stage */ +}; + +struct isp1760_ep { + struct isp1760_udc *udc; + struct usb_ep ep; + + struct list_head queue; + + unsigned int addr; + unsigned int maxpacket; + char name[7]; + + const struct usb_endpoint_descriptor *desc; + + bool rx_pending; + bool halted; + bool wedged; +}; + +/** + * struct isp1760_udc - UDC state information + * irq: IRQ number + * irqname: IRQ name (as passed to request_irq) + * regs: Base address of the UDC registers + * driver: Gadget driver + * gadget: Gadget device + * lock: Protects driver, vbus_timer, ep, ep0_*, DC_EPINDEX register + * ep: Array of endpoints + * ep0_state: Control request state for endpoint 0 + * ep0_dir: Direction of the current control request + * ep0_length: Length of the current control request + * connected: Tracks gadget driver bus connection state + */ +struct isp1760_udc { +#if CONFIG_USB_ISP1761_UDC + struct isp1760_device *isp; + + int irq; + char *irqname; + void __iomem *regs; + + struct usb_gadget_driver *driver; + struct usb_gadget gadget; + + spinlock_t lock; + struct timer_list vbus_timer; + + struct isp1760_ep ep[15]; + + enum isp1760_ctrl_state ep0_state; + u8 ep0_dir; + u16 ep0_length; + + bool connected; + + unsigned int devstatus; +#endif +}; + +#if CONFIG_USB_ISP1761_UDC +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags); +void isp1760_udc_unregister(struct isp1760_device *isp); +#else +static inline int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + return 0; +} + +static inline void isp1760_udc_unregister(struct isp1760_device *isp) +{ +} +#endif + +#endif -- cgit v0.10.2 From 100832abf065bc186ae48165c16546784b90a4be Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:03 +0200 Subject: usb: isp1760: Make HCD support optional Enable compilation of the isp1760 driver in pure host mode, pure device mode, or dual-role mode. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/isp1760/Kconfig b/drivers/usb/isp1760/Kconfig index c09ab8f..c94b7d9 100644 --- a/drivers/usb/isp1760/Kconfig +++ b/drivers/usb/isp1760/Kconfig @@ -1,6 +1,6 @@ config USB_ISP1760 tristate "NXP ISP 1760/1761 support" - depends on USB + depends on USB || USB_GADGET help Say Y or M here if your system as an ISP1760 USB host controller or an ISP1761 USB dual-role controller. @@ -14,9 +14,46 @@ config USB_ISP1760 To compile this driver as a module, choose M here: the module will be called isp1760. +config USB_ISP1760_HCD + bool + config USB_ISP1761_UDC - boolean "NXP ISP1761 USB Device Controller" - depends on USB_ISP1760 && USB_GADGET + bool + +if USB_ISP1760 + +choice + bool "ISP1760 Mode Selection" + default USB_ISP1760_DUAL_ROLE if (USB && USB_GADGET) + default USB_ISP1760_HOST_ROLE if (USB && !USB_GADGET) + default USB_ISP1760_GADGET_ROLE if (!USB && USB_GADGET) + +config USB_ISP1760_HOST_ROLE + bool "Host only mode" + depends on USB=y || USB=USB_ISP1760 + select USB_ISP1760_HCD + help + Select this if you want to use the ISP1760 in host mode only. The + gadget function will be disabled. + +config USB_ISP1760_GADGET_ROLE + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_ISP1760 + select USB_ISP1761_UDC + help + Select this if you want to use the ISP1760 in peripheral mode only. + The host function will be disabled. + +config USB_ISP1760_DUAL_ROLE + bool "Dual Role mode" + depends on USB=y || USB=USB_ISP1760 + depends on USB_GADGET=y || USB_GADGET=USB_ISP1760 + select USB_ISP1760_HCD + select USB_ISP1761_UDC help - The NXP ISP1761 is a dual-role high-speed USB host and device - controller. + Select this if you want to use the ISP1760 in both host and + peripheral modes. + +endchoice + +endif diff --git a/drivers/usb/isp1760/Makefile b/drivers/usb/isp1760/Makefile index 698ccb0..2b74107 100644 --- a/drivers/usb/isp1760/Makefile +++ b/drivers/usb/isp1760/Makefile @@ -1,4 +1,5 @@ -isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o +isp1760-y := isp1760-core.o isp1760-if.o +isp1760-$(CONFIG_USB_ISP1760_HCD) += isp1760-hcd.o isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o obj-$(CONFIG_USB_ISP1760) += isp1760.o diff --git a/drivers/usb/isp1760/isp1760-hcd.h b/drivers/usb/isp1760/isp1760-hcd.h index df7ea36..0c1c98d 100644 --- a/drivers/usb/isp1760/isp1760-hcd.h +++ b/drivers/usb/isp1760/isp1760-hcd.h @@ -47,6 +47,7 @@ enum isp1760_queue_head_types { }; struct isp1760_hcd { +#ifdef CONFIG_USB_ISP1760_HCD struct usb_hcd *hcd; u32 hcs_params; @@ -64,8 +65,10 @@ struct isp1760_hcd { unsigned i_thresh; unsigned long reset_done; unsigned long next_statechange; +#endif }; +#ifdef CONFIG_USB_ISP1760_HCD int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, struct resource *mem, int irq, unsigned long irqflags, struct device *dev); @@ -73,5 +76,27 @@ void isp1760_hcd_unregister(struct isp1760_hcd *priv); int isp1760_init_kmem_once(void); void isp1760_deinit_kmem_cache(void); +#else +static inline int isp1760_hcd_register(struct isp1760_hcd *priv, + void __iomem *regs, struct resource *mem, + int irq, unsigned long irqflags, + struct device *dev) +{ + return 0; +} + +static inline void isp1760_hcd_unregister(struct isp1760_hcd *priv) +{ +} + +static inline int isp1760_init_kmem_once(void) +{ + return 0; +} + +static inline void isp1760_deinit_kmem_cache(void) +{ +} +#endif #endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/isp1760/isp1760-udc.h b/drivers/usb/isp1760/isp1760-udc.h index 4af6ba6..26899ed 100644 --- a/drivers/usb/isp1760/isp1760-udc.h +++ b/drivers/usb/isp1760/isp1760-udc.h @@ -62,7 +62,7 @@ struct isp1760_ep { * connected: Tracks gadget driver bus connection state */ struct isp1760_udc { -#if CONFIG_USB_ISP1761_UDC +#ifdef CONFIG_USB_ISP1761_UDC struct isp1760_device *isp; int irq; @@ -87,7 +87,7 @@ struct isp1760_udc { #endif }; -#if CONFIG_USB_ISP1761_UDC +#ifdef CONFIG_USB_ISP1761_UDC int isp1760_udc_register(struct isp1760_device *isp, int irq, unsigned long irqflags); void isp1760_udc_unregister(struct isp1760_device *isp); -- cgit v0.10.2 From 1f8d9b9b503070e5450f49e0a341ac3b43d9164d Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:04 +0200 Subject: usb: isp1760: Remove duplicate usb_disabled() check Both isp1760_register() and isp1761_pci_probe() check whether USB is disabled by calling usb_disabled(), and bail out with an error if it is. One check is enough, remove the PCI-specific check. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/isp1760/isp1760-if.c b/drivers/usb/isp1760/isp1760-if.c index c2a94c9..264be4d 100644 --- a/drivers/usb/isp1760/isp1760-if.c +++ b/drivers/usb/isp1760/isp1760-if.c @@ -126,9 +126,6 @@ static int isp1761_pci_probe(struct pci_dev *dev, unsigned int devflags = 0; int ret; - if (usb_disabled()) - return -ENODEV; - if (!dev->irq) return -ENODEV; -- cgit v0.10.2 From d21daf1e90514cee8e3fb11c8e28acee3fb87edf Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:05 +0200 Subject: usb: isp1760: Fix USB disabled check The isp1760 driver registration function returns an error if USB is disabled. This made sense when the driver only supported host controllers, but now that it supports peripheral controllers host support isn't mandatory anymore. Fix this by returning an error only when both the HCD and UDC functions are disabled, either through the kernel configuration or at runtime. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c index 727e90a..b982755 100644 --- a/drivers/usb/isp1760/isp1760-core.c +++ b/drivers/usb/isp1760/isp1760-core.c @@ -112,9 +112,15 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) { struct isp1760_device *isp; + bool udc_disabled = !(devflags & ISP1760_FLAG_ISP1761); int ret; - if (usb_disabled()) + /* + * If neither the HCD not the UDC is enabled return an error, as no + * device would be registered. + */ + if ((!IS_ENABLED(CONFIG_USB_ISP1760_HCD) || usb_disabled()) && + (!IS_ENABLED(CONFIG_USB_ISP1761_UDC) || udc_disabled)) return -ENODEV; /* prevent usb-core allocating DMA pages */ @@ -137,12 +143,14 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, isp1760_init_core(isp); - ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, - irqflags | IRQF_SHARED, dev); - if (ret < 0) - return ret; + if (IS_ENABLED(CONFIG_USB_ISP1760_HCD) && !usb_disabled()) { + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, + irqflags | IRQF_SHARED, dev); + if (ret < 0) + return ret; + } - if (devflags & ISP1760_FLAG_ISP1761) { + if (IS_ENABLED(CONFIG_USB_ISP1761_UDC) && !udc_disabled) { ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | IRQF_DISABLED); if (ret < 0) { @@ -160,9 +168,7 @@ void isp1760_unregister(struct device *dev) { struct isp1760_device *isp = dev_get_drvdata(dev); - if (isp->devflags & ISP1760_FLAG_ISP1761) - isp1760_udc_unregister(isp); - + isp1760_udc_unregister(isp); isp1760_hcd_unregister(&isp->hcd); } diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 568446c..996b2c1 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -2226,6 +2226,9 @@ error: void isp1760_hcd_unregister(struct isp1760_hcd *priv) { + if (!priv->hcd) + return; + usb_remove_hcd(priv->hcd); usb_put_hcd(priv->hcd); } diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c index 6bfda30..9612d79 100644 --- a/drivers/usb/isp1760/isp1760-udc.c +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -1488,6 +1488,9 @@ void isp1760_udc_unregister(struct isp1760_device *isp) { struct isp1760_udc *udc = &isp->udc; + if (!udc->isp) + return; + usb_del_gadget_udc(&udc->gadget); free_irq(udc->irq, udc); -- cgit v0.10.2 From dd811ba7427f7e6b7e521f3738a5313d00319675 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 16 Jan 2015 18:28:58 +0800 Subject: usb: phy: mxs: don't need IP fix for imx6sx The RLT code has already done it, so no software operation is needed. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 58cae78..fcadbd2 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -131,8 +131,7 @@ static const struct mxs_phy_data vf610_phy_data = { }; static const struct mxs_phy_data imx6sx_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | - MXS_PHY_NEED_IP_FIX, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, }; static const struct of_device_id mxs_phy_dt_ids[] = { -- cgit v0.10.2 From 7b09e67639d6857439bae0d6276d443a7c2c9c40 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Fri, 16 Jan 2015 18:28:59 +0800 Subject: usb: phy: mxs: refine mxs_phy_disconnect_line For non-otg mode, we keep the usage of disconnect line between phy analog and digital unchanging; for otg mode, at peripheral role, we keep the usage unchanging too, at host role, the digital part needs to know dp/dm change to respond device's data pulse when it is at low power mode. Signed-off-by: Peter Chen Signed-off-by: Li Jun Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index fcadbd2..eb74605 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -40,6 +40,7 @@ #define BM_USBPHY_CTRL_SFTRST BIT(31) #define BM_USBPHY_CTRL_CLKGATE BIT(30) +#define BM_USBPHY_CTRL_OTG_ID_VALUE BIT(27) #define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26) #define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25) #define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23) @@ -255,6 +256,18 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect) usleep_range(500, 1000); } +static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy) +{ + void __iomem *base = mxs_phy->phy.io_priv; + u32 phyctrl = readl(base + HW_USBPHY_CTRL); + + if (IS_ENABLED(CONFIG_USB_OTG) && + !(phyctrl & BM_USBPHY_CTRL_OTG_ID_VALUE)) + return true; + + return false; +} + static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) { bool vbus_is_on = false; @@ -269,7 +282,7 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); - if (on && !vbus_is_on) + if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) __mxs_phy_disconnect_line(mxs_phy, true); else __mxs_phy_disconnect_line(mxs_phy, false); -- cgit v0.10.2 From efdbd3a5d6e6108f1565ab4dc4c53e77aba6fe0a Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 16 Jan 2015 18:29:00 +0800 Subject: usb: phy: mxs: do not set PWD.RXPWD1PT1 for low speed connection At very rare cases, the SoF will not send out after resume with low speed connection. The workaround is do not power down PWD.RXPWD1PT1 bit during the suspend. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index eb74605..c3177a1 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -70,6 +70,9 @@ #define ANADIG_USB2_LOOPBACK_SET 0x244 #define ANADIG_USB2_LOOPBACK_CLR 0x248 +#define ANADIG_USB1_MISC 0x1f0 +#define ANADIG_USB2_MISC 0x250 + #define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12) #define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11) @@ -81,6 +84,11 @@ #define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2) #define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5) +#define BM_ANADIG_USB1_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB1_MISC_RX_VMIN_FS BIT(28) +#define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -323,13 +331,50 @@ static void mxs_phy_shutdown(struct usb_phy *phy) clk_disable_unprepare(mxs_phy->clk); } +static bool mxs_phy_is_low_speed_connection(struct mxs_phy *mxs_phy) +{ + unsigned int line_state; + /* bit definition is the same for all controllers */ + unsigned int dp_bit = BM_ANADIG_USB1_MISC_RX_VPIN_FS, + dm_bit = BM_ANADIG_USB1_MISC_RX_VMIN_FS; + unsigned int reg = ANADIG_USB1_MISC; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return false; + + if (mxs_phy->port_id == 0) + reg = ANADIG_USB1_MISC; + else if (mxs_phy->port_id == 1) + reg = ANADIG_USB2_MISC; + + regmap_read(mxs_phy->regmap_anatop, reg, &line_state); + + if ((line_state & (dp_bit | dm_bit)) == dm_bit) + return true; + else + return false; +} + static int mxs_phy_suspend(struct usb_phy *x, int suspend) { int ret; struct mxs_phy *mxs_phy = to_mxs_phy(x); + bool low_speed_connection, vbus_is_on; + + low_speed_connection = mxs_phy_is_low_speed_connection(mxs_phy); + vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); if (suspend) { - writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + /* + * FIXME: Do not power down RXPWD1PT1 bit for low speed + * connect. The low speed connection will have problem at + * very rare cases during usb suspend and resume process. + */ + if (low_speed_connection & vbus_is_on) + writel(0xfffbffff, x->io_priv + HW_USBPHY_PWD); + else + writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); clk_disable_unprepare(mxs_phy->clk); -- cgit v0.10.2 From e235f7b86f33beea7e096b46db1802dbf5d7d22e Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 16 Jan 2015 18:29:01 +0800 Subject: usb: phy: mxs: add delay before set phyctrl.clkgate There is a request from IC engineer that if we doesn't set phypwd as 0xffffffff, we need to delay about five 32Khz cycles before set phy's pwd register, otherwise, the wakeup signal may can't wake up controller. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index c3177a1..8f7cb06 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -371,10 +371,16 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) * connect. The low speed connection will have problem at * very rare cases during usb suspend and resume process. */ - if (low_speed_connection & vbus_is_on) - writel(0xfffbffff, x->io_priv + HW_USBPHY_PWD); - else + if (low_speed_connection & vbus_is_on) { + /* + * If value to be set as pwd value is not 0xffffffff, + * several 32Khz cycles are needed. + */ + mxs_phy_clock_switch_delay(); + writel(0xffbfffff, x->io_priv + HW_USBPHY_PWD); + } else { writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + } writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); clk_disable_unprepare(mxs_phy->clk); -- cgit v0.10.2 From 727968357eb84497ac8e2514cf06be3d8779d24f Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 19 Jan 2015 13:52:57 +0100 Subject: usb: gadget: uvc: use explicit type instead of void * The first parameter of __uvcg_iter_strm_cls() is always used in the context of struct uvcg_streaming_header, so change the function prototype accordingly. Acked-by: Laurent Pinchart Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 49f25e8..51d8e9e 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1918,10 +1918,10 @@ enum uvcg_strm_type { UVCG_FRAME }; -static int __uvcg_iter_strm_cls(void *priv1, void *priv2, void *priv3, +static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, + void *priv2, void *priv3, int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type)) { - struct uvcg_streaming_header *h = priv1; struct uvcg_format_ptr *f; struct config_group *grp; struct config_item *item; -- cgit v0.10.2 From 578d0b6b6171e7a7afa49e74f87c683297e9cae9 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 19 Jan 2015 13:52:58 +0100 Subject: usb: gadget: uvc: comments for iterating over streaming hierarchy The purpose of the functions and their parametrs might not be obvious to the reader, so explain it. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 51d8e9e..3c0467b 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1918,6 +1918,25 @@ enum uvcg_strm_type { UVCG_FRAME }; +/* + * Iterate over a hierarchy of streaming descriptors' config items. + * The items are created by the user with configfs. + * + * It "processes" the header pointed to by @priv1, then for each format + * that follows the header "processes" the format itself and then for + * each frame inside a format "processes" the frame. + * + * As a "processing" function the @fun is used. + * + * __uvcg_iter_strm_cls() is used in two context: first, to calculate + * the amount of memory needed for an array of streaming descriptors + * and second, to actually fill the array. + * + * @h: streaming header pointer + * @priv2: an "inout" parameter (the caller might want to see the changes to it) + * @priv3: an "inout" parameter (the caller might want to see the changes to it) + * @fun: callback function for processing each level of the hierarchy + */ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, void *priv2, void *priv3, int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type)) @@ -1951,6 +1970,14 @@ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, return ret; } +/* + * Count how many bytes are needed for an array of streaming descriptors. + * + * @priv1: pointer to a header, format or frame + * @priv2: inout parameter, accumulated size of the array + * @priv3: inout parameter, accumulated number of the array elements + * @n: unused, this function's prototype must match @fun in __uvcg_iter_strm_cls + */ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, enum uvcg_strm_type type) { @@ -2000,6 +2027,13 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, return 0; } +/* + * Fill an array of streaming descriptors. + * + * @priv1: pointer to a header, format or frame + * @priv2: inout parameter, pointer into a block of memory + * @priv3: inout parameter, pointer to a 2-dimensional array + */ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, enum uvcg_strm_type type) { -- cgit v0.10.2 From 4cd8f6d05599be8bd2052df28c805f7c2f8ff479 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:24:26 -0600 Subject: usb: dwc3: gadget: avoid variable shadowing We already have both ret and dwc defined in this same function. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 6c5e344..944036d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1110,15 +1110,10 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * handled. */ if (dep->stream_capable) { - int ret; - ret = __dwc3_gadget_kick_transfer(dep, 0, true); - if (ret && ret != -EBUSY) { - struct dwc3 *dwc = dep->dwc; - + if (ret && ret != -EBUSY) dev_dbg(dwc->dev, "%s: failed to kick transfers\n", dep->name); - } } return 0; -- cgit v0.10.2 From e9f2aa871c732acfe29c3ba5e6b541b2aa786778 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:49:28 -0600 Subject: usb: dwc3: gadget: WARN() in case of unknown IRQ if an unknown IRQ event is triggered, that means the HW is really misbehaving. Instead of printing a debug message, let's WARN() so users report when that happens. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 944036d..c6eec26 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2518,7 +2518,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, dev_vdbg(dwc->dev, "Overflow\n"); break; default: - dev_dbg(dwc->dev, "UNKNOWN IRQ %d\n", event->type); + dev_WARN(dwc->dev, "UNKNOWN IRQ %d\n", event->type); } } -- cgit v0.10.2 From 6bac4ff0a52262faea4649dc8142962767f88b82 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:47:02 -0600 Subject: usb: dwc3: trace: add trace logs for core and gadget Sometimes we want to just print a formatted string without passing any extra data. The following will be used for removing reliance on dev_vdbg() from dwc3. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 9fc20b3..9c10669 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -47,6 +47,16 @@ DEFINE_EVENT(dwc3_log_msg, dwc3_writel, TP_ARGS(vaf) ); +DEFINE_EVENT(dwc3_log_msg, dwc3_gadget, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(dwc3_log_msg, dwc3_core, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + DEFINE_EVENT(dwc3_log_msg, dwc3_ep0, TP_PROTO(struct va_format *vaf), TP_ARGS(vaf) -- cgit v0.10.2 From 73815280a5af8d95e818eb25f4091e86497dfa4b Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:48:14 -0600 Subject: usb: dwc3: remove reliance on dev_vdbg() By moving all dev_vdbg() to tracepoints, we can finally get rid of dev_vdbg() usage from dwc3. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 58b5b2c..edbf9c8 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -104,12 +104,6 @@ config USB_DWC3_DEBUG help Say Y here to enable debugging messages on DWC3 Driver. -config USB_DWC3_VERBOSE - bool "Enable Verbose Debugging Messages" - depends on USB_DWC3_DEBUG - help - Say Y here to enable verbose debugging messages on DWC3 Driver. - config DWC3_HOST_USB3_LPM_ENABLE bool "Enable USB3 LPM Capability" depends on USB_DWC3_HOST=y || USB_DWC3_DUAL_ROLE=y diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index bb34fbc..46172f4 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -2,7 +2,6 @@ CFLAGS_trace.o := -I$(src) ccflags-$(CONFIG_USB_DWC3_DEBUG) := -DDEBUG -ccflags-$(CONFIG_USB_DWC3_VERBOSE) += -DVERBOSE_DEBUG obj-$(CONFIG_USB_DWC3) += dwc3.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 25ddc39..9f0e209 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -345,7 +345,7 @@ static void dwc3_core_num_eps(struct dwc3 *dwc) dwc->num_in_eps = DWC3_NUM_IN_EPS(parms); dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps; - dev_vdbg(dwc->dev, "found %d IN and %d OUT endpoints\n", + dwc3_trace(trace_dwc3_core, "found %d IN and %d OUT endpoints", dwc->num_in_eps, dwc->num_out_eps); } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index c6eec26..7cce00e 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -139,7 +139,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state) udelay(5); } - dev_vdbg(dwc->dev, "link state change request timed out\n"); + dwc3_trace(trace_dwc3_gadget, + "link state change request timed out"); return -ETIMEDOUT; } @@ -219,7 +220,7 @@ int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc) fifo_size |= (last_fifo_depth << 16); - dev_vdbg(dwc->dev, "%s: Fifo Addr %04x Size %d\n", + dwc3_trace(trace_dwc3_gadget, "%s: Fifo Addr %04x Size %d", dep->name, last_fifo_depth, fifo_size & 0xffff); dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num), fifo_size); @@ -287,7 +288,8 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) do { reg = dwc3_readl(dwc->regs, DWC3_DGCMD); if (!(reg & DWC3_DGCMD_CMDACT)) { - dev_vdbg(dwc->dev, "Command Complete --> %d\n", + dwc3_trace(trace_dwc3_gadget, + "Command Complete --> %d", DWC3_DGCMD_STATUS(reg)); return 0; } @@ -297,8 +299,11 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) * interrupt context. */ timeout--; - if (!timeout) + if (!timeout) { + dwc3_trace(trace_dwc3_gadget, + "Command Timed Out"); return -ETIMEDOUT; + } udelay(1); } while (1); } @@ -320,7 +325,8 @@ int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, do { reg = dwc3_readl(dwc->regs, DWC3_DEPCMD(ep)); if (!(reg & DWC3_DEPCMD_CMDACT)) { - dev_vdbg(dwc->dev, "Command Complete --> %d\n", + dwc3_trace(trace_dwc3_gadget, + "Command Complete --> %d", DWC3_DEPCMD_STATUS(reg)); return 0; } @@ -330,8 +336,11 @@ int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, * interrupt context. */ timeout--; - if (!timeout) + if (!timeout) { + dwc3_trace(trace_dwc3_gadget, + "Command Timed Out"); return -ETIMEDOUT; + } udelay(1); } while (1); @@ -489,7 +498,7 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, u32 reg; int ret; - dev_vdbg(dwc->dev, "Enabling %s\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "Enabling %s", dep->name); if (!(dep->flags & DWC3_EP_ENABLED)) { ret = dwc3_gadget_start_config(dwc, dep); @@ -726,10 +735,9 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_request *req, dma_addr_t dma, unsigned length, unsigned last, unsigned chain, unsigned node) { - struct dwc3 *dwc = dep->dwc; struct dwc3_trb *trb; - dev_vdbg(dwc->dev, "%s: req %p dma %08llx length %d%s%s\n", + dwc3_trace(trace_dwc3_gadget, "%s: req %p dma %08llx length %d%s%s", dep->name, req, (unsigned long long) dma, length, last ? " last" : "", chain ? " chain" : ""); @@ -931,7 +939,7 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, u32 cmd; if (start_new && (dep->flags & DWC3_EP_BUSY)) { - dev_vdbg(dwc->dev, "%s: endpoint busy\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "%s: endpoint busy", dep->name); return -EBUSY; } dep->flags &= ~DWC3_EP_PENDING_REQUEST; @@ -1002,8 +1010,9 @@ static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, u32 uf; if (list_empty(&dep->request_list)) { - dev_vdbg(dwc->dev, "ISOC ep %s run out for requests.\n", - dep->name); + dwc3_trace(trace_dwc3_gadget, + "ISOC ep %s run out for requests", + dep->name); dep->flags |= DWC3_EP_PENDING_REQUEST; return; } @@ -1144,8 +1153,6 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, goto out; } - dev_vdbg(dwc->dev, "queing request %p to %s length %d\n", - request, ep->name, request->length); trace_dwc3_ep_queue(req); ret = __dwc3_gadget_ep_queue(dep, req); @@ -1460,7 +1467,7 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) udelay(1); } while (1); - dev_vdbg(dwc->dev, "gadget %s data soft-%s\n", + dwc3_trace(trace_dwc3_gadget, "gadget %s data soft-%s", dwc->gadget_driver ? dwc->gadget_driver->function : "no-function", is_on ? "connect" : "disconnect"); @@ -1680,7 +1687,7 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, dep->endpoint.name = dep->name; - dev_vdbg(dwc->dev, "initializing %s\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "initializing %s", dep->name); if (epnum == 0 || epnum == 1) { usb_ep_set_maxpacket_limit(&dep->endpoint, 512); @@ -1717,13 +1724,15 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_out_eps, 0); if (ret < 0) { - dev_vdbg(dwc->dev, "failed to allocate OUT endpoints\n"); + dwc3_trace(trace_dwc3_gadget, + "failed to allocate OUT endpoints"); return ret; } ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_in_eps, 1); if (ret < 0) { - dev_vdbg(dwc->dev, "failed to allocate IN endpoints\n"); + dwc3_trace(trace_dwc3_gadget, + "failed to allocate IN endpoints"); return ret; } @@ -1969,7 +1978,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, } else { int ret; - dev_vdbg(dwc->dev, "%s: reason %s\n", + dwc3_trace(trace_dwc3_gadget, "%s: reason %s", dep->name, event->status & DEPEVT_STATUS_TRANSFER_ACTIVE ? "Transfer Active" @@ -1993,7 +2002,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, switch (event->status) { case DEPEVT_STREAMEVT_FOUND: - dev_vdbg(dwc->dev, "Stream %d found and started\n", + dwc3_trace(trace_dwc3_gadget, + "Stream %d found and started", event->parameters); break; @@ -2007,7 +2017,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, dev_dbg(dwc->dev, "%s FIFO Overrun\n", dep->name); break; case DWC3_DEPEVT_EPCMDCMPLT: - dev_vdbg(dwc->dev, "Endpoint Command Complete\n"); + dwc3_trace(trace_dwc3_gadget, "Endpoint Command Complete"); break; } } @@ -2381,7 +2391,8 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, (pwropt != DWC3_GHWPARAMS1_EN_PWROPT_HIB)) { if ((dwc->link_state == DWC3_LINK_STATE_U3) && (next == DWC3_LINK_STATE_RESUME)) { - dev_vdbg(dwc->dev, "ignoring transition U3 -> Resume\n"); + dwc3_trace(trace_dwc3_gadget, + "ignoring transition U3 -> Resume"); return; } } @@ -2503,19 +2514,19 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); break; case DWC3_DEVICE_EVENT_EOPF: - dev_vdbg(dwc->dev, "End of Periodic Frame\n"); + dwc3_trace(trace_dwc3_gadget, "End of Periodic Frame"); break; case DWC3_DEVICE_EVENT_SOF: - dev_vdbg(dwc->dev, "Start of Periodic Frame\n"); + dwc3_trace(trace_dwc3_gadget, "Start of Periodic Frame"); break; case DWC3_DEVICE_EVENT_ERRATIC_ERROR: - dev_vdbg(dwc->dev, "Erratic Error\n"); + dwc3_trace(trace_dwc3_gadget, "Erratic Error"); break; case DWC3_DEVICE_EVENT_CMD_CMPL: - dev_vdbg(dwc->dev, "Command Complete\n"); + dwc3_trace(trace_dwc3_gadget, "Command Complete"); break; case DWC3_DEVICE_EVENT_OVERFLOW: - dev_vdbg(dwc->dev, "Overflow\n"); + dwc3_trace(trace_dwc3_gadget, "Overflow"); break; default: dev_WARN(dwc->dev, "UNKNOWN IRQ %d\n", event->type); -- cgit v0.10.2 From 5c7b3b02de766a8634708953e805399e3544a290 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 29 Jan 2015 10:29:18 -0600 Subject: usb: dwc3: gadget: add missing spin_lock() commit 8e74475b0e0a (usb: dwc3: gadget: use udc-core's reset notifier) added support for the new UDC core's reset notifier to dwc3 but while at it, it removed a spin_lock() from dwc3_reset_gadget() which might cause an unbalanced spin_unlock() further down the line Fixes: 8e74475b0e0a (usb: dwc3: gadget: use udc-core's reset notifier) Cc: # v3.19 Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 7cce00e..eccd29b 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2045,6 +2045,7 @@ static void dwc3_resume_gadget(struct dwc3 *dwc) if (dwc->gadget_driver && dwc->gadget_driver->resume) { spin_unlock(&dwc->lock); dwc->gadget_driver->resume(&dwc->gadget); + spin_lock(&dwc->lock); } } -- cgit v0.10.2 From 80b2502cea34a965a6b3390691854e753945ca5f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:24 +0800 Subject: usb: gadget: introduce is_selfpowered for usb_gadget Whether the gadget is selfpowerwed or not can be determined by composite core, so we can use a common entry to indicate if the self-powered is supported by gadget, and the related private variable at individual udc driver can be deleted. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 70ddb39..e2f00fd 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -523,6 +523,7 @@ struct usb_gadget_ops { * enabled HNP support. * @quirk_ep_out_aligned_size: epout requires buffer size to be aligned to * MaxPacketSize. + * @is_selfpowered: if the gadget is self-powered. * * Gadgets have a mostly-portable "gadget driver" implementing device * functions, handling all usb configurations and interfaces. Gadget @@ -563,6 +564,7 @@ struct usb_gadget { unsigned a_hnp_support:1; unsigned a_alt_hnp_support:1; unsigned quirk_ep_out_aligned_size:1; + unsigned is_selfpowered:1; }; #define work_to_gadget(w) (container_of((w), struct usb_gadget, work)) -- cgit v0.10.2 From 1009f9a36d30f87676ede4c69f97a4eb45001f15 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:25 +0800 Subject: usb: chipidea: udc: add set_selfpowered gaget ops The gadget power property will be used at get_status request. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 4fe18ce..ff45104 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -819,8 +819,8 @@ __acquires(hwep->lock) } if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - /* Assume that device is bus powered for now. */ - *(u16 *)req->buf = ci->remote_wakeup << 1; + *(u16 *)req->buf = (ci->remote_wakeup << 1) | + ci->gadget.is_selfpowered; } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -1520,6 +1520,19 @@ static int ci_udc_vbus_draw(struct usb_gadget *_gadget, unsigned ma) return -ENOTSUPP; } +static int ci_udc_selfpowered(struct usb_gadget *_gadget, int is_on) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + struct ci_hw_ep *hwep = ci->ep0in; + unsigned long flags; + + spin_lock_irqsave(hwep->lock, flags); + _gadget->is_selfpowered = (is_on != 0); + spin_unlock_irqrestore(hwep->lock, flags); + + return 0; +} + /* Change Data+ pullup status * this func is used by usb_gadget_connect/disconnet */ @@ -1549,6 +1562,7 @@ static int ci_udc_stop(struct usb_gadget *gadget); static const struct usb_gadget_ops usb_gadget_ops = { .vbus_session = ci_udc_vbus_session, .wakeup = ci_udc_wakeup, + .set_selfpowered = ci_udc_selfpowered, .pullup = ci_udc_pullup, .vbus_draw = ci_udc_vbus_draw, .udc_start = ci_udc_start, -- cgit v0.10.2 From 7301971f50d08250ecddc1a2298dde5466d6e315 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:26 +0800 Subject: usb: gadget: at91_udc: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index c862656..c0ec5f7 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -176,7 +176,7 @@ static int proc_udc_show(struct seq_file *s, void *unused) udc->enabled ? (udc->vbus ? "active" : "enabled") : "disabled", - udc->selfpowered ? "self" : "VBUS", + udc->gadget.is_selfpowered ? "self" : "VBUS", udc->suspended ? ", suspended" : "", udc->driver ? udc->driver->driver.name : "(none)"); @@ -1000,7 +1000,7 @@ static int at91_set_selfpowered(struct usb_gadget *gadget, int is_on) unsigned long flags; spin_lock_irqsave(&udc->lock, flags); - udc->selfpowered = (is_on != 0); + gadget->is_selfpowered = (is_on != 0); spin_unlock_irqrestore(&udc->lock, flags); return 0; } @@ -1149,7 +1149,7 @@ static void handle_setup(struct at91_udc *udc, struct at91_ep *ep, u32 csr) */ case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) | USB_REQ_GET_STATUS: - tmp = (udc->selfpowered << USB_DEVICE_SELF_POWERED); + tmp = (udc->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED); if (at91_udp_read(udc, AT91_UDP_GLB_STAT) & AT91_UDP_ESR) tmp |= (1 << USB_DEVICE_REMOTE_WAKEUP); PACKET("get device status\n"); @@ -1653,7 +1653,7 @@ static int at91_start(struct usb_gadget *gadget, udc->driver = driver; udc->gadget.dev.of_node = udc->pdev->dev.of_node; udc->enabled = 1; - udc->selfpowered = 1; + udc->gadget.is_selfpowered = 1; return 0; } diff --git a/drivers/usb/gadget/udc/at91_udc.h b/drivers/usb/gadget/udc/at91_udc.h index 0175246..467dcfb 100644 --- a/drivers/usb/gadget/udc/at91_udc.h +++ b/drivers/usb/gadget/udc/at91_udc.h @@ -122,7 +122,6 @@ struct at91_udc { unsigned req_pending:1; unsigned wait_for_addr_ack:1; unsigned wait_for_config_ack:1; - unsigned selfpowered:1; unsigned active_suspend:1; u8 addr; struct at91_udc_data board; -- cgit v0.10.2 From 9239d88fc5e58a2a72bc949362f999aac9bffb29 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:27 +0800 Subject: staging: emxx_udc: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/staging/emxx_udc/emxx_udc.c b/drivers/staging/emxx_udc/emxx_udc.c index eb178fc..1d3135a 100644 --- a/drivers/staging/emxx_udc/emxx_udc.c +++ b/drivers/staging/emxx_udc/emxx_udc.c @@ -1608,7 +1608,7 @@ static int std_req_get_status(struct nbu2ss_udc *udc) switch (recipient) { case USB_RECIP_DEVICE: if (udc->ctrl.wIndex == 0x0000) { - if (udc->self_powered) + if (udc->gadget.is_selfpowered) status_data |= (1 << USB_DEVICE_SELF_POWERED); if (udc->remote_wakeup) @@ -3117,7 +3117,6 @@ static int nbu2ss_gad_wakeup(struct usb_gadget *pgadget) static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, int is_selfpowered) { - struct nbu2ss_udc *udc; unsigned long flags; /* INFO("=== %s()\n", __func__); */ @@ -3127,10 +3126,8 @@ static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, return -EINVAL; } - udc = container_of(pgadget, struct nbu2ss_udc, gadget); - spin_lock_irqsave(&udc->lock, flags); - udc->self_powered = (is_selfpowered != 0); + pgadget->is_selfpowered = (is_selfpowered != 0); spin_unlock_irqrestore(&udc->lock, flags); return 0; @@ -3308,7 +3305,7 @@ static int __init nbu2ss_drv_contest_init( spin_lock_init(&udc->lock); udc->dev = &pdev->dev; - udc->self_powered = 1; + udc->gadget.is_selfpowered = 1; udc->devstate = USB_STATE_NOTATTACHED; udc->pdev = pdev; udc->mA = 0; diff --git a/drivers/staging/emxx_udc/emxx_udc.h b/drivers/staging/emxx_udc/emxx_udc.h index ee1b80d..202e2dc 100644 --- a/drivers/staging/emxx_udc/emxx_udc.h +++ b/drivers/staging/emxx_udc/emxx_udc.h @@ -624,7 +624,6 @@ struct nbu2ss_udc { unsigned linux_suspended:1; unsigned linux_resume:1; unsigned usb_suspended:1; - unsigned self_powered:1; unsigned remote_wakeup:1; unsigned udc_enabled:1; -- cgit v0.10.2 From a17fd41206a6d3efc565d8319405692325874884 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:28 +0800 Subject: usb: renesas_usbhs: gadget: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c index 8697e6e..e0384af 100644 --- a/drivers/usb/renesas_usbhs/mod_gadget.c +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -926,6 +926,8 @@ static int usbhsg_set_selfpowered(struct usb_gadget *gadget, int is_self) else usbhsg_status_clr(gpriv, USBHSG_STATUS_SELF_POWERED); + gadget->is_selfpowered = (is_self != 0); + return 0; } -- cgit v0.10.2 From 4605437e335bf1cf3b30c1261a6a146dbd830188 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:29 +0800 Subject: usb: gadget: bdc_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_udc.c b/drivers/usb/gadget/udc/bdc/bdc_udc.c index 3700ce7..7f77db5 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_udc.c +++ b/drivers/usb/gadget/udc/bdc/bdc_udc.c @@ -454,6 +454,7 @@ static int bdc_udc_set_selfpowered(struct usb_gadget *gadget, unsigned long flags; dev_dbg(bdc->dev, "%s()\n", __func__); + gadget->is_selfpowered = (is_self != 0); spin_lock_irqsave(&bdc->lock, flags); if (!is_self) bdc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; -- cgit v0.10.2 From 5d9cb6afc496b20b2d2ef71e4dd3adf50841f8c8 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:30 +0800 Subject: usb: gadget: dummy_hcd: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 270c1ec..8dda484 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -802,6 +802,7 @@ static int dummy_set_selfpowered(struct usb_gadget *_gadget, int value) { struct dummy *dum; + _gadget->is_selfpowered = (value != 0); dum = gadget_to_dummy_hcd(_gadget)->dum; if (value) dum->devstatus |= (1 << USB_DEVICE_SELF_POWERED); -- cgit v0.10.2 From d60d939221dc16dfc0589f4fbb730aab1aa4daeb Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:31 +0800 Subject: usb: gadget: lpc32xx_udc: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 34d9b7b..27fd413 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -191,7 +191,6 @@ struct lpc32xx_udc { bool enabled; bool clocked; bool suspended; - bool selfpowered; int ep0state; atomic_t enabled_ep_cnt; wait_queue_head_t ep_disable_wait_queue; @@ -547,7 +546,7 @@ static int proc_udc_show(struct seq_file *s, void *unused) udc->vbus ? "present" : "off", udc->enabled ? (udc->vbus ? "active" : "enabled") : "disabled", - udc->selfpowered ? "self" : "VBUS", + udc->gadget.is_selfpowered ? "self" : "VBUS", udc->suspended ? ", suspended" : "", udc->driver ? udc->driver->driver.name : "(none)"); @@ -2212,7 +2211,7 @@ static int udc_get_status(struct lpc32xx_udc *udc, u16 reqtype, u16 wIndex) break; /* Not supported */ case USB_RECIP_DEVICE: - ep0buff = (udc->selfpowered << USB_DEVICE_SELF_POWERED); + ep0buff = udc->gadget.is_selfpowered; if (udc->dev_status & (1 << USB_DEVICE_REMOTE_WAKEUP)) ep0buff |= (1 << USB_DEVICE_REMOTE_WAKEUP); break; @@ -2498,10 +2497,7 @@ static int lpc32xx_wakeup(struct usb_gadget *gadget) static int lpc32xx_set_selfpowered(struct usb_gadget *gadget, int is_on) { - struct lpc32xx_udc *udc = to_udc(gadget); - - /* Always self-powered */ - udc->selfpowered = (is_on != 0); + gadget->is_selfpowered = (is_on != 0); return 0; } @@ -2946,7 +2942,7 @@ static int lpc32xx_start(struct usb_gadget *gadget, udc->driver = driver; udc->gadget.dev.of_node = udc->dev->of_node; udc->enabled = 1; - udc->selfpowered = 1; + udc->gadget.is_selfpowered = 1; udc->vbus = 0; /* Force VBUS process once to check for cable insertion */ -- cgit v0.10.2 From 4651fcf3294eaa018c63dfdae0b1fb65e432f727 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:32 +0800 Subject: usb: gadget: fsl_udc_core: set value for common is_selfpowered fsl udc core assumes itself always self powered, so set is_selfpowered is 1. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index c3830ad..55fcb93 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -1337,7 +1337,7 @@ static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value, if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { /* Get device status */ - tmp = 1 << USB_DEVICE_SELF_POWERED; + tmp = udc->gadget.is_selfpowered; tmp |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { /* Get interface status */ @@ -1948,6 +1948,7 @@ static int fsl_udc_start(struct usb_gadget *g, /* hook up the driver */ udc_controller->driver = driver; spin_unlock_irqrestore(&udc_controller->lock, flags); + g->is_selfpowered = 1; if (!IS_ERR_OR_NULL(udc_controller->transceiver)) { /* Suspend the controller until OTG enable it */ -- cgit v0.10.2 From 58ae8e0b1455ab2adc7c6733ad2dde9e0317cc36 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:33 +0800 Subject: usb: gadget: omap_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c index 288c087..e2fcdb8 100644 --- a/drivers/usb/gadget/udc/omap_udc.c +++ b/drivers/usb/gadget/udc/omap_udc.c @@ -1171,6 +1171,7 @@ omap_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) unsigned long flags; u16 syscon1; + gadget->is_selfpowered = (is_selfpowered != 0); udc = container_of(gadget, struct omap_udc, gadget); spin_lock_irqsave(&udc->lock, flags); syscon1 = omap_readw(UDC_SYSCON1); -- cgit v0.10.2 From 0bcff9eaa83228f36b8ae42cedc588e9337b7e71 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:34 +0800 Subject: usb: gadget: r8a66597-udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c index 06870da..2495fe9 100644 --- a/drivers/usb/gadget/udc/r8a66597-udc.c +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -1803,6 +1803,7 @@ static int r8a66597_set_selfpowered(struct usb_gadget *gadget, int is_self) { struct r8a66597 *r8a66597 = gadget_to_r8a66597(gadget); + gadget->is_selfpowered = (is_self != 0); if (is_self) r8a66597->device_status |= 1 << USB_DEVICE_SELF_POWERED; else -- cgit v0.10.2 From c8678d9f288e7df66f50ab7be1eff1beb6bc7d70 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:35 +0800 Subject: usb: gadget: net2280: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 6411ed8..d2c0bf6 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1365,10 +1365,10 @@ static int net2280_set_selfpowered(struct usb_gadget *_gadget, int value) tmp = readl(&dev->usb->usbctl); if (value) { tmp |= BIT(SELF_POWERED_STATUS); - dev->selfpowered = 1; + _gadget->is_selfpowered = 1; } else { tmp &= ~BIT(SELF_POWERED_STATUS); - dev->selfpowered = 0; + _gadget->is_selfpowered = 0; } writel(tmp, &dev->usb->usbctl); spin_unlock_irqrestore(&dev->lock, flags); @@ -2611,7 +2611,7 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, switch (r.bRequestType) { case (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE): status = dev->wakeup_enable ? 0x02 : 0x00; - if (dev->selfpowered) + if (dev->gadget.is_selfpowered) status |= BIT(0); status |= (dev->u1_enable << 2 | dev->u2_enable << 3 | dev->ltm_enable << 4); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index a307dce..ac8d5a2 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -164,7 +164,6 @@ struct net2280 { u2_enable:1, ltm_enable:1, wakeup_enable:1, - selfpowered:1, addressed_state:1, bug7734_patched:1; u16 chiprev; -- cgit v0.10.2 From b3764dd1a318a227099984a4406f47f861b1d6eb Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:36 +0800 Subject: usb: gadget: s3c2410_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/s3c2410_udc.c b/drivers/usb/gadget/udc/s3c2410_udc.c index 256a67b..b808951 100644 --- a/drivers/usb/gadget/udc/s3c2410_udc.c +++ b/drivers/usb/gadget/udc/s3c2410_udc.c @@ -1434,6 +1434,7 @@ static int s3c2410_udc_set_selfpowered(struct usb_gadget *gadget, int value) dprintk(DEBUG_NORMAL, "%s()\n", __func__); + gadget->is_selfpowered = (value != 0); if (value) udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); else -- cgit v0.10.2 From d4a1c47946a2796520a95da6bed8e5642ce9fb19 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:37 +0800 Subject: usb: gadget: net2272: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c index d20de1f..195baf3 100644 --- a/drivers/usb/gadget/udc/net2272.c +++ b/drivers/usb/gadget/udc/net2272.c @@ -1132,13 +1132,10 @@ net2272_wakeup(struct usb_gadget *_gadget) static int net2272_set_selfpowered(struct usb_gadget *_gadget, int value) { - struct net2272 *dev; - if (!_gadget) return -ENODEV; - dev = container_of(_gadget, struct net2272, gadget); - dev->is_selfpowered = value; + _gadget->is_selfpowered = (value != 0); return 0; } @@ -1844,7 +1841,7 @@ net2272_handle_stat0_irqs(struct net2272 *dev, u8 stat) case USB_RECIP_DEVICE: if (u.r.wLength > 2) goto do_stall; - if (dev->is_selfpowered) + if (dev->gadget.is_selfpowered) status = (1 << USB_DEVICE_SELF_POWERED); /* don't bother with a request object! */ diff --git a/drivers/usb/gadget/udc/net2272.h b/drivers/usb/gadget/udc/net2272.h index e595057..127ab03 100644 --- a/drivers/usb/gadget/udc/net2272.h +++ b/drivers/usb/gadget/udc/net2272.h @@ -458,7 +458,6 @@ struct net2272 { struct usb_gadget_driver *driver; unsigned protocol_stall:1, softconnect:1, - is_selfpowered:1, wakeup:1, dma_eot_polarity:1, dma_dack_polarity:1, -- cgit v0.10.2 From d618c368ef98631da3adf802e500d5c61103804f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:38 +0800 Subject: usb: gadget: atmel_usba_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 4b5b7fa..c041086 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -989,6 +989,7 @@ usba_udc_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) struct usba_udc *udc = to_usba_udc(gadget); unsigned long flags; + gadget->is_selfpowered = (is_selfpowered != 0); spin_lock_irqsave(&udc->lock, flags); if (is_selfpowered) udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; -- cgit v0.10.2 From 716013b0ab2b13fe6a176d869b9fcb9bf0f8e112 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:39 +0800 Subject: usb: gadget: pch_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c index 1c7379a..613547f 100644 --- a/drivers/usb/gadget/udc/pch_udc.c +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -1161,6 +1161,7 @@ static int pch_udc_pcd_selfpowered(struct usb_gadget *gadget, int value) if (!gadget) return -EINVAL; + gadget->is_selfpowered = (value != 0); dev = container_of(gadget, struct pch_udc_dev, gadget); if (value) pch_udc_set_selfpowered(dev); -- cgit v0.10.2 From bcdea50312b291ee3920bad43a13e42d7102f900 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:40 +0800 Subject: usb: dwc3: gadget: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 0842aa8..d201910 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -682,7 +682,6 @@ struct dwc3_scratchpad_array { * @is_utmi_l1_suspend: the core asserts output signal * 0 - utmi_sleep_n * 1 - utmi_l1_suspend_n - * @is_selfpowered: true when we are selfpowered * @is_fpga: true when we are using the FPGA board * @needs_fifo_resize: not all users might want fifo resizing, flag it * @pullups_connected: true when Run/Stop bit is set @@ -806,7 +805,6 @@ struct dwc3 { unsigned has_hibernation:1; unsigned has_lpm_erratum:1; unsigned is_utmi_l1_suspend:1; - unsigned is_selfpowered:1; unsigned is_fpga:1; unsigned needs_fifo_resize:1; unsigned pullups_connected:1; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 1bc77a3..2ef3c8d 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -344,7 +344,7 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc, /* * LTM will be set once we know how to set this in HW. */ - usb_status |= dwc->is_selfpowered << USB_DEVICE_SELF_POWERED; + usb_status |= dwc->gadget.is_selfpowered; if (dwc->speed == DWC3_DSTS_SUPERSPEED) { reg = dwc3_readl(dwc->regs, DWC3_DCTL); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index eccd29b..a03a485 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1415,7 +1415,7 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, unsigned long flags; spin_lock_irqsave(&dwc->lock, flags); - dwc->is_selfpowered = !!is_selfpowered; + g->is_selfpowered = !!is_selfpowered; spin_unlock_irqrestore(&dwc->lock, flags); return 0; -- cgit v0.10.2 From dadac9861f466635c3ac0851f2a39be68fa10a71 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:41 +0800 Subject: usb: musb: gadget: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 49b04cb..b2d9040 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1612,9 +1612,7 @@ done: static int musb_gadget_set_self_powered(struct usb_gadget *gadget, int is_selfpowered) { - struct musb *musb = gadget_to_musb(gadget); - - musb->is_self_powered = !!is_selfpowered; + gadget->is_selfpowered = !!is_selfpowered; return 0; } diff --git a/drivers/usb/musb/musb_gadget_ep0.c b/drivers/usb/musb/musb_gadget_ep0.c index 2af45a0..10d30af 100644 --- a/drivers/usb/musb/musb_gadget_ep0.c +++ b/drivers/usb/musb/musb_gadget_ep0.c @@ -85,7 +85,7 @@ static int service_tx_status_request( switch (recip) { case USB_RECIP_DEVICE: - result[0] = musb->is_self_powered << USB_DEVICE_SELF_POWERED; + result[0] = musb->g.is_selfpowered << USB_DEVICE_SELF_POWERED; result[0] |= musb->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; if (musb->g.is_otg) { result[0] |= musb->g.b_hnp_enable -- cgit v0.10.2 From 3f6dd4feda71760c41336963c17b594872346654 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:42 +0800 Subject: usb: udc-core: add is_selfpowered sys entry The user can read it through sys entry. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/udc-core.c b/drivers/usb/gadget/udc/udc-core.c index e31d574..5a81cb0 100644 --- a/drivers/usb/gadget/udc/udc-core.c +++ b/drivers/usb/gadget/udc/udc-core.c @@ -564,6 +564,7 @@ static USB_UDC_ATTR(is_a_peripheral); static USB_UDC_ATTR(b_hnp_enable); static USB_UDC_ATTR(a_hnp_support); static USB_UDC_ATTR(a_alt_hnp_support); +static USB_UDC_ATTR(is_selfpowered); static struct attribute *usb_udc_attrs[] = { &dev_attr_srp.attr, @@ -577,6 +578,7 @@ static struct attribute *usb_udc_attrs[] = { &dev_attr_b_hnp_enable.attr, &dev_attr_a_hnp_support.attr, &dev_attr_a_alt_hnp_support.attr, + &dev_attr_is_selfpowered.attr, NULL, }; -- cgit v0.10.2 From a0cb12e2ed61f4c1bb586268ca43b27dd8ce959e Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 28 Jan 2015 22:50:04 +0100 Subject: usb: musb: add generic usb phy dependencies Multiple musb glue drivers depend on the generic usb phy support, but fail to list it as a dependency in Kconfig. This results in build erros like: drivers/built-in.o: In function `am35x_remove': :(.text+0xadacc): undefined reference to `usb_phy_generic_unregister' drivers/built-in.o: In function `am35x_probe': :(.text+0xae1c8): undefined reference to `usb_phy_generic_register' :(.text+0xae244): undefined reference to `usb_phy_generic_unregister' drivers/built-in.o: In function `jz4740_remove': :(.text+0xaf648): undefined reference to `usb_phy_generic_unregister' drivers/built-in.o: In function `jz4740_musb_init': :(.text+0xaf694): undefined reference to `usb_phy_generic_register' This adds the ones that are missing. Signed-off-by: Arnd Bergmann Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index b005010..26c835f 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -63,11 +63,13 @@ comment "Platform Glue Layer" config USB_MUSB_DAVINCI tristate "DaVinci" depends on ARCH_DAVINCI_DMx + depends on NOP_USB_XCEIV depends on BROKEN config USB_MUSB_DA8XX tristate "DA8xx/OMAP-L1x" depends on ARCH_DAVINCI_DA8XX + depends on NOP_USB_XCEIV depends on BROKEN config USB_MUSB_TUSB6010 @@ -83,6 +85,7 @@ config USB_MUSB_OMAP2PLUS config USB_MUSB_AM35X tristate "AM35x" depends on ARCH_OMAP + depends on NOP_USB_XCEIV config USB_MUSB_DSPS tristate "TI DSPS platforms" @@ -93,6 +96,7 @@ config USB_MUSB_DSPS config USB_MUSB_BLACKFIN tristate "Blackfin" depends on (BF54x && !BF544) || (BF52x && ! BF522 && !BF523) + depends on NOP_USB_XCEIV config USB_MUSB_UX500 tristate "Ux500 platforms" @@ -100,6 +104,7 @@ config USB_MUSB_UX500 config USB_MUSB_JZ4740 tristate "JZ4740" + depends on NOP_USB_XCEIV depends on MACH_JZ4740 || COMPILE_TEST depends on USB_MUSB_GADGET depends on USB_OTG_BLACKLIST_HUB -- cgit v0.10.2 From fbba7db3990cb707ff91cd6507d53a0a730afe97 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 28 Jan 2015 22:51:04 +0100 Subject: usb: musb: add omap-control dependency The omap musb front-end calls into the phy driver directly instead of using a generic phy interface, which causes a link error when the specific driver is not built-in: drivers/built-in.o: In function `omap2430_musb_disable': usb/musb/omap2430.c:480: undefined reference to `omap_control_usb_set_mode' drivers/built-in.o: In function `omap2430_musb_enable': usb/musb/omap2430.c:466: undefined reference to `omap_control_usb_set_mode' usb/musb/omap2430.c:447: undefined reference to `omap_control_usb_set_mode' drivers/built-in.o: In function `omap_musb_set_mailbox': usb/musb/omap2430.c:273: undefined reference to `omap_control_usb_set_mode' usb/musb/omap2430.c:304: undefined reference to `omap_control_usb_set_mode' drivers/built-in.o:(.debug_addr+0xbd9e0): more undefined references to `omap_control_usb_set_mode' follow This adds an explicit dependency. Signed-off-by: Arnd Bergmann Fixes: ca784be36cc725 ("usb: start using the control module driver") Cc: # v3.9+ Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 26c835f..14e1628 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -79,7 +79,7 @@ config USB_MUSB_TUSB6010 config USB_MUSB_OMAP2PLUS tristate "OMAP2430 and onwards" - depends on ARCH_OMAP2PLUS && USB + depends on ARCH_OMAP2PLUS && USB && OMAP_CONTROL_PHY select GENERIC_PHY config USB_MUSB_AM35X -- cgit v0.10.2 From 9b43e5ec89dd0c863959ae0157ad3d693588ab44 Mon Sep 17 00:00:00 2001 From: Yunzhi Li Date: Wed, 21 Jan 2015 18:26:00 +0800 Subject: Documentation: bindings: add dt documentation for Rockchip usb PHY This patch adds a binding that describes the Rockchip usb PHYs found on Rockchip SoCs usb interface. Signed-off-by: Yunzhi Li Signed-off-by: Kishon Vijay Abraham I diff --git a/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt new file mode 100644 index 0000000..826454a --- /dev/null +++ b/Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt @@ -0,0 +1,37 @@ +ROCKCHIP USB2 PHY + +Required properties: + - compatible: rockchip,rk3288-usb-phy + - rockchip,grf : phandle to the syscon managing the "general + register files" + - #address-cells: should be 1 + - #size-cells: should be 0 + +Sub-nodes: +Each PHY should be represented as a sub-node. + +Sub-nodes +required properties: +- #phy-cells: should be 0 +- reg: PHY configure reg address offset in GRF + "0x320" - for PHY attach to OTG controller + "0x334" - for PHY attach to HOST0 controller + "0x348" - for PHY attach to HOST1 controller + +Optional Properties: +- clocks : phandle + clock specifier for the phy clocks +- clock-names: string, clock name, must be "phyclk" + +Example: + +usbphy: phy { + compatible = "rockchip,rk3288-usb-phy"; + rockchip,grf = <&grf>; + #address-cells = <1>; + #size-cells = <0>; + + usbphy0: usb-phy0 { + #phy-cells = <0>; + reg = <0x320>; + }; +}; -- cgit v0.10.2 From 64d11406de2eac7330d8905ac064d62befe5a0b0 Mon Sep 17 00:00:00 2001 From: Yunzhi Li Date: Fri, 12 Dec 2014 23:07:46 +0800 Subject: phy: add a driver for the Rockchip SoC internal USB2.0 PHY This patch to add a generic PHY driver for ROCKCHIP usb PHYs, currently this driver can support RK3288. The RK3288 SoC have three independent USB PHY IPs which are all configured through a set of registers located in the GRF (general register files) module. Signed-off-by: Yunzhi Li Tested-by: Doug Anderson Signed-off-by: Kishon Vijay Abraham I diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index ccad880..b24500a 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -239,6 +239,13 @@ config PHY_QCOM_IPQ806X_SATA depends on OF select GENERIC_PHY +config PHY_ROCKCHIP_USB + tristate "Rockchip USB2 PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip USB 2.0 PHY. + config PHY_ST_SPEAR1310_MIPHY tristate "ST SPEAR1310-MIPHY driver" select GENERIC_PHY diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index aa74f96..48bf5a1 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -28,6 +28,7 @@ phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o +obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c new file mode 100644 index 0000000..22011c3 --- /dev/null +++ b/drivers/phy/phy-rockchip-usb.c @@ -0,0 +1,158 @@ +/* + * Rockchip usb PHY driver + * + * Copyright (C) 2014 Yunzhi Li + * Copyright (C) 2014 ROCKCHIP, Inc. + * + * 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. + * + * 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 + +/* + * The higher 16-bit of this register is used for write protection + * only if BIT(13 + 16) set to 1 the BIT(13) can be written. + */ +#define SIDDQ_WRITE_ENA BIT(29) +#define SIDDQ_ON BIT(13) +#define SIDDQ_OFF (0 << 13) + +struct rockchip_usb_phy { + unsigned int reg_offset; + struct regmap *reg_base; + struct clk *clk; + struct phy *phy; +}; + +static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, + bool siddq) +{ + return regmap_write(phy->reg_base, phy->reg_offset, + SIDDQ_WRITE_ENA | (siddq ? SIDDQ_ON : SIDDQ_OFF)); +} + +static int rockchip_usb_phy_power_off(struct phy *_phy) +{ + struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); + int ret = 0; + + /* Power down usb phy analog blocks by set siddq 1 */ + ret = rockchip_usb_phy_power(phy, 1); + if (ret) + return ret; + + clk_disable_unprepare(phy->clk); + if (ret) + return ret; + + return 0; +} + +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) + return ret; + + return 0; +} + +static struct phy_ops ops = { + .power_on = rockchip_usb_phy_power_on, + .power_off = rockchip_usb_phy_power_off, + .owner = THIS_MODULE, +}; + +static int rockchip_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_usb_phy *rk_phy; + struct phy_provider *phy_provider; + struct device_node *child; + struct regmap *grf; + unsigned int reg_offset; + + grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); + if (IS_ERR(grf)) { + dev_err(&pdev->dev, "Missing rockchip,grf property\n"); + return PTR_ERR(grf); + } + + for_each_available_child_of_node(dev->of_node, child) { + rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); + if (!rk_phy) + return -ENOMEM; + + if (of_property_read_u32(child, "reg", ®_offset)) { + dev_err(dev, "missing reg property in node %s\n", + child->name); + return -EINVAL; + } + + rk_phy->reg_offset = reg_offset; + rk_phy->reg_base = grf; + + 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"); + return PTR_ERR(rk_phy->phy); + } + phy_set_drvdata(rk_phy->phy, rk_phy); + } + + 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 rockchip_usb_phy_dt_ids[] = { + { .compatible = "rockchip,rk3288-usb-phy" }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_usb_phy_dt_ids); + +static struct platform_driver rockchip_usb_driver = { + .probe = rockchip_usb_phy_probe, + .driver = { + .name = "rockchip-usb-phy", + .owner = THIS_MODULE, + .of_match_table = rockchip_usb_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_usb_driver); + +MODULE_AUTHOR("Yunzhi Li "); +MODULE_DESCRIPTION("Rockchip USB 2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From e4b3d38088df6f3acd40259c8ec32c9bd3bfe3da Mon Sep 17 00:00:00 2001 From: Sylwester Nawrocki Date: Fri, 16 Jan 2015 18:30:37 +0100 Subject: phy: exynos-video-mipi: Fix regression by adding support for PMU regmap After the Exynos Power Management Unit (PMU) driver was converted to the platform device driver in commit 14fc8b93d47323561edf5d482 ("ARM: EXYNOS: Add platform driver support for Exynos PMU") and then PMU device nodes added to Exynos4 DTs in commit 7b9613aca42a5522d269 ("ARM: dts: add PMU syscon node for exynos4") the mipi video phy driver started failing probing, due to overlapping memory mapped register region resources. Now all the Exynos peripheral devices which have registers in the PMU region are supposed to use the regmap provided by the syscon driver. So support for regmap is added in this patch, this unfortunately creates yet another indirection into that supposedly trivial driver. The additional mutex is required because single register is used by PHY pairs (they share bit in a register). An improvement here could be to allow a PHY instance be created with a driver custom mutex, which would then be common for each PHY pair. This would eliminate one of 3 mutexes which need to be taken in the phy_power_on/ phy_power_off code path. However, I tried to keep this bug fix patch possibly simple. This change is needed to make MIPI DSI displays and MIPI CSI-2 camera sensors working again on Exynos4 boards. Cc: Pankaj Dubey Cc: Kukjin Kim Signed-off-by: Sylwester Nawrocki Signed-off-by: Kishon Vijay Abraham I diff --git a/Documentation/devicetree/bindings/phy/samsung-phy.txt b/Documentation/devicetree/bindings/phy/samsung-phy.txt index d5bad92..91e38cf 100644 --- a/Documentation/devicetree/bindings/phy/samsung-phy.txt +++ b/Documentation/devicetree/bindings/phy/samsung-phy.txt @@ -3,8 +3,8 @@ Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY Required properties: - compatible : should be "samsung,s5pv210-mipi-video-phy"; -- reg : offset and length of the MIPI DPHY register set; - #phy-cells : from the generic phy bindings, must be 1; +- syscon - phandle to the PMU system controller; For "samsung,s5pv210-mipi-video-phy" compatible PHYs the second cell in the PHY specifier identifies the PHY and its meaning is as follows: diff --git a/drivers/phy/phy-exynos-mipi-video.c b/drivers/phy/phy-exynos-mipi-video.c index 943e0f8..f017b2f 100644 --- a/drivers/phy/phy-exynos-mipi-video.c +++ b/drivers/phy/phy-exynos-mipi-video.c @@ -12,19 +12,18 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include -/* MIPI_PHYn_CONTROL register offset: n = 0..1 */ +/* MIPI_PHYn_CONTROL reg. offset (for base address from ioremap): n = 0..1 */ #define EXYNOS_MIPI_PHY_CONTROL(n) ((n) * 4) -#define EXYNOS_MIPI_PHY_ENABLE (1 << 0) -#define EXYNOS_MIPI_PHY_SRESETN (1 << 1) -#define EXYNOS_MIPI_PHY_MRESETN (1 << 2) -#define EXYNOS_MIPI_PHY_RESET_MASK (3 << 1) enum exynos_mipi_phy_id { EXYNOS_MIPI_PHY_ID_CSIS0, @@ -38,43 +37,62 @@ enum exynos_mipi_phy_id { ((id) == EXYNOS_MIPI_PHY_ID_DSIM0 || (id) == EXYNOS_MIPI_PHY_ID_DSIM1) struct exynos_mipi_video_phy { - spinlock_t slock; struct video_phy_desc { struct phy *phy; unsigned int index; } phys[EXYNOS_MIPI_PHYS_NUM]; + spinlock_t slock; void __iomem *regs; + struct mutex mutex; + struct regmap *regmap; }; static int __set_phy_state(struct exynos_mipi_video_phy *state, enum exynos_mipi_phy_id id, unsigned int on) { + const unsigned int offset = EXYNOS4_MIPI_PHY_CONTROL(id / 2); void __iomem *addr; - u32 reg, reset; - - addr = state->regs + EXYNOS_MIPI_PHY_CONTROL(id / 2); + u32 val, reset; if (is_mipi_dsim_phy_id(id)) - reset = EXYNOS_MIPI_PHY_MRESETN; - else - reset = EXYNOS_MIPI_PHY_SRESETN; - - spin_lock(&state->slock); - reg = readl(addr); - if (on) - reg |= reset; + reset = EXYNOS4_MIPI_PHY_MRESETN; else - reg &= ~reset; - writel(reg, addr); - - /* Clear ENABLE bit only if MRESETN, SRESETN bits are not set. */ - if (on) - reg |= EXYNOS_MIPI_PHY_ENABLE; - else if (!(reg & EXYNOS_MIPI_PHY_RESET_MASK)) - reg &= ~EXYNOS_MIPI_PHY_ENABLE; + reset = EXYNOS4_MIPI_PHY_SRESETN; + + if (state->regmap) { + mutex_lock(&state->mutex); + regmap_read(state->regmap, offset, &val); + if (on) + val |= reset; + else + val &= ~reset; + regmap_write(state->regmap, offset, val); + if (on) + val |= EXYNOS4_MIPI_PHY_ENABLE; + else if (!(val & EXYNOS4_MIPI_PHY_RESET_MASK)) + val &= ~EXYNOS4_MIPI_PHY_ENABLE; + regmap_write(state->regmap, offset, val); + mutex_unlock(&state->mutex); + } else { + addr = state->regs + EXYNOS_MIPI_PHY_CONTROL(id / 2); + + spin_lock(&state->slock); + val = readl(addr); + if (on) + val |= reset; + else + val &= ~reset; + writel(val, addr); + /* Clear ENABLE bit only if MRESETN, SRESETN bits are not set */ + if (on) + val |= EXYNOS4_MIPI_PHY_ENABLE; + else if (!(val & EXYNOS4_MIPI_PHY_RESET_MASK)) + val &= ~EXYNOS4_MIPI_PHY_ENABLE; + + writel(val, addr); + spin_unlock(&state->slock); + } - writel(reg, addr); - spin_unlock(&state->slock); return 0; } @@ -118,7 +136,6 @@ static int exynos_mipi_video_phy_probe(struct platform_device *pdev) { struct exynos_mipi_video_phy *state; struct device *dev = &pdev->dev; - struct resource *res; struct phy_provider *phy_provider; unsigned int i; @@ -126,14 +143,22 @@ static int exynos_mipi_video_phy_probe(struct platform_device *pdev) if (!state) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + state->regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); + if (IS_ERR(state->regmap)) { + struct resource *res; - state->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(state->regs)) - return PTR_ERR(state->regs); + dev_info(dev, "regmap lookup failed: %ld\n", + PTR_ERR(state->regmap)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + state->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(state->regs)) + return PTR_ERR(state->regs); + } dev_set_drvdata(dev, state); spin_lock_init(&state->slock); + mutex_init(&state->mutex); for (i = 0; i < EXYNOS_MIPI_PHYS_NUM; i++) { struct phy *phy = devm_phy_create(dev, NULL, diff --git a/include/linux/mfd/syscon/exynos4-pmu.h b/include/linux/mfd/syscon/exynos4-pmu.h new file mode 100644 index 0000000..278b1b1 --- /dev/null +++ b/include/linux/mfd/syscon/exynos4-pmu.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * + * 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 _LINUX_MFD_SYSCON_PMU_EXYNOS4_H_ +#define _LINUX_MFD_SYSCON_PMU_EXYNOS4_H_ + +/* Exynos4 PMU register definitions */ + +/* MIPI_PHYn_CONTROL register offset: n = 0..1 */ +#define EXYNOS4_MIPI_PHY_CONTROL(n) (0x710 + (n) * 4) +#define EXYNOS4_MIPI_PHY_ENABLE (1 << 0) +#define EXYNOS4_MIPI_PHY_SRESETN (1 << 1) +#define EXYNOS4_MIPI_PHY_MRESETN (1 << 2) +#define EXYNOS4_MIPI_PHY_RESET_MASK (3 << 1) + +#endif /* _LINUX_MFD_SYSCON_PMU_EXYNOS4_H_ */ -- cgit v0.10.2 From bbd3ce86c768928d5db334881e20116c6da4d0c7 Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Wed, 7 Jan 2015 15:04:07 +0000 Subject: phy: miphy365x: Pass sysconfig register offsets via syscfg dt property. Based on Arnds review comments here https://lkml.org/lkml/2014/11/13/161, update the miphy365 phy driver to access sysconfig register offsets via syscfg dt property. This is because the reg property should not be mixing address spaces like it does currently for miphy365. This change then also aligns us to how other platforms such as keystone and bcm7445 pass there syscon offsets via DT. This patch breaks DT compatibility, but this platform is considered WIP, and is only used by a few developers who are upstreaming support for it. This change has been done as a single atomic commit to ensure it is bisectable. Signed-off-by: Peter Griffin Reviewed-by: Arnd Bergmann Acked-by: Maxime Coquelin Tested-by: Maxime Coquelin diff --git a/Documentation/devicetree/bindings/phy/phy-miphy365x.txt b/Documentation/devicetree/bindings/phy/phy-miphy365x.txt index 42c8808..9802d5d 100644 --- a/Documentation/devicetree/bindings/phy/phy-miphy365x.txt +++ b/Documentation/devicetree/bindings/phy/phy-miphy365x.txt @@ -6,8 +6,10 @@ for SATA and PCIe. Required properties (controller (parent) node): - compatible : Should be "st,miphy365x-phy" -- st,syscfg : Should be a phandle of the system configuration register group - which contain the SATA, PCIe mode setting bits +- st,syscfg : Phandle / integer array property. Phandle of sysconfig group + containing the miphy registers and integer array should contain + an entry for each port sub-node, specifying the control + register offset inside the sysconfig group. Required nodes : A sub-node is required for each channel the controller provides. Address range information including the usual @@ -26,7 +28,6 @@ Required properties (port (child) node): registers filled in "reg": - sata: For SATA devices - pcie: For PCIe devices - - syscfg: To specify the syscfg based config register Optional properties (port (child) node): - st,sata-gen : Generation of locally attached SATA IP. Expected values @@ -39,20 +40,20 @@ Example: miphy365x_phy: miphy365x@fe382000 { compatible = "st,miphy365x-phy"; - st,syscfg = <&syscfg_rear>; + st,syscfg = <&syscfg_rear 0x824 0x828>; #address-cells = <1>; #size-cells = <1>; ranges; phy_port0: port@fe382000 { - reg = <0xfe382000 0x100>, <0xfe394000 0x100>, <0x824 0x4>; - reg-names = "sata", "pcie", "syscfg"; + reg = <0xfe382000 0x100>, <0xfe394000 0x100>; + reg-names = "sata", "pcie"; #phy-cells = <1>; st,sata-gen = <3>; }; phy_port1: port@fe38a000 { - reg = <0xfe38a000 0x100>, <0xfe804000 0x100>, <0x828 0x4>;; + reg = <0xfe38a000 0x100>, <0xfe804000 0x100>;; reg-names = "sata", "pcie", "syscfg"; #phy-cells = <1>; st,pcie-tx-pol-inv; diff --git a/arch/arm/boot/dts/stih416.dtsi b/arch/arm/boot/dts/stih416.dtsi index fad9073..85afe01 100644 --- a/arch/arm/boot/dts/stih416.dtsi +++ b/arch/arm/boot/dts/stih416.dtsi @@ -283,21 +283,21 @@ miphy365x_phy: phy@fe382000 { compatible = "st,miphy365x-phy"; - st,syscfg = <&syscfg_rear>; + st,syscfg = <&syscfg_rear 0x824 0x828>; #address-cells = <1>; #size-cells = <1>; ranges; phy_port0: port@fe382000 { #phy-cells = <1>; - reg = <0xfe382000 0x100>, <0xfe394000 0x100>, <0x824 0x4>; - reg-names = "sata", "pcie", "syscfg"; + reg = <0xfe382000 0x100>, <0xfe394000 0x100>; + reg-names = "sata", "pcie"; }; phy_port1: port@fe38a000 { #phy-cells = <1>; - reg = <0xfe38a000 0x100>, <0xfe804000 0x100>, <0x828 0x4>; - reg-names = "sata", "pcie", "syscfg"; + reg = <0xfe38a000 0x100>, <0xfe804000 0x100>; + reg-names = "sata", "pcie"; }; }; diff --git a/drivers/phy/phy-miphy365x.c b/drivers/phy/phy-miphy365x.c index 6ab43a8..6c80154 100644 --- a/drivers/phy/phy-miphy365x.c +++ b/drivers/phy/phy-miphy365x.c @@ -141,7 +141,7 @@ struct miphy365x_phy { bool pcie_tx_pol_inv; bool sata_tx_pol_inv; u32 sata_gen; - u64 ctrlreg; + u32 ctrlreg; u8 type; }; @@ -179,7 +179,7 @@ static int miphy365x_set_path(struct miphy365x_phy *miphy_phy, bool sata = (miphy_phy->type == MIPHY_TYPE_SATA); return regmap_update_bits(miphy_dev->regmap, - (unsigned int)miphy_phy->ctrlreg, + miphy_phy->ctrlreg, SYSCFG_SELECT_SATA_MASK, sata << SYSCFG_SELECT_SATA_POS); } @@ -445,7 +445,6 @@ int miphy365x_get_addr(struct device *dev, struct miphy365x_phy *miphy_phy, { struct device_node *phynode = miphy_phy->phy->dev.of_node; const char *name; - const __be32 *taddr; int type = miphy_phy->type; int ret; @@ -455,22 +454,6 @@ int miphy365x_get_addr(struct device *dev, struct miphy365x_phy *miphy_phy, return ret; } - if (!strncmp(name, "syscfg", 6)) { - taddr = of_get_address(phynode, index, NULL, NULL); - if (!taddr) { - dev_err(dev, "failed to fetch syscfg address\n"); - return -EINVAL; - } - - miphy_phy->ctrlreg = of_translate_address(phynode, taddr); - if (miphy_phy->ctrlreg == OF_BAD_ADDR) { - dev_err(dev, "failed to translate syscfg address\n"); - return -EINVAL; - } - - return 0; - } - if (!((!strncmp(name, "sata", 4) && type == MIPHY_TYPE_SATA) || (!strncmp(name, "pcie", 4) && type == MIPHY_TYPE_PCIE))) return 0; @@ -606,7 +589,15 @@ static int miphy365x_probe(struct platform_device *pdev) return ret; phy_set_drvdata(phy, miphy_dev->phys[port]); + port++; + /* sysconfig offsets are indexed from 1 */ + ret = of_property_read_u32_index(np, "st,syscfg", port, + &miphy_phy->ctrlreg); + if (ret) { + dev_err(&pdev->dev, "No sysconfig offset found\n"); + return ret; + } } provider = devm_of_phy_provider_register(&pdev->dev, miphy365x_xlate); -- cgit v0.10.2 From b46146d59fdac6a6f559d5e6618f128abf0c2912 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:26 +0100 Subject: usb: dwc2: host: resume root hub on remote wakeup When a remote wakeup happens during bus_suspend, hcd needs to resume its root hub. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index c3e66f0..4a9bcdd 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -316,10 +316,12 @@ void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) */ static void dwc2_hcd_rem_wakeup(struct dwc2_hsotg *hsotg) { - if (hsotg->lx_state == DWC2_L2) + if (hsotg->lx_state == DWC2_L2) { hsotg->flags.b.port_suspend_change = 1; - else + usb_hcd_resume_root_hub(hsotg->priv); + } else { hsotg->flags.b.port_l1_change = 1; + } } /** -- cgit v0.10.2 From c00dd4a6ec20a58ed38be85f8e47efda40625f17 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:27 +0100 Subject: usb: dwc2: gadget: fix clear halt feature handling When clearing HALT on an endpoint, req->complete of in progress requests must be called with locks off. New request should only be started if there is not already a pending request on the endpoint. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 50ae096..02d0e9a 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1007,16 +1007,22 @@ static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, hs_req = ep->req; ep->req = NULL; list_del_init(&hs_req->queue); - usb_gadget_giveback_request(&ep->ep, - &hs_req->req); + if (hs_req->req.complete) { + spin_unlock(&hsotg->lock); + usb_gadget_giveback_request( + &ep->ep, &hs_req->req); + spin_lock(&hsotg->lock); + } } /* If we have pending request, then start it */ - restart = !list_empty(&ep->queue); - if (restart) { - hs_req = get_ep_head(ep); - s3c_hsotg_start_req(hsotg, ep, - hs_req, false); + if (!ep->req) { + restart = !list_empty(&ep->queue); + if (restart) { + hs_req = get_ep_head(ep); + s3c_hsotg_start_req(hsotg, ep, + hs_req, false); + } } } -- cgit v0.10.2 From 9e14d0a566f48d33a9253096963b3b8199e4df57 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:28 +0100 Subject: usb: dwc2: gadget: add TEST_MODE feature support Handle SET_FEATURE TEST_MODE request sent by the host. Slightly rework FEATURE request handling to allow parsing other request types than Endpoint. Also add a debugfs to change test mode value from user space. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index f09b3de..c750fd3 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -567,12 +567,14 @@ struct dwc2_hw_params { * @num_of_eps: Number of available EPs (excluding EP0) * @debug_root: Root directrory for debugfs. * @debug_file: Main status file for debugfs. + * @debug_testmode: Testmode status file for debugfs. * @debug_fifo: FIFO status file for debugfs. * @ep0_reply: Request used for ep0 reply. * @ep0_buff: Buffer for EP0 reply data, if needed. * @ctrl_buff: Buffer for EP0 control requests. * @ctrl_req: Request for EP0 control packets. * @ep0_state: EP0 control transfers state + * @test_mode: USB test mode requested by the host * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework * @g_using_dma: Indicate if dma usage is enabled @@ -610,6 +612,7 @@ struct dwc2_hsotg { struct dentry *debug_root; struct dentry *debug_file; + struct dentry *debug_testmode; struct dentry *debug_fifo; /* DWC OTG HW Release versions */ @@ -706,6 +709,7 @@ struct dwc2_hsotg { void *ep0_buff; void *ctrl_buff; enum dwc2_ep0_state ep0_state; + u8 test_mode; struct usb_gadget gadget; unsigned int enabled:1; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 02d0e9a..67ea258 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "core.h" #include "hw.h" @@ -835,6 +836,32 @@ static struct s3c_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, } /** + * s3c_hsotg_set_test_mode - Enable usb Test Modes + * @hsotg: The driver state. + * @testmode: requested usb test mode + * Enable usb Test Mode requested by the Host. + */ +static int s3c_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode) +{ + int dctl = readl(hsotg->regs + DCTL); + + dctl &= ~DCTL_TSTCTL_MASK; + switch (testmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + dctl |= testmode << DCTL_TSTCTL_SHIFT; + break; + default: + return -EINVAL; + } + writel(dctl, hsotg->regs + DCTL); + return 0; +} + +/** * s3c_hsotg_send_reply - send reply to control request * @hsotg: The device state * @ep: Endpoint 0 @@ -968,19 +995,48 @@ static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *ep; int ret; bool halted; + u32 recip; + u32 wValue; + u32 wIndex; dev_dbg(hsotg->dev, "%s: %s_FEATURE\n", __func__, set ? "SET" : "CLEAR"); - if (ctrl->bRequestType == USB_RECIP_ENDPOINT) { - ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (wValue) { + case USB_DEVICE_TEST_MODE: + if ((wIndex & 0xff) != 0) + return -EINVAL; + if (!set) + return -EINVAL; + + hsotg->test_mode = wIndex >> 8; + ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); + if (ret) { + dev_err(hsotg->dev, + "%s: failed to send reply\n", __func__); + return ret; + } + break; + default: + return -ENOENT; + } + break; + + case USB_RECIP_ENDPOINT: + ep = ep_from_windex(hsotg, wIndex); if (!ep) { dev_dbg(hsotg->dev, "%s: no endpoint for 0x%04x\n", - __func__, le16_to_cpu(ctrl->wIndex)); + __func__, wIndex); return -ENOENT; } - switch (le16_to_cpu(ctrl->wValue)) { + switch (wValue) { case USB_ENDPOINT_HALT: halted = ep->halted; @@ -1031,9 +1087,10 @@ static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, default: return -ENOENT; } - } else - return -ENOENT; /* currently only deal with endpoint */ - + break; + default: + return -ENOENT; + } return 1; } @@ -1734,6 +1791,17 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_IN) { dev_dbg(hsotg->dev, "zlp packet sent\n"); s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + if (hsotg->test_mode) { + int ret; + + ret = s3c_hsotg_set_test_mode(hsotg, hsotg->test_mode); + if (ret < 0) { + dev_dbg(hsotg->dev, "Invalid Test #%d\n", + hsotg->test_mode); + s3c_hsotg_stall_ep0(hsotg); + return; + } + } s3c_hsotg_enqueue_setup(hsotg); return; } @@ -2045,6 +2113,7 @@ void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg) return; hsotg->connected = 0; + hsotg->test_mode = 0; for (ep = 0; ep < hsotg->num_of_eps; ep++) { if (hsotg->eps_in[ep]) @@ -3257,6 +3326,103 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg) } /** + * testmode_write - debugfs: change usb test mode + * @seq: The seq file to write to. + * @v: Unused parameter. + * + * This debugfs entry modify the current usb test mode. + */ +static ssize_t testmode_write(struct file *file, const char __user *ubuf, size_t + count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct dwc2_hsotg *hsotg = s->private; + unsigned long flags; + u32 testmode = 0; + char buf[32]; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "test_j", 6)) + testmode = TEST_J; + else if (!strncmp(buf, "test_k", 6)) + testmode = TEST_K; + else if (!strncmp(buf, "test_se0_nak", 12)) + testmode = TEST_SE0_NAK; + else if (!strncmp(buf, "test_packet", 11)) + testmode = TEST_PACKET; + else if (!strncmp(buf, "test_force_enable", 17)) + testmode = TEST_FORCE_EN; + else + testmode = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + s3c_hsotg_set_test_mode(hsotg, testmode); + spin_unlock_irqrestore(&hsotg->lock, flags); + return count; +} + +/** + * testmode_show - debugfs: show usb test mode state + * @seq: The seq file to write to. + * @v: Unused parameter. + * + * This debugfs entry shows which usb test mode is currently enabled. + */ +static int testmode_show(struct seq_file *s, void *unused) +{ + struct dwc2_hsotg *hsotg = s->private; + unsigned long flags; + int dctl; + + spin_lock_irqsave(&hsotg->lock, flags); + dctl = readl(hsotg->regs + DCTL); + dctl &= DCTL_TSTCTL_MASK; + dctl >>= DCTL_TSTCTL_SHIFT; + spin_unlock_irqrestore(&hsotg->lock, flags); + + switch (dctl) { + case 0: + seq_puts(s, "no test\n"); + break; + case TEST_J: + seq_puts(s, "test_j\n"); + break; + case TEST_K: + seq_puts(s, "test_k\n"); + break; + case TEST_SE0_NAK: + seq_puts(s, "test_se0_nak\n"); + break; + case TEST_PACKET: + seq_puts(s, "test_packet\n"); + break; + case TEST_FORCE_EN: + seq_puts(s, "test_force_enable\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", dctl); + } + + return 0; +} + +static int testmode_open(struct inode *inode, struct file *file) +{ + return single_open(file, testmode_show, inode->i_private); +} + +static const struct file_operations testmode_fops = { + .owner = THIS_MODULE, + .open = testmode_open, + .write = testmode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** * state_show - debugfs: show overall driver and device state. * @seq: The seq file to write to. * @v: Unused parameter. @@ -3490,6 +3656,14 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) if (IS_ERR(hsotg->debug_file)) dev_err(hsotg->dev, "%s: failed to create state\n", __func__); + hsotg->debug_testmode = debugfs_create_file("testmode", + S_IRUGO | S_IWUSR, root, + hsotg, &testmode_fops); + + if (IS_ERR(hsotg->debug_testmode)) + dev_err(hsotg->dev, "%s: failed to create testmode\n", + __func__); + hsotg->debug_fifo = debugfs_create_file("fifo", 0444, root, hsotg, &fifo_fops); @@ -3544,6 +3718,7 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) } debugfs_remove(hsotg->debug_file); + debugfs_remove(hsotg->debug_testmode); debugfs_remove(hsotg->debug_fifo); debugfs_remove(hsotg->debug_root); } -- cgit v0.10.2 From 58f7c43e06d0560ace01137d158108df0a08dc08 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:29 +0100 Subject: usb: dwc2: gadget: fix a typo in comment s3c_hsotg_process_req_feature comments was not correct Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 67ea258..aa5c0ba 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -981,7 +981,7 @@ static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) } /** - * s3c_hsotg_process_req_featire - process request {SET,CLEAR}_FEATURE + * s3c_hsotg_process_req_feature - process request {SET,CLEAR}_FEATURE * @hsotg: The device state * @ctrl: USB control request */ -- cgit v0.10.2 From d7c747c590a98d2d75e4c2c97c875f0dcd5743ee Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:30 +0100 Subject: usb: dwc2: gadget: remove hardcoded if (0) and if (1) checks Remove dead code as well. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index aa5c0ba..fc9462a 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -566,11 +566,6 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, length = ureq->length - ureq->actual; dev_dbg(hsotg->dev, "ureq->length:%d ureq->actual:%d\n", ureq->length, ureq->actual); - if (0) - dev_dbg(hsotg->dev, - "REQ buf %p len %d dma %pad noi=%d zp=%d snok=%d\n", - ureq->buf, length, &ureq->dma, - ureq->no_interrupt, ureq->zero, ureq->short_not_ok); maxreq = get_ep_limit(hs_ep); if (length > maxreq) { @@ -1566,8 +1561,7 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) size = grxstsr & GRXSTS_BYTECNT_MASK; size >>= GRXSTS_BYTECNT_SHIFT; - if (1) - dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", + dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", __func__, grxstsr, size, epnum); switch ((status & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT) { @@ -2926,12 +2920,6 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) /* Be in disconnected state until gadget is registered */ __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); - if (0) { - /* post global nak until we're ready */ - writel(DCTL_SGNPINNAK | DCTL_SGOUTNAK, - hsotg->regs + DCTL); - } - /* setup fifos */ dev_dbg(hsotg->dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", -- cgit v0.10.2 From 7d24c1b5a77c71d885fca047d1f25721f6b366e7 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:31 +0100 Subject: usb: dwc2: gadget: add unaligned buffers support When using DMA, dwc2 requires buffers to be 4 bytes aligned. Use bounce buffers if they are not. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index c750fd3..485b195 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -158,10 +158,12 @@ struct s3c_hsotg_ep { * struct s3c_hsotg_req - data transfer request * @req: The USB gadget request * @queue: The list of requests for the endpoint this is queued for. + * @saved_req_buf: variable to save req.buf when bounce buffers are used. */ struct s3c_hsotg_req { struct usb_request req; struct list_head queue; + void *saved_req_buf; }; #if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index fc9462a..5e91cf15 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -726,6 +726,59 @@ dma_error: return -EIO; } +static int s3c_hsotg_handle_unaligned_buf_start(struct dwc2_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req) +{ + void *req_buf = hs_req->req.buf; + + /* If dma is not being used or buffer is aligned */ + if (!using_dma(hsotg) || !((long)req_buf & 3)) + return 0; + + WARN_ON(hs_req->saved_req_buf); + + dev_dbg(hsotg->dev, "%s: %s: buf=%p length=%d\n", __func__, + hs_ep->ep.name, req_buf, hs_req->req.length); + + hs_req->req.buf = kmalloc(hs_req->req.length, GFP_ATOMIC); + if (!hs_req->req.buf) { + hs_req->req.buf = req_buf; + dev_err(hsotg->dev, + "%s: unable to allocate memory for bounce buffer\n", + __func__); + return -ENOMEM; + } + + /* Save actual buffer */ + hs_req->saved_req_buf = req_buf; + + if (hs_ep->dir_in) + memcpy(hs_req->req.buf, req_buf, hs_req->req.length); + return 0; +} + +static void s3c_hsotg_handle_unaligned_buf_complete(struct dwc2_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req) +{ + /* If dma is not being used or buffer was aligned */ + if (!using_dma(hsotg) || !hs_req->saved_req_buf) + return; + + dev_dbg(hsotg->dev, "%s: %s: status=%d actual-length=%d\n", __func__, + hs_ep->ep.name, hs_req->req.status, hs_req->req.actual); + + /* Copy data from bounce buffer on successful out transfer */ + if (!hs_ep->dir_in && !hs_req->req.status) + memcpy(hs_req->saved_req_buf, hs_req->req.buf, + hs_req->req.actual); + + /* Free bounce buffer */ + kfree(hs_req->req.buf); + + hs_req->req.buf = hs_req->saved_req_buf; + hs_req->saved_req_buf = NULL; +} + static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) { @@ -733,6 +786,7 @@ static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, struct s3c_hsotg_ep *hs_ep = our_ep(ep); struct dwc2_hsotg *hs = hs_ep->parent; bool first; + int ret; dev_dbg(hs->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n", ep->name, req, req->length, req->buf, req->no_interrupt, @@ -743,9 +797,13 @@ static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, req->actual = 0; req->status = -EINPROGRESS; + ret = s3c_hsotg_handle_unaligned_buf_start(hs, hs_ep, hs_req); + if (ret) + return ret; + /* if we're using DMA, sync the buffers as necessary */ if (using_dma(hs)) { - int ret = s3c_hsotg_map_dma(hs, hs_ep, req); + ret = s3c_hsotg_map_dma(hs, hs_ep, req); if (ret) return ret; } @@ -1326,6 +1384,8 @@ static void s3c_hsotg_complete_request(struct dwc2_hsotg *hsotg, if (hs_req->req.status == -EINPROGRESS) hs_req->req.status = result; + s3c_hsotg_handle_unaligned_buf_complete(hsotg, hs_ep, hs_req); + hs_ep->req = NULL; list_del_init(&hs_req->queue); -- cgit v0.10.2 From 643cc4dee1ee804b46f8d288babca046cca7e099 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:32 +0100 Subject: usb: dwc2: gadget: add reset flag in init function Add a flag to request physical reset of the controller when s3c_hsotg_core_init_disconnected is called. During the usb reset, controller must not be fully reconfigured and resetted. Else this leads to shorter chirp-k duration during enumeration. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 485b195..f74304b 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -999,7 +999,8 @@ extern int s3c_hsotg_remove(struct dwc2_hsotg *hsotg); extern int s3c_hsotg_suspend(struct dwc2_hsotg *dwc2); extern int s3c_hsotg_resume(struct dwc2_hsotg *dwc2); extern int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq); -extern void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2); +extern void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, + bool reset); extern void s3c_hsotg_core_connect(struct dwc2_hsotg *hsotg); extern void s3c_hsotg_disconnect(struct dwc2_hsotg *dwc2); #else @@ -1011,7 +1012,8 @@ static inline int s3c_hsotg_resume(struct dwc2_hsotg *dwc2) { return 0; } static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) { return 0; } -static inline void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2) {} +static inline void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, + bool reset) {} static inline void s3c_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} static inline void s3c_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} #endif diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5e91cf15..3cf9c6a 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2271,9 +2271,13 @@ static int s3c_hsotg_corereset(struct dwc2_hsotg *hsotg) * * Issue a soft reset to the core, and await the core finishing it. */ -void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) +void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, + bool is_usb_reset) { - s3c_hsotg_corereset(hsotg); + u32 val; + + if (!is_usb_reset) + s3c_hsotg_corereset(hsotg); /* * we must now enable ep0 ready for host detection and then @@ -2286,7 +2290,8 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) s3c_hsotg_init_fifo(hsotg); - __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); + if (!is_usb_reset) + __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); writel(1 << 18 | DCFG_DEVSPD_HS, hsotg->regs + DCFG); @@ -2357,9 +2362,11 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) s3c_hsotg_ctrl_epint(hsotg, 0, 0, 1); s3c_hsotg_ctrl_epint(hsotg, 0, 1, 1); - __orr32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); - udelay(10); /* see openiboot */ - __bic32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + if (!is_usb_reset) { + __orr32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + udelay(10); /* see openiboot */ + __bic32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + } dev_dbg(hsotg->dev, "DCTL=0x%08x\n", readl(hsotg->regs + DCTL)); @@ -2388,8 +2395,10 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) readl(hsotg->regs + DOEPCTL0)); /* clear global NAKs */ - writel(DCTL_CGOUTNAK | DCTL_CGNPINNAK | DCTL_SFTDISCON, - hsotg->regs + DCTL); + val = DCTL_CGOUTNAK | DCTL_CGNPINNAK; + if (!is_usb_reset) + val |= DCTL_SFTDISCON; + __orr32(hsotg->regs + DCTL, val); /* must be at-least 3ms to allow bus to see disconnect */ mdelay(3); @@ -2482,7 +2491,7 @@ irq_retry: kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, true); s3c_hsotg_core_connect(hsotg); } } @@ -3052,7 +3061,7 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget, spin_lock_irqsave(&hsotg->lock, flags); s3c_hsotg_init(hsotg); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); hsotg->enabled = 0; spin_unlock_irqrestore(&hsotg->lock, flags); @@ -3171,7 +3180,7 @@ static int s3c_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) if (is_active) { /* Kill any ep0 requests as controller will be reinitialized */ kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); if (hsotg->enabled) s3c_hsotg_core_connect(hsotg); } else { @@ -4097,7 +4106,7 @@ int s3c_hsotg_resume(struct dwc2_hsotg *hsotg) s3c_hsotg_phy_enable(hsotg); spin_lock_irqsave(&hsotg->lock, flags); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); if (hsotg->enabled) s3c_hsotg_core_connect(hsotg); spin_unlock_irqrestore(&hsotg->lock, flags); diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 4a9bcdd..c78c874 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1373,7 +1373,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) hsotg->op_state = OTG_STATE_B_PERIPHERAL; dwc2_core_init(hsotg, false, -1); dwc2_enable_global_interrupts(hsotg); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); s3c_hsotg_core_connect(hsotg); } else { /* A-Device connector (Host Mode) */ -- cgit v0.10.2 From 86e37bf9f5d7f1e634ba5ca6bbc02aa85d004d2f Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:33 +0100 Subject: usb: dwc2: gadget: don't modify pullup status during reset Pullup doesn't need to be enabled during usb reset since it is already enabled. This leads to shorter chirp-k duration if done during usb reset. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 3cf9c6a..5090300 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2492,7 +2492,6 @@ irq_retry: -ECONNRESET); s3c_hsotg_core_init_disconnected(hsotg, true); - s3c_hsotg_core_connect(hsotg); } } } -- cgit v0.10.2 From ccb34a9101b340885b1ca3ec766e8544a6a433a7 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:34 +0100 Subject: usb: dwc2: gadget: fix debug message for zlp Print debug message according to zlp direction. Always saying "Sending" is misleading. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5090300..38ba022 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1335,7 +1335,12 @@ static void s3c_hsotg_program_zlp(struct dwc2_hsotg *hsotg, u32 epctl_reg = hs_ep->dir_in ? DIEPCTL(index) : DOEPCTL(index); u32 epsiz_reg = hs_ep->dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); - dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", index); + if (hs_ep->dir_in) + dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", + index); + else + dev_dbg(hsotg->dev, "Receiving zero-length packet on ep%d\n", + index); writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | DXEPTSIZ_XFERSIZE(0), hsotg->regs + -- cgit v0.10.2 From fa4a8d722bc2f0b1887ea929874a0f6754eda8b4 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:35 +0100 Subject: usb: dwc2: gadget: fix phy interface configuration hsotg->phyif is set in dwc2_gadget_init according to phy interface width. Use it for configuration instead of hardcoded value. Moreover, set USB turnaround time according to phy width. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 38ba022..8c3f8f8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2290,8 +2290,9 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, */ /* set the PLL on, remove the HNP/SRP and set the PHY */ + val = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | - (0x5 << 10), hsotg->regs + GUSBCFG); + (val << 10), hsotg->regs + GUSBCFG); s3c_hsotg_init_fifo(hsotg); @@ -2978,6 +2979,7 @@ static void s3c_hsotg_phy_disable(struct dwc2_hsotg *hsotg) */ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) { + u32 trdtim; /* unmask subset of endpoint interrupts */ writel(DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK | @@ -3002,8 +3004,10 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) s3c_hsotg_init_fifo(hsotg); /* set the PLL on, remove the HNP/SRP and set the PHY */ - writel(GUSBCFG_PHYIF16 | GUSBCFG_TOUTCAL(7) | (0x5 << 10), - hsotg->regs + GUSBCFG); + trdtim = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; + writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | + (trdtim << 10), + hsotg->regs + GUSBCFG); if (using_dma(hsotg)) __orr32(hsotg->regs + GAHBCFG, GAHBCFG_DMA_EN); -- cgit v0.10.2 From f889f23d1c15fa73edaecc2cbce35a441eb52581 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:36 +0100 Subject: usb: dwc2: gadget: replace constants with defines Defines are more readable and searchable than constants. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 8c3f8f8..f201f6d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2292,14 +2292,14 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, /* set the PLL on, remove the HNP/SRP and set the PHY */ val = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | - (val << 10), hsotg->regs + GUSBCFG); + (val << GUSBCFG_USBTRDTIM_SHIFT), hsotg->regs + GUSBCFG); s3c_hsotg_init_fifo(hsotg); if (!is_usb_reset) __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); - writel(1 << 18 | DCFG_DEVSPD_HS, hsotg->regs + DCFG); + writel(DCFG_EPMISCNT(1) | DCFG_DEVSPD_HS, hsotg->regs + DCFG); /* Clear any pending OTG interrupts */ writel(0xffffffff, hsotg->regs + GOTGINT); @@ -3006,7 +3006,7 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) /* set the PLL on, remove the HNP/SRP and set the PHY */ trdtim = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | - (trdtim << 10), + (trdtim << GUSBCFG_USBTRDTIM_SHIFT), hsotg->regs + GUSBCFG); if (using_dma(hsotg)) @@ -3295,7 +3295,7 @@ static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) /* check hardware configuration */ cfg = readl(hsotg->regs + GHWCFG2); - hsotg->num_of_eps = (cfg >> 10) & 0xF; + hsotg->num_of_eps = (cfg >> GHWCFG2_NUM_DEV_EP_SHIFT) & 0xF; /* Add ep0 */ hsotg->num_of_eps++; @@ -3326,10 +3326,10 @@ static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) } cfg = readl(hsotg->regs + GHWCFG3); - hsotg->fifo_mem = (cfg >> 16); + hsotg->fifo_mem = (cfg >> GHWCFG3_DFIFO_DEPTH_SHIFT); cfg = readl(hsotg->regs + GHWCFG4); - hsotg->dedicated_fifos = (cfg >> 25) & 1; + hsotg->dedicated_fifos = (cfg >> GHWCFG4_DED_FIFO_SHIFT) & 1; dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", hsotg->num_of_eps, @@ -3354,8 +3354,8 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg) readl(regs + DCFG), readl(regs + DCTL), readl(regs + DIEPMSK)); - dev_info(dev, "GAHBCFG=0x%08x, 0x44=0x%08x\n", - readl(regs + GAHBCFG), readl(regs + 0x44)); + dev_info(dev, "GAHBCFG=0x%08x, GHWCFG1=0x%08x\n", + readl(regs + GAHBCFG), readl(regs + GHWCFG1)); dev_info(dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", readl(regs + GRXFSIZ), readl(regs + GNPTXFSIZ)); @@ -3715,7 +3715,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) /* create general state file */ - hsotg->debug_file = debugfs_create_file("state", 0444, root, + hsotg->debug_file = debugfs_create_file("state", S_IRUGO, root, hsotg, &state_fops); if (IS_ERR(hsotg->debug_file)) @@ -3729,7 +3729,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) dev_err(hsotg->dev, "%s: failed to create testmode\n", __func__); - hsotg->debug_fifo = debugfs_create_file("fifo", 0444, root, + hsotg->debug_fifo = debugfs_create_file("fifo", S_IRUGO, root, hsotg, &fifo_fops); if (IS_ERR(hsotg->debug_fifo)) @@ -3741,7 +3741,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) ep = hsotg->eps_out[epidx]; if (ep) { - ep->debugfs = debugfs_create_file(ep->name, 0444, + ep->debugfs = debugfs_create_file(ep->name, S_IRUGO, root, ep, &ep_fops); if (IS_ERR(ep->debugfs)) @@ -3755,7 +3755,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) ep = hsotg->eps_in[epidx]; if (ep) { - ep->debugfs = debugfs_create_file(ep->name, 0444, + ep->debugfs = debugfs_create_file(ep->name, S_IRUGO, root, ep, &ep_fops); if (IS_ERR(ep->debugfs)) diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index d018ebe..d0a5ed8 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -294,6 +294,7 @@ #define GHWCFG4_NUM_IN_EPS_MASK (0xf << 26) #define GHWCFG4_NUM_IN_EPS_SHIFT 26 #define GHWCFG4_DED_FIFO_EN (1 << 25) +#define GHWCFG4_DED_FIFO_SHIFT 25 #define GHWCFG4_SESSION_END_FILT_EN (1 << 24) #define GHWCFG4_B_VALID_FILT_EN (1 << 23) #define GHWCFG4_A_VALID_FILT_EN (1 << 22) -- cgit v0.10.2 From b4c2378df6419b81eefcb9cd56726321c29a70ca Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:37 +0100 Subject: usb: dwc2: gadget: initialize controller in pullup callback USB reset interrupt is no more used to reset the controller. Thus, reset the controller in pullup callback as described by Synopsys programming guide. Otherwise enumeration sometimes fails when usb configuration is switched without physical disconnection. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f201f6d..15aa578 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3162,6 +3162,7 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) if (is_on) { clk_enable(hsotg->clk); hsotg->enabled = 1; + s3c_hsotg_core_init_disconnected(hsotg, false); s3c_hsotg_core_connect(hsotg); } else { s3c_hsotg_core_disconnect(hsotg); -- cgit v0.10.2 From 9eb0797722895f4309b46d122e24d87ad17f473b Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Fri, 30 Jan 2015 17:22:45 +0100 Subject: usb: phy: generic: fix the gpios to be optional All the gpios, ie. reset-gpios and vbus-detect-gpio, should be optional and not prevent the driver from working. Fix the regression in the behavior introduced by commit "usb: phy: generic: migrate to gpio_desc". Signed-off-by: Robert Jarzmik Tested-by: Fabio Estevam Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index 9a826ff..bdb4cb3 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -218,12 +218,12 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, clk_rate = 0; needs_vcc = of_property_read_bool(node, "vcc-supply"); - nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset-gpios"); - err = PTR_ERR(nop->gpiod_reset); + nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset"); + err = PTR_ERR_OR_ZERO(nop->gpiod_reset); if (!err) { nop->gpiod_vbus = devm_gpiod_get_optional(dev, - "vbus-detect-gpio"); - err = PTR_ERR(nop->gpiod_vbus); + "vbus-detect"); + err = PTR_ERR_OR_ZERO(nop->gpiod_vbus); } } else if (pdata) { type = pdata->type; @@ -242,9 +242,11 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, if (err == -EPROBE_DEFER) return -EPROBE_DEFER; if (err) { - dev_err(dev, "Error requesting RESET GPIO\n"); + dev_err(dev, "Error requesting RESET or VBUS GPIO\n"); return err; } + if (nop->gpiod_reset) + gpiod_direction_output(nop->gpiod_reset, 1); nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg), GFP_KERNEL); -- cgit v0.10.2 From 0f4ff5f1f95796ff5e32766136f3d3dfce1ec2d7 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Fri, 30 Jan 2015 17:22:46 +0100 Subject: usb: phy: generic: fix the vbus interrupt request Declare the interrupt as "one shot" so that it is masked until the end of the threaded handler. This prevents the irq core from spitting out an error : "Threaded irq requested with handler=NULL and !ONESHOT for irq 63" This was introduced by commit "usb: phy: generic: add vbus support". Signed-off-by: Robert Jarzmik Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index bdb4cb3..48af068 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -41,7 +41,8 @@ #include "phy-generic.h" #define VBUS_IRQ_FLAGS \ - (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) + (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \ + IRQF_ONESHOT) struct platform_device *usb_phy_generic_register(void) { -- cgit v0.10.2 From ffb9da65755815611010e07ad1c937858f226cb2 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 30 Jan 2015 15:58:23 +0900 Subject: usb: renesas_usbhs: fix NULL pointer dereference in dma_release_channel() This patch fixes an issue that the following commit causes NULL pointer dereference in dma_release_channel(). "usb: renesas_usbhs: add support for requesting DT DMA" (commit id abd2dbf6bb1b5f3a03a8c76b1a8879da1dd30caa) The usbhsf_dma_init_dt() should set fifo->{t,r}x_chan to NULL if dma_request_slave_channel_reason() returns IS_ERR value. Otherwise, usbhsf_dma_quit() will call dma_release_channel(), and then NULL pointer dereference happens. Acked-by: Geert Uytterhoeven Tested-by: Simon Horman Reported-by: Simon Horman Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index 4c086b1..d891bff 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -1072,7 +1072,11 @@ static void usbhsf_dma_init_pdev(struct usbhs_fifo *fifo) static void usbhsf_dma_init_dt(struct device *dev, struct usbhs_fifo *fifo) { fifo->tx_chan = dma_request_slave_channel_reason(dev, "tx"); + if (IS_ERR(fifo->tx_chan)) + fifo->tx_chan = NULL; fifo->rx_chan = dma_request_slave_channel_reason(dev, "rx"); + if (IS_ERR(fifo->rx_chan)) + fifo->rx_chan = NULL; } static void usbhsf_dma_init(struct usbhs_priv *priv, -- cgit v0.10.2 From b7974de821bb65a7efe1961a600c850b5029ed6d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 28 Jan 2015 22:49:00 +0100 Subject: usb: dwc2: fix USB core dependencies It is currently possible to configure the dwc2 driver as built-in when host mode or dual-role is enabled, but the USB core is a loadable module. This leads to a link failure: drivers/built-in.o: In function `_dwc2_hcd_start': :(.text+0x84538): undefined reference to `usb_hcd_resume_root_hub' drivers/built-in.o: In function `_dwc2_hcd_urb_dequeue': :(.text+0x84aa0): undefined reference to `usb_hcd_check_unlink_urb' :(.text+0x84e4c): undefined reference to `usb_hcd_unlink_urb_from_ep' :(.text+0x84e74): undefined reference to `usb_hcd_giveback_urb' drivers/built-in.o: In function `dwc2_assign_and_init_hc': :(.text+0x86b98): undefined reference to `usb_hcd_unmap_urb_for_dma' drivers/built-in.o: In function `_dwc2_hcd_urb_enqueue': :(.text+0x8717c): undefined reference to `usb_hcd_link_urb_to_ep' :(.text+0x872f4): undefined reference to `usb_hcd_unlink_urb_from_ep' drivers/built-in.o: In function `dwc2_host_complete': :(.text+0x875d4): undefined reference to `usb_hcd_unlink_urb_from_ep' :(.text+0x87600): undefined reference to `usb_hcd_giveback_urb' drivers/built-in.o: In function `dwc2_hcd_init': :(.text+0x87ba8): undefined reference to `usb_disabled' :(.text+0x87d38): undefined reference to `usb_create_hcd' :(.text+0x88094): undefined reference to `usb_add_hcd' :(.text+0x880dc): undefined reference to `usb_put_hcd' drivers/built-in.o: In function `dwc2_hcd_remove': :(.text+0x8821c): undefined reference to `usb_remove_hcd' :(.text+0x8823c): undefined reference to `usb_put_hcd' drivers/built-in.o: In function `dwc2_hc_handle_tt_clear.isra.10': :(.text+0x88e2c): undefined reference to `usb_hub_clear_tt_buffer' drivers/built-in.o: In function `dwc2_hcd_qtd_add': :(.text+0x8b554): undefined reference to `usb_calc_bus_time' To fix the problem, this patch changes the dependencies so that dwc2 host mode can only be enabled if either the USB core is built-in or both USB and dwc2 are modules. Acked-by: John Youn Signed-off-by: Arnd Bergmann Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig index b323c4c..76b9ba4 100644 --- a/drivers/usb/dwc2/Kconfig +++ b/drivers/usb/dwc2/Kconfig @@ -23,7 +23,7 @@ choice config USB_DWC2_HOST bool "Host only mode" - depends on USB + depends on USB=y || (USB_DWC2=m && USB) help The Designware USB2.0 high-speed host controller integrated into many SoCs. Select this option if you want the @@ -42,7 +42,7 @@ config USB_DWC2_PERIPHERAL config USB_DWC2_DUAL_ROLE bool "Dual Role mode" - depends on (USB=y || USB=USB_DWC2) && (USB_GADGET=y || USB_GADGET=USB_DWC2) + depends on (USB=y && USB_GADGET=y) || (USB_DWC2=m && USB && USB_GADGET) help Select this option if you want the driver to work in a dual-role mode. In this mode both host and gadget features are enabled, and -- cgit v0.10.2 From 74379991f6eb760c8767a39c60b4a918af30f80b Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 28 Jan 2015 13:19:17 -0200 Subject: usb: phy: phy-generic: Fix USB PHY gpio reset Since commit e9f2cefb0cdc2ae ("usb: phy: generic: migrate to gpio_desc") a kernel hang is observed on imx51-babbage board: [ 1.392824] ci_hdrc ci_hdrc.1: doesn't support gadget [ 1.397975] ci_hdrc ci_hdrc.1: EHCI Host Controller [ 1.403205] ci_hdrc ci_hdrc.1: new USB bus registered, assigned bus number 1 [ 1.422335] ci_hdrc ci_hdrc.1: USB 2.0 started, EHCI 1.00 [ 1.432962] hub 1-0:1.0: USB hub found [ 1.437119] hub 1-0:1.0: 1 port detected This hang happens because the reset GPIO stays at logic level 0. The USB PHY reset gpio is defined in the dts file as: reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; , which means it is active low, so what the gpio reset pin needs to do in this case is the following: - Go to logic level 0 to reset the USB PHY - Stay at 0 for a bit - Go back to logic level 1 When switching to gpiod API we need to following according to Documentation/gpio/consumer.txt: "The first thing a driver must do with a GPIO is setting its direction. If no direction-setting flags have been given to gpiod_get*(), this is done by invoking one of the gpiod_direction_*() functions: int gpiod_direction_input(struct gpio_desc *desc) int gpiod_direction_output(struct gpio_desc *desc, int value)" Since no direction-setting flags have been given to devm_gpiod_get_optional() in our case, we need to use gpiod_direction_output to comply with the gpiod API. With this change the USB PHY reset performs a proper reset, the kernel boots fine and USB host is functional. Signed-off-by: Fabio Estevam Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index 48af068..70be50b 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -64,11 +64,12 @@ static int nop_set_suspend(struct usb_phy *x, int suspend) static void nop_reset_set(struct usb_phy_generic *nop, int asserted) { - if (nop->gpiod_reset) - gpiod_set_value(nop->gpiod_reset, asserted); + if (!nop->gpiod_reset) + return; - if (!asserted) - usleep_range(10000, 20000); + gpiod_direction_output(nop->gpiod_reset, !asserted); + usleep_range(10000, 20000); + gpiod_set_value(nop->gpiod_reset, asserted); } /* interface to regulator framework */ -- cgit v0.10.2 From 7e860a6e7aa62b337a61110430cd633db5b0d2dd Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 28 Jan 2015 11:14:55 +0100 Subject: cdc-acm: add sanity checks Check the special CDC headers for a plausible minimum length. Another big operating systems ignores such garbage. Signed-off-by: Oliver Neukum CC: stable@vger.kernel.org Reviewed-by: Adam Lee Tested-by: Adam Lee Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 546a17e8..a417b73 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1091,6 +1091,7 @@ static int acm_probe(struct usb_interface *intf, unsigned long quirks; int num_rx_buf; int i; + unsigned int elength = 0; int combined_interfaces = 0; struct device *tty_dev; int rv = -ENOMEM; @@ -1136,9 +1137,12 @@ static int acm_probe(struct usb_interface *intf, dev_err(&intf->dev, "skipping garbage\n"); goto next_desc; } + elength = buffer[0]; switch (buffer[2]) { case USB_CDC_UNION_TYPE: /* we've found it */ + if (elength < sizeof(struct usb_cdc_union_desc)) + goto next_desc; if (union_header) { dev_err(&intf->dev, "More than one " "union descriptor, skipping ...\n"); @@ -1147,29 +1151,36 @@ static int acm_probe(struct usb_interface *intf, union_header = (struct usb_cdc_union_desc *)buffer; break; case USB_CDC_COUNTRY_TYPE: /* export through sysfs*/ + if (elength < sizeof(struct usb_cdc_country_functional_desc)) + goto next_desc; cfd = (struct usb_cdc_country_functional_desc *)buffer; break; case USB_CDC_HEADER_TYPE: /* maybe check version */ break; /* for now we ignore it */ case USB_CDC_ACM_TYPE: + if (elength < 4) + goto next_desc; ac_management_function = buffer[3]; break; case USB_CDC_CALL_MANAGEMENT_TYPE: + if (elength < 5) + goto next_desc; call_management_function = buffer[3]; call_interface_num = buffer[4]; break; default: - /* there are LOTS more CDC descriptors that + /* + * there are LOTS more CDC descriptors that * could legitimately be found here. */ dev_dbg(&intf->dev, "Ignoring descriptor: " - "type %02x, length %d\n", - buffer[2], buffer[0]); + "type %02x, length %ud\n", + buffer[2], elength); break; } next_desc: - buflen -= buffer[0]; - buffer += buffer[0]; + buflen -= elength; + buffer += elength; } if (!union_header) { -- cgit v0.10.2 From 17136d4984b37d2a9acb67e1da1a3e95e3fed918 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 28 Jan 2015 16:56:24 +0100 Subject: cdc-acm: kill unnecessary messages Memory allocation failures are reported by a central facility. No need to repeat the job. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index a417b73..e78720b 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1298,10 +1298,8 @@ made_compressed_probe: dev_dbg(&intf->dev, "interfaces are valid\n"); acm = kzalloc(sizeof(struct acm), GFP_KERNEL); - if (acm == NULL) { - dev_err(&intf->dev, "out of memory (acm kzalloc)\n"); + if (acm == NULL) goto alloc_fail; - } minor = acm_alloc_minor(acm); if (minor == ACM_TTY_MINORS) { @@ -1340,42 +1338,32 @@ made_compressed_probe: acm->quirks = quirks; buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); - if (!buf) { - dev_err(&intf->dev, "out of memory (ctrl buffer alloc)\n"); + if (!buf) goto alloc_fail2; - } acm->ctrl_buffer = buf; - if (acm_write_buffers_alloc(acm) < 0) { - dev_err(&intf->dev, "out of memory (write buffer alloc)\n"); + if (acm_write_buffers_alloc(acm) < 0) goto alloc_fail4; - } acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); - if (!acm->ctrlurb) { - dev_err(&intf->dev, "out of memory (ctrlurb kmalloc)\n"); + if (!acm->ctrlurb) goto alloc_fail5; - } + for (i = 0; i < num_rx_buf; i++) { struct acm_rb *rb = &(acm->read_buffers[i]); struct urb *urb; rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL, &rb->dma); - if (!rb->base) { - dev_err(&intf->dev, "out of memory " - "(read bufs usb_alloc_coherent)\n"); + if (!rb->base) goto alloc_fail6; - } rb->index = i; rb->instance = acm; urb = usb_alloc_urb(0, GFP_KERNEL); - if (!urb) { - dev_err(&intf->dev, - "out of memory (read urbs usb_alloc_urb)\n"); + if (!urb) goto alloc_fail6; - } + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; urb->transfer_dma = rb->dma; if (acm->is_int_ep) { @@ -1400,11 +1388,8 @@ made_compressed_probe: struct acm_wb *snd = &(acm->wb[i]); snd->urb = usb_alloc_urb(0, GFP_KERNEL); - if (snd->urb == NULL) { - dev_err(&intf->dev, - "out of memory (write urbs usb_alloc_urb)\n"); + if (snd->urb == NULL) goto alloc_fail7; - } if (usb_endpoint_xfer_int(epwrite)) usb_fill_int_urb(snd->urb, usb_dev, -- cgit v0.10.2 From 3f2cee73b650921b2e214bf487b2061a1c266504 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 29 Jan 2015 11:29:13 -0500 Subject: USB: usbfs: allow URBs to be reaped after disconnection The usbfs API has a peculiar hole: Users are not allowed to reap their URBs after the device has been disconnected. There doesn't seem to be any good reason for this; it is an ad-hoc inconsistency. The patch allows users to issue the USBDEVFS_REAPURB and USBDEVFS_REAPURBNDELAY ioctls (together with their 32-bit counterparts on 64-bit systems) even after the device is gone. If no URBs are pending for a disconnected device then the ioctls will return -ENODEV rather than -EAGAIN, because obviously no new URBs will ever be able to complete. The patch also adds a new capability flag for USBDEVFS_GET_CAPABILITIES to indicate that the reap-after-disconnect feature is supported. Signed-off-by: Alan Stern Tested-by: Chris Dickens Acked-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 0b59731..66abdbc 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1689,7 +1689,7 @@ static struct async *reap_as(struct usb_dev_state *ps) for (;;) { __set_current_state(TASK_INTERRUPTIBLE); as = async_getcompleted(ps); - if (as) + if (as || !connected(ps)) break; if (signal_pending(current)) break; @@ -1712,7 +1712,7 @@ static int proc_reapurb(struct usb_dev_state *ps, void __user *arg) } if (signal_pending(current)) return -EINTR; - return -EIO; + return -ENODEV; } static int proc_reapurbnonblock(struct usb_dev_state *ps, void __user *arg) @@ -1721,10 +1721,11 @@ static int proc_reapurbnonblock(struct usb_dev_state *ps, void __user *arg) struct async *as; as = async_getcompleted(ps); - retval = -EAGAIN; if (as) { retval = processcompl(as, (void __user * __user *)arg); free_async(as); + } else { + retval = (connected(ps) ? -EAGAIN : -ENODEV); } return retval; } @@ -1854,7 +1855,7 @@ static int proc_reapurb_compat(struct usb_dev_state *ps, void __user *arg) } if (signal_pending(current)) return -EINTR; - return -EIO; + return -ENODEV; } static int proc_reapurbnonblock_compat(struct usb_dev_state *ps, void __user *arg) @@ -1862,11 +1863,12 @@ static int proc_reapurbnonblock_compat(struct usb_dev_state *ps, void __user *ar int retval; struct async *as; - retval = -EAGAIN; as = async_getcompleted(ps); if (as) { retval = processcompl_compat(as, (void __user * __user *)arg); free_async(as); + } else { + retval = (connected(ps) ? -EAGAIN : -ENODEV); } return retval; } @@ -2038,7 +2040,8 @@ static int proc_get_capabilities(struct usb_dev_state *ps, void __user *arg) { __u32 caps; - caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM; + caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM | + USBDEVFS_CAP_REAP_AFTER_DISCONNECT; if (!ps->dev->bus->no_stop_on_short) caps |= USBDEVFS_CAP_BULK_CONTINUATION; if (ps->dev->bus->sg_tablesize) @@ -2138,6 +2141,32 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, return -EPERM; usb_lock_device(dev); + + /* Reap operations are allowed even after disconnection */ + switch (cmd) { + case USBDEVFS_REAPURB: + snoop(&dev->dev, "%s: REAPURB\n", __func__); + ret = proc_reapurb(ps, p); + goto done; + + case USBDEVFS_REAPURBNDELAY: + snoop(&dev->dev, "%s: REAPURBNDELAY\n", __func__); + ret = proc_reapurbnonblock(ps, p); + goto done; + +#ifdef CONFIG_COMPAT + case USBDEVFS_REAPURB32: + snoop(&dev->dev, "%s: REAPURB32\n", __func__); + ret = proc_reapurb_compat(ps, p); + goto done; + + case USBDEVFS_REAPURBNDELAY32: + snoop(&dev->dev, "%s: REAPURBNDELAY32\n", __func__); + ret = proc_reapurbnonblock_compat(ps, p); + goto done; +#endif + } + if (!connected(ps)) { usb_unlock_device(dev); return -ENODEV; @@ -2231,16 +2260,6 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, inode->i_mtime = CURRENT_TIME; break; - case USBDEVFS_REAPURB32: - snoop(&dev->dev, "%s: REAPURB32\n", __func__); - ret = proc_reapurb_compat(ps, p); - break; - - case USBDEVFS_REAPURBNDELAY32: - snoop(&dev->dev, "%s: REAPURBNDELAY32\n", __func__); - ret = proc_reapurbnonblock_compat(ps, p); - break; - case USBDEVFS_IOCTL32: snoop(&dev->dev, "%s: IOCTL32\n", __func__); ret = proc_ioctl_compat(ps, ptr_to_compat(p)); @@ -2252,16 +2271,6 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, ret = proc_unlinkurb(ps, p); break; - case USBDEVFS_REAPURB: - snoop(&dev->dev, "%s: REAPURB\n", __func__); - ret = proc_reapurb(ps, p); - break; - - case USBDEVFS_REAPURBNDELAY: - snoop(&dev->dev, "%s: REAPURBNDELAY\n", __func__); - ret = proc_reapurbnonblock(ps, p); - break; - case USBDEVFS_DISCSIGNAL: snoop(&dev->dev, "%s: DISCSIGNAL\n", __func__); ret = proc_disconnectsignal(ps, p); @@ -2304,6 +2313,8 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, ret = proc_free_streams(ps, p); break; } + + done: usb_unlock_device(dev); if (ret >= 0) inode->i_atime = CURRENT_TIME; diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h index abe5f4b..019ba1e 100644 --- a/include/uapi/linux/usbdevice_fs.h +++ b/include/uapi/linux/usbdevice_fs.h @@ -128,11 +128,12 @@ struct usbdevfs_hub_portinfo { char port [127]; /* e.g. port 3 connects to device 27 */ }; -/* Device capability flags */ +/* System and bus capability flags */ #define USBDEVFS_CAP_ZERO_PACKET 0x01 #define USBDEVFS_CAP_BULK_CONTINUATION 0x02 #define USBDEVFS_CAP_NO_PACKET_SIZE_LIM 0x04 #define USBDEVFS_CAP_BULK_SCATTER_GATHER 0x08 +#define USBDEVFS_CAP_REAP_AFTER_DISCONNECT 0x10 /* USBDEVFS_DISCONNECT_CLAIM flags & struct */ -- cgit v0.10.2 From 074f9dd55f9cab1b82690ed7e44bcf38b9616ce0 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 29 Jan 2015 15:05:04 -0500 Subject: USB: add flag for HCDs that can't receive wakeup requests (isp1760-hcd) Currently the USB stack assumes that all host controller drivers are capable of receiving wakeup requests from downstream devices. However, this isn't true for the isp1760-hcd driver, which means that it isn't safe to do a runtime suspend of any device attached to a root-hub port if the device requires wakeup. This patch adds a "cant_recv_wakeups" flag to the usb_hcd structure and sets the flag in isp1760-hcd. The core is modified to prevent a direct child of the root hub from being put into runtime suspend with wakeup enabled if the flag is set. Signed-off-by: Alan Stern Tested-by: Nicolas Pitre CC: Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index c76ec97..818369a 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1780,6 +1780,18 @@ static int autosuspend_check(struct usb_device *udev) dev_dbg(&udev->dev, "remote wakeup needed for autosuspend\n"); return -EOPNOTSUPP; } + + /* + * If the device is a direct child of the root hub and the HCD + * doesn't handle wakeup requests, don't allow autosuspend when + * wakeup is needed. + */ + if (w && udev->parent == udev->bus->root_hub && + bus_to_hcd(udev->bus)->cant_recv_wakeups) { + dev_dbg(&udev->dev, "HCD doesn't handle wakeup requests\n"); + return -EOPNOTSUPP; + } + udev->do_remote_wakeup = w; return 0; } diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index dbba455..cecf39a 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2245,6 +2245,9 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, hcd->rsrc_start = res_start; hcd->rsrc_len = res_len; + /* This driver doesn't support wakeup requests */ + hcd->cant_recv_wakeups = 1; + ret = usb_add_hcd(hcd, irq, irqflags); if (ret) goto err_unmap; diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 8968f61..68b1e83 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -146,6 +146,8 @@ struct usb_hcd { unsigned amd_resume_bug:1; /* AMD remote wakeup quirk */ unsigned can_do_streams:1; /* HC supports streams */ unsigned tpl_support:1; /* OTG & EH TPL support */ + unsigned cant_recv_wakeups:1; + /* wakeup requests from downstream aren't received */ unsigned int irq; /* irq allocated */ void __iomem *regs; /* device memory/io */ -- cgit v0.10.2 From e307ff0f78891cef56199954a0052466675ee97f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 29 Jan 2015 12:35:19 +0200 Subject: usb: host: pci_quirks: joing string literals The patch joins the string literals for happy debugging. There is no functional change. Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index ce63646..f940056 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -603,9 +603,9 @@ static void quirk_usb_handoff_ohci(struct pci_dev *pdev) msleep(10); } if (wait_time <= 0) - dev_warn(&pdev->dev, "OHCI: BIOS handoff failed" - " (BIOS bug?) %08x\n", - readl(base + OHCI_CONTROL)); + dev_warn(&pdev->dev, + "OHCI: BIOS handoff failed (BIOS bug?) %08x\n", + readl(base + OHCI_CONTROL)); } #endif @@ -733,8 +733,9 @@ static void ehci_bios_handoff(struct pci_dev *pdev, * and hope nothing goes too wrong */ if (try_handoff) - dev_warn(&pdev->dev, "EHCI: BIOS handoff failed" - " (BIOS bug?) %08x\n", cap); + dev_warn(&pdev->dev, + "EHCI: BIOS handoff failed (BIOS bug?) %08x\n", + cap); pci_write_config_byte(pdev, offset + 2, 0); } @@ -781,8 +782,9 @@ static void quirk_usb_disable_ehci(struct pci_dev *pdev) case 0: /* Illegal reserved cap, set cap=0 so we exit */ cap = 0; /* then fallthrough... */ default: - dev_warn(&pdev->dev, "EHCI: unrecognized capability " - "%02x\n", cap & 0xff); + dev_warn(&pdev->dev, + "EHCI: unrecognized capability %02x\n", + cap & 0xff); } offset = (cap >> 8) & 0xff; } @@ -893,8 +895,7 @@ void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev) */ if (!IS_ENABLED(CONFIG_USB_XHCI_HCD)) { dev_warn(&xhci_pdev->dev, - "CONFIG_USB_XHCI_HCD is turned off, " - "defaulting to EHCI.\n"); + "CONFIG_USB_XHCI_HCD is turned off, defaulting to EHCI.\n"); dev_warn(&xhci_pdev->dev, "USB 3.0 devices will work at USB 2.0 speeds.\n"); usb_disable_xhci_ports(xhci_pdev); @@ -919,8 +920,9 @@ void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev) pci_read_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, &ports_available); - dev_dbg(&xhci_pdev->dev, "USB 3.0 ports that are now enabled " - "under xHCI: 0x%x\n", ports_available); + dev_dbg(&xhci_pdev->dev, + "USB 3.0 ports that are now enabled under xHCI: 0x%x\n", + ports_available); /* Read XUSB2PRM, xHCI USB 2.0 Port Routing Mask Register * Indicate the USB 2.0 ports to be controlled by the xHCI host. @@ -941,8 +943,9 @@ void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev) pci_read_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, &ports_available); - dev_dbg(&xhci_pdev->dev, "USB 2.0 ports that are now switched over " - "to xHCI: 0x%x\n", ports_available); + dev_dbg(&xhci_pdev->dev, + "USB 2.0 ports that are now switched over to xHCI: 0x%x\n", + ports_available); } EXPORT_SYMBOL_GPL(usb_enable_intel_xhci_ports); @@ -1010,8 +1013,9 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev) /* Assume a buggy BIOS and take HC ownership anyway */ if (timeout) { - dev_warn(&pdev->dev, "xHCI BIOS handoff failed" - " (BIOS bug ?) %08x\n", val); + dev_warn(&pdev->dev, + "xHCI BIOS handoff failed (BIOS bug ?) %08x\n", + val); writel(val & ~XHCI_HC_BIOS_OWNED, base + ext_cap_offset); } } @@ -1039,8 +1043,8 @@ hc_init: if (timeout) { val = readl(op_reg_base + XHCI_STS_OFFSET); dev_warn(&pdev->dev, - "xHCI HW not ready after 5 sec (HC bug?) " - "status = 0x%x\n", val); + "xHCI HW not ready after 5 sec (HC bug?) status = 0x%x\n", + val); } /* Send the halt and disable interrupts command */ @@ -1054,8 +1058,8 @@ hc_init: if (timeout) { val = readl(op_reg_base + XHCI_STS_OFFSET); dev_warn(&pdev->dev, - "xHCI HW did not halt within %d usec " - "status = 0x%x\n", XHCI_MAX_HALT_USEC, val); + "xHCI HW did not halt within %d usec status = 0x%x\n", + XHCI_MAX_HALT_USEC, val); } iounmap(base); @@ -1075,8 +1079,8 @@ static void quirk_usb_early_handoff(struct pci_dev *pdev) return; if (pci_enable_device(pdev) < 0) { - dev_warn(&pdev->dev, "Can't enable PCI device, " - "BIOS handoff failed.\n"); + dev_warn(&pdev->dev, + "Can't enable PCI device, BIOS handoff failed.\n"); return; } if (pdev->class == PCI_CLASS_SERIAL_USB_UHCI) -- cgit v0.10.2 From cefa9a31a5f0856b81d53030815485fcb1fe8aab Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Jan 2015 20:04:06 +0200 Subject: ehci-pci: disable for Intel MID platforms On some Intel MID platforms the ChipIdea USB controller is used. The EHCI PCI is in conflict with the proper driver. The patch makes ehci-pci to be ignored in favour of ChipIdea controller. Signed-off-by: Andy Shevchenko Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 851006a..9652021 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -42,6 +42,20 @@ static inline bool is_intel_quark_x1000(struct pci_dev *pdev) pdev->device == PCI_DEVICE_ID_INTEL_QUARK_X1000_SOC; } +static const struct pci_device_id ci_hdrc_pci_id_table[] = { + { PCI_DEVICE(0x153F, 0x1004), }, + { PCI_DEVICE(0x153F, 0x1006), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0811), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe006), }, + {} +}; + +static inline bool is_ci_hdrc_pci(struct pci_dev *pdev) +{ + return !!pci_match_id(ci_hdrc_pci_id_table, pdev); +} + /* * 0x84 is the offset of in/out threshold register, * and it is the same offset as the register of 'hostpc'. @@ -352,6 +366,13 @@ static const struct ehci_driver_overrides pci_overrides __initconst = { /*-------------------------------------------------------------------------*/ +static int ehci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + if (is_ci_hdrc_pci(pdev)) + return -ENODEV; + return usb_hcd_pci_probe(pdev, id); +} + /* PCI driver selection metadata; PCI hotplugging uses this */ static const struct pci_device_id pci_ids [] = { { /* handle any USB 2.0 EHCI controller */ @@ -370,7 +391,7 @@ static struct pci_driver ehci_pci_driver = { .name = (char *) hcd_name, .id_table = pci_ids, - .probe = usb_hcd_pci_probe, + .probe = ehci_pci_probe, .remove = usb_hcd_pci_remove, .shutdown = usb_hcd_pci_shutdown, -- cgit v0.10.2 From c99197902da284b4b723451c1471c45b18537cde Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 30 Jan 2015 12:58:26 -0500 Subject: USB: fix use-after-free bug in usb_hcd_unlink_urb() The usb_hcd_unlink_urb() routine in hcd.c contains two possible use-after-free errors. The dev_dbg() statement at the end of the routine dereferences urb and urb->dev even though both structures may have been deallocated. This patch fixes the problem by storing urb->dev in a local variable (avoiding the dereference of urb) and moving the dev_dbg() up before the usb_put_dev() call. Signed-off-by: Alan Stern Reported-by: Joe Lawrence Tested-by: Joe Lawrence CC: Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 11cee55..45a915c 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1618,6 +1618,7 @@ static int unlink1(struct usb_hcd *hcd, struct urb *urb, int status) int usb_hcd_unlink_urb (struct urb *urb, int status) { struct usb_hcd *hcd; + struct usb_device *udev = urb->dev; int retval = -EIDRM; unsigned long flags; @@ -1629,20 +1630,19 @@ int usb_hcd_unlink_urb (struct urb *urb, int status) spin_lock_irqsave(&hcd_urb_unlink_lock, flags); if (atomic_read(&urb->use_count) > 0) { retval = 0; - usb_get_dev(urb->dev); + usb_get_dev(udev); } spin_unlock_irqrestore(&hcd_urb_unlink_lock, flags); if (retval == 0) { hcd = bus_to_hcd(urb->dev->bus); retval = unlink1(hcd, urb, status); - usb_put_dev(urb->dev); + if (retval == 0) + retval = -EINPROGRESS; + else if (retval != -EIDRM && retval != -EBUSY) + dev_dbg(&udev->dev, "hcd_unlink_urb %p fail %d\n", + urb, retval); + usb_put_dev(udev); } - - if (retval == 0) - retval = -EINPROGRESS; - else if (retval != -EIDRM && retval != -EBUSY) - dev_dbg(&urb->dev->dev, "hcd_unlink_urb %p fail %d\n", - urb, retval); return retval; } -- cgit v0.10.2 From d9b2b19fc45265a2423fa68e22318882b42a111c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 31 Jan 2015 21:13:59 -0800 Subject: usb: musb: blackfin: remove incorrect __exit_p() bfin_remove() is not (nor should it be) marked as __exit, so we should not be using __exit_p() wrapper with it, otherwise unbinding through sysfs does not work properly. Signed-off-by: Dmitry Torokhov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c index 1782501..6123b74 100644 --- a/drivers/usb/musb/blackfin.c +++ b/drivers/usb/musb/blackfin.c @@ -608,7 +608,7 @@ static SIMPLE_DEV_PM_OPS(bfin_pm_ops, bfin_suspend, bfin_resume); static struct platform_driver bfin_driver = { .probe = bfin_probe, - .remove = __exit_p(bfin_remove), + .remove = bfin_remove, .driver = { .name = "musb-blackfin", .pm = &bfin_pm_ops, -- cgit v0.10.2 From 8333d3cd06f89bdddea61c64ae123b2265ffab9d Mon Sep 17 00:00:00 2001 From: Christoph Jaeger Date: Mon, 2 Feb 2015 10:00:03 -0500 Subject: usb: gadget: Kconfig: use bool instead of boolean Keyword 'boolean' for type definition attributes is considered deprecated and, therefore, should not be used anymore. See http://lkml.kernel.org/r/cover.1418003065.git.cj@linux.com See http://lkml.kernel.org/r/1419108071-11607-1-git-send-email-cj@linux.com Signed-off-by: Christoph Jaeger Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 65f7f12..9653903 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -424,7 +424,7 @@ config USB_CONFIGFS_F_HID For more information, see Documentation/usb/gadget_hid.txt. config USB_CONFIGFS_F_UVC - boolean "USB Webcam function" + bool "USB Webcam function" depends on USB_CONFIGFS depends on VIDEO_DEV select VIDEOBUF2_VMALLOC -- cgit v0.10.2 From 518ca8d9326e258f4595bfe9bc7fc61f5c3636cb Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 3 Feb 2015 18:08:39 +0200 Subject: ehci-pci: disable for Intel MID platforms (update) This is a follow up to the previously submitted commit cefa9a31a5f0 (ehci-pci: disable for Intel MID platforms). It includes the following changes: - table and function are renamed to reflect this is not only about ChipIdea - ChipIdea PCI driver (ci_hdrc_pci.c) gets the comment about the table in ehci-pci.c - MIPS IDs removed from the list since it was discovered and tested on Intel MID platforms Reviewed-by: Alexander Shishkin Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/chipidea/ci_hdrc_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c index 241ae34..4df6694 100644 --- a/drivers/usb/chipidea/ci_hdrc_pci.c +++ b/drivers/usb/chipidea/ci_hdrc_pci.c @@ -111,6 +111,9 @@ static void ci_hdrc_pci_remove(struct pci_dev *pdev) * PCI device structure * * Check "pci.h" for details + * + * Note: ehci-pci driver may try to probe the device first. You have to add an + * ID to the bypass_pci_id_table in ehci-pci driver to prevent this. */ static const struct pci_device_id ci_hdrc_pci_id_table[] = { { diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 9652021..2a5d2fd 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -42,18 +42,22 @@ static inline bool is_intel_quark_x1000(struct pci_dev *pdev) pdev->device == PCI_DEVICE_ID_INTEL_QUARK_X1000_SOC; } -static const struct pci_device_id ci_hdrc_pci_id_table[] = { - { PCI_DEVICE(0x153F, 0x1004), }, - { PCI_DEVICE(0x153F, 0x1006), }, +/* + * This is the list of PCI IDs for the devices that have EHCI USB class and + * specific drivers for that. One of the example is a ChipIdea device installed + * on some Intel MID platforms. + */ +static const struct pci_device_id bypass_pci_id_table[] = { + /* ChipIdea on Intel MID platform */ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0811), }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829), }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe006), }, {} }; -static inline bool is_ci_hdrc_pci(struct pci_dev *pdev) +static inline bool is_bypassed_id(struct pci_dev *pdev) { - return !!pci_match_id(ci_hdrc_pci_id_table, pdev); + return !!pci_match_id(bypass_pci_id_table, pdev); } /* @@ -368,7 +372,7 @@ static const struct ehci_driver_overrides pci_overrides __initconst = { static int ehci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - if (is_ci_hdrc_pci(pdev)) + if (is_bypassed_id(pdev)) return -ENODEV; return usb_hcd_pci_probe(pdev, id); } -- cgit v0.10.2 From c31316cb6c5adeb3f6e92094d717faa375245822 Mon Sep 17 00:00:00 2001 From: Kevin Hao Date: Sat, 31 Jan 2015 21:47:44 +0800 Subject: usb: kconfig: replace PPC_OF with PPC The PPC_OF is a ppc specific option which is used to mean that the firmware device tree access functions are available. Since all the ppc platforms have a device tree, it is aways set to 'y' for ppc. So it makes no sense to keep a such option in the current kernel. Replace it with PPC Signed-off-by: Kevin Hao Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index fafc628..688ba86 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -219,7 +219,7 @@ config USB_EHCI_TEGRA config USB_EHCI_HCD_PPC_OF bool "EHCI support for PPC USB controller on OF platform bus" - depends on PPC_OF + depends on PPC default y ---help--- Enables support for the USB controller present on the PowerPC @@ -494,7 +494,7 @@ config USB_OHCI_ATH79 config USB_OHCI_HCD_PPC_OF_BE bool "OHCI support for OF platform bus (big endian)" - depends on PPC_OF + depends on PPC select USB_OHCI_BIG_ENDIAN_DESC select USB_OHCI_BIG_ENDIAN_MMIO ---help--- @@ -503,7 +503,7 @@ config USB_OHCI_HCD_PPC_OF_BE config USB_OHCI_HCD_PPC_OF_LE bool "OHCI support for OF platform bus (little endian)" - depends on PPC_OF + depends on PPC select USB_OHCI_LITTLE_ENDIAN ---help--- Enables support for little-endian USB controllers present on the @@ -511,7 +511,7 @@ config USB_OHCI_HCD_PPC_OF_LE config USB_OHCI_HCD_PPC_OF bool - depends on PPC_OF + depends on PPC default USB_OHCI_HCD_PPC_OF_BE || USB_OHCI_HCD_PPC_OF_LE config USB_OHCI_HCD_PCI -- cgit v0.10.2 From 673016d9f7f7a92636cb83ee1120deaa66269322 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Sun, 25 Jan 2015 21:00:44 +0300 Subject: uhci-hub: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c index 93e17b1..19ba5ea 100644 --- a/drivers/usb/host/uhci-hub.c +++ b/drivers/usb/host/uhci-hub.c @@ -17,8 +17,9 @@ static const __u8 root_hub_hub_des[] = 0x09, /* __u8 bLength; */ 0x29, /* __u8 bDescriptorType; Hub-descriptor */ 0x02, /* __u8 bNbrPorts; */ - 0x0a, /* __u16 wHubCharacteristics; */ - 0x00, /* (per-port OC, no power switching) */ + HUB_CHAR_NO_LPSM | /* __u16 wHubCharacteristics; */ + HUB_CHAR_INDV_PORT_OCPM, /* (per-port OC, no power switching) */ + 0x00, 0x01, /* __u8 bPwrOn2pwrGood; 2ms */ 0x00, /* __u8 bHubContrCurrent; 0 mA */ 0x00, /* __u8 DeviceRemovable; *** 7 Ports max */ -- cgit v0.10.2 From 7671bd1e97b9fa09aea69e76375ada9534c735a3 Mon Sep 17 00:00:00 2001 From: Zhuang Jin Can Date: Mon, 26 Jan 2015 23:30:39 +0800 Subject: Revert "usb: Reset USB-3 devices on USB-3 link bounce" This revert a82b76f7fa6154e8ab2d8071842a3e38b9c0d0ff. The commit causes an extra reset in remote wakeup as described in: http://www.spinics.net/lists/linux-usb/msg119080.html Signed-off-by: Zhuang Jin Can Acked-by: Hans de Goede Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 3e9c4d4..4c259ac 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4886,7 +4886,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, static void port_event(struct usb_hub *hub, int port1) __must_hold(&port_dev->status_lock) { - int connect_change, reset_device = 0; + int connect_change; struct usb_port *port_dev = hub->ports[port1 - 1]; struct usb_device *udev = port_dev->child; struct usb_device *hdev = hub->hdev; @@ -4974,30 +4974,14 @@ static void port_event(struct usb_hub *hub, int port1) if (hub_port_reset(hub, port1, NULL, HUB_BH_RESET_TIME, true) < 0) hub_port_disable(hub, port1, 1); - } else - reset_device = 1; - } - - /* - * On disconnect USB3 protocol ports transit from U0 to - * SS.Inactive to Rx.Detect. If this happens a warm- - * reset is not needed, but a (re)connect may happen - * before hub_wq runs and sees the disconnect, and the - * device may be an unknown state. - * - * If the port went through SS.Inactive without hub_wq - * seeing it the C_LINK_STATE change flag will be set, - * and we reset the dev to put it in a known state. - */ - if (reset_device || (udev && hub_is_superspeed(hub->hdev) - && (portchange & USB_PORT_STAT_C_LINK_STATE) - && (portstatus & USB_PORT_STAT_CONNECTION))) { - usb_unlock_port(port_dev); - usb_lock_device(udev); - usb_reset_device(udev); - usb_unlock_device(udev); - usb_lock_port(port_dev); - connect_change = 0; + } else { + usb_unlock_port(port_dev); + usb_lock_device(udev); + usb_reset_device(udev); + usb_unlock_device(udev); + usb_lock_port(port_dev); + connect_change = 0; + } } if (connect_change) -- cgit v0.10.2 From 7fa40910e0bf5ef32eca49595d950cb24f6402bf Mon Sep 17 00:00:00 2001 From: Julius Werner Date: Tue, 27 Jan 2015 13:20:12 -0800 Subject: usb: Retry port status check on resume to work around RH bugs The EHCI controller on the RK3288 SoC is violating basic parts of the USB spec and thereby unable to properly resume a suspended port. It does not start SOF generation within 3ms of finishing resume signaling, so the attached device will drop of the bus again. This is a particular problem with runtime PM, where accessing the device will trigger a resume that immediately makes it unavailable (and reenumerate with a new handle). Thankfully, the persist feature is generally able to work around stuff like that. Unfortunately, it doesn't quite work in this particular case because the controller will turn off the CurrentConnectStatus bit for an instant while the device is reconnecting, which causes the kernel to conclude that it permanently disappeared. This patch adds a tiny retry mechanism to the core port resume code which will catch this case and shouldn't have any notable impact on other controllers. Signed-off-by: Julius Werner Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 4c259ac..58d2cde 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2896,10 +2896,12 @@ static int port_is_suspended(struct usb_hub *hub, unsigned portstatus) */ static int check_port_resume_type(struct usb_device *udev, struct usb_hub *hub, int port1, - int status, unsigned portchange, unsigned portstatus) + int status, u16 portchange, u16 portstatus) { struct usb_port *port_dev = hub->ports[port1 - 1]; + int retries = 3; + retry: /* Is a warm reset needed to recover the connection? */ if (status == 0 && udev->reset_resume && hub_port_warm_reset_required(hub, port1, portstatus)) { @@ -2907,10 +2909,17 @@ static int check_port_resume_type(struct usb_device *udev, } /* Is the device still present? */ else if (status || port_is_suspended(hub, portstatus) || - !port_is_power_on(hub, portstatus) || - !(portstatus & USB_PORT_STAT_CONNECTION)) { + !port_is_power_on(hub, portstatus)) { if (status >= 0) status = -ENODEV; + } else if (!(portstatus & USB_PORT_STAT_CONNECTION)) { + if (retries--) { + usleep_range(200, 300); + status = hub_port_status(hub, port1, &portstatus, + &portchange); + goto retry; + } + status = -ENODEV; } /* Can't do a normal resume if the port isn't enabled, -- cgit v0.10.2 From 1fa2df0c70dadba1139c9dd52c9070d00fe23c98 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 3 Feb 2015 10:37:06 +0800 Subject: staging: emxx_udc: fix the build error Fix below build error: reproduce: wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross git checkout 9239d88fc5e58a2a72bc949362f999aac9bffb29 # save the attached .config to linux build tree make.cross ARCH=arm All error/warnings: In file included from include/linux/seqlock.h:35:0, from include/linux/time.h:5, from include/linux/stat.h:18, from include/linux/module.h:10, from drivers/staging/emxx_udc/emxx_udc.c:22: drivers/staging/emxx_udc/emxx_udc.c: In function 'nbu2ss_gad_set_selfpowered': >> drivers/staging/emxx_udc/emxx_udc.c:3129:21: error: 'udc' undeclared (first use in this function) spin_lock_irqsave(&udc->lock, flags); ^ include/linux/spinlock.h:215:34: note: in definition of macro 'raw_spin_lock_irqsave' flags = _raw_spin_lock_irqsave(lock); \ ^ >> drivers/staging/emxx_udc/emxx_udc.c:3129:2: note: in expansion of macro 'spin_lock_irqsave' spin_lock_irqsave(&udc->lock, flags); ^ drivers/staging/emxx_udc/emxx_udc.c:3129:21: note: each undeclared identifier is reported only once for each function it appears in spin_lock_irqsave(&udc->lock, flags); ^ include/linux/spinlock.h:215:34: note: in definition of macro 'raw_spin_lock_irqsave' flags = _raw_spin_lock_irqsave(lock); \ ^ >> drivers/staging/emxx_udc/emxx_udc.c:3129:2: note: in expansion of macro 'spin_lock_irqsave' spin_lock_irqsave(&udc->lock, flags); ^ vim +/udc +3129 drivers/staging/emxx_udc/emxx_udc.c 33aa8d45 Magnus Damm 2014-06-06 3123 33aa8d45 Magnus Damm 2014-06-06 3124 if (pgadget == NULL) { 33aa8d45 Magnus Damm 2014-06-06 3125 ERR("%s, bad param\n", __func__); 33aa8d45 Magnus Damm 2014-06-06 3126 return -EINVAL; 33aa8d45 Magnus Damm 2014-06-06 3127 } 33aa8d45 Magnus Damm 2014-06-06 3128 33aa8d45 Magnus Damm 2014-06-06 @3129 spin_lock_irqsave(&udc->lock, flags); 9239d88f Peter Chen 2015-01-28 3130 pgadget->is_selfpowered = (is_selfpowered != 0); 33aa8d45 Magnus Damm 2014-06-06 3131 spin_unlock_irqrestore(&udc->lock, flags); 33aa8d45 Magnus Damm 2014-06-06 3132 Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/staging/emxx_udc/emxx_udc.c b/drivers/staging/emxx_udc/emxx_udc.c index 1d3135a..bd70ea0 100644 --- a/drivers/staging/emxx_udc/emxx_udc.c +++ b/drivers/staging/emxx_udc/emxx_udc.c @@ -3117,6 +3117,7 @@ static int nbu2ss_gad_wakeup(struct usb_gadget *pgadget) static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, int is_selfpowered) { + struct nbu2ss_udc *udc; unsigned long flags; /* INFO("=== %s()\n", __func__); */ @@ -3126,6 +3127,8 @@ static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, return -EINVAL; } + udc = container_of(pgadget, struct nbu2ss_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); pgadget->is_selfpowered = (is_selfpowered != 0); spin_unlock_irqrestore(&udc->lock, flags); -- cgit v0.10.2 From 251a17f5aff990ab117590df2a13ef032464c0d3 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Mon, 2 Feb 2015 14:55:38 -0800 Subject: usb: dwc2: Fix a bug in reading the endpoint directions from reg. According to the DWC2 datasheet, the HWCFG1 register stores the configured endpoint directions for endpoints 0-15 in bit positions 0-31. ========================== Endpoint Direction (EpDir) This 32-bit field uses two bits per endpoint to determine the endpoint direction. Endpoint Bits [31:30]: Endpoint 15 direction Bits [29:28]: Endpoint 14 direction .... Bits [3:2]: Endpoint 1 direction Bits[1:0]: Endpoint 0 direction (always BIDIR) ========================== The DWC2 driver is currently interpreting the contents of the register as directions for endpoints 1-15 which leads to an error in determining the configured endpoint directions in the core because the first 2 bits determine the direction of endpoint 0 and not 1. This is based on testing/next branch in Felipe's git. Signed-off-by: Roshan Pius Acked-by: John Youn Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 15aa578..6a30887 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3308,7 +3308,7 @@ static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) hsotg->eps_out[0] = hsotg->eps_in[0]; cfg = readl(hsotg->regs + GHWCFG1); - for (i = 1; i < hsotg->num_of_eps; i++, cfg >>= 2) { + for (i = 1, cfg >>= 2; i < hsotg->num_of_eps; i++, cfg >>= 2) { ep_type = cfg & 3; /* Direction in or both */ if (!(ep_type & 2)) { -- cgit v0.10.2 From 9298b4aad37e8c6962edcdbd0b62620adb207d03 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 3 Feb 2015 11:02:10 -0600 Subject: usb: musb: fix device hotplug behind hub The commit 889ad3b "usb: musb: try a race-free wakeup" breaks device hotplug enumeraitonn when the device is connected behind a hub while usb autosuspend is enabled. Adding finish_resume_work into runtime resume callback fixes the issue. Also resume root hub is required to resume the bus from runtime suspend, so move musb_host_resume_root_hub() back to its original location, where handles RESUME interrupt. Signed-off-by: Bin Liu Tested-by: Tony Lindgren Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 34cce3e..e6f4cbf 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -567,6 +567,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, musb->xceiv->otg->state = OTG_STATE_A_HOST; musb->is_active = 1; + musb_host_resume_root_hub(musb); break; case OTG_STATE_B_WAIT_ACON: musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL; @@ -2500,6 +2501,12 @@ static int musb_runtime_resume(struct device *dev) musb_restore_context(musb); first = 0; + if (musb->need_finish_resume) { + musb->need_finish_resume = 0; + schedule_delayed_work(&musb->finish_resume_work, + msecs_to_jiffies(20)); + } + return 0; } diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index 662de58..294e159 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -72,7 +72,6 @@ void musb_host_finish_resume(struct work_struct *work) musb->xceiv->otg->state = OTG_STATE_A_HOST; spin_unlock_irqrestore(&musb->lock, flags); - musb_host_resume_root_hub(musb); } void musb_port_suspend(struct musb *musb, bool do_suspend) -- cgit v0.10.2