diff options
Diffstat (limited to 'target/linux/sunxi/patches-3.12/153-add-sunxi-ehci.patch')
-rw-r--r-- | target/linux/sunxi/patches-3.12/153-add-sunxi-ehci.patch | 499 |
1 files changed, 0 insertions, 499 deletions
diff --git a/target/linux/sunxi/patches-3.12/153-add-sunxi-ehci.patch b/target/linux/sunxi/patches-3.12/153-add-sunxi-ehci.patch deleted file mode 100644 index 8a78c2d54b..0000000000 --- a/target/linux/sunxi/patches-3.12/153-add-sunxi-ehci.patch +++ /dev/null @@ -1,499 +0,0 @@ -From b84d49247eb062672a56ce15f1c08f792099de97 Mon Sep 17 00:00:00 2001 -From: arokux <arokux@gmail.com> -Date: Wed, 18 Sep 2013 21:45:03 +0200 -Subject: [PATCH] ARM: sunxi: usb: Add Allwinner sunXi EHCI driver - ---- - drivers/usb/host/Kconfig | 9 + - drivers/usb/host/Makefile | 1 + - drivers/usb/host/ehci-sunxi.c | 446 ++++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 456 insertions(+) - create mode 100644 drivers/usb/host/ehci-sunxi.c - -diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig -index b3f20d7..ecc2745 100644 ---- a/drivers/usb/host/Kconfig -+++ b/drivers/usb/host/Kconfig -@@ -274,6 +274,15 @@ config USB_OCTEON_EHCI - USB 2.0 device support. All CN6XXX based chips with USB are - supported. - -+config USB_SUNXI_EHCI -+ tristate "Allwinner sunXi EHCI support" -+ depends on ARCH_SUNXI -+ default n -+ help -+ Enable support for the Allwinner sunXi on-chip EHCI -+ controller. It is needed for high-speed (480Mbit/sec) -+ USB 2.0 device support. -+ - endif # USB_EHCI_HCD - - config USB_OXU210HP_HCD -diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile -index 50b0041..d2e2c8c 100644 ---- a/drivers/usb/host/Makefile -+++ b/drivers/usb/host/Makefile -@@ -38,6 +38,7 @@ obj-$(CONFIG_USB_EHCI_S5P) += ehci-s5p.o - obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o - obj-$(CONFIG_USB_EHCI_MSM) += ehci-msm.o - obj-$(CONFIG_USB_EHCI_TEGRA) += ehci-tegra.o -+obj-$(CONFIG_USB_SUNXI_EHCI) += ehci-sunxi.o - - obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o - obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o -diff --git a/drivers/usb/host/ehci-sunxi.c b/drivers/usb/host/ehci-sunxi.c -new file mode 100644 -index 0000000..e7e15cc ---- /dev/null -+++ b/drivers/usb/host/ehci-sunxi.c -@@ -0,0 +1,446 @@ -+/* -+ * Copyright (C) 2013 Roman Byshko -+ * -+ * Roman Byshko <rbyshko@gmail.com> -+ * -+ * Based on code from -+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com> -+ * -+ * This file is licensed under the terms of the GNU General Public -+ * License version 2. This program is licensed "as is" without any -+ * warranty of any kind, whether express or implied. -+ */ -+ -+#include <linux/bitops.h> -+#include <linux/clk.h> -+#include <linux/dma-mapping.h> -+#include <linux/io.h> -+#include <linux/irq.h> -+#include <linux/module.h> -+#include <linux/of.h> -+#include <linux/platform_device.h> -+#include <linux/regulator/consumer.h> -+#include <linux/reset.h> -+#include <linux/usb.h> -+#include <linux/usb/hcd.h> -+ -+#include "ehci.h" -+ -+#define DRV_DESC "Allwinner sunXi EHCI driver" -+#define DRV_NAME "sunxi-ehci" -+ -+#define SUNXI_USB_PASSBY_EN 1 -+ -+#define SUNXI_EHCI_AHB_ICHR8_EN BIT(10) -+#define SUNXI_EHCI_AHB_INCR4_BURST_EN BIT(9) -+#define SUNXI_EHCI_AHB_INCRX_ALIGN_EN BIT(8) -+#define SUNXI_EHCI_ULPI_BYPASS_EN BIT(0) -+ -+struct sunxi_ehci_hcd { -+ struct clk *phy_clk; -+ struct clk *ahb_ehci_clk; -+ struct reset_control *reset; -+ struct regulator *vbus_reg; -+ void __iomem *csr; -+ void __iomem *pmuirq; -+ int irq; -+ int id; -+}; -+ -+ -+static void usb_phy_write(struct sunxi_ehci_hcd *sunxi_ehci,u32 addr, u32 data, u32 len) -+{ -+ u32 j = 0; -+ u32 temp = 0; -+ u32 usbc_bit = 0; -+ void __iomem *dest = sunxi_ehci->csr; -+ -+ usbc_bit = BIT(sunxi_ehci->id << 1); -+ -+ for (j = 0; j < len; j++) { -+ temp = readl(dest); -+ -+ /* clear the address portion */ -+ temp &= ~(0xff << 8); -+ -+ /* set the address */ -+ temp |= ((addr + j) << 8); -+ writel(temp, dest); -+ -+ /* set the data bit and clear usbc bit*/ -+ temp = readb(dest); -+ if (data & 0x1) -+ temp |= BIT(7); -+ else -+ temp &= ~BIT(7); -+ temp &= ~usbc_bit; -+ writeb(temp, dest); -+ -+ /* flip usbc_bit */ -+ __set_bit(usbc_bit, dest); -+ __clear_bit(usbc_bit, dest); -+ -+ data >>= 1; -+ } -+} -+ -+/* FIXME: should this function be protected by a lock? -+ * ehci1 and ehci0 could call it concurrently with same csr. -+ */ -+static void sunxi_usb_phy_init(struct sunxi_ehci_hcd *sunxi_ehci) -+{ -+ /* The following comments are machine -+ * translated from Chinese, you have been warned! -+ */ -+ -+ /* adjust PHY's magnitude and rate */ -+ usb_phy_write(sunxi_ehci, 0x20, 0x14, 5); -+ -+ /* threshold adjustment disconnect */ -+ usb_phy_write(sunxi_ehci, 0x2a, 3, 2); -+ -+ return; -+} -+ -+static void sunxi_usb_passby(struct sunxi_ehci_hcd *sunxi_ehci, int enable) -+{ -+ unsigned long reg_value = 0; -+ unsigned long bits = 0; -+ static DEFINE_SPINLOCK(lock); -+ unsigned long flags = 0; -+ void __iomem *addr = sunxi_ehci->pmuirq; -+ -+ bits = SUNXI_EHCI_AHB_ICHR8_EN | -+ SUNXI_EHCI_AHB_INCR4_BURST_EN | -+ SUNXI_EHCI_AHB_INCRX_ALIGN_EN | -+ SUNXI_EHCI_ULPI_BYPASS_EN; -+ -+ spin_lock_irqsave(&lock, flags); -+ -+ reg_value = readl(addr); -+ -+ if (enable) -+ reg_value |= bits; -+ else -+ reg_value &= ~bits; -+ -+ writel(reg_value, addr); -+ -+ spin_unlock_irqrestore(&lock, flags); -+ -+ return; -+} -+ -+static void sunxi_ehci_disable(struct sunxi_ehci_hcd *sunxi_ehci) -+{ -+ regulator_disable(sunxi_ehci->vbus_reg); -+ -+ sunxi_usb_passby(sunxi_ehci, !SUNXI_USB_PASSBY_EN); -+ -+ clk_disable_unprepare(sunxi_ehci->ahb_ehci_clk); -+ clk_disable_unprepare(sunxi_ehci->phy_clk); -+ -+ reset_control_assert(sunxi_ehci->reset); -+} -+ -+static int sunxi_ehci_enable(struct sunxi_ehci_hcd *sunxi_ehci) -+{ -+ int ret; -+ -+ ret = clk_prepare_enable(sunxi_ehci->phy_clk); -+ if (ret) -+ return ret; -+ -+ ret = reset_control_deassert(sunxi_ehci->reset); -+ if (ret) -+ goto fail1; -+ -+ ret = clk_prepare_enable(sunxi_ehci->ahb_ehci_clk); -+ if (ret) -+ goto fail2; -+ -+ sunxi_usb_phy_init(sunxi_ehci); -+ -+ sunxi_usb_passby(sunxi_ehci, SUNXI_USB_PASSBY_EN); -+ -+ ret = regulator_enable(sunxi_ehci->vbus_reg); -+ if (ret) -+ goto fail3; -+ -+ return 0; -+ -+fail3: -+ clk_disable_unprepare(sunxi_ehci->ahb_ehci_clk); -+fail2: -+ reset_control_assert(sunxi_ehci->reset); -+fail1: -+ clk_disable_unprepare(sunxi_ehci->phy_clk); -+ -+ return ret; -+} -+ -+#ifdef CONFIG_PM -+static int sunxi_ehci_suspend(struct device *dev) -+{ -+ struct sunxi_ehci_hcd *sunxi_ehci = NULL; -+ struct usb_hcd *hcd = dev_get_drvdata(dev); -+ int ret; -+ -+ bool do_wakeup = device_may_wakeup(dev); -+ -+ ret = ehci_suspend(hcd, do_wakeup); -+ -+ sunxi_ehci = (struct sunxi_ehci_hcd *)hcd_to_ehci(hcd)->priv; -+ -+ sunxi_ehci_disable(sunxi_ehci); -+ -+ return ret; -+} -+ -+static int sunxi_ehci_resume(struct device *dev) -+{ -+ struct sunxi_ehci_hcd *sunxi_ehci = NULL; -+ struct usb_hcd *hcd = dev_get_drvdata(dev); -+ int ret; -+ -+ sunxi_ehci = (struct sunxi_ehci_hcd *)hcd_to_ehci(hcd)->priv; -+ -+ ret = sunxi_ehci_enable(sunxi_ehci); -+ if (ret) -+ return ret; -+ -+ return ehci_resume(hcd, false); -+} -+ -+ -+static const struct dev_pm_ops sunxi_ehci_pmops = { -+ .suspend = sunxi_ehci_suspend, -+ .resume = sunxi_ehci_resume, -+}; -+ -+#define SUNXI_EHCI_PMOPS (&sunxi_ehci_pmops) -+#else /* !CONFIG_PM */ -+#define SUNXI_EHCI_PMOPS NULL -+#endif /* CONFIG_PM */ -+ -+static const struct ehci_driver_overrides sunxi_overrides __initconst = { -+ .reset = NULL, -+ .extra_priv_size = sizeof(struct sunxi_ehci_hcd), -+}; -+ -+/* FIXME: Should there be two instances of hc_driver, -+ * or one is enough to handle two EHCI controllers? */ -+static struct hc_driver __read_mostly sunxi_ehci_hc_driver; -+ -+static int sunxi_ehci_init(struct platform_device *pdev, struct usb_hcd *hcd, -+ struct sunxi_ehci_hcd *sunxi_ehci) -+{ -+ void __iomem *ehci_regs = NULL; -+ struct resource *res = NULL; -+ -+ sunxi_ehci->vbus_reg = devm_regulator_get(&pdev->dev, "vbus"); -+ if (IS_ERR(sunxi_ehci->vbus_reg)) { -+ if (PTR_ERR(sunxi_ehci->vbus_reg) == -EPROBE_DEFER) -+ return -EPROBE_DEFER; -+ -+ dev_info(&pdev->dev, "no USB VBUS power supply found\n"); -+ } -+ -+ sunxi_ehci->id = of_alias_get_id(pdev->dev.of_node, "ehci"); -+ if (sunxi_ehci->id < 0) -+ return sunxi_ehci->id; -+ -+ /* FIXME: should res be freed on some failure? */ -+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -+ if (!res) { -+ dev_err(&pdev->dev, "failed to get I/O memory\n"); -+ return -ENXIO; -+ } -+ ehci_regs = devm_ioremap_resource(&pdev->dev, res); -+ if (IS_ERR(ehci_regs)) -+ return PTR_ERR(ehci_regs); -+ -+ hcd->rsrc_start = res->start; -+ hcd->rsrc_len = resource_size(res); -+ hcd->regs = ehci_regs; -+ hcd_to_ehci(hcd)->caps = ehci_regs; -+ -+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); -+ if (!res) { -+ dev_err(&pdev->dev, "failed to get I/O memory\n"); -+ return -ENXIO; -+ } -+ sunxi_ehci->pmuirq = devm_ioremap_resource(&pdev->dev, res); -+ if (IS_ERR(sunxi_ehci->pmuirq)) -+ return PTR_ERR(sunxi_ehci->pmuirq); -+ -+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2); -+ if (!res) { -+ dev_err(&pdev->dev, "failed to get I/O memory\n"); -+ return -ENXIO; -+ } -+ -+ /* FIXME: this one byte needs to be shared between both EHCIs, -+ * that is why ioremap instead of devm_ioremap_resource, -+ * memory is not unmaped back for now. -+ */ -+ sunxi_ehci->csr = ioremap(res->start, resource_size(res)); -+ if (IS_ERR(sunxi_ehci->csr)) { -+ dev_err(&pdev->dev, "failed to remap memory\n"); -+ return PTR_ERR(sunxi_ehci->csr); -+ } -+ -+ sunxi_ehci->irq = platform_get_irq(pdev, 0); -+ if (!sunxi_ehci->irq) { -+ dev_err(&pdev->dev, "failed to get IRQ\n"); -+ return -ENODEV; -+ } -+ -+ sunxi_ehci->phy_clk = devm_clk_get(&pdev->dev, "usb_phy"); -+ if (IS_ERR(sunxi_ehci->phy_clk)) { -+ dev_err(&pdev->dev, "failed to get usb_phy clock\n"); -+ return PTR_ERR(sunxi_ehci->phy_clk); -+ } -+ sunxi_ehci->ahb_ehci_clk = devm_clk_get(&pdev->dev, "ahb_ehci"); -+ if (IS_ERR(sunxi_ehci->ahb_ehci_clk)) { -+ dev_err(&pdev->dev, "failed to get ahb_ehci clock\n"); -+ return PTR_ERR(sunxi_ehci->ahb_ehci_clk); -+ } -+ -+ sunxi_ehci->reset = reset_control_get(&pdev->dev, "ehci_reset"); -+ if (IS_ERR(sunxi_ehci->reset)) -+ { -+ dev_err(&pdev->dev, "failed to get ehci_reset reset line\n"); -+ return PTR_ERR(sunxi_ehci->reset); -+ } -+ -+ return 0; -+} -+ -+static int sunxi_ehci_probe(struct platform_device *pdev) -+{ -+ struct sunxi_ehci_hcd *sunxi_ehci = NULL; -+ struct usb_hcd *hcd = NULL; -+ int ret; -+ -+ if (pdev->num_resources != 4) { -+ dev_err(&pdev->dev, "invalid number of resources: %i\n", -+ pdev->num_resources); -+ return -ENODEV; -+ } -+ -+ if (pdev->resource[0].flags != IORESOURCE_MEM -+ || pdev->resource[1].flags != IORESOURCE_MEM -+ || pdev->resource[2].flags != IORESOURCE_MEM -+ || pdev->resource[3].flags != IORESOURCE_IRQ) { -+ dev_err(&pdev->dev, "invalid resource type\n"); -+ return -ENODEV; -+ } -+ -+ if (!pdev->dev.dma_mask) -+ pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; -+ if (!pdev->dev.coherent_dma_mask) -+ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); -+ -+ hcd = usb_create_hcd(&sunxi_ehci_hc_driver, &pdev->dev, -+ dev_name(&pdev->dev)); -+ if (!hcd) { -+ dev_err(&pdev->dev, "unable to create HCD\n"); -+ return -ENOMEM; -+ } -+ -+ platform_set_drvdata(pdev, hcd); -+ -+ sunxi_ehci = (struct sunxi_ehci_hcd *)hcd_to_ehci(hcd)->priv; -+ ret = sunxi_ehci_init(pdev, hcd, sunxi_ehci); -+ if (ret) -+ goto fail1; -+ -+ ret = sunxi_ehci_enable(sunxi_ehci); -+ if (ret) -+ goto fail1; -+ -+ ret = usb_add_hcd(hcd, sunxi_ehci->irq, IRQF_SHARED | IRQF_DISABLED); -+ if (ret) { -+ dev_err(&pdev->dev, "failed to add USB HCD\n"); -+ goto fail2; -+ } -+ -+ return 0; -+ -+fail2: -+ sunxi_ehci_disable(sunxi_ehci); -+ -+fail1: -+ usb_put_hcd(hcd); -+ return ret; -+} -+ -+static int sunxi_ehci_remove(struct platform_device *pdev) -+{ -+ struct usb_hcd *hcd = platform_get_drvdata(pdev); -+ struct sunxi_ehci_hcd *sunxi_ehci = NULL; -+ -+ sunxi_ehci = (struct sunxi_ehci_hcd *)hcd_to_ehci(hcd)->priv; -+ -+ usb_remove_hcd(hcd); -+ -+ sunxi_ehci_disable(sunxi_ehci); -+ -+ usb_put_hcd(hcd); -+ -+ return 0; -+} -+ -+static void sunxi_ehci_shutdown(struct platform_device *pdev) -+{ -+ struct usb_hcd *hcd = platform_get_drvdata(pdev); -+ struct sunxi_ehci_hcd *sunxi_ehci = NULL; -+ -+ sunxi_ehci = (struct sunxi_ehci_hcd *)hcd_to_ehci(hcd)->priv; -+ -+ usb_hcd_platform_shutdown(pdev); -+ -+ sunxi_ehci_disable(sunxi_ehci); -+} -+ -+static const struct of_device_id ehci_of_match[] = { -+ {.compatible = "allwinner,sunxi-ehci"}, -+ {}, -+}; -+ -+static struct platform_driver ehci_sunxi_driver = { -+ .driver = { -+ .of_match_table = ehci_of_match, -+ .name = DRV_NAME, -+ .pm = SUNXI_EHCI_PMOPS, -+ }, -+ .probe = sunxi_ehci_probe, -+ .remove = sunxi_ehci_remove, -+ .shutdown = sunxi_ehci_shutdown, -+}; -+ -+static int __init sunxi_ehci_init_module(void) -+{ -+ if (usb_disabled()) -+ return -ENODEV; -+ -+ pr_info(DRV_NAME ": " DRV_DESC "\n"); -+ -+ ehci_init_driver(&sunxi_ehci_hc_driver, &sunxi_overrides); -+ -+ return platform_driver_register(&ehci_sunxi_driver); -+} -+module_init(sunxi_ehci_init_module); -+ -+static void __exit sunxi_ehci_exit_module(void) -+{ -+ platform_driver_unregister(&ehci_sunxi_driver); -+} -+module_exit(sunxi_ehci_exit_module); -+ -+MODULE_DESCRIPTION(DRIVER_DESC); -+MODULE_LICENSE("GPL"); -+MODULE_ALIAS("platform:" DRV_NAME); -+MODULE_DEVICE_TABLE(of, ehci_of_match); -+MODULE_AUTHOR("Roman Byshko <rbyshko@gmail.com>"); --- -1.8.4 - |