Re: [PATCH 1/7 v2] USB: EHCI: Add Marvell Host Controller driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux