diff options
| author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-06-02 10:47:03 +0900 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-06-02 10:47:03 +0900 |
| commit | e152813ff5fea74094ae296367f477411df1ba24 (patch) | |
| tree | 6e8c38a3b83f0f07f75df50793332aa47a81c709 /drivers/usb | |
| parent | 74db22cb3a16dcd782a31236eb139f5865804ae6 (diff) | |
| parent | e18b7975c885bc3a938b9a76daf32957ea0235fa (diff) | |
| download | linux-e152813ff5fea74094ae296367f477411df1ba24.tar.gz linux-e152813ff5fea74094ae296367f477411df1ba24.tar.bz2 linux-e152813ff5fea74094ae296367f477411df1ba24.zip | |
Merge tag 'usb-for-v4.2' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next
Felipe writes:
usb: patches for v4.2 merge window
- dwc2 adds hibernation support
- preparation for sunxi glue to musb driver
- new ULPI bus
- new ULPI PHY driver for TUSB1210
- musb patches to support multiple DMA engines on same binary
- support for R-Car E2 on renesas_usbhs
Signed-off-by: Felipe Balbi <balbi@ti.com>
Diffstat (limited to 'drivers/usb')
65 files changed, 3397 insertions, 1265 deletions
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile index ca2f8bd0e431..6bbb3ec17018 100644 --- a/drivers/usb/common/Makefile +++ b/drivers/usb/common/Makefile @@ -7,3 +7,4 @@ usb-common-y += common.o usb-common-$(CONFIG_USB_LED_TRIG) += led.o obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o +obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c new file mode 100644 index 000000000000..0e6f968e93fe --- /dev/null +++ b/drivers/usb/common/ulpi.c @@ -0,0 +1,255 @@ +/** + * ulpi.c - USB ULPI PHY bus + * + * Copyright (C) 2015 Intel Corporation + * + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * + * 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. + */ + +#include <linux/ulpi/interface.h> +#include <linux/ulpi/driver.h> +#include <linux/ulpi/regs.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/acpi.h> + +/* -------------------------------------------------------------------------- */ + +int ulpi_read(struct ulpi *ulpi, u8 addr) +{ + return ulpi->ops->read(ulpi->ops, addr); +} +EXPORT_SYMBOL_GPL(ulpi_read); + +int ulpi_write(struct ulpi *ulpi, u8 addr, u8 val) +{ + return ulpi->ops->write(ulpi->ops, addr, val); +} +EXPORT_SYMBOL_GPL(ulpi_write); + +/* -------------------------------------------------------------------------- */ + +static int ulpi_match(struct device *dev, struct device_driver *driver) +{ + struct ulpi_driver *drv = to_ulpi_driver(driver); + struct ulpi *ulpi = to_ulpi_dev(dev); + const struct ulpi_device_id *id; + + for (id = drv->id_table; id->vendor; id++) + if (id->vendor == ulpi->id.vendor && + id->product == ulpi->id.product) + return 1; + + return 0; +} + +static int ulpi_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct ulpi *ulpi = to_ulpi_dev(dev); + + if (add_uevent_var(env, "MODALIAS=ulpi:v%04xp%04x", + ulpi->id.vendor, ulpi->id.product)) + return -ENOMEM; + return 0; +} + +static int ulpi_probe(struct device *dev) +{ + struct ulpi_driver *drv = to_ulpi_driver(dev->driver); + + return drv->probe(to_ulpi_dev(dev)); +} + +static int ulpi_remove(struct device *dev) +{ + struct ulpi_driver *drv = to_ulpi_driver(dev->driver); + + if (drv->remove) + drv->remove(to_ulpi_dev(dev)); + + return 0; +} + +static struct bus_type ulpi_bus = { + .name = "ulpi", + .match = ulpi_match, + .uevent = ulpi_uevent, + .probe = ulpi_probe, + .remove = ulpi_remove, +}; + +/* -------------------------------------------------------------------------- */ + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ulpi *ulpi = to_ulpi_dev(dev); + + return sprintf(buf, "ulpi:v%04xp%04x\n", + ulpi->id.vendor, ulpi->id.product); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *ulpi_dev_attrs[] = { + &dev_attr_modalias.attr, + NULL +}; + +static struct attribute_group ulpi_dev_attr_group = { + .attrs = ulpi_dev_attrs, +}; + +static const struct attribute_group *ulpi_dev_attr_groups[] = { + &ulpi_dev_attr_group, + NULL +}; + +static void ulpi_dev_release(struct device *dev) +{ + kfree(to_ulpi_dev(dev)); +} + +static struct device_type ulpi_dev_type = { + .name = "ulpi_device", + .groups = ulpi_dev_attr_groups, + .release = ulpi_dev_release, +}; + +/* -------------------------------------------------------------------------- */ + +/** + * ulpi_register_driver - register a driver with the ULPI bus + * @drv: driver being registered + * + * Registers a driver with the ULPI bus. + */ +int ulpi_register_driver(struct ulpi_driver *drv) +{ + if (!drv->probe) + return -EINVAL; + + drv->driver.bus = &ulpi_bus; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(ulpi_register_driver); + +/** + * ulpi_unregister_driver - unregister a driver with the ULPI bus + * @drv: driver to unregister + * + * Unregisters a driver with the ULPI bus. + */ +void ulpi_unregister_driver(struct ulpi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(ulpi_unregister_driver); + +/* -------------------------------------------------------------------------- */ + +static int ulpi_register(struct device *dev, struct ulpi *ulpi) +{ + int ret; + + /* Test the interface */ + ret = ulpi_write(ulpi, ULPI_SCRATCH, 0xaa); + if (ret < 0) + return ret; + + ret = ulpi_read(ulpi, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != 0xaa) + return -ENODEV; + + ulpi->id.vendor = ulpi_read(ulpi, ULPI_VENDOR_ID_LOW); + ulpi->id.vendor |= ulpi_read(ulpi, ULPI_VENDOR_ID_HIGH) << 8; + + ulpi->id.product = ulpi_read(ulpi, ULPI_PRODUCT_ID_LOW); + ulpi->id.product |= ulpi_read(ulpi, ULPI_PRODUCT_ID_HIGH) << 8; + + ulpi->dev.parent = dev; + ulpi->dev.bus = &ulpi_bus; + ulpi->dev.type = &ulpi_dev_type; + dev_set_name(&ulpi->dev, "%s.ulpi", dev_name(dev)); + + ACPI_COMPANION_SET(&ulpi->dev, ACPI_COMPANION(dev)); + + request_module("ulpi:v%04xp%04x", ulpi->id.vendor, ulpi->id.product); + + ret = device_register(&ulpi->dev); + if (ret) + return ret; + + dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n", + ulpi->id.vendor, ulpi->id.product); + + return 0; +} + +/** + * ulpi_register_interface - instantiate new ULPI device + * @dev: USB controller's device interface + * @ops: ULPI register access + * + * Allocates and registers a ULPI device and an interface for it. Called from + * the USB controller that provides the ULPI interface. + */ +struct ulpi *ulpi_register_interface(struct device *dev, struct ulpi_ops *ops) +{ + struct ulpi *ulpi; + int ret; + + ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) + return ERR_PTR(-ENOMEM); + + ulpi->ops = ops; + ops->dev = dev; + + ret = ulpi_register(dev, ulpi); + if (ret) { + kfree(ulpi); + return ERR_PTR(ret); + } + + return ulpi; +} +EXPORT_SYMBOL_GPL(ulpi_register_interface); + +/** + * ulpi_unregister_interface - unregister ULPI interface + * @intrf: struct ulpi_interface + * + * Unregisters a ULPI device and it's interface that was created with + * ulpi_create_interface(). + */ +void ulpi_unregister_interface(struct ulpi *ulpi) +{ + device_unregister(&ulpi->dev); +} +EXPORT_SYMBOL_GPL(ulpi_unregister_interface); + +/* -------------------------------------------------------------------------- */ + +static int __init ulpi_init(void) +{ + return bus_register(&ulpi_bus); +} +module_init(ulpi_init); + +static void __exit ulpi_exit(void) +{ + bus_unregister(&ulpi_bus); +} +module_exit(ulpi_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB ULPI PHY bus"); diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index cc0ced08bae2..a99c89e78126 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -84,3 +84,23 @@ config USB_OTG_FSM Implements OTG Finite State Machine as specified in On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification. +config USB_ULPI_BUS + tristate "USB ULPI PHY interface support" + depends on USB_SUPPORT + help + UTMI+ Low Pin Interface (ULPI) is specification for a commonly used + USB 2.0 PHY interface. The ULPI specification defines a standard set + of registers that can be used to detect the vendor and product which + allows ULPI to be handled as a bus. This module is the driver for that + bus. + + The ULPI interfaces (the buses) are registered by the drivers for USB + controllers which support ULPI register access and have ULPI PHY + attached to them. The ULPI PHY drivers themselves are normal PHY + drivers. + + ULPI PHYs provide often functions such as ADP sensing/probing (OTG + protocol) and USB charger detection. + + To compile this driver as a module, choose M here: the module will + be called ulpi. diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig index 1bcb36ae6505..fd95ba6ec317 100644 --- a/drivers/usb/dwc2/Kconfig +++ b/drivers/usb/dwc2/Kconfig @@ -50,18 +50,10 @@ config USB_DWC2_DUAL_ROLE option requires USB_GADGET to be enabled. endchoice -config USB_DWC2_PLATFORM - tristate "DWC2 Platform" - default USB_DWC2_HOST || USB_DWC2_PERIPHERAL - help - The Designware USB2.0 platform interface module for - controllers directly connected to the CPU. - config USB_DWC2_PCI tristate "DWC2 PCI" depends on PCI default n - select USB_DWC2_PLATFORM select NOP_USB_XCEIV help The Designware USB2.0 PCI interface module for controllers diff --git a/drivers/usb/dwc2/Makefile b/drivers/usb/dwc2/Makefile index f07b425eaff3..50fdaace1e73 100644 --- a/drivers/usb/dwc2/Makefile +++ b/drivers/usb/dwc2/Makefile @@ -2,7 +2,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG obj-$(CONFIG_USB_DWC2) += dwc2.o -dwc2-y := core.o core_intr.o +dwc2-y := core.o core_intr.o platform.o ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),) dwc2-y += hcd.o hcd_intr.o @@ -13,6 +13,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC2_PERIPHERAL) $(CONFIG_USB_DWC2_DUAL_ROLE)),) dwc2-y += gadget.o endif +ifneq ($(CONFIG_DEBUG_FS),) + dwc2-y += debugfs.o +endif + # NOTE: The previous s3c-hsotg peripheral mode only driver has been moved to # this location and renamed gadget.c. When building for dynamically linked # modules, dwc2.ko will get built for host mode, peripheral mode, and dual-role @@ -21,6 +25,3 @@ endif obj-$(CONFIG_USB_DWC2_PCI) += dwc2_pci.o dwc2_pci-y := pci.o - -obj-$(CONFIG_USB_DWC2_PLATFORM) += dwc2_platform.o -dwc2_platform-y := platform.o diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index d5197d492e21..e5b546f1152e 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -56,6 +56,389 @@ #include "core.h" #include "hcd.h" +#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +/** + * dwc2_backup_host_registers() - Backup controller host registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller + */ +static int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hregs_backup *hr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Backup Host regs */ + hr = hsotg->hr_backup; + if (!hr) { + hr = devm_kzalloc(hsotg->dev, sizeof(*hr), GFP_KERNEL); + if (!hr) { + dev_err(hsotg->dev, "%s: can't allocate host regs\n", + __func__); + return -ENOMEM; + } + + hsotg->hr_backup = hr; + } + hr->hcfg = readl(hsotg->regs + HCFG); + hr->haintmsk = readl(hsotg->regs + HAINTMSK); + for (i = 0; i < hsotg->core_params->host_channels; ++i) + hr->hcintmsk[i] = readl(hsotg->regs + HCINTMSK(i)); + + hr->hprt0 = readl(hsotg->regs + HPRT0); + hr->hfir = readl(hsotg->regs + HFIR); + + return 0; +} + +/** + * dwc2_restore_host_registers() - Restore controller host registers. + * When resuming usb bus, device registers needs to be restored + * if controller power were disabled. + * + * @hsotg: Programming view of the DWC_otg controller + */ +static int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hregs_backup *hr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Restore host regs */ + hr = hsotg->hr_backup; + if (!hr) { + dev_err(hsotg->dev, "%s: no host registers to restore\n", + __func__); + return -EINVAL; + } + + writel(hr->hcfg, hsotg->regs + HCFG); + writel(hr->haintmsk, hsotg->regs + HAINTMSK); + + for (i = 0; i < hsotg->core_params->host_channels; ++i) + writel(hr->hcintmsk[i], hsotg->regs + HCINTMSK(i)); + + writel(hr->hprt0, hsotg->regs + HPRT0); + writel(hr->hfir, hsotg->regs + HFIR); + + return 0; +} +#else +static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) +{ return 0; } + +static inline int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +#endif + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +/** + * dwc2_backup_device_registers() - Backup controller device registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller + */ +static int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_dregs_backup *dr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Backup dev regs */ + dr = hsotg->dr_backup; + if (!dr) { + dr = devm_kzalloc(hsotg->dev, sizeof(*dr), GFP_KERNEL); + if (!dr) { + dev_err(hsotg->dev, "%s: can't allocate device regs\n", + __func__); + return -ENOMEM; + } + + hsotg->dr_backup = dr; + } + + dr->dcfg = readl(hsotg->regs + DCFG); + dr->dctl = readl(hsotg->regs + DCTL); + dr->daintmsk = readl(hsotg->regs + DAINTMSK); + dr->diepmsk = readl(hsotg->regs + DIEPMSK); + dr->doepmsk = readl(hsotg->regs + DOEPMSK); + + for (i = 0; i < hsotg->num_of_eps; i++) { + /* Backup IN EPs */ + dr->diepctl[i] = readl(hsotg->regs + DIEPCTL(i)); + + /* Ensure DATA PID is correctly configured */ + if (dr->diepctl[i] & DXEPCTL_DPID) + dr->diepctl[i] |= DXEPCTL_SETD1PID; + else + dr->diepctl[i] |= DXEPCTL_SETD0PID; + + dr->dieptsiz[i] = readl(hsotg->regs + DIEPTSIZ(i)); + dr->diepdma[i] = readl(hsotg->regs + DIEPDMA(i)); + + /* Backup OUT EPs */ + dr->doepctl[i] = readl(hsotg->regs + DOEPCTL(i)); + + /* Ensure DATA PID is correctly configured */ + if (dr->doepctl[i] & DXEPCTL_DPID) + dr->doepctl[i] |= DXEPCTL_SETD1PID; + else + dr->doepctl[i] |= DXEPCTL_SETD0PID; + + dr->doeptsiz[i] = readl(hsotg->regs + DOEPTSIZ(i)); + dr->doepdma[i] = readl(hsotg->regs + DOEPDMA(i)); + } + + return 0; +} + +/** + * dwc2_restore_device_registers() - Restore controller device registers. + * When resuming usb bus, device registers needs to be restored + * if controller power were disabled. + * + * @hsotg: Programming view of the DWC_otg controller + */ +static int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_dregs_backup *dr; + u32 dctl; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Restore dev regs */ + dr = hsotg->dr_backup; + if (!dr) { + dev_err(hsotg->dev, "%s: no device registers to restore\n", + __func__); + return -EINVAL; + } + + writel(dr->dcfg, hsotg->regs + DCFG); + writel(dr->dctl, hsotg->regs + DCTL); + writel(dr->daintmsk, hsotg->regs + DAINTMSK); + writel(dr->diepmsk, hsotg->regs + DIEPMSK); + writel(dr->doepmsk, hsotg->regs + DOEPMSK); + + for (i = 0; i < hsotg->num_of_eps; i++) { + /* Restore IN EPs */ + writel(dr->diepctl[i], hsotg->regs + DIEPCTL(i)); + writel(dr->dieptsiz[i], hsotg->regs + DIEPTSIZ(i)); + writel(dr->diepdma[i], hsotg->regs + DIEPDMA(i)); + + /* Restore OUT EPs */ + writel(dr->doepctl[i], hsotg->regs + DOEPCTL(i)); + writel(dr->doeptsiz[i], hsotg->regs + DOEPTSIZ(i)); + writel(dr->doepdma[i], hsotg->regs + DOEPDMA(i)); + } + + /* Set the Power-On Programming done bit */ + dctl = readl(hsotg->regs + DCTL); + dctl |= DCTL_PWRONPRGDONE; + writel(dctl, hsotg->regs + DCTL); + + return 0; +} +#else +static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) +{ return 0; } + +static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +#endif + +/** + * dwc2_backup_global_registers() - Backup global controller registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller + */ +static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_gregs_backup *gr; + int i; + + /* Backup global regs */ + gr = hsotg->gr_backup; + if (!gr) { + gr = devm_kzalloc(hsotg->dev, sizeof(*gr), GFP_KERNEL); + if (!gr) { + dev_err(hsot |
