Hi, On Wed, Nov 30, 2011 at 09:57:11AM +0800, Neil Zhang wrote: > This patch adds support for EHCI compliant HSUSB Host controller found > on Marvell Socs. > > It fits both OTG and SPH controller on marvell Socs, including > PXA920/MMP2/MMP3/MGx. > > Signed-off-by: Neil Zhang <zhangwm@xxxxxxxxxxx> > --- This is Alan's territory. Alan, any comments ? > drivers/usb/host/Kconfig | 9 + > drivers/usb/host/ehci-hcd.c | 5 + > drivers/usb/host/ehci-mv.c | 393 ++++++++++++++++++++++++++++++++++ > include/linux/platform_data/mv_usb.h | 1 + > 4 files changed, 408 insertions(+), 0 deletions(-) > create mode 100644 drivers/usb/host/ehci-mv.c > > diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig > index 060e0e2..e704899 100644 > --- a/drivers/usb/host/Kconfig > +++ b/drivers/usb/host/Kconfig > @@ -194,6 +194,15 @@ config USB_EHCI_S5P > help > Enable support for the S5P SOC's on-chip EHCI controller. > > +config USB_EHCI_PXA > + bool "EHCI support for PXA USB SPH and OTG controller" > + depends on USB_EHCI_HCD > + select USB_EHCI_ROOT_HUB_TT > + ---help--- > + Enables support for PXA USB SPH and OTG controller. SPH is a > + single port host, and it can only be EHCI host. OTG is controller > + that can switch to be host mode. > + > config USB_W90X900_EHCI > bool "W90X900(W90P910) EHCI support" > depends on USB_EHCI_HCD && ARCH_W90X900 > diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c > index 3ff9f82..4eb19f8 100644 > --- a/drivers/usb/host/ehci-hcd.c > +++ b/drivers/usb/host/ehci-hcd.c > @@ -1329,6 +1329,11 @@ MODULE_LICENSE ("GPL"); > #define PLATFORM_DRIVER ehci_xls_driver > #endif > > +#ifdef CONFIG_USB_EHCI_PXA > +#include "ehci-mv.c" > +#define PLATFORM_DRIVER mv_ehci_driver > +#endif > + > #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \ > !defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \ > !defined(XILINX_OF_PLATFORM_DRIVER) > diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c > new file mode 100644 > index 0000000..5834fc7 > --- /dev/null > +++ b/drivers/usb/host/ehci-mv.c > @@ -0,0 +1,393 @@ > +/* > + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. > + * Author: Chao Xie <chao.xie@xxxxxxxxxxx> > + * Neil Zhang <zhangwm@xxxxxxxxxxx> > + * > + * 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/kernel.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/clk.h> > +#include <linux/usb/otg.h> > +#include <linux/platform_data/mv_usb.h> > + > +#define CAPLENGTH_MASK (0xff) > + > +struct ehci_hcd_mv { > + struct usb_hcd *hcd; > + > + /* Which mode does this ehci running OTG/Host ? */ > + int mode; > + > + void __iomem *phy_regs; > + void __iomem *cap_regs; > + void __iomem *op_regs; > + > + struct otg_transceiver *otg; > + > + struct mv_usb_platform_data *pdata; > + > + /* clock source and total clock number */ > + unsigned int clknum; > + struct clk *clk[0]; > +}; > + > +static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) > +{ > + unsigned int i; > + > + for (i = 0; i < ehci_mv->clknum; i++) > + clk_enable(ehci_mv->clk[i]); > +} > + > +static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) > +{ > + unsigned int i; > + > + for (i = 0; i < ehci_mv->clknum; i++) > + clk_disable(ehci_mv->clk[i]); > +} > + > +static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) > +{ > + int retval; > + > + ehci_clock_enable(ehci_mv); > + if (ehci_mv->pdata->phy_init) { > + retval = ehci_mv->pdata->phy_init( > + (unsigned int) ehci_mv->phy_regs); > + if (retval) > + return retval; > + } > + > + return 0; > +} > + > +static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) > +{ > + if (ehci_mv->pdata->phy_deinit) > + ehci_mv->pdata->phy_deinit( > + (unsigned int) ehci_mv->phy_regs); > + ehci_clock_disable(ehci_mv); > +} > + > +static int mv_ehci_reset(struct usb_hcd *hcd) > +{ > + struct ehci_hcd *ehci = hcd_to_ehci(hcd); > + struct device *dev = hcd->self.controller; > + struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev); > + int retval; > + > + if (ehci_mv == NULL) { > + dev_err(dev, "Can not find private ehci data\n"); > + return -ENODEV; > + } > + > + /* > + * data structure init > + */ > + retval = ehci_init(hcd); > + if (retval) { > + dev_err(dev, "ehci_init failed %d\n", retval); > + return retval; > + } > + > + hcd->has_tt = 1; > + ehci->sbrn = 0x20; > + > + retval = ehci_reset(ehci); > + if (retval) { > + dev_err(dev, "ehci_reset failed %d\n", retval); > + return retval; > + } > + > + return 0; > +} > + > +static const struct hc_driver mv_ehci_hc_driver = { > + .description = hcd_name, > + .product_desc = "Marvell EHCI", > + .hcd_priv_size = sizeof(struct ehci_hcd), > + > + /* > + * generic hardware linkage > + */ > + .irq = ehci_irq, > + .flags = HCD_MEMORY | HCD_USB2, > + > + /* > + * basic lifecycle operations > + */ > + .reset = mv_ehci_reset, > + .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, > + .bus_suspend = ehci_bus_suspend, > + .bus_resume = ehci_bus_resume, > +}; > + > +static int mv_ehci_probe(struct platform_device *pdev) > +{ > + struct mv_usb_platform_data *pdata = pdev->dev.platform_data; > + struct usb_hcd *hcd; > + struct ehci_hcd *ehci; > + struct ehci_hcd_mv *ehci_mv; > + struct resource *r; > + int clk_i, retval = -ENODEV; > + u32 offset; > + size_t size; > + > + if (!pdata) { > + dev_err(&pdev->dev, "missing platform_data\n"); > + return -ENODEV; > + } > + > + if (usb_disabled()) > + return -ENODEV; > + > + hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci"); > + if (!hcd) > + return -ENOMEM; > + > + size = sizeof(*ehci_mv) + sizeof(struct clk *) * pdata->clknum; > + ehci_mv = kzalloc(size, GFP_KERNEL); > + if (ehci_mv == NULL) { > + dev_err(&pdev->dev, "cannot allocate ehci_hcd_mv\n"); > + retval = -ENOMEM; > + goto err_put_hcd; > + } > + > + platform_set_drvdata(pdev, ehci_mv); > + ehci_mv->pdata = pdata; > + ehci_mv->hcd = hcd; > + > + ehci_mv->clknum = pdata->clknum; > + for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++) { > + ehci_mv->clk[clk_i] = > + clk_get(&pdev->dev, pdata->clkname[clk_i]); > + if (IS_ERR(ehci_mv->clk[clk_i])) { > + dev_err(&pdev->dev, "error get clck \"%s\"\n", > + pdata->clkname[clk_i]); > + retval = PTR_ERR(ehci_mv->clk[clk_i]); > + goto err_put_clk; > + } > + } > + > + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); > + if (r == NULL) { > + dev_err(&pdev->dev, > + "no phy I/O memory resource defined\n"); > + retval = -ENODEV; > + goto err_put_clk; > + } > + > + ehci_mv->phy_regs = ioremap(r->start, resource_size(r)); > + if (ehci_mv->phy_regs == 0) { > + dev_err(&pdev->dev, "failed to map phy I/O memory\n"); > + retval = -EFAULT; > + goto err_put_clk; > + } > + > + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); > + if (!r) { > + dev_err(&pdev->dev, "no I/O memory resource defined\n"); > + retval = -ENODEV; > + goto err_iounmap_phyreg; > + } > + > + ehci_mv->cap_regs = ioremap(r->start, resource_size(r)); > + if (ehci_mv->cap_regs == NULL) { > + dev_err(&pdev->dev, "failed to map I/O memory\n"); > + retval = -EFAULT; > + goto err_iounmap_phyreg; > + } > + > + retval = mv_ehci_enable(ehci_mv); > + if (retval) { > + dev_err(&pdev->dev, "init phy error %d\n", retval); > + goto err_iounmap_capreg; > + } > + > + offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; > + ehci_mv->op_regs = (void __iomem *) ((u32) ehci_mv->cap_regs + offset); > + > + hcd->rsrc_start = r->start; > + hcd->rsrc_len = r->end - r->start + 1; > + hcd->regs = ehci_mv->op_regs; > + > + hcd->irq = platform_get_irq(pdev, 0); > + if (!hcd->irq) { > + dev_err(&pdev->dev, "Cannot get irq."); > + retval = -ENODEV; > + goto err_disable_clk; > + } > + > + ehci = hcd_to_ehci(hcd); > + ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs; > + ehci->regs = (struct ehci_regs *) ehci_mv->op_regs; > + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); > + > + ehci_mv->mode = pdata->mode; > + if (ehci_mv->mode == MV_USB_MODE_OTG) { > +#ifdef CONFIG_USB_OTG_UTILS > + ehci_mv->otg = otg_get_transceiver(); > + if (!ehci_mv->otg) { > + dev_err(&pdev->dev, "unable to find transceiver\n"); > + retval = -ENODEV; > + goto err_disable_clk; > + } > + > + retval = otg_set_host(ehci_mv->otg, &hcd->self); > + if (retval < 0) { > + dev_err(&pdev->dev, > + "unable to register with transceiver\n"); > + retval = -ENODEV; > + goto err_put_transceiver; > + } > + /* otg will enable clock before use as host */ > + mv_ehci_disable(ehci_mv); > +#else > + dev_info(&pdev->dev, "MV_USB_MODE_OTG " > + "must have CONFIG_USB_OTG_UTILS enabled\n"); > + goto err_disable_clk; > +#endif > + } else { > + if (pdata->set_vbus) > + pdata->set_vbus(1); > + > + retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); > + if (retval) { > + dev_err(&pdev->dev, > + "failed to add hcd with err %d\n", retval); > + goto err_set_vbus; > + } > + } > + > + if (pdata->private_init) > + pdata->private_init((unsigned int) ehci_mv->op_regs, > + (unsigned int) ehci_mv->phy_regs); > + > + dev_info(&pdev->dev, > + "successful find EHCI device with regs 0x%p irq %d" > + " working in %s mode\n", hcd->regs, hcd->irq, > + ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host"); > + > + return 0; > + > +err_set_vbus: > + if (pdata->set_vbus) > + pdata->set_vbus(0); > +#ifdef CONFIG_USB_OTG_UTILS > +err_put_transceiver: > + if (ehci_mv->otg) > + otg_put_transceiver(ehci_mv->otg); > +#endif > +err_disable_clk: > + mv_ehci_disable(ehci_mv); > +err_iounmap_capreg: > + iounmap(ehci_mv->cap_regs); > +err_iounmap_phyreg: > + iounmap(ehci_mv->phy_regs); > +err_put_clk: > + for (clk_i--; clk_i >= 0; clk_i--) > + clk_put(ehci_mv->clk[clk_i]); > + platform_set_drvdata(pdev, NULL); > + kfree(ehci_mv); > +err_put_hcd: > + usb_put_hcd(hcd); > + > + return retval; > +} > + > +static int mv_ehci_remove(struct platform_device *pdev) > +{ > + struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); > + struct usb_hcd *hcd = ehci_mv->hcd; > + int clk_i; > + > + if (hcd->rh_registered) > + usb_remove_hcd(hcd); > + > + if (ehci_mv->otg) { > + otg_set_host(ehci_mv->otg, NULL); > + otg_put_transceiver(ehci_mv->otg); > + } > + > + if (ehci_mv->mode == MV_USB_MODE_HOST) { > + if (ehci_mv->pdata->set_vbus) > + ehci_mv->pdata->set_vbus(0); > + > + mv_ehci_disable(ehci_mv); > + } > + > + iounmap(ehci_mv->cap_regs); > + iounmap(ehci_mv->phy_regs); > + > + for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++) > + clk_put(ehci_mv->clk[clk_i]); > + > + platform_set_drvdata(pdev, NULL); > + > + kfree(ehci_mv); > + usb_put_hcd(hcd); > + > + return 0; > +} > + > +MODULE_ALIAS("mv-ehci"); > + > +static const struct platform_device_id ehci_id_table[] = { > + {"pxa-u2oehci", PXA_U2OEHCI}, > + {"pxa-sph", PXA_SPH}, > + {"mmp3-hsic", MMP3_HSIC}, > + {"mmp3-fsic", MMP3_FSIC}, > + {}, > +}; > + > +static void mv_ehci_shutdown(struct platform_device *pdev) > +{ > + struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); > + struct usb_hcd *hcd = ehci_mv->hcd; > + > + if (!hcd->rh_registered) > + return; > + > + if (hcd->driver->shutdown) > + hcd->driver->shutdown(hcd); > +} > + > +static struct platform_driver mv_ehci_driver = { > + .probe = mv_ehci_probe, > + .remove = mv_ehci_remove, > + .shutdown = mv_ehci_shutdown, > + .driver = { > + .name = "pxa-ehci", > + .bus = &platform_bus_type, > + }, > + .id_table = ehci_id_table, > +}; > diff --git a/include/linux/platform_data/mv_usb.h b/include/linux/platform_data/mv_usb.h > index e9d9149..8c30ce5 100644 > --- a/include/linux/platform_data/mv_usb.h > +++ b/include/linux/platform_data/mv_usb.h > @@ -45,6 +45,7 @@ struct mv_usb_platform_data { > int (*phy_init)(unsigned int regbase); > void (*phy_deinit)(unsigned int regbase); > int (*set_vbus)(unsigned int vbus); > + int (*private_init)(unsigned int opregs, unsigned int phyregs); > }; > > #endif > -- > 1.7.4.1 -- balbi
Attachment:
signature.asc
Description: Digital signature