]> rtime.felk.cvut.cz Git - zynq/linux.git/commitdiff
Xilinx: PS USB Host Controller Driver
authorNaveen Mamindlapalli <naveenm@xhd-epdswlin32re1.(none)>
Tue, 12 Apr 2011 14:51:47 +0000 (20:21 +0530)
committerJohn Linn <john.linn@xilinx.com>
Tue, 12 Apr 2011 19:11:46 +0000 (13:11 -0600)
Added new driver files for PS USB Host Controller.
This driver is same as FSL ehci driver(ehci-fsl.c) with
few minor modifications to support Xilinx PS USB controller.

Signed-off-by: Naveen Mamindlapalli <naveenm@xilinx.com>
drivers/usb/host/Kconfig
drivers/usb/host/ehci-xilinx-usbps.c [new file with mode: 0755]
drivers/usb/host/ehci-xilinx-usbps.h [new file with mode: 0755]

index 07baeb24f2b82860ee0d2dea6709db0efb0698af..d6356fceedbafce19c790f6882ec3b0de3e193f6 100644 (file)
@@ -115,12 +115,20 @@ config XPS_USB_HCD_XILINX
                support both high speed and full speed devices, or high speed
                devices only.
 
+config USB_EHCI_XUSBPS
+       bool "Support for Xilinx PS EHCI USB controller"
+       depends on USB_EHCI_HCD && ARCH_XILINX
+       select USB_EHCI_ROOT_HUB_TT
+       ---help---
+               Xilinx PS USB host controller core is EHCI compilant and has
+               transaction translator built-in.
+
 config USB_FSL_MPH_DR_OF
        tristate
 
 config USB_EHCI_FSL
        bool "Support for Freescale on-chip EHCI USB controller"
-       depends on USB_EHCI_HCD && (FSL_SOC || ARCH_XILINX)
+       depends on USB_EHCI_HCD && FSL_SOC
        select USB_EHCI_ROOT_HUB_TT
        select USB_FSL_MPH_DR_OF if OF
        ---help---
diff --git a/drivers/usb/host/ehci-xilinx-usbps.c b/drivers/usb/host/ehci-xilinx-usbps.c
new file mode 100755 (executable)
index 0000000..2509d10
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * Xilinx PS USB Host Controller Driver.
+ *
+ * Copyright (C) 2011 Xilinx, Inc.
+ *
+ * This file is based on ehci-fsl.c file with few minor modifications
+ * to support Xilinx PS USB controller.
+ *
+ * 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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/xilinx_devices.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/xilinx_usbps_otg.h>
+
+#include "ehci-xilinx-usbps.h"
+
+#ifdef CONFIG_USB_XUSBPS_OTG
+/********************************************************************
+ * OTG related functions
+ ********************************************************************/
+static int ehci_xusbps_reinit(struct ehci_hcd *ehci);
+
+/* This connection event is useful when a OTG test device is connected.
+   In that case, the device connect notify event will not be generated
+   since the device will be suspended before complete enumeration.
+*/
+static int ehci_xusbps_update_device(struct usb_hcd *hcd, struct usb_device
+               *udev)
+{
+       struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+       struct xusbps_otg *xotg = xceiv_to_xotg(ehci->transceiver);
+
+       if (udev->portnum == hcd->self.otg_port) {
+               /* HNP test device */
+               if ((le16_to_cpu(udev->descriptor.idVendor) == 0x1a0a &&
+                       le16_to_cpu(udev->descriptor.idProduct) == 0xbadd)) {
+                       if (xotg->otg.default_a == 1)
+                               xotg->hsm.b_conn = 1;
+                       else
+                               xotg->hsm.a_conn = 1;
+                       xusbps_update_transceiver();
+               }
+       }
+       return 0;
+}
+
+static void ehci_xusbps_start_hnp(struct ehci_hcd *ehci)
+{
+       const unsigned  port = ehci_to_hcd(ehci)->self.otg_port - 1;
+       unsigned long   flags;
+       u32 portsc;
+
+       local_irq_save(flags);
+       portsc = ehci_readl(ehci, &ehci->regs->port_status[port]);
+       portsc |= PORT_SUSPEND;
+       ehci_writel(ehci, portsc, &ehci->regs->port_status[port]);
+       local_irq_restore(flags);
+
+       otg_start_hnp(ehci->transceiver);
+}
+
+static int ehci_xusbps_otg_start_host(struct otg_transceiver  *otg)
+{
+       struct usb_hcd          *hcd = bus_to_hcd(otg->host);
+       struct xusbps_otg *xotg =
+                       xceiv_to_xotg(hcd_to_ehci(hcd)->transceiver);
+
+       usb_add_hcd(hcd, xotg->irq, IRQF_SHARED | IRQF_DISABLED);
+       return 0;
+}
+
+static int ehci_xusbps_otg_stop_host(struct otg_transceiver  *otg)
+{
+       struct usb_hcd          *hcd = bus_to_hcd(otg->host);
+
+       usb_remove_hcd(hcd);
+       return 0;
+}
+#endif
+
+/* configure so an HC device and id are always provided */
+/* always called with process context; sleeping is OK */
+
+/**
+ * usb_hcd_xusbps_probe - initialize XUSBPS-based HCDs
+ * @driver: Driver to be used for this HCD
+ * @pdev: USB Host Controller being probed
+ * Context: !in_interrupt()
+ *
+ * Allocates basic resources for this USB host controller.
+ *
+ */
+static int usb_hcd_xusbps_probe(const struct hc_driver *driver,
+                            struct platform_device *pdev)
+{
+       struct xusbps_usb2_platform_data *pdata;
+       struct usb_hcd *hcd;
+       int irq;
+       int retval;
+#ifdef CONFIG_USB_XUSBPS_OTG
+       struct xusbps_otg *xotg;
+       struct ehci_hcd *ehci;
+#endif
+
+       pr_debug("initializing XUSBPS-SOC USB Controller\n");
+
+       /* Need platform data for setup */
+       pdata = (struct xusbps_usb2_platform_data *)pdev->dev.platform_data;
+       if (!pdata) {
+               dev_err(&pdev->dev,
+                       "No platform data for %s.\n", dev_name(&pdev->dev));
+               return -ENODEV;
+       }
+
+       /*
+        * This is a host mode driver, verify that we're supposed to be
+        * in host mode.
+        */
+       if (!((pdata->operating_mode == XUSBPS_USB2_DR_HOST) ||
+             (pdata->operating_mode == XUSBPS_USB2_MPH_HOST) ||
+             (pdata->operating_mode == XUSBPS_USB2_DR_OTG))) {
+               dev_err(&pdev->dev, "Non Host Mode configured for %s. Wrong \
+                               driver linked.\n", dev_name(&pdev->dev));
+               return -ENODEV;
+       }
+
+       hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
+       if (!hcd) {
+               retval = -ENOMEM;
+               goto err1;
+       }
+
+       irq = pdata->irq;
+       hcd->regs = pdata->regs;
+
+       if (hcd->regs == NULL) {
+               dev_dbg(&pdev->dev, "error mapping memory\n");
+               retval = -EFAULT;
+               goto err2;
+       }
+
+       if (pdata->otg)
+               hcd->self.otg_port = 1;
+       /*
+        * do platform specific init: check the clock, grab/config pins, etc.
+        */
+       if (pdata->init && pdata->init(pdev)) {
+               retval = -ENODEV;
+               goto err2;
+       }
+
+#ifdef CONFIG_USB_XUSBPS_OTG
+       ehci = hcd_to_ehci(hcd);
+       if (pdata->otg) {
+               ehci->transceiver = pdata->otg;
+               retval = otg_set_host(ehci->transceiver,
+                               &ehci_to_hcd(ehci)->self);
+               if (retval)
+                       return retval;
+               xotg = xceiv_to_xotg(ehci->transceiver);
+               ehci->start_hnp = ehci_xusbps_start_hnp;
+               xotg->start_host = ehci_xusbps_otg_start_host;
+               xotg->stop_host = ehci_xusbps_otg_stop_host;
+               /* inform otg driver about host driver */
+               xusbps_update_transceiver();
+       } else {
+               retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED);
+               if (retval != 0)
+                       goto err2;
+       }
+#else
+       /* Don't need to set host mode here. It will be done by tdi_reset() */
+       retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED);
+       if (retval != 0)
+               goto err2;
+#endif
+       return retval;
+
+err2:
+       usb_put_hcd(hcd);
+err1:
+       dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval);
+       if (pdata->exit)
+               pdata->exit(pdev);
+
+       return retval;
+}
+
+/* may be called without controller electrically present */
+/* may be called with controller, bus, and devices active */
+
+/**
+ * usb_hcd_xusbps_remove - shutdown processing for XUSBPS-based HCDs
+ * @dev: USB Host Controller being removed
+ * Context: !in_interrupt()
+ *
+ * Reverses the effect of usb_hcd_xusbps_probe().
+ *
+ */
+static void usb_hcd_xusbps_remove(struct usb_hcd *hcd,
+                              struct platform_device *pdev)
+{
+       struct xusbps_usb2_platform_data *pdata = pdev->dev.platform_data;
+
+       usb_remove_hcd(hcd);
+
+       /*
+        * do platform specific un-initialization:
+        * release iomux pins, disable clock, etc.
+        */
+       if (pdata->exit)
+               pdata->exit(pdev);
+       usb_put_hcd(hcd);
+}
+
+static void ehci_xusbps_setup_phy(struct ehci_hcd *ehci,
+                              enum xusbps_usb2_phy_modes phy_mode,
+                              unsigned int port_offset)
+{
+       u32 portsc;
+
+       portsc = ehci_readl(ehci, &ehci->regs->port_status[port_offset]);
+       portsc &= ~(PORT_PTS_MSK | PORT_PTS_PTW);
+
+       switch (phy_mode) {
+       case XUSBPS_USB2_PHY_ULPI:
+               portsc |= PORT_PTS_ULPI;
+               break;
+       case XUSBPS_USB2_PHY_SERIAL:
+               portsc |= PORT_PTS_SERIAL;
+               break;
+       case XUSBPS_USB2_PHY_UTMI_WIDE:
+               portsc |= PORT_PTS_PTW;
+               /* fall through */
+       case XUSBPS_USB2_PHY_UTMI:
+               portsc |= PORT_PTS_UTMI;
+               break;
+       case XUSBPS_USB2_PHY_NONE:
+               break;
+       }
+       ehci_writel(ehci, portsc, &ehci->regs->port_status[port_offset]);
+}
+
+static void ehci_xusbps_usb_setup(struct ehci_hcd *ehci)
+{
+       struct usb_hcd *hcd = ehci_to_hcd(ehci);
+       struct xusbps_usb2_platform_data *pdata;
+
+       pdata = hcd->self.controller->platform_data;
+
+       if ((pdata->operating_mode == XUSBPS_USB2_DR_HOST) ||
+                       (pdata->operating_mode == XUSBPS_USB2_DR_OTG))
+               ehci_xusbps_setup_phy(ehci, pdata->phy_mode, 0);
+
+       if (pdata->operating_mode == XUSBPS_USB2_MPH_HOST) {
+               if (pdata->port_enables & XUSBPS_USB2_PORT0_ENABLED)
+                       ehci_xusbps_setup_phy(ehci, pdata->phy_mode, 0);
+               if (pdata->port_enables & XUSBPS_USB2_PORT1_ENABLED)
+                       ehci_xusbps_setup_phy(ehci, pdata->phy_mode, 1);
+       }
+}
+
+/* called after powerup, by probe or system-pm "wakeup" */
+static int ehci_xusbps_reinit(struct ehci_hcd *ehci)
+{
+       ehci_xusbps_usb_setup(ehci);
+#ifdef CONFIG_USB_XUSBPS_OTG
+       /* Don't turn off port power in OTG mode */
+       if (!ehci->transceiver)
+#endif
+               ehci_port_power(ehci, 0);
+
+       return 0;
+}
+
+struct ehci_xusbps {
+       struct ehci_hcd ehci;
+
+#ifdef CONFIG_PM
+       /* Saved USB PHY settings, need to restore after deep sleep. */
+       u32 usb_ctrl;
+#endif
+};
+
+/* called during probe() after chip reset completes */
+static int ehci_xusbps_setup(struct usb_hcd *hcd)
+{
+       struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+       int retval;
+
+       /* EHCI registers start at offset 0x100 */
+       ehci->caps = hcd->regs + 0x100;
+       ehci->regs = hcd->regs + 0x100 +
+           HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
+       dbg_hcs_params(ehci, "reset");
+       dbg_hcc_params(ehci, "reset");
+
+       /* cache this readonly data; minimize chip reads */
+       ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
+
+       hcd->has_tt = 1;
+
+       retval = ehci_halt(ehci);
+       if (retval)
+               return retval;
+
+       /* data structure init */
+       retval = ehci_init(hcd);
+       if (retval)
+               return retval;
+
+       ehci->sbrn = 0x20;
+
+       ehci_reset(ehci);
+
+       retval = ehci_xusbps_reinit(ehci);
+       return retval;
+}
+
+#ifdef CONFIG_PM
+
+static int ehci_xusbps_drv_suspend(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+
+       ehci_prepare_ports_for_controller_suspend(hcd_to_ehci(hcd),
+                       device_may_wakeup(dev));
+
+       return 0;
+}
+
+static int ehci_xusbps_drv_resume(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+       struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+       ehci_prepare_ports_for_controller_resume(ehci);
+
+       usb_root_hub_lost_power(hcd->self.root_hub);
+
+       ehci_reset(ehci);
+       ehci_xusbps_reinit(ehci);
+
+       return 0;
+}
+
+static int ehci_xusbps_drv_restore(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+
+       usb_root_hub_lost_power(hcd->self.root_hub);
+       return 0;
+}
+
+static struct dev_pm_ops ehci_xusbps_pm_ops = {
+       .suspend = ehci_xusbps_drv_suspend,
+       .resume = ehci_xusbps_drv_resume,
+       .restore = ehci_xusbps_drv_restore,
+};
+
+#define EHCI_XUSBPS_PM_OPS             (&ehci_xusbps_pm_ops)
+#else
+#define EHCI_XUSBPS_PM_OPS             NULL
+#endif /* CONFIG_PM */
+
+static const struct hc_driver ehci_xusbps_hc_driver = {
+       .description = hcd_name,
+       .product_desc = "Xilinx PS USB EHCI Host Controller",
+       .hcd_priv_size = sizeof(struct ehci_xusbps),
+
+       /*
+        * generic hardware linkage
+        */
+       .irq = ehci_irq,
+       .flags = HCD_USB2 | HCD_MEMORY,
+
+       /*
+        * basic lifecycle operations
+        */
+       .reset = ehci_xusbps_setup,
+       .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,
+
+       /*
+        * scheduling support
+        */
+       .get_frame_number = ehci_get_frame,
+
+       /*
+        * root hub support
+        */
+       .hub_status_data = ehci_hub_status_data,
+       .hub_control = ehci_hub_control,
+       .bus_suspend = ehci_bus_suspend,
+       .bus_resume = ehci_bus_resume,
+       .relinquish_port = ehci_relinquish_port,
+       .port_handed_over = ehci_port_handed_over,
+
+       .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+#ifdef CONFIG_USB_XUSBPS_OTG
+       .update_device = ehci_xusbps_update_device,
+#endif
+};
+
+static int ehci_xusbps_drv_probe(struct platform_device *pdev)
+{
+       if (usb_disabled())
+               return -ENODEV;
+
+       /* FIXME we only want one one probe() not two */
+       return usb_hcd_xusbps_probe(&ehci_xusbps_hc_driver, pdev);
+}
+
+static int ehci_xusbps_drv_remove(struct platform_device *pdev)
+{
+       struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+       /* FIXME we only want one one remove() not two */
+       usb_hcd_xusbps_remove(hcd, pdev);
+       return 0;
+}
+
+MODULE_ALIAS("platform:xusbps-ehci");
+
+static struct platform_driver ehci_xusbps_driver = {
+       .probe = ehci_xusbps_drv_probe,
+       .remove = ehci_xusbps_drv_remove,
+       .shutdown = usb_hcd_platform_shutdown,
+       .driver = {
+               .name = "xusbps-ehci",
+               .pm = EHCI_XUSBPS_PM_OPS,
+       },
+};
diff --git a/drivers/usb/host/ehci-xilinx-usbps.h b/drivers/usb/host/ehci-xilinx-usbps.h
new file mode 100755 (executable)
index 0000000..bb0f2d4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Xilinx PS USB Host Controller Driver Header file.
+ *
+ * Copyright (C) 2011 Xilinx, Inc.
+ *
+ * This file is based on ehci-fsl.h file with few minor modifications
+ * to support Xilinx PS USB controller.
+ *
+ * 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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _EHCI_XILINX_XUSBPS_H
+#define _EHCI_XILINX_XUSBPS_H
+
+#include <linux/usb/xilinx_usbps_otg.h>
+
+/* offsets for the non-ehci registers in the XUSBPS SOC USB controller */
+#define XUSBPS_SOC_USB_ULPIVP  0x170
+#define XUSBPS_SOC_USB_PORTSC1 0x184
+#define PORT_PTS_MSK           (3<<30)
+#define PORT_PTS_UTMI          (0<<30)
+#define PORT_PTS_ULPI          (2<<30)
+#define        PORT_PTS_SERIAL         (3<<30)
+#define PORT_PTS_PTW           (1<<28)
+#define XUSBPS_SOC_USB_PORTSC2 0x188
+
+#endif                         /* _EHCI_XILINX_XUSBPS_H */