--- /dev/null
+/*
+ * EHCI-compliant USB host controller driver for Rockchip SoCs
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2009 - 2013 NVIDIA Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * 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.
+ *
+ */
+# include <linux/platform_device.h>
+# include <linux/clk.h>
+# include <linux/err.h>
+# include <linux/device.h>
+# include <linux/of.h>
+# include <linux/of_platform.h>
+# include "ehci.h"
+#ifdef CONFIG_DWC_OTG_274
+# include "../dwc_otg/usbdev_rk.h"
+#endif
+#ifdef CONFIG_DWC_OTG_310
+# include "../dwc_otg_310/usbdev_rk.h"
+#endif
+
+static int rkehci_status = 1;
+static struct ehci_hcd *g_ehci;
+#define EHCI_PRINT(x...) printk( KERN_INFO "EHCI: " x )
+
+static struct rkehci_pdata_id rkehci_pdata[] = {
+ {
+ .name = "rk3188-reserved",
+ .pdata = NULL,
+ },
+ {
+ .name = "rk3288-ehci",
+ .pdata = &rkehci_pdata_rk3288,
+ },
+ { },
+};
+
+static void ehci_port_power (struct ehci_hcd *ehci, int is_on)
+{
+ unsigned port;
+
+ if (!HCS_PPC (ehci->hcs_params))
+ return;
+
+ ehci_dbg (ehci, "...power%s ports...\n", is_on ? "up" : "down");
+ for (port = HCS_N_PORTS (ehci->hcs_params); port > 0; )
+ (void) ehci_hub_control(ehci_to_hcd(ehci),
+ is_on ? SetPortFeature : ClearPortFeature,
+ USB_PORT_FEAT_POWER,
+ port--, NULL, 0);
+ /* Flush those writes */
+ ehci_readl(ehci, &ehci->regs->command);
+ msleep(20);
+}
+
+static struct hc_driver rk_ehci_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "Rockchip On-Chip EHCI Host Controller",
+ .hcd_priv_size = sizeof(struct ehci_hcd),
+
+ /*
+ * generic hardware linkage
+ */
+ .irq = ehci_irq,
+ .flags = HCD_USB2 | HCD_MEMORY,
+
+ .reset = ehci_init,
+ .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,
+ .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ehci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ehci_hub_status_data,
+ .hub_control = ehci_hub_control,
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
+
+ /*
+ * PM support
+ */
+#ifdef CONFIG_PM
+ .bus_suspend = ehci_bus_suspend,
+ .bus_resume = ehci_bus_resume,
+#endif
+};
+static ssize_t ehci_power_show( struct device *_dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", rkehci_status);
+}
+static ssize_t ehci_power_store( struct device *_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count )
+{
+ uint32_t val = simple_strtoul(buf, NULL, 16);
+ struct usb_hcd *hcd = dev_get_drvdata(_dev);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ struct rkehci_platform_data *pldata = _dev->platform_data;
+
+ printk("%s: %d setting to: %d\n", __func__, rkehci_status, val);
+ if(val == rkehci_status)
+ goto out;
+
+ rkehci_status = val;
+ switch(val){
+ case 0: //power down
+ ehci_port_power(ehci, 0);
+ msleep(5);
+ usb_remove_hcd(hcd);
+ break;
+ case 1:// power on
+ pldata->soft_reset();
+ usb_add_hcd(hcd, hcd->irq, IRQF_DISABLED | IRQF_SHARED);
+ ehci_port_power(ehci, 1);
+ writel_relaxed(0x1d4d ,hcd->regs +0x90);
+ writel_relaxed(0x4 ,hcd->regs +0xa0);
+ dsb();
+ break;
+ default:
+ break;
+ }
+out:
+ return count;
+}
+static DEVICE_ATTR(ehci_power, S_IRUGO|S_IWUSR, ehci_power_show, ehci_power_store);
+static ssize_t debug_show( struct device *_dev,
+ struct device_attribute *attr, char *buf)
+{
+ volatile uint32_t *addr;
+
+ EHCI_PRINT("******** EHCI Capability Registers **********\n");
+ addr = &g_ehci->caps->hc_capbase;
+ EHCI_PRINT("HCIVERSION / CAPLENGTH @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->caps->hcs_params;
+ EHCI_PRINT("HCSPARAMS @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->caps->hcc_params;
+ EHCI_PRINT("HCCPARAMS @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ EHCI_PRINT("********* EHCI Operational Registers *********\n");
+ addr = &g_ehci->regs->command;
+ EHCI_PRINT("USBCMD @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->regs->status;
+ EHCI_PRINT("USBSTS @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->regs->intr_enable;
+ EHCI_PRINT("USBINTR @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->regs->frame_index;
+ EHCI_PRINT("FRINDEX @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->regs->segment;
+ EHCI_PRINT("CTRLDSSEGMENT @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->regs->frame_list;
+ EHCI_PRINT("PERIODICLISTBASE @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->regs->async_next;
+ EHCI_PRINT("ASYNCLISTADDR @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = &g_ehci->regs->configured_flag;
+ EHCI_PRINT("CONFIGFLAG @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ addr = g_ehci->regs->port_status;
+ EHCI_PRINT("PORTSC @0x%08x: 0x%08x\n", (uint32_t)addr, readl_relaxed(addr));
+ return sprintf(buf, "EHCI Registers Dump\n");
+}
+static DEVICE_ATTR(debug_ehci, S_IRUGO, debug_show, NULL);
+
+static struct of_device_id rk_ehci_of_match[] = {
+ {
+ .compatible = "rockchip,rk3288_rk_ehci_host",
+ .data = &rkehci_pdata[RK3288_USB_CTLR],
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rk_ehci_of_match);
+
+static int ehci_rk_probe(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd;
+ struct ehci_hcd *ehci;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct rkehci_platform_data *pldata;
+ int ret;
+ int retval = 0;
+ static u64 usb_dmamask = 0xffffffffUL;
+ struct device_node *node = pdev->dev.of_node;
+ struct rkehci_pdata_id *p;
+ const struct of_device_id *match =
+ of_match_device(of_match_ptr( rk_ehci_of_match ), &pdev->dev);
+
+ dev_dbg(&pdev->dev, "ehci_rk proble\n");
+
+ if (match){
+ p = (struct rkehci_pdata_id *)match->data;
+ }else{
+ dev_err(dev, "ehci_rk match failed\n");
+ return -EINVAL;
+ }
+
+ dev->platform_data = p->pdata;
+ pldata = dev->platform_data;
+ pldata->dev = dev;
+
+ if (!node) {
+ dev_err(dev, "device node not found\n");
+ return -EINVAL;
+ }
+
+ dev->dma_mask = &usb_dmamask;
+
+ retval = device_create_file(dev, &dev_attr_ehci_power);
+ retval = device_create_file(dev, &dev_attr_debug_ehci);
+ hcd = usb_create_hcd(&rk_ehci_hc_driver, &pdev->dev, dev_name(&pdev->dev));
+ if (!hcd) {
+ dev_err(&pdev->dev, "Unable to create HCD\n");
+ return -ENOMEM;
+ }
+
+ if(pldata->hw_init)
+ pldata->hw_init();
+
+ if(pldata->clock_init){
+ pldata->clock_init(pldata);
+ pldata->clock_enable(pldata, 1);
+ }
+
+ if(pldata->soft_reset)
+ pldata->soft_reset();
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get memory resource\n");
+ ret = -ENODEV;
+ goto put_hcd;
+ }
+
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = resource_size(res);
+ hcd->regs = devm_ioremap_resource(dev, res);
+
+ if (!hcd->regs) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto put_hcd;
+ }
+
+ hcd->irq = platform_get_irq(pdev, 0);
+ if (hcd->irq < 0) {
+ dev_err(&pdev->dev, "Unable to get IRQ resource\n");
+ ret = hcd->irq;
+ goto put_hcd;
+ }
+
+ ehci = hcd_to_ehci(hcd);
+ ehci->caps = hcd->regs;
+ ehci->regs = hcd->regs + 0x10;
+ printk("%s %p %p\n", __func__, ehci->caps, ehci->regs);
+
+ dbg_hcs_params(ehci, "reset");
+ dbg_hcc_params(ehci, "reset");
+
+ ehci->hcs_params = readl(&ehci->caps->hcs_params);
+
+ ret = usb_add_hcd(hcd, hcd->irq, IRQF_DISABLED | IRQF_SHARED);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add USB HCD\n");
+ goto put_hcd;
+ }
+
+ g_ehci = ehci;
+ ehci_port_power(ehci, 1);
+ writel_relaxed(0x1d4d ,hcd->regs +0x90);
+ writel_relaxed(0x4 ,hcd->regs +0xa0);
+ dsb();
+
+ printk("%s ok\n", __func__);
+
+ return 0;
+
+put_hcd:
+ if(pldata->clock_enable)
+ pldata->clock_enable(pldata, 0);
+ usb_put_hcd(hcd);
+
+ return ret;
+}
+static int ehci_rk_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+ usb_put_hcd(hcd);
+
+ return 0;
+}
+#ifdef CONFIG_PM
+static int ehci_rk_pm_suspend(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ bool wakeup = device_may_wakeup(dev);
+
+ dev_dbg(dev, "ehci-rockchip PM suspend\n");
+
+ /*
+ * EHCI helper function has also the same check before manipulating
+ * port wakeup flags. We do check here the same condition before
+ * calling the same helper function to avoid bringing hardware
+ * from Low power mode when there is no need for adjusting port
+ * wakeup flags.
+ */
+ if (hcd->self.root_hub->do_remote_wakeup && !wakeup) {
+ pm_runtime_resume(dev);
+ ehci_prepare_ports_for_controller_suspend(hcd_to_ehci(hcd),
+ wakeup);
+ }
+
+ return 0;
+}
+
+static int ehci_rk_pm_resume(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "ehci-rockchip PM resume\n");
+ ehci_prepare_ports_for_controller_resume(hcd_to_ehci(hcd));
+
+ return 0;
+}
+#else
+#define ehci_rk_pm_suspend NULL
+#define ehci_rk_pm_resume NULL
+#endif
+
+static const struct dev_pm_ops ehci_rk_dev_pm_ops = {
+ .suspend = ehci_rk_pm_suspend,
+ .resume = ehci_rk_pm_resume,
+};
+
+static struct platform_driver ehci_rk_driver = {
+ .probe = ehci_rk_probe,
+ .remove = ehci_rk_remove,
+ .driver = {
+ .name = "rockchip_ehci_host",
+ .of_match_table = of_match_ptr(rk_ehci_of_match),
+#ifdef CONFIG_PM
+ .pm = &ehci_rk_dev_pm_ops,
+#endif
+ },
+};
--- /dev/null
+/*
+ * ROCKCHIP USB HOST OHCI Controller
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_platform.h>
+#ifdef CONFIG_DWC_OTG_274
+# include "../dwc_otg/usbdev_rk.h"
+#endif
+#ifdef CONFIG_DWC_OTG_310
+# include "../dwc_otg_310/usbdev_rk.h"
+#endif
+
+static struct rkehci_pdata_id rkohci_pdata[] = {
+ {
+ .name = "rk3188-reserved",
+ .pdata = NULL,
+ },
+ {
+ .name = "rk3288-ohci",
+ .pdata = &rkohci_pdata_rk3288,
+ },
+ { },
+};
+
+static int ohci_rk_init(struct usb_hcd *hcd)
+{
+ dev_dbg(hcd->self.controller, "starting OHCI controller\n");
+
+ return ohci_init(hcd_to_ohci(hcd));
+}
+
+static int ohci_rk_start(struct usb_hcd *hcd)
+{
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ int ret;
+
+ /*
+ * RemoteWakeupConnected has to be set explicitly before
+ * calling ohci_run. The reset value of RWC is 0.
+ */
+ ohci->hc_control = OHCI_CTRL_RWC;
+ writel(OHCI_CTRL_RWC, &ohci->regs->control);
+
+ ret = ohci_run(ohci);
+
+ if (ret < 0) {
+ dev_err(hcd->self.controller, "can't start\n");
+ ohci_stop(hcd);
+ }
+
+ return ret;
+}
+
+static const struct hc_driver ohci_rk_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "RK OHCI Host Controller",
+ .hcd_priv_size = sizeof(struct ohci_hcd),
+
+ /*
+ * generic hardware linkage
+ */
+ .irq = ohci_irq,
+ .flags = HCD_USB11 | HCD_MEMORY,
+
+ /*
+ * basic lifecycle operations
+ */
+ .reset = ohci_rk_init,
+ .start = ohci_rk_start,
+ .stop = ohci_stop,
+ .shutdown = ohci_shutdown,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = ohci_urb_enqueue,
+ .urb_dequeue = ohci_urb_dequeue,
+ .endpoint_disable = ohci_endpoint_disable,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ohci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ohci_hub_status_data,
+ .hub_control = ohci_hub_control,
+#ifdef CONFIG_PM
+ .bus_suspend = ohci_bus_suspend,
+ .bus_resume = ohci_bus_resume,
+#endif
+ .start_port_reset = ohci_start_port_reset,
+};
+
+static struct of_device_id rk_ohci_of_match[] = {
+ {
+ .compatible = "rockchip,rk3288_rk_ohci_host",
+ .data = &rkohci_pdata[RK3288_USB_CTLR],
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rk_ohci_of_match);
+
+/* ohci_hcd_rk_probe - initialize RK-based HCDs
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ */
+static int ohci_hcd_rk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct usb_hcd *hcd = NULL;
+ void __iomem *regs = NULL;
+ struct resource *res;
+ int ret = -ENODEV;
+ int irq;
+ struct rkehci_platform_data *pldata;
+ struct device_node *node = pdev->dev.of_node;
+ struct rkehci_pdata_id *p;
+ const struct of_device_id *match =
+ of_match_device(of_match_ptr( rk_ohci_of_match ), &pdev->dev);
+
+ dev_dbg(&pdev->dev, "ohci_hcd_rk_probe\n");
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ if (match){
+ p = (struct rkehci_pdata_id *)match->data;
+ }else{
+ dev_err(dev, "ohci_rk match failed\n");
+ return -EINVAL;
+ }
+
+ dev->platform_data = p->pdata;
+ pldata = dev->platform_data;
+ pldata->dev = dev;
+
+ if (!node) {
+ dev_err(dev, "device node not found\n");
+ return -EINVAL;
+ }
+
+ if(pldata->hw_init)
+ pldata->hw_init();
+
+ if(pldata->clock_init){
+ pldata->clock_init(pldata);
+ pldata->clock_enable(pldata, 1);
+ }
+
+ if(pldata->soft_reset)
+ pldata->soft_reset();
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "OHCI irq failed\n");
+ ret = irq;
+ goto clk_disable;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "UHH OHCI get resource failed\n");
+ ret = -ENOMEM;
+ goto clk_disable;
+ }
+
+ regs = devm_ioremap_resource(dev, res);
+ if (!regs) {
+ dev_err(dev, "UHH OHCI ioremap failed\n");
+ ret = -ENOMEM;
+ goto clk_disable;
+ }
+
+ /*
+ * Right now device-tree probed devices don't get dma_mask set.
+ * Since shared usb code relies on it, set it here for now.
+ * Once we have dma capability bindings this can go away.
+ */
+ if (!dev->dma_mask)
+ dev->dma_mask = &dev->coherent_dma_mask;
+ if (!dev->coherent_dma_mask)
+ dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ hcd = usb_create_hcd(&ohci_rk_hc_driver, dev,
+ dev_name(dev));
+ if (!hcd) {
+ dev_err(dev, "usb_create_hcd failed\n");
+ ret = -ENOMEM;
+ goto clk_disable;
+ }
+
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = resource_size(res);
+ hcd->regs = regs;
+
+ ohci_hcd_init(hcd_to_ohci(hcd));
+
+ ret = usb_add_hcd(hcd, irq, 0);
+ if (ret) {
+ dev_dbg(dev, "failed to add hcd with err %d\n", ret);
+ goto err_add_hcd;
+ }
+
+ return 0;
+
+err_add_hcd:
+ usb_put_hcd(hcd);
+
+clk_disable:
+ if(pldata->clock_enable)
+ pldata->clock_enable(pldata, 0);
+
+ return ret;
+}
+
+static int ohci_hcd_rk_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+
+ usb_remove_hcd(hcd);
+ usb_put_hcd(hcd);
+ return 0;
+}
+
+static void ohci_hcd_rk_shutdown(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev);
+
+ if (hcd->driver->shutdown)
+ hcd->driver->shutdown(hcd);
+}
+
+static struct platform_driver ohci_hcd_rk_driver = {
+ .probe = ohci_hcd_rk_probe,
+ .remove = ohci_hcd_rk_remove,
+ .shutdown = ohci_hcd_rk_shutdown,
+ .driver = {
+ .name = "ohci-rockchip",
+ .of_match_table = of_match_ptr(rk_ohci_of_match),
+ },
+};
+MODULE_ALIAS("platform:rockchip-ohci");