diff options
Diffstat (limited to 'drivers/usb/chipidea')
| -rw-r--r-- | drivers/usb/chipidea/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/usb/chipidea/Makefile | 4 | ||||
| -rw-r--r-- | drivers/usb/chipidea/bits.h | 13 | ||||
| -rw-r--r-- | drivers/usb/chipidea/ci.h | 114 | ||||
| -rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.c | 55 | ||||
| -rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.h | 5 | ||||
| -rw-r--r-- | drivers/usb/chipidea/ci_hdrc_msm.c | 24 | ||||
| -rw-r--r-- | drivers/usb/chipidea/ci_hdrc_pci.c | 9 | ||||
| -rw-r--r-- | drivers/usb/chipidea/ci_hdrc_zevio.c | 72 | ||||
| -rw-r--r-- | drivers/usb/chipidea/core.c | 345 | ||||
| -rw-r--r-- | drivers/usb/chipidea/debug.c | 135 | ||||
| -rw-r--r-- | drivers/usb/chipidea/host.c | 33 | ||||
| -rw-r--r-- | drivers/usb/chipidea/otg.c | 48 | ||||
| -rw-r--r-- | drivers/usb/chipidea/otg.h | 25 | ||||
| -rw-r--r-- | drivers/usb/chipidea/otg_fsm.c | 842 | ||||
| -rw-r--r-- | drivers/usb/chipidea/otg_fsm.h | 129 | ||||
| -rw-r--r-- | drivers/usb/chipidea/udc.c | 494 | ||||
| -rw-r--r-- | drivers/usb/chipidea/usbmisc_imx.c | 114 | 
18 files changed, 1999 insertions, 464 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 4a851e15e58..77b47d82c9a 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -1,6 +1,6 @@  config USB_CHIPIDEA  	tristate "ChipIdea Highspeed Dual Role Controller" -	depends on (USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET) +	depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA  	help  	  Say Y here if your system has a dual role high speed USB  	  controller based on ChipIdea silicon IP. Currently, only the diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index a99d980454a..2f099c7df7b 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -6,10 +6,12 @@ ci_hdrc-y				:= core.o otg.o  ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC)	+= udc.o  ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST)	+= host.o  ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG)	+= debug.o +ci_hdrc-$(CONFIG_USB_OTG_FSM)		+= otg_fsm.o  # Glue/Bridge layers go here  obj-$(CONFIG_USB_CHIPIDEA)	+= ci_hdrc_msm.o +obj-$(CONFIG_USB_CHIPIDEA)	+= ci_hdrc_zevio.o  # PCI doesn't provide stubs, need to check  ifneq ($(CONFIG_PCI),) @@ -17,5 +19,5 @@ ifneq ($(CONFIG_PCI),)  endif  ifneq ($(CONFIG_OF),) -	obj-$(CONFIG_USB_CHIPIDEA)	+= ci_hdrc_imx.o usbmisc_imx.o +	obj-$(CONFIG_USB_CHIPIDEA)	+= usbmisc_imx.o ci_hdrc_imx.o  endif diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index 464584c6cca..ca57e3dcd3d 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -44,17 +44,28 @@  #define DEVICEADDR_USBADR     (0x7FUL << 25)  /* PORTSC */ +#define PORTSC_CCS            BIT(0) +#define PORTSC_CSC            BIT(1) +#define PORTSC_PEC            BIT(3) +#define PORTSC_OCC            BIT(5)  #define PORTSC_FPR            BIT(6)  #define PORTSC_SUSP           BIT(7)  #define PORTSC_HSP            BIT(9) +#define PORTSC_PP             BIT(12)  #define PORTSC_PTC            (0x0FUL << 16) +#define PORTSC_PHCD(d)	      ((d) ? BIT(22) : BIT(23))  /* PTS and PTW for non lpm version only */ +#define PORTSC_PFSC           BIT(24)  #define PORTSC_PTS(d)						\  	(u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0))  #define PORTSC_PTW            BIT(28)  #define PORTSC_STS            BIT(29) +#define PORTSC_W1C_BITS						\ +	(PORTSC_CSC | PORTSC_PEC | PORTSC_OCC) +  /* DEVLC */ +#define DEVLC_PFSC            BIT(23)  #define DEVLC_PSPD            (0x03UL << 25)  #define DEVLC_PSPD_HS         (0x02UL << 25)  #define DEVLC_PTW             BIT(27) @@ -69,6 +80,8 @@  /* OTGSC */  #define OTGSC_IDPU	      BIT(5) +#define OTGSC_HADP	      BIT(6) +#define OTGSC_HABA	      BIT(7)  #define OTGSC_ID	      BIT(8)  #define OTGSC_AVV	      BIT(9)  #define OTGSC_ASV	      BIT(10) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 1c94fc5257f..9563cb56d56 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -17,6 +17,7 @@  #include <linux/irqreturn.h>  #include <linux/usb.h>  #include <linux/usb/gadget.h> +#include <linux/usb/otg-fsm.h>  /******************************************************************************   * DEFINE @@ -26,6 +27,35 @@  #define ENDPT_MAX          32  /****************************************************************************** + * REGISTERS + *****************************************************************************/ +/* register indices */ +enum ci_hw_regs { +	CAP_CAPLENGTH, +	CAP_HCCPARAMS, +	CAP_DCCPARAMS, +	CAP_TESTMODE, +	CAP_LAST = CAP_TESTMODE, +	OP_USBCMD, +	OP_USBSTS, +	OP_USBINTR, +	OP_DEVICEADDR, +	OP_ENDPTLISTADDR, +	OP_PORTSC, +	OP_DEVLC, +	OP_OTGSC, +	OP_USBMODE, +	OP_ENDPTSETUPSTAT, +	OP_ENDPTPRIME, +	OP_ENDPTFLUSH, +	OP_ENDPTSTAT, +	OP_ENDPTCOMPLETE, +	OP_ENDPTCTRL, +	/* endptctrl1..15 follow */ +	OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2, +}; + +/******************************************************************************   * STRUCTURES   *****************************************************************************/  /** @@ -98,7 +128,7 @@ struct hw_bank {  	void __iomem	*cap;  	void __iomem	*op;  	size_t		size; -	void __iomem	**regmap; +	void __iomem	*regmap[OP_LAST + 1];  };  /** @@ -110,6 +140,8 @@ struct hw_bank {   * @roles: array of supported roles for this controller   * @role: current role   * @is_otg: if the device is otg-capable + * @fsm: otg finite state machine + * @fsm_timer: pointer to timer list of otg fsm   * @work: work for role changing   * @wq: workqueue thread   * @qh_pool: allocation pool for queue heads @@ -135,6 +167,7 @@ struct hw_bank {   * @id_event: indicates there is an id event, and handled at ci_otg_work   * @b_sess_valid_event: indicates there is a vbus event, and handled   * at ci_otg_work + * @imx28_write_fix: Freescale imx28 needs swp instruction for writing   */  struct ci_hdrc {  	struct device			*dev; @@ -144,6 +177,8 @@ struct ci_hdrc {  	struct ci_role_driver		*roles[CI_ROLE_END];  	enum ci_role			role;  	bool				is_otg; +	struct otg_fsm			fsm; +	struct ci_otg_fsm_timer_list	*fsm_timer;  	struct work_struct		work;  	struct workqueue_struct		*wq; @@ -166,13 +201,12 @@ struct ci_hdrc {  	struct ci_hdrc_platform_data	*platdata;  	int				vbus_active; -	/* FIXME: some day, we'll not use global phy */ -	bool				global_phy;  	struct usb_phy			*transceiver;  	struct usb_hcd			*hcd;  	struct dentry			*debugfs;  	bool				id_event;  	bool				b_sess_valid_event; +	bool				imx28_write_fix;  };  static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) @@ -209,38 +243,6 @@ static inline void ci_role_stop(struct ci_hdrc *ci)  	ci->roles[role]->stop(ci);  } -/****************************************************************************** - * REGISTERS - *****************************************************************************/ -/* register size */ -#define REG_BITS   (32) - -/* register indices */ -enum ci_hw_regs { -	CAP_CAPLENGTH, -	CAP_HCCPARAMS, -	CAP_DCCPARAMS, -	CAP_TESTMODE, -	CAP_LAST = CAP_TESTMODE, -	OP_USBCMD, -	OP_USBSTS, -	OP_USBINTR, -	OP_DEVICEADDR, -	OP_ENDPTLISTADDR, -	OP_PORTSC, -	OP_DEVLC, -	OP_OTGSC, -	OP_USBMODE, -	OP_ENDPTSETUPSTAT, -	OP_ENDPTPRIME, -	OP_ENDPTFLUSH, -	OP_ENDPTSTAT, -	OP_ENDPTCOMPLETE, -	OP_ENDPTCTRL, -	/* endptctrl1..15 follow */ -	OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2, -}; -  /**   * hw_read: reads from a hw register   * @reg:  register index @@ -253,6 +255,26 @@ static inline u32 hw_read(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask)  	return ioread32(ci->hw_bank.regmap[reg]) & mask;  } +#ifdef CONFIG_SOC_IMX28 +static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr) +{ +	__asm__ ("swp %0, %0, [%1]" : : "r"(val), "r"(addr)); +} +#else +static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr) +{ +} +#endif + +static inline void __hw_write(struct ci_hdrc *ci, u32 val, +		void __iomem *addr) +{ +	if (ci->imx28_write_fix) +		imx28_ci_writel(val, addr); +	else +		iowrite32(val, addr); +} +  /**   * hw_write: writes to a hw register   * @reg:  register index @@ -266,7 +288,7 @@ static inline void hw_write(struct ci_hdrc *ci, enum ci_hw_regs reg,  		data = (ioread32(ci->hw_bank.regmap[reg]) & ~mask)  			| (data & mask); -	iowrite32(data, ci->hw_bank.regmap[reg]); +	__hw_write(ci, data, ci->hw_bank.regmap[reg]);  }  /** @@ -281,7 +303,7 @@ static inline u32 hw_test_and_clear(struct ci_hdrc *ci, enum ci_hw_regs reg,  {  	u32 val = ioread32(ci->hw_bank.regmap[reg]) & mask; -	iowrite32(val, ci->hw_bank.regmap[reg]); +	__hw_write(ci, val, ci->hw_bank.regmap[reg]);  	return val;  } @@ -302,6 +324,24 @@ static inline u32 hw_test_and_write(struct ci_hdrc *ci, enum ci_hw_regs reg,  	return (val & mask) >> __ffs(mask);  } +/** + * ci_otg_is_fsm_mode: runtime check if otg controller + * is in otg fsm mode. + */ +static inline bool ci_otg_is_fsm_mode(struct ci_hdrc *ci) +{ +#ifdef CONFIG_USB_OTG_FSM +	return ci->is_otg && ci->roles[CI_ROLE_HOST] && +					ci->roles[CI_ROLE_GADGET]; +#else +	return false; +#endif +} + +u32 hw_read_intr_enable(struct ci_hdrc *ci); + +u32 hw_read_intr_status(struct ci_hdrc *ci); +  int hw_device_reset(struct ci_hdrc *ci, u32 mode);  int hw_port_test_set(struct ci_hdrc *ci, u8 mode); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 74d998d9b45..2e58f8dfd31 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -23,6 +23,26 @@  #include "ci.h"  #include "ci_hdrc_imx.h" +#define CI_HDRC_IMX_IMX28_WRITE_FIX BIT(0) + +struct ci_hdrc_imx_platform_flag { +	unsigned int flags; +}; + +static const struct ci_hdrc_imx_platform_flag imx27_usb_data = { +}; + +static const struct ci_hdrc_imx_platform_flag imx28_usb_data = { +	.flags = CI_HDRC_IMX_IMX28_WRITE_FIX, +}; + +static const struct of_device_id ci_hdrc_imx_dt_ids[] = { +	{ .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, +	{ .compatible = "fsl,imx27-usb", .data = &imx27_usb_data}, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); +  struct ci_hdrc_imx_data {  	struct usb_phy *phy;  	struct platform_device *ci_pdev; @@ -76,12 +96,15 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)  {  	struct ci_hdrc_imx_data *data;  	struct ci_hdrc_platform_data pdata = { -		.name		= "ci_hdrc_imx", +		.name		= dev_name(&pdev->dev),  		.capoffset	= DEF_CAPOFFSET,  		.flags		= CI_HDRC_REQUIRE_TRANSCEIVER |  				  CI_HDRC_DISABLE_STREAMING,  	};  	int ret; +	const struct of_device_id *of_id = +			of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev); +	const struct ci_hdrc_imx_platform_flag *imx_platform_flag = of_id->data;  	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);  	if (!data) { @@ -108,23 +131,19 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)  	}  	data->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "fsl,usbphy", 0); -	if (!IS_ERR(data->phy)) { -		ret = usb_phy_init(data->phy); -		if (ret) { -			dev_err(&pdev->dev, "unable to init phy: %d\n", ret); -			goto err_clk; -		} -	} else if (PTR_ERR(data->phy) == -EPROBE_DEFER) { -		ret = -EPROBE_DEFER; +	if (IS_ERR(data->phy)) { +		ret = PTR_ERR(data->phy);  		goto err_clk;  	}  	pdata.phy = data->phy; -	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); +	if (imx_platform_flag->flags & CI_HDRC_IMX_IMX28_WRITE_FIX) +		pdata.flags |= CI_HDRC_IMX28_WRITE_FIX; + +	ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); +	if (ret) +		goto err_clk;  	if (data->usbmisc_data) {  		ret = imx_usbmisc_init(data->usbmisc_data); @@ -175,21 +194,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev)  	pm_runtime_disable(&pdev->dev);  	ci_hdrc_remove_device(data->ci_pdev); - -	if (data->phy) -		usb_phy_shutdown(data->phy); -  	clk_disable_unprepare(data->clk);  	return 0;  } -static const struct of_device_id ci_hdrc_imx_dt_ids[] = { -	{ .compatible = "fsl,imx27-usb", }, -	{ /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); -  static struct platform_driver ci_hdrc_imx_driver = {  	.probe = ci_hdrc_imx_probe,  	.remove = ci_hdrc_imx_remove, diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index c7271590dd0..996ec93467b 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -9,6 +9,9 @@   * http://www.gnu.org/copyleft/gpl.html   */ +#ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H +#define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H +  struct imx_usbmisc_data {  	int index; @@ -18,3 +21,5 @@ struct imx_usbmisc_data {  int imx_usbmisc_init(struct imx_usbmisc_data *);  int imx_usbmisc_init_post(struct imx_usbmisc_data *); + +#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c index 2d51d852b47..d72b9d2de2c 100644 --- a/drivers/usb/chipidea/ci_hdrc_msm.c +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -47,6 +47,7 @@ static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)  static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {  	.name			= "ci_hdrc_msm", +	.capoffset		= DEF_CAPOFFSET,  	.flags			= CI_HDRC_REGS_SHARED |  				  CI_HDRC_REQUIRE_TRANSCEIVER |  				  CI_HDRC_DISABLE_STREAMING, @@ -57,9 +58,21 @@ static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {  static int ci_hdrc_msm_probe(struct platform_device *pdev)  {  	struct platform_device *plat_ci; +	struct usb_phy *phy;  	dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); +	/* +	 * OTG(PHY) driver takes care of PHY initialization, clock management, +	 * powering up VBUS, mapping of registers address space and power +	 * management. +	 */ +	phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); +	if (IS_ERR(phy)) +		return PTR_ERR(phy); + +	ci_hdrc_msm_platdata.phy = phy; +  	plat_ci = ci_hdrc_add_device(&pdev->dev,  				pdev->resource, pdev->num_resources,  				&ci_hdrc_msm_platdata); @@ -86,10 +99,19 @@ static int ci_hdrc_msm_remove(struct platform_device *pdev)  	return 0;  } +static const struct of_device_id msm_ci_dt_match[] = { +	{ .compatible = "qcom,ci-hdrc", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, msm_ci_dt_match); +  static struct platform_driver ci_hdrc_msm_driver = {  	.probe = ci_hdrc_msm_probe,  	.remove = ci_hdrc_msm_remove, -	.driver = { .name = "msm_hsusb", }, +	.driver = { +		.name = "msm_hsusb", +		.of_match_table = msm_ci_dt_match, +	},  };  module_platform_driver(ci_hdrc_msm_driver); diff --git a/drivers/usb/chipidea/ci_hdrc_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c index 042320a6c6c..241ae3444fd 100644 --- a/drivers/usb/chipidea/ci_hdrc_pci.c +++ b/drivers/usb/chipidea/ci_hdrc_pci.c @@ -112,7 +112,7 @@ static void ci_hdrc_pci_remove(struct pci_dev *pdev)   *   * Check "pci.h" for details   */ -static DEFINE_PCI_DEVICE_TABLE(ci_hdrc_pci_id_table) = { +static const struct pci_device_id ci_hdrc_pci_id_table[] = {  	{  		PCI_DEVICE(0x153F, 0x1004),  		.driver_data = (kernel_ulong_t)&pci_platdata, @@ -129,7 +129,12 @@ static DEFINE_PCI_DEVICE_TABLE(ci_hdrc_pci_id_table) = {  		PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829),  		.driver_data = (kernel_ulong_t)&penwell_pci_platdata,  	}, -	{ 0, 0, 0, 0, 0, 0, 0 /* end: all zeroes */ } +	{ +		/* Intel Clovertrail */ +		PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe006), +		.driver_data = (kernel_ulong_t)&penwell_pci_platdata, +	}, +	{ 0 } /* end: all zeroes */  };  MODULE_DEVICE_TABLE(pci, ci_hdrc_pci_id_table); diff --git a/drivers/usb/chipidea/ci_hdrc_zevio.c b/drivers/usb/chipidea/ci_hdrc_zevio.c new file mode 100644 index 00000000000..3bf6489ef5e --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_zevio.c @@ -0,0 +1,72 @@ +/* + *	Copyright (C) 2013 Daniel Tang <tangrs@tangrs.id.au> + * + * 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. + * + * Based off drivers/usb/chipidea/ci_hdrc_msm.c + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb/gadget.h> +#include <linux/usb/chipidea.h> + +#include "ci.h" + +static struct ci_hdrc_platform_data ci_hdrc_zevio_platdata = { +	.name			= "ci_hdrc_zevio", +	.flags			= CI_HDRC_REGS_SHARED, +	.capoffset		= DEF_CAPOFFSET, +}; + +static int ci_hdrc_zevio_probe(struct platform_device *pdev) +{ +	struct platform_device *ci_pdev; + +	dev_dbg(&pdev->dev, "ci_hdrc_zevio_probe\n"); + +	ci_pdev = ci_hdrc_add_device(&pdev->dev, +				pdev->resource, pdev->num_resources, +				&ci_hdrc_zevio_platdata); + +	if (IS_ERR(ci_pdev)) { +		dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); +		return PTR_ERR(ci_pdev); +	} + +	platform_set_drvdata(pdev, ci_pdev); + +	return 0; +} + +static int ci_hdrc_zevio_remove(struct platform_device *pdev) +{ +	struct platform_device *ci_pdev = platform_get_drvdata(pdev); + +	ci_hdrc_remove_device(ci_pdev); + +	return 0; +} + +static const struct of_device_id ci_hdrc_zevio_dt_ids[] = { +	{ .compatible = "lsi,zevio-usb", }, +	{ /* sentinel */ } +}; + +static struct platform_driver ci_hdrc_zevio_driver = { +	.probe = ci_hdrc_zevio_probe, +	.remove = ci_hdrc_zevio_remove, +	.driver = { +		.name = "zevio_usb", +		.owner = THIS_MODULE, +		.of_match_table = ci_hdrc_zevio_dt_ids, +	}, +}; + +MODULE_DEVICE_TABLE(of, ci_hdrc_zevio_dt_ids); +module_platform_driver(ci_hdrc_zevio_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 94626409559..619d13e2999 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -23,7 +23,7 @@   * - BUS:    bus glue code, bus abstraction layer   *   * Compile Options - * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - CONFIG_USB_CHIPIDEA_DEBUG: enable debug facilities   * - STALL_IN:  non-empty bulk-in pipes cannot be halted   *              if defined mass storage compliance succeeds but with warnings   *              => case 4: Hi >  Dn @@ -42,10 +42,6 @@   * - Not Supported: 15 & 16 (ISO)   *   * TODO List - * - OTG - * - Interrupt Traffic - * - GET_STATUS(device) - always reports 0 - * - Gadget API (majority of optional features)   * - Suspend & Remote Wakeup   */  #include <linux/delay.h> @@ -64,6 +60,7 @@  #include <linux/usb/otg.h>  #include <linux/usb/chipidea.h>  #include <linux/usb/of.h> +#include <linux/of.h>  #include <linux/phy.h>  #include <linux/regulator/consumer.h> @@ -73,63 +70,57 @@  #include "host.h"  #include "debug.h"  #include "otg.h" +#include "otg_fsm.h"  /* Controller register map */ -static uintptr_t ci_regs_nolpm[] = { -	[CAP_CAPLENGTH]		= 0x000UL, -	[CAP_HCCPARAMS]		= 0x008UL, -	[CAP_DCCPARAMS]		= 0x024UL, -	[CAP_TESTMODE]		= 0x038UL, -	[OP_USBCMD]		= 0x000UL, -	[OP_USBSTS]		= 0x004UL, -	[OP_USBINTR]		= 0x008UL, -	[OP_DEVICEADDR]		= 0x014UL, -	[OP_ENDPTLISTADDR]	= 0x018UL, -	[OP_PORTSC]		= 0x044UL, -	[OP_DEVLC]		= 0x084UL, -	[OP_OTGSC]		= 0x064UL, -	[OP_USBMODE]		= 0x068UL, -	[OP_ENDPTSETUPSTAT]	= 0x06CUL, -	[OP_ENDPTPRIME]		= 0x070UL, -	[OP_ENDPTFLUSH]		= 0x074UL, -	[OP_ENDPTSTAT]		= 0x078UL, -	[OP_ENDPTCOMPLETE]	= 0x07CUL, -	[OP_ENDPTCTRL]		= 0x080UL, +static const u8 ci_regs_nolpm[] = { +	[CAP_CAPLENGTH]		= 0x00U, +	[CAP_HCCPARAMS]		= 0x08U, +	[CAP_DCCPARAMS]		= 0x24U, +	[CAP_TESTMODE]		= 0x38U, +	[OP_USBCMD]		= 0x00U, +	[OP_USBSTS]		= 0x04U, +	[OP_USBINTR]		= 0x08U, +	[OP_DEVICEADDR]		= 0x14U, +	[OP_ENDPTLISTADDR]	= 0x18U, +	[OP_PORTSC]		= 0x44U, +	[OP_DEVLC]		= 0x84U, +	[OP_OTGSC]		= 0x64U, +	[OP_USBMODE]		= 0x68U, +	[OP_ENDPTSETUPSTAT]	= 0x6CU, +	[OP_ENDPTPRIME]		= 0x70U, +	[OP_ENDPTFLUSH]		= 0x74U, +	[OP_ENDPTSTAT]		= 0x78U, +	[OP_ENDPTCOMPLETE]	= 0x7CU, +	[OP_ENDPTCTRL]		= 0x80U,  }; -static uintptr_t ci_regs_lpm[] = { -	[CAP_CAPLENGTH]		= 0x000UL, -	[CAP_HCCPARAMS]		= 0x008UL, -	[CAP_DCCPARAMS]		= 0x024UL, -	[CAP_TESTMODE]		= 0x0FCUL, -	[OP_USBCMD]		= 0x000UL, -	[OP_USBSTS]		= 0x004UL, -	[OP_USBINTR]		= 0x008UL, -	[OP_DEVICEADDR]		= 0x014UL, -	[OP_ENDPTLISTADDR]	= 0x018UL, -	[OP_PORTSC]		= 0x044UL, -	[OP_DEVLC]		= 0x084UL, -	[OP_OTGSC]		= 0x0C4UL, -	[OP_USBMODE]		= 0x0C8UL, -	[OP_ENDPTSETUPSTAT]	= 0x0D8UL, -	[OP_ENDPTPRIME]		= 0x0DCUL, -	[OP_ENDPTFLUSH]		= 0x0E0UL, -	[OP_ENDPTSTAT]		= 0x0E4UL, -	[OP_ENDPTCOMPLETE]	= 0x0E8UL, -	[OP_ENDPTCTRL]		= 0x0ECUL, +static const u8 ci_regs_lpm[] = { +	[CAP_CAPLENGTH]		= 0x00U, +	[CAP_HCCPARAMS]		= 0x08U, +	[CAP_DCCPARAMS]		= 0x24U, +	[CAP_TESTMODE]		= 0xFCU, +	[OP_USBCMD]		= 0x00U, +	[OP_USBSTS]		= 0x04U, +	[OP_USBINTR]		= 0x08U, +	[OP_DEVICEADDR]		= 0x14U, +	[OP_ENDPTLISTADDR]	= 0x18U, +	[OP_PORTSC]		= 0x44U, +	[OP_DEVLC]		= 0x84U, +	[OP_OTGSC]		= 0xC4U, +	[OP_USBMODE]		= 0xC8U, +	[OP_ENDPTSETUPSTAT]	= 0xD8U, +	[OP_ENDPTPRIME]		= 0xDCU, +	[OP_ENDPTFLUSH]		= 0xE0U, +	[OP_ENDPTSTAT]		= 0xE4U, +	[OP_ENDPTCOMPLETE]	= 0xE8U, +	[OP_ENDPTCTRL]		= 0xECU,  };  static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)  {  	int i; -	kfree(ci->hw_bank.regmap); - -	ci->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), -				     GFP_KERNEL); -	if (!ci->hw_bank.regmap) -		return -ENOMEM; -  	for (i = 0; i < OP_ENDPTCTRL; i++)  		ci->hw_bank.regmap[i] =  			(i <= CAP_LAST ? ci->hw_bank.cap : ci->hw_bank.op) + @@ -146,6 +137,26 @@ static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)  }  /** + * hw_read_intr_enable: returns interrupt enable register + * + * This function returns register data + */ +u32 hw_read_intr_enable(struct ci_hdrc *ci) +{ +	return hw_read(ci, OP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * This function returns register data + */ +u32 hw_read_intr_status(struct ci_hdrc *ci) +{ +	return hw_read(ci, OP_USBSTS, ~0); +} + +/**   * hw_port_test_set: writes port test mode (execute without interruption)   * @mode: new value   * @@ -172,6 +183,26 @@ u8 hw_port_test_get(struct ci_hdrc *ci)  	return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC);  } +/* The PHY enters/leaves low power mode */ +static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +{ +	enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC; +	bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm))); + +	if (enable && !lpm) { +		hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm), +				PORTSC_PHCD(ci->hw_bank.lpm)); +	} else  if (!enable && lpm) { +		hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm), +				0); +		/*  +		 * the PHY needs some time (less +		 * than 1ms) to leave low power mode. +		 */ +		usleep_range(1000, 1100); +	} +} +  static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)  {  	u32 reg; @@ -187,7 +218,8 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)  	reg = hw_read(ci, CAP_HCCPARAMS, HCCPARAMS_LEN) >>  		__ffs(HCCPARAMS_LEN);  	ci->hw_bank.lpm  = reg; -	hw_alloc_regmap(ci, !!reg); +	if (reg) +		hw_alloc_regmap(ci, !!reg);  	ci->hw_bank.size = ci->hw_bank.op - ci->hw_bank.abs;  	ci->hw_bank.size += OP_LAST;  	ci->hw_bank.size /= sizeof(u32); @@ -199,6 +231,8 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)  	if (ci->hw_ep_max > ENDPT_MAX)  		return -ENODEV; +	ci_hdrc_enter_lpm(ci, false); +  	/* Disable all interrupts bits */  	hw_write(ci, OP_USBINTR, 0xffffffff, 0); @@ -219,7 +253,7 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)  static void hw_phymode_configure(struct ci_hdrc *ci)  { -	u32 portsc, lpm, sts; +	u32 portsc, lpm, sts = 0;  	switch (ci->platdata->phy_mode) {  	case USBPHY_INTERFACE_MODE_UTMI: @@ -249,14 +283,49 @@ static void hw_phymode_configure(struct ci_hdrc *ci)  	if (ci->hw_bank.lpm) {  		hw_write(ci, OP_DEVLC, DEVLC_PTS(7) | DEVLC_PTW, lpm); -		hw_write(ci, OP_DEVLC, DEVLC_STS, sts); +		if (sts) +			hw_write(ci, OP_DEVLC, DEVLC_STS, DEVLC_STS);  	} else {  		hw_write(ci, OP_PORTSC, PORTSC_PTS(7) | PORTSC_PTW, portsc); -		hw_write(ci, OP_PORTSC, PORTSC_STS, sts); +		if (sts) +			hw_write(ci, OP_PORTSC, PORTSC_STS, PORTSC_STS);  	}  }  /** + * ci_usb_phy_init: initialize phy according to different phy type + * @ci: the controller +  * + * This function returns an error code if usb_phy_init has failed + */ +static int ci_usb_phy_init(struct ci_hdrc *ci) +{ +	int ret; + +	switch (ci->platdata->phy_mode) { +	case USBPHY_INTERFACE_MODE_UTMI: +	case USBPHY_INTERFACE_MODE_UTMIW: +	case USBPHY_INTERFACE_MODE_HSIC: +		ret = usb_phy_init(ci->transceiver); +		if (ret) +			return ret; +		hw_phymode_configure(ci); +		break; +	case USBPHY_INTERFACE_MODE_ULPI: +	case USBPHY_INTERFACE_MODE_SERIAL: +		hw_phymode_configure(ci); +		ret = usb_phy_init(ci->transceiver); +		if (ret) +			return ret; +		break; +	default: +		ret = usb_phy_init(ci->transceiver); +	} + +	return ret; +} + +/**   * hw_device_reset: resets chip (execute without interruption)   * @ci: the controller    * @@ -279,6 +348,13 @@ int hw_device_reset(struct ci_hdrc *ci, u32 mode)  	if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING)  		hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); +	if (ci->platdata->flags & CI_HDRC_FORCE_FULLSPEED) { +		if (ci->hw_bank.lpm) +			hw_write(ci, OP_DEVLC, DEVLC_PFSC, DEVLC_PFSC); +		else +			hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC); +	} +  	/* USBMODE should be configured step by step */  	hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE);  	hw_write(ci, OP_USBMODE, USBMODE_CM, mode); @@ -332,8 +408,14 @@ static irqreturn_t ci_irq(int irq, void *data)  	irqreturn_t ret = IRQ_NONE;  	u32 otgsc = 0; -	if (ci->is_otg) -		otgsc = hw_read(ci, OP_OTGSC, ~0); +	if (ci->is_otg) { +		otgsc = hw_read_otgsc(ci, ~0); +		if (ci_otg_is_fsm_mode(ci)) { +			ret = ci_otg_fsm_irq(ci); +			if (ret == IRQ_HANDLED) +				return ret; +		} +	}  	/*  	 * Handle id change interrupt, it indicates device/host function @@ -341,9 +423,9 @@ static irqreturn_t ci_irq(int irq, void *data)  	 */  	if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) {  		ci->id_event = true; -		ci_clear_otg_interrupt(ci, OTGSC_IDIS); -		disable_irq_nosync(ci->irq); -		queue_work(ci->wq, &ci->work); +		/* Clear ID change irq status */ +		hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); +		ci_otg_queue_work(ci);  		return IRQ_HANDLED;  	} @@ -353,9 +435,9 @@ static irqreturn_t ci_irq(int irq, void *data)  	 */  	if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {  		ci->b_sess_valid_event = true; -		ci_clear_otg_interrupt(ci, OTGSC_BSVIS); -		disable_irq_nosync(ci->irq); -		queue_work(ci->wq, &ci->work); +		/* Clear BSV irq */ +		hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); +		ci_otg_queue_work(ci);  		return IRQ_HANDLED;  	} @@ -369,18 +451,33 @@ static irqreturn_t ci_irq(int irq, void *data)  static int ci_get_platdata(struct device *dev,  		struct ci_hdrc_platform_data *platdata)  { -	/* Get the vbus regulator */ -	platdata->reg_vbus = devm_regulator_get(dev, "vbus"); -	if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) { -		return -EPROBE_DEFER; -	} else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) { -		platdata->reg_vbus = NULL; /* no vbus regualator is needed */ -	} else if (IS_ERR(platdata->reg_vbus)) { -		dev_err(dev, "Getting regulator error: %ld\n", -			PTR_ERR(platdata->reg_vbus)); -		return PTR_ERR(platdata->reg_vbus); +	if (!platdata->phy_mode) +		platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); + +	if (!platdata->dr_mode) +		platdata->dr_mode = of_usb_get_dr_mode(dev->of_node); + +	if (platdata->dr_mode == USB_DR_MODE_UNKNOWN) +		platdata->dr_mode = USB_DR_MODE_OTG; + +	if (platdata->dr_mode != USB_DR_MODE_PERIPHERAL) { +		/* Get the vbus regulator */ +		platdata->reg_vbus = devm_regulator_get(dev, "vbus"); +		if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) { +			return -EPROBE_DEFER; +		} else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) { +			/* no vbus regualator is needed */ +			platdata->reg_vbus = NULL; +		} else if (IS_ERR(platdata->reg_vbus)) { +			dev_err(dev, "Getting regulator error: %ld\n", +				PTR_ERR(platdata->reg_vbus)); +			return PTR_ERR(platdata->reg_vbus); +		}  	} +	if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL) +		platdata->flags |= CI_HDRC_FORCE_FULLSPEED; +  	return 0;  } @@ -458,11 +555,8 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)  		ci->is_otg = (hw_read(ci, CAP_DCCPARAMS,  				DCCPARAMS_DC | DCCPARAMS_HC)  					== (DCCPARAMS_DC | DCCPARAMS_HC)); -	if (ci->is_otg) { +	if (ci->is_otg)  		dev_dbg(ci->dev, "It is OTG capable controller\n"); -		ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS); -		ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS); -	}  }  static int ci_hdrc_probe(struct platform_device *pdev) @@ -473,9 +567,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)  	void __iomem	*base;  	int		ret;  	enum usb_dr_mode dr_mode; -	struct device_node *of_node = dev->of_node ?: dev->parent->of_node; -	if (!dev->platform_data) { +	if (!dev_get_platdata(dev)) {  		dev_err(dev, "platform data missing\n");  		return -ENODEV;  	} @@ -492,11 +585,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)  	}  	ci->dev = dev; -	ci->platdata = dev->platform_data; -	if (ci->platdata->phy) -		ci->transceiver = ci->platdata->phy; -	else -		ci->global_phy = true; +	ci->platdata = dev_get_platdata(dev); +	ci->imx28_write_fix = !!(ci->platdata->flags & +		CI_HDRC_IMX28_WRITE_FIX);  	ret = hw_device_init(ci, base);  	if (ret < 0) { @@ -504,27 +595,49 @@ static int ci_hdrc_probe(struct platform_device *pdev)  		return -ENODEV;  	} +	if (ci->platdata->phy) +		ci->transceiver = ci->platdata->phy; +	else +		ci->transceiver = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + +	if (IS_ERR(ci->transceiver)) { +		ret = PTR_ERR(ci->transceiver); +		/* +		 * if -ENXIO is returned, it means PHY layer wasn't +		 * enabled, so it makes no sense to return -EPROBE_DEFER +		 * in that case, since no PHY driver will ever probe. +		 */ +		if (ret == -ENXIO) +			return ret; + +		dev_err(dev, "no usb2 phy configured\n"); +		return -EPROBE_DEFER; +	} + +	ret = ci_usb_phy_init(ci); +	if (ret) { +		dev_err(dev, "unable to init phy: %d\n", ret); +		return ret; +	} else { +		/*  +		 * The delay to sync PHY's status, the maximum delay is +		 * 2ms since the otgsc uses 1ms timer to debounce the +		 * PHY's input +		 */ +		usleep_range(2000, 2500); +	} +  	ci->hw_bank.phys = res->start;  	ci->irq = platform_get_irq(pdev, 0);  	if (ci->irq < 0) {  		dev_err(dev, "missing IRQ\n"); -		return -ENODEV; +		ret = ci->irq; +		goto deinit_phy;  	}  	ci_get_otg_capable(ci); -	if (!ci->platdata->phy_mode) -		ci->platdata->phy_mode = of_usb_get_phy_mode(of_node); - -	hw_phymode_configure(ci); - -	if (!ci->platdata->dr_mode) -		ci->platdata->dr_mode = of_usb_get_dr_mode(of_node); - -	if (ci->platdata->dr_mode == USB_DR_MODE_UNKNOWN) -		ci->platdata->dr_mode = USB_DR_MODE_OTG; -  	dr_mode = ci->platdata->dr_mode;  	/* initialize role(s) before the interrupt is requested */  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { @@ -541,10 +654,14 @@ static int ci_hdrc_probe(struct platform_device *pdev)  	if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) {  		dev_err(dev, "no supported roles\n"); -		return -ENODEV; +		ret = -ENODEV; +		goto deinit_phy;  	}  	if (ci->is_otg) { +		/* Disable and clear all OTG irq */ +		hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, +							OTGSC_INT_STATUS_BITS);  		ret = ci_hdrc_otg_init(ci);  		if (ret) {  			dev_err(dev, "init otg fails, ret = %d\n", ret); @@ -554,13 +671,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)  	if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {  		if (ci->is_otg) { -			/* -			 * ID pin needs 1ms debouce time, -			 * we delay 2ms for safe. -			 */ -			mdelay(2);  			ci->role = ci_otg_role(ci); -			ci_enable_otg_interrupt(ci, OTGSC_IDIE); +			/* Enable ID change irq */ +			hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);  		} else {  			/*  			 * If the controller is not OTG capable, but support @@ -575,10 +688,17 @@ static int ci_hdrc_probe(struct platform_device *pdev)  			: CI_ROLE_GADGET;  	} -	ret = ci_role_start(ci, ci->role); -	if (ret) { -		dev_err(dev, "can't start %s role\n", ci_role(ci)->name); -		goto stop; +	/* only update vbus status for peripheral */ +	if (ci->role == CI_ROLE_GADGET) +		ci_handle_vbus_change(ci); + +	if (!ci_otg_is_fsm_mode(ci)) { +		ret = ci_role_start(ci, ci->role); +		if (ret) { +			dev_err(dev, "can't start %s role\n", +						ci_role(ci)->name); +			goto stop; +		}  	}  	platform_set_drvdata(pdev, ci); @@ -587,6 +707,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)  	if (ret)  		goto stop; +	if (ci_otg_is_fsm_mode(ci)) +		ci_hdrc_otg_fsm_start(ci); +  	ret = dbg_create_files(ci);  	if (!ret)  		return 0; @@ -594,6 +717,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)  	free_irq(ci->irq, ci);  stop:  	ci_role_destroy(ci); +deinit_phy: +	usb_phy_shutdown(ci->transceiver);  	return ret;  } @@ -605,6 +730,9 @@ static int ci_hdrc_remove(struct platform_device *pdev)  	dbg_remove_files(ci);  	free_irq(ci->irq, ci);  	ci_role_destroy(ci); +	ci_hdrc_enter_lpm(ci, true); +	usb_phy_shutdown(ci->transceiver); +	kfree(ci->hw_bank.regmap);  	return 0;  } @@ -614,6 +742,7 @@ static struct platform_driver ci_hdrc_driver = {  	.remove	= ci_hdrc_remove,  	.driver	= {  		.name	= "ci_hdrc", +		.owner	= THIS_MODULE,  	},  }; diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c index 96d899aee47..7cccab6ff30 100644 --- a/drivers/usb/chipidea/debug.c +++ b/drivers/usb/chipidea/debug.c @@ -7,11 +7,15 @@  #include <linux/uaccess.h>  #include <linux/usb/ch9.h>  #include <linux/usb/gadget.h> +#include <linux/usb/phy.h> +#include <linux/usb/otg.h> +#include <linux/usb/otg-fsm.h>  #include "ci.h"  #include "udc.h"  #include "bits.h"  #include "debug.h" +#include "otg.h"  /**   * ci_device_show: prints information about device capabilities and status @@ -204,6 +208,80 @@ static const struct file_operations ci_requests_fops = {  	.release	= single_release,  }; +int ci_otg_show(struct seq_file *s, void *unused) +{ +	struct ci_hdrc *ci = s->private; +	struct otg_fsm *fsm; + +	if (!ci || !ci_otg_is_fsm_mode(ci)) +		return 0; + +	fsm = &ci->fsm; + +	/* ------ State ----- */ +	seq_printf(s, "OTG state: %s\n\n", +		usb_otg_state_string(ci->transceiver->state)); + +	/* ------ State Machine Variables ----- */ +	seq_printf(s, "a_bus_drop: %d\n", fsm->a_bus_drop); + +	seq_printf(s, "a_bus_req: %d\n", fsm->a_bus_req); + +	seq_printf(s, "a_srp_det: %d\n", fsm->a_srp_det); + +	seq_printf(s, "a_vbus_vld: %d\n", fsm->a_vbus_vld); + +	seq_printf(s, "b_conn: %d\n", fsm->b_conn); + +	seq_printf(s, "adp_change: %d\n", fsm->adp_change); + +	seq_printf(s, "power_up: %d\n", fsm->power_up); + +	seq_printf(s, "a_bus_resume: %d\n", fsm->a_bus_resume); + +	seq_printf(s, "a_bus_suspend: %d\n", fsm->a_bus_suspend); + +	seq_printf(s, "a_conn: %d\n", fsm->a_conn); + +	seq_printf(s, "b_bus_req: %d\n", fsm->b_bus_req); + +	seq_printf(s, "b_bus_suspend: %d\n", fsm->b_bus_suspend); + +	seq_printf(s, "b_se0_srp: %d\n", fsm->b_se0_srp); + +	seq_printf(s, "b_ssend_srp: %d\n", fsm->b_ssend_srp); + +	seq_printf(s, "b_sess_vld: %d\n", fsm->b_sess_vld); + +	seq_printf(s, "b_srp_done: %d\n", fsm->b_srp_done); + +	seq_printf(s, "drv_vbus: %d\n", fsm->drv_vbus); + +	seq_printf(s, "loc_conn: %d\n", fsm->loc_conn); + +	seq_printf(s, "loc_sof: %d\n", fsm->loc_sof); + +	seq_printf(s, "adp_prb: %d\n", fsm->adp_prb); + +	seq_printf(s, "id: %d\n", fsm->id); + +	seq_printf(s, "protocol: %d\n", fsm->protocol); + +	return 0; +} + +static int ci_otg_open(struct inode *inode, struct file *file) +{ +	return single_open(file, ci_otg_show, inode->i_private); +} + +static const struct file_operations ci_otg_fops = { +	.open			= ci_otg_open, +	.read			= seq_read, +	.llseek			= seq_lseek, +	.release		= single_release, +}; +  static int ci_role_show(struct seq_file *s, void *data)  {  	struct ci_hdrc *ci = s->private; @@ -253,6 +331,50 @@ static const struct file_operations ci_role_fops = {  	.release	= single_release,  }; +int ci_registers_show(struct seq_file *s, void *unused) +{ +	struct ci_hdrc *ci = s->private; +	u32 tmp_reg; + +	if (!ci) +		return 0; + +	/* ------ Registers ----- */ +	tmp_reg = hw_read_intr_enable(ci); +	seq_printf(s, "USBINTR reg: %08x\n", tmp_reg); + +	tmp_reg = hw_read_intr_status(ci); +	seq_printf(s, "USBSTS reg: %08x\n", tmp_reg); + +	tmp_reg = hw_read(ci, OP_USBMODE, ~0); +	seq_printf(s, "USBMODE reg: %08x\n", tmp_reg); + +	tmp_reg = hw_read(ci, OP_USBCMD, ~0); +	seq_printf(s, "USBCMD reg: %08x\n", tmp_reg); + +	tmp_reg = hw_read(ci, OP_PORTSC, ~0); +	seq_printf(s, "PORTSC reg: %08x\n", tmp_reg); + +	if (ci->is_otg) { +		tmp_reg = hw_read_otgsc(ci, ~0); +		seq_printf(s, "OTGSC reg: %08x\n", tmp_reg); +	} + +	return 0; +} + +static int ci_registers_open(struct inode *inode, struct file *file) +{ +	return single_open(file, ci_registers_show, inode->i_private); +} + +static const struct file_operations ci_registers_fops = { +	.open			= ci_registers_open, +	.read			= seq_read, +	.llseek			= seq_lseek, +	.release		= single_release, +}; +  /**   * dbg_create_files: initializes the attribute interface   * @ci: device @@ -287,8 +409,21 @@ int dbg_create_files(struct ci_hdrc *ci)  	if (!dent)  		goto err; +	if (ci_otg_is_fsm_mode(ci)) { +		dent = debugfs_create_file("otg", S_IRUGO, ci->debugfs, ci, +					&ci_otg_fops); +		if (!dent) +			goto err; +	} +  	dent = debugfs_create_file("role", S_IRUGO | S_IWUSR, ci->debugfs, ci,  				   &ci_role_fops); +	if (!dent) +		goto err; + +	dent = debugfs_create_file("registers", S_IRUGO, ci->debugfs, ci, +				&ci_registers_fops); +  	if (dent)  		return 0;  err: diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 6f96795dd20..a93d950e946 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -65,8 +65,13 @@ static int host_start(struct ci_hdrc *ci)  	ehci->caps = ci->hw_bank.cap;  	ehci->has_hostpc = ci->hw_bank.lpm;  	ehci->has_tdi_phy_lpm = ci->hw_bank.lpm; +	ehci->imx28_write_fix = ci->imx28_write_fix; -	if (ci->platdata->reg_vbus) { +	/* +	 * vbus is always on if host is not in OTG FSM mode, +	 * otherwise should be controlled by OTG FSM +	 */ +	if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) {  		ret = regulator_enable(ci->platdata->reg_vbus);  		if (ret) {  			dev_err(ci->dev, @@ -77,10 +82,17 @@ static int host_start(struct ci_hdrc *ci)  	}  	ret = usb_add_hcd(hcd, 0, 0); -	if (ret) +	if (ret) {  		goto disable_reg; -	else +	} else { +		struct usb_otg *otg = ci->transceiver->otg; +  		ci->hcd = hcd; +		if (otg) { +			otg->host = &hcd->self; +			hcd->self.otg_port = 1; +		} +	}  	if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING)  		hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); @@ -88,7 +100,8 @@ static int host_start(struct ci_hdrc *ci)  	return ret;  disable_reg: -	regulator_disable(ci->platdata->reg_vbus); +	if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) +		regulator_disable(ci->platdata->reg_vbus);  put_hcd:  	usb_put_hcd(hcd); @@ -100,16 +113,18 @@ static void host_stop(struct ci_hdrc *ci)  {  	struct usb_hcd *hcd = ci->hcd; -	usb_remove_hcd(hcd); -	usb_put_hcd(hcd); -	if (ci->platdata->reg_vbus) -		regulator_disable(ci->platdata->reg_vbus); +	if (hcd) { +		usb_remove_hcd(hcd); +		usb_put_hcd(hcd); +		if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) +			regulator_disable(ci->platdata->reg_vbus); +	}  }  void ci_hdrc_host_destroy(struct ci_hdrc *ci)  { -	if (ci->role == CI_ROLE_HOST) +	if (ci->role == CI_ROLE_HOST && ci->hcd)  		host_stop(ci);  } diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index 39bd7ec8bf7..a048b08b9d4 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -11,8 +11,8 @@   */  /* - * This file mainly handles otgsc register, it may include OTG operation - * in the future. + * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP + * are also included.   */  #include <linux/usb/otg.h> @@ -22,6 +22,26 @@  #include "ci.h"  #include "bits.h"  #include "otg.h" +#include "otg_fsm.h" + +/** + * hw_read_otgsc returns otgsc register bits value. + * @mask: bitfield mask + */ +u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) +{ +	return hw_read(ci, OP_OTGSC, mask); +} + +/** + * hw_write_otgsc updates target bits of OTGSC register. + * @mask: bitfield mask + * @data: to be written + */ +void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) +{ +	hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data); +}  /**   * ci_otg_role - pick role based on ID pin state @@ -29,8 +49,7 @@   */  enum ci_role ci_otg_role(struct ci_hdrc *ci)  { -	u32 sts = hw_read(ci, OP_OTGSC, ~0); -	enum ci_role role = sts & OTGSC_ID +	enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)  		? CI_ROLE_GADGET  		: CI_ROLE_HOST; @@ -39,14 +58,10 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)  void ci_handle_vbus_change(struct ci_hdrc *ci)  { -	u32 otgsc; -  	if (!ci->is_otg)  		return; -	otgsc = hw_read(ci, OP_OTGSC, ~0); - -	if (otgsc & OTGSC_BSV) +	if (hw_read_otgsc(ci, OTGSC_BSV))  		usb_gadget_vbus_connect(&ci->gadget);  	else  		usb_gadget_vbus_disconnect(&ci->gadget); @@ -76,6 +91,11 @@ static void ci_otg_work(struct work_struct *work)  {  	struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); +	if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) { +		enable_irq(ci->irq); +		return; +	} +  	if (ci->id_event) {  		ci->id_event = false;  		ci_handle_id_switch(ci); @@ -102,6 +122,9 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci)  		return -ENODEV;  	} +	if (ci_otg_is_fsm_mode(ci)) +		return ci_hdrc_otg_fsm_init(ci); +  	return 0;  } @@ -115,6 +138,9 @@ void ci_hdrc_otg_destroy(struct ci_hdrc *ci)  		flush_workqueue(ci->wq);  		destroy_workqueue(ci->wq);  	} -	ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS); -	ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS); +	/* Disable all OTG irq and clear status */ +	hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, +						OTGSC_INT_STATUS_BITS); +	if (ci_otg_is_fsm_mode(ci)) +		ci_hdrc_otg_fsm_remove(ci);  } diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h index 2d9f090733b..9ecb598e48f 100644 --- a/drivers/usb/chipidea/otg.h +++ b/drivers/usb/chipidea/otg.h @@ -1,5 +1,5 @@  /* - * Copyright (C) 2013 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2014 Freescale Semiconductor, Inc.   *   * Author: Peter Chen   * @@ -11,25 +11,16 @@  #ifndef __DRIVERS_USB_CHIPIDEA_OTG_H  #define __DRIVERS_USB_CHIPIDEA_OTG_H -static inline void ci_clear_otg_interrupt(struct ci_hdrc *ci, u32 bits) -{ -	/* Only clear request bits */ -	hw_write(ci, OP_OTGSC, OTGSC_INT_STATUS_BITS, bits); -} - -static inline void ci_enable_otg_interrupt(struct ci_hdrc *ci, u32 bits) -{ -	hw_write(ci, OP_OTGSC, bits, bits); -} - -static inline void ci_disable_otg_interrupt(struct ci_hdrc *ci, u32 bits) -{ -	hw_write(ci, OP_OTGSC, bits, 0); -} - +u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask); +void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data);  int ci_hdrc_otg_init(struct ci_hdrc *ci);  void ci_hdrc_otg_destroy(struct ci_hdrc *ci);  enum ci_role ci_otg_role(struct ci_hdrc *ci);  void ci_handle_vbus_change(struct ci_hdrc *ci); +static inline void ci_otg_queue_work(struct ci_hdrc *ci) +{ +	disable_irq_nosync(ci->irq); +	queue_work(ci->wq, &ci->work); +}  #endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c new file mode 100644 index 00000000000..caaabc58021 --- /dev/null +++ b/drivers/usb/chipidea/otg_fsm.c @@ -0,0 +1,842 @@ +/* + * otg_fsm.c - ChipIdea USB IP core OTG FSM driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Jun Li + * + * 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. + */ + +/* + * This file mainly handles OTG fsm, it includes OTG fsm operations + * for HNP and SRP. + * + * TODO List + * - ADP + * - OTG test device + */ + +#include <linux/usb/otg.h> +#include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> +#include <linux/usb/chipidea.h> +#include <linux/regulator/consumer.h> + +#include "ci.h" +#include "bits.h" +#include "otg.h" +#include "otg_fsm.h" + +static struct ci_otg_fsm_timer *otg_timer_initializer +(struct ci_hdrc *ci, void (*function)(void *, unsigned long), +			unsigned long expires, unsigned long data) +{ +	struct ci_otg_fsm_timer *timer; + +	timer = devm_kzalloc(ci->dev, sizeof(struct ci_otg_fsm_timer), +								GFP_KERNEL); +	if (!timer) +		return NULL; +	timer->function = function; +	timer->expires = expires; +	timer->data = data; +	return timer; +} + +/* Add for otg: interact with user space app */ +static ssize_t +get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf) +{ +	char		*next; +	unsigned	size, t; +	struct ci_hdrc	*ci = dev_get_drvdata(dev); + +	next = buf; +	size = PAGE_SIZE; +	t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_req); +	size -= t; +	next += t; + +	return PAGE_SIZE - size; +} + +static ssize_t +set_a_bus_req(struct device *dev, struct device_attribute *attr, +					const char *buf, size_t count) +{ +	struct ci_hdrc *ci = dev_get_drvdata(dev); + +	if (count > 2) +		return -1; + +	mutex_lock(&ci->fsm.lock); +	if (buf[0] == '0') { +		ci->fsm.a_bus_req = 0; +	} else if (buf[0] == '1') { +		/* If a_bus_drop is TRUE, a_bus_req can't be set */ +		if (ci->fsm.a_bus_drop) { +			mutex_unlock(&ci->fsm.lock); +			return count; +		} +		ci->fsm.a_bus_req = 1; +	} + +	ci_otg_queue_work(ci); +	mutex_unlock(&ci->fsm.lock); + +	return count; +} +static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req, set_a_bus_req); + +static ssize_t +get_a_bus_drop(struct device *dev, struct device_attribute *attr, char *buf) +{ +	char		*next; +	unsigned	size, t; +	struct ci_hdrc	*ci = dev_get_drvdata(dev); + +	next = buf; +	size = PAGE_SIZE; +	t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_drop); +	size -= t; +	next += t; + +	return PAGE_SIZE - size; +} + +static ssize_t +set_a_bus_drop(struct device *dev, struct device_attribute *attr, +					const char *buf, size_t count) +{ +	struct ci_hdrc	*ci = dev_get_drvdata(dev); + +	if (count > 2) +		return -1; + +	mutex_lock(&ci->fsm.lock); +	if (buf[0] == '0') { +		ci->fsm.a_bus_drop = 0; +	} else if (buf[0] == '1') { +		ci->fsm.a_bus_drop = 1; +		ci->fsm.a_bus_req = 0; +	} + +	ci_otg_queue_work(ci); +	mutex_unlock(&ci->fsm.lock); + +	return count; +} +static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR, get_a_bus_drop, +						set_a_bus_drop); + +static ssize_t +get_b_bus_req(struct device *dev, struct device_attribute *attr, char *buf) +{ +	char		*next; +	unsigned	size, t; +	struct ci_hdrc	*ci = dev_get_drvdata(dev); + +	next = buf; +	size = PAGE_SIZE; +	t = scnprintf(next, size, "%d\n", ci->fsm.b_bus_req); +	size -= t; +	next += t; + +	return PAGE_SIZE - size; +} + +static ssize_t +set_b_bus_req(struct device *dev, struct device_attribute *attr, +					const char *buf, size_t count) +{ +	struct ci_hdrc	*ci = dev_get_drvdata(dev); + +	if (count > 2) +		return -1; + +	mutex_lock(&ci->fsm.lock); +	if (buf[0] == '0') +		ci->fsm.b_bus_req = 0; +	else if (buf[0] == '1') +		ci->fsm.b_bus_req = 1; + +	ci_otg_queue_work(ci); +	mutex_unlock(&ci->fsm.lock); + +	return count; +} +static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUSR, get_b_bus_req, set_b_bus_req); + +static ssize_t +set_a_clr_err(struct device *dev, struct device_attribute *attr, +					const char *buf, size_t count) +{ +	struct ci_hdrc	*ci = dev_get_drvdata(dev); + +	if (count > 2) +		return -1; + +	mutex_lock(&ci->fsm.lock); +	if (buf[0] == '1') +		ci->fsm.a_clr_err = 1; + +	ci_otg_queue_work(ci); +	mutex_unlock(&ci->fsm.lock); + +	return count; +} +static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err); + +static struct attribute *inputs_attrs[] = { +	&dev_attr_a_bus_req.attr, +	&dev_attr_a_bus_drop.attr, +	&dev_attr_b_bus_req.attr, +	&dev_attr_a_clr_err.attr, +	NULL, +}; + +static struct attribute_group inputs_attr_group = { +	.name = "inputs", +	.attrs = inputs_attrs, +}; + +/* + * Add timer to active timer list + */ +static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) +{ +	struct ci_otg_fsm_timer *tmp_timer; +	struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; +	struct list_head *active_timers = &ci->fsm_timer->active_timers; + +	if (t >= NUM_CI_OTG_FSM_TIMERS) +		return; + +	/* +	 * Check if the timer is already in the active list, +	 * if so update timer count +	 */ +	list_for_each_entry(tmp_timer, active_timers, list) +		if (tmp_timer == timer) { +			timer->count = timer->expires; +			return; +		} + +	timer->count = timer->expires; +	list_add_tail(&timer->list, active_timers); + +	/* Enable 1ms irq */ +	if (!(hw_read_otgsc(ci, OTGSC_1MSIE))) +		hw_write_otgsc(ci, OTGSC_1MSIE, OTGSC_1MSIE); +} + +/* + * Remove timer from active timer list + */ +static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) +{ +	struct ci_otg_fsm_timer *tmp_timer, *del_tmp; +	struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; +	struct list_head *active_timers = &ci->fsm_timer->active_timers; + +	if (t >= NUM_CI_OTG_FSM_TIMERS) +		return; + +	list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) +		if (tmp_timer == timer) +			list_del(&timer->list); + +	/* Disable 1ms irq if there is no any active timer */ +	if (list_empty(active_timers)) +		hw_write_otgsc(ci, OTGSC_1MSIE, 0); +} + +/* + * Reduce timer count by 1, and find timeout conditions. + * Called by otg 1ms timer interrupt + */ +static inline int ci_otg_tick_timer(struct ci_hdrc *ci) +{ +	struct ci_otg_fsm_timer *tmp_timer, *del_tmp; +	struct list_head *active_timers = &ci->fsm_timer->active_timers; +	int expired = 0; + +	list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) { +		tmp_timer->count--; +		/* check if timer expires */ +		if (!tmp_timer->count) { +			list_del(&tmp_timer->list); +			tmp_timer->function(ci, tmp_timer->data); +			expired = 1; +		} +	} + +	/* disable 1ms irq if there is no any timer active */ +	if ((expired == 1) && list_empty(active_timers)) +		hw_write_otgsc(ci, OTGSC_1MSIE, 0); + +	return expired; +} + +/* The timeout callback function to set time out bit */ +static void set_tmout(void *ptr, unsigned long indicator) +{ +	*(int *)indicator = 1; +} + +static void set_tmout_and_fsm(void *ptr, unsigned long indicator) +{ +	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + +	set_tmout(ci, indicator); + +	ci_otg_queue_work(ci); +} + +static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator) +{ +	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + +	set_tmout(ci, indicator); +	/* Disable port power */ +	hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, 0); +	/* Clear exsiting DP irq */ +	hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); +	/* Enable data pulse irq */ +	hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); +	ci_otg_queue_work(ci); +} + +static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator) +{ +	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + +	set_tmout(ci, indicator); +	if (!hw_read_otgsc(ci, OTGSC_BSV)) +		ci->fsm.b_sess_vld = 0; + +	ci_otg_queue_work(ci); +} + +static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator) +{ +	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + +	set_tmout(ci, indicator); + +	/* only vbus fall below B_sess_vld in b_idle state */ +	if (ci->transceiver->state == OTG_STATE_B_IDLE) +		ci_otg_queue_work(ci); +} + +static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator) +{ +	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + +	/* Check if A detached */ +	if (!(hw_read_otgsc(ci, OTGSC_BSV))) { +		ci->fsm.b_sess_vld = 0; +		ci_otg_add_timer(ci, B_SSEND_SRP); +		ci_otg_queue_work(ci); +	} +} + +static void b_data_pulse_end(void *ptr, unsigned long indicator) +{ +	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + +	ci->fsm.b_srp_done = 1; +	ci->fsm.b_bus_req = 0; +	if (ci->fsm.power_up) +		ci->fsm.power_up = 0; + +	hw_write_otgsc(ci, OTGSC_HABA, 0); + +	ci_otg_queue_work(ci); +} + +/* Initialize timers */ +static int ci_otg_init_timers(struct ci_hdrc *ci) +{ +	struct otg_fsm *fsm = &ci->fsm; + +	/* FSM used timers */ +	ci->fsm_timer->timer_list[A_WAIT_VRISE] = +		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_VRISE, +			(unsigned long)&fsm->a_wait_vrise_tmout); +	if (ci->fsm_timer->timer_list[A_WAIT_VRISE] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[A_WAIT_VFALL] = +		otg_timer_initializer(ci, &a_wait_vfall_tmout_func, +		TA_WAIT_VFALL, (unsigned long)&fsm->a_wait_vfall_tmout); +	if (ci->fsm_timer->timer_list[A_WAIT_VFALL] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[A_WAIT_BCON] = +		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_BCON, +				(unsigned long)&fsm->a_wait_bcon_tmout); +	if (ci->fsm_timer->timer_list[A_WAIT_BCON] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[A_AIDL_BDIS] = +		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_AIDL_BDIS, +				(unsigned long)&fsm->a_aidl_bdis_tmout); +	if (ci->fsm_timer->timer_list[A_AIDL_BDIS] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[A_BIDL_ADIS] = +		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_BIDL_ADIS, +				(unsigned long)&fsm->a_bidl_adis_tmout); +	if (ci->fsm_timer->timer_list[A_BIDL_ADIS] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[B_ASE0_BRST] = +		otg_timer_initializer(ci, &b_ase0_brst_tmout_func, TB_ASE0_BRST, +					(unsigned long)&fsm->b_ase0_brst_tmout); +	if (ci->fsm_timer->timer_list[B_ASE0_BRST] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[B_SE0_SRP] = +		otg_timer_initializer(ci, &set_tmout_and_fsm, TB_SE0_SRP, +					(unsigned long)&fsm->b_se0_srp); +	if (ci->fsm_timer->timer_list[B_SE0_SRP] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[B_SSEND_SRP] = +		otg_timer_initializer(ci, &b_ssend_srp_tmout_func, TB_SSEND_SRP, +					(unsigned long)&fsm->b_ssend_srp); +	if (ci->fsm_timer->timer_list[B_SSEND_SRP] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[B_SRP_FAIL] = +		otg_timer_initializer(ci, &set_tmout, TB_SRP_FAIL, +				(unsigned long)&fsm->b_srp_done); +	if (ci->fsm_timer->timer_list[B_SRP_FAIL] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[B_DATA_PLS] = +		otg_timer_initializer(ci, &b_data_pulse_end, TB_DATA_PLS, 0); +	if (ci->fsm_timer->timer_list[B_DATA_PLS] == NULL) +		return -ENOMEM; + +	ci->fsm_timer->timer_list[B_SESS_VLD] =	otg_timer_initializer(ci, +					&b_sess_vld_tmout_func, TB_SESS_VLD, 0); +	if (ci->fsm_timer->timer_list[B_SESS_VLD] == NULL) +		return -ENOMEM; + +	return 0; +} + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ +/* -------------------------------------------------------------*/ +static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) +{ +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	if (t < NUM_OTG_FSM_TIMERS) +		ci_otg_add_timer(ci, t); +	return; +} + +static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) +{ +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	if (t < NUM_OTG_FSM_TIMERS) +		ci_otg_del_timer(ci, t); +	return; +} + +/* + * A-device drive vbus: turn on vbus regulator and enable port power + * Data pulse irq should be disabled while vbus is on. + */ +static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on) +{ +	int ret; +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	if (on) { +		/* Enable power power */ +		hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, +							PORTSC_PP); +		if (ci->platdata->reg_vbus) { +			ret = regulator_enable(ci->platdata->reg_vbus); +			if (ret) { +				dev_err(ci->dev, +				"Failed to enable vbus regulator, ret=%d\n", +				ret); +				return; +			} +		} +		/* Disable data pulse irq */ +		hw_write_otgsc(ci, OTGSC_DPIE, 0); + +		fsm->a_srp_det = 0; +		fsm->power_up = 0; +	} else { +		if (ci->platdata->reg_vbus) +			regulator_disable(ci->platdata->reg_vbus); + +		fsm->a_bus_drop = 1; +		fsm->a_bus_req = 0; +	} +} + +/* + * Control data line by Run Stop bit. + */ +static void ci_otg_loc_conn(struct otg_fsm *fsm, int on) +{ +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	if (on) +		hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); +	else +		hw_write(ci, OP_USBCMD, USBCMD_RS, 0); +} + +/* + * Generate SOF by host. + * This is controlled through suspend/resume the port. + * In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + */ +static void ci_otg_loc_sof(struct otg_fsm *fsm, int on) +{ +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	if (on) +		hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR, +							PORTSC_FPR); +	else +		hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP, +							PORTSC_SUSP); +} + +/* + * Start SRP pulsing by data-line pulsing, + * no v-bus pulsing followed + */ +static void ci_otg_start_pulse(struct otg_fsm *fsm) +{ +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	/* Hardware Assistant Data pulse */ +	hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP); + +	ci_otg_add_timer(ci, B_DATA_PLS); +} + +static int ci_otg_start_host(struct otg_fsm *fsm, int on) +{ +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	mutex_unlock(&fsm->lock); +	if (on) { +		ci_role_stop(ci); +		ci_role_start(ci, CI_ROLE_HOST); +	} else { +		ci_role_stop(ci); +		hw_device_reset(ci, USBMODE_CM_DC); +		ci_role_start(ci, CI_ROLE_GADGET); +	} +	mutex_lock(&fsm->lock); +	return 0; +} + +static int ci_otg_start_gadget(struct otg_fsm *fsm, int on) +{ +	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); + +	mutex_unlock(&fsm->lock); +	if (on) +		usb_gadget_vbus_connect(&ci->gadget); +	else +		usb_gadget_vbus_disconnect(&ci->gadget); +	mutex_lock(&fsm->lock); + +	return 0; +} + +static struct otg_fsm_ops ci_otg_ops = { +	.drv_vbus = ci_otg_drv_vbus, +	.loc_conn = ci_otg_loc_conn, +	.loc_sof = ci_otg_loc_sof, +	.start_pulse = ci_otg_start_pulse, +	.add_timer = ci_otg_fsm_add_timer, +	.del_timer = ci_otg_fsm_del_timer, +	.start_host = ci_otg_start_host, +	.start_gadget = ci_otg_start_gadget, +}; + +int ci_otg_fsm_work(struct ci_hdrc *ci) +{ +	/* +	 * Don't do fsm transition for B device +	 * when there is no gadget class driver +	 */ +	if (ci->fsm.id && !(ci->driver) && +		ci->transceiver->state < OTG_STATE_A_IDLE) +		return 0; + +	if (otg_statemachine(&ci->fsm)) { +		if (ci->transceiver->state == OTG_STATE_A_IDLE) { +			/* +			 * Further state change for cases: +			 * a_idle to b_idle; or +			 * a_idle to a_wait_vrise due to ID change(1->0), so +			 * B-dev becomes A-dev can try to start new session +			 * consequently; or +			 * a_idle to a_wait_vrise when power up +			 */ +			if ((ci->fsm.id) || (ci->id_event) || +						(ci->fsm.power_up)) +				ci_otg_queue_work(ci); +			if (ci->id_event) +				ci->id_event = false; +		} else if (ci->transceiver->state == OTG_STATE_B_IDLE) { +			if (ci->fsm.b_sess_vld) { +				ci->fsm.power_up = 0; +				/* +				 * Further transite to b_periphearl state +				 * when register gadget driver with vbus on +				 */ +				ci_otg_queue_work(ci); +			} +		} +	} +	return 0; +} + +/* + * Update fsm variables in each state if catching expected interrupts, + * called by otg fsm isr. + */ +static void ci_otg_fsm_event(struct ci_hdrc *ci) +{ +	u32 intr_sts, otg_bsess_vld, port_conn; +	struct otg_fsm *fsm = &ci->fsm; + +	intr_sts = hw_read_intr_status(ci); +	otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV); +	port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS); + +	switch (ci->transceiver->state) { +	case OTG_STATE_A_WAIT_BCON: +		if (port_conn) { +			fsm->b_conn = 1; +			fsm->a_bus_req = 1; +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_B_IDLE: +		if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) { +			fsm->b_sess_vld = 1; +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_B_PERIPHERAL: +		if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) { +			fsm->a_bus_suspend = 1; +			ci_otg_queue_work(ci); +		} else if (intr_sts & USBi_PCI) { +			if (fsm->a_bus_suspend == 1) +				fsm->a_bus_suspend = 0; +		} +		break; +	case OTG_STATE_B_HOST: +		if ((intr_sts & USBi_PCI) && !port_conn) { +			fsm->a_conn = 0; +			fsm->b_bus_req = 0; +			ci_otg_queue_work(ci); +			ci_otg_add_timer(ci, B_SESS_VLD); +		} +		break; +	case OTG_STATE_A_PERIPHERAL: +		if (intr_sts & USBi_SLI) { +			 fsm->b_bus_suspend = 1; +			/* +			 * Init a timer to know how long this suspend +			 * will contine, if time out, indicates B no longer +			 * wants to be host role +			 */ +			 ci_otg_add_timer(ci, A_BIDL_ADIS); +		} + +		if (intr_sts & USBi_URI) +			ci_otg_del_timer(ci, A_BIDL_ADIS); + +		if (intr_sts & USBi_PCI) { +			if (fsm->b_bus_suspend == 1) { +				ci_otg_del_timer(ci, A_BIDL_ADIS); +				fsm->b_bus_suspend = 0; +			} +		} +		break; +	case OTG_STATE_A_SUSPEND: +		if ((intr_sts & USBi_PCI) && !port_conn) { +			fsm->b_conn = 0; + +			/* if gadget driver is binded */ +			if (ci->driver) { +				/* A device to be peripheral mode */ +				ci->gadget.is_a_peripheral = 1; +			} +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_A_HOST: +		if ((intr_sts & USBi_PCI) && !port_conn) { +			fsm->b_conn = 0; +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_B_WAIT_ACON: +		if ((intr_sts & USBi_PCI) && port_conn) { +			fsm->a_conn = 1; +			ci_otg_queue_work(ci); +		} +		break; +	default: +		break; +	} +} + +/* + * ci_otg_irq - otg fsm related irq handling + * and also update otg fsm variable by monitoring usb host and udc + * state change interrupts. + * @ci: ci_hdrc + */ +irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) +{ +	irqreturn_t retval =  IRQ_NONE; +	u32 otgsc, otg_int_src = 0; +	struct otg_fsm *fsm = &ci->fsm; + +	otgsc = hw_read_otgsc(ci, ~0); +	otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8); +	fsm->id = (otgsc & OTGSC_ID) ? 1 : 0; + +	if (otg_int_src) { +		if (otg_int_src & OTGSC_1MSIS) { +			hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS); +			retval = ci_otg_tick_timer(ci); +			return IRQ_HANDLED; +		} else if (otg_int_src & OTGSC_DPIS) { +			hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); +			fsm->a_srp_det = 1; +			fsm->a_bus_drop = 0; +		} else if (otg_int_src & OTGSC_IDIS) { +			hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); +			if (fsm->id == 0) { +				fsm->a_bus_drop = 0; +				fsm->a_bus_req = 1; +				ci->id_event = true; +			} +		} else if (otg_int_src & OTGSC_BSVIS) { +			hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); +			if (otgsc & OTGSC_BSV) { +				fsm->b_sess_vld = 1; +				ci_otg_del_timer(ci, B_SSEND_SRP); +				ci_otg_del_timer(ci, B_SRP_FAIL); +				fsm->b_ssend_srp = 0; +			} else { +				fsm->b_sess_vld = 0; +				if (fsm->id) +					ci_otg_add_timer(ci, B_SSEND_SRP); +			} +		} else if (otg_int_src & OTGSC_AVVIS) { +			hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS); +			if (otgsc & OTGSC_AVV) { +				fsm->a_vbus_vld = 1; +			} else { +				fsm->a_vbus_vld = 0; +				fsm->b_conn = 0; +			} +		} +		ci_otg_queue_work(ci); +		return IRQ_HANDLED; +	} + +	ci_otg_fsm_event(ci); + +	return retval; +} + +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) +{ +	ci_otg_queue_work(ci); +} + +int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) +{ +	int retval = 0; +	struct usb_otg *otg; + +	otg = devm_kzalloc(ci->dev, +			sizeof(struct usb_otg), GFP_KERNEL); +	if (!otg) { +		dev_err(ci->dev, +		"Failed to allocate usb_otg structure for ci hdrc otg!\n"); +		return -ENOMEM; +	} + +	otg->phy = ci->transceiver; +	otg->gadget = &ci->gadget; +	ci->fsm.otg = otg; +	ci->transceiver->otg = ci->fsm.otg; +	ci->fsm.power_up = 1; +	ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0; +	ci->transceiver->state = OTG_STATE_UNDEFINED; +	ci->fsm.ops = &ci_otg_ops; + +	mutex_init(&ci->fsm.lock); + +	ci->fsm_timer = devm_kzalloc(ci->dev, +			sizeof(struct ci_otg_fsm_timer_list), GFP_KERNEL); +	if (!ci->fsm_timer) { +		dev_err(ci->dev, +		"Failed to allocate timer structure for ci hdrc otg!\n"); +		return -ENOMEM; +	} + +	INIT_LIST_HEAD(&ci->fsm_timer->active_timers); +	retval = ci_otg_init_timers(ci); +	if (retval) { +		dev_err(ci->dev, "Couldn't init OTG timers\n"); +		return retval; +	} + +	retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group); +	if (retval < 0) { +		dev_dbg(ci->dev, +			"Can't register sysfs attr group: %d\n", retval); +		return retval; +	} + +	/* Enable A vbus valid irq */ +	hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE); + +	if (ci->fsm.id) { +		ci->fsm.b_ssend_srp = +			hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1; +		ci->fsm.b_sess_vld = +			hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0; +		/* Enable BSV irq */ +		hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE); +	} + +	return 0; +} + +void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) +{ +	sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group); +} diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h new file mode 100644 index 00000000000..94c085f456a --- /dev/null +++ b/drivers/usb/chipidea/otg_fsm.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Jun Li + * + * 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. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_OTG_FSM_H +#define __DRIVERS_USB_CHIPIDEA_OTG_FSM_H + +#include <linux/usb/otg-fsm.h> + +/* + *  A-DEVICE timing  constants + */ + +/* Wait for VBUS Rise  */ +#define TA_WAIT_VRISE        (100)	/* a_wait_vrise: section 7.1.2 +					 * a_wait_vrise_tmr: section 7.4.5.1 +					 * TA_VBUS_RISE <= 100ms, section 4.4 +					 * Table 4-1: Electrical Characteristics +					 * ->DC Electrical Timing +					 */ +/* Wait for VBUS Fall  */ +#define TA_WAIT_VFALL        (1000)	/* a_wait_vfall: section 7.1.7 +					 * a_wait_vfall_tmr: section: 7.4.5.2 +					 */ +/* Wait for B-Connect */ +#define TA_WAIT_BCON         (10000)	/* a_wait_bcon: section 7.1.3 +					 * TA_WAIT_BCON: should be between 1100 +					 * and 30000 ms, section 5.5, Table 5-1 +					 */ +/* A-Idle to B-Disconnect */ +#define TA_AIDL_BDIS         (5000)	/* a_suspend min 200 ms, section 5.2.1 +					 * TA_AIDL_BDIS: section 5.5, Table 5-1 +					 */ +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS         (500)	/* TA_BIDL_ADIS: section 5.2.1 +					 * 500ms is used for B switch to host +					 * for safe +					 */ + +/* + * B-device timing constants + */ + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS          (10)	/* b_srp_init,continue 5~10ms +					 * section:5.1.3 +					 */ +/* SRP Fail Time  */ +#define TB_SRP_FAIL          (6000)	/* b_srp_init,fail time 5~6s +					 * section:5.1.6 +					 */ +/* A-SE0 to B-Reset  */ +#define TB_ASE0_BRST         (155)	/* minimum 155 ms, section:5.3.1 */ +/* SE0 Time Before SRP */ +#define TB_SE0_SRP           (1000)	/* b_idle,minimum 1s, section:5.1.2 */ +/* SSEND time before SRP */ +#define TB_SSEND_SRP         (1500)	/* minimum 1.5 sec, section:5.1.2 */ + +#define TB_SESS_VLD          (1000) + +enum ci_otg_fsm_timer_index { +	/* +	 * CI specific timers, start from the end +	 * of standard and auxiliary OTG timers +	 */ +	B_DATA_PLS = NUM_OTG_FSM_TIMERS, +	B_SSEND_SRP, +	B_SESS_VLD, + +	NUM_CI_OTG_FSM_TIMERS, +}; + +struct ci_otg_fsm_timer { +	unsigned long expires;  /* Number of count increase to timeout */ +	unsigned long count;    /* Tick counter */ +	void (*function)(void *, unsigned long);        /* Timeout function */ +	unsigned long data;     /* Data passed to function */ +	struct list_head list; +}; + +struct ci_otg_fsm_timer_list { +	struct ci_otg_fsm_timer *timer_list[NUM_CI_OTG_FSM_TIMERS]; +	struct list_head active_timers; +}; + +#ifdef CONFIG_USB_OTG_FSM + +int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci); +int ci_otg_fsm_work(struct ci_hdrc *ci); +irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci); +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci); +void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci); + +#else + +static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) +{ +	return 0; +} + +static inline int ci_otg_fsm_work(struct ci_hdrc *ci) +{ +	return -ENXIO; +} + +static inline irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) +{ +	return IRQ_NONE; +} + +static inline void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) +{ + +} + +static inline void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) +{ + +} + +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 6b4c2f2eb94..b8125aa64ad 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -20,7 +20,7 @@  #include <linux/pm_runtime.h>  #include <linux/usb/ch9.h>  #include <linux/usb/gadget.h> -#include <linux/usb/otg.h> +#include <linux/usb/otg-fsm.h>  #include <linux/usb/chipidea.h>  #include "ci.h" @@ -28,6 +28,7 @@  #include "bits.h"  #include "debug.h"  #include "otg.h" +#include "otg_fsm.h"  /* control endpoint description */  static const struct usb_endpoint_descriptor @@ -106,7 +107,7 @@ static int hw_ep_flush(struct ci_hdrc *ci, int num, int dir)  	do {  		/* flush any pending transfer */ -		hw_write(ci, OP_ENDPTFLUSH, BIT(n), BIT(n)); +		hw_write(ci, OP_ENDPTFLUSH, ~0, BIT(n));  		while (hw_read(ci, OP_ENDPTFLUSH, BIT(n)))  			cpu_relax();  	} while (hw_read(ci, OP_ENDPTSTAT, BIT(n))); @@ -179,19 +180,6 @@ static int hw_ep_get_halt(struct ci_hdrc *ci, int num, int dir)  }  /** - * hw_test_and_clear_setup_status: test & clear setup status (execute without - *                                 interruption) - * @n: endpoint number - * - * This function returns setup status - */ -static int hw_test_and_clear_setup_status(struct ci_hdrc *ci, int n) -{ -	n = ep_to_bit(ci, n); -	return hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(n)); -} - -/**   * hw_ep_prime: primes endpoint (execute without interruption)   * @num:     endpoint number   * @dir:     endpoint direction @@ -206,7 +194,7 @@ static int hw_ep_prime(struct ci_hdrc *ci, int num, int dir, int is_ctrl)  	if (is_ctrl && dir == RX && hw_read(ci, OP_ENDPTSETUPSTAT, BIT(num)))  		return -EAGAIN; -	hw_write(ci, OP_ENDPTPRIME, BIT(n), BIT(n)); +	hw_write(ci, OP_ENDPTPRIME, ~0, BIT(n));  	while (hw_read(ci, OP_ENDPTPRIME, BIT(n)))  		cpu_relax(); @@ -256,26 +244,6 @@ static int hw_port_is_high_speed(struct ci_hdrc *ci)  }  /** - * hw_read_intr_enable: returns interrupt enable register - * - * This function returns register data - */ -static u32 hw_read_intr_enable(struct ci_hdrc *ci) -{ -	return hw_read(ci, OP_USBINTR, ~0); -} - -/** - * hw_read_intr_status: returns interrupt status register - * - * This function returns register data - */ -static u32 hw_read_intr_status(struct ci_hdrc *ci) -{ -	return hw_read(ci, OP_USBSTS, ~0); -} - -/**   * hw_test_and_clear_complete: test & clear complete status (execute without   *                             interruption)   * @n: endpoint number @@ -394,6 +362,14 @@ static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq,  	node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES));  	node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES);  	node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE); +	if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) { +		u32 mul = hwreq->req.length / hwep->ep.maxpacket; + +		if (hwreq->req.length == 0 +				|| hwreq->req.length % hwep->ep.maxpacket) +			mul++; +		node->ptr->token |= mul << __ffs(TD_MULTO); +	}  	temp = (u32) (hwreq->req.dma + hwreq->req.actual);  	if (length) { @@ -516,10 +492,11 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)  	hwep->qh.ptr->td.token &=  		cpu_to_le32(~(TD_STATUS_HALTED|TD_STATUS_ACTIVE)); -	if (hwep->type == USB_ENDPOINT_XFER_ISOC) { +	if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == RX) {  		u32 mul = hwreq->req.length / hwep->ep.maxpacket; -		if (hwreq->req.length % hwep->ep.maxpacket) +		if (hwreq->req.length == 0 +				|| hwreq->req.length % hwep->ep.maxpacket)  			mul++;  		hwep->qh.ptr->cap |= mul << __ffs(QH_MULT);  	} @@ -686,9 +663,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)  	usb_ep_fifo_flush(&ci->ep0out->ep);  	usb_ep_fifo_flush(&ci->ep0in->ep); -	if (ci->driver) -		ci->driver->disconnect(gadget); -  	/* make sure to disable all endpoints */  	gadget_for_each_ep(ep, gadget) {  		usb_ep_disable(ep); @@ -718,6 +692,11 @@ __acquires(ci->lock)  	int retval;  	spin_unlock(&ci->lock); +	if (ci->gadget.speed != USB_SPEED_UNKNOWN) { +		if (ci->driver) +			ci->driver->disconnect(&ci->gadget); +	} +  	retval = _gadget_stop_activity(&ci->gadget);  	if (retval)  		goto done; @@ -730,6 +709,8 @@ __acquires(ci->lock)  	if (ci->status == NULL)  		retval = -ENOMEM; +	usb_gadget_set_state(&ci->gadget, USB_STATE_DEFAULT); +  done:  	spin_lock(&ci->lock); @@ -844,7 +825,6 @@ __acquires(hwep->lock)  	if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) {  		/* Assume that device is bus powered for now. */  		*(u16 *)req->buf = ci->remote_wakeup << 1; -		retval = 0;  	} else if ((setup->bRequestType & USB_RECIP_MASK) \  		   == USB_RECIP_ENDPOINT) {  		dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -886,6 +866,8 @@ isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req)  	if (ci->setaddr) {  		hw_usb_set_address(ci, ci->address);  		ci->setaddr = false; +		if (ci->address) +			usb_gadget_set_state(&ci->gadget, USB_STATE_ADDRESS);  	}  	spin_lock_irqsave(&ci->lock, flags); @@ -952,6 +934,164 @@ __acquires(hwep->lock)  }  /** + * isr_setup_packet_handler: setup packet handler + * @ci: UDC descriptor + * + * This function handles setup packet  + */ +static void isr_setup_packet_handler(struct ci_hdrc *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ +	struct ci_hw_ep *hwep = &ci->ci_hw_ep[0]; +	struct usb_ctrlrequest req; +	int type, num, dir, err = -EINVAL; +	u8 tmode = 0; + +	/* +	 * Flush data and handshake transactions of previous +	 * setup packet. +	 */ +	_ep_nuke(ci->ep0out); +	_ep_nuke(ci->ep0in); + +	/* read_setup_packet */ +	do { +		hw_test_and_set_setup_guard(ci); +		memcpy(&req, &hwep->qh.ptr->setup, sizeof(req)); +	} while (!hw_test_and_clear_setup_guard(ci)); + +	type = req.bRequestType; + +	ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; + +	switch (req.bRequest) { +	case USB_REQ_CLEAR_FEATURE: +		if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && +				le16_to_cpu(req.wValue) == +				USB_ENDPOINT_HALT) { +			if (req.wLength != 0) +				break; +			num  = le16_to_cpu(req.wIndex); +			dir = num & USB_ENDPOINT_DIR_MASK; +			num &= USB_ENDPOINT_NUMBER_MASK; +			if (dir) /* TX */ +				num += ci->hw_ep_max / 2; +			if (!ci->ci_hw_ep[num].wedge) { +				spin_unlock(&ci->lock); +				err = usb_ep_clear_halt( +					&ci->ci_hw_ep[num].ep); +				spin_lock(&ci->lock); +				if (err) +					break; +			} +			err = isr_setup_status_phase(ci); +		} else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && +				le16_to_cpu(req.wValue) == +				USB_DEVICE_REMOTE_WAKEUP) { +			if (req.wLength != 0) +				break; +			ci->remote_wakeup = 0; +			err = isr_setup_status_phase(ci); +		} else { +			goto delegate; +		} +		break; +	case USB_REQ_GET_STATUS: +		if (type != (USB_DIR_IN|USB_RECIP_DEVICE)   && +		    type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && +		    type != (USB_DIR_IN|USB_RECIP_INTERFACE)) +			goto delegate; +		if (le16_to_cpu(req.wLength) != 2 || +		    le16_to_cpu(req.wValue)  != 0) +			break; +		err = isr_get_status_response(ci, &req); +		break; +	case USB_REQ_SET_ADDRESS: +		if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) +			goto delegate; +		if (le16_to_cpu(req.wLength) != 0 || +		    le16_to_cpu(req.wIndex)  != 0) +			break; +		ci->address = (u8)le16_to_cpu(req.wValue); +		ci->setaddr = true; +		err = isr_setup_status_phase(ci); +		break; +	case USB_REQ_SET_FEATURE: +		if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && +				le16_to_cpu(req.wValue) == +				USB_ENDPOINT_HALT) { +			if (req.wLength != 0) +				break; +			num  = le16_to_cpu(req.wIndex); +			dir = num & USB_ENDPOINT_DIR_MASK; +			num &= USB_ENDPOINT_NUMBER_MASK; +			if (dir) /* TX */ +				num += ci->hw_ep_max / 2; + +			spin_unlock(&ci->lock); +			err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep); +			spin_lock(&ci->lock); +			if (!err) +				isr_setup_status_phase(ci); +		} else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { +			if (req.wLength != 0) +				break; +			switch (le16_to_cpu(req.wValue)) { +			case USB_DEVICE_REMOTE_WAKEUP: +				ci->remote_wakeup = 1; +				err = isr_setup_status_phase(ci); +				break; +			case USB_DEVICE_TEST_MODE: +				tmode = le16_to_cpu(req.wIndex) >> 8; +				switch (tmode) { +				case TEST_J: +				case TEST_K: +				case TEST_SE0_NAK: +				case TEST_PACKET: +				case TEST_FORCE_EN: +					ci->test_mode = tmode; +					err = isr_setup_status_phase( +							ci); +					break; +				default: +					break; +				} +				break; +			case USB_DEVICE_B_HNP_ENABLE: +				if (ci_otg_is_fsm_mode(ci)) { +					ci->gadget.b_hnp_enable = 1; +					err = isr_setup_status_phase( +							ci); +				} +				break; +			default: +				goto delegate; +			} +		} else { +			goto delegate; +		} +		break; +	default: +delegate: +		if (req.wLength == 0)   /* no data phase */ +			ci->ep0_dir = TX; + +		spin_unlock(&ci->lock); +		err = ci->driver->setup(&ci->gadget, &req); +		spin_lock(&ci->lock); +		break; +	} + +	if (err < 0) { +		spin_unlock(&ci->lock); +		if (usb_ep_set_halt(&hwep->ep)) +			dev_err(ci->dev, "error: ep_set_halt\n"); +		spin_lock(&ci->lock); +	} +} + +/**   * isr_tr_complete_handler: transaction complete interrupt handler   * @ci: UDC descriptor   * @@ -962,12 +1102,10 @@ __releases(ci->lock)  __acquires(ci->lock)  {  	unsigned i; -	u8 tmode = 0; +	int err;  	for (i = 0; i < ci->hw_ep_max; i++) {  		struct ci_hw_ep *hwep  = &ci->ci_hw_ep[i]; -		int type, num, dir, err = -EINVAL; -		struct usb_ctrlrequest req;  		if (hwep->ep.desc == NULL)  			continue;   /* not configured */ @@ -987,148 +1125,10 @@ __acquires(ci->lock)  			}  		} -		if (hwep->type != USB_ENDPOINT_XFER_CONTROL || -		    !hw_test_and_clear_setup_status(ci, i)) -			continue; - -		if (i != 0) { -			dev_warn(ci->dev, "ctrl traffic at endpoint %d\n", i); -			continue; -		} - -		/* -		 * Flush data and handshake transactions of previous -		 * setup packet. -		 */ -		_ep_nuke(ci->ep0out); -		_ep_nuke(ci->ep0in); - -		/* read_setup_packet */ -		do { -			hw_test_and_set_setup_guard(ci); -			memcpy(&req, &hwep->qh.ptr->setup, sizeof(req)); -		} while (!hw_test_and_clear_setup_guard(ci)); - -		type = req.bRequestType; - -		ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; - -		switch (req.bRequest) { -		case USB_REQ_CLEAR_FEATURE: -			if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && -					le16_to_cpu(req.wValue) == -					USB_ENDPOINT_HALT) { -				if (req.wLength != 0) -					break; -				num  = le16_to_cpu(req.wIndex); -				dir = num & USB_ENDPOINT_DIR_MASK; -				num &= USB_ENDPOINT_NUMBER_MASK; -				if (dir) /* TX */ -					num += ci->hw_ep_max/2; -				if (!ci->ci_hw_ep[num].wedge) { -					spin_unlock(&ci->lock); -					err = usb_ep_clear_halt( -						&ci->ci_hw_ep[num].ep); -					spin_lock(&ci->lock); -					if (err) -						break; -				} -				err = isr_setup_status_phase(ci); -			} else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && -					le16_to_cpu(req.wValue) == -					USB_DEVICE_REMOTE_WAKEUP) { -				if (req.wLength != 0) -					break; -				ci->remote_wakeup = 0; -				err = isr_setup_status_phase(ci); -			} else { -				goto delegate; -			} -			break; -		case USB_REQ_GET_STATUS: -			if (type != (USB_DIR_IN|USB_RECIP_DEVICE)   && -			    type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && -			    type != (USB_DIR_IN|USB_RECIP_INTERFACE)) -				goto delegate; -			if (le16_to_cpu(req.wLength) != 2 || -			    le16_to_cpu(req.wValue)  != 0) -				break; -			err = isr_get_status_response(ci, &req); -			break; -		case USB_REQ_SET_ADDRESS: -			if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) -				goto delegate; -			if (le16_to_cpu(req.wLength) != 0 || -			    le16_to_cpu(req.wIndex)  != 0) -				break; -			ci->address = (u8)le16_to_cpu(req.wValue); -			ci->setaddr = true; -			err = isr_setup_status_phase(ci); -			break; -		case USB_REQ_SET_FEATURE: -			if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && -					le16_to_cpu(req.wValue) == -					USB_ENDPOINT_HALT) { -				if (req.wLength != 0) -					break; -				num  = le16_to_cpu(req.wIndex); -				dir = num & USB_ENDPOINT_DIR_MASK; -				num &= USB_ENDPOINT_NUMBER_MASK; -				if (dir) /* TX */ -					num += ci->hw_ep_max/2; - -				spin_unlock(&ci->lock); -				err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep); -				spin_lock(&ci->lock); -				if (!err) -					isr_setup_status_phase(ci); -			} else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { -				if (req.wLength != 0) -					break; -				switch (le16_to_cpu(req.wValue)) { -				case USB_DEVICE_REMOTE_WAKEUP: -					ci->remote_wakeup = 1; -					err = isr_setup_status_phase(ci); -					break; -				case USB_DEVICE_TEST_MODE: -					tmode = le16_to_cpu(req.wIndex) >> 8; -					switch (tmode) { -					case TEST_J: -					case TEST_K: -					case TEST_SE0_NAK: -					case TEST_PACKET: -					case TEST_FORCE_EN: -						ci->test_mode = tmode; -						err = isr_setup_status_phase( -								ci); -						break; -					default: -						break; -					} -				default: -					goto delegate; -				} -			} else { -				goto delegate; -			} -			break; -		default: -delegate: -			if (req.wLength == 0)   /* no data phase */ -				ci->ep0_dir = TX; - -			spin_unlock(&ci->lock); -			err = ci->driver->setup(&ci->gadget, &req); -			spin_lock(&ci->lock); -			break; -		} - -		if (err < 0) { -			spin_unlock(&ci->lock); -			if (usb_ep_set_halt(&hwep->ep)) -				dev_err(ci->dev, "error: ep_set_halt\n"); -			spin_lock(&ci->lock); -		} +		/* Only handle setup packet below */ +		if (i == 0 && +			hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0))) +			isr_setup_packet_handler(ci);  	}  } @@ -1169,14 +1169,25 @@ static int ep_enable(struct usb_ep *ep,  	if (hwep->type == USB_ENDPOINT_XFER_CONTROL)  		cap |= QH_IOS; -	if (hwep->num) -		cap |= QH_ZLT; + +	cap |= QH_ZLT;  	cap |= (hwep->ep.maxpacket << __ffs(QH_MAX_PKT)) & QH_MAX_PKT; +	/* +	 * For ISO-TX, we set mult at QH as the largest value, and use +	 * MultO at TD as real mult value. +	 */ +	if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) +		cap |= 3 << __ffs(QH_MULT);  	hwep->qh.ptr->cap = cpu_to_le32(cap);  	hwep->qh.ptr->td.next |= cpu_to_le32(TD_TERMINATE);   /* needed? */ +	if (hwep->num != 0 && hwep->type == USB_ENDPOINT_XFER_CONTROL) { +		dev_err(hwep->ci->dev, "Set control xfer at non-ep0\n"); +		retval = -EINVAL; +	} +  	/*  	 * Enable endpoints in the HW other than ep0 as ep0  	 * is always enabled @@ -1310,6 +1321,7 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)  	struct ci_hw_ep  *hwep  = container_of(ep,  struct ci_hw_ep, ep);  	struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req);  	unsigned long flags; +	struct td_node *node, *tmpnode;  	if (ep == NULL || req == NULL || hwreq->req.status != -EALREADY ||  		hwep->ep.desc == NULL || list_empty(&hwreq->queue) || @@ -1320,6 +1332,12 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)  	hw_ep_flush(hwep->ci, hwep->num, hwep->dir); +	list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { +		dma_pool_free(hwep->td_pool, node->ptr, node->dma); +		list_del(&node->td); +		kfree(node); +	} +  	/* pop request */  	list_del_init(&hwreq->queue); @@ -1459,15 +1477,17 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)  			pm_runtime_get_sync(&_gadget->dev);  			hw_device_reset(ci, USBMODE_CM_DC);  			hw_device_state(ci, ci->ep0out->qh.dma); -			dev_dbg(ci->dev, "Connected to host\n"); +			usb_gadget_set_state(_gadget, USB_STATE_POWERED);  		} else { +			if (ci->driver) +				ci->driver->disconnect(&ci->gadget);  			hw_device_state(ci, 0);  			if (ci->platdata->notify_event)  				ci->platdata->notify_event(ci,  				CI_HDRC_CONTROLLER_STOPPED_EVENT);  			_gadget_stop_activity(&ci->gadget);  			pm_runtime_put_sync(&_gadget->dev); -			dev_dbg(ci->dev, "Disconnected from host\n"); +			usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED);  		}  	} @@ -1563,7 +1583,7 @@ static int init_eps(struct ci_hdrc *ci)  			 * eps, maxP is set by epautoconfig() called  			 * by gadget layer  			 */ -			hwep->ep.maxpacket = (unsigned short)~0; +			usb_ep_set_maxpacket_limit(&hwep->ep, (unsigned short)~0);  			INIT_LIST_HEAD(&hwep->qh.queue);  			hwep->qh.ptr = dma_pool_alloc(ci->qh_pool, GFP_KERNEL, @@ -1583,7 +1603,7 @@ static int init_eps(struct ci_hdrc *ci)  				else  					ci->ep0in = hwep; -				hwep->ep.maxpacket = CTRL_PAYLOAD_MAX; +				usb_ep_set_maxpacket_limit(&hwep->ep, CTRL_PAYLOAD_MAX);  				continue;  			} @@ -1600,6 +1620,8 @@ static void destroy_eps(struct ci_hdrc *ci)  	for (i = 0; i < ci->hw_ep_max; i++) {  		struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; +		if (hwep->pending_td) +			free_pending_td(hwep);  		dma_pool_free(ci->qh_pool, hwep->qh.ptr, hwep->qh.dma);  	}  } @@ -1631,23 +1653,29 @@ static int ci_udc_start(struct usb_gadget *gadget,  	retval = usb_ep_enable(&ci->ep0in->ep);  	if (retval)  		return retval; -	spin_lock_irqsave(&ci->lock, flags);  	ci->driver = driver; + +	/* Start otg fsm for B-device */ +	if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { +		ci_hdrc_otg_fsm_start(ci); +		return retval; +	} +  	pm_runtime_get_sync(&ci->gadget.dev);  	if (ci->vbus_active) { +		spin_lock_irqsave(&ci->lock, flags);  		hw_device_reset(ci, USBMODE_CM_DC);  	} else {  		pm_runtime_put_sync(&ci->gadget.dev); -		goto done; +		return retval;  	}  	retval = hw_device_state(ci, ci->ep0out->qh.dma); +	spin_unlock_irqrestore(&ci->lock, flags);  	if (retval)  		pm_runtime_put_sync(&ci->gadget.dev); - done: -	spin_unlock_irqrestore(&ci->lock, flags);  	return retval;  } @@ -1667,13 +1695,13 @@ static int ci_udc_stop(struct usb_gadget *gadget,  		if (ci->platdata->notify_event)  			ci->platdata->notify_event(ci,  			CI_HDRC_CONTROLLER_STOPPED_EVENT); -		ci->driver = NULL;  		spin_unlock_irqrestore(&ci->lock, flags);  		_gadget_stop_activity(&ci->gadget);  		spin_lock_irqsave(&ci->lock, flags);  		pm_runtime_put(&ci->gadget.dev);  	} +	ci->driver = NULL;  	spin_unlock_irqrestore(&ci->lock, flags);  	return 0; @@ -1732,6 +1760,8 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)  				ci->suspended = 1;  				spin_unlock(&ci->lock);  				ci->driver->suspend(&ci->gadget); +				usb_gadget_set_state(&ci->gadget, +						USB_STATE_SUSPENDED);  				spin_lock(&ci->lock);  			}  		} @@ -1758,7 +1788,7 @@ static int udc_start(struct ci_hdrc *ci)  	ci->gadget.ops          = &usb_gadget_ops;  	ci->gadget.speed        = USB_SPEED_UNKNOWN;  	ci->gadget.max_speed    = USB_SPEED_HIGH; -	ci->gadget.is_otg       = 0; +	ci->gadget.is_otg       = ci->is_otg ? 1 : 0;  	ci->gadget.name         = ci->platdata->name;  	INIT_LIST_HEAD(&ci->gadget.ep_list); @@ -1784,54 +1814,15 @@ static int udc_start(struct ci_hdrc *ci)  	ci->gadget.ep0 = &ci->ep0in->ep; -	if (ci->global_phy) { -		ci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); -		if (IS_ERR(ci->transceiver)) -			ci->transceiver = NULL; -	} - -	if (ci->platdata->flags & CI_HDRC_REQUIRE_TRANSCEIVER) { -		if (ci->transceiver == NULL) { -			retval = -ENODEV; -			goto destroy_eps; -		} -	} - -	if (ci->transceiver) { -		retval = otg_set_peripheral(ci->transceiver->otg, -						&ci->gadget); -		/* -		 * If we implement all USB functions using chipidea drivers, -		 * it doesn't need to call above API, meanwhile, if we only -		 * use gadget function, calling above API is useless. -		 */ -		if (retval && retval != -ENOTSUPP) -			goto put_transceiver; -	} -  	retval = usb_add_gadget_udc(dev, &ci->gadget);  	if (retval) -		goto remove_trans; +		goto destroy_eps;  	pm_runtime_no_callbacks(&ci->gadget.dev);  	pm_runtime_enable(&ci->gadget.dev); -	/* Update ci->vbus_active */ -	ci_handle_vbus_change(ci); -  	return retval; -remove_trans: -	if (ci->transceiver) { -		otg_set_peripheral(ci->transceiver->otg, NULL); -		if (ci->global_phy) -			usb_put_phy(ci->transceiver); -	} - -	dev_err(dev, "error = %i\n", retval); -put_transceiver: -	if (ci->transceiver && ci->global_phy) -		usb_put_phy(ci->transceiver);  destroy_eps:  	destroy_eps(ci);  free_pools: @@ -1857,31 +1848,26 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)  	dma_pool_destroy(ci->td_pool);  	dma_pool_destroy(ci->qh_pool); - -	if (ci->transceiver) { -		otg_set_peripheral(ci->transceiver->otg, NULL); -		if (ci->global_phy) -			usb_put_phy(ci->transceiver); -	}  }  static int udc_id_switch_for_device(struct ci_hdrc *ci)  { -	if (ci->is_otg) { -		ci_clear_otg_interrupt(ci, OTGSC_BSVIS); -		ci_enable_otg_interrupt(ci, OTGSC_BSVIE); -	} +	if (ci->is_otg) +		/* Clear and enable BSV irq */ +		hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, +					OTGSC_BSVIS | OTGSC_BSVIE);  	return 0;  }  static void udc_id_switch_for_host(struct ci_hdrc *ci)  { -	if (ci->is_otg) { -		/* host doesn't care B_SESSION_VALID event */ -		ci_clear_otg_interrupt(ci, OTGSC_BSVIS); -		ci_disable_otg_interrupt(ci, OTGSC_BSVIE); -	} +	/* +	 * host doesn't care B_SESSION_VALID event +	 * so clear and disbale BSV irq +	 */ +	if (ci->is_otg) +		hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);  }  /** diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 8a1094b1182..85293b8b1df 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -21,12 +21,39 @@  #define MX25_USB_PHY_CTRL_OFFSET	0x08  #define MX25_BM_EXTERNAL_VBUS_DIVIDER	BIT(23) +#define MX25_EHCI_INTERFACE_SINGLE_UNI	(2 << 0) +#define MX25_EHCI_INTERFACE_DIFF_UNI	(0 << 0) +#define MX25_EHCI_INTERFACE_MASK	(0xf) + +#define MX25_OTG_SIC_SHIFT		29 +#define MX25_OTG_SIC_MASK		(0x3 << MX25_OTG_SIC_SHIFT) +#define MX25_OTG_PM_BIT			BIT(24) +#define MX25_OTG_PP_BIT			BIT(11) +#define MX25_OTG_OCPOL_BIT		BIT(3) + +#define MX25_H1_SIC_SHIFT		21 +#define MX25_H1_SIC_MASK		(0x3 << MX25_H1_SIC_SHIFT) +#define MX25_H1_PP_BIT			BIT(18) +#define MX25_H1_PM_BIT			BIT(16) +#define MX25_H1_IPPUE_UP_BIT		BIT(7) +#define MX25_H1_IPPUE_DOWN_BIT		BIT(6) +#define MX25_H1_TLL_BIT			BIT(5) +#define MX25_H1_USBTE_BIT		BIT(4) +#define MX25_H1_OCPOL_BIT		BIT(2) + +#define MX27_H1_PM_BIT			BIT(8) +#define MX27_H2_PM_BIT			BIT(16) +#define MX27_OTG_PM_BIT			BIT(24) +  #define MX53_USB_OTG_PHY_CTRL_0_OFFSET	0x08 +#define MX53_USB_OTG_PHY_CTRL_1_OFFSET	0x0c  #define MX53_USB_UH2_CTRL_OFFSET	0x14  #define MX53_USB_UH3_CTRL_OFFSET	0x18  #define MX53_BM_OVER_CUR_DIS_H1		BIT(5)  #define MX53_BM_OVER_CUR_DIS_OTG	BIT(8)  #define MX53_BM_OVER_CUR_DIS_UHx	BIT(30) +#define MX53_USB_PHYCTRL1_PLLDIV_MASK	0x3 +#define MX53_USB_PLL_DIV_24_MHZ		0x01  #define MX6_BM_OVER_CUR_DIS		BIT(7) @@ -46,6 +73,39 @@ struct imx_usbmisc {  static struct imx_usbmisc *usbmisc; +static int usbmisc_imx25_init(struct imx_usbmisc_data *data) +{ +	unsigned long flags; +	u32 val = 0; + +	if (data->index > 1) +		return -EINVAL; + +	spin_lock_irqsave(&usbmisc->lock, flags); +	switch (data->index) { +	case 0: +		val = readl(usbmisc->base); +		val &= ~(MX25_OTG_SIC_MASK | MX25_OTG_PP_BIT); +		val |= (MX25_EHCI_INTERFACE_DIFF_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_OTG_SIC_SHIFT; +		val |= (MX25_OTG_PM_BIT | MX25_OTG_OCPOL_BIT); +		writel(val, usbmisc->base); +		break; +	case 1: +		val = readl(usbmisc->base); +		val &= ~(MX25_H1_SIC_MASK | MX25_H1_PP_BIT |  MX25_H1_IPPUE_UP_BIT); +		val |= (MX25_EHCI_INTERFACE_SINGLE_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_H1_SIC_SHIFT; +		val |= (MX25_H1_PM_BIT | MX25_H1_OCPOL_BIT | MX25_H1_TLL_BIT | +			MX25_H1_USBTE_BIT | MX25_H1_IPPUE_DOWN_BIT); + +		writel(val, usbmisc->base); + +		break; +	} +	spin_unlock_irqrestore(&usbmisc->lock, flags); + +	return 0; +} +  static int usbmisc_imx25_post(struct imx_usbmisc_data *data)  {  	void __iomem *reg; @@ -68,6 +128,36 @@ static int usbmisc_imx25_post(struct imx_usbmisc_data *data)  	return 0;  } +static int usbmisc_imx27_init(struct imx_usbmisc_data *data) +{ +	unsigned long flags; +	u32 val; + +	switch (data->index) { +	case 0: +		val = MX27_OTG_PM_BIT; +		break; +	case 1: +		val = MX27_H1_PM_BIT; +		break; +	case 2: +		val = MX27_H2_PM_BIT; +		break; +	default: +		return -EINVAL; +	}; + +	spin_lock_irqsave(&usbmisc->lock, flags); +	if (data->disable_oc) +		val = readl(usbmisc->base) | val; +	else +		val = readl(usbmisc->base) & ~val; +	writel(val, usbmisc->base); +	spin_unlock_irqrestore(&usbmisc->lock, flags); + +	return 0; +} +  static int usbmisc_imx53_init(struct imx_usbmisc_data *data)  {  	void __iomem *reg = NULL; @@ -77,6 +167,13 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data)  	if (data->index > 3)  		return -EINVAL; +	/* Select a 24 MHz reference clock for the PHY  */ +	reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET; +	val = readl(reg); +	val &= ~MX53_USB_PHYCTRL1_PLLDIV_MASK; +	val |= MX53_USB_PLL_DIV_24_MHZ; +	writel(val, usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET); +  	if (data->disable_oc) {  		spin_lock_irqsave(&usbmisc->lock, flags);  		switch (data->index) { @@ -125,9 +222,14 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)  }  static const struct usbmisc_ops imx25_usbmisc_ops = { +	.init = usbmisc_imx25_init,  	.post = usbmisc_imx25_post,  }; +static const struct usbmisc_ops imx27_usbmisc_ops = { +	.init = usbmisc_imx27_init, +}; +  static const struct usbmisc_ops imx53_usbmisc_ops = {  	.init = usbmisc_imx53_init,  }; @@ -162,6 +264,18 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = {  		.data = &imx25_usbmisc_ops,  	},  	{ +		.compatible = "fsl,imx35-usbmisc", +		.data = &imx25_usbmisc_ops, +	}, +	{ +		.compatible = "fsl,imx27-usbmisc", +		.data = &imx27_usbmisc_ops, +	}, +	{ +		.compatible = "fsl,imx51-usbmisc", +		.data = &imx53_usbmisc_ops, +	}, +	{  		.compatible = "fsl,imx53-usbmisc",  		.data = &imx53_usbmisc_ops,  	},  | 
