From e443b333629f82ca0da91a05ca638050943bbedd Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Fri, 11 May 2012 17:25:46 +0300 Subject: usb: chipidea: split the driver code into units Split the driver into the following parts: * core -- resources, register access, capabilities, etc; * udc -- device controller functionality; * debug -- logging events. Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 324 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 drivers/usb/chipidea/core.c (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c new file mode 100644 index 000000000000..f6eab327ffea --- /dev/null +++ b/drivers/usb/chipidea/core.c @@ -0,0 +1,324 @@ +/* + * core.c - ChipIdea USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * Description: ChipIdea USB IP core family device controller + * + * This driver is composed of several blocks: + * - HW: hardware interface + * - DBG: debug facilities (optional) + * - UTIL: utilities + * - ISR: interrupts handling + * - ENDPT: endpoint operations (Gadget API) + * - GADGET: gadget operations (Gadget API) + * - BUS: bus glue code, bus abstraction layer + * + * Compile Options + * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - STALL_IN: non-empty bulk-in pipes cannot be halted + * if defined mass storage compliance succeeds but with warnings + * => case 4: Hi > Dn + * => case 5: Hi > Di + * => case 8: Hi <> Do + * if undefined usbtest 13 fails + * - TRACE: enable function tracing (depends on DEBUG) + * + * Main Features + * - Chapter 9 & Mass Storage Compliance with Gadget File Storage + * - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined) + * - Normal & LPM support + * + * USBTEST Report + * - OK: 0-12, 13 (STALL_IN defined) & 14 + * - Not Supported: 15 & 16 (ISO) + * + * TODO List + * - OTG + * - Isochronous & Interrupt Traffic + * - Handle requests which spawns into several TDs + * - GET_STATUS(device) - always reports 0 + * - Gadget API (majority of optional features) + * - Suspend & Remote Wakeup + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "debug.h" + +/* MSM specific */ +#define ABS_AHBBURST (0x0090UL) +#define ABS_AHBMODE (0x0098UL) +/* UDC register map */ +static uintptr_t ci_regs_nolpm[] = { + [CAP_CAPLENGTH] = 0x000UL, + [CAP_HCCPARAMS] = 0x008UL, + [CAP_DCCPARAMS] = 0x024UL, + [CAP_TESTMODE] = 0x038UL, + [OP_USBCMD] = 0x000UL, + [OP_USBSTS] = 0x004UL, + [OP_USBINTR] = 0x008UL, + [OP_DEVICEADDR] = 0x014UL, + [OP_ENDPTLISTADDR] = 0x018UL, + [OP_PORTSC] = 0x044UL, + [OP_DEVLC] = 0x084UL, + [OP_USBMODE] = 0x068UL, + [OP_ENDPTSETUPSTAT] = 0x06CUL, + [OP_ENDPTPRIME] = 0x070UL, + [OP_ENDPTFLUSH] = 0x074UL, + [OP_ENDPTSTAT] = 0x078UL, + [OP_ENDPTCOMPLETE] = 0x07CUL, + [OP_ENDPTCTRL] = 0x080UL, +}; + +static uintptr_t ci_regs_lpm[] = { + [CAP_CAPLENGTH] = 0x000UL, + [CAP_HCCPARAMS] = 0x008UL, + [CAP_DCCPARAMS] = 0x024UL, + [CAP_TESTMODE] = 0x0FCUL, + [OP_USBCMD] = 0x000UL, + [OP_USBSTS] = 0x004UL, + [OP_USBINTR] = 0x008UL, + [OP_DEVICEADDR] = 0x014UL, + [OP_ENDPTLISTADDR] = 0x018UL, + [OP_PORTSC] = 0x044UL, + [OP_DEVLC] = 0x084UL, + [OP_USBMODE] = 0x0C8UL, + [OP_ENDPTSETUPSTAT] = 0x0D8UL, + [OP_ENDPTPRIME] = 0x0DCUL, + [OP_ENDPTFLUSH] = 0x0E0UL, + [OP_ENDPTSTAT] = 0x0E4UL, + [OP_ENDPTCOMPLETE] = 0x0E8UL, + [OP_ENDPTCTRL] = 0x0ECUL, +}; + +static int hw_alloc_regmap(struct ci13xxx *udc, bool is_lpm) +{ + int i; + + kfree(udc->hw_bank.regmap); + + udc->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), + GFP_KERNEL); + if (!udc->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) + + (is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]); + + for (; i <= OP_LAST; i++) + udc->hw_bank.regmap[i] = udc->hw_bank.op + + 4 * (i - OP_ENDPTCTRL) + + (is_lpm + ? ci_regs_lpm[OP_ENDPTCTRL] + : ci_regs_nolpm[OP_ENDPTCTRL]); + + return 0; +} + +/** + * hw_port_test_set: writes port test mode (execute without interruption) + * @mode: new value + * + * This function returns an error code + */ +int hw_port_test_set(struct ci13xxx *ci, u8 mode) +{ + const u8 TEST_MODE_MAX = 7; + + if (mode > TEST_MODE_MAX) + return -EINVAL; + + hw_write(ci, OP_PORTSC, PORTSC_PTC, mode << ffs_nr(PORTSC_PTC)); + return 0; +} + +/** + * hw_port_test_get: reads port test mode value + * + * This function returns port test mode value + */ +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) +{ + u32 reg; + + /* bank is a module variable */ + udc->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); + + hw_alloc_regmap(udc, false); + reg = hw_read(udc, 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); + + reg = hw_read(udc, CAP_DCCPARAMS, DCCPARAMS_DEN) >> + ffs_nr(DCCPARAMS_DEN); + udc->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ + + if (udc->hw_ep_max == 0 || udc->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); + + /* 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) + * @ci: the controller + * + * This function returns an error code + */ +int hw_device_reset(struct ci13xxx *ci) +{ + /* should flush & stop before reset */ + hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST); + while (hw_read(ci, OP_USBCMD, USBCMD_RST)) + udelay(10); /* not RTOS friendly */ + + + if (ci->udc_driver->notify_event) + ci->udc_driver->notify_event(ci, + CI13XXX_CONTROLLER_RESET_EVENT); + + if (ci->udc_driver->flags & CI13XXX_DISABLE_STREAMING) + hw_write(ci, OP_USBMODE, USBMODE_SDIS, USBMODE_SDIS); + + /* USBMODE should be configured step by step */ + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); + /* HW >= 2.3 */ + hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); + + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) { + pr_err("cannot enter in device mode"); + pr_err("lpm = %i", ci->hw_bank.lpm); + return -ENODEV; + } + + return 0; +} + +static int __devinit ci_udc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ci13xxx_udc_driver *driver = dev->platform_data; + struct ci13xxx *udc; + struct resource *res; + void __iomem *base; + int ret; + + if (!driver) { + dev_err(dev, "platform data missing\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing resource\n"); + return -ENODEV; + } + + base = devm_request_and_ioremap(dev, res); + if (!res) { + dev_err(dev, "can't request and ioremap resource\n"); + return -ENOMEM; + } + + ret = udc_probe(driver, dev, base, &udc); + if (ret) + return ret; + + udc->irq = platform_get_irq(pdev, 0); + if (udc->irq < 0) { + dev_err(dev, "missing IRQ\n"); + ret = -ENODEV; + goto out; + } + + platform_set_drvdata(pdev, udc); + ret = request_irq(udc->irq, udc_irq, IRQF_SHARED, driver->name, udc); + +out: + if (ret) + udc_remove(udc); + + return ret; +} + +static int __devexit ci_udc_remove(struct platform_device *pdev) +{ + struct ci13xxx *udc = platform_get_drvdata(pdev); + + free_irq(udc->irq, udc); + udc_remove(udc); + + return 0; +} + +static struct platform_driver ci_udc_driver = { + .probe = ci_udc_probe, + .remove = __devexit_p(ci_udc_remove), + .driver = { + .name = "ci_udc", + }, +}; + +module_platform_driver(ci_udc_driver); + +MODULE_ALIAS("platform:ci_udc"); +MODULE_ALIAS("platform:ci13xxx"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("David Lopo "); +MODULE_DESCRIPTION("ChipIdea UDC Driver"); -- cgit v1.2.3-70-g09d2 From 5f36e231e9dbffb5264612e5b5817ab574a5e5db Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Fri, 11 May 2012 17:25:47 +0300 Subject: 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 Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/bits.h | 18 +++ drivers/usb/chipidea/ci.h | 64 +++++++++- drivers/usb/chipidea/ci13xxx_msm.c | 4 +- drivers/usb/chipidea/ci13xxx_pci.c | 4 +- drivers/usb/chipidea/core.c | 250 +++++++++++++++++++++++++++++-------- drivers/usb/chipidea/udc.c | 80 ++++++------ drivers/usb/chipidea/udc.h | 20 +-- 7 files changed, 325 insertions(+), 115 deletions(-) (limited to 'drivers/usb/chipidea/core.c') 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 +#include #include /****************************************************************************** @@ -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 "); -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 */ -- cgit v1.2.3-70-g09d2 From f7daaa2d6e84f7be1e302d7bcba4f5f11567eddb Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Fri, 11 May 2012 17:25:52 +0300 Subject: usb: chipidea: drop unused msm register definitions These definitions are unused, and the same registers are also defined in . Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 2342f35c8071..52a1b45431d3 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -72,9 +72,6 @@ #include "bits.h" #include "debug.h" -/* MSM specific */ -#define ABS_AHBBURST (0x0090UL) -#define ABS_AHBMODE (0x0098UL) /* Controller register map */ static uintptr_t ci_regs_nolpm[] = { [CAP_CAPLENGTH] = 0x000UL, -- cgit v1.2.3-70-g09d2 From 758fc9860c19eceb56e5886a5225db623c521971 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Fri, 11 May 2012 17:25:53 +0300 Subject: usb: chipidea: use common definition for USBMODE bits Some of the bits of USBMODE register are defined in , use them instead of having our own definitions. Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/bits.h | 8 ++++---- drivers/usb/chipidea/core.c | 6 +++--- drivers/usb/chipidea/udc.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index 62c35af1a5af..44b3e254b6a5 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -13,6 +13,8 @@ #ifndef __DRIVERS_USB_CHIPIDEA_BITS_H #define __DRIVERS_USB_CHIPIDEA_BITS_H +#include + /* HCCPARAMS */ #define HCCPARAMS_LEN BIT(17) @@ -70,11 +72,9 @@ /* USBMODE */ #define USBMODE_CM (0x03UL << 0) -#define USBMODE_CM_IDLE (0x00UL << 0) -#define USBMODE_CM_DEVICE (0x02UL << 0) -#define USBMODE_CM_HOST (0x03UL << 0) +#define USBMODE_CM_DC (0x02UL << 0) #define USBMODE_SLOM BIT(3) -#define USBMODE_SDIS BIT(4) +#define USBMODE_CI_SDIS BIT(4) /* ENDPTCTRL */ #define ENDPTCTRL_RXS BIT(0) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 52a1b45431d3..3d48c9be6923 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -231,15 +231,15 @@ int hw_device_reset(struct ci13xxx *ci) CI13XXX_CONTROLLER_RESET_EVENT); if (ci->udc_driver->flags & CI13XXX_DISABLE_STREAMING) - hw_write(ci, OP_USBMODE, USBMODE_SDIS, USBMODE_SDIS); + hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); /* USBMODE should be configured step by step */ hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); - hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DC); /* HW >= 2.3 */ hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); - if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) { + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DC) { pr_err("cannot enter in device mode"); pr_err("lpm = %i", ci->hw_bank.lpm); return -ENODEV; diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index bdb034420fc6..290946d618d8 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1606,7 +1606,7 @@ static irqreturn_t udc_irq(struct ci13xxx *udc) if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) { if (hw_read(udc, OP_USBMODE, USBMODE_CM) != - USBMODE_CM_DEVICE) { + USBMODE_CM_DC) { spin_unlock(&udc->lock); return IRQ_NONE; } -- cgit v1.2.3-70-g09d2 From eb70e5ab8f95a81283623c03d2c99dfc59fcb319 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Fri, 11 May 2012 17:25:54 +0300 Subject: usb: chipidea: add host role This adds EHCI host support to the chipidea driver. We want it to be part of the hdrc driver and not a standalone (sub-)driver module, as the structure of ehci-hcd.c suggests, so for chipidea controller we hack it to not provide platform-related code, but only the ehci hcd. The ehci-platform driver won't work for us here too, because the controller uses the same registers for both device and host mode and also otg-related bits, so it's not really possible to put ehci registers into a separate resource. This is not a pretty solution, but the alternative is exporting symbols from the chipidea driver to a ehci-chipidea driver and doing all the module refcounting. Signed-off-by: Alexander Shishkin Cc: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/Kconfig | 6 ++ drivers/usb/chipidea/Makefile | 1 + drivers/usb/chipidea/bits.h | 1 + drivers/usb/chipidea/ci.h | 7 +- drivers/usb/chipidea/core.c | 15 ++-- drivers/usb/chipidea/host.c | 158 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/chipidea/host.h | 17 +++++ drivers/usb/chipidea/udc.c | 8 +-- drivers/usb/host/ehci-hcd.c | 8 +++ 9 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 drivers/usb/chipidea/host.c create mode 100644 drivers/usb/chipidea/host.h (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 553c1976a66e..fd36dc8b889b 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -18,6 +18,12 @@ config USB_CHIPIDEA_UDC Say Y here to enable device controller functionality of the ChipIdea driver. +config USB_CHIPIDEA_HOST + bool "ChipIdea host controller" + help + Say Y here to enable host controller functionality of the + ChipIdea driver. + config USB_CHIPIDEA_DEBUG bool "ChipIdea driver debug" help diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index a8279aac6a4a..cc3493769724 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o ci_hdrc-y := core.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o ifneq ($(CONFIG_PCI),) diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index 44b3e254b6a5..050de8562a04 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -21,6 +21,7 @@ /* DCCPARAMS */ #define DCCPARAMS_DEN (0x1F << 0) #define DCCPARAMS_DC BIT(7) +#define DCCPARAMS_HC BIT(8) /* TESTMODE */ #define TESTMODE_FORCE BIT(0) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 0ab83411d800..c605acc5568a 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -15,6 +15,7 @@ #include #include +#include #include /****************************************************************************** @@ -84,6 +85,7 @@ struct ci_role_driver { /** * struct hw_bank - hardware register mapping representation * @lpm: set if the device is LPM capable + * @phys: physical address of the controller's registers * @abs: absolute address of the beginning of register window * @cap: capability registers * @op: operational registers @@ -92,6 +94,7 @@ struct ci_role_driver { */ struct hw_bank { unsigned lpm; + resource_size_t phys; void __iomem *abs; void __iomem *cap; void __iomem *op; @@ -128,6 +131,7 @@ struct hw_bank { * @udc_driver: platform specific information supplied by parent device * @vbus_active: is VBUS active * @transceiver: pointer to USB PHY, if any + * @hcd: pointer to usb_hcd for ehci host driver */ struct ci13xxx { struct device *dev; @@ -160,6 +164,7 @@ struct ci13xxx { struct ci13xxx_udc_driver *udc_driver; int vbus_active; struct usb_phy *transceiver; + struct usb_hcd *hcd; }; static inline struct ci_role_driver *ci_role(struct ci13xxx *ci) @@ -302,7 +307,7 @@ static inline u32 hw_test_and_write(struct ci13xxx *udc, enum ci13xxx_regs reg, return (val & mask) >> ffs_nr(mask); } -int hw_device_reset(struct ci13xxx *ci); +int hw_device_reset(struct ci13xxx *ci, u32 mode); int hw_port_test_set(struct ci13xxx *ci, u8 mode); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 3d48c9be6923..f568b8e86cee 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -70,6 +70,7 @@ #include "ci.h" #include "udc.h" #include "bits.h" +#include "host.h" #include "debug.h" /* Controller register map */ @@ -215,7 +216,7 @@ static int hw_device_init(struct ci13xxx *ci, void __iomem *base) * * This function returns an error code */ -int hw_device_reset(struct ci13xxx *ci) +int hw_device_reset(struct ci13xxx *ci, u32 mode) { /* should flush & stop before reset */ hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); @@ -235,12 +236,12 @@ int hw_device_reset(struct ci13xxx *ci) /* USBMODE should be configured step by step */ hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); - hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DC); + hw_write(ci, OP_USBMODE, USBMODE_CM, mode); /* HW >= 2.3 */ hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); - if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DC) { - pr_err("cannot enter in device mode"); + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != mode) { + pr_err("cannot enter in %s mode", ci_role(ci)->name); pr_err("lpm = %i", ci->hw_bank.lpm); return -ENODEV; } @@ -371,6 +372,8 @@ static int __devinit ci_hdrc_probe(struct platform_device *pdev) return -ENODEV; } + ci->hw_bank.phys = res->start; + ci->irq = platform_get_irq(pdev, 0); if (ci->irq < 0) { dev_err(dev, "missing IRQ\n"); @@ -385,6 +388,10 @@ static int __devinit ci_hdrc_probe(struct platform_device *pdev) } /* initialize role(s) before the interrupt is requested */ + ret = ci_hdrc_host_init(ci); + if (ret) + dev_info(dev, "doesn't support host\n"); + ret = ci_hdrc_gadget_init(ci); if (ret) dev_info(dev, "doesn't support gadget\n"); diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c new file mode 100644 index 000000000000..8c8362c89a8c --- /dev/null +++ b/drivers/usb/chipidea/host.c @@ -0,0 +1,158 @@ +/* + * host.c - ChipIdea USB host controller driver + * + * Copyright (c) 2012 Intel Corporation + * + * Author: Alexander Shishkin + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#define CHIPIDEA_EHCI +#include "../host/ehci-hcd.c" + +#include "ci.h" +#include "bits.h" +#include "host.h" + +static int ci_ehci_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int ret; + + hcd->has_tt = 1; + + ret = ehci_setup(hcd); + if (ret) + return ret; + + ehci_port_power(ehci, 0); + + return ret; +} + +static const struct hc_driver ci_ehci_hc_driver = { + .description = "ehci_hcd", + .product_desc = "ChipIdea HDRC EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ci_ehci_setup, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static irqreturn_t host_irq(struct ci13xxx *ci) +{ + return usb_hcd_irq(ci->irq, ci->hcd); +} + +static int host_start(struct ci13xxx *ci) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + int ret; + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev)); + if (!hcd) + return -ENOMEM; + + dev_set_drvdata(ci->dev, ci); + hcd->rsrc_start = ci->hw_bank.phys; + hcd->rsrc_len = ci->hw_bank.size; + hcd->regs = ci->hw_bank.abs; + hcd->has_tt = 1; + + ehci = hcd_to_ehci(hcd); + ehci->caps = ci->hw_bank.cap; + ehci->has_hostpc = ci->hw_bank.lpm; + + ret = usb_add_hcd(hcd, 0, 0); + if (ret) + usb_remove_hcd(hcd); + else + ci->hcd = hcd; + + return ret; +} + +static void host_stop(struct ci13xxx *ci) +{ + struct usb_hcd *hcd = ci->hcd; + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +int ci_hdrc_host_init(struct ci13xxx *ci) +{ + struct ci_role_driver *rdrv; + + if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_HC)) + return -ENXIO; + + rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = host_start; + rdrv->stop = host_stop; + rdrv->irq = host_irq; + rdrv->name = "host"; + ci->roles[CI_ROLE_HOST] = rdrv; + + return 0; +} diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h new file mode 100644 index 000000000000..761fb1fd6d99 --- /dev/null +++ b/drivers/usb/chipidea/host.h @@ -0,0 +1,17 @@ +#ifndef __DRIVERS_USB_CHIPIDEA_HOST_H +#define __DRIVERS_USB_CHIPIDEA_HOST_H + +#ifdef CONFIG_USB_CHIPIDEA_HOST + +int ci_hdrc_host_init(struct ci13xxx *ci); + +#else + +static inline int ci_hdrc_host_init(struct ci13xxx *ci) +{ + return -ENXIO; +} + +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 290946d618d8..fb0a91158006 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1,5 +1,5 @@ /* - * udc.h - ChipIdea UDC driver + * udc.c - ChipIdea UDC driver * * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. * @@ -1396,7 +1396,7 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) if (gadget_ready) { if (is_active) { pm_runtime_get_sync(&_gadget->dev); - hw_device_reset(udc); + hw_device_reset(udc, USBMODE_CM_DC); hw_device_state(udc, udc->ep0out->qh.dma); } else { hw_device_state(udc, 0); @@ -1540,7 +1540,7 @@ static int ci13xxx_start(struct usb_gadget *gadget, 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); + hw_device_reset(udc, USBMODE_CM_DC); } else { pm_runtime_put_sync(&udc->gadget.dev); goto done; @@ -1720,7 +1720,7 @@ static int udc_start(struct ci13xxx *udc) } if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) { - retval = hw_device_reset(udc); + retval = hw_device_reset(udc, USBMODE_CM_DC); if (retval) goto put_transceiver; } diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index de1e689d3df0..5cb775b1802d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1246,6 +1246,13 @@ static int ehci_get_frame (struct usb_hcd *hcd) } /*-------------------------------------------------------------------------*/ +/* + * The EHCI in ChipIdea HDRC cannot be a separate module or device, + * because its registers (and irq) are shared between host/gadget/otg + * functions and in order to facilitate role switching we cannot + * give the ehci driver exclusive access to those. + */ +#ifndef CHIPIDEA_EHCI MODULE_DESCRIPTION(DRIVER_DESC); MODULE_AUTHOR (DRIVER_AUTHOR); @@ -1504,3 +1511,4 @@ static void __exit ehci_hcd_cleanup(void) } module_exit(ehci_hcd_cleanup); +#endif /* CHIPIDEA_EHCI */ -- cgit v1.2.3-70-g09d2 From 09c94e628ac3f871f06eaa7a5be266ca1aaa75a1 Mon Sep 17 00:00:00 2001 From: Richard Zhao Date: Tue, 15 May 2012 21:58:18 +0800 Subject: usb: chipidea: remove zero check of hw_ep_max It's 0 for host only device. Signed-off-by: Richard Zhao Cc: Marek Vasut Cc: Alan Stern Cc: Alexander Shishkin Cc: Felipe Balbi Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index f568b8e86cee..15e03b308f8a 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -195,7 +195,7 @@ static int hw_device_init(struct ci13xxx *ci, void __iomem *base) ffs_nr(DCCPARAMS_DEN); ci->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ - if (ci->hw_ep_max == 0 || ci->hw_ep_max > ENDPT_MAX) + if (ci->hw_ep_max > ENDPT_MAX) return -ENODEV; dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n", -- cgit v1.2.3-70-g09d2