usb/gadget: Add driver for Aspeed SoC virtual hub
PATCH v4 2/2 usb/gadget: Add driver for Aspeed SoC virtual hub
drivers/usb/gadget/udc/aspeed-vhub/core.c
Quoted 109 lines · Benjamin Herrenschmidt
The Aspeed BMC SoCs support a "virtual hub" function. It provides some HW support for a top-level USB2 hub behind which sit 5 gadget "ports".
This driver adds support for the full functionality, emulating the hub standard requests and exposing 5 UDC gadget drivers corresponding to the ports.
The hub itself has HW provided dedicated EP0 and EP1 (the latter for hub interrupts). It also has dedicated EP0s for each function. For other endpoints, there's a pool of 15 "generic" endpoints that are shared among the ports.
The driver relies on my previous patch adding a "dispose" EP op to handle EP allocation between ports. EPs are allocated from the shared pool in the UDC "
match_ep" callback and assigned to the UDC instance (added to the gadgetep_list).When the composite driver gets unbound, the new hook will allow the UDC to clean things up and return those EPs to the shared pool.
Signed-off-by Benjamin Herrenschmidt <[email protected]>---
v4. - Fix missing unlockast_vhub_udc_wakeup()error path
- Make "irq" signed to deal with error fromplatform_get_irq
- FixMakefilefor module builds
- Fix a missing endian conversionv3. - Rebased
- Add clk stuffv2. - Cosmetic fixes
- Properly "allocate" device addresses instead of using a never reset counter
- Move .dtsi updates to a different patchfoo
goo
--- drivers/usb/gadget/udc/Kconfig | 2 + drivers/usb/gadget/udc/Makefile | 1 + drivers/usb/gadget/udc/aspeed-vhub/Kconfig | 6 + drivers/usb/gadget/udc/aspeed-vhub/Makefile | 3 + drivers/usb/gadget/udc/aspeed-vhub/core.c | 429 ++++++++++++++ drivers/usb/gadget/udc/aspeed-vhub/dev.c | 580 +++++++++++++++++++ drivers/usb/gadget/udc/aspeed-vhub/ep0.c | 474 ++++++++++++++++ drivers/usb/gadget/udc/aspeed-vhub/epn.c | 840 ++++++++++++++++++++++++++++ drivers/usb/gadget/udc/aspeed-vhub/hub.c | 822 +++++++++++++++++++++++++++ drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 496 ++++++++++++++++ 10 files changed, 3653 insertions(+) create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/Kconfig create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/Makefile create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/core.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/dev.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/ep0.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/epn.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/hub.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/vhub.hdrivers/usb/gadget/udc/Kconfig +2
-0@@ -439,6 +439,8 @@ config USB_GADGET_XILINX dynamically linked module called "udc-xilinx" and force all gadget drivers to also be dynamically linked. +source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig" + # # LAST -- dummy/emulated controller #drivers/usb/gadget/udc/Makefile +1
-0@@ -39,4 +39,5 @@ obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o obj-$(CONFIG_USB_GR_UDC) += gr_udc.o obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o +obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/ obj-$(CONFIG_USB_BDC_UDC) += bdc/drivers/usb/gadget/udc/aspeed-vhub/Kconfig new file +6
-0@@ -0,0 +1,6 @@ +config USB_ASPEED_VHUB + tristate "Aspeed vHub UDC driver" + depends on ARCH_ASPEED || COMPILE_TEST + help + USB peripheral controller for the Aspeed AST2500 family + SoCs supporting the "vHub" functionality and USB2.0drivers/usb/gadget/udc/aspeed-vhub/Makefile new file +3
-0@@ -0,0 +1,3 @@ +obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub.o +aspeed-vhub-y := core.o ep0.o epn.o dev.o hub.o +drivers/usb/gadget/udc/aspeed-vhub/core.c new file +0
-0@@ -0,0 +1,429 @@
missing SPDX license identifier (all files)
drivers/usb/gadget/udc/aspeed-vhub/core.c line 7
Quoted 7 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +7
-0+static bool force_usb1 = false; +static bool no_dma_desc = false; + +module_param_named (force_usb1, force_usb1, bool,0644 ); +MODULE_PARM_DESC (force_usb1,"Set to true to force to USB1 speed" ); +module_param_named (no_dma_desc, no_dma_desc, bool,0644 ); +MODULE_PARM_DESC (no_dma_desc,"Set to true to disable use of DMA descriptors" );
module parameters? Sorry, but no.
Quoted 29 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +29
-0+void ast_vhub_done (struct ast_vhub_ep *ep,struct ast_vhub_req *req, +int status) +{ +bool internal = req->internal; + +EPVDBG (ep,"completing request @%p, status %d\n" , req, status); + +list_del_init (&req->queue); + +if (req->req.status == -EINPROGRESS ) + req->req.status = status; + +if (req->req.dma) { +if (!WARN_ON (!ep->dev)) +usb_gadget_unmap_request (&ep->dev->gadget, + &req->req, ep->epn.is_in); + req->req.dma =0 ; + } + +/* +* If this isn't an internal EP0 request, call the core +* to call the gadget completion. +*/ +if (!internal) { +spin_unlock (&ep->vhub->lock); +usb_gadget_giveback_request (&ep->ep, &req->req); +spin_lock (&ep->vhub->lock); + } +}
drivers/usb/gadget/udc/aspeed-vhub/core.c line 46
Quoted 10 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +10
-0+ +void ast_vhub_nuke (struct ast_vhub_ep *ep,int status) +{ +struct ast_vhub_req *req; + +EPDBG (ep,"Nuking\n" ); + +/* terminate any request in the queue */ +if (list_empty (&ep->queue)) +return ;
unnecessary test.
drivers/usb/gadget/udc/aspeed-vhub/core.c line 50
Quoted 4 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +4
-0+ +/* Beware, lock will be dropped & req-acquired by done() */ +while (!list_empty (&ep->queue)) { + req = list_first_entry(&ep->queue, struct ast_vhub_req, queue);
list_for_each_entry_safe() ??
drivers/usb/gadget/udc/aspeed-vhub/core.c line 62
Quoted 12 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +12
-0+ast_vhub_done (ep, req, status); + } +} + +struct usb_request *ast_vhub_alloc_request (struct usb_ep *u_ep, +gfp_t gfp_flags) +{ +struct ast_vhub_req *req; + + req =kzalloc (sizeof (*req), gfp_flags); +if (!req) +return NULL ;
Don't you wanna keep a reference to the endpoint that owns $this request?
drivers/usb/gadget/udc/aspeed-vhub/core.c line 63
Quoted 1 line
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +1
-0+INIT_LIST_HEAD (&req->queue);
unnecessary list initialization
drivers/usb/gadget/udc/aspeed-vhub/core.c line 74
Quoted 11 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +11
-0+return &req->req; +} + +void ast_vhub_free_request (struct usb_ep *u_ep,struct usb_request *u_req) +{ +struct ast_vhub_req *req =to_ast_req (u_req); + +kfree (req); +} + +static irqreturn_t ast_vhub_irq (int irq,void *data)
^
unnecessary spacedrivers/usb/gadget/udc/aspeed-vhub/core.c line 84
Quoted 10 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +10
-0+{ +struct ast_vhub *vhub = data; +irqreturn_t iret =IRQ_NONE ; +u32 istat; + +/* Stale interrupt while tearing down */ +if (!vhub->ep0_bufs) +return IRQ_NONE ; + +spin_lock (&vhub->lock);
this is your hardirq handler right? Do you really need this lock here?
Quoted 82 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +82
-0+ +/* Read and ACK interrupts */ + istat =readl (vhub->regs +AST_VHUB_ISR ); +if (!istat) + goto bail; +writel (istat, vhub->regs +AST_VHUB_ISR ); + iret =IRQ_HANDLED ; + +UDCVDBG (vhub,"irq status=%08x, ep_acks=%08x ep_nacks=%08x\n" , + istat, +readl (vhub->regs +AST_VHUB_EP_ACK_ISR ), +readl (vhub->regs +AST_VHUB_EP_NACK_ISR )); + +/* Handle generic EPs first */ +if (istat &VHUB_IRQ_EP_POOL_ACK_STALL ) { +u32 i, ep_acks =readl (vhub->regs +AST_VHUB_EP_ACK_ISR ); +writel (ep_acks, vhub->regs +AST_VHUB_EP_ACK_ISR ); + +/* Check if we have a useful ffs on arm ... */ +for (i =0 ; ep_acks && i < AST_VHUB_NUM_GEN_EPs; i++) { +u32 mask =1 << i; +if (ep_acks & mask) { +ast_vhub_epn_ack_irq (&vhub->epns[i]); + ep_acks &= ~mask; + } + } + } + +/* Handle device interrupts */ +if (istat & (VHUB_IRQ_DEVICE1 | +VHUB_IRQ_DEVICE2 | +VHUB_IRQ_DEVICE3 | +VHUB_IRQ_DEVICE4 | +VHUB_IRQ_DEVICE5 )) { +if (istat &VHUB_IRQ_DEVICE1 ) +ast_vhub_dev_irq (&vhub->ports[0 ].dev); +if (istat &VHUB_IRQ_DEVICE2 ) +ast_vhub_dev_irq (&vhub->ports[1 ].dev); +if (istat &VHUB_IRQ_DEVICE3 ) +ast_vhub_dev_irq (&vhub->ports[2 ].dev); +if (istat &VHUB_IRQ_DEVICE4 ) +ast_vhub_dev_irq (&vhub->ports[3 ].dev); +if (istat &VHUB_IRQ_DEVICE5 ) +ast_vhub_dev_irq (&vhub->ports[4 ].dev); + } + +/* Handle top-level vHub EP0 interrupts */ +if (istat & (VHUB_IRQ_HUB_EP0_OUT_ACK_STALL | +VHUB_IRQ_HUB_EP0_IN_ACK_STALL | +VHUB_IRQ_HUB_EP0_SETUP )) { +if (istat &VHUB_IRQ_HUB_EP0_IN_ACK_STALL ) +ast_vhub_ep0_handle_ack (&vhub->ep0, true); +if (istat &VHUB_IRQ_HUB_EP0_OUT_ACK_STALL ) +ast_vhub_ep0_handle_ack (&vhub->ep0, false); +if (istat &VHUB_IRQ_HUB_EP0_SETUP ) +ast_vhub_ep0_handle_setup (&vhub->ep0); + } + +/* Various top level bus events */ +if (istat & (VHUB_IRQ_BUS_RESUME | +VHUB_IRQ_BUS_SUSPEND | +VHUB_IRQ_BUS_RESET )) { +if (istat &VHUB_IRQ_BUS_RESUME ) +ast_vhub_hub_resume (vhub); +if (istat &VHUB_IRQ_BUS_SUSPEND ) +ast_vhub_hub_suspend (vhub); +if (istat &VHUB_IRQ_BUS_RESET ) +ast_vhub_hub_reset (vhub); + } + + bail: +spin_unlock (&vhub->lock); +return iret; +} + +void ast_vhub_init_hw (struct ast_vhub *vhub) +{ +u32 ctrl; + +UDCDBG (vhub,"(Re)Starting HW ...\n" ); + +/* Enable PHY */
drivers/usb/gadget/udc/aspeed-vhub/core.c line 176
Quoted 10 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +10
-0+ ctrl =VHUB_CTRL_PHY_CLK | +VHUB_CTRL_PHY_RESET_DIS ; + +#if 0 /* +* This causes registers to become inaccessible during +* suspend. Need to figure out how to bring the controller +* back into life to issue a wakeup. +*/ + ctrl |=VHUB_CTRL_CLK_STOP_SUSPEND ; +#endif
no commented code.
Quoted 12 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +12
-0+/* +* Set some ISO & split control bits according to Aspeed +* recommendation +* +* VHUB_CTRL_ISO_RSP_CTRL: When set tells the HW to respond +* with 0 bytes data packet to ISO IN endpoints when no data +* is available. +* +* VHUB_CTRL_SPLIT_IN: This makes a SOF complete a split IN +* transaction. +*/ + ctrl |=VHUB_CTRL_ISO_RSP_CTRL |VHUB_CTRL_SPLIT_IN ;
drivers/usb/gadget/udc/aspeed-vhub/core.c line 198
Quoted 10 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +10
-0+writel (ctrl, vhub->regs +AST_VHUB_CTRL ); +udelay (1 ); + +/* Set descriptor ring size */ +#if AST_VHUB_DESCS_COUNT ==256 + ctrl |=VHUB_CTRL_LONG_DESC ; +writel (ctrl, vhub->regs +AST_VHUB_CTRL ); +#elif AST_VHUB_DESCS_COUNT !=32 +#error Invalid AST_VHUB_DESCS_COUNT +#endif
find a better way for this. No ifdefs
drivers/usb/gadget/udc/aspeed-vhub/core.c line 201
Quoted 3 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +3
-0+ +/* Reset all devices */ +writel (0x33f , vhub->regs +AST_VHUB_SW_REST );
no magic constants...
drivers/usb/gadget/udc/aspeed-vhub/core.c line 209
Quoted 8 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +8
-0+udelay (1 ); +writel (0 , vhub->regs +AST_VHUB_SW_REST ); + +/* Disable and cleanup EP ACK/NACK interrupts */ +writel (0 , vhub->regs +AST_VHUB_EP_ACK_IER ); +writel (0 , vhub->regs +AST_VHUB_EP_NACK_IER ); +writel (0xffffffff , vhub->regs +AST_VHUB_EP_ACK_ISR ); +writel (0xffffffff , vhub->regs +AST_VHUB_EP_NACK_ISR );
... of any kind
Quoted 109 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +109
-0+/* Default settings for EP0, enable HW hub EP1 */ +writel (0 , vhub->regs +AST_VHUB_EP0_CTRL ); +writel (VHUB_EP1_CTRL_RESET_TOGGLE | +VHUB_EP1_CTRL_ENABLE , + vhub->regs +AST_VHUB_EP1_CTRL ); +writel (0 , vhub->regs +AST_VHUB_EP1_STS_CHG ); + +/* Configure EP0 DMA buffer */ +writel (vhub->ep0.buf_dma, vhub->regs +AST_VHUB_EP0_DATA ); + +/* Clear address */ +writel (0 , vhub->regs +AST_VHUB_CONF ); + +/* Pullup hub (activate on host) */ +if (vhub->force_usb1) + ctrl |=VHUB_CTRL_FULL_SPEED_ONLY ; + ctrl |=VHUB_CTRL_UPSTREAM_CONNECT ; +writel (ctrl, vhub->regs +AST_VHUB_CTRL ); + +/* Enable some interrupts */ +writel (VHUB_IRQ_HUB_EP0_IN_ACK_STALL | +VHUB_IRQ_HUB_EP0_OUT_ACK_STALL | +VHUB_IRQ_HUB_EP0_SETUP | +VHUB_IRQ_EP_POOL_ACK_STALL | +VHUB_IRQ_BUS_RESUME | +VHUB_IRQ_BUS_SUSPEND | +VHUB_IRQ_BUS_RESET , + vhub->regs +AST_VHUB_IER ); +} + +static int ast_vhub_remove (struct platform_device *pdev) +{ +struct ast_vhub *vhub =platform_get_drvdata (pdev); +unsigned long flags; +int i; + +if (!vhub || !vhub->regs) +return 0 ; + +/* Remove devices */ +for (i =0 ; i <AST_VHUB_NUM_PORTS ; i++) +ast_vhub_del_dev (&vhub->ports[i].dev); + +spin_lock_irqsave (&vhub->lock, flags); + +/* Mask & ack all interrupts */ +writel (0 , vhub->regs +AST_VHUB_IER ); +writel (VHUB_IRQ_ACK_ALL , vhub->regs +AST_VHUB_ISR ); + +/* Pull device, leave PHY enabled */ +writel (VHUB_CTRL_PHY_CLK | +VHUB_CTRL_PHY_RESET_DIS , + vhub->regs +AST_VHUB_CTRL ); + +if (vhub->clk) +clk_disable_unprepare (vhub->clk); + +spin_unlock_irqrestore (&vhub->lock, flags); + +if (vhub->ep0_bufs) +dma_free_coherent (&pdev->dev, +AST_VHUB_EP0_MAX_PACKET * + (AST_VHUB_NUM_PORTS +1 ), + vhub->ep0_bufs, + vhub->ep0_bufs_dma); + vhub->ep0_bufs =NULL ; + +return 0 ; +} + +static int ast_vhub_probe (struct platform_device *pdev) +{ +struct ast_vhub *vhub; +struct resource *res; +int i, rc =0 ; + + vhub =devm_kzalloc (&pdev->dev,sizeof (*vhub),GFP_KERNEL ); +if (!vhub) +return -ENOMEM ; + +spin_lock_init (&vhub->lock); + vhub->pdev = pdev; + vhub->force_usb1 = force_usb1; + vhub->no_dma_desc = no_dma_desc; + + res =platform_get_resource (pdev,IORESOURCE_MEM ,0 ); + vhub->regs =devm_ioremap_resource (&pdev->dev, res); +if (IS_ERR (vhub->regs)) { +dev_err (&pdev->dev,"Failed to map resources\n" ); +return PTR_ERR (vhub->regs); + } +UDCDBG (vhub,"vHub@%pR mapped @%p\n" , res, vhub->regs); + +platform_set_drvdata (pdev, vhub); + + vhub->clk =devm_clk_get (&pdev->dev,NULL ); +if (IS_ERR (vhub->clk)) { + rc =PTR_ERR (vhub->clk); + goto err; + } + rc =clk_prepare_enable (vhub->clk); +if (rc) { +dev_err (&pdev->dev,"Error couldn't enable clock (%d)\n" , rc); + goto err; + } + +/* Mask & ack all interrupts before installing the handler */ +writel (0 , vhub->regs +AST_VHUB_IER ); +writel (VHUB_IRQ_ACK_ALL , vhub->regs +AST_VHUB_ISR );
drivers/usb/gadget/udc/aspeed-vhub/core.c line 328
Quoted 10 lines
drivers/usb/gadget/udc/aspeed-vhub/core.c new file +10
-0+ +/* Find interrupt and install handler */ + vhub->irq =platform_get_irq (pdev,0 ); +if (vhub->irq <0 ) { +dev_err (&pdev->dev,"Failed to get interrupt\n" ); + rc = vhub->irq; + goto err; + } + rc =devm_request_irq (&pdev->dev, vhub->irq, ast_vhub_irq,0 , +KBUILD_MODNAME , vhub);
we usually rely on the device name, but okay.