From: Pavankumar Kondeti <pkondeti@xxxxxxxxxxxxxx> Introduces ci13xxx_udc_driver struct for bus glue drivers to hint ci13xxx_udc core about their special requirements. The flags include avoiding hardware register access when controller is not in peripheral mode, enabling pull-up upon VBUS, disabling streaming mode and dependency on transceiver driver. Initialize gadget_ops in udc_probe so that transceiver can notify VBUS presence even when no gadget driver is bounded. A notify_event callback is embedded in the same struct. This patch implements two events called CONTROLLER_RESET_EVENT and CONTROLLER_STOPPED_EVENT to notify the bus glue driver after resetting and stopping the controller for performing SoC specific quirks. Signed-off-by: Pavankumar Kondeti <pkondeti@xxxxxxxxxxxxxx> Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxx> --- drivers/usb/gadget/ci13xxx_pci.c | 6 +- drivers/usb/gadget/ci13xxx_udc.c | 205 ++++++++++++++++++++++++++++---------- drivers/usb/gadget/ci13xxx_udc.h | 19 ++++ 3 files changed, 175 insertions(+), 55 deletions(-) diff --git a/drivers/usb/gadget/ci13xxx_pci.c b/drivers/usb/gadget/ci13xxx_pci.c index 7a0f153..883ab5e 100644 --- a/drivers/usb/gadget/ci13xxx_pci.c +++ b/drivers/usb/gadget/ci13xxx_pci.c @@ -38,6 +38,10 @@ static irqreturn_t ci13xxx_pci_irq(int irq, void *pdev) return udc_irq(); } +static struct ci13xxx_udc_driver ci13xxx_pci_udc_driver = { + .name = UDC_DRIVER_NAME, +}; + /** * ci13xxx_pci_probe: PCI probe * @pdev: USB device controller being probed @@ -82,7 +86,7 @@ static int __devinit ci13xxx_pci_probe(struct pci_dev *pdev, pci_set_master(pdev); pci_try_set_mwi(pdev); - retval = udc_probe(&pdev->dev, regs, UDC_DRIVER_NAME); + retval = udc_probe(&ci13xxx_pci_udc_driver, &pdev->dev, regs); if (retval) goto iounmap; diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c index 956fa64..c10d1ae 100644 --- a/drivers/usb/gadget/ci13xxx_udc.c +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -62,6 +62,7 @@ #include <linux/slab.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> +#include <linux/usb/otg.h> #include "ci13xxx_udc.h" @@ -126,6 +127,9 @@ static struct { size_t size; /* bank size */ } hw_bank; +/* MSM specific */ +#define ABS_AHBBURST (0x0090UL) +#define ABS_AHBMODE (0x0098UL) /* UDC register map */ #define ABS_CAPLENGTH (0x100UL) #define ABS_HCCPARAMS (0x108UL) @@ -242,13 +246,7 @@ static u32 hw_ctest_and_write(u32 addr, u32 mask, u32 data) return (reg & mask) >> ffs_nr(mask); } -/** - * hw_device_reset: resets chip (execute without interruption) - * @base: register base address - * - * This function returns an error code - */ -static int hw_device_reset(void __iomem *base) +static int hw_device_init(void __iomem *base) { u32 reg; @@ -265,6 +263,28 @@ static int hw_device_reset(void __iomem *base) hw_bank.size += CAP_LAST; hw_bank.size /= sizeof(u32); + reg = hw_aread(ABS_DCCPARAMS, DCCPARAMS_DEN) >> ffs_nr(DCCPARAMS_DEN); + if (reg == 0 || reg > ENDPT_MAX) + return -ENODEV; + + hw_ep_max = reg; /* cache hw ENDPT_MAX */ + + /* setup lock mode ? */ + + /* ENDPTSETUPSTAT is '0' by default */ + + /* HCSPARAMS.bf.ppc SHOULD BE zero for device */ + + return 0; +} +/** + * hw_device_reset: resets chip (execute without interruption) + * @base: register base address + * + * This function returns an error code + */ +static int hw_device_reset(struct ci13xxx *udc) +{ /* should flush & stop before reset */ hw_cwrite(CAP_ENDPTFLUSH, ~0, ~0); hw_cwrite(CAP_USBCMD, USBCMD_RS, 0); @@ -273,6 +293,14 @@ static int hw_device_reset(void __iomem *base) while (hw_cread(CAP_USBCMD, USBCMD_RST)) udelay(10); /* not RTOS friendly */ + + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_RESET_EVENT); + + if (udc->udc_driver->flags && CI13XXX_DISABLE_STREAMING) + hw_cwrite(CAP_USBMODE, USBMODE_SDIS, USBMODE_SDIS); + /* USBMODE should be configured step by step */ hw_cwrite(CAP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); hw_cwrite(CAP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); @@ -284,18 +312,6 @@ static int hw_device_reset(void __iomem *base) return -ENODEV; } - reg = hw_aread(ABS_DCCPARAMS, DCCPARAMS_DEN) >> ffs_nr(DCCPARAMS_DEN); - if (reg == 0 || reg > ENDPT_MAX) - return -ENODEV; - - hw_ep_max = reg; /* cache hw ENDPT_MAX */ - - /* setup lock mode ? */ - - /* ENDPTSETUPSTAT is '0' by default */ - - /* HCSPARAMS.bf.ppc SHOULD BE zero for device */ - return 0; } @@ -1551,8 +1567,6 @@ __acquires(mEp->lock) * Caller must hold lock */ static int _gadget_stop_activity(struct usb_gadget *gadget) -__releases(udc->lock) -__acquires(udc->lock) { struct usb_ep *ep; struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); @@ -1564,8 +1578,6 @@ __acquires(udc->lock) if (gadget == NULL) return -EINVAL; - spin_unlock(udc->lock); - /* flush all endpoints */ gadget_for_each_ep(ep, gadget) { usb_ep_fifo_flush(ep); @@ -1585,8 +1597,6 @@ __acquires(udc->lock) mEp->status = NULL; } - spin_lock(udc->lock); - return 0; } @@ -1615,6 +1625,7 @@ __acquires(udc->lock) dbg_event(0xFF, "BUS RST", 0); + spin_unlock(udc->lock); retval = _gadget_stop_activity(&udc->gadget); if (retval) goto done; @@ -1623,7 +1634,6 @@ __acquires(udc->lock) if (retval) goto done; - spin_unlock(udc->lock); retval = usb_ep_enable(&mEp->ep, &ctrl_endpt_desc); if (!retval) { mEp->status = usb_ep_alloc_request(&mEp->ep, GFP_ATOMIC); @@ -2321,12 +2331,45 @@ static const struct usb_ep_ops usb_ep_ops = { /****************************************************************************** * GADGET block *****************************************************************************/ +static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int gadget_ready = 0; + + if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS)) + return -EOPNOTSUPP; + + spin_lock_irqsave(udc->lock, flags); + udc->vbus_active = is_active; + if (udc->driver) + gadget_ready = 1; + spin_unlock_irqrestore(udc->lock, flags); + + if (gadget_ready) { + if (is_active) { + hw_device_reset(udc); + hw_device_state(udc->ci13xxx_ep[0].qh[RX].dma); + } else { + hw_device_state(0); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(&udc->gadget); + } + } + + return 0; +} + /** * Device operations part of the API to the USB controller hardware, * which don't involve endpoints (or i/o) * Check "usb_gadget.h" for details */ -static const struct usb_gadget_ops usb_gadget_ops; +static const struct usb_gadget_ops usb_gadget_ops = { + .vbus_session = ci13xxx_vbus_session, +}; /** * usb_gadget_probe_driver: register a gadget driver @@ -2379,7 +2422,6 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, info("hw_ep_max = %d", hw_ep_max); udc->driver = driver; - udc->gadget.ops = NULL; udc->gadget.dev.driver = NULL; retval = 0; @@ -2420,7 +2462,6 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, /* bind gadget */ driver->driver.bus = NULL; - udc->gadget.ops = &usb_gadget_ops; udc->gadget.dev.driver = &driver->driver; spin_unlock_irqrestore(udc->lock, flags); @@ -2428,11 +2469,19 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, spin_lock_irqsave(udc->lock, flags); if (retval) { - udc->gadget.ops = NULL; udc->gadget.dev.driver = NULL; goto done; } + if (udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) { + if (udc->vbus_active) { + if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) + hw_device_reset(udc); + } else { + goto done; + } + } + retval = hw_device_state(udc->ci13xxx_ep[0].qh[RX].dma); done: @@ -2466,19 +2515,21 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) spin_lock_irqsave(udc->lock, flags); - hw_device_state(0); - - /* unbind gadget */ - if (udc->gadget.ops != NULL) { + if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) || + udc->vbus_active) { + hw_device_state(0); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_STOPPED_EVENT); _gadget_stop_activity(&udc->gadget); + } - spin_unlock_irqrestore(udc->lock, flags); - driver->unbind(&udc->gadget); /* MAY SLEEP */ - spin_lock_irqsave(udc->lock, flags); + /* unbind gadget */ + spin_unlock_irqrestore(udc->lock, flags); + driver->unbind(&udc->gadget); /* MAY SLEEP */ + spin_lock_irqsave(udc->lock, flags); - udc->gadget.ops = NULL; - udc->gadget.dev.driver = NULL; - } + udc->gadget.dev.driver = NULL; /* free resources */ for (i = 0; i < hw_ep_max; i++) { @@ -2535,6 +2586,14 @@ static irqreturn_t udc_irq(void) } spin_lock(udc->lock); + + if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) { + if (hw_cread(CAP_USBMODE, USBMODE_CM) != + USBMODE_CM_DEVICE) { + spin_unlock(udc->lock); + return IRQ_NONE; + } + } intr = hw_test_and_clear_intr_active(); if (intr) { isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intr; @@ -2593,14 +2652,16 @@ static void udc_release(struct device *dev) * No interrupts active, the IRQ has not been requested yet * Kernel assumes 32-bit DMA operations by default, no need to dma_set_mask */ -static int udc_probe(struct device *dev, void __iomem *regs, const char *name) +static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, + void __iomem *regs) { struct ci13xxx *udc; int retval = 0; trace("%p, %p, %p", dev, regs, name); - if (dev == NULL || regs == NULL || name == NULL) + if (dev == NULL || regs == NULL || driver == NULL || + driver->name == NULL) return -EINVAL; udc = kzalloc(sizeof(struct ci13xxx), GFP_KERNEL); @@ -2608,16 +2669,14 @@ static int udc_probe(struct device *dev, void __iomem *regs, const char *name) return -ENOMEM; udc->lock = &udc_lock; + udc->regs = regs; + udc->udc_driver = driver; - retval = hw_device_reset(regs); - if (retval) - goto done; - - udc->gadget.ops = NULL; + udc->gadget.ops = &usb_gadget_ops; udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.is_dualspeed = 1; udc->gadget.is_otg = 0; - udc->gadget.name = name; + udc->gadget.name = driver->name; INIT_LIST_HEAD(&udc->gadget.ep_list); udc->gadget.ep0 = NULL; @@ -2628,23 +2687,57 @@ static int udc_probe(struct device *dev, void __iomem *regs, const char *name) udc->gadget.dev.parent = dev; udc->gadget.dev.release = udc_release; + retval = hw_device_init(regs); + if (retval < 0) + goto free_udc; + + udc->transceiver = otg_get_transceiver(); + + if (udc->udc_driver->flags & CI13XXX_REQUIRE_TRANSCEIVER) { + if (udc->transceiver == NULL) { + retval = -ENODEV; + goto free_udc; + } + } + + if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) { + retval = hw_device_reset(udc); + if (retval) + goto put_transceiver; + } + retval = device_register(&udc->gadget.dev); - if (retval) - goto done; + if (retval) { + put_device(&udc->gadget.dev); + goto put_transceiver; + } #ifdef CONFIG_USB_GADGET_DEBUG_FILES retval = dbg_create_files(&udc->gadget.dev); #endif - if (retval) { - device_unregister(&udc->gadget.dev); - goto done; + if (retval) + goto unreg_device; + + if (udc->transceiver) { + retval = otg_set_peripheral(udc->transceiver, &udc->gadget); + if (retval) + goto remove_dbg; } _udc = udc; return retval; - done: err("error = %i", retval); +remove_dbg: +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + dbg_remove_files(&udc->gadget.dev); +#endif +unreg_device: + device_unregister(&udc->gadget.dev); +put_transceiver: + if (udc->transceiver) + otg_put_transceiver(udc->transceiver); +free_udc: kfree(udc); _udc = NULL; return retval; @@ -2664,6 +2757,10 @@ static void udc_remove(void) return; } + if (udc->transceiver) { + otg_set_peripheral(udc->transceiver, &udc->gadget); + otg_put_transceiver(udc->transceiver); + } #ifdef CONFIG_USB_GADGET_DEBUG_FILES dbg_remove_files(&udc->gadget.dev); #endif diff --git a/drivers/usb/gadget/ci13xxx_udc.h b/drivers/usb/gadget/ci13xxx_udc.h index 4026e9c..4fd1931 100644 --- a/drivers/usb/gadget/ci13xxx_udc.h +++ b/drivers/usb/gadget/ci13xxx_udc.h @@ -97,9 +97,24 @@ struct ci13xxx_ep { struct dma_pool *td_pool; }; +struct ci13xxx; +struct ci13xxx_udc_driver { + const char *name; + unsigned long flags; +#define CI13XXX_REGS_SHARED BIT(0) +#define CI13XXX_REQUIRE_TRANSCEIVER BIT(1) +#define CI13XXX_PULLUP_ON_VBUS BIT(2) +#define CI13XXX_DISABLE_STREAMING BIT(3) + +#define CI13XXX_CONTROLLER_RESET_EVENT 0 +#define CI13XXX_CONTROLLER_STOPPED_EVENT 1 + void (*notify_event) (struct ci13xxx *udc, unsigned event); +}; + /* CI13XXX UDC descriptor & global resources */ struct ci13xxx { spinlock_t *lock; /* ctrl register bank access */ + void __iomem *regs; /* registers address space */ struct dma_pool *qh_pool; /* DMA pool for queue heads */ struct dma_pool *td_pool; /* DMA pool for transfer descs */ @@ -108,6 +123,9 @@ struct ci13xxx { struct ci13xxx_ep ci13xxx_ep[ENDPT_MAX]; /* extended endpts */ struct usb_gadget_driver *driver; /* 3rd party gadget driver */ + struct ci13xxx_udc_driver *udc_driver; /* device controller driver */ + int vbus_active; /* is VBUS active */ + struct otg_transceiver *transceiver; /* Transceiver struct */ }; /****************************************************************************** @@ -157,6 +175,7 @@ struct ci13xxx { #define USBMODE_CM_DEVICE (0x02UL << 0) #define USBMODE_CM_HOST (0x03UL << 0) #define USBMODE_SLOM BIT(3) +#define USBMODE_SDIS BIT(4) /* ENDPTCTRL */ #define ENDPTCTRL_RXS BIT(0) -- 1.7.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html