diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/Kconfig | 1 | ||||
-rw-r--r-- | drivers/usb/core/driver.c | 5 | ||||
-rw-r--r-- | drivers/usb/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/host/alchemy-common.c | 337 | ||||
-rw-r--r-- | drivers/usb/host/ehci-au1xxx.c | 77 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/ohci-at91.c | 239 | ||||
-rw-r--r-- | drivers/usb/host/ohci-au1xxx.c | 110 |
8 files changed, 585 insertions, 187 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 4ac2750491d..791f11bed60 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -62,7 +62,6 @@ config USB_ARCH_HAS_EHCI boolean default y if FSL_SOC default y if PPC_MPC512x - default y if SOC_AU1200 default y if ARCH_IXP4XX default y if ARCH_W90X900 default y if ARCH_AT91SAM9G45 diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 2de2803f653..45887a0ff87 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1668,6 +1668,11 @@ int usb_runtime_suspend(struct device *dev) return -EAGAIN; status = usb_suspend_both(udev, PMSG_AUTO_SUSPEND); + + /* Allow a retry if autosuspend failed temporarily */ + if (status == -EAGAIN || status == -EBUSY) + usb_mark_last_busy(udev); + /* The PM core reacts badly unless the return code is 0, * -EAGAIN, or -EBUSY, so always return -EBUSY on an error. */ diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index ed48a5d79e1..7ca290fcb07 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -36,3 +36,4 @@ obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o obj-$(CONFIG_USB_OCTEON2_COMMON) += octeon2-common.o +obj-$(CONFIG_MIPS_ALCHEMY) += alchemy-common.o diff --git a/drivers/usb/host/alchemy-common.c b/drivers/usb/host/alchemy-common.c new file mode 100644 index 00000000000..b4192c964d0 --- /dev/null +++ b/drivers/usb/host/alchemy-common.c @@ -0,0 +1,337 @@ +/* + * USB block power/access management abstraction. + * + * Au1000+: The OHCI block control register is at the far end of the OHCI memory + * area. Au1550 has OHCI on different base address. No need to handle + * UDC here. + * Au1200: one register to control access and clocks to O/EHCI, UDC and OTG + * as well as the PHY for EHCI and UDC. + * + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/syscore_ops.h> +#include <asm/mach-au1x00/au1000.h> + +/* control register offsets */ +#define AU1000_OHCICFG 0x7fffc +#define AU1550_OHCICFG 0x07ffc +#define AU1200_USBCFG 0x04 + +/* Au1000 USB block config bits */ +#define USBHEN_RD (1 << 4) /* OHCI reset-done indicator */ +#define USBHEN_CE (1 << 3) /* OHCI block clock enable */ +#define USBHEN_E (1 << 2) /* OHCI block enable */ +#define USBHEN_C (1 << 1) /* OHCI block coherency bit */ +#define USBHEN_BE (1 << 0) /* OHCI Big-Endian */ + +/* Au1200 USB config bits */ +#define USBCFG_PFEN (1 << 31) /* prefetch enable (undoc) */ +#define USBCFG_RDCOMB (1 << 30) /* read combining (undoc) */ +#define USBCFG_UNKNOWN (5 << 20) /* unknown, leave this way */ +#define USBCFG_SSD (1 << 23) /* serial short detect en */ +#define USBCFG_PPE (1 << 19) /* HS PHY PLL */ +#define USBCFG_UCE (1 << 18) /* UDC clock enable */ +#define USBCFG_ECE (1 << 17) /* EHCI clock enable */ +#define USBCFG_OCE (1 << 16) /* OHCI clock enable */ +#define USBCFG_FLA(x) (((x) & 0x3f) << 8) +#define USBCFG_UCAM (1 << 7) /* coherent access (undoc) */ +#define USBCFG_GME (1 << 6) /* OTG mem access */ +#define USBCFG_DBE (1 << 5) /* UDC busmaster enable */ +#define USBCFG_DME (1 << 4) /* UDC mem enable */ +#define USBCFG_EBE (1 << 3) /* EHCI busmaster enable */ +#define USBCFG_EME (1 << 2) /* EHCI mem enable */ +#define USBCFG_OBE (1 << 1) /* OHCI busmaster enable */ +#define USBCFG_OME (1 << 0) /* OHCI mem enable */ +#define USBCFG_INIT_AU1200 (USBCFG_PFEN | USBCFG_RDCOMB | USBCFG_UNKNOWN |\ + USBCFG_SSD | USBCFG_FLA(0x20) | USBCFG_UCAM | \ + USBCFG_GME | USBCFG_DBE | USBCFG_DME | \ + USBCFG_EBE | USBCFG_EME | USBCFG_OBE | \ + USBCFG_OME) + + +static DEFINE_SPINLOCK(alchemy_usb_lock); + + +static inline void __au1200_ohci_control(void __iomem *base, int enable) +{ + unsigned long r = __raw_readl(base + AU1200_USBCFG); + if (enable) { + __raw_writel(r | USBCFG_OCE, base + AU1200_USBCFG); + wmb(); + udelay(2000); + } else { + __raw_writel(r & ~USBCFG_OCE, base + AU1200_USBCFG); + wmb(); + udelay(1000); + } +} + +static inline void __au1200_ehci_control(void __iomem *base, int enable) +{ + unsigned long r = __raw_readl(base + AU1200_USBCFG); + if (enable) { + __raw_writel(r | USBCFG_ECE | USBCFG_PPE, base + AU1200_USBCFG); + wmb(); + udelay(1000); + } else { + if (!(r & USBCFG_UCE)) /* UDC also off? */ + r &= ~USBCFG_PPE; /* yes: disable HS PHY PLL */ + __raw_writel(r & ~USBCFG_ECE, base + AU1200_USBCFG); + wmb(); + udelay(1000); + } +} + +static inline void __au1200_udc_control(void __iomem *base, int enable) +{ + unsigned long r = __raw_readl(base + AU1200_USBCFG); + if (enable) { + __raw_writel(r | USBCFG_UCE | USBCFG_PPE, base + AU1200_USBCFG); + wmb(); + } else { + if (!(r & USBCFG_ECE)) /* EHCI also off? */ + r &= ~USBCFG_PPE; /* yes: disable HS PHY PLL */ + __raw_writel(r & ~USBCFG_UCE, base + AU1200_USBCFG); + wmb(); + } +} + +static inline int au1200_coherency_bug(void) +{ +#if defined(CONFIG_DMA_COHERENT) + /* Au1200 AB USB does not support coherent memory */ + if (!(read_c0_prid() & 0xff)) { + printk(KERN_INFO "Au1200 USB: this is chip revision AB !!\n"); + printk(KERN_INFO "Au1200 USB: update your board or re-configure" + " the kernel\n"); + return -ENODEV; + } +#endif + return 0; +} + +static inline int au1200_usb_control(int block, int enable) +{ + void __iomem *base = + (void __iomem *)KSEG1ADDR(AU1200_USB_CTL_PHYS_ADDR); + int ret = 0; + + switch (block) { + case ALCHEMY_USB_OHCI0: + ret = au1200_coherency_bug(); + if (ret && enable) + goto out; + __au1200_ohci_control(base, enable); + break; + case ALCHEMY_USB_UDC0: + __au1200_udc_control(base, enable); + break; + case ALCHEMY_USB_EHCI0: + ret = au1200_coherency_bug(); + if (ret && enable) + goto out; + __au1200_ehci_control(base, enable); + break; + default: + ret = -ENODEV; + } +out: + return ret; +} + + +/* initialize USB block(s) to a known working state */ +static inline void au1200_usb_init(void) +{ + void __iomem *base = + (void __iomem *)KSEG1ADDR(AU1200_USB_CTL_PHYS_ADDR); + __raw_writel(USBCFG_INIT_AU1200, base + AU1200_USBCFG); + wmb(); + udelay(1000); +} + +static inline void au1000_usb_init(unsigned long rb, int reg) +{ + void __iomem *base = (void __iomem *)KSEG1ADDR(rb + reg); + unsigned long r = __raw_readl(base); + +#if defined(__BIG_ENDIAN) + r |= USBHEN_BE; +#endif + r |= USBHEN_C; + + __raw_writel(r, base); + wmb(); + udelay(1000); +} + + +static inline void __au1xx0_ohci_control(int enable, unsigned long rb, int creg) +{ + void __iomem *base = (void __iomem *)KSEG1ADDR(rb); + unsigned long r = __raw_readl(base + creg); + + if (enable) { + __raw_writel(r | USBHEN_CE, base + creg); + wmb(); + udelay(1000); + __raw_writel(r | USBHEN_CE | USBHEN_E, base + creg); + wmb(); + udelay(1000); + + /* wait for reset complete (read reg twice: au1500 erratum) */ + while (__raw_readl(base + creg), + !(__raw_readl(base + creg) & USBHEN_RD)) + udelay(1000); + } else { + __raw_writel(r & ~(USBHEN_CE | USBHEN_E), base + creg); + wmb(); + } +} + +static inline int au1000_usb_control(int block, int enable, unsigned long rb, + int creg) +{ + int ret = 0; + + switch (block) { + case ALCHEMY_USB_OHCI0: + __au1xx0_ohci_control(enable, rb, creg); + break; + default: + ret = -ENODEV; + } + return ret; +} + +/* + * alchemy_usb_control - control Alchemy on-chip USB blocks + * @block: USB block to target + * @enable: set 1 to enable a block, 0 to disable + */ +int alchemy_usb_control(int block, int enable) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&alchemy_usb_lock, flags); + switch (alchemy_get_cputype()) { + case ALCHEMY_CPU_AU1000: + case ALCHEMY_CPU_AU1500: + case ALCHEMY_CPU_AU1100: + ret = au1000_usb_control(block, enable, + AU1000_USB_OHCI_PHYS_ADDR, AU1000_OHCICFG); + break; + case ALCHEMY_CPU_AU1550: + ret = au1000_usb_control(block, enable, + AU1550_USB_OHCI_PHYS_ADDR, AU1550_OHCICFG); + break; + case ALCHEMY_CPU_AU1200: + ret = au1200_usb_control(block, enable); + break; + default: + ret = -ENODEV; + } + spin_unlock_irqrestore(&alchemy_usb_lock, flags); + return ret; +} +EXPORT_SYMBOL_GPL(alchemy_usb_control); + + +static unsigned long alchemy_usb_pmdata[2]; + +static void au1000_usb_pm(unsigned long br, int creg, int susp) +{ + void __iomem *base = (void __iomem *)KSEG1ADDR(br); + + if (susp) { + alchemy_usb_pmdata[0] = __raw_readl(base + creg); + /* There appears to be some undocumented reset register.... */ + __raw_writel(0, base + 0x04); + wmb(); + __raw_writel(0, base + creg); + wmb(); + } else { + __raw_writel(alchemy_usb_pmdata[0], base + creg); + wmb(); + } +} + +static void au1200_usb_pm(int susp) +{ + void __iomem *base = + (void __iomem *)KSEG1ADDR(AU1200_USB_OTG_PHYS_ADDR); + if (susp) { + /* save OTG_CAP/MUX registers which indicate port routing */ + /* FIXME: write an OTG driver to do that */ + alchemy_usb_pmdata[0] = __raw_readl(base + 0x00); + alchemy_usb_pmdata[1] = __raw_readl(base + 0x04); + } else { + /* restore access to all MMIO areas */ + au1200_usb_init(); + + /* restore OTG_CAP/MUX registers */ + __raw_writel(alchemy_usb_pmdata[0], base + 0x00); + __raw_writel(alchemy_usb_pmdata[1], base + 0x04); + wmb(); + } +} + +static void alchemy_usb_pm(int susp) +{ + switch (alchemy_get_cputype()) { + case ALCHEMY_CPU_AU1000: + case ALCHEMY_CPU_AU1500: + case ALCHEMY_CPU_AU1100: + au1000_usb_pm(AU1000_USB_OHCI_PHYS_ADDR, AU1000_OHCICFG, susp); + break; + case ALCHEMY_CPU_AU1550: + au1000_usb_pm(AU1550_USB_OHCI_PHYS_ADDR, AU1550_OHCICFG, susp); + break; + case ALCHEMY_CPU_AU1200: + au1200_usb_pm(susp); + break; + } +} + +static int alchemy_usb_suspend(void) +{ + alchemy_usb_pm(1); + return 0; +} + +static void alchemy_usb_resume(void) +{ + alchemy_usb_pm(0); +} + +static struct syscore_ops alchemy_usb_pm_ops = { + .suspend = alchemy_usb_suspend, + .resume = alchemy_usb_resume, +}; + +static int __init alchemy_usb_init(void) +{ + switch (alchemy_get_cputype()) { + case ALCHEMY_CPU_AU1000: + case ALCHEMY_CPU_AU1500: + case ALCHEMY_CPU_AU1100: + au1000_usb_init(AU1000_USB_OHCI_PHYS_ADDR, AU1000_OHCICFG); + break; + case ALCHEMY_CPU_AU1550: + au1000_usb_init(AU1550_USB_OHCI_PHYS_ADDR, AU1550_OHCICFG); + break; + case ALCHEMY_CPU_AU1200: + au1200_usb_init(); + break; + } + + register_syscore_ops(&alchemy_usb_pm_ops); + + return 0; +} +arch_initcall(alchemy_usb_init); diff --git a/drivers/usb/host/ehci-au1xxx.c b/drivers/usb/host/ehci-au1xxx.c index 65719e8d24e..18bafa99fe5 100644 --- a/drivers/usb/host/ehci-au1xxx.c +++ b/drivers/usb/host/ehci-au1xxx.c @@ -14,61 +14,9 @@ #include <linux/platform_device.h> #include <asm/mach-au1x00/au1000.h> -#define USB_HOST_CONFIG (USB_MSR_BASE + USB_MSR_MCFG) -#define USB_MCFG_PFEN (1<<31) -#define USB_MCFG_RDCOMB (1<<30) -#define USB_MCFG_SSDEN (1<<23) -#define USB_MCFG_PHYPLLEN (1<<19) -#define USB_MCFG_UCECLKEN (1<<18) -#define USB_MCFG_EHCCLKEN (1<<17) -#ifdef CONFIG_DMA_COHERENT -#define USB_MCFG_UCAM (1<<7) -#else -#define USB_MCFG_UCAM (0) -#endif -#define USB_MCFG_EBMEN (1<<3) -#define USB_MCFG_EMEMEN (1<<2) - -#define USBH_ENABLE_CE (USB_MCFG_PHYPLLEN | USB_MCFG_EHCCLKEN) -#define USBH_ENABLE_INIT (USB_MCFG_PFEN | USB_MCFG_RDCOMB | \ - USBH_ENABLE_CE | USB_MCFG_SSDEN | \ - USB_MCFG_UCAM | USB_MCFG_EBMEN | \ - USB_MCFG_EMEMEN) - -#define USBH_DISABLE (USB_MCFG_EBMEN | USB_MCFG_EMEMEN) extern int usb_disabled(void); -static void au1xxx_start_ehc(void) -{ - /* enable clock to EHCI block and HS PHY PLL*/ - au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_CE, USB_HOST_CONFIG); - au_sync(); - udelay(1000); - - /* enable EHCI mmio */ - au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG); - au_sync(); - udelay(1000); -} - -static void au1xxx_stop_ehc(void) -{ - unsigned long c; - - /* Disable mem */ - au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_DISABLE, USB_HOST_CONFIG); - au_sync(); - udelay(1000); - - /* Disable EHC clock. If the HS PHY is unused disable it too. */ - c = au_readl(USB_HOST_CONFIG) & ~USB_MCFG_EHCCLKEN; - if (!(c & USB_MCFG_UCECLKEN)) /* UDC disabled? */ - c &= ~USB_MCFG_PHYPLLEN; /* yes: disable HS PHY PLL */ - au_writel(c, USB_HOST_CONFIG); - au_sync(); -} - static int au1xxx_ehci_setup(struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -136,16 +84,6 @@ static int ehci_hcd_au1xxx_drv_probe(struct platform_device *pdev) if (usb_disabled()) return -ENODEV; -#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT) - /* Au1200 AB USB does not support coherent memory */ - if (!(read_c0_prid() & 0xff)) { - printk(KERN_INFO "%s: this is chip revision AB!\n", pdev->name); - printk(KERN_INFO "%s: update your board or re-configure" - " the kernel\n", pdev->name); - return -ENODEV; - } -#endif - if (pdev->resource[1].flags != IORESOURCE_IRQ) { pr_debug("resource[1] is not IORESOURCE_IRQ"); return -ENOMEM; @@ -171,7 +109,11 @@ static int ehci_hcd_au1xxx_drv_probe(struct platform_device *pdev) goto err2; } - au1xxx_start_ehc(); + if (alchemy_usb_control(ALCHEMY_USB_EHCI0, 1)) { + printk(KERN_INFO "%s: controller init failed!\n", pdev->name); + ret = -ENODEV; + goto err3; + } ehci = hcd_to_ehci(hcd); ehci->caps = hcd->regs; @@ -187,7 +129,8 @@ static int ehci_hcd_au1xxx_drv_probe(struct platform_device *pdev) return ret; } - au1xxx_stop_ehc(); + alchemy_usb_control(ALCHEMY_USB_EHCI0, 0); +err3: iounmap(hcd->regs); err2: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); @@ -201,10 +144,10 @@ static int ehci_hcd_au1xxx_drv_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); + alchemy_usb_control(ALCHEMY_USB_EHCI0, 0); iounmap(hcd->regs); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - au1xxx_stop_ehc(); platform_set_drvdata(pdev, NULL); return 0; @@ -236,7 +179,7 @@ static int ehci_hcd_au1xxx_drv_suspend(struct device *dev) // could save FLADJ in case of Vaux power loss // ... we'd only use it to handle clock skew - au1xxx_stop_ehc(); + alchemy_usb_control(ALCHEMY_USB_EHCI0, 0); return rc; } @@ -246,7 +189,7 @@ static int ehci_hcd_au1xxx_drv_resume(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); struct ehci_hcd *ehci = hcd_to_ehci(hcd); - au1xxx_start_ehc(); + alchemy_usb_control(ALCHEMY_USB_EHCI0, 1); // maybe restore FLADJ diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 59e81615e09..3ff9f82f726 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1224,7 +1224,7 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ehci_hcd_sh_driver #endif -#ifdef CONFIG_SOC_AU1200 +#ifdef CONFIG_MIPS_ALCHEMY #include "ehci-au1xxx.c" #define PLATFORM_DRIVER ehci_hcd_au1xxx_driver #endif diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 944291e10f9..ba3a46b78b7 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -35,8 +35,7 @@ extern int usb_disabled(void); static void at91_start_clock(void) { - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_enable(hclk); + clk_enable(hclk); clk_enable(iclk); clk_enable(fclk); clocked = 1; @@ -46,8 +45,7 @@ static void at91_stop_clock(void) { clk_disable(fclk); clk_disable(iclk); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_disable(hclk); + clk_disable(hclk); clocked = 0; } @@ -142,8 +140,7 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, iclk = clk_get(&pdev->dev, "ohci_clk"); fclk = clk_get(&pdev->dev, "uhpck"); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - hclk = clk_get(&pdev->dev, "hck0"); + hclk = clk_get(&pdev->dev, "hclk"); at91_start_hc(pdev); ohci_hcd_init(hcd_to_ohci(hcd)); @@ -155,8 +152,7 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, /* Error handling */ at91_stop_hc(pdev); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_put(hclk); + clk_put(hclk); clk_put(fclk); clk_put(iclk); @@ -192,8 +188,7 @@ static void usb_hcd_at91_remove(struct usb_hcd *hcd, release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_put(hclk); + clk_put(hclk); clk_put(fclk); clk_put(iclk); fclk = iclk = hclk = NULL; @@ -223,6 +218,156 @@ ohci_at91_start (struct usb_hcd *hcd) return 0; } +static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable) +{ + if (port < 0 || port >= 2) + return; + + gpio_set_value(pdata->vbus_pin[port], !pdata->vbus_pin_inverted ^ enable); +} + +static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port) +{ + if (port < 0 || port >= 2) + return -EINVAL; + + return gpio_get_value(pdata->vbus_pin[port]) ^ !pdata->vbus_pin_inverted; +} + +/* + * Update the status data from the hub with the over-current indicator change. + */ +static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct at91_usbh_data *pdata = hcd->self.controller->platform_data; + int length = ohci_hub_status_data(hcd, buf); + int port; + + for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) { + if (pdata->overcurrent_changed[port]) { + if (! length) + length = 1; + buf[0] |= 1 << (port + 1); + } + } + + return length; +} + +/* + * Look at the control requests to the root hub and see if we need to override. + */ +static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct at91_usbh_data *pdata = hcd->self.controller->platform_data; + struct usb_hub_descriptor *desc; + int ret = -EINVAL; + u32 *data = (u32 *)buf; + + dev_dbg(hcd->self.controller, + "ohci_at91_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n", + hcd, typeReq, wValue, wIndex, buf, wLength); + + switch (typeReq) { + case SetPortFeature: + if (wValue == USB_PORT_FEAT_POWER) { + dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n"); + ohci_at91_usb_set_power(pdata, wIndex - 1, 1); + goto out; + } + break; + + case ClearPortFeature: + switch (wValue) { + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(hcd->self.controller, + "ClearPortFeature: C_OVER_CURRENT\n"); + + if (wIndex == 1 || wIndex == 2) { + pdata->overcurrent_changed[wIndex-1] = 0; + pdata->overcurrent_status[wIndex-1] = 0; + } + + goto out; + + case USB_PORT_FEAT_OVER_CURRENT: + dev_dbg(hcd->self.controller, + "ClearPortFeature: OVER_CURRENT\n"); + + if (wIndex == 1 || wIndex == 2) { + pdata->overcurrent_status[wIndex-1] = 0; + } + + goto out; + + case USB_PORT_FEAT_POWER: + dev_dbg(hcd->self.controller, + "ClearPortFeature: POWER\n"); + + if (wIndex == 1 || wIndex == 2) { + ohci_at91_usb_set_power(pdata, wIndex - 1, 0); + return 0; + } + } + break; + } + + ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); + if (ret) + goto out; + + switch (typeReq) { + case GetHubDescriptor: + + /* update the hub's descriptor */ + + desc = (struct usb_hub_descriptor *)buf; + + dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n", + desc->wHubCharacteristics); + + /* remove the old configurations for power-switching, and + * over-current protection, and insert our new configuration + */ + + desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM); + desc->wHubCharacteristics |= cpu_to_le16(0x0001); + + if (pdata->overcurrent_supported) { + desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM); + desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001); + } + + dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n", + desc->wHubCharacteristics); + + return ret; + + case GetPortStatus: + /* check port status */ + + dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex); + + if (wIndex == 1 || wIndex == 2) { + if (! ohci_at91_usb_get_power(pdata, wIndex-1)) { + *data &= ~cpu_to_le32(RH_PS_PPS); + } + + if (pdata->overcurrent_changed[wIndex-1]) { + *data |= cpu_to_le32(RH_PS_OCIC); + } + + if (pdata->overcurrent_status[wIndex-1]) { + *data |= cpu_to_le32(RH_PS_POCI); + } + } + } + + out: + return ret; +} + /*-------------------------------------------------------------------------*/ static const struct hc_driver ohci_at91_hc_driver = { @@ -258,8 +403,8 @@ static const struct hc_driver ohci_at91_hc_driver = { /* * root hub support */ - .hub_status_data = ohci_hub_status_data, - .hub_control = ohci_hub_control, + .hub_status_data = ohci_at91_hub_status_data, + .hub_control = ohci_at91_hub_control, #ifdef CONFIG_PM .bus_suspend = ohci_bus_suspend, .bus_resume = ohci_bus_resume, @@ -269,22 +414,71 @@ static const struct hc_driver ohci_at91_hc_driver = { /*-------------------------------------------------------------------------*/ +static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct at91_usbh_data *pdata = pdev->dev.platform_data; + int val, gpio, port; + + /* From the GPIO notifying the over-current situation, find + * out the corresponding port */ + gpio = irq_to_gpio(irq); + for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) { + if (pdata->overcurrent_pin[port] == gpio) + break; + } + + if (port == ARRAY_SIZE(pdata->overcurrent_pin)) { + dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n"); + return IRQ_HANDLED; + } + + val = gpio_get_value(gpio); + + /* When notified of an over-current situation, disable power + on the corresponding port, and mark this port in + over-current. */ + if (! val) { + ohci_at91_usb_set_power(pdata, port, 0); + pdata->overcurrent_status[port] = 1; + pdata->overcurrent_changed[port] = 1; + } + + dev_dbg(& pdev->dev, "overcurrent situation %s\n", + val ? "exited" : "notified"); + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) { struct at91_usbh_data *pdata = pdev->dev.platform_data; int i; if (pdata) { - /* REVISIT make the driver support per-port power switching, - * and also overcurrent detection. Here we assume the ports - * are always powered while this driver is active, and use - * active-low power switches. - */ for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) { if (pdata->vbus_pin[i] <= 0) continue; gpio_request(pdata->vbus_pin[i], "ohci_vbus"); - gpio_direction_output(pdata->vbus_pin[i], 0); + ohci_at91_usb_set_power(pdata, i, 1); + } + + for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) { + int ret; + + if (pdata->overcurrent_pin[i] <= 0) + continue; + gpio_request(pdata->overcurrent_pin[i], "ohci_overcurrent"); + + ret = request_irq(gpio_to_irq(pdata->overcurrent_pin[i]), + ohci_hcd_at91_overcurrent_irq, + IRQF_SHARED, "ohci_overcurrent", pdev); + if (ret) { + gpio_free(pdata->overcurrent_pin[i]); + dev_warn(& pdev->dev, "cannot get GPIO IRQ for overcurrent\n"); + } } } @@ -301,9 +495,16 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) { if (pdata->vbus_pin[i] <= 0) continue; - gpio_direction_output(pdata->vbus_pin[i], 1); + ohci_at91_usb_set_power(pdata, i, 0); gpio_free(pdata->vbus_pin[i]); } + + for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) { + if (pdata->overcurrent_pin[i] <= 0) + continue; + free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev); + gpio_free(pdata->overcurrent_pin[i]); + } } device_init_wakeup(&pdev->dev, 0); diff --git a/drivers/usb/host/ohci-au1xxx.c b/drivers/usb/host/ohci-au1xxx.c index 6b7bc50dfea..9b66df8278f 100644 --- a/drivers/usb/host/ohci-au1xxx.c +++ b/drivers/usb/host/ohci-au1xxx.c @@ -23,92 +23,9 @@ #include <asm/mach-au1x00/au1000.h> -#ifndef CONFIG_SOC_AU1200 - -#define USBH_ENABLE_BE (1<<0) -#define USBH_ENABLE_C (1<<1) -#define USBH_ENABLE_E (1<<2) -#define USBH_ENABLE_CE (1<<3) -#define USBH_ENABLE_RD (1<<4) - -#ifdef __LITTLE_ENDIAN -#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C) -#elif defined(__BIG_ENDIAN) -#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C | \ - USBH_ENABLE_BE) -#else -#error not byte order defined -#endif - -#else /* Au1200 */ - -#define USB_HOST_CONFIG (USB_MSR_BASE + USB_MSR_MCFG) -#define USB_MCFG_PFEN (1<<31) -#define USB_MCFG_RDCOMB (1<<30) -#define USB_MCFG_SSDEN (1<<23) -#define USB_MCFG_OHCCLKEN (1<<16) -#ifdef CONFIG_DMA_COHERENT -#define USB_MCFG_UCAM (1<<7) -#else -#define USB_MCFG_UCAM (0) -#endif -#define USB_MCFG_OBMEN (1<<1) -#define USB_MCFG_OMEMEN (1<<0) - -#define USBH_ENABLE_CE USB_MCFG_OHCCLKEN - -#define USBH_ENABLE_INIT (USB_MCFG_PFEN | USB_MCFG_RDCOMB | \ - USBH_ENABLE_CE | USB_MCFG_SSDEN | \ - USB_MCFG_UCAM | \ - USB_MCFG_OBMEN | USB_MCFG_OMEMEN) - -#define USBH_DISABLE (USB_MCFG_OBMEN | USB_MCFG_OMEMEN) - -#endif /* Au1200 */ extern int usb_disabled(void); -static void au1xxx_start_ohc(void) -{ - /* enable host controller */ -#ifndef CONFIG_SOC_AU1200 - au_writel(USBH_ENABLE_CE, USB_HOST_CONFIG); - au_sync(); - udelay(1000); - - au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG); - au_sync(); - udelay(1000); - - /* wait for reset complete (read register twice; see au1500 errata) */ - while (au_readl(USB_HOST_CONFIG), - !(au_readl(USB_HOST_CONFIG) & USBH_ENABLE_RD)) - udelay(1000); - -#else /* Au1200 */ - au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_CE, USB_HOST_CONFIG); - au_sync(); - udelay(1000); - - au_writel(au_readl(USB_HOST_CONFIG) | USBH_ENABLE_INIT, USB_HOST_CONFIG); - au_sync(); - udelay(2000); -#endif /* Au1200 */ -} - -static void au1xxx_stop_ohc(void) -{ -#ifdef CONFIG_SOC_AU1200 - /* Disable mem */ - au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_DISABLE, USB_HOST_CONFIG); - au_sync(); - udelay(1000); -#endif - /* Disable clock */ - au_writel(au_readl(USB_HOST_CONFIG) & ~USBH_ENABLE_CE, USB_HOST_CONFIG); - au_sync(); -} - static int __devinit ohci_au1xxx_start(struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci(hcd); @@ -178,17 +95,6 @@ static int ohci_hcd_au1xxx_drv_probe(struct platform_device *pdev) if (usb_disabled()) return -ENODEV; -#if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT) - /* Au1200 AB USB does not support coherent memory */ - if (!(read_c0_prid() & 0xff)) { - printk(KERN_INFO "%s: this is chip revision AB !!\n", - pdev->name); - printk(KERN_INFO "%s: update your board or re-configure " - "the kernel\n", pdev->name); - return -ENODEV; - } -#endif - if (pdev->resource[1].flags != IORESOURCE_IRQ) { pr_debug("resource[1] is not IORESOURCE_IRQ\n"); return -ENOMEM; @@ -214,7 +120,12 @@ static int ohci_hcd_au1xxx_drv_probe(struct platform_device *pdev) goto err2; } - au1xxx_start_ohc(); + if (alchemy_usb_control(ALCHEMY_USB_OHCI0, 1)) { + printk(KERN_INFO "%s: controller init failed!\n", pdev->name); + ret = -ENODEV; + goto err3; + } + ohci_hcd_init(hcd_to_ohci(hcd)); ret = usb_add_hcd(hcd, pdev->resource[1].start, @@ -224,7 +135,8 @@ static int ohci_hcd_au1xxx_drv_probe(struct platform_device *pdev) return ret; } - au1xxx_stop_ohc(); + alchemy_usb_control(ALCHEMY_USB_OHCI0, 0); +err3: iounmap(hcd->regs); err2: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); @@ -238,7 +150,7 @@ static int ohci_hcd_au1xxx_drv_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); - au1xxx_stop_ohc(); + alchemy_usb_control(ALCHEMY_USB_OHCI0, 0); iounmap(hcd->regs); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); @@ -275,7 +187,7 @@ static int ohci_hcd_au1xxx_drv_suspend(struct device *dev) clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - au1xxx_stop_ohc(); + alchemy_usb_control(ALCHEMY_USB_OHCI0, 0); bail: spin_unlock_irqrestore(&ohci->lock, flags); @@ -286,7 +198,7 @@ static int ohci_hcd_au1xxx_drv_resume(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); - au1xxx_start_ohc(); + alchemy_usb_control(ALCHEMY_USB_OHCI0, 1); set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); ohci_finish_controller_resume(hcd); |