diff options
author | Alexander Shishkin <alexander.shishkin@linux.intel.com> | 2012-05-11 17:25:47 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-05-11 16:52:10 -0700 |
commit | 5f36e231e9dbffb5264612e5b5817ab574a5e5db (patch) | |
tree | a71027cded532334d3d51cbf737925240d34e7df /drivers | |
parent | e443b333629f82ca0da91a05ca638050943bbedd (diff) |
usb: chipidea: add support for roles
Add some generic code for roles and implement simple role switching
based on ID pin state and/or a sysfs file. At this, we also rename
the device to ci_hdrc, which is what it is.
The "manual" switch is made into a sysfs file and not debugfs, because
it might be useful even in non-debug context. For some boards, like
sheevaplug, it seems to be the only way to switch roles without modifying
the hardware, since the ID pin is always grounded.
Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/chipidea/bits.h | 18 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci.h | 64 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci13xxx_msm.c | 4 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci13xxx_pci.c | 4 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 250 | ||||
-rw-r--r-- | drivers/usb/chipidea/udc.c | 80 | ||||
-rw-r--r-- | drivers/usb/chipidea/udc.h | 20 |
7 files changed, 325 insertions, 115 deletions
diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index 5fbff11cf220..62c35af1a5af 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -50,6 +50,24 @@ #define DEVLC_PSPD (0x03UL << 25) #define DEVLC_PSPD_HS (0x02UL << 25) +/* OTGSC */ +#define OTGSC_IDPU BIT(5) +#define OTGSC_ID BIT(8) +#define OTGSC_AVV BIT(9) +#define OTGSC_ASV BIT(10) +#define OTGSC_BSV BIT(11) +#define OTGSC_BSE BIT(12) +#define OTGSC_IDIS BIT(16) +#define OTGSC_AVVIS BIT(17) +#define OTGSC_ASVIS BIT(18) +#define OTGSC_BSVIS BIT(19) +#define OTGSC_BSEIS BIT(20) +#define OTGSC_IDIE BIT(24) +#define OTGSC_AVVIE BIT(25) +#define OTGSC_ASVIE BIT(26) +#define OTGSC_BSVIE BIT(27) +#define OTGSC_BSEIE BIT(28) + /* USBMODE */ #define USBMODE_CM (0x03UL << 0) #define USBMODE_CM_IDLE (0x00UL << 0) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index f5b3b8538a3b..56cb73b1e903 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -14,6 +14,7 @@ #define __DRIVERS_USB_CHIPIDEA_CI_H #include <linux/list.h> +#include <linux/irqreturn.h> #include <linux/usb/gadget.h> /****************************************************************************** @@ -47,6 +48,26 @@ struct ci13xxx_ep { struct dma_pool *td_pool; }; +enum ci_role { + CI_ROLE_HOST = 0, + CI_ROLE_GADGET, + CI_ROLE_END, +}; + +/** + * struct ci_role_driver - host/gadget role driver + * start: start this role + * stop: stop this role + * irq: irq handler for this role + * name: role name string (host/gadget) + */ +struct ci_role_driver { + int (*start)(struct ci13xxx *); + void (*stop)(struct ci13xxx *); + irqreturn_t (*irq)(struct ci13xxx *); + const char *name; +}; + struct hw_bank { unsigned lpm; /* is LPM? */ void __iomem *abs; /* bus map offset */ @@ -85,8 +106,47 @@ struct ci13xxx { struct ci13xxx_udc_driver *udc_driver; /* device controller driver */ int vbus_active; /* is VBUS active */ struct usb_phy *transceiver; /* Transceiver struct */ + struct ci_role_driver *roles[CI_ROLE_END]; + enum ci_role role; + bool is_otg; + struct work_struct work; + struct workqueue_struct *wq; }; +static inline struct ci_role_driver *ci_role(struct ci13xxx *ci) +{ + BUG_ON(ci->role >= CI_ROLE_END || !ci->roles[ci->role]); + return ci->roles[ci->role]; +} + +static inline int ci_role_start(struct ci13xxx *ci, enum ci_role role) +{ + int ret; + + if (role >= CI_ROLE_END) + return -EINVAL; + + if (!ci->roles[role]) + return -ENXIO; + + ret = ci->roles[role]->start(ci); + if (!ret) + ci->role = role; + return ret; +} + +static inline void ci_role_stop(struct ci13xxx *ci) +{ + enum ci_role role = ci->role; + + if (role == CI_ROLE_END) + return; + + ci->role = CI_ROLE_END; + + ci->roles[role]->stop(ci); +} + /****************************************************************************** * REGISTERS *****************************************************************************/ @@ -107,6 +167,7 @@ enum ci13xxx_regs { OP_ENDPTLISTADDR, OP_PORTSC, OP_DEVLC, + OP_OTGSC, OP_USBMODE, OP_ENDPTSETUPSTAT, OP_ENDPTPRIME, @@ -118,7 +179,6 @@ enum ci13xxx_regs { OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2, }; - /** * ffs_nr: find first (least significant) bit set * @x: the word to search @@ -193,8 +253,6 @@ static inline u32 hw_test_and_write(struct ci13xxx *udc, enum ci13xxx_regs reg, return (val & mask) >> ffs_nr(mask); } -int hw_device_init(struct ci13xxx *udc, void __iomem *base, - uintptr_t cap_offset); int hw_device_reset(struct ci13xxx *ci); int hw_port_test_set(struct ci13xxx *ci, u8 mode); diff --git a/drivers/usb/chipidea/ci13xxx_msm.c b/drivers/usb/chipidea/ci13xxx_msm.c index 27427931b681..9b09f0cd3d5a 100644 --- a/drivers/usb/chipidea/ci13xxx_msm.c +++ b/drivers/usb/chipidea/ci13xxx_msm.c @@ -62,9 +62,9 @@ static int ci13xxx_msm_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "ci13xxx_msm_probe\n"); - plat_ci = platform_device_alloc("ci_udc", -1); + plat_ci = platform_device_alloc("ci_hdrc", -1); if (!plat_ci) { - dev_err(&pdev->dev, "can't allocate ci_udc platform device\n"); + dev_err(&pdev->dev, "can't allocate ci_hdrc platform device\n"); return -ENOMEM; } diff --git a/drivers/usb/chipidea/ci13xxx_pci.c b/drivers/usb/chipidea/ci13xxx_pci.c index 84e8ab8d4f47..f190140cf612 100644 --- a/drivers/usb/chipidea/ci13xxx_pci.c +++ b/drivers/usb/chipidea/ci13xxx_pci.c @@ -69,9 +69,9 @@ static int __devinit ci13xxx_pci_probe(struct pci_dev *pdev, pci_set_master(pdev); pci_try_set_mwi(pdev); - plat_ci = platform_device_alloc("ci_udc", -1); + plat_ci = platform_device_alloc("ci_hdrc", -1); if (!plat_ci) { - dev_err(&pdev->dev, "can't allocate ci_udc platform device\n"); + dev_err(&pdev->dev, "can't allocate ci_hdrc platform device\n"); retval = -ENOMEM; goto disable_device; } diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index f6eab327ffea..2342f35c8071 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -75,7 +75,7 @@ /* MSM specific */ #define ABS_AHBBURST (0x0090UL) #define ABS_AHBMODE (0x0098UL) -/* UDC register map */ +/* Controller register map */ static uintptr_t ci_regs_nolpm[] = { [CAP_CAPLENGTH] = 0x000UL, [CAP_HCCPARAMS] = 0x008UL, @@ -88,6 +88,7 @@ static uintptr_t ci_regs_nolpm[] = { [OP_ENDPTLISTADDR] = 0x018UL, [OP_PORTSC] = 0x044UL, [OP_DEVLC] = 0x084UL, + [OP_OTGSC] = 0x064UL, [OP_USBMODE] = 0x068UL, [OP_ENDPTSETUPSTAT] = 0x06CUL, [OP_ENDPTPRIME] = 0x070UL, @@ -109,6 +110,7 @@ static uintptr_t ci_regs_lpm[] = { [OP_ENDPTLISTADDR] = 0x018UL, [OP_PORTSC] = 0x044UL, [OP_DEVLC] = 0x084UL, + [OP_OTGSC] = 0x0C4UL, [OP_USBMODE] = 0x0C8UL, [OP_ENDPTSETUPSTAT] = 0x0D8UL, [OP_ENDPTPRIME] = 0x0DCUL, @@ -118,24 +120,24 @@ static uintptr_t ci_regs_lpm[] = { [OP_ENDPTCTRL] = 0x0ECUL, }; -static int hw_alloc_regmap(struct ci13xxx *udc, bool is_lpm) +static int hw_alloc_regmap(struct ci13xxx *ci, bool is_lpm) { int i; - kfree(udc->hw_bank.regmap); + kfree(ci->hw_bank.regmap); - udc->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), - GFP_KERNEL); - if (!udc->hw_bank.regmap) + ci->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), + GFP_KERNEL); + if (!ci->hw_bank.regmap) return -ENOMEM; for (i = 0; i < OP_ENDPTCTRL; i++) - udc->hw_bank.regmap[i] = - (i <= CAP_LAST ? udc->hw_bank.cap : udc->hw_bank.op) + + ci->hw_bank.regmap[i] = + (i <= CAP_LAST ? ci->hw_bank.cap : ci->hw_bank.op) + (is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]); for (; i <= OP_LAST; i++) - udc->hw_bank.regmap[i] = udc->hw_bank.op + + ci->hw_bank.regmap[i] = ci->hw_bank.op + 4 * (i - OP_ENDPTCTRL) + (is_lpm ? ci_regs_lpm[OP_ENDPTCTRL] @@ -171,36 +173,35 @@ u8 hw_port_test_get(struct ci13xxx *ci) return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC); } -int hw_device_init(struct ci13xxx *udc, void __iomem *base, - uintptr_t cap_offset) +static int hw_device_init(struct ci13xxx *ci, void __iomem *base) { u32 reg; /* bank is a module variable */ - udc->hw_bank.abs = base; + ci->hw_bank.abs = base; - udc->hw_bank.cap = udc->hw_bank.abs; - udc->hw_bank.cap += cap_offset; - udc->hw_bank.op = udc->hw_bank.cap + ioread8(udc->hw_bank.cap); + ci->hw_bank.cap = ci->hw_bank.abs; + ci->hw_bank.cap += ci->udc_driver->capoffset; + ci->hw_bank.op = ci->hw_bank.cap + ioread8(ci->hw_bank.cap); - hw_alloc_regmap(udc, false); - reg = hw_read(udc, CAP_HCCPARAMS, HCCPARAMS_LEN) >> + hw_alloc_regmap(ci, false); + reg = hw_read(ci, CAP_HCCPARAMS, HCCPARAMS_LEN) >> ffs_nr(HCCPARAMS_LEN); - udc->hw_bank.lpm = reg; - hw_alloc_regmap(udc, !!reg); - udc->hw_bank.size = udc->hw_bank.op - udc->hw_bank.abs; - udc->hw_bank.size += OP_LAST; - udc->hw_bank.size /= sizeof(u32); + ci->hw_bank.lpm = reg; + hw_alloc_regmap(ci, !!reg); + ci->hw_bank.size = ci->hw_bank.op - ci->hw_bank.abs; + ci->hw_bank.size += OP_LAST; + ci->hw_bank.size /= sizeof(u32); - reg = hw_read(udc, CAP_DCCPARAMS, DCCPARAMS_DEN) >> + reg = hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DEN) >> ffs_nr(DCCPARAMS_DEN); - udc->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ + ci->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ - if (udc->hw_ep_max == 0 || udc->hw_ep_max > ENDPT_MAX) + if (ci->hw_ep_max == 0 || ci->hw_ep_max > ENDPT_MAX) return -ENODEV; - dev_dbg(udc->dev, "ChipIdea UDC found, lpm: %d; cap: %p op: %p\n", - udc->hw_bank.lpm, udc->hw_bank.cap, udc->hw_bank.op); + dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n", + ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op); /* setup lock mode ? */ @@ -250,16 +251,98 @@ int hw_device_reset(struct ci13xxx *ci) return 0; } -static int __devinit ci_udc_probe(struct platform_device *pdev) +/** + * ci_otg_role - pick role based on ID pin state + * @ci: the controller + */ +static enum ci_role ci_otg_role(struct ci13xxx *ci) +{ + u32 sts = hw_read(ci, OP_OTGSC, ~0); + enum ci_role role = sts & OTGSC_ID + ? CI_ROLE_GADGET + : CI_ROLE_HOST; + + return role; +} + +/** + * ci_role_work - perform role changing based on ID pin + * @work: work struct + */ +static void ci_role_work(struct work_struct *work) +{ + struct ci13xxx *ci = container_of(work, struct ci13xxx, work); + enum ci_role role = ci_otg_role(ci); + + hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS); + + if (role != ci->role) { + dev_dbg(ci->dev, "switching from %s to %s\n", + ci_role(ci)->name, ci->roles[role]->name); + + ci_role_stop(ci); + ci_role_start(ci, role); + } +} + +static ssize_t show_role(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *ci = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", ci_role(ci)->name); +} + +static ssize_t store_role(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *ci = dev_get_drvdata(dev); + enum ci_role role; + int ret; + + for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) + if (ci->roles[role] && !strcmp(buf, ci->roles[role]->name)) + break; + + if (role == CI_ROLE_END || role == ci->role) + return -EINVAL; + + ci_role_stop(ci); + ret = ci_role_start(ci, role); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR(role, S_IRUSR | S_IWUSR, show_role, store_role); + +static irqreturn_t ci_irq(int irq, void *data) +{ + struct ci13xxx *ci = data; + irqreturn_t ret = IRQ_NONE; + + if (ci->is_otg) { + u32 sts = hw_read(ci, OP_OTGSC, ~0); + + if (sts & OTGSC_IDIS) { + queue_work(ci->wq, &ci->work); + ret = IRQ_HANDLED; + } + } + + return ci->role == CI_ROLE_END ? ret : ci_role(ci)->irq(ci); +} + +static int __devinit ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct ci13xxx_udc_driver *driver = dev->platform_data; - struct ci13xxx *udc; + struct ci13xxx *ci; struct resource *res; void __iomem *base; int ret; - if (!driver) { + if (!dev->platform_data) { dev_err(dev, "platform data missing\n"); return -ENODEV; } @@ -276,49 +359,112 @@ static int __devinit ci_udc_probe(struct platform_device *pdev) return -ENOMEM; } - ret = udc_probe(driver, dev, base, &udc); - if (ret) - return ret; + ci = devm_kzalloc(dev, sizeof(*ci), GFP_KERNEL); + if (!ci) { + dev_err(dev, "can't allocate device\n"); + return -ENOMEM; + } + + ci->dev = dev; + ci->udc_driver = dev->platform_data; + + ret = hw_device_init(ci, base); + if (ret < 0) { + dev_err(dev, "can't initialize hardware\n"); + return -ENODEV; + } - udc->irq = platform_get_irq(pdev, 0); - if (udc->irq < 0) { + ci->irq = platform_get_irq(pdev, 0); + if (ci->irq < 0) { dev_err(dev, "missing IRQ\n"); + return -ENODEV; + } + + INIT_WORK(&ci->work, ci_role_work); + ci->wq = create_singlethread_workqueue("ci_otg"); + if (!ci->wq) { + dev_err(dev, "can't create workqueue\n"); + return -ENODEV; + } + + /* initialize role(s) before the interrupt is requested */ + ret = ci_hdrc_gadget_init(ci); + if (ret) + dev_info(dev, "doesn't support gadget\n"); + + if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) { + dev_err(dev, "no supported roles\n"); + ret = -ENODEV; + goto rm_wq; + } + + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { + ci->is_otg = true; + ci->role = ci_otg_role(ci); + } else { + ci->role = ci->roles[CI_ROLE_HOST] + ? CI_ROLE_HOST + : CI_ROLE_GADGET; + } + + ret = ci_role_start(ci, ci->role); + if (ret) { + dev_err(dev, "can't start %s role\n", ci_role(ci)->name); ret = -ENODEV; - goto out; + goto rm_wq; } - platform_set_drvdata(pdev, udc); - ret = request_irq(udc->irq, udc_irq, IRQF_SHARED, driver->name, udc); + platform_set_drvdata(pdev, ci); + ret = request_irq(ci->irq, ci_irq, IRQF_SHARED, ci->udc_driver->name, + ci); + if (ret) + goto stop; -out: + ret = device_create_file(dev, &dev_attr_role); if (ret) - udc_remove(udc); + goto rm_attr; + + if (ci->is_otg) + hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE); + + return ret; + +rm_attr: + device_remove_file(dev, &dev_attr_role); +stop: + ci_role_stop(ci); +rm_wq: + flush_workqueue(ci->wq); + destroy_workqueue(ci->wq); return ret; } -static int __devexit ci_udc_remove(struct platform_device *pdev) +static int __devexit ci_hdrc_remove(struct platform_device *pdev) { - struct ci13xxx *udc = platform_get_drvdata(pdev); + struct ci13xxx *ci = platform_get_drvdata(pdev); - free_irq(udc->irq, udc); - udc_remove(udc); + flush_workqueue(ci->wq); + destroy_workqueue(ci->wq); + device_remove_file(ci->dev, &dev_attr_role); + free_irq(ci->irq, ci); + ci_role_stop(ci); return 0; } -static struct platform_driver ci_udc_driver = { - .probe = ci_udc_probe, - .remove = __devexit_p(ci_udc_remove), +static struct platform_driver ci_hdrc_driver = { + .probe = ci_hdrc_probe, + .remove = __devexit_p(ci_hdrc_remove), .driver = { - .name = "ci_udc", + .name = "ci_hdrc", }, }; -module_platform_driver(ci_udc_driver); +module_platform_driver(ci_hdrc_driver); -MODULE_ALIAS("platform:ci_udc"); +MODULE_ALIAS("platform:ci_hdrc"); MODULE_ALIAS("platform:ci13xxx"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("David Lopo <dlopo@chipidea.mips.com>"); -MODULE_DESCRIPTION("ChipIdea UDC Driver"); +MODULE_DESCRIPTION("ChipIdea HDRC Driver"); diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 6866ef085397..9133a59450f4 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1592,14 +1592,13 @@ static int ci13xxx_stop(struct usb_gadget *gadget, * BUS block *****************************************************************************/ /** - * udc_irq: global interrupt handler + * udc_irq: udc interrupt handler * * This function returns IRQ_HANDLED if the IRQ has been handled * It locks access to registers */ -irqreturn_t udc_irq(int irq, void *data) +static irqreturn_t udc_irq(struct ci13xxx *udc) { - struct ci13xxx *udc = data; irqreturn_t retval; u32 intr; @@ -1666,38 +1665,24 @@ static void udc_release(struct device *dev) } /** - * udc_probe: parent probe must call this to initialize UDC - * @dev: parent device - * @regs: registers base address - * @name: driver name - * - * This function returns an error code - * No interrupts active, the IRQ has not been requested yet - * Kernel assumes 32-bit DMA operations by default, no need to dma_set_mask + * udc_start: initialize gadget role + * @udc: chipidea controller */ -int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, - void __iomem *regs, struct ci13xxx **_udc) +static int udc_start(struct ci13xxx *udc) { - struct ci13xxx *udc; + struct device *dev = udc->dev; int retval = 0; - if (dev == NULL || regs == NULL || driver == NULL || - driver->name == NULL) + if (!udc) return -EINVAL; - udc = kzalloc(sizeof(struct ci13xxx), GFP_KERNEL); - if (udc == NULL) - return -ENOMEM; - spin_lock_init(&udc->lock); - udc->regs = regs; - udc->udc_driver = driver; udc->gadget.ops = &usb_gadget_ops; udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.max_speed = USB_SPEED_HIGH; udc->gadget.is_otg = 0; - udc->gadget.name = driver->name; + udc->gadget.name = udc->udc_driver->name; INIT_LIST_HEAD(&udc->gadget.ep_list); @@ -1707,16 +1692,12 @@ int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, udc->gadget.dev.parent = dev; udc->gadget.dev.release = udc_release; - udc->dev = dev; - /* alloc resources */ udc->qh_pool = dma_pool_create("ci13xxx_qh", dev, sizeof(struct ci13xxx_qh), 64, CI13XXX_PAGE_SIZE); - if (udc->qh_pool == NULL) { - retval = -ENOMEM; - goto free_udc; - } + if (udc->qh_pool == NULL) + return -ENOMEM; udc->td_pool = dma_pool_create("ci13xxx_td", dev, sizeof(struct ci13xxx_td), @@ -1726,10 +1707,6 @@ int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, goto free_qh_pool; } - retval = hw_device_init(udc, regs, driver->capoffset); - if (retval < 0) - goto free_pools; - retval = init_eps(udc); if (retval) goto free_pools; @@ -1775,7 +1752,6 @@ int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, pm_runtime_no_callbacks(&udc->gadget.dev); pm_runtime_enable(&udc->gadget.dev); - *_udc = udc; return retval; remove_trans: @@ -1796,9 +1772,6 @@ free_pools: dma_pool_destroy(udc->td_pool); free_qh_pool: dma_pool_destroy(udc->qh_pool); -free_udc: - kfree(udc); - *_udc = NULL; return retval; } @@ -1807,7 +1780,7 @@ free_udc: * * No interrupts active, the IRQ has been released */ -void udc_remove(struct ci13xxx *udc) +static void udc_stop(struct ci13xxx *udc) { int i; @@ -1826,12 +1799,37 @@ void udc_remove(struct ci13xxx *udc) dma_pool_destroy(udc->qh_pool); if (udc->transceiver) { - otg_set_peripheral(udc->transceiver->otg, &udc->gadget); + otg_set_peripheral(udc->transceiver->otg, NULL); usb_put_transceiver(udc->transceiver); } dbg_remove_files(&udc->gadget.dev); device_unregister(&udc->gadget.dev); + /* my kobject is dynamic, I swear! */ + memset(&udc->gadget, 0, sizeof(udc->gadget)); +} + +/** + * ci_hdrc_gadget_init - initialize device related bits + * ci: the controller + * + * This function enables the gadget role, if the device is "device capable". + */ +int ci_hdrc_gadget_init(struct ci13xxx *ci) +{ + struct ci_role_driver *rdrv; + + if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC)) + return -ENXIO; + + rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = udc_start; + rdrv->stop = udc_stop; + rdrv->irq = udc_irq; + rdrv->name = "gadget"; + ci->roles[CI_ROLE_GADGET] = rdrv; - kfree(udc->hw_bank.regmap); - kfree(udc); + return 0; } diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h index 82c9e3e772f7..3a9e6694f327 100644 --- a/drivers/usb/chipidea/udc.h +++ b/drivers/usb/chipidea/udc.h @@ -71,26 +71,16 @@ struct ci13xxx_req { }; #ifdef CONFIG_USB_CHIPIDEA_UDC -irqreturn_t udc_irq(int irq, void *data); -int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, - void __iomem *regs, struct ci13xxx **_udc); -void udc_remove(struct ci13xxx *udc); + +int ci_hdrc_gadget_init(struct ci13xxx *ci); + #else -static inline irqreturn_t udc_irq(int irq, void *data) -{ - return IRQ_NONE; -} -static inline -int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, - void __iomem *regs, struct ci13xxx **_udc) +static inline int ci_hdrc_gadget_init(struct ci13xxx *ci) { - return -ENODEV; + return -ENXIO; } -static inline void udc_remove(struct ci13xxx *udc) -{ -} #endif #endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */ |