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/usb/chipidea/core.c | |
parent | e443b333629f82ca0da91a05ca638050943bbedd (diff) | |
download | talos-obmc-linux-5f36e231e9dbffb5264612e5b5817ab574a5e5db.tar.gz talos-obmc-linux-5f36e231e9dbffb5264612e5b5817ab574a5e5db.zip |
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/usb/chipidea/core.c')
-rw-r--r-- | drivers/usb/chipidea/core.c | 250 |
1 files changed, 198 insertions, 52 deletions
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"); |