diff options
Diffstat (limited to 'drivers/usb/phy')
33 files changed, 12255 insertions, 1037 deletions
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 65217a59006..aab2ab2fbc9 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -1,13 +1,74 @@  #  # Physical Layer USB driver configuration  # -comment "USB Physical Layer drivers" -	depends on USB || USB_GADGET +menuconfig USB_PHY +	bool "USB Physical Layer drivers" +	help +	  USB controllers (those which are host, device or DRD) need a +	  device to handle the physical layer signalling, commonly called +	  a PHY. + +	  The following drivers add support for such PHY devices. + +if USB_PHY + +# +# USB Transceiver Drivers +# +config AB8500_USB +	tristate "AB8500 USB Transceiver Driver" +	depends on AB8500_CORE +	help +	  Enable this to support the USB OTG transceiver in AB8500 chip. +	  This transceiver supports high and full speed devices plus, +	  in host mode, low speed. + +config FSL_USB2_OTG +	bool "Freescale USB OTG Transceiver Driver" +	depends on USB_EHCI_FSL && USB_FSL_USB2 && USB_SUSPEND +	select USB_OTG +	help +	  Enable this to support Freescale USB OTG transceiver. + +config ISP1301_OMAP +	tristate "Philips ISP1301 with OMAP OTG" +	depends on I2C && ARCH_OMAP_OTG +	help +	  If you say yes here you get support for the Philips ISP1301 +	  USB-On-The-Go transceiver working with the OMAP OTG controller. +	  The ISP1301 is a full speed USB  transceiver which is used in +	  products including H2, H3, and H4 development boards for Texas +	  Instruments OMAP processors. + +	  This driver can also be built as a module.  If so, the module +	  will be called isp1301_omap. + +config MV_U3D_PHY +	bool "Marvell USB 3.0 PHY controller Driver" +	depends on CPU_MMP3 +	help +	  Enable this to support Marvell USB 3.0 phy controller for Marvell +	  SoC. + +config NOP_USB_XCEIV +	tristate "NOP USB Transceiver Driver" +	help +	  This driver is to be used by all the usb transceiver which are either +	  built-in with usb ip or which are autonomous and doesn't require any +	  phy programming such as ISP1x04 etc. + +config OMAP_CONTROL_USB +	tristate "OMAP CONTROL USB Driver" +	help +	  Enable this to add support for the USB part present in the control +	  module. This driver has API to power on the USB2 PHY and to write to +	  the mailbox. The mailbox is present only in omap4 and the register to +	  power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an +	  additional register to power on USB3 PHY.  config OMAP_USB2  	tristate "OMAP USB2 PHY Driver"  	depends on ARCH_OMAP2PLUS -	select USB_OTG_UTILS  	select OMAP_CONTROL_USB  	help  	  Enable this to support the transceiver that is part of SOC. This @@ -17,7 +78,6 @@ config OMAP_USB2  config OMAP_USB3  	tristate "OMAP USB3 PHY Driver" -	select USB_OTG_UTILS  	select OMAP_CONTROL_USB  	help  	  Enable this to support the USB3 PHY that is part of SOC. This @@ -25,19 +85,61 @@ config OMAP_USB3  	  This driver interacts with the "OMAP Control USB Driver" to power  	  on/off the PHY. -config OMAP_CONTROL_USB -	tristate "OMAP CONTROL USB Driver" +config SAMSUNG_USBPHY +	tristate "Samsung USB PHY Driver"  	help -	  Enable this to add support for the USB part present in the control -	  module. This driver has API to power on the USB2 PHY and to write to -	  the mailbox. The mailbox is present only in omap4 and the register to -	  power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an -	  additional register to power on USB3 PHY. +	  Enable this to support Samsung USB phy helper driver for Samsung SoCs. +	  This driver provides common interface to interact, for Samsung USB 2.0 PHY +	  driver and later for Samsung USB 3.0 PHY driver. + +config SAMSUNG_USB2PHY +	tristate "Samsung USB 2.0 PHY controller Driver" +	select SAMSUNG_USBPHY +	help +	  Enable this to support Samsung USB 2.0 (High Speed) PHY controller +	  driver for Samsung SoCs. + +config SAMSUNG_USB3PHY +	tristate "Samsung USB 3.0 PHY controller Driver" +	select SAMSUNG_USBPHY +	help +	  Enable this to support Samsung USB 3.0 (Super Speed) phy controller +	  for samsung SoCs. + +config TWL4030_USB +	tristate "TWL4030 USB Transceiver Driver" +	depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS +	help +	  Enable this to support the USB OTG transceiver on TWL4030 +	  family chips (including the TWL5030 and TPS659x0 devices). +	  This transceiver supports high and full speed devices plus, +	  in host mode, low speed. + +config TWL6030_USB +	tristate "TWL6030 USB Transceiver Driver" +	depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS +	help +	  Enable this to support the USB OTG transceiver on TWL6030 +	  family chips. This TWL6030 transceiver has the VBUS and ID GND +	  and OTG SRP events capabilities. For all other transceiver functionality +	  UTMI PHY is embedded in OMAP4430. The internal PHY configurations APIs +	  are hooked to this driver through platform_data structure. +	  The definition of internal PHY APIs are in the mach-omap2 layer. + +config USB_GPIO_VBUS +	tristate "GPIO based peripheral-only VBUS sensing 'transceiver'" +	depends on GENERIC_GPIO +	help +	  Provides simple GPIO VBUS sensing for controllers with an +	  internal transceiver via the usb_phy interface, and +	  optionally control of a D+ pullup GPIO as well as a VBUS +	  current limit regulator.  config USB_ISP1301  	tristate "NXP ISP1301 USB transceiver support"  	depends on USB || USB_GADGET  	depends on I2C +	select USB_OTG_UTILS  	help  	  Say Y here to add support for the NXP ISP1301 USB transceiver driver.  	  This chip is typically used as USB transceiver for USB host, gadget @@ -46,18 +148,41 @@ config USB_ISP1301  	  To compile this driver as a module, choose M here: the  	  module will be called isp1301. -config MV_U3D_PHY -	bool "Marvell USB 3.0 PHY controller Driver" -	depends on USB_MV_U3D -	select USB_OTG_UTILS +config USB_MSM_OTG +	tristate "OTG support for Qualcomm on-chip USB controller" +	depends on (USB || USB_GADGET) && ARCH_MSM  	help -	  Enable this to support Marvell USB 3.0 phy controller for Marvell -	  SoC. +	  Enable this to support the USB OTG transceiver on MSM chips. It +	  handles PHY initialization, clock management, and workarounds +	  required after resetting the hardware and power management. +	  This driver is required even for peripheral only or host only +	  mode configurations. +	  This driver is not supported on boards like trout which +	  has an external PHY. + +config USB_MV_OTG +	tristate "Marvell USB OTG support" +	depends on USB_EHCI_MV && USB_MV_UDC && USB_SUSPEND +	select USB_OTG +	help +	  Say Y here if you want to build Marvell USB OTG transciever +	  driver in kernel (including PXA and MMP series). This driver +	  implements role switch between EHCI host driver and gadget driver. + +	  To compile this driver as a module, choose M here. + +config USB_MXS_PHY +	tristate "Freescale MXS USB PHY support" +	depends on ARCH_MXC || ARCH_MXS +	select STMP_DEVICE +	help +	  Enable this to support the Freescale MXS USB PHY. + +	  MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x.  config USB_RCAR_PHY  	tristate "Renesas R-Car USB phy support"  	depends on USB || USB_GADGET -	select USB_OTG_UTILS  	help  	  Say Y here to add support for the Renesas R-Car USB phy driver.  	  This chip is typically used as USB phy for USB host, gadget. @@ -66,10 +191,18 @@ config USB_RCAR_PHY  	  To compile this driver as a module, choose M here: the  	  module will be called rcar-phy. -config SAMSUNG_USBPHY -	bool "Samsung USB PHY controller Driver" -	depends on USB_S3C_HSOTG || USB_EHCI_S5P || USB_OHCI_EXYNOS -	select USB_OTG_UTILS +config USB_ULPI +	bool "Generic ULPI Transceiver Driver" +	depends on ARM +	help +	  Enable this to support ULPI connected USB OTG transceivers which +	  are likely found on embedded boards. + +config USB_ULPI_VIEWPORT +	bool +	depends on USB_ULPI  	help -	  Enable this to support Samsung USB phy controller for samsung -	  SoCs. +	  Provides read/write operations to the ULPI phy register set for +	  controllers with a viewport register (e.g. Chipidea/ARC controllers). + +endif # USB_PHY diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index b13faa193e0..a9169cb1e6f 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -4,11 +4,30 @@  ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG -obj-$(CONFIG_OMAP_USB2)			+= omap-usb2.o -obj-$(CONFIG_OMAP_USB3)			+= omap-usb3.o -obj-$(CONFIG_OMAP_CONTROL_USB)		+= omap-control-usb.o -obj-$(CONFIG_USB_ISP1301)		+= isp1301.o -obj-$(CONFIG_MV_U3D_PHY)		+= mv_u3d_phy.o -obj-$(CONFIG_USB_EHCI_TEGRA)	+= tegra_usb_phy.o -obj-$(CONFIG_USB_RCAR_PHY)		+= rcar-phy.o -obj-$(CONFIG_SAMSUNG_USBPHY)		+= samsung-usbphy.o +obj-$(CONFIG_USB_PHY)			+= phy.o + +# transceiver drivers, keep the list sorted + +obj-$(CONFIG_AB8500_USB)		+= phy-ab8500-usb.o +phy-fsl-usb2-objs			:= phy-fsl-usb.o phy-fsm-usb.o +obj-$(CONFIG_FSL_USB2_OTG)		+= phy-fsl-usb2.o +obj-$(CONFIG_ISP1301_OMAP)		+= phy-isp1301-omap.o +obj-$(CONFIG_MV_U3D_PHY)		+= phy-mv-u3d-usb.o +obj-$(CONFIG_NOP_USB_XCEIV)		+= phy-nop.o +obj-$(CONFIG_OMAP_CONTROL_USB)		+= phy-omap-control.o +obj-$(CONFIG_OMAP_USB2)			+= phy-omap-usb2.o +obj-$(CONFIG_OMAP_USB3)			+= phy-omap-usb3.o +obj-$(CONFIG_SAMSUNG_USBPHY)		+= phy-samsung-usb.o +obj-$(CONFIG_SAMSUNG_USB2PHY)		+= phy-samsung-usb2.o +obj-$(CONFIG_SAMSUNG_USB3PHY)		+= phy-samsung-usb3.o +obj-$(CONFIG_TWL4030_USB)		+= phy-twl4030-usb.o +obj-$(CONFIG_TWL6030_USB)		+= phy-twl6030-usb.o +obj-$(CONFIG_USB_EHCI_TEGRA)		+= phy-tegra-usb.o +obj-$(CONFIG_USB_GPIO_VBUS)		+= phy-gpio-vbus-usb.o +obj-$(CONFIG_USB_ISP1301)		+= phy-isp1301.o +obj-$(CONFIG_USB_MSM_OTG)		+= phy-msm-usb.o +obj-$(CONFIG_USB_MV_OTG)		+= phy-mv-usb.o +obj-$(CONFIG_USB_MXS_PHY)		+= phy-mxs-usb.o +obj-$(CONFIG_USB_RCAR_PHY)		+= phy-rcar-usb.o +obj-$(CONFIG_USB_ULPI)			+= phy-ulpi.o +obj-$(CONFIG_USB_ULPI_VIEWPORT)		+= phy-ulpi-viewport.o diff --git a/drivers/usb/phy/isp1301.c b/drivers/usb/phy/isp1301.c deleted file mode 100644 index 18dbf7e3760..00000000000 --- a/drivers/usb/phy/isp1301.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * NXP ISP1301 USB transceiver driver - * - * Copyright (C) 2012 Roland Stigge - * - * Author: Roland Stigge <stigge@antcom.de> - * - * 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. - */ - -#include <linux/module.h> -#include <linux/i2c.h> - -#define DRV_NAME		"isp1301" - -static const struct i2c_device_id isp1301_id[] = { -	{ "isp1301", 0 }, -	{ } -}; - -static struct i2c_client *isp1301_i2c_client; - -static int isp1301_probe(struct i2c_client *client, -			 const struct i2c_device_id *i2c_id) -{ -	isp1301_i2c_client = client; -	return 0; -} - -static int isp1301_remove(struct i2c_client *client) -{ -	return 0; -} - -static struct i2c_driver isp1301_driver = { -	.driver = { -		.name = DRV_NAME, -	}, -	.probe = isp1301_probe, -	.remove = isp1301_remove, -	.id_table = isp1301_id, -}; - -module_i2c_driver(isp1301_driver); - -static int match(struct device *dev, void *data) -{ -	struct device_node *node = (struct device_node *)data; -	return (dev->of_node == node) && -		(dev->driver == &isp1301_driver.driver); -} - -struct i2c_client *isp1301_get_client(struct device_node *node) -{ -	if (node) { /* reference of ISP1301 I2C node via DT */ -		struct device *dev = bus_find_device(&i2c_bus_type, NULL, -						     node, match); -		if (!dev) -			return NULL; -		return to_i2c_client(dev); -	} else { /* non-DT: only one ISP1301 chip supported */ -		return isp1301_i2c_client; -	} -} -EXPORT_SYMBOL_GPL(isp1301_get_client); - -MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); -MODULE_DESCRIPTION("NXP ISP1301 USB transceiver driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c new file mode 100644 index 00000000000..4acef26a2ef --- /dev/null +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -0,0 +1,924 @@ +/* + * drivers/usb/otg/ab8500_usb.c + * + * USB transceiver driver for AB8500 chip + * + * Copyright (C) 2010 ST-Ericsson AB + * Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb/otg.h> +#include <linux/slab.h> +#include <linux/notifier.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/usb/musb-ux500.h> +#include <linux/regulator/consumer.h> +#include <linux/pinctrl/consumer.h> + +/* Bank AB8500_SYS_CTRL2_BLOCK */ +#define AB8500_MAIN_WD_CTRL_REG 0x01 + +/* Bank AB8500_USB */ +#define AB8500_USB_LINE_STAT_REG 0x80 +#define AB8505_USB_LINE_STAT_REG 0x94 +#define AB8500_USB_PHY_CTRL_REG 0x8A + +/* Bank AB8500_DEVELOPMENT */ +#define AB8500_BANK12_ACCESS 0x00 + +/* Bank AB8500_DEBUG */ +#define AB8500_USB_PHY_TUNE1 0x05 +#define AB8500_USB_PHY_TUNE2 0x06 +#define AB8500_USB_PHY_TUNE3 0x07 + +#define AB8500_BIT_OTG_STAT_ID (1 << 0) +#define AB8500_BIT_PHY_CTRL_HOST_EN (1 << 0) +#define AB8500_BIT_PHY_CTRL_DEVICE_EN (1 << 1) +#define AB8500_BIT_WD_CTRL_ENABLE (1 << 0) +#define AB8500_BIT_WD_CTRL_KICK (1 << 1) + +#define AB8500_WD_KICK_DELAY_US 100 /* usec */ +#define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */ +#define AB8500_V20_31952_DISABLE_DELAY_US 100 /* usec */ + +/* Usb line status register */ +enum ab8500_usb_link_status { +	USB_LINK_NOT_CONFIGURED_8500 = 0, +	USB_LINK_STD_HOST_NC_8500, +	USB_LINK_STD_HOST_C_NS_8500, +	USB_LINK_STD_HOST_C_S_8500, +	USB_LINK_HOST_CHG_NM_8500, +	USB_LINK_HOST_CHG_HS_8500, +	USB_LINK_HOST_CHG_HS_CHIRP_8500, +	USB_LINK_DEDICATED_CHG_8500, +	USB_LINK_ACA_RID_A_8500, +	USB_LINK_ACA_RID_B_8500, +	USB_LINK_ACA_RID_C_NM_8500, +	USB_LINK_ACA_RID_C_HS_8500, +	USB_LINK_ACA_RID_C_HS_CHIRP_8500, +	USB_LINK_HM_IDGND_8500, +	USB_LINK_RESERVED_8500, +	USB_LINK_NOT_VALID_LINK_8500, +}; + +enum ab8505_usb_link_status { +	USB_LINK_NOT_CONFIGURED_8505 = 0, +	USB_LINK_STD_HOST_NC_8505, +	USB_LINK_STD_HOST_C_NS_8505, +	USB_LINK_STD_HOST_C_S_8505, +	USB_LINK_CDP_8505, +	USB_LINK_RESERVED0_8505, +	USB_LINK_RESERVED1_8505, +	USB_LINK_DEDICATED_CHG_8505, +	USB_LINK_ACA_RID_A_8505, +	USB_LINK_ACA_RID_B_8505, +	USB_LINK_ACA_RID_C_NM_8505, +	USB_LINK_RESERVED2_8505, +	USB_LINK_RESERVED3_8505, +	USB_LINK_HM_IDGND_8505, +	USB_LINK_CHARGERPORT_NOT_OK_8505, +	USB_LINK_CHARGER_DM_HIGH_8505, +	USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8505, +	USB_LINK_STD_UPSTREAM_NO_IDGNG_NO_VBUS_8505, +	USB_LINK_STD_UPSTREAM_8505, +	USB_LINK_CHARGER_SE1_8505, +	USB_LINK_CARKIT_CHGR_1_8505, +	USB_LINK_CARKIT_CHGR_2_8505, +	USB_LINK_ACA_DOCK_CHGR_8505, +	USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_8505, +	USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_8505, +	USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8505, +	USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8505, +	USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8505, +}; + +enum ab8500_usb_mode { +	USB_IDLE = 0, +	USB_PERIPHERAL, +	USB_HOST, +	USB_DEDICATED_CHG +}; + +struct ab8500_usb { +	struct usb_phy phy; +	struct device *dev; +	struct ab8500 *ab8500; +	unsigned vbus_draw; +	struct work_struct phy_dis_work; +	enum ab8500_usb_mode mode; +	struct regulator *v_ape; +	struct regulator *v_musb; +	struct regulator *v_ulpi; +	int saved_v_ulpi; +	int previous_link_status_state; +	struct pinctrl *pinctrl; +	struct pinctrl_state *pins_sleep; +}; + +static inline struct ab8500_usb *phy_to_ab(struct usb_phy *x) +{ +	return container_of(x, struct ab8500_usb, phy); +} + +static void ab8500_usb_wd_workaround(struct ab8500_usb *ab) +{ +	abx500_set_register_interruptible(ab->dev, +		AB8500_SYS_CTRL2_BLOCK, +		AB8500_MAIN_WD_CTRL_REG, +		AB8500_BIT_WD_CTRL_ENABLE); + +	udelay(AB8500_WD_KICK_DELAY_US); + +	abx500_set_register_interruptible(ab->dev, +		AB8500_SYS_CTRL2_BLOCK, +		AB8500_MAIN_WD_CTRL_REG, +		(AB8500_BIT_WD_CTRL_ENABLE +		| AB8500_BIT_WD_CTRL_KICK)); + +	udelay(AB8500_WD_V11_DISABLE_DELAY_US); + +	abx500_set_register_interruptible(ab->dev, +		AB8500_SYS_CTRL2_BLOCK, +		AB8500_MAIN_WD_CTRL_REG, +		0); +} + +static void ab8500_usb_regulator_enable(struct ab8500_usb *ab) +{ +	int ret, volt; + +	ret = regulator_enable(ab->v_ape); +	if (ret) +		dev_err(ab->dev, "Failed to enable v-ape\n"); + +	if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { +		ab->saved_v_ulpi = regulator_get_voltage(ab->v_ulpi); +		if (ab->saved_v_ulpi < 0) +			dev_err(ab->dev, "Failed to get v_ulpi voltage\n"); + +		ret = regulator_set_voltage(ab->v_ulpi, 1300000, 1350000); +		if (ret < 0) +			dev_err(ab->dev, "Failed to set the Vintcore to 1.3V, ret=%d\n", +					ret); + +		ret = regulator_set_optimum_mode(ab->v_ulpi, 28000); +		if (ret < 0) +			dev_err(ab->dev, "Failed to set optimum mode (ret=%d)\n", +					ret); +	} + +	ret = regulator_enable(ab->v_ulpi); +	if (ret) +		dev_err(ab->dev, "Failed to enable vddulpivio18\n"); + +	if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { +		volt = regulator_get_voltage(ab->v_ulpi); +		if ((volt != 1300000) && (volt != 1350000)) +			dev_err(ab->dev, "Vintcore is not set to 1.3V volt=%d\n", +					volt); +	} + +	ret = regulator_enable(ab->v_musb); +	if (ret) +		dev_err(ab->dev, "Failed to enable musb_1v8\n"); +} + +static void ab8500_usb_regulator_disable(struct ab8500_usb *ab) +{ +	int ret; + +	regulator_disable(ab->v_musb); + +	regulator_disable(ab->v_ulpi); + +	/* USB is not the only consumer of Vintcore, restore old settings */ +	if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { +		if (ab->saved_v_ulpi > 0) { +			ret = regulator_set_voltage(ab->v_ulpi, +					ab->saved_v_ulpi, ab->saved_v_ulpi); +			if (ret < 0) +				dev_err(ab->dev, "Failed to set the Vintcore to %duV, ret=%d\n", +						ab->saved_v_ulpi, ret); +		} + +		ret = regulator_set_optimum_mode(ab->v_ulpi, 0); +		if (ret < 0) +			dev_err(ab->dev, "Failed to set optimum mode (ret=%d)\n", +					ret); +	} + +	regulator_disable(ab->v_ape); +} + +static void ab8500_usb_wd_linkstatus(struct ab8500_usb *ab, u8 bit) +{ +	/* Workaround for v2.0 bug # 31952 */ +	if (is_ab8500_2p0(ab->ab8500)) { +		abx500_mask_and_set_register_interruptible(ab->dev, +				AB8500_USB, AB8500_USB_PHY_CTRL_REG, +				bit, bit); +		udelay(AB8500_V20_31952_DISABLE_DELAY_US); +	} +} + +static void ab8500_usb_phy_enable(struct ab8500_usb *ab, bool sel_host) +{ +	u8 bit; +	bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN : +		AB8500_BIT_PHY_CTRL_DEVICE_EN; + +	/* mux and configure USB pins to DEFAULT state */ +	ab->pinctrl = pinctrl_get_select(ab->dev, PINCTRL_STATE_DEFAULT); +	if (IS_ERR(ab->pinctrl)) +		dev_err(ab->dev, "could not get/set default pinstate\n"); + +	ab8500_usb_regulator_enable(ab); + +	abx500_mask_and_set_register_interruptible(ab->dev, +			AB8500_USB, AB8500_USB_PHY_CTRL_REG, +			bit, bit); +} + +static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) +{ +	u8 bit; +	bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN : +		AB8500_BIT_PHY_CTRL_DEVICE_EN; + +	ab8500_usb_wd_linkstatus(ab, bit); + +	abx500_mask_and_set_register_interruptible(ab->dev, +			AB8500_USB, AB8500_USB_PHY_CTRL_REG, +			bit, 0); + +	/* Needed to disable the phy.*/ +	ab8500_usb_wd_workaround(ab); + +	ab8500_usb_regulator_disable(ab); + +	if (!IS_ERR(ab->pinctrl)) { +		/* configure USB pins to SLEEP state */ +		ab->pins_sleep = pinctrl_lookup_state(ab->pinctrl, +				PINCTRL_STATE_SLEEP); + +		if (IS_ERR(ab->pins_sleep)) +			dev_dbg(ab->dev, "could not get sleep pinstate\n"); +		else if (pinctrl_select_state(ab->pinctrl, ab->pins_sleep)) +			dev_err(ab->dev, "could not set pins to sleep state\n"); + +		/* as USB pins are shared with idddet, release them to allow +		 * iddet to request them +		 */ +		pinctrl_put(ab->pinctrl); +	} +} + +#define ab8500_usb_host_phy_en(ab)	ab8500_usb_phy_enable(ab, true) +#define ab8500_usb_host_phy_dis(ab)	ab8500_usb_phy_disable(ab, true) +#define ab8500_usb_peri_phy_en(ab)	ab8500_usb_phy_enable(ab, false) +#define ab8500_usb_peri_phy_dis(ab)	ab8500_usb_phy_disable(ab, false) + +static int ab8505_usb_link_status_update(struct ab8500_usb *ab, +		enum ab8505_usb_link_status lsts) +{ +	enum ux500_musb_vbus_id_status event = 0; + +	dev_dbg(ab->dev, "ab8505_usb_link_status_update %d\n", lsts); + +	/* +	 * Spurious link_status interrupts are seen at the time of +	 * disconnection of a device in RIDA state +	 */ +	if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8505 && +			(lsts == USB_LINK_STD_HOST_NC_8505)) +		return 0; + +	ab->previous_link_status_state = lsts; + +	switch (lsts) { +	case USB_LINK_ACA_RID_B_8505: +		event = UX500_MUSB_RIDB; +	case USB_LINK_NOT_CONFIGURED_8505: +	case USB_LINK_RESERVED0_8505: +	case USB_LINK_RESERVED1_8505: +	case USB_LINK_RESERVED2_8505: +	case USB_LINK_RESERVED3_8505: +		ab->mode = USB_IDLE; +		ab->phy.otg->default_a = false; +		ab->vbus_draw = 0; +		if (event != UX500_MUSB_RIDB) +			event = UX500_MUSB_NONE; +		/* +		 * Fallback to default B_IDLE as nothing +		 * is connected +		 */ +		ab->phy.state = OTG_STATE_B_IDLE; +		break; + +	case USB_LINK_ACA_RID_C_NM_8505: +		event = UX500_MUSB_RIDC; +	case USB_LINK_STD_HOST_NC_8505: +	case USB_LINK_STD_HOST_C_NS_8505: +	case USB_LINK_STD_HOST_C_S_8505: +	case USB_LINK_CDP_8505: +		if (ab->mode == USB_IDLE) { +			ab->mode = USB_PERIPHERAL; +			ab8500_usb_peri_phy_en(ab); +			atomic_notifier_call_chain(&ab->phy.notifier, +					UX500_MUSB_PREPARE, &ab->vbus_draw); +		} +		if (event != UX500_MUSB_RIDC) +			event = UX500_MUSB_VBUS; +		break; + +	case USB_LINK_ACA_RID_A_8505: +	case USB_LINK_ACA_DOCK_CHGR_8505: +		event = UX500_MUSB_RIDA; +	case USB_LINK_HM_IDGND_8505: +		if (ab->mode == USB_IDLE) { +			ab->mode = USB_HOST; +			ab8500_usb_host_phy_en(ab); +			atomic_notifier_call_chain(&ab->phy.notifier, +					UX500_MUSB_PREPARE, &ab->vbus_draw); +		} +		ab->phy.otg->default_a = true; +		if (event != UX500_MUSB_RIDA) +			event = UX500_MUSB_ID; +		atomic_notifier_call_chain(&ab->phy.notifier, +				event, &ab->vbus_draw); +		break; + +	case USB_LINK_DEDICATED_CHG_8505: +		ab->mode = USB_DEDICATED_CHG; +		event = UX500_MUSB_CHARGER; +		atomic_notifier_call_chain(&ab->phy.notifier, +				event, &ab->vbus_draw); +		break; + +	default: +		break; +	} + +	return 0; +} + +static int ab8500_usb_link_status_update(struct ab8500_usb *ab, +		enum ab8500_usb_link_status lsts) +{ +	enum ux500_musb_vbus_id_status event = 0; + +	dev_dbg(ab->dev, "ab8500_usb_link_status_update %d\n", lsts); + +	/* +	 * Spurious link_status interrupts are seen in case of a +	 * disconnection of a device in IDGND and RIDA stage +	 */ +	if (ab->previous_link_status_state == USB_LINK_HM_IDGND_8500 && +			(lsts == USB_LINK_STD_HOST_C_NS_8500 || +			 lsts == USB_LINK_STD_HOST_NC_8500)) +		return 0; + +	if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8500 && +			lsts == USB_LINK_STD_HOST_NC_8500) +		return 0; + +	ab->previous_link_status_state = lsts; + +	switch (lsts) { +	case USB_LINK_ACA_RID_B_8500: +		event = UX500_MUSB_RIDB; +	case USB_LINK_NOT_CONFIGURED_8500: +	case USB_LINK_NOT_VALID_LINK_8500: +		ab->mode = USB_IDLE; +		ab->phy.otg->default_a = false; +		ab->vbus_draw = 0; +		if (event != UX500_MUSB_RIDB) +			event = UX500_MUSB_NONE; +		/* Fallback to default B_IDLE as nothing is connected */ +		ab->phy.state = OTG_STATE_B_IDLE; +		break; + +	case USB_LINK_ACA_RID_C_NM_8500: +	case USB_LINK_ACA_RID_C_HS_8500: +	case USB_LINK_ACA_RID_C_HS_CHIRP_8500: +		event = UX500_MUSB_RIDC; +	case USB_LINK_STD_HOST_NC_8500: +	case USB_LINK_STD_HOST_C_NS_8500: +	case USB_LINK_STD_HOST_C_S_8500: +	case USB_LINK_HOST_CHG_NM_8500: +	case USB_LINK_HOST_CHG_HS_8500: +	case USB_LINK_HOST_CHG_HS_CHIRP_8500: +		if (ab->mode == USB_IDLE) { +			ab->mode = USB_PERIPHERAL; +			ab8500_usb_peri_phy_en(ab); +			atomic_notifier_call_chain(&ab->phy.notifier, +					UX500_MUSB_PREPARE, &ab->vbus_draw); +		} +		if (event != UX500_MUSB_RIDC) +			event = UX500_MUSB_VBUS; +		break; + +	case USB_LINK_ACA_RID_A_8500: +		event = UX500_MUSB_RIDA; +	case USB_LINK_HM_IDGND_8500: +		if (ab->mode == USB_IDLE) { +			ab->mode = USB_HOST; +			ab8500_usb_host_phy_en(ab); +			atomic_notifier_call_chain(&ab->phy.notifier, +					UX500_MUSB_PREPARE, &ab->vbus_draw); +		} +		ab->phy.otg->default_a = true; +		if (event != UX500_MUSB_RIDA) +			event = UX500_MUSB_ID; +		atomic_notifier_call_chain(&ab->phy.notifier, +				event, &ab->vbus_draw); +		break; + +	case USB_LINK_DEDICATED_CHG_8500: +		ab->mode = USB_DEDICATED_CHG; +		event = UX500_MUSB_CHARGER; +		atomic_notifier_call_chain(&ab->phy.notifier, +				event, &ab->vbus_draw); +		break; + +	case USB_LINK_RESERVED_8500: +		break; +	} + +	return 0; +} + +/* + * Connection Sequence: + *   1. Link Status Interrupt + *   2. Enable AB clock + *   3. Enable AB regulators + *   4. Enable USB phy + *   5. Reset the musb controller + *   6. Switch the ULPI GPIO pins to fucntion mode + *   7. Enable the musb Peripheral5 clock + *   8. Restore MUSB context + */ +static int abx500_usb_link_status_update(struct ab8500_usb *ab) +{ +	u8 reg; +	int ret = 0; + +	if (is_ab8500(ab->ab8500)) { +		enum ab8500_usb_link_status lsts; + +		abx500_get_register_interruptible(ab->dev, +				AB8500_USB, AB8500_USB_LINE_STAT_REG, ®); +		lsts = (reg >> 3) & 0x0F; +		ret = ab8500_usb_link_status_update(ab, lsts); +	} else if (is_ab8505(ab->ab8500)) { +		enum ab8505_usb_link_status lsts; + +		abx500_get_register_interruptible(ab->dev, +				AB8500_USB, AB8505_USB_LINE_STAT_REG, ®); +		lsts = (reg >> 3) & 0x1F; +		ret = ab8505_usb_link_status_update(ab, lsts); +	} + +	return ret; +} + +/* + * Disconnection Sequence: + *   1. Disconect Interrupt + *   2. Disable regulators + *   3. Disable AB clock + *   4. Disable the Phy + *   5. Link Status Interrupt + *   6. Disable Musb Clock + */ +static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data) +{ +	struct ab8500_usb *ab = (struct ab8500_usb *) data; +	enum usb_phy_events event = UX500_MUSB_NONE; + +	/* Link status will not be updated till phy is disabled. */ +	if (ab->mode == USB_HOST) { +		ab->phy.otg->default_a = false; +		ab->vbus_draw = 0; +		atomic_notifier_call_chain(&ab->phy.notifier, +				event, &ab->vbus_draw); +		ab8500_usb_host_phy_dis(ab); +		ab->mode = USB_IDLE; +	} + +	if (ab->mode == USB_PERIPHERAL) { +		atomic_notifier_call_chain(&ab->phy.notifier, +				event, &ab->vbus_draw); +		ab8500_usb_peri_phy_dis(ab); +		atomic_notifier_call_chain(&ab->phy.notifier, +				UX500_MUSB_CLEAN, &ab->vbus_draw); +		ab->mode = USB_IDLE; +		ab->phy.otg->default_a = false; +		ab->vbus_draw = 0; +	} + +	if (is_ab8500_2p0(ab->ab8500)) { +		if (ab->mode == USB_DEDICATED_CHG) { +			ab8500_usb_wd_linkstatus(ab, +					AB8500_BIT_PHY_CTRL_DEVICE_EN); +			abx500_mask_and_set_register_interruptible(ab->dev, +					AB8500_USB, AB8500_USB_PHY_CTRL_REG, +					AB8500_BIT_PHY_CTRL_DEVICE_EN, 0); +		} +	} + +	return IRQ_HANDLED; +} + +static irqreturn_t ab8500_usb_link_status_irq(int irq, void *data) +{ +	struct ab8500_usb *ab = (struct ab8500_usb *) data; + +	abx500_usb_link_status_update(ab); + +	return IRQ_HANDLED; +} + +static void ab8500_usb_phy_disable_work(struct work_struct *work) +{ +	struct ab8500_usb *ab = container_of(work, struct ab8500_usb, +						phy_dis_work); + +	if (!ab->phy.otg->host) +		ab8500_usb_host_phy_dis(ab); + +	if (!ab->phy.otg->gadget) +		ab8500_usb_peri_phy_dis(ab); +} + +static unsigned ab8500_eyediagram_workaroud(struct ab8500_usb *ab, unsigned mA) +{ +	/* +	 * AB8500 V2 has eye diagram issues when drawing more than 100mA from +	 * VBUS.  Set charging current to 100mA in case of standard host +	 */ +	if (is_ab8500_2p0_or_earlier(ab->ab8500)) +		if (mA > 100) +			mA = 100; + +	return mA; +} + +static int ab8500_usb_set_power(struct usb_phy *phy, unsigned mA) +{ +	struct ab8500_usb *ab; + +	if (!phy) +		return -ENODEV; + +	ab = phy_to_ab(phy); + +	mA = ab8500_eyediagram_workaroud(ab, mA); + +	ab->vbus_draw = mA; + +	atomic_notifier_call_chain(&ab->phy.notifier, +			UX500_MUSB_VBUS, &ab->vbus_draw); + +	return 0; +} + +static int ab8500_usb_set_suspend(struct usb_phy *x, int suspend) +{ +	/* TODO */ +	return 0; +} + +static int ab8500_usb_set_peripheral(struct usb_otg *otg, +					struct usb_gadget *gadget) +{ +	struct ab8500_usb *ab; + +	if (!otg) +		return -ENODEV; + +	ab = phy_to_ab(otg->phy); + +	ab->phy.otg->gadget = gadget; + +	/* Some drivers call this function in atomic context. +	 * Do not update ab8500 registers directly till this +	 * is fixed. +	 */ + +	if ((ab->mode != USB_IDLE) && (!gadget)) { +		ab->mode = USB_IDLE; +		schedule_work(&ab->phy_dis_work); +	} + +	return 0; +} + +static int ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	struct ab8500_usb *ab; + +	if (!otg) +		return -ENODEV; + +	ab = phy_to_ab(otg->phy); + +	ab->phy.otg->host = host; + +	/* Some drivers call this function in atomic context. +	 * Do not update ab8500 registers directly till this +	 * is fixed. +	 */ + +	if ((ab->mode != USB_IDLE) && (!host)) { +		ab->mode = USB_IDLE; +		schedule_work(&ab->phy_dis_work); +	} + +	return 0; +} + +static int ab8500_usb_regulator_get(struct ab8500_usb *ab) +{ +	int err; + +	ab->v_ape = devm_regulator_get(ab->dev, "v-ape"); +	if (IS_ERR(ab->v_ape)) { +		dev_err(ab->dev, "Could not get v-ape supply\n"); +		err = PTR_ERR(ab->v_ape); +		return err; +	} + +	ab->v_ulpi = devm_regulator_get(ab->dev, "vddulpivio18"); +	if (IS_ERR(ab->v_ulpi)) { +		dev_err(ab->dev, "Could not get vddulpivio18 supply\n"); +		err = PTR_ERR(ab->v_ulpi); +		return err; +	} + +	ab->v_musb = devm_regulator_get(ab->dev, "musb_1v8"); +	if (IS_ERR(ab->v_musb)) { +		dev_err(ab->dev, "Could not get musb_1v8 supply\n"); +		err = PTR_ERR(ab->v_musb); +		return err; +	} + +	return 0; +} + +static int ab8500_usb_irq_setup(struct platform_device *pdev, +		struct ab8500_usb *ab) +{ +	int err; +	int irq; + +	irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); +	if (irq < 0) { +		dev_err(&pdev->dev, "Link status irq not found\n"); +		return irq; +	} +	err = devm_request_threaded_irq(&pdev->dev, irq, NULL, +			ab8500_usb_link_status_irq, +			IRQF_NO_SUSPEND | IRQF_SHARED, "usb-link-status", ab); +	if (err < 0) { +		dev_err(ab->dev, "request_irq failed for link status irq\n"); +		return err; +	} + +	irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); +	if (irq < 0) { +		dev_err(&pdev->dev, "ID fall irq not found\n"); +		return irq; +	} +	err = devm_request_threaded_irq(&pdev->dev, irq, NULL, +			ab8500_usb_disconnect_irq, +			IRQF_NO_SUSPEND | IRQF_SHARED, "usb-id-fall", ab); +	if (err < 0) { +		dev_err(ab->dev, "request_irq failed for ID fall irq\n"); +		return err; +	} + +	irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); +	if (irq < 0) { +		dev_err(&pdev->dev, "VBUS fall irq not found\n"); +		return irq; +	} +	err = devm_request_threaded_irq(&pdev->dev, irq, NULL, +			ab8500_usb_disconnect_irq, +			IRQF_NO_SUSPEND | IRQF_SHARED, "usb-vbus-fall", ab); +	if (err < 0) { +		dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); +		return err; +	} + +	return 0; +} + +static int ab8500_usb_probe(struct platform_device *pdev) +{ +	struct ab8500_usb	*ab; +	struct ab8500		*ab8500; +	struct usb_otg		*otg; +	int err; +	int rev; + +	ab8500 = dev_get_drvdata(pdev->dev.parent); +	rev = abx500_get_chip_id(&pdev->dev); + +	if (is_ab8500_1p1_or_earlier(ab8500)) { +		dev_err(&pdev->dev, "Unsupported AB8500 chip rev=%d\n", rev); +		return -ENODEV; +	} + +	ab = devm_kzalloc(&pdev->dev, sizeof(*ab), GFP_KERNEL); +	if (!ab) +		return -ENOMEM; + +	otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); +	if (!otg) +		return -ENOMEM; + +	ab->dev			= &pdev->dev; +	ab->ab8500		= ab8500; +	ab->phy.dev		= ab->dev; +	ab->phy.otg		= otg; +	ab->phy.label		= "ab8500"; +	ab->phy.set_suspend	= ab8500_usb_set_suspend; +	ab->phy.set_power	= ab8500_usb_set_power; +	ab->phy.state		= OTG_STATE_UNDEFINED; + +	otg->phy		= &ab->phy; +	otg->set_host		= ab8500_usb_set_host; +	otg->set_peripheral	= ab8500_usb_set_peripheral; + +	platform_set_drvdata(pdev, ab); + +	ATOMIC_INIT_NOTIFIER_HEAD(&ab->phy.notifier); + +	/* all: Disable phy when called from set_host and set_peripheral */ +	INIT_WORK(&ab->phy_dis_work, ab8500_usb_phy_disable_work); + +	err = ab8500_usb_regulator_get(ab); +	if (err) +		return err; + +	err = ab8500_usb_irq_setup(pdev, ab); +	if (err < 0) +		return err; + +	err = usb_add_phy(&ab->phy, USB_PHY_TYPE_USB2); +	if (err) { +		dev_err(&pdev->dev, "Can't register transceiver\n"); +		return err; +	} + +	/* Phy tuning values for AB8500 */ +	if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { +		/* Enable the PBT/Bank 0x12 access */ +		err = abx500_set_register_interruptible(ab->dev, +				AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); +		if (err < 0) +			dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", +					err); + +		err = abx500_set_register_interruptible(ab->dev, +				AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); +		if (err < 0) +			dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", +					err); + +		err = abx500_set_register_interruptible(ab->dev, +				AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x00); +		if (err < 0) +			dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", +					err); + +		err = abx500_set_register_interruptible(ab->dev, +				AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x78); +		if (err < 0) +			dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", +					err); + +		/* Switch to normal mode/disable Bank 0x12 access */ +		err = abx500_set_register_interruptible(ab->dev, +				AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); +		if (err < 0) +			dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", +					err); +	} + +	/* Phy tuning values for AB8505 */ +	if (is_ab8505(ab->ab8500)) { +		/* Enable the PBT/Bank 0x12 access */ +		err = abx500_mask_and_set_register_interruptible(ab->dev, +				AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, +				0x01, 0x01); +		if (err < 0) +			dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", +					err); + +		err = abx500_mask_and_set_register_interruptible(ab->dev, +				AB8500_DEBUG, AB8500_USB_PHY_TUNE1, +				0xC8, 0xC8); +		if (err < 0) +			dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", +					err); + +		err = abx500_mask_and_set_register_interruptible(ab->dev, +				AB8500_DEBUG, AB8500_USB_PHY_TUNE2, +				0x60, 0x60); +		if (err < 0) +			dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", +					err); + +		err = abx500_mask_and_set_register_interruptible(ab->dev, +				AB8500_DEBUG, AB8500_USB_PHY_TUNE3, +				0xFC, 0x80); + +		if (err < 0) +			dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", +					err); + +		/* Switch to normal mode/disable Bank 0x12 access */ +		err = abx500_mask_and_set_register_interruptible(ab->dev, +				AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, +				0x00, 0x00); +		if (err < 0) +			dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", +					err); +	} + +	/* Needed to enable ID detection. */ +	ab8500_usb_wd_workaround(ab); + +	abx500_usb_link_status_update(ab); + +	dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev); + +	return 0; +} + +static int ab8500_usb_remove(struct platform_device *pdev) +{ +	struct ab8500_usb *ab = platform_get_drvdata(pdev); + +	cancel_work_sync(&ab->phy_dis_work); + +	usb_remove_phy(&ab->phy); + +	if (ab->mode == USB_HOST) +		ab8500_usb_host_phy_dis(ab); +	else if (ab->mode == USB_PERIPHERAL) +		ab8500_usb_peri_phy_dis(ab); + +	platform_set_drvdata(pdev, NULL); + +	return 0; +} + +static struct platform_driver ab8500_usb_driver = { +	.probe		= ab8500_usb_probe, +	.remove		= ab8500_usb_remove, +	.driver		= { +		.name	= "ab8500-usb", +		.owner	= THIS_MODULE, +	}, +}; + +static int __init ab8500_usb_init(void) +{ +	return platform_driver_register(&ab8500_usb_driver); +} +subsys_initcall(ab8500_usb_init); + +static void __exit ab8500_usb_exit(void) +{ +	platform_driver_unregister(&ab8500_usb_driver); +} +module_exit(ab8500_usb_exit); + +MODULE_ALIAS("platform:ab8500_usb"); +MODULE_AUTHOR("ST-Ericsson AB"); +MODULE_DESCRIPTION("AB8500 usb transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c new file mode 100644 index 00000000000..97b9308507c --- /dev/null +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -0,0 +1,1173 @@ +/* + * Copyright (C) 2007,2008 Freescale semiconductor, Inc. + * + * Author: Li Yang <LeoLi@freescale.com> + *         Jerry Huang <Chang-Ming.Huang@freescale.com> + * + * Initialization based on code from Shlomi Gridish. + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of  the GNU General  Public License as published by the + * Free Software Foundation;  either version 2 of the  License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the  GNU General Public License along + * with this program; if not, write  to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/timer.h> +#include <linux/usb.h> +#include <linux/device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/workqueue.h> +#include <linux/time.h> +#include <linux/fsl_devices.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> + +#include <asm/unaligned.h> + +#include "phy-fsl-usb.h" + +#define DRIVER_VERSION "Rev. 1.55" +#define DRIVER_AUTHOR "Jerry Huang/Li Yang" +#define DRIVER_DESC "Freescale USB OTG Transceiver Driver" +#define DRIVER_INFO DRIVER_DESC " " DRIVER_VERSION + +static const char driver_name[] = "fsl-usb2-otg"; + +const pm_message_t otg_suspend_state = { +	.event = 1, +}; + +#define HA_DATA_PULSE + +static struct usb_dr_mmap *usb_dr_regs; +static struct fsl_otg *fsl_otg_dev; +static int srp_wait_done; + +/* FSM timers */ +struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, +	*b_ase0_brst_tmr, *b_se0_srp_tmr; + +/* Driver specific timers */ +struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, +	*b_srp_wait_tmr, *a_wait_enum_tmr; + +static struct list_head active_timers; + +static struct fsl_otg_config fsl_otg_initdata = { +	.otg_port = 1, +}; + +#ifdef CONFIG_PPC32 +static u32 _fsl_readl_be(const unsigned __iomem *p) +{ +	return in_be32(p); +} + +static u32 _fsl_readl_le(const unsigned __iomem *p) +{ +	return in_le32(p); +} + +static void _fsl_writel_be(u32 v, unsigned __iomem *p) +{ +	out_be32(p, v); +} + +static void _fsl_writel_le(u32 v, unsigned __iomem *p) +{ +	out_le32(p, v); +} + +static u32 (*_fsl_readl)(const unsigned __iomem *p); +static void (*_fsl_writel)(u32 v, unsigned __iomem *p); + +#define fsl_readl(p)		(*_fsl_readl)((p)) +#define fsl_writel(v, p)	(*_fsl_writel)((v), (p)) + +#else +#define fsl_readl(addr)		readl(addr) +#define fsl_writel(val, addr)	writel(val, addr) +#endif /* CONFIG_PPC32 */ + +/* Routines to access transceiver ULPI registers */ +u8 view_ulpi(u8 addr) +{ +	u32 temp; + +	temp = 0x40000000 | (addr << 16); +	fsl_writel(temp, &usb_dr_regs->ulpiview); +	udelay(1000); +	while (temp & 0x40) +		temp = fsl_readl(&usb_dr_regs->ulpiview); +	return (le32_to_cpu(temp) & 0x0000ff00) >> 8; +} + +int write_ulpi(u8 addr, u8 data) +{ +	u32 temp; + +	temp = 0x60000000 | (addr << 16) | data; +	fsl_writel(temp, &usb_dr_regs->ulpiview); +	return 0; +} + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ + +/* Charge vbus for vbus pulsing in SRP */ +void fsl_otg_chrg_vbus(int on) +{ +	u32 tmp; + +	tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + +	if (on) +		/* stop discharging, start charging */ +		tmp = (tmp & ~OTGSC_CTRL_VBUS_DISCHARGE) | +			OTGSC_CTRL_VBUS_CHARGE; +	else +		/* stop charging */ +		tmp &= ~OTGSC_CTRL_VBUS_CHARGE; + +	fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* Discharge vbus through a resistor to ground */ +void fsl_otg_dischrg_vbus(int on) +{ +	u32 tmp; + +	tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + +	if (on) +		/* stop charging, start discharging */ +		tmp = (tmp & ~OTGSC_CTRL_VBUS_CHARGE) | +			OTGSC_CTRL_VBUS_DISCHARGE; +	else +		/* stop discharging */ +		tmp &= ~OTGSC_CTRL_VBUS_DISCHARGE; + +	fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* A-device driver vbus, controlled through PP bit in PORTSC */ +void fsl_otg_drv_vbus(int on) +{ +	u32 tmp; + +	if (on) { +		tmp = fsl_readl(&usb_dr_regs->portsc) & ~PORTSC_W1C_BITS; +		fsl_writel(tmp | PORTSC_PORT_POWER, &usb_dr_regs->portsc); +	} else { +		tmp = fsl_readl(&usb_dr_regs->portsc) & +		      ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER; +		fsl_writel(tmp, &usb_dr_regs->portsc); +	} +} + +/* + * Pull-up D+, signalling connect by periperal. Also used in + * data-line pulsing in SRP + */ +void fsl_otg_loc_conn(int on) +{ +	u32 tmp; + +	tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; + +	if (on) +		tmp |= OTGSC_CTRL_DATA_PULSING; +	else +		tmp &= ~OTGSC_CTRL_DATA_PULSING; + +	fsl_writel(tmp, &usb_dr_regs->otgsc); +} + +/* + * 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. + */ +void fsl_otg_loc_sof(int on) +{ +	u32 tmp; + +	tmp = fsl_readl(&fsl_otg_dev->dr_mem_map->portsc) & ~PORTSC_W1C_BITS; +	if (on) +		tmp |= PORTSC_PORT_FORCE_RESUME; +	else +		tmp |= PORTSC_PORT_SUSPEND; + +	fsl_writel(tmp, &fsl_otg_dev->dr_mem_map->portsc); + +} + +/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ +void fsl_otg_start_pulse(void) +{ +	u32 tmp; + +	srp_wait_done = 0; +#ifdef HA_DATA_PULSE +	tmp = fsl_readl(&usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK; +	tmp |= OTGSC_HA_DATA_PULSE; +	fsl_writel(tmp, &usb_dr_regs->otgsc); +#else +	fsl_otg_loc_conn(1); +#endif + +	fsl_otg_add_timer(b_data_pulse_tmr); +} + +void b_data_pulse_end(unsigned long foo) +{ +#ifdef HA_DATA_PULSE +#else +	fsl_otg_loc_conn(0); +#endif + +	/* Do VBUS pulse after data pulse */ +	fsl_otg_pulse_vbus(); +} + +void fsl_otg_pulse_vbus(void) +{ +	srp_wait_done = 0; +	fsl_otg_chrg_vbus(1); +	/* start the timer to end vbus charge */ +	fsl_otg_add_timer(b_vbus_pulse_tmr); +} + +void b_vbus_pulse_end(unsigned long foo) +{ +	fsl_otg_chrg_vbus(0); + +	/* +	 * As USB3300 using the same a_sess_vld and b_sess_vld voltage +	 * we need to discharge the bus for a while to distinguish +	 * residual voltage of vbus pulsing and A device pull up +	 */ +	fsl_otg_dischrg_vbus(1); +	fsl_otg_add_timer(b_srp_wait_tmr); +} + +void b_srp_end(unsigned long foo) +{ +	fsl_otg_dischrg_vbus(0); +	srp_wait_done = 1; + +	if ((fsl_otg_dev->phy.state == OTG_STATE_B_SRP_INIT) && +	    fsl_otg_dev->fsm.b_sess_vld) +		fsl_otg_dev->fsm.b_srp_done = 1; +} + +/* + * Workaround for a_host suspending too fast.  When a_bus_req=0, + * a_host will start by SRP.  It needs to set b_hnp_enable before + * actually suspending to start HNP + */ +void a_wait_enum(unsigned long foo) +{ +	VDBG("a_wait_enum timeout\n"); +	if (!fsl_otg_dev->phy.otg->host->b_hnp_enable) +		fsl_otg_add_timer(a_wait_enum_tmr); +	else +		otg_statemachine(&fsl_otg_dev->fsm); +} + +/* The timeout callback function to set time out bit */ +void set_tmout(unsigned long indicator) +{ +	*(int *)indicator = 1; +} + +/* Initialize timers */ +int fsl_otg_init_timers(struct otg_fsm *fsm) +{ +	/* FSM used timers */ +	a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, +				(unsigned long)&fsm->a_wait_vrise_tmout); +	if (!a_wait_vrise_tmr) +		return -ENOMEM; + +	a_wait_bcon_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_BCON, +				(unsigned long)&fsm->a_wait_bcon_tmout); +	if (!a_wait_bcon_tmr) +		return -ENOMEM; + +	a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, +				(unsigned long)&fsm->a_aidl_bdis_tmout); +	if (!a_aidl_bdis_tmr) +		return -ENOMEM; + +	b_ase0_brst_tmr = otg_timer_initializer(&set_tmout, TB_ASE0_BRST, +				(unsigned long)&fsm->b_ase0_brst_tmout); +	if (!b_ase0_brst_tmr) +		return -ENOMEM; + +	b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP, +				(unsigned long)&fsm->b_se0_srp); +	if (!b_se0_srp_tmr) +		return -ENOMEM; + +	b_srp_fail_tmr = otg_timer_initializer(&set_tmout, TB_SRP_FAIL, +				(unsigned long)&fsm->b_srp_done); +	if (!b_srp_fail_tmr) +		return -ENOMEM; + +	a_wait_enum_tmr = otg_timer_initializer(&a_wait_enum, 10, +				(unsigned long)&fsm); +	if (!a_wait_enum_tmr) +		return -ENOMEM; + +	/* device driver used timers */ +	b_srp_wait_tmr = otg_timer_initializer(&b_srp_end, TB_SRP_WAIT, 0); +	if (!b_srp_wait_tmr) +		return -ENOMEM; + +	b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end, +				TB_DATA_PLS, 0); +	if (!b_data_pulse_tmr) +		return -ENOMEM; + +	b_vbus_pulse_tmr = otg_timer_initializer(&b_vbus_pulse_end, +				TB_VBUS_PLS, 0); +	if (!b_vbus_pulse_tmr) +		return -ENOMEM; + +	return 0; +} + +/* Uninitialize timers */ +void fsl_otg_uninit_timers(void) +{ +	/* FSM used timers */ +	kfree(a_wait_vrise_tmr); +	kfree(a_wait_bcon_tmr); +	kfree(a_aidl_bdis_tmr); +	kfree(b_ase0_brst_tmr); +	kfree(b_se0_srp_tmr); +	kfree(b_srp_fail_tmr); +	kfree(a_wait_enum_tmr); + +	/* device driver used timers */ +	kfree(b_srp_wait_tmr); +	kfree(b_data_pulse_tmr); +	kfree(b_vbus_pulse_tmr); +} + +/* Add timer to timer list */ +void fsl_otg_add_timer(void *gtimer) +{ +	struct fsl_otg_timer *timer = gtimer; +	struct fsl_otg_timer *tmp_timer; + +	/* +	 * 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); +} + +/* Remove timer from the timer list; clear timeout status */ +void fsl_otg_del_timer(void *gtimer) +{ +	struct fsl_otg_timer *timer = gtimer; +	struct fsl_otg_timer *tmp_timer, *del_tmp; + +	list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) +		if (tmp_timer == timer) +			list_del(&timer->list); +} + +/* + * Reduce timer count by 1, and find timeout conditions. + * Called by fsl_otg 1ms timer interrupt + */ +int fsl_otg_tick_timer(void) +{ +	struct fsl_otg_timer *tmp_timer, *del_tmp; +	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(tmp_timer->data); +			expired = 1; +		} +	} + +	return expired; +} + +/* Reset controller, not reset the bus */ +void otg_reset_controller(void) +{ +	u32 command; + +	command = fsl_readl(&usb_dr_regs->usbcmd); +	command |= (1 << 1); +	fsl_writel(command, &usb_dr_regs->usbcmd); +	while (fsl_readl(&usb_dr_regs->usbcmd) & (1 << 1)) +		; +} + +/* Call suspend/resume routines in host driver */ +int fsl_otg_start_host(struct otg_fsm *fsm, int on) +{ +	struct usb_otg *otg = fsm->otg; +	struct device *dev; +	struct fsl_otg *otg_dev = container_of(otg->phy, struct fsl_otg, phy); +	u32 retval = 0; + +	if (!otg->host) +		return -ENODEV; +	dev = otg->host->controller; + +	/* +	 * Update a_vbus_vld state as a_vbus_vld int is disabled +	 * in device mode +	 */ +	fsm->a_vbus_vld = +		!!(fsl_readl(&usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID); +	if (on) { +		/* start fsl usb host controller */ +		if (otg_dev->host_working) +			goto end; +		else { +			otg_reset_controller(); +			VDBG("host on......\n"); +			if (dev->driver->pm && dev->driver->pm->resume) { +				retval = dev->driver->pm->resume(dev); +				if (fsm->id) { +					/* default-b */ +					fsl_otg_drv_vbus(1); +					/* +					 * Workaround: b_host can't driver +					 * vbus, but PP in PORTSC needs to +					 * be 1 for host to work. +					 * So we set drv_vbus bit in +					 * transceiver to 0 thru ULPI. +					 */ +					write_ulpi(0x0c, 0x20); +				} +			} + +			otg_dev->host_working = 1; +		} +	} else { +		/* stop fsl usb host controller */ +		if (!otg_dev->host_working) +			goto end; +		else { +			VDBG("host off......\n"); +			if (dev && dev->driver) { +				if (dev->driver->pm && dev->driver->pm->suspend) +					retval = dev->driver->pm->suspend(dev); +				if (fsm->id) +					/* default-b */ +					fsl_otg_drv_vbus(0); +			} +			otg_dev->host_working = 0; +		} +	} +end: +	return retval; +} + +/* + * Call suspend and resume function in udc driver + * to stop and start udc driver. + */ +int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +{ +	struct usb_otg *otg = fsm->otg; +	struct device *dev; + +	if (!otg->gadget || !otg->gadget->dev.parent) +		return -ENODEV; + +	VDBG("gadget %s\n", on ? "on" : "off"); +	dev = otg->gadget->dev.parent; + +	if (on) { +		if (dev->driver->resume) +			dev->driver->resume(dev); +	} else { +		if (dev->driver->suspend) +			dev->driver->suspend(dev, otg_suspend_state); +	} + +	return 0; +} + +/* + * Called by initialization code of host driver.  Register host controller + * to the OTG.  Suspend host for OTG role detection. + */ +static int fsl_otg_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	struct fsl_otg *otg_dev; + +	if (!otg) +		return -ENODEV; + +	otg_dev = container_of(otg->phy, struct fsl_otg, phy); +	if (otg_dev != fsl_otg_dev) +		return -ENODEV; + +	otg->host = host; + +	otg_dev->fsm.a_bus_drop = 0; +	otg_dev->fsm.a_bus_req = 1; + +	if (host) { +		VDBG("host off......\n"); + +		otg->host->otg_port = fsl_otg_initdata.otg_port; +		otg->host->is_b_host = otg_dev->fsm.id; +		/* +		 * must leave time for khubd to finish its thing +		 * before yanking the host driver out from under it, +		 * so suspend the host after a short delay. +		 */ +		otg_dev->host_working = 1; +		schedule_delayed_work(&otg_dev->otg_event, 100); +		return 0; +	} else { +		/* host driver going away */ +		if (!(fsl_readl(&otg_dev->dr_mem_map->otgsc) & +		      OTGSC_STS_USB_ID)) { +			/* Mini-A cable connected */ +			struct otg_fsm *fsm = &otg_dev->fsm; + +			otg->phy->state = OTG_STATE_UNDEFINED; +			fsm->protocol = PROTO_UNDEF; +		} +	} + +	otg_dev->host_working = 0; + +	otg_statemachine(&otg_dev->fsm); + +	return 0; +} + +/* Called by initialization code of udc.  Register udc to OTG. */ +static int fsl_otg_set_peripheral(struct usb_otg *otg, +					struct usb_gadget *gadget) +{ +	struct fsl_otg *otg_dev; + +	if (!otg) +		return -ENODEV; + +	otg_dev = container_of(otg->phy, struct fsl_otg, phy); +	VDBG("otg_dev 0x%x\n", (int)otg_dev); +	VDBG("fsl_otg_dev 0x%x\n", (int)fsl_otg_dev); +	if (otg_dev != fsl_otg_dev) +		return -ENODEV; + +	if (!gadget) { +		if (!otg->default_a) +			otg->gadget->ops->vbus_draw(otg->gadget, 0); +		usb_gadget_vbus_disconnect(otg->gadget); +		otg->gadget = 0; +		otg_dev->fsm.b_bus_req = 0; +		otg_statemachine(&otg_dev->fsm); +		return 0; +	} + +	otg->gadget = gadget; +	otg->gadget->is_a_peripheral = !otg_dev->fsm.id; + +	otg_dev->fsm.b_bus_req = 1; + +	/* start the gadget right away if the ID pin says Mini-B */ +	DBG("ID pin=%d\n", otg_dev->fsm.id); +	if (otg_dev->fsm.id == 1) { +		fsl_otg_start_host(&otg_dev->fsm, 0); +		otg_drv_vbus(&otg_dev->fsm, 0); +		fsl_otg_start_gadget(&otg_dev->fsm, 1); +	} + +	return 0; +} + +/* Set OTG port power, only for B-device */ +static int fsl_otg_set_power(struct usb_phy *phy, unsigned mA) +{ +	if (!fsl_otg_dev) +		return -ENODEV; +	if (phy->state == OTG_STATE_B_PERIPHERAL) +		pr_info("FSL OTG: Draw %d mA\n", mA); + +	return 0; +} + +/* + * Delayed pin detect interrupt processing. + * + * When the Mini-A cable is disconnected from the board, + * the pin-detect interrupt happens before the disconnect + * interrupts for the connected device(s).  In order to + * process the disconnect interrupt(s) prior to switching + * roles, the pin-detect interrupts are delayed, and handled + * by this routine. + */ +static void fsl_otg_event(struct work_struct *work) +{ +	struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work); +	struct otg_fsm *fsm = &og->fsm; + +	if (fsm->id) {		/* switch to gadget */ +		fsl_otg_start_host(fsm, 0); +		otg_drv_vbus(fsm, 0); +		fsl_otg_start_gadget(fsm, 1); +	} +} + +/* B-device start SRP */ +static int fsl_otg_start_srp(struct usb_otg *otg) +{ +	struct fsl_otg *otg_dev; + +	if (!otg || otg->phy->state != OTG_STATE_B_IDLE) +		return -ENODEV; + +	otg_dev = container_of(otg->phy, struct fsl_otg, phy); +	if (otg_dev != fsl_otg_dev) +		return -ENODEV; + +	otg_dev->fsm.b_bus_req = 1; +	otg_statemachine(&otg_dev->fsm); + +	return 0; +} + +/* A_host suspend will call this function to start hnp */ +static int fsl_otg_start_hnp(struct usb_otg *otg) +{ +	struct fsl_otg *otg_dev; + +	if (!otg) +		return -ENODEV; + +	otg_dev = container_of(otg->phy, struct fsl_otg, phy); +	if (otg_dev != fsl_otg_dev) +		return -ENODEV; + +	DBG("start_hnp...n"); + +	/* clear a_bus_req to enter a_suspend state */ +	otg_dev->fsm.a_bus_req = 0; +	otg_statemachine(&otg_dev->fsm); + +	return 0; +} + +/* + * Interrupt handler.  OTG/host/peripheral share the same int line. + * OTG driver clears OTGSC interrupts and leaves USB interrupts + * intact.  It needs to have knowledge of some USB interrupts + * such as port change. + */ +irqreturn_t fsl_otg_isr(int irq, void *dev_id) +{ +	struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; +	struct usb_otg *otg = ((struct fsl_otg *)dev_id)->phy.otg; +	u32 otg_int_src, otg_sc; + +	otg_sc = fsl_readl(&usb_dr_regs->otgsc); +	otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8); + +	/* Only clear otg interrupts */ +	fsl_writel(otg_sc, &usb_dr_regs->otgsc); + +	/*FIXME: ID change not generate when init to 0 */ +	fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; +	otg->default_a = (fsm->id == 0); + +	/* process OTG interrupts */ +	if (otg_int_src) { +		if (otg_int_src & OTGSC_INTSTS_USB_ID) { +			fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; +			otg->default_a = (fsm->id == 0); +			/* clear conn information */ +			if (fsm->id) +				fsm->b_conn = 0; +			else +				fsm->a_conn = 0; + +			if (otg->host) +				otg->host->is_b_host = fsm->id; +			if (otg->gadget) +				otg->gadget->is_a_peripheral = !fsm->id; +			VDBG("ID int (ID is %d)\n", fsm->id); + +			if (fsm->id) {	/* switch to gadget */ +				schedule_delayed_work( +					&((struct fsl_otg *)dev_id)->otg_event, +					100); +			} else {	/* switch to host */ +				cancel_delayed_work(& +						    ((struct fsl_otg *)dev_id)-> +						    otg_event); +				fsl_otg_start_gadget(fsm, 0); +				otg_drv_vbus(fsm, 1); +				fsl_otg_start_host(fsm, 1); +			} +			return IRQ_HANDLED; +		} +	} +	return IRQ_NONE; +} + +static struct otg_fsm_ops fsl_otg_ops = { +	.chrg_vbus = fsl_otg_chrg_vbus, +	.drv_vbus = fsl_otg_drv_vbus, +	.loc_conn = fsl_otg_loc_conn, +	.loc_sof = fsl_otg_loc_sof, +	.start_pulse = fsl_otg_start_pulse, + +	.add_timer = fsl_otg_add_timer, +	.del_timer = fsl_otg_del_timer, + +	.start_host = fsl_otg_start_host, +	.start_gadget = fsl_otg_start_gadget, +}; + +/* Initialize the global variable fsl_otg_dev and request IRQ for OTG */ +static int fsl_otg_conf(struct platform_device *pdev) +{ +	struct fsl_otg *fsl_otg_tc; +	int status; + +	if (fsl_otg_dev) +		return 0; + +	/* allocate space to fsl otg device */ +	fsl_otg_tc = kzalloc(sizeof(struct fsl_otg), GFP_KERNEL); +	if (!fsl_otg_tc) +		return -ENOMEM; + +	fsl_otg_tc->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); +	if (!fsl_otg_tc->phy.otg) { +		kfree(fsl_otg_tc); +		return -ENOMEM; +	} + +	INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event); + +	INIT_LIST_HEAD(&active_timers); +	status = fsl_otg_init_timers(&fsl_otg_tc->fsm); +	if (status) { +		pr_info("Couldn't init OTG timers\n"); +		goto err; +	} +	spin_lock_init(&fsl_otg_tc->fsm.lock); + +	/* Set OTG state machine operations */ +	fsl_otg_tc->fsm.ops = &fsl_otg_ops; + +	/* initialize the otg structure */ +	fsl_otg_tc->phy.label = DRIVER_DESC; +	fsl_otg_tc->phy.set_power = fsl_otg_set_power; + +	fsl_otg_tc->phy.otg->phy = &fsl_otg_tc->phy; +	fsl_otg_tc->phy.otg->set_host = fsl_otg_set_host; +	fsl_otg_tc->phy.otg->set_peripheral = fsl_otg_set_peripheral; +	fsl_otg_tc->phy.otg->start_hnp = fsl_otg_start_hnp; +	fsl_otg_tc->phy.otg->start_srp = fsl_otg_start_srp; + +	fsl_otg_dev = fsl_otg_tc; + +	/* Store the otg transceiver */ +	status = usb_add_phy(&fsl_otg_tc->phy, USB_PHY_TYPE_USB2); +	if (status) { +		pr_warn(FSL_OTG_NAME ": unable to register OTG transceiver.\n"); +		goto err; +	} + +	return 0; +err: +	fsl_otg_uninit_timers(); +	kfree(fsl_otg_tc->phy.otg); +	kfree(fsl_otg_tc); +	return status; +} + +/* OTG Initialization */ +int usb_otg_start(struct platform_device *pdev) +{ +	struct fsl_otg *p_otg; +	struct usb_phy *otg_trans = usb_get_phy(USB_PHY_TYPE_USB2); +	struct otg_fsm *fsm; +	int status; +	struct resource *res; +	u32 temp; +	struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + +	p_otg = container_of(otg_trans, struct fsl_otg, phy); +	fsm = &p_otg->fsm; + +	/* Initialize the state machine structure with default values */ +	SET_OTG_STATE(otg_trans, OTG_STATE_UNDEFINED); +	fsm->otg = p_otg->phy.otg; + +	/* We don't require predefined MEM/IRQ resource index */ +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) +		return -ENXIO; + +	/* We don't request_mem_region here to enable resource sharing +	 * with host/device */ + +	usb_dr_regs = ioremap(res->start, sizeof(struct usb_dr_mmap)); +	p_otg->dr_mem_map = (struct usb_dr_mmap *)usb_dr_regs; +	pdata->regs = (void *)usb_dr_regs; + +	if (pdata->init && pdata->init(pdev) != 0) +		return -EINVAL; + +	if (pdata->big_endian_mmio) { +		_fsl_readl = _fsl_readl_be; +		_fsl_writel = _fsl_writel_be; +	} else { +		_fsl_readl = _fsl_readl_le; +		_fsl_writel = _fsl_writel_le; +	} + +	/* request irq */ +	p_otg->irq = platform_get_irq(pdev, 0); +	status = request_irq(p_otg->irq, fsl_otg_isr, +				IRQF_SHARED, driver_name, p_otg); +	if (status) { +		dev_dbg(p_otg->phy.dev, "can't get IRQ %d, error %d\n", +			p_otg->irq, status); +		iounmap(p_otg->dr_mem_map); +		kfree(p_otg->phy.otg); +		kfree(p_otg); +		return status; +	} + +	/* stop the controller */ +	temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); +	temp &= ~USB_CMD_RUN_STOP; +	fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + +	/* reset the controller */ +	temp = fsl_readl(&p_otg->dr_mem_map->usbcmd); +	temp |= USB_CMD_CTRL_RESET; +	fsl_writel(temp, &p_otg->dr_mem_map->usbcmd); + +	/* wait reset completed */ +	while (fsl_readl(&p_otg->dr_mem_map->usbcmd) & USB_CMD_CTRL_RESET) +		; + +	/* configure the VBUSHS as IDLE(both host and device) */ +	temp = USB_MODE_STREAM_DISABLE | (pdata->es ? USB_MODE_ES : 0); +	fsl_writel(temp, &p_otg->dr_mem_map->usbmode); + +	/* configure PHY interface */ +	temp = fsl_readl(&p_otg->dr_mem_map->portsc); +	temp &= ~(PORTSC_PHY_TYPE_SEL | PORTSC_PTW); +	switch (pdata->phy_mode) { +	case FSL_USB2_PHY_ULPI: +		temp |= PORTSC_PTS_ULPI; +		break; +	case FSL_USB2_PHY_UTMI_WIDE: +		temp |= PORTSC_PTW_16BIT; +		/* fall through */ +	case FSL_USB2_PHY_UTMI: +		temp |= PORTSC_PTS_UTMI; +		/* fall through */ +	default: +		break; +	} +	fsl_writel(temp, &p_otg->dr_mem_map->portsc); + +	if (pdata->have_sysif_regs) { +		/* configure control enable IO output, big endian register */ +		temp = __raw_readl(&p_otg->dr_mem_map->control); +		temp |= USB_CTRL_IOENB; +		__raw_writel(temp, &p_otg->dr_mem_map->control); +	} + +	/* disable all interrupt and clear all OTGSC status */ +	temp = fsl_readl(&p_otg->dr_mem_map->otgsc); +	temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; +	temp |= OTGSC_INTERRUPT_STATUS_BITS_MASK | OTGSC_CTRL_VBUS_DISCHARGE; +	fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + +	/* +	 * The identification (id) input is FALSE when a Mini-A plug is inserted +	 * in the devices Mini-AB receptacle. Otherwise, this input is TRUE. +	 * Also: record initial state of ID pin +	 */ +	if (fsl_readl(&p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) { +		p_otg->phy.state = OTG_STATE_UNDEFINED; +		p_otg->fsm.id = 1; +	} else { +		p_otg->phy.state = OTG_STATE_A_IDLE; +		p_otg->fsm.id = 0; +	} + +	DBG("initial ID pin=%d\n", p_otg->fsm.id); + +	/* enable OTG ID pin interrupt */ +	temp = fsl_readl(&p_otg->dr_mem_map->otgsc); +	temp |= OTGSC_INTR_USB_ID_EN; +	temp &= ~(OTGSC_CTRL_VBUS_DISCHARGE | OTGSC_INTR_1MS_TIMER_EN); +	fsl_writel(temp, &p_otg->dr_mem_map->otgsc); + +	return 0; +} + +/* + * state file in sysfs + */ +static int show_fsl_usb2_otg_state(struct device *dev, +				   struct device_attribute *attr, char *buf) +{ +	struct otg_fsm *fsm = &fsl_otg_dev->fsm; +	char *next = buf; +	unsigned size = PAGE_SIZE; +	unsigned long flags; +	int t; + +	spin_lock_irqsave(&fsm->lock, flags); + +	/* basic driver infomation */ +	t = scnprintf(next, size, +			DRIVER_DESC "\n" "fsl_usb2_otg version: %s\n\n", +			DRIVER_VERSION); +	size -= t; +	next += t; + +	/* Registers */ +	t = scnprintf(next, size, +			"OTGSC:   0x%08x\n" +			"PORTSC:  0x%08x\n" +			"USBMODE: 0x%08x\n" +			"USBCMD:  0x%08x\n" +			"USBSTS:  0x%08x\n" +			"USBINTR: 0x%08x\n", +			fsl_readl(&usb_dr_regs->otgsc), +			fsl_readl(&usb_dr_regs->portsc), +			fsl_readl(&usb_dr_regs->usbmode), +			fsl_readl(&usb_dr_regs->usbcmd), +			fsl_readl(&usb_dr_regs->usbsts), +			fsl_readl(&usb_dr_regs->usbintr)); +	size -= t; +	next += t; + +	/* State */ +	t = scnprintf(next, size, +		      "OTG state: %s\n\n", +		      usb_otg_state_string(fsl_otg_dev->phy.state)); +	size -= t; +	next += t; + +	/* State Machine Variables */ +	t = scnprintf(next, size, +			"a_bus_req: %d\n" +			"b_bus_req: %d\n" +			"a_bus_resume: %d\n" +			"a_bus_suspend: %d\n" +			"a_conn: %d\n" +			"a_sess_vld: %d\n" +			"a_srp_det: %d\n" +			"a_vbus_vld: %d\n" +			"b_bus_resume: %d\n" +			"b_bus_suspend: %d\n" +			"b_conn: %d\n" +			"b_se0_srp: %d\n" +			"b_sess_end: %d\n" +			"b_sess_vld: %d\n" +			"id: %d\n", +			fsm->a_bus_req, +			fsm->b_bus_req, +			fsm->a_bus_resume, +			fsm->a_bus_suspend, +			fsm->a_conn, +			fsm->a_sess_vld, +			fsm->a_srp_det, +			fsm->a_vbus_vld, +			fsm->b_bus_resume, +			fsm->b_bus_suspend, +			fsm->b_conn, +			fsm->b_se0_srp, +			fsm->b_sess_end, +			fsm->b_sess_vld, +			fsm->id); +	size -= t; +	next += t; + +	spin_unlock_irqrestore(&fsm->lock, flags); + +	return PAGE_SIZE - size; +} + +static DEVICE_ATTR(fsl_usb2_otg_state, S_IRUGO, show_fsl_usb2_otg_state, NULL); + + +/* Char driver interface to control some OTG input */ + +/* + * Handle some ioctl command, such as get otg + * status and set host suspend + */ +static long fsl_otg_ioctl(struct file *file, unsigned int cmd, +			  unsigned long arg) +{ +	u32 retval = 0; + +	switch (cmd) { +	case GET_OTG_STATUS: +		retval = fsl_otg_dev->host_working; +		break; + +	case SET_A_SUSPEND_REQ: +		fsl_otg_dev->fsm.a_suspend_req = arg; +		break; + +	case SET_A_BUS_DROP: +		fsl_otg_dev->fsm.a_bus_drop = arg; +		break; + +	case SET_A_BUS_REQ: +		fsl_otg_dev->fsm.a_bus_req = arg; +		break; + +	case SET_B_BUS_REQ: +		fsl_otg_dev->fsm.b_bus_req = arg; +		break; + +	default: +		break; +	} + +	otg_statemachine(&fsl_otg_dev->fsm); + +	return retval; +} + +static int fsl_otg_open(struct inode *inode, struct file *file) +{ +	return 0; +} + +static int fsl_otg_release(struct inode *inode, struct file *file) +{ +	return 0; +} + +static const struct file_operations otg_fops = { +	.owner = THIS_MODULE, +	.llseek = NULL, +	.read = NULL, +	.write = NULL, +	.unlocked_ioctl = fsl_otg_ioctl, +	.open = fsl_otg_open, +	.release = fsl_otg_release, +}; + +static int fsl_otg_probe(struct platform_device *pdev) +{ +	int ret; + +	if (!pdev->dev.platform_data) +		return -ENODEV; + +	/* configure the OTG */ +	ret = fsl_otg_conf(pdev); +	if (ret) { +		dev_err(&pdev->dev, "Couldn't configure OTG module\n"); +		return ret; +	} + +	/* start OTG */ +	ret = usb_otg_start(pdev); +	if (ret) { +		dev_err(&pdev->dev, "Can't init FSL OTG device\n"); +		return ret; +	} + +	ret = register_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME, &otg_fops); +	if (ret) { +		dev_err(&pdev->dev, "unable to register FSL OTG device\n"); +		return ret; +	} + +	ret = device_create_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); +	if (ret) +		dev_warn(&pdev->dev, "Can't register sysfs attribute\n"); + +	return ret; +} + +static int fsl_otg_remove(struct platform_device *pdev) +{ +	struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + +	usb_remove_phy(&fsl_otg_dev->phy); +	free_irq(fsl_otg_dev->irq, fsl_otg_dev); + +	iounmap((void *)usb_dr_regs); + +	fsl_otg_uninit_timers(); +	kfree(fsl_otg_dev->phy.otg); +	kfree(fsl_otg_dev); + +	device_remove_file(&pdev->dev, &dev_attr_fsl_usb2_otg_state); + +	unregister_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME); + +	if (pdata->exit) +		pdata->exit(pdev); + +	return 0; +} + +struct platform_driver fsl_otg_driver = { +	.probe = fsl_otg_probe, +	.remove = fsl_otg_remove, +	.driver = { +		.name = driver_name, +		.owner = THIS_MODULE, +	}, +}; + +module_platform_driver(fsl_otg_driver); + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-fsl-usb.h b/drivers/usb/phy/phy-fsl-usb.h new file mode 100644 index 00000000000..ca266280895 --- /dev/null +++ b/drivers/usb/phy/phy-fsl-usb.h @@ -0,0 +1,406 @@ +/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of  the GNU General  Public License as published by the + * Free Software Foundation;  either version 2 of the  License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the  GNU General Public License along + * with this program; if not, write  to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "otg_fsm.h" +#include <linux/usb/otg.h> +#include <linux/ioctl.h> + +/* USB Command Register Bit Masks */ +#define USB_CMD_RUN_STOP		(0x1<<0) +#define USB_CMD_CTRL_RESET		(0x1<<1) +#define USB_CMD_PERIODIC_SCHEDULE_EN	(0x1<<4) +#define USB_CMD_ASYNC_SCHEDULE_EN	(0x1<<5) +#define USB_CMD_INT_AA_DOORBELL		(0x1<<6) +#define USB_CMD_ASP			(0x3<<8) +#define USB_CMD_ASYNC_SCH_PARK_EN	(0x1<<11) +#define USB_CMD_SUTW			(0x1<<13) +#define USB_CMD_ATDTW			(0x1<<14) +#define USB_CMD_ITC			(0xFF<<16) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024		(0x0<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_512		(0x0<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_256		(0x0<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_128		(0x0<<15 | 0x3<<2) +#define USB_CMD_FRAME_SIZE_64		(0x1<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_32		(0x1<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_16		(0x1<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_8		(0x1<<15 | 0x3<<2) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00			(0x0<<8) +#define USB_CMD_ASP_01			(0x1<<8) +#define USB_CMD_ASP_10			(0x2<<8) +#define USB_CMD_ASP_11			(0x3<<8) +#define USB_CMD_ASP_BIT_POS		(8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD	(0x00<<16) +#define USB_CMD_ITC_1_MICRO_FRM		(0x01<<16) +#define USB_CMD_ITC_2_MICRO_FRM		(0x02<<16) +#define USB_CMD_ITC_4_MICRO_FRM		(0x04<<16) +#define USB_CMD_ITC_8_MICRO_FRM		(0x08<<16) +#define USB_CMD_ITC_16_MICRO_FRM	(0x10<<16) +#define USB_CMD_ITC_32_MICRO_FRM	(0x20<<16) +#define USB_CMD_ITC_64_MICRO_FRM	(0x40<<16) +#define USB_CMD_ITC_BIT_POS		(16) + +/* USB Status Register Bit Masks */ +#define USB_STS_INT			(0x1<<0) +#define USB_STS_ERR			(0x1<<1) +#define USB_STS_PORT_CHANGE		(0x1<<2) +#define USB_STS_FRM_LST_ROLL		(0x1<<3) +#define USB_STS_SYS_ERR			(0x1<<4) +#define USB_STS_IAA			(0x1<<5) +#define USB_STS_RESET_RECEIVED		(0x1<<6) +#define USB_STS_SOF			(0x1<<7) +#define USB_STS_DCSUSPEND		(0x1<<8) +#define USB_STS_HC_HALTED		(0x1<<12) +#define USB_STS_RCL			(0x1<<13) +#define USB_STS_PERIODIC_SCHEDULE	(0x1<<14) +#define USB_STS_ASYNC_SCHEDULE		(0x1<<15) + +/* USB Interrupt Enable Register Bit Masks */ +#define USB_INTR_INT_EN			(0x1<<0) +#define USB_INTR_ERR_INT_EN		(0x1<<1) +#define USB_INTR_PC_DETECT_EN		(0x1<<2) +#define USB_INTR_FRM_LST_ROLL_EN	(0x1<<3) +#define USB_INTR_SYS_ERR_EN		(0x1<<4) +#define USB_INTR_ASYN_ADV_EN		(0x1<<5) +#define USB_INTR_RESET_EN		(0x1<<6) +#define USB_INTR_SOF_EN			(0x1<<7) +#define USB_INTR_DEVICE_SUSPEND		(0x1<<8) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK		(0x7F<<25) +#define USB_DEVICE_ADDRESS_BIT_POS	(25) +/* PORTSC  Register Bit Masks,Only one PORT in OTG mode*/ +#define PORTSC_CURRENT_CONNECT_STATUS	(0x1<<0) +#define PORTSC_CONNECT_STATUS_CHANGE	(0x1<<1) +#define PORTSC_PORT_ENABLE		(0x1<<2) +#define PORTSC_PORT_EN_DIS_CHANGE	(0x1<<3) +#define PORTSC_OVER_CURRENT_ACT		(0x1<<4) +#define PORTSC_OVER_CUURENT_CHG		(0x1<<5) +#define PORTSC_PORT_FORCE_RESUME	(0x1<<6) +#define PORTSC_PORT_SUSPEND		(0x1<<7) +#define PORTSC_PORT_RESET		(0x1<<8) +#define PORTSC_LINE_STATUS_BITS		(0x3<<10) +#define PORTSC_PORT_POWER		(0x1<<12) +#define PORTSC_PORT_INDICTOR_CTRL	(0x3<<14) +#define PORTSC_PORT_TEST_CTRL		(0xF<<16) +#define PORTSC_WAKE_ON_CONNECT_EN	(0x1<<20) +#define PORTSC_WAKE_ON_CONNECT_DIS	(0x1<<21) +#define PORTSC_WAKE_ON_OVER_CURRENT	(0x1<<22) +#define PORTSC_PHY_LOW_POWER_SPD	(0x1<<23) +#define PORTSC_PORT_FORCE_FULL_SPEED	(0x1<<24) +#define PORTSC_PORT_SPEED_MASK		(0x3<<26) +#define PORTSC_TRANSCEIVER_WIDTH	(0x1<<28) +#define PORTSC_PHY_TYPE_SEL		(0x3<<30) +/* bit 11-10 are line status */ +#define PORTSC_LINE_STATUS_SE0		(0x0<<10) +#define PORTSC_LINE_STATUS_JSTATE	(0x1<<10) +#define PORTSC_LINE_STATUS_KSTATE	(0x2<<10) +#define PORTSC_LINE_STATUS_UNDEF	(0x3<<10) +#define PORTSC_LINE_STATUS_BIT_POS	(10) + +/* bit 15-14 are port indicator control */ +#define PORTSC_PIC_OFF			(0x0<<14) +#define PORTSC_PIC_AMBER		(0x1<<14) +#define PORTSC_PIC_GREEN		(0x2<<14) +#define PORTSC_PIC_UNDEF		(0x3<<14) +#define PORTSC_PIC_BIT_POS		(14) + +/* bit 19-16 are port test control */ +#define PORTSC_PTC_DISABLE		(0x0<<16) +#define PORTSC_PTC_JSTATE		(0x1<<16) +#define PORTSC_PTC_KSTATE		(0x2<<16) +#define PORTSC_PTC_SEQNAK		(0x3<<16) +#define PORTSC_PTC_PACKET		(0x4<<16) +#define PORTSC_PTC_FORCE_EN		(0x5<<16) +#define PORTSC_PTC_BIT_POS		(16) + +/* bit 27-26 are port speed */ +#define PORTSC_PORT_SPEED_FULL		(0x0<<26) +#define PORTSC_PORT_SPEED_LOW		(0x1<<26) +#define PORTSC_PORT_SPEED_HIGH		(0x2<<26) +#define PORTSC_PORT_SPEED_UNDEF		(0x3<<26) +#define PORTSC_SPEED_BIT_POS		(26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSC_PTW			(0x1<<28) +#define PORTSC_PTW_8BIT			(0x0<<28) +#define PORTSC_PTW_16BIT		(0x1<<28) + +/* bit 31-30 are port transceiver select */ +#define PORTSC_PTS_UTMI			(0x0<<30) +#define PORTSC_PTS_ULPI			(0x2<<30) +#define PORTSC_PTS_FSLS_SERIAL		(0x3<<30) +#define PORTSC_PTS_BIT_POS		(30) + +#define PORTSC_W1C_BITS			\ +	(PORTSC_CONNECT_STATUS_CHANGE |	\ +	 PORTSC_PORT_EN_DIS_CHANGE    |	\ +	 PORTSC_OVER_CUURENT_CHG) + +/* OTG Status Control Register Bit Masks */ +#define OTGSC_CTRL_VBUS_DISCHARGE	(0x1<<0) +#define OTGSC_CTRL_VBUS_CHARGE		(0x1<<1) +#define OTGSC_CTRL_OTG_TERMINATION	(0x1<<3) +#define OTGSC_CTRL_DATA_PULSING		(0x1<<4) +#define OTGSC_CTRL_ID_PULL_EN		(0x1<<5) +#define OTGSC_HA_DATA_PULSE		(0x1<<6) +#define OTGSC_HA_BA			(0x1<<7) +#define OTGSC_STS_USB_ID		(0x1<<8) +#define OTGSC_STS_A_VBUS_VALID		(0x1<<9) +#define OTGSC_STS_A_SESSION_VALID	(0x1<<10) +#define OTGSC_STS_B_SESSION_VALID	(0x1<<11) +#define OTGSC_STS_B_SESSION_END		(0x1<<12) +#define OTGSC_STS_1MS_TOGGLE		(0x1<<13) +#define OTGSC_STS_DATA_PULSING		(0x1<<14) +#define OTGSC_INTSTS_USB_ID		(0x1<<16) +#define OTGSC_INTSTS_A_VBUS_VALID	(0x1<<17) +#define OTGSC_INTSTS_A_SESSION_VALID	(0x1<<18) +#define OTGSC_INTSTS_B_SESSION_VALID	(0x1<<19) +#define OTGSC_INTSTS_B_SESSION_END	(0x1<<20) +#define OTGSC_INTSTS_1MS		(0x1<<21) +#define OTGSC_INTSTS_DATA_PULSING	(0x1<<22) +#define OTGSC_INTR_USB_ID_EN		(0x1<<24) +#define OTGSC_INTR_A_VBUS_VALID_EN	(0x1<<25) +#define OTGSC_INTR_A_SESSION_VALID_EN	(0x1<<26) +#define OTGSC_INTR_B_SESSION_VALID_EN	(0x1<<27) +#define OTGSC_INTR_B_SESSION_END_EN	(0x1<<28) +#define OTGSC_INTR_1MS_TIMER_EN		(0x1<<29) +#define OTGSC_INTR_DATA_PULSING_EN	(0x1<<30) +#define OTGSC_INTSTS_MASK		(0x00ff0000) + +/* USB MODE Register Bit Masks */ +#define  USB_MODE_CTRL_MODE_IDLE	(0x0<<0) +#define  USB_MODE_CTRL_MODE_DEVICE	(0x2<<0) +#define  USB_MODE_CTRL_MODE_HOST	(0x3<<0) +#define  USB_MODE_CTRL_MODE_RSV		(0x1<<0) +#define  USB_MODE_SETUP_LOCK_OFF	(0x1<<3) +#define  USB_MODE_STREAM_DISABLE	(0x1<<4) +#define  USB_MODE_ES			(0x1<<2) /* Endian Select */ + +/* control Register Bit Masks */ +#define  USB_CTRL_IOENB			(0x1<<2) +#define  USB_CTRL_ULPI_INT0EN		(0x1<<0) + +/* BCSR5 */ +#define BCSR5_INT_USB			(0x02) + +/* USB module clk cfg */ +#define SCCR_OFFS			(0xA08) +#define SCCR_USB_CLK_DISABLE		(0x00000000)	/* USB clk disable */ +#define SCCR_USB_MPHCM_11		(0x00c00000) +#define SCCR_USB_MPHCM_01		(0x00400000) +#define SCCR_USB_MPHCM_10		(0x00800000) +#define SCCR_USB_DRCM_11		(0x00300000) +#define SCCR_USB_DRCM_01		(0x00100000) +#define SCCR_USB_DRCM_10		(0x00200000) + +#define SICRL_OFFS			(0x114) +#define SICRL_USB0			(0x40000000) +#define SICRL_USB1			(0x20000000) + +#define SICRH_OFFS			(0x118) +#define SICRH_USB_UTMI			(0x00020000) + +/* OTG interrupt enable bit masks */ +#define  OTGSC_INTERRUPT_ENABLE_BITS_MASK  \ +	(OTGSC_INTR_USB_ID_EN            | \ +	OTGSC_INTR_1MS_TIMER_EN		 | \ +	OTGSC_INTR_A_VBUS_VALID_EN       | \ +	OTGSC_INTR_A_SESSION_VALID_EN    | \ +	OTGSC_INTR_B_SESSION_VALID_EN    | \ +	OTGSC_INTR_B_SESSION_END_EN      | \ +	OTGSC_INTR_DATA_PULSING_EN) + +/* OTG interrupt status bit masks */ +#define  OTGSC_INTERRUPT_STATUS_BITS_MASK  \ +	(OTGSC_INTSTS_USB_ID          |    \ +	OTGSC_INTR_1MS_TIMER_EN       |    \ +	OTGSC_INTSTS_A_VBUS_VALID     |    \ +	OTGSC_INTSTS_A_SESSION_VALID  |    \ +	OTGSC_INTSTS_B_SESSION_VALID  |    \ +	OTGSC_INTSTS_B_SESSION_END    |    \ +	OTGSC_INTSTS_DATA_PULSING) + +/* + *  A-DEVICE timing  constants + */ + +/* Wait for VBUS Rise  */ +#define TA_WAIT_VRISE	(100)	/* a_wait_vrise 100 ms, section: 6.6.5.1 */ + +/* Wait for B-Connect */ +#define TA_WAIT_BCON	(10000)  /* a_wait_bcon > 1 sec, section: 6.6.5.2 +				  * This is only used to get out of +				  * OTG_STATE_A_WAIT_BCON state if there was +				  * no connection for these many milliseconds +				  */ + +/* A-Idle to B-Disconnect */ +/* It is necessary for this timer to be more than 750 ms because of a bug in OPT + * test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated + * in the test description + */ +#define TA_AIDL_BDIS	(5000)	/* a_suspend minimum 200 ms, section: 6.6.5.3 */ + +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS	(12)	/* 3 to 200 ms */ + +/* B-device timing constants */ + + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS	(10)	/* b_srp_init,continue 5~10ms, section:5.3.3 */ +#define TB_DATA_PLS_MIN	(5)	/* minimum 5 ms */ +#define TB_DATA_PLS_MAX	(10)	/* maximum 10 ms */ + +/* SRP Initiate Time  */ +#define TB_SRP_INIT	(100)	/* b_srp_init,maximum 100 ms, section:5.3.8 */ + +/* SRP Fail Time  */ +#define TB_SRP_FAIL	(7000)	/* b_srp_init,Fail time 5~30s, section:6.8.2.2*/ + +/* SRP result wait time */ +#define TB_SRP_WAIT	(60) + +/* VBus time */ +#define TB_VBUS_PLS	(30)	/* time to keep vbus pulsing asserted */ + +/* Discharge time */ +/* This time should be less than 10ms. It varies from system to system. */ +#define TB_VBUS_DSCHRG	(8) + +/* A-SE0 to B-Reset  */ +#define TB_ASE0_BRST	(20)	/* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */ + +/* A bus suspend timer before we can switch to b_wait_aconn */ +#define TB_A_SUSPEND	(7) +#define TB_BUS_RESUME	(12) + +/* SE0 Time Before SRP */ +#define TB_SE0_SRP	(2)	/* b_idle,minimum 2 ms, section:5.3.2 */ + +#define SET_OTG_STATE(otg_ptr, newstate)	((otg_ptr)->state = newstate) + +struct usb_dr_mmap { +	/* Capability register */ +	u8 res1[256]; +	u16 caplength;		/* Capability Register Length */ +	u16 hciversion;		/* Host Controller Interface Version */ +	u32 hcsparams;		/* Host Controller Structual Parameters */ +	u32 hccparams;		/* Host Controller Capability Parameters */ +	u8 res2[20]; +	u32 dciversion;		/* Device Controller Interface Version */ +	u32 dccparams;		/* Device Controller Capability Parameters */ +	u8 res3[24]; +	/* Operation register */ +	u32 usbcmd;		/* USB Command Register */ +	u32 usbsts;		/* USB Status Register */ +	u32 usbintr;		/* USB Interrupt Enable Register */ +	u32 frindex;		/* Frame Index Register */ +	u8 res4[4]; +	u32 deviceaddr;		/* Device Address */ +	u32 endpointlistaddr;	/* Endpoint List Address Register */ +	u8 res5[4]; +	u32 burstsize;		/* Master Interface Data Burst Size Register */ +	u32 txttfilltuning;	/* Transmit FIFO Tuning Controls Register */ +	u8 res6[8]; +	u32 ulpiview;		/* ULPI register access */ +	u8 res7[12]; +	u32 configflag;		/* Configure Flag Register */ +	u32 portsc;		/* Port 1 Status and Control Register */ +	u8 res8[28]; +	u32 otgsc;		/* On-The-Go Status and Control */ +	u32 usbmode;		/* USB Mode Register */ +	u32 endptsetupstat;	/* Endpoint Setup Status Register */ +	u32 endpointprime;	/* Endpoint Initialization Register */ +	u32 endptflush;		/* Endpoint Flush Register */ +	u32 endptstatus;	/* Endpoint Status Register */ +	u32 endptcomplete;	/* Endpoint Complete Register */ +	u32 endptctrl[6];	/* Endpoint Control Registers */ +	u8 res9[552]; +	u32 snoop1; +	u32 snoop2; +	u32 age_cnt_thresh;	/* Age Count Threshold Register */ +	u32 pri_ctrl;		/* Priority Control Register */ +	u32 si_ctrl;		/* System Interface Control Register */ +	u8 res10[236]; +	u32 control;		/* General Purpose Control Register */ +}; + +struct fsl_otg_timer { +	unsigned long expires;	/* Number of count increase to timeout */ +	unsigned long count;	/* Tick counter */ +	void (*function)(unsigned long);	/* Timeout function */ +	unsigned long data;	/* Data passed to function */ +	struct list_head list; +}; + +inline struct fsl_otg_timer *otg_timer_initializer +(void (*function)(unsigned long), unsigned long expires, unsigned long data) +{ +	struct fsl_otg_timer *timer; + +	timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL); +	if (!timer) +		return NULL; +	timer->function = function; +	timer->expires = expires; +	timer->data = data; +	return timer; +} + +struct fsl_otg { +	struct usb_phy phy; +	struct otg_fsm fsm; +	struct usb_dr_mmap *dr_mem_map; +	struct delayed_work otg_event; + +	/* used for usb host */ +	struct work_struct work_wq; +	u8	host_working; + +	int irq; +}; + +struct fsl_otg_config { +	u8 otg_port; +}; + +/* For SRP and HNP handle */ +#define FSL_OTG_MAJOR		240 +#define FSL_OTG_NAME		"fsl-usb2-otg" +/* Command to OTG driver ioctl */ +#define OTG_IOCTL_MAGIC		FSL_OTG_MAJOR +/* if otg work as host, it should return 1, otherwise return 0 */ +#define GET_OTG_STATUS		_IOR(OTG_IOCTL_MAGIC, 1, int) +#define SET_A_SUSPEND_REQ	_IOW(OTG_IOCTL_MAGIC, 2, int) +#define SET_A_BUS_DROP		_IOW(OTG_IOCTL_MAGIC, 3, int) +#define SET_A_BUS_REQ		_IOW(OTG_IOCTL_MAGIC, 4, int) +#define SET_B_BUS_REQ		_IOW(OTG_IOCTL_MAGIC, 5, int) +#define GET_A_SUSPEND_REQ	_IOR(OTG_IOCTL_MAGIC, 6, int) +#define GET_A_BUS_DROP		_IOR(OTG_IOCTL_MAGIC, 7, int) +#define GET_A_BUS_REQ		_IOR(OTG_IOCTL_MAGIC, 8, int) +#define GET_B_BUS_REQ		_IOR(OTG_IOCTL_MAGIC, 9, int) + +void fsl_otg_add_timer(void *timer); +void fsl_otg_del_timer(void *timer); +void fsl_otg_pulse_vbus(void); diff --git a/drivers/usb/phy/phy-fsm-usb.c b/drivers/usb/phy/phy-fsm-usb.c new file mode 100644 index 00000000000..c520b3548e7 --- /dev/null +++ b/drivers/usb/phy/phy-fsm-usb.c @@ -0,0 +1,348 @@ +/* + * OTG Finite State Machine from OTG spec + * + * Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * Author:	Li Yang <LeoLi@freescale.com> + *		Jerry Huang <Chang-Ming.Huang@freescale.com> + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of  the GNU General  Public License as published by the + * Free Software Foundation;  either version 2 of the  License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the  GNU General Public License along + * with this program; if not, write  to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/usb.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> + +#include "phy-otg-fsm.h" + +/* Change USB protocol when there is a protocol change */ +static int otg_set_protocol(struct otg_fsm *fsm, int protocol) +{ +	int ret = 0; + +	if (fsm->protocol != protocol) { +		VDBG("Changing role fsm->protocol= %d; new protocol= %d\n", +			fsm->protocol, protocol); +		/* stop old protocol */ +		if (fsm->protocol == PROTO_HOST) +			ret = fsm->ops->start_host(fsm, 0); +		else if (fsm->protocol == PROTO_GADGET) +			ret = fsm->ops->start_gadget(fsm, 0); +		if (ret) +			return ret; + +		/* start new protocol */ +		if (protocol == PROTO_HOST) +			ret = fsm->ops->start_host(fsm, 1); +		else if (protocol == PROTO_GADGET) +			ret = fsm->ops->start_gadget(fsm, 1); +		if (ret) +			return ret; + +		fsm->protocol = protocol; +		return 0; +	} + +	return 0; +} + +static int state_changed; + +/* Called when leaving a state.  Do state clean up jobs here */ +void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) +{ +	switch (old_state) { +	case OTG_STATE_B_IDLE: +		otg_del_timer(fsm, b_se0_srp_tmr); +		fsm->b_se0_srp = 0; +		break; +	case OTG_STATE_B_SRP_INIT: +		fsm->b_srp_done = 0; +		break; +	case OTG_STATE_B_PERIPHERAL: +		break; +	case OTG_STATE_B_WAIT_ACON: +		otg_del_timer(fsm, b_ase0_brst_tmr); +		fsm->b_ase0_brst_tmout = 0; +		break; +	case OTG_STATE_B_HOST: +		break; +	case OTG_STATE_A_IDLE: +		break; +	case OTG_STATE_A_WAIT_VRISE: +		otg_del_timer(fsm, a_wait_vrise_tmr); +		fsm->a_wait_vrise_tmout = 0; +		break; +	case OTG_STATE_A_WAIT_BCON: +		otg_del_timer(fsm, a_wait_bcon_tmr); +		fsm->a_wait_bcon_tmout = 0; +		break; +	case OTG_STATE_A_HOST: +		otg_del_timer(fsm, a_wait_enum_tmr); +		break; +	case OTG_STATE_A_SUSPEND: +		otg_del_timer(fsm, a_aidl_bdis_tmr); +		fsm->a_aidl_bdis_tmout = 0; +		fsm->a_suspend_req = 0; +		break; +	case OTG_STATE_A_PERIPHERAL: +		break; +	case OTG_STATE_A_WAIT_VFALL: +		otg_del_timer(fsm, a_wait_vrise_tmr); +		break; +	case OTG_STATE_A_VBUS_ERR: +		break; +	default: +		break; +	} +} + +/* Called when entering a state */ +int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ +	state_changed = 1; +	if (fsm->otg->phy->state == new_state) +		return 0; +	VDBG("Set state: %s\n", usb_otg_state_string(new_state)); +	otg_leave_state(fsm, fsm->otg->phy->state); +	switch (new_state) { +	case OTG_STATE_B_IDLE: +		otg_drv_vbus(fsm, 0); +		otg_chrg_vbus(fsm, 0); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_UNDEF); +		otg_add_timer(fsm, b_se0_srp_tmr); +		break; +	case OTG_STATE_B_SRP_INIT: +		otg_start_pulse(fsm); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_UNDEF); +		otg_add_timer(fsm, b_srp_fail_tmr); +		break; +	case OTG_STATE_B_PERIPHERAL: +		otg_chrg_vbus(fsm, 0); +		otg_loc_conn(fsm, 1); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_GADGET); +		break; +	case OTG_STATE_B_WAIT_ACON: +		otg_chrg_vbus(fsm, 0); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_HOST); +		otg_add_timer(fsm, b_ase0_brst_tmr); +		fsm->a_bus_suspend = 0; +		break; +	case OTG_STATE_B_HOST: +		otg_chrg_vbus(fsm, 0); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 1); +		otg_set_protocol(fsm, PROTO_HOST); +		usb_bus_start_enum(fsm->otg->host, +				fsm->otg->host->otg_port); +		break; +	case OTG_STATE_A_IDLE: +		otg_drv_vbus(fsm, 0); +		otg_chrg_vbus(fsm, 0); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_HOST); +		break; +	case OTG_STATE_A_WAIT_VRISE: +		otg_drv_vbus(fsm, 1); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_HOST); +		otg_add_timer(fsm, a_wait_vrise_tmr); +		break; +	case OTG_STATE_A_WAIT_BCON: +		otg_drv_vbus(fsm, 1); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_HOST); +		otg_add_timer(fsm, a_wait_bcon_tmr); +		break; +	case OTG_STATE_A_HOST: +		otg_drv_vbus(fsm, 1); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 1); +		otg_set_protocol(fsm, PROTO_HOST); +		/* +		 * When HNP is triggered while a_bus_req = 0, a_host will +		 * suspend too fast to complete a_set_b_hnp_en +		 */ +		if (!fsm->a_bus_req || fsm->a_suspend_req) +			otg_add_timer(fsm, a_wait_enum_tmr); +		break; +	case OTG_STATE_A_SUSPEND: +		otg_drv_vbus(fsm, 1); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_HOST); +		otg_add_timer(fsm, a_aidl_bdis_tmr); + +		break; +	case OTG_STATE_A_PERIPHERAL: +		otg_loc_conn(fsm, 1); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_GADGET); +		otg_drv_vbus(fsm, 1); +		break; +	case OTG_STATE_A_WAIT_VFALL: +		otg_drv_vbus(fsm, 0); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_HOST); +		break; +	case OTG_STATE_A_VBUS_ERR: +		otg_drv_vbus(fsm, 0); +		otg_loc_conn(fsm, 0); +		otg_loc_sof(fsm, 0); +		otg_set_protocol(fsm, PROTO_UNDEF); +		break; +	default: +		break; +	} + +	fsm->otg->phy->state = new_state; +	return 0; +} + +/* State change judgement */ +int otg_statemachine(struct otg_fsm *fsm) +{ +	enum usb_otg_state state; +	unsigned long flags; + +	spin_lock_irqsave(&fsm->lock, flags); + +	state = fsm->otg->phy->state; +	state_changed = 0; +	/* State machine state change judgement */ + +	switch (state) { +	case OTG_STATE_UNDEFINED: +		VDBG("fsm->id = %d\n", fsm->id); +		if (fsm->id) +			otg_set_state(fsm, OTG_STATE_B_IDLE); +		else +			otg_set_state(fsm, OTG_STATE_A_IDLE); +		break; +	case OTG_STATE_B_IDLE: +		if (!fsm->id) +			otg_set_state(fsm, OTG_STATE_A_IDLE); +		else if (fsm->b_sess_vld && fsm->otg->gadget) +			otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); +		else if (fsm->b_bus_req && fsm->b_sess_end && fsm->b_se0_srp) +			otg_set_state(fsm, OTG_STATE_B_SRP_INIT); +		break; +	case OTG_STATE_B_SRP_INIT: +		if (!fsm->id || fsm->b_srp_done) +			otg_set_state(fsm, OTG_STATE_B_IDLE); +		break; +	case OTG_STATE_B_PERIPHERAL: +		if (!fsm->id || !fsm->b_sess_vld) +			otg_set_state(fsm, OTG_STATE_B_IDLE); +		else if (fsm->b_bus_req && fsm->otg-> +				gadget->b_hnp_enable && fsm->a_bus_suspend) +			otg_set_state(fsm, OTG_STATE_B_WAIT_ACON); +		break; +	case OTG_STATE_B_WAIT_ACON: +		if (fsm->a_conn) +			otg_set_state(fsm, OTG_STATE_B_HOST); +		else if (!fsm->id || !fsm->b_sess_vld) +			otg_set_state(fsm, OTG_STATE_B_IDLE); +		else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) { +			fsm->b_ase0_brst_tmout = 0; +			otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); +		} +		break; +	case OTG_STATE_B_HOST: +		if (!fsm->id || !fsm->b_sess_vld) +			otg_set_state(fsm, OTG_STATE_B_IDLE); +		else if (!fsm->b_bus_req || !fsm->a_conn) +			otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); +		break; +	case OTG_STATE_A_IDLE: +		if (fsm->id) +			otg_set_state(fsm, OTG_STATE_B_IDLE); +		else if (!fsm->a_bus_drop && (fsm->a_bus_req || fsm->a_srp_det)) +			otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE); +		break; +	case OTG_STATE_A_WAIT_VRISE: +		if (fsm->id || fsm->a_bus_drop || fsm->a_vbus_vld || +				fsm->a_wait_vrise_tmout) { +			otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); +		} +		break; +	case OTG_STATE_A_WAIT_BCON: +		if (!fsm->a_vbus_vld) +			otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); +		else if (fsm->b_conn) +			otg_set_state(fsm, OTG_STATE_A_HOST); +		else if (fsm->id | fsm->a_bus_drop | fsm->a_wait_bcon_tmout) +			otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); +		break; +	case OTG_STATE_A_HOST: +		if ((!fsm->a_bus_req || fsm->a_suspend_req) && +				fsm->otg->host->b_hnp_enable) +			otg_set_state(fsm, OTG_STATE_A_SUSPEND); +		else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop) +			otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); +		else if (!fsm->a_vbus_vld) +			otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); +		break; +	case OTG_STATE_A_SUSPEND: +		if (!fsm->b_conn && fsm->otg->host->b_hnp_enable) +			otg_set_state(fsm, OTG_STATE_A_PERIPHERAL); +		else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable) +			otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); +		else if (fsm->a_bus_req || fsm->b_bus_resume) +			otg_set_state(fsm, OTG_STATE_A_HOST); +		else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout) +			otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); +		else if (!fsm->a_vbus_vld) +			otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); +		break; +	case OTG_STATE_A_PERIPHERAL: +		if (fsm->id || fsm->a_bus_drop) +			otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); +		else if (fsm->b_bus_suspend) +			otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); +		else if (!fsm->a_vbus_vld) +			otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); +		break; +	case OTG_STATE_A_WAIT_VFALL: +		if (fsm->id || fsm->a_bus_req || (!fsm->a_sess_vld && +					!fsm->b_conn)) +			otg_set_state(fsm, OTG_STATE_A_IDLE); +		break; +	case OTG_STATE_A_VBUS_ERR: +		if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err) +			otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); +		break; +	default: +		break; +	} +	spin_unlock_irqrestore(&fsm->lock, flags); + +	VDBG("quit statemachine, changed = %d\n", state_changed); +	return state_changed; +} diff --git a/drivers/usb/phy/phy-fsm-usb.h b/drivers/usb/phy/phy-fsm-usb.h new file mode 100644 index 00000000000..c30a2e1d9e4 --- /dev/null +++ b/drivers/usb/phy/phy-fsm-usb.h @@ -0,0 +1,154 @@ +/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of  the GNU General  Public License as published by the + * Free Software Foundation;  either version 2 of the  License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the  GNU General Public License along + * with this program; if not, write  to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#undef DEBUG +#undef VERBOSE + +#ifdef DEBUG +#define DBG(fmt, args...) printk(KERN_DEBUG "[%s]  " fmt , \ +				 __func__, ## args) +#else +#define DBG(fmt, args...)	do {} while (0) +#endif + +#ifdef VERBOSE +#define VDBG		DBG +#else +#define VDBG(stuff...)	do {} while (0) +#endif + +#ifdef VERBOSE +#define MPC_LOC printk("Current Location [%s]:[%d]\n", __FILE__, __LINE__) +#else +#define MPC_LOC do {} while (0) +#endif + +#define PROTO_UNDEF	(0) +#define PROTO_HOST	(1) +#define PROTO_GADGET	(2) + +/* OTG state machine according to the OTG spec */ +struct otg_fsm { +	/* Input */ +	int a_bus_resume; +	int a_bus_suspend; +	int a_conn; +	int a_sess_vld; +	int a_srp_det; +	int a_vbus_vld; +	int b_bus_resume; +	int b_bus_suspend; +	int b_conn; +	int b_se0_srp; +	int b_sess_end; +	int b_sess_vld; +	int id; + +	/* Internal variables */ +	int a_set_b_hnp_en; +	int b_srp_done; +	int b_hnp_enable; + +	/* Timeout indicator for timers */ +	int a_wait_vrise_tmout; +	int a_wait_bcon_tmout; +	int a_aidl_bdis_tmout; +	int b_ase0_brst_tmout; + +	/* Informative variables */ +	int a_bus_drop; +	int a_bus_req; +	int a_clr_err; +	int a_suspend_req; +	int b_bus_req; + +	/* Output */ +	int drv_vbus; +	int loc_conn; +	int loc_sof; + +	struct otg_fsm_ops *ops; +	struct usb_otg *otg; + +	/* Current usb protocol used: 0:undefine; 1:host; 2:client */ +	int protocol; +	spinlock_t lock; +}; + +struct otg_fsm_ops { +	void	(*chrg_vbus)(int on); +	void	(*drv_vbus)(int on); +	void	(*loc_conn)(int on); +	void	(*loc_sof)(int on); +	void	(*start_pulse)(void); +	void	(*add_timer)(void *timer); +	void	(*del_timer)(void *timer); +	int	(*start_host)(struct otg_fsm *fsm, int on); +	int	(*start_gadget)(struct otg_fsm *fsm, int on); +}; + + +static inline void otg_chrg_vbus(struct otg_fsm *fsm, int on) +{ +	fsm->ops->chrg_vbus(on); +} + +static inline void otg_drv_vbus(struct otg_fsm *fsm, int on) +{ +	if (fsm->drv_vbus != on) { +		fsm->drv_vbus = on; +		fsm->ops->drv_vbus(on); +	} +} + +static inline void otg_loc_conn(struct otg_fsm *fsm, int on) +{ +	if (fsm->loc_conn != on) { +		fsm->loc_conn = on; +		fsm->ops->loc_conn(on); +	} +} + +static inline void otg_loc_sof(struct otg_fsm *fsm, int on) +{ +	if (fsm->loc_sof != on) { +		fsm->loc_sof = on; +		fsm->ops->loc_sof(on); +	} +} + +static inline void otg_start_pulse(struct otg_fsm *fsm) +{ +	fsm->ops->start_pulse(); +} + +static inline void otg_add_timer(struct otg_fsm *fsm, void *timer) +{ +	fsm->ops->add_timer(timer); +} + +static inline void otg_del_timer(struct otg_fsm *fsm, void *timer) +{ +	fsm->ops->del_timer(timer); +} + +int otg_statemachine(struct otg_fsm *fsm); + +/* Defined by device specific driver, for different timer implementation */ +extern struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, +	*a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr, +	*a_wait_enum_tmr; diff --git a/drivers/usb/phy/phy-gpio-vbus-usb.c b/drivers/usb/phy/phy-gpio-vbus-usb.c new file mode 100644 index 00000000000..4c76074e518 --- /dev/null +++ b/drivers/usb/phy/phy-gpio-vbus-usb.c @@ -0,0 +1,421 @@ +/* + * gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices + * + * Copyright (c) 2008 Philipp Zabel <philipp.zabel@gmail.com> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +#include <linux/regulator/consumer.h> + +#include <linux/usb/gadget.h> +#include <linux/usb/gpio_vbus.h> +#include <linux/usb/otg.h> + + +/* + * A simple GPIO VBUS sensing driver for B peripheral only devices + * with internal transceivers. It can control a D+ pullup GPIO and + * a regulator to limit the current drawn from VBUS. + * + * Needs to be loaded before the UDC driver that will use it. + */ +struct gpio_vbus_data { +	struct usb_phy		phy; +	struct device          *dev; +	struct regulator       *vbus_draw; +	int			vbus_draw_enabled; +	unsigned		mA; +	struct delayed_work	work; +	int			vbus; +	int			irq; +}; + + +/* + * This driver relies on "both edges" triggering.  VBUS has 100 msec to + * stabilize, so the peripheral controller driver may need to cope with + * some bouncing due to current surges (e.g. charging local capacitance) + * and contact chatter. + * + * REVISIT in desperate straits, toggling between rising and falling + * edges might be workable. + */ +#define VBUS_IRQ_FLAGS \ +	(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) + + +/* interface to regulator framework */ +static void set_vbus_draw(struct gpio_vbus_data *gpio_vbus, unsigned mA) +{ +	struct regulator *vbus_draw = gpio_vbus->vbus_draw; +	int enabled; +	int ret; + +	if (!vbus_draw) +		return; + +	enabled = gpio_vbus->vbus_draw_enabled; +	if (mA) { +		regulator_set_current_limit(vbus_draw, 0, 1000 * mA); +		if (!enabled) { +			ret = regulator_enable(vbus_draw); +			if (ret < 0) +				return; +			gpio_vbus->vbus_draw_enabled = 1; +		} +	} else { +		if (enabled) { +			ret = regulator_disable(vbus_draw); +			if (ret < 0) +				return; +			gpio_vbus->vbus_draw_enabled = 0; +		} +	} +	gpio_vbus->mA = mA; +} + +static int is_vbus_powered(struct gpio_vbus_mach_info *pdata) +{ +	int vbus; + +	vbus = gpio_get_value(pdata->gpio_vbus); +	if (pdata->gpio_vbus_inverted) +		vbus = !vbus; + +	return vbus; +} + +static void gpio_vbus_work(struct work_struct *work) +{ +	struct gpio_vbus_data *gpio_vbus = +		container_of(work, struct gpio_vbus_data, work.work); +	struct gpio_vbus_mach_info *pdata = gpio_vbus->dev->platform_data; +	int gpio, status, vbus; + +	if (!gpio_vbus->phy.otg->gadget) +		return; + +	vbus = is_vbus_powered(pdata); +	if ((vbus ^ gpio_vbus->vbus) == 0) +		return; +	gpio_vbus->vbus = vbus; + +	/* Peripheral controllers which manage the pullup themselves won't have +	 * gpio_pullup configured here.  If it's configured here, we'll do what +	 * isp1301_omap::b_peripheral() does and enable the pullup here... although +	 * that may complicate usb_gadget_{,dis}connect() support. +	 */ +	gpio = pdata->gpio_pullup; + +	if (vbus) { +		status = USB_EVENT_VBUS; +		gpio_vbus->phy.state = OTG_STATE_B_PERIPHERAL; +		gpio_vbus->phy.last_event = status; +		usb_gadget_vbus_connect(gpio_vbus->phy.otg->gadget); + +		/* drawing a "unit load" is *always* OK, except for OTG */ +		set_vbus_draw(gpio_vbus, 100); + +		/* optionally enable D+ pullup */ +		if (gpio_is_valid(gpio)) +			gpio_set_value(gpio, !pdata->gpio_pullup_inverted); + +		atomic_notifier_call_chain(&gpio_vbus->phy.notifier, +					   status, gpio_vbus->phy.otg->gadget); +	} else { +		/* optionally disable D+ pullup */ +		if (gpio_is_valid(gpio)) +			gpio_set_value(gpio, pdata->gpio_pullup_inverted); + +		set_vbus_draw(gpio_vbus, 0); + +		usb_gadget_vbus_disconnect(gpio_vbus->phy.otg->gadget); +		status = USB_EVENT_NONE; +		gpio_vbus->phy.state = OTG_STATE_B_IDLE; +		gpio_vbus->phy.last_event = status; + +		atomic_notifier_call_chain(&gpio_vbus->phy.notifier, +					   status, gpio_vbus->phy.otg->gadget); +	} +} + +/* VBUS change IRQ handler */ +static irqreturn_t gpio_vbus_irq(int irq, void *data) +{ +	struct platform_device *pdev = data; +	struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; +	struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); +	struct usb_otg *otg = gpio_vbus->phy.otg; + +	dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n", +		is_vbus_powered(pdata) ? "supplied" : "inactive", +		otg->gadget ? otg->gadget->name : "none"); + +	if (otg->gadget) +		schedule_delayed_work(&gpio_vbus->work, msecs_to_jiffies(100)); + +	return IRQ_HANDLED; +} + +/* OTG transceiver interface */ + +/* bind/unbind the peripheral controller */ +static int gpio_vbus_set_peripheral(struct usb_otg *otg, +					struct usb_gadget *gadget) +{ +	struct gpio_vbus_data *gpio_vbus; +	struct gpio_vbus_mach_info *pdata; +	struct platform_device *pdev; +	int gpio; + +	gpio_vbus = container_of(otg->phy, struct gpio_vbus_data, phy); +	pdev = to_platform_device(gpio_vbus->dev); +	pdata = gpio_vbus->dev->platform_data; +	gpio = pdata->gpio_pullup; + +	if (!gadget) { +		dev_dbg(&pdev->dev, "unregistering gadget '%s'\n", +			otg->gadget->name); + +		/* optionally disable D+ pullup */ +		if (gpio_is_valid(gpio)) +			gpio_set_value(gpio, pdata->gpio_pullup_inverted); + +		set_vbus_draw(gpio_vbus, 0); + +		usb_gadget_vbus_disconnect(otg->gadget); +		otg->phy->state = OTG_STATE_UNDEFINED; + +		otg->gadget = NULL; +		return 0; +	} + +	otg->gadget = gadget; +	dev_dbg(&pdev->dev, "registered gadget '%s'\n", gadget->name); + +	/* initialize connection state */ +	gpio_vbus->vbus = 0; /* start with disconnected */ +	gpio_vbus_irq(gpio_vbus->irq, pdev); +	return 0; +} + +/* effective for B devices, ignored for A-peripheral */ +static int gpio_vbus_set_power(struct usb_phy *phy, unsigned mA) +{ +	struct gpio_vbus_data *gpio_vbus; + +	gpio_vbus = container_of(phy, struct gpio_vbus_data, phy); + +	if (phy->state == OTG_STATE_B_PERIPHERAL) +		set_vbus_draw(gpio_vbus, mA); +	return 0; +} + +/* for non-OTG B devices: set/clear transceiver suspend mode */ +static int gpio_vbus_set_suspend(struct usb_phy *phy, int suspend) +{ +	struct gpio_vbus_data *gpio_vbus; + +	gpio_vbus = container_of(phy, struct gpio_vbus_data, phy); + +	/* draw max 0 mA from vbus in suspend mode; or the previously +	 * recorded amount of current if not suspended +	 * +	 * NOTE: high powered configs (mA > 100) may draw up to 2.5 mA +	 * if they're wake-enabled ... we don't handle that yet. +	 */ +	return gpio_vbus_set_power(phy, suspend ? 0 : gpio_vbus->mA); +} + +/* platform driver interface */ + +static int __init gpio_vbus_probe(struct platform_device *pdev) +{ +	struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; +	struct gpio_vbus_data *gpio_vbus; +	struct resource *res; +	int err, gpio, irq; +	unsigned long irqflags; + +	if (!pdata || !gpio_is_valid(pdata->gpio_vbus)) +		return -EINVAL; +	gpio = pdata->gpio_vbus; + +	gpio_vbus = kzalloc(sizeof(struct gpio_vbus_data), GFP_KERNEL); +	if (!gpio_vbus) +		return -ENOMEM; + +	gpio_vbus->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); +	if (!gpio_vbus->phy.otg) { +		kfree(gpio_vbus); +		return -ENOMEM; +	} + +	platform_set_drvdata(pdev, gpio_vbus); +	gpio_vbus->dev = &pdev->dev; +	gpio_vbus->phy.label = "gpio-vbus"; +	gpio_vbus->phy.set_power = gpio_vbus_set_power; +	gpio_vbus->phy.set_suspend = gpio_vbus_set_suspend; +	gpio_vbus->phy.state = OTG_STATE_UNDEFINED; + +	gpio_vbus->phy.otg->phy = &gpio_vbus->phy; +	gpio_vbus->phy.otg->set_peripheral = gpio_vbus_set_peripheral; + +	err = gpio_request(gpio, "vbus_detect"); +	if (err) { +		dev_err(&pdev->dev, "can't request vbus gpio %d, err: %d\n", +			gpio, err); +		goto err_gpio; +	} +	gpio_direction_input(gpio); + +	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (res) { +		irq = res->start; +		irqflags = (res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED; +	} else { +		irq = gpio_to_irq(gpio); +		irqflags = VBUS_IRQ_FLAGS; +	} + +	gpio_vbus->irq = irq; + +	/* if data line pullup is in use, initialize it to "not pulling up" */ +	gpio = pdata->gpio_pullup; +	if (gpio_is_valid(gpio)) { +		err = gpio_request(gpio, "udc_pullup"); +		if (err) { +			dev_err(&pdev->dev, +				"can't request pullup gpio %d, err: %d\n", +				gpio, err); +			gpio_free(pdata->gpio_vbus); +			goto err_gpio; +		} +		gpio_direction_output(gpio, pdata->gpio_pullup_inverted); +	} + +	err = request_irq(irq, gpio_vbus_irq, irqflags, "vbus_detect", pdev); +	if (err) { +		dev_err(&pdev->dev, "can't request irq %i, err: %d\n", +			irq, err); +		goto err_irq; +	} + +	ATOMIC_INIT_NOTIFIER_HEAD(&gpio_vbus->phy.notifier); + +	INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work); + +	gpio_vbus->vbus_draw = regulator_get(&pdev->dev, "vbus_draw"); +	if (IS_ERR(gpio_vbus->vbus_draw)) { +		dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n", +			PTR_ERR(gpio_vbus->vbus_draw)); +		gpio_vbus->vbus_draw = NULL; +	} + +	/* only active when a gadget is registered */ +	err = usb_add_phy(&gpio_vbus->phy, USB_PHY_TYPE_USB2); +	if (err) { +		dev_err(&pdev->dev, "can't register transceiver, err: %d\n", +			err); +		goto err_otg; +	} + +	device_init_wakeup(&pdev->dev, pdata->wakeup); + +	return 0; +err_otg: +	regulator_put(gpio_vbus->vbus_draw); +	free_irq(irq, pdev); +err_irq: +	if (gpio_is_valid(pdata->gpio_pullup)) +		gpio_free(pdata->gpio_pullup); +	gpio_free(pdata->gpio_vbus); +err_gpio: +	platform_set_drvdata(pdev, NULL); +	kfree(gpio_vbus->phy.otg); +	kfree(gpio_vbus); +	return err; +} + +static int __exit gpio_vbus_remove(struct platform_device *pdev) +{ +	struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); +	struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; +	int gpio = pdata->gpio_vbus; + +	device_init_wakeup(&pdev->dev, 0); +	cancel_delayed_work_sync(&gpio_vbus->work); +	regulator_put(gpio_vbus->vbus_draw); + +	usb_remove_phy(&gpio_vbus->phy); + +	free_irq(gpio_vbus->irq, pdev); +	if (gpio_is_valid(pdata->gpio_pullup)) +		gpio_free(pdata->gpio_pullup); +	gpio_free(gpio); +	platform_set_drvdata(pdev, NULL); +	kfree(gpio_vbus->phy.otg); +	kfree(gpio_vbus); + +	return 0; +} + +#ifdef CONFIG_PM +static int gpio_vbus_pm_suspend(struct device *dev) +{ +	struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev); + +	if (device_may_wakeup(dev)) +		enable_irq_wake(gpio_vbus->irq); + +	return 0; +} + +static int gpio_vbus_pm_resume(struct device *dev) +{ +	struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev); + +	if (device_may_wakeup(dev)) +		disable_irq_wake(gpio_vbus->irq); + +	return 0; +} + +static const struct dev_pm_ops gpio_vbus_dev_pm_ops = { +	.suspend	= gpio_vbus_pm_suspend, +	.resume		= gpio_vbus_pm_resume, +}; +#endif + +/* NOTE:  the gpio-vbus device may *NOT* be hotplugged */ + +MODULE_ALIAS("platform:gpio-vbus"); + +static struct platform_driver gpio_vbus_driver = { +	.driver = { +		.name  = "gpio-vbus", +		.owner = THIS_MODULE, +#ifdef CONFIG_PM +		.pm = &gpio_vbus_dev_pm_ops, +#endif +	}, +	.remove  = __exit_p(gpio_vbus_remove), +}; + +module_platform_driver_probe(gpio_vbus_driver, gpio_vbus_probe); + +MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver"); +MODULE_AUTHOR("Philipp Zabel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-isp1301-omap.c b/drivers/usb/phy/phy-isp1301-omap.c new file mode 100644 index 00000000000..ae481afcb3e --- /dev/null +++ b/drivers/usb/phy/phy-isp1301-omap.c @@ -0,0 +1,1656 @@ +/* + * isp1301_omap - ISP 1301 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb.h> +#include <linux/usb/otg.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> + +#include <asm/irq.h> +#include <asm/mach-types.h> + +#include <mach/mux.h> + +#include <mach/usb.h> + +#ifndef	DEBUG +#undef	VERBOSE +#endif + + +#define	DRIVER_VERSION	"24 August 2004" +#define	DRIVER_NAME	(isp1301_driver.driver.name) + +MODULE_DESCRIPTION("ISP1301 USB OTG Transceiver Driver"); +MODULE_LICENSE("GPL"); + +struct isp1301 { +	struct usb_phy		phy; +	struct i2c_client	*client; +	void			(*i2c_release)(struct device *dev); + +	int			irq_type; + +	u32			last_otg_ctrl; +	unsigned		working:1; + +	struct timer_list	timer; + +	/* use keventd context to change the state for us */ +	struct work_struct	work; + +	unsigned long		todo; +#		define WORK_UPDATE_ISP	0	/* update ISP from OTG */ +#		define WORK_UPDATE_OTG	1	/* update OTG from ISP */ +#		define WORK_HOST_RESUME	4	/* resume host */ +#		define WORK_TIMER	6	/* timer fired */ +#		define WORK_STOP	7	/* don't resubmit */ +}; + + +/* bits in OTG_CTRL */ + +#define	OTG_XCEIV_OUTPUTS \ +	(OTG_ASESSVLD|OTG_BSESSEND|OTG_BSESSVLD|OTG_VBUSVLD|OTG_ID) +#define	OTG_XCEIV_INPUTS \ +	(OTG_PULLDOWN|OTG_PULLUP|OTG_DRV_VBUS|OTG_PD_VBUS|OTG_PU_VBUS|OTG_PU_ID) +#define	OTG_CTRL_BITS \ +	(OTG_A_BUSREQ|OTG_A_SETB_HNPEN|OTG_B_BUSREQ|OTG_B_HNPEN|OTG_BUSDROP) +	/* and OTG_PULLUP is sometimes written */ + +#define	OTG_CTRL_MASK	(OTG_DRIVER_SEL| \ +	OTG_XCEIV_OUTPUTS|OTG_XCEIV_INPUTS| \ +	OTG_CTRL_BITS) + + +/*-------------------------------------------------------------------------*/ + +/* board-specific PM hooks */ + +#if defined(CONFIG_MACH_OMAP_H2) || defined(CONFIG_MACH_OMAP_H3) + +#if	defined(CONFIG_TPS65010) || defined(CONFIG_TPS65010_MODULE) + +#include <linux/i2c/tps65010.h> + +#else + +static inline int tps65010_set_vbus_draw(unsigned mA) +{ +	pr_debug("tps65010: draw %d mA (STUB)\n", mA); +	return 0; +} + +#endif + +static void enable_vbus_draw(struct isp1301 *isp, unsigned mA) +{ +	int status = tps65010_set_vbus_draw(mA); +	if (status < 0) +		pr_debug("  VBUS %d mA error %d\n", mA, status); +} + +#else + +static void enable_vbus_draw(struct isp1301 *isp, unsigned mA) +{ +	/* H4 controls this by DIP switch S2.4; no soft control. +	 * ON means the charger is always enabled.  Leave it OFF +	 * unless the OTG port is used only in B-peripheral mode. +	 */ +} + +#endif + +static void enable_vbus_source(struct isp1301 *isp) +{ +	/* this board won't supply more than 8mA vbus power. +	 * some boards can switch a 100ma "unit load" (or more). +	 */ +} + + +/* products will deliver OTG messages with LEDs, GUI, etc */ +static inline void notresponding(struct isp1301 *isp) +{ +	printk(KERN_NOTICE "OTG device not responding.\n"); +} + + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver isp1301_driver; + +/* smbus apis are used for portability */ + +static inline u8 +isp1301_get_u8(struct isp1301 *isp, u8 reg) +{ +	return i2c_smbus_read_byte_data(isp->client, reg + 0); +} + +static inline int +isp1301_get_u16(struct isp1301 *isp, u8 reg) +{ +	return i2c_smbus_read_word_data(isp->client, reg); +} + +static inline int +isp1301_set_bits(struct isp1301 *isp, u8 reg, u8 bits) +{ +	return i2c_smbus_write_byte_data(isp->client, reg + 0, bits); +} + +static inline int +isp1301_clear_bits(struct isp1301 *isp, u8 reg, u8 bits) +{ +	return i2c_smbus_write_byte_data(isp->client, reg + 1, bits); +} + +/*-------------------------------------------------------------------------*/ + +/* identification */ +#define	ISP1301_VENDOR_ID		0x00	/* u16 read */ +#define	ISP1301_PRODUCT_ID		0x02	/* u16 read */ +#define	ISP1301_BCD_DEVICE		0x14	/* u16 read */ + +#define	I2C_VENDOR_ID_PHILIPS		0x04cc +#define	I2C_PRODUCT_ID_PHILIPS_1301	0x1301 + +/* operational registers */ +#define	ISP1301_MODE_CONTROL_1		0x04	/* u8 read, set, +1 clear */ +#	define	MC1_SPEED		(1 << 0) +#	define	MC1_SUSPEND		(1 << 1) +#	define	MC1_DAT_SE0		(1 << 2) +#	define	MC1_TRANSPARENT		(1 << 3) +#	define	MC1_BDIS_ACON_EN	(1 << 4) +#	define	MC1_OE_INT_EN		(1 << 5) +#	define	MC1_UART_EN		(1 << 6) +#	define	MC1_MASK		0x7f +#define	ISP1301_MODE_CONTROL_2		0x12	/* u8 read, set, +1 clear */ +#	define	MC2_GLOBAL_PWR_DN	(1 << 0) +#	define	MC2_SPD_SUSP_CTRL	(1 << 1) +#	define	MC2_BI_DI		(1 << 2) +#	define	MC2_TRANSP_BDIR0	(1 << 3) +#	define	MC2_TRANSP_BDIR1	(1 << 4) +#	define	MC2_AUDIO_EN		(1 << 5) +#	define	MC2_PSW_EN		(1 << 6) +#	define	MC2_EN2V7		(1 << 7) +#define	ISP1301_OTG_CONTROL_1		0x06	/* u8 read, set, +1 clear */ +#	define	OTG1_DP_PULLUP		(1 << 0) +#	define	OTG1_DM_PULLUP		(1 << 1) +#	define	OTG1_DP_PULLDOWN	(1 << 2) +#	define	OTG1_DM_PULLDOWN	(1 << 3) +#	define	OTG1_ID_PULLDOWN	(1 << 4) +#	define	OTG1_VBUS_DRV		(1 << 5) +#	define	OTG1_VBUS_DISCHRG	(1 << 6) +#	define	OTG1_VBUS_CHRG		(1 << 7) +#define	ISP1301_OTG_STATUS		0x10	/* u8 readonly */ +#	define	OTG_B_SESS_END		(1 << 6) +#	define	OTG_B_SESS_VLD		(1 << 7) + +#define	ISP1301_INTERRUPT_SOURCE	0x08	/* u8 read */ +#define	ISP1301_INTERRUPT_LATCH		0x0A	/* u8 read, set, +1 clear */ + +#define	ISP1301_INTERRUPT_FALLING	0x0C	/* u8 read, set, +1 clear */ +#define	ISP1301_INTERRUPT_RISING	0x0E	/* u8 read, set, +1 clear */ + +/* same bitfields in all interrupt registers */ +#	define	INTR_VBUS_VLD		(1 << 0) +#	define	INTR_SESS_VLD		(1 << 1) +#	define	INTR_DP_HI		(1 << 2) +#	define	INTR_ID_GND		(1 << 3) +#	define	INTR_DM_HI		(1 << 4) +#	define	INTR_ID_FLOAT		(1 << 5) +#	define	INTR_BDIS_ACON		(1 << 6) +#	define	INTR_CR_INT		(1 << 7) + +/*-------------------------------------------------------------------------*/ + +static inline const char *state_name(struct isp1301 *isp) +{ +	return usb_otg_state_string(isp->phy.state); +} + +/*-------------------------------------------------------------------------*/ + +/* NOTE:  some of this ISP1301 setup is specific to H2 boards; + * not everything is guarded by board-specific checks, or even using + * omap_usb_config data to deduce MC1_DAT_SE0 and MC2_BI_DI. + * + * ALSO:  this currently doesn't use ISP1301 low-power modes + * while OTG is running. + */ + +static void power_down(struct isp1301 *isp) +{ +	isp->phy.state = OTG_STATE_UNDEFINED; + +	// isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN); +	isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND); + +	isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_ID_PULLDOWN); +	isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); +} + +static void power_up(struct isp1301 *isp) +{ +	// isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN); +	isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SUSPEND); + +	/* do this only when cpu is driving transceiver, +	 * so host won't see a low speed device... +	 */ +	isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); +} + +#define	NO_HOST_SUSPEND + +static int host_suspend(struct isp1301 *isp) +{ +#ifdef	NO_HOST_SUSPEND +	return 0; +#else +	struct device	*dev; + +	if (!isp->phy.otg->host) +		return -ENODEV; + +	/* Currently ASSUMES only the OTG port matters; +	 * other ports could be active... +	 */ +	dev = isp->phy.otg->host->controller; +	return dev->driver->suspend(dev, 3, 0); +#endif +} + +static int host_resume(struct isp1301 *isp) +{ +#ifdef	NO_HOST_SUSPEND +	return 0; +#else +	struct device	*dev; + +	if (!isp->phy.otg->host) +		return -ENODEV; + +	dev = isp->phy.otg->host->controller; +	return dev->driver->resume(dev, 0); +#endif +} + +static int gadget_suspend(struct isp1301 *isp) +{ +	isp->phy.otg->gadget->b_hnp_enable = 0; +	isp->phy.otg->gadget->a_hnp_support = 0; +	isp->phy.otg->gadget->a_alt_hnp_support = 0; +	return usb_gadget_vbus_disconnect(isp->phy.otg->gadget); +} + +/*-------------------------------------------------------------------------*/ + +#define	TIMER_MINUTES	10 +#define	TIMER_JIFFIES	(TIMER_MINUTES * 60 * HZ) + +/* Almost all our I2C messaging comes from a work queue's task context. + * NOTE: guaranteeing certain response times might mean we shouldn't + * share keventd's work queue; a realtime task might be safest. + */ +static void isp1301_defer_work(struct isp1301 *isp, int work) +{ +	int status; + +	if (isp && !test_and_set_bit(work, &isp->todo)) { +		(void) get_device(&isp->client->dev); +		status = schedule_work(&isp->work); +		if (!status && !isp->working) +			dev_vdbg(&isp->client->dev, +				"work item %d may be lost\n", work); +	} +} + +/* called from irq handlers */ +static void a_idle(struct isp1301 *isp, const char *tag) +{ +	u32 l; + +	if (isp->phy.state == OTG_STATE_A_IDLE) +		return; + +	isp->phy.otg->default_a = 1; +	if (isp->phy.otg->host) { +		isp->phy.otg->host->is_b_host = 0; +		host_suspend(isp); +	} +	if (isp->phy.otg->gadget) { +		isp->phy.otg->gadget->is_a_peripheral = 1; +		gadget_suspend(isp); +	} +	isp->phy.state = OTG_STATE_A_IDLE; +	l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; +	omap_writel(l, OTG_CTRL); +	isp->last_otg_ctrl = l; +	pr_debug("  --> %s/%s\n", state_name(isp), tag); +} + +/* called from irq handlers */ +static void b_idle(struct isp1301 *isp, const char *tag) +{ +	u32 l; + +	if (isp->phy.state == OTG_STATE_B_IDLE) +		return; + +	isp->phy.otg->default_a = 0; +	if (isp->phy.otg->host) { +		isp->phy.otg->host->is_b_host = 1; +		host_suspend(isp); +	} +	if (isp->phy.otg->gadget) { +		isp->phy.otg->gadget->is_a_peripheral = 0; +		gadget_suspend(isp); +	} +	isp->phy.state = OTG_STATE_B_IDLE; +	l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; +	omap_writel(l, OTG_CTRL); +	isp->last_otg_ctrl = l; +	pr_debug("  --> %s/%s\n", state_name(isp), tag); +} + +static void +dump_regs(struct isp1301 *isp, const char *label) +{ +#ifdef	DEBUG +	u8	ctrl = isp1301_get_u8(isp, ISP1301_OTG_CONTROL_1); +	u8	status = isp1301_get_u8(isp, ISP1301_OTG_STATUS); +	u8	src = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE); + +	pr_debug("otg: %06x, %s %s, otg/%02x stat/%02x.%02x\n", +		omap_readl(OTG_CTRL), label, state_name(isp), +		ctrl, status, src); +	/* mode control and irq enables don't change much */ +#endif +} + +/*-------------------------------------------------------------------------*/ + +#ifdef	CONFIG_USB_OTG + +/* + * The OMAP OTG controller handles most of the OTG state transitions. + * + * We translate isp1301 outputs (mostly voltage comparator status) into + * OTG inputs; OTG outputs (mostly pullup/pulldown controls) and HNP state + * flags into isp1301 inputs ... and infer state transitions. + */ + +#ifdef	VERBOSE + +static void check_state(struct isp1301 *isp, const char *tag) +{ +	enum usb_otg_state	state = OTG_STATE_UNDEFINED; +	u8			fsm = omap_readw(OTG_TEST) & 0x0ff; +	unsigned		extra = 0; + +	switch (fsm) { + +	/* default-b */ +	case 0x0: +		state = OTG_STATE_B_IDLE; +		break; +	case 0x3: +	case 0x7: +		extra = 1; +	case 0x1: +		state = OTG_STATE_B_PERIPHERAL; +		break; +	case 0x11: +		state = OTG_STATE_B_SRP_INIT; +		break; + +	/* extra dual-role default-b states */ +	case 0x12: +	case 0x13: +	case 0x16: +		extra = 1; +	case 0x17: +		state = OTG_STATE_B_WAIT_ACON; +		break; +	case 0x34: +		state = OTG_STATE_B_HOST; +		break; + +	/* default-a */ +	case 0x36: +		state = OTG_STATE_A_IDLE; +		break; +	case 0x3c: +		state = OTG_STATE_A_WAIT_VFALL; +		break; +	case 0x7d: +		state = OTG_STATE_A_VBUS_ERR; +		break; +	case 0x9e: +	case 0x9f: +		extra = 1; +	case 0x89: +		state = OTG_STATE_A_PERIPHERAL; +		break; +	case 0xb7: +		state = OTG_STATE_A_WAIT_VRISE; +		break; +	case 0xb8: +		state = OTG_STATE_A_WAIT_BCON; +		break; +	case 0xb9: +		state = OTG_STATE_A_HOST; +		break; +	case 0xba: +		state = OTG_STATE_A_SUSPEND; +		break; +	default: +		break; +	} +	if (isp->phy.state == state && !extra) +		return; +	pr_debug("otg: %s FSM %s/%02x, %s, %06x\n", tag, +		usb_otg_state_string(state), fsm, state_name(isp), +		omap_readl(OTG_CTRL)); +} + +#else + +static inline void check_state(struct isp1301 *isp, const char *tag) { } + +#endif + +/* outputs from ISP1301_INTERRUPT_SOURCE */ +static void update_otg1(struct isp1301 *isp, u8 int_src) +{ +	u32	otg_ctrl; + +	otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; +	otg_ctrl &= ~OTG_XCEIV_INPUTS; +	otg_ctrl &= ~(OTG_ID|OTG_ASESSVLD|OTG_VBUSVLD); + +	if (int_src & INTR_SESS_VLD) +		otg_ctrl |= OTG_ASESSVLD; +	else if (isp->phy.state == OTG_STATE_A_WAIT_VFALL) { +		a_idle(isp, "vfall"); +		otg_ctrl &= ~OTG_CTRL_BITS; +	} +	if (int_src & INTR_VBUS_VLD) +		otg_ctrl |= OTG_VBUSVLD; +	if (int_src & INTR_ID_GND) {		/* default-A */ +		if (isp->phy.state == OTG_STATE_B_IDLE +				|| isp->phy.state +					== OTG_STATE_UNDEFINED) { +			a_idle(isp, "init"); +			return; +		} +	} else {				/* default-B */ +		otg_ctrl |= OTG_ID; +		if (isp->phy.state == OTG_STATE_A_IDLE +			|| isp->phy.state == OTG_STATE_UNDEFINED) { +			b_idle(isp, "init"); +			return; +		} +	} +	omap_writel(otg_ctrl, OTG_CTRL); +} + +/* outputs from ISP1301_OTG_STATUS */ +static void update_otg2(struct isp1301 *isp, u8 otg_status) +{ +	u32	otg_ctrl; + +	otg_ctrl = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; +	otg_ctrl &= ~OTG_XCEIV_INPUTS; +	otg_ctrl &= ~(OTG_BSESSVLD | OTG_BSESSEND); +	if (otg_status & OTG_B_SESS_VLD) +		otg_ctrl |= OTG_BSESSVLD; +	else if (otg_status & OTG_B_SESS_END) +		otg_ctrl |= OTG_BSESSEND; +	omap_writel(otg_ctrl, OTG_CTRL); +} + +/* inputs going to ISP1301 */ +static void otg_update_isp(struct isp1301 *isp) +{ +	u32	otg_ctrl, otg_change; +	u8	set = OTG1_DM_PULLDOWN, clr = OTG1_DM_PULLUP; + +	otg_ctrl = omap_readl(OTG_CTRL); +	otg_change = otg_ctrl ^ isp->last_otg_ctrl; +	isp->last_otg_ctrl = otg_ctrl; +	otg_ctrl = otg_ctrl & OTG_XCEIV_INPUTS; + +	switch (isp->phy.state) { +	case OTG_STATE_B_IDLE: +	case OTG_STATE_B_PERIPHERAL: +	case OTG_STATE_B_SRP_INIT: +		if (!(otg_ctrl & OTG_PULLUP)) { +			// if (otg_ctrl & OTG_B_HNPEN) { +			if (isp->phy.otg->gadget->b_hnp_enable) { +				isp->phy.state = OTG_STATE_B_WAIT_ACON; +				pr_debug("  --> b_wait_acon\n"); +			} +			goto pulldown; +		} +pullup: +		set |= OTG1_DP_PULLUP; +		clr |= OTG1_DP_PULLDOWN; +		break; +	case OTG_STATE_A_SUSPEND: +	case OTG_STATE_A_PERIPHERAL: +		if (otg_ctrl & OTG_PULLUP) +			goto pullup; +		/* FALLTHROUGH */ +	// case OTG_STATE_B_WAIT_ACON: +	default: +pulldown: +		set |= OTG1_DP_PULLDOWN; +		clr |= OTG1_DP_PULLUP; +		break; +	} + +#	define toggle(OTG,ISP) do { \ +		if (otg_ctrl & OTG) set |= ISP; \ +		else clr |= ISP; \ +		} while (0) + +	if (!(isp->phy.otg->host)) +		otg_ctrl &= ~OTG_DRV_VBUS; + +	switch (isp->phy.state) { +	case OTG_STATE_A_SUSPEND: +		if (otg_ctrl & OTG_DRV_VBUS) { +			set |= OTG1_VBUS_DRV; +			break; +		} +		/* HNP failed for some reason (A_AIDL_BDIS timeout) */ +		notresponding(isp); + +		/* FALLTHROUGH */ +	case OTG_STATE_A_VBUS_ERR: +		isp->phy.state = OTG_STATE_A_WAIT_VFALL; +		pr_debug("  --> a_wait_vfall\n"); +		/* FALLTHROUGH */ +	case OTG_STATE_A_WAIT_VFALL: +		/* FIXME usbcore thinks port power is still on ... */ +		clr |= OTG1_VBUS_DRV; +		break; +	case OTG_STATE_A_IDLE: +		if (otg_ctrl & OTG_DRV_VBUS) { +			isp->phy.state = OTG_STATE_A_WAIT_VRISE; +			pr_debug("  --> a_wait_vrise\n"); +		} +		/* FALLTHROUGH */ +	default: +		toggle(OTG_DRV_VBUS, OTG1_VBUS_DRV); +	} + +	toggle(OTG_PU_VBUS, OTG1_VBUS_CHRG); +	toggle(OTG_PD_VBUS, OTG1_VBUS_DISCHRG); + +#	undef toggle + +	isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, set); +	isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, clr); + +	/* HNP switch to host or peripheral; and SRP */ +	if (otg_change & OTG_PULLUP) { +		u32 l; + +		switch (isp->phy.state) { +		case OTG_STATE_B_IDLE: +			if (clr & OTG1_DP_PULLUP) +				break; +			isp->phy.state = OTG_STATE_B_PERIPHERAL; +			pr_debug("  --> b_peripheral\n"); +			break; +		case OTG_STATE_A_SUSPEND: +			if (clr & OTG1_DP_PULLUP) +				break; +			isp->phy.state = OTG_STATE_A_PERIPHERAL; +			pr_debug("  --> a_peripheral\n"); +			break; +		default: +			break; +		} +		l = omap_readl(OTG_CTRL); +		l |= OTG_PULLUP; +		omap_writel(l, OTG_CTRL); +	} + +	check_state(isp, __func__); +	dump_regs(isp, "otg->isp1301"); +} + +static irqreturn_t omap_otg_irq(int irq, void *_isp) +{ +	u16		otg_irq = omap_readw(OTG_IRQ_SRC); +	u32		otg_ctrl; +	int		ret = IRQ_NONE; +	struct isp1301	*isp = _isp; +	struct usb_otg	*otg = isp->phy.otg; + +	/* update ISP1301 transceiver from OTG controller */ +	if (otg_irq & OPRT_CHG) { +		omap_writew(OPRT_CHG, OTG_IRQ_SRC); +		isp1301_defer_work(isp, WORK_UPDATE_ISP); +		ret = IRQ_HANDLED; + +	/* SRP to become b_peripheral failed */ +	} else if (otg_irq & B_SRP_TMROUT) { +		pr_debug("otg: B_SRP_TIMEOUT, %06x\n", omap_readl(OTG_CTRL)); +		notresponding(isp); + +		/* gadget drivers that care should monitor all kinds of +		 * remote wakeup (SRP, normal) using their own timer +		 * to give "check cable and A-device" messages. +		 */ +		if (isp->phy.state == OTG_STATE_B_SRP_INIT) +			b_idle(isp, "srp_timeout"); + +		omap_writew(B_SRP_TMROUT, OTG_IRQ_SRC); +		ret = IRQ_HANDLED; + +	/* HNP to become b_host failed */ +	} else if (otg_irq & B_HNP_FAIL) { +		pr_debug("otg: %s B_HNP_FAIL, %06x\n", +				state_name(isp), omap_readl(OTG_CTRL)); +		notresponding(isp); + +		otg_ctrl = omap_readl(OTG_CTRL); +		otg_ctrl |= OTG_BUSDROP; +		otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; +		omap_writel(otg_ctrl, OTG_CTRL); + +		/* subset of b_peripheral()... */ +		isp->phy.state = OTG_STATE_B_PERIPHERAL; +		pr_debug("  --> b_peripheral\n"); + +		omap_writew(B_HNP_FAIL, OTG_IRQ_SRC); +		ret = IRQ_HANDLED; + +	/* detect SRP from B-device ... */ +	} else if (otg_irq & A_SRP_DETECT) { +		pr_debug("otg: %s SRP_DETECT, %06x\n", +				state_name(isp), omap_readl(OTG_CTRL)); + +		isp1301_defer_work(isp, WORK_UPDATE_OTG); +		switch (isp->phy.state) { +		case OTG_STATE_A_IDLE: +			if (!otg->host) +				break; +			isp1301_defer_work(isp, WORK_HOST_RESUME); +			otg_ctrl = omap_readl(OTG_CTRL); +			otg_ctrl |= OTG_A_BUSREQ; +			otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ) +					& ~OTG_XCEIV_INPUTS +					& OTG_CTRL_MASK; +			omap_writel(otg_ctrl, OTG_CTRL); +			break; +		default: +			break; +		} + +		omap_writew(A_SRP_DETECT, OTG_IRQ_SRC); +		ret = IRQ_HANDLED; + +	/* timer expired:  T(a_wait_bcon) and maybe T(a_wait_vrise) +	 * we don't track them separately +	 */ +	} else if (otg_irq & A_REQ_TMROUT) { +		otg_ctrl = omap_readl(OTG_CTRL); +		pr_info("otg: BCON_TMOUT from %s, %06x\n", +				state_name(isp), otg_ctrl); +		notresponding(isp); + +		otg_ctrl |= OTG_BUSDROP; +		otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; +		omap_writel(otg_ctrl, OTG_CTRL); +		isp->phy.state = OTG_STATE_A_WAIT_VFALL; + +		omap_writew(A_REQ_TMROUT, OTG_IRQ_SRC); +		ret = IRQ_HANDLED; + +	/* A-supplied voltage fell too low; overcurrent */ +	} else if (otg_irq & A_VBUS_ERR) { +		otg_ctrl = omap_readl(OTG_CTRL); +		printk(KERN_ERR "otg: %s, VBUS_ERR %04x ctrl %06x\n", +			state_name(isp), otg_irq, otg_ctrl); + +		otg_ctrl |= OTG_BUSDROP; +		otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; +		omap_writel(otg_ctrl, OTG_CTRL); +		isp->phy.state = OTG_STATE_A_VBUS_ERR; + +		omap_writew(A_VBUS_ERR, OTG_IRQ_SRC); +		ret = IRQ_HANDLED; + +	/* switch driver; the transceiver code activates it, +	 * ungating the udc clock or resuming OHCI. +	 */ +	} else if (otg_irq & DRIVER_SWITCH) { +		int	kick = 0; + +		otg_ctrl = omap_readl(OTG_CTRL); +		printk(KERN_NOTICE "otg: %s, SWITCH to %s, ctrl %06x\n", +				state_name(isp), +				(otg_ctrl & OTG_DRIVER_SEL) +					? "gadget" : "host", +				otg_ctrl); +		isp1301_defer_work(isp, WORK_UPDATE_ISP); + +		/* role is peripheral */ +		if (otg_ctrl & OTG_DRIVER_SEL) { +			switch (isp->phy.state) { +			case OTG_STATE_A_IDLE: +				b_idle(isp, __func__); +				break; +			default: +				break; +			} +			isp1301_defer_work(isp, WORK_UPDATE_ISP); + +		/* role is host */ +		} else { +			if (!(otg_ctrl & OTG_ID)) { +				otg_ctrl &= OTG_CTRL_MASK & ~OTG_XCEIV_INPUTS; +				omap_writel(otg_ctrl | OTG_A_BUSREQ, OTG_CTRL); +			} + +			if (otg->host) { +				switch (isp->phy.state) { +				case OTG_STATE_B_WAIT_ACON: +					isp->phy.state = OTG_STATE_B_HOST; +					pr_debug("  --> b_host\n"); +					kick = 1; +					break; +				case OTG_STATE_A_WAIT_BCON: +					isp->phy.state = OTG_STATE_A_HOST; +					pr_debug("  --> a_host\n"); +					break; +				case OTG_STATE_A_PERIPHERAL: +					isp->phy.state = OTG_STATE_A_WAIT_BCON; +					pr_debug("  --> a_wait_bcon\n"); +					break; +				default: +					break; +				} +				isp1301_defer_work(isp, WORK_HOST_RESUME); +			} +		} + +		omap_writew(DRIVER_SWITCH, OTG_IRQ_SRC); +		ret = IRQ_HANDLED; + +		if (kick) +			usb_bus_start_enum(otg->host, otg->host->otg_port); +	} + +	check_state(isp, __func__); +	return ret; +} + +static struct platform_device *otg_dev; + +static int isp1301_otg_init(struct isp1301 *isp) +{ +	u32 l; + +	if (!otg_dev) +		return -ENODEV; + +	dump_regs(isp, __func__); +	/* some of these values are board-specific... */ +	l = omap_readl(OTG_SYSCON_2); +	l |= OTG_EN +		/* for B-device: */ +		| SRP_GPDATA		/* 9msec Bdev D+ pulse */ +		| SRP_GPDVBUS		/* discharge after VBUS pulse */ +		// | (3 << 24)		/* 2msec VBUS pulse */ +		/* for A-device: */ +		| (0 << 20)		/* 200ms nominal A_WAIT_VRISE timer */ +		| SRP_DPW		/* detect 167+ns SRP pulses */ +		| SRP_DATA | SRP_VBUS	/* accept both kinds of SRP pulse */ +		; +	omap_writel(l, OTG_SYSCON_2); + +	update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE)); +	update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS)); + +	check_state(isp, __func__); +	pr_debug("otg: %s, %s %06x\n", +			state_name(isp), __func__, omap_readl(OTG_CTRL)); + +	omap_writew(DRIVER_SWITCH | OPRT_CHG +			| B_SRP_TMROUT | B_HNP_FAIL +			| A_VBUS_ERR | A_SRP_DETECT | A_REQ_TMROUT, OTG_IRQ_EN); + +	l = omap_readl(OTG_SYSCON_2); +	l |= OTG_EN; +	omap_writel(l, OTG_SYSCON_2); + +	return 0; +} + +static int otg_probe(struct platform_device *dev) +{ +	// struct omap_usb_config *config = dev->platform_data; + +	otg_dev = dev; +	return 0; +} + +static int otg_remove(struct platform_device *dev) +{ +	otg_dev = NULL; +	return 0; +} + +static struct platform_driver omap_otg_driver = { +	.probe		= otg_probe, +	.remove		= otg_remove, +	.driver		= { +		.owner	= THIS_MODULE, +		.name	= "omap_otg", +	}, +}; + +static int otg_bind(struct isp1301 *isp) +{ +	int	status; + +	if (otg_dev) +		return -EBUSY; + +	status = platform_driver_register(&omap_otg_driver); +	if (status < 0) +		return status; + +	if (otg_dev) +		status = request_irq(otg_dev->resource[1].start, omap_otg_irq, +				0, DRIVER_NAME, isp); +	else +		status = -ENODEV; + +	if (status < 0) +		platform_driver_unregister(&omap_otg_driver); +	return status; +} + +static void otg_unbind(struct isp1301 *isp) +{ +	if (!otg_dev) +		return; +	free_irq(otg_dev->resource[1].start, isp); +} + +#else + +/* OTG controller isn't clocked */ + +#endif	/* CONFIG_USB_OTG */ + +/*-------------------------------------------------------------------------*/ + +static void b_peripheral(struct isp1301 *isp) +{ +	u32 l; + +	l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; +	omap_writel(l, OTG_CTRL); + +	usb_gadget_vbus_connect(isp->phy.otg->gadget); + +#ifdef	CONFIG_USB_OTG +	enable_vbus_draw(isp, 8); +	otg_update_isp(isp); +#else +	enable_vbus_draw(isp, 100); +	/* UDC driver just set OTG_BSESSVLD */ +	isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLUP); +	isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_DP_PULLDOWN); +	isp->phy.state = OTG_STATE_B_PERIPHERAL; +	pr_debug("  --> b_peripheral\n"); +	dump_regs(isp, "2periph"); +#endif +} + +static void isp_update_otg(struct isp1301 *isp, u8 stat) +{ +	struct usb_otg		*otg = isp->phy.otg; +	u8			isp_stat, isp_bstat; +	enum usb_otg_state	state = isp->phy.state; + +	if (stat & INTR_BDIS_ACON) +		pr_debug("OTG:  BDIS_ACON, %s\n", state_name(isp)); + +	/* start certain state transitions right away */ +	isp_stat = isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE); +	if (isp_stat & INTR_ID_GND) { +		if (otg->default_a) { +			switch (state) { +			case OTG_STATE_B_IDLE: +				a_idle(isp, "idle"); +				/* FALLTHROUGH */ +			case OTG_STATE_A_IDLE: +				enable_vbus_source(isp); +				/* FALLTHROUGH */ +			case OTG_STATE_A_WAIT_VRISE: +				/* we skip over OTG_STATE_A_WAIT_BCON, since +				 * the HC will transition to A_HOST (or +				 * A_SUSPEND!) without our noticing except +				 * when HNP is used. +				 */ +				if (isp_stat & INTR_VBUS_VLD) +					isp->phy.state = OTG_STATE_A_HOST; +				break; +			case OTG_STATE_A_WAIT_VFALL: +				if (!(isp_stat & INTR_SESS_VLD)) +					a_idle(isp, "vfell"); +				break; +			default: +				if (!(isp_stat & INTR_VBUS_VLD)) +					isp->phy.state = OTG_STATE_A_VBUS_ERR; +				break; +			} +			isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS); +		} else { +			switch (state) { +			case OTG_STATE_B_PERIPHERAL: +			case OTG_STATE_B_HOST: +			case OTG_STATE_B_WAIT_ACON: +				usb_gadget_vbus_disconnect(otg->gadget); +				break; +			default: +				break; +			} +			if (state != OTG_STATE_A_IDLE) +				a_idle(isp, "id"); +			if (otg->host && state == OTG_STATE_A_IDLE) +				isp1301_defer_work(isp, WORK_HOST_RESUME); +			isp_bstat = 0; +		} +	} else { +		u32 l; + +		/* if user unplugged mini-A end of cable, +		 * don't bypass A_WAIT_VFALL. +		 */ +		if (otg->default_a) { +			switch (state) { +			default: +				isp->phy.state = OTG_STATE_A_WAIT_VFALL; +				break; +			case OTG_STATE_A_WAIT_VFALL: +				state = OTG_STATE_A_IDLE; +				/* khubd may take a while to notice and +				 * handle this disconnect, so don't go +				 * to B_IDLE quite yet. +				 */ +				break; +			case OTG_STATE_A_IDLE: +				host_suspend(isp); +				isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, +						MC1_BDIS_ACON_EN); +				isp->phy.state = OTG_STATE_B_IDLE; +				l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; +				l &= ~OTG_CTRL_BITS; +				omap_writel(l, OTG_CTRL); +				break; +			case OTG_STATE_B_IDLE: +				break; +			} +		} +		isp_bstat = isp1301_get_u8(isp, ISP1301_OTG_STATUS); + +		switch (isp->phy.state) { +		case OTG_STATE_B_PERIPHERAL: +		case OTG_STATE_B_WAIT_ACON: +		case OTG_STATE_B_HOST: +			if (likely(isp_bstat & OTG_B_SESS_VLD)) +				break; +			enable_vbus_draw(isp, 0); +#ifndef	CONFIG_USB_OTG +			/* UDC driver will clear OTG_BSESSVLD */ +			isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, +						OTG1_DP_PULLDOWN); +			isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, +						OTG1_DP_PULLUP); +			dump_regs(isp, __func__); +#endif +			/* FALLTHROUGH */ +		case OTG_STATE_B_SRP_INIT: +			b_idle(isp, __func__); +			l = omap_readl(OTG_CTRL) & OTG_XCEIV_OUTPUTS; +			omap_writel(l, OTG_CTRL); +			/* FALLTHROUGH */ +		case OTG_STATE_B_IDLE: +			if (otg->gadget && (isp_bstat & OTG_B_SESS_VLD)) { +#ifdef	CONFIG_USB_OTG +				update_otg1(isp, isp_stat); +				update_otg2(isp, isp_bstat); +#endif +				b_peripheral(isp); +			} else if (!(isp_stat & (INTR_VBUS_VLD|INTR_SESS_VLD))) +				isp_bstat |= OTG_B_SESS_END; +			break; +		case OTG_STATE_A_WAIT_VFALL: +			break; +		default: +			pr_debug("otg: unsupported b-device %s\n", +				state_name(isp)); +			break; +		} +	} + +	if (state != isp->phy.state) +		pr_debug("  isp, %s -> %s\n", +				usb_otg_state_string(state), state_name(isp)); + +#ifdef	CONFIG_USB_OTG +	/* update the OTG controller state to match the isp1301; may +	 * trigger OPRT_CHG irqs for changes going to the isp1301. +	 */ +	update_otg1(isp, isp_stat); +	update_otg2(isp, isp_bstat); +	check_state(isp, __func__); +#endif + +	dump_regs(isp, "isp1301->otg"); +} + +/*-------------------------------------------------------------------------*/ + +static u8 isp1301_clear_latch(struct isp1301 *isp) +{ +	u8 latch = isp1301_get_u8(isp, ISP1301_INTERRUPT_LATCH); +	isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, latch); +	return latch; +} + +static void +isp1301_work(struct work_struct *work) +{ +	struct isp1301	*isp = container_of(work, struct isp1301, work); +	int		stop; + +	/* implicit lock:  we're the only task using this device */ +	isp->working = 1; +	do { +		stop = test_bit(WORK_STOP, &isp->todo); + +#ifdef	CONFIG_USB_OTG +		/* transfer state from otg engine to isp1301 */ +		if (test_and_clear_bit(WORK_UPDATE_ISP, &isp->todo)) { +			otg_update_isp(isp); +			put_device(&isp->client->dev); +		} +#endif +		/* transfer state from isp1301 to otg engine */ +		if (test_and_clear_bit(WORK_UPDATE_OTG, &isp->todo)) { +			u8		stat = isp1301_clear_latch(isp); + +			isp_update_otg(isp, stat); +			put_device(&isp->client->dev); +		} + +		if (test_and_clear_bit(WORK_HOST_RESUME, &isp->todo)) { +			u32	otg_ctrl; + +			/* +			 * skip A_WAIT_VRISE; hc transitions invisibly +			 * skip A_WAIT_BCON; same. +			 */ +			switch (isp->phy.state) { +			case OTG_STATE_A_WAIT_BCON: +			case OTG_STATE_A_WAIT_VRISE: +				isp->phy.state = OTG_STATE_A_HOST; +				pr_debug("  --> a_host\n"); +				otg_ctrl = omap_readl(OTG_CTRL); +				otg_ctrl |= OTG_A_BUSREQ; +				otg_ctrl &= ~(OTG_BUSDROP|OTG_B_BUSREQ) +						& OTG_CTRL_MASK; +				omap_writel(otg_ctrl, OTG_CTRL); +				break; +			case OTG_STATE_B_WAIT_ACON: +				isp->phy.state = OTG_STATE_B_HOST; +				pr_debug("  --> b_host (acon)\n"); +				break; +			case OTG_STATE_B_HOST: +			case OTG_STATE_B_IDLE: +			case OTG_STATE_A_IDLE: +				break; +			default: +				pr_debug("  host resume in %s\n", +						state_name(isp)); +			} +			host_resume(isp); +			// mdelay(10); +			put_device(&isp->client->dev); +		} + +		if (test_and_clear_bit(WORK_TIMER, &isp->todo)) { +#ifdef	VERBOSE +			dump_regs(isp, "timer"); +			if (!stop) +				mod_timer(&isp->timer, jiffies + TIMER_JIFFIES); +#endif +			put_device(&isp->client->dev); +		} + +		if (isp->todo) +			dev_vdbg(&isp->client->dev, +				"work done, todo = 0x%lx\n", +				isp->todo); +		if (stop) { +			dev_dbg(&isp->client->dev, "stop\n"); +			break; +		} +	} while (isp->todo); +	isp->working = 0; +} + +static irqreturn_t isp1301_irq(int irq, void *isp) +{ +	isp1301_defer_work(isp, WORK_UPDATE_OTG); +	return IRQ_HANDLED; +} + +static void isp1301_timer(unsigned long _isp) +{ +	isp1301_defer_work((void *)_isp, WORK_TIMER); +} + +/*-------------------------------------------------------------------------*/ + +static void isp1301_release(struct device *dev) +{ +	struct isp1301	*isp; + +	isp = dev_get_drvdata(dev); + +	/* FIXME -- not with a "new style" driver, it doesn't!! */ + +	/* ugly -- i2c hijacks our memory hook to wait_for_completion() */ +	if (isp->i2c_release) +		isp->i2c_release(dev); +	kfree(isp->phy.otg); +	kfree (isp); +} + +static struct isp1301 *the_transceiver; + +static int isp1301_remove(struct i2c_client *i2c) +{ +	struct isp1301	*isp; + +	isp = i2c_get_clientdata(i2c); + +	isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0); +	isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0); +	free_irq(i2c->irq, isp); +#ifdef	CONFIG_USB_OTG +	otg_unbind(isp); +#endif +	if (machine_is_omap_h2()) +		gpio_free(2); + +	isp->timer.data = 0; +	set_bit(WORK_STOP, &isp->todo); +	del_timer_sync(&isp->timer); +	flush_work(&isp->work); + +	put_device(&i2c->dev); +	the_transceiver = NULL; + +	return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* NOTE:  three modes are possible here, only one of which + * will be standards-conformant on any given system: + * + *  - OTG mode (dual-role), required if there's a Mini-AB connector + *  - HOST mode, for when there's one or more A (host) connectors + *  - DEVICE mode, for when there's a B/Mini-B (device) connector + * + * As a rule, you won't have an isp1301 chip unless it's there to + * support the OTG mode.  Other modes help testing USB controllers + * in isolation from (full) OTG support, or maybe so later board + * revisions can help to support those feature. + */ + +#ifdef	CONFIG_USB_OTG + +static int isp1301_otg_enable(struct isp1301 *isp) +{ +	power_up(isp); +	isp1301_otg_init(isp); + +	/* NOTE:  since we don't change this, this provides +	 * a few more interrupts than are strictly needed. +	 */ +	isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, +		INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND); +	isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, +		INTR_VBUS_VLD | INTR_SESS_VLD | INTR_ID_GND); + +	dev_info(&isp->client->dev, "ready for dual-role USB ...\n"); + +	return 0; +} + +#endif + +/* add or disable the host device+driver */ +static int +isp1301_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	struct isp1301	*isp = container_of(otg->phy, struct isp1301, phy); + +	if (!otg || isp != the_transceiver) +		return -ENODEV; + +	if (!host) { +		omap_writew(0, OTG_IRQ_EN); +		power_down(isp); +		otg->host = NULL; +		return 0; +	} + +#ifdef	CONFIG_USB_OTG +	otg->host = host; +	dev_dbg(&isp->client->dev, "registered host\n"); +	host_suspend(isp); +	if (otg->gadget) +		return isp1301_otg_enable(isp); +	return 0; + +#elif	!defined(CONFIG_USB_GADGET_OMAP) +	// FIXME update its refcount +	otg->host = host; + +	power_up(isp); + +	if (machine_is_omap_h2()) +		isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); + +	dev_info(&isp->client->dev, "A-Host sessions ok\n"); +	isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, +		INTR_ID_GND); +	isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, +		INTR_ID_GND); + +	/* If this has a Mini-AB connector, this mode is highly +	 * nonstandard ... but can be handy for testing, especially with +	 * the Mini-A end of an OTG cable.  (Or something nonstandard +	 * like MiniB-to-StandardB, maybe built with a gender mender.) +	 */ +	isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, OTG1_VBUS_DRV); + +	dump_regs(isp, __func__); + +	return 0; + +#else +	dev_dbg(&isp->client->dev, "host sessions not allowed\n"); +	return -EINVAL; +#endif + +} + +static int +isp1301_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) +{ +	struct isp1301	*isp = container_of(otg->phy, struct isp1301, phy); + +	if (!otg || isp != the_transceiver) +		return -ENODEV; + +	if (!gadget) { +		omap_writew(0, OTG_IRQ_EN); +		if (!otg->default_a) +			enable_vbus_draw(isp, 0); +		usb_gadget_vbus_disconnect(otg->gadget); +		otg->gadget = NULL; +		power_down(isp); +		return 0; +	} + +#ifdef	CONFIG_USB_OTG +	otg->gadget = gadget; +	dev_dbg(&isp->client->dev, "registered gadget\n"); +	/* gadget driver may be suspended until vbus_connect () */ +	if (otg->host) +		return isp1301_otg_enable(isp); +	return 0; + +#elif	!defined(CONFIG_USB_OHCI_HCD) && !defined(CONFIG_USB_OHCI_HCD_MODULE) +	otg->gadget = gadget; +	// FIXME update its refcount + +	{ +		u32 l; + +		l = omap_readl(OTG_CTRL) & OTG_CTRL_MASK; +		l &= ~(OTG_XCEIV_OUTPUTS|OTG_CTRL_BITS); +		l |= OTG_ID; +		omap_writel(l, OTG_CTRL); +	} + +	power_up(isp); +	isp->phy.state = OTG_STATE_B_IDLE; + +	if (machine_is_omap_h2() || machine_is_omap_h3()) +		isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_DAT_SE0); + +	isp1301_set_bits(isp, ISP1301_INTERRUPT_RISING, +		INTR_SESS_VLD); +	isp1301_set_bits(isp, ISP1301_INTERRUPT_FALLING, +		INTR_VBUS_VLD); +	dev_info(&isp->client->dev, "B-Peripheral sessions ok\n"); +	dump_regs(isp, __func__); + +	/* If this has a Mini-AB connector, this mode is highly +	 * nonstandard ... but can be handy for testing, so long +	 * as you don't plug a Mini-A cable into the jack. +	 */ +	if (isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE) & INTR_VBUS_VLD) +		b_peripheral(isp); + +	return 0; + +#else +	dev_dbg(&isp->client->dev, "peripheral sessions not allowed\n"); +	return -EINVAL; +#endif +} + + +/*-------------------------------------------------------------------------*/ + +static int +isp1301_set_power(struct usb_phy *dev, unsigned mA) +{ +	if (!the_transceiver) +		return -ENODEV; +	if (dev->state == OTG_STATE_B_PERIPHERAL) +		enable_vbus_draw(the_transceiver, mA); +	return 0; +} + +static int +isp1301_start_srp(struct usb_otg *otg) +{ +	struct isp1301	*isp = container_of(otg->phy, struct isp1301, phy); +	u32		otg_ctrl; + +	if (!otg || isp != the_transceiver +			|| isp->phy.state != OTG_STATE_B_IDLE) +		return -ENODEV; + +	otg_ctrl = omap_readl(OTG_CTRL); +	if (!(otg_ctrl & OTG_BSESSEND)) +		return -EINVAL; + +	otg_ctrl |= OTG_B_BUSREQ; +	otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_MASK; +	omap_writel(otg_ctrl, OTG_CTRL); +	isp->phy.state = OTG_STATE_B_SRP_INIT; + +	pr_debug("otg: SRP, %s ... %06x\n", state_name(isp), +			omap_readl(OTG_CTRL)); +#ifdef	CONFIG_USB_OTG +	check_state(isp, __func__); +#endif +	return 0; +} + +static int +isp1301_start_hnp(struct usb_otg *otg) +{ +#ifdef	CONFIG_USB_OTG +	struct isp1301	*isp = container_of(otg->phy, struct isp1301, phy); +	u32 l; + +	if (!otg || isp != the_transceiver) +		return -ENODEV; +	if (otg->default_a && (otg->host == NULL || !otg->host->b_hnp_enable)) +		return -ENOTCONN; +	if (!otg->default_a && (otg->gadget == NULL +			|| !otg->gadget->b_hnp_enable)) +		return -ENOTCONN; + +	/* We want hardware to manage most HNP protocol timings. +	 * So do this part as early as possible... +	 */ +	switch (isp->phy.state) { +	case OTG_STATE_B_HOST: +		isp->phy.state = OTG_STATE_B_PERIPHERAL; +		/* caller will suspend next */ +		break; +	case OTG_STATE_A_HOST: +#if 0 +		/* autoconnect mode avoids irq latency bugs */ +		isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, +				MC1_BDIS_ACON_EN); +#endif +		/* caller must suspend then clear A_BUSREQ */ +		usb_gadget_vbus_connect(otg->gadget); +		l = omap_readl(OTG_CTRL); +		l |= OTG_A_SETB_HNPEN; +		omap_writel(l, OTG_CTRL); + +		break; +	case OTG_STATE_A_PERIPHERAL: +		/* initiated by B-Host suspend */ +		break; +	default: +		return -EILSEQ; +	} +	pr_debug("otg: HNP %s, %06x ...\n", +		state_name(isp), omap_readl(OTG_CTRL)); +	check_state(isp, __func__); +	return 0; +#else +	/* srp-only */ +	return -EINVAL; +#endif +} + +/*-------------------------------------------------------------------------*/ + +static int +isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ +	int			status; +	struct isp1301		*isp; + +	if (the_transceiver) +		return 0; + +	isp = kzalloc(sizeof *isp, GFP_KERNEL); +	if (!isp) +		return 0; + +	isp->phy.otg = kzalloc(sizeof *isp->phy.otg, GFP_KERNEL); +	if (!isp->phy.otg) { +		kfree(isp); +		return 0; +	} + +	INIT_WORK(&isp->work, isp1301_work); +	init_timer(&isp->timer); +	isp->timer.function = isp1301_timer; +	isp->timer.data = (unsigned long) isp; + +	i2c_set_clientdata(i2c, isp); +	isp->client = i2c; + +	/* verify the chip (shouldn't be necessary) */ +	status = isp1301_get_u16(isp, ISP1301_VENDOR_ID); +	if (status != I2C_VENDOR_ID_PHILIPS) { +		dev_dbg(&i2c->dev, "not philips id: %d\n", status); +		goto fail; +	} +	status = isp1301_get_u16(isp, ISP1301_PRODUCT_ID); +	if (status != I2C_PRODUCT_ID_PHILIPS_1301) { +		dev_dbg(&i2c->dev, "not isp1301, %d\n", status); +		goto fail; +	} +	isp->i2c_release = i2c->dev.release; +	i2c->dev.release = isp1301_release; + +	/* initial development used chiprev 2.00 */ +	status = i2c_smbus_read_word_data(i2c, ISP1301_BCD_DEVICE); +	dev_info(&i2c->dev, "chiprev %x.%02x, driver " DRIVER_VERSION "\n", +		status >> 8, status & 0xff); + +	/* make like power-on reset */ +	isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_1, MC1_MASK); + +	isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_BI_DI); +	isp1301_clear_bits(isp, ISP1301_MODE_CONTROL_2, ~MC2_BI_DI); + +	isp1301_set_bits(isp, ISP1301_OTG_CONTROL_1, +				OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN); +	isp1301_clear_bits(isp, ISP1301_OTG_CONTROL_1, +				~(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN)); + +	isp1301_clear_bits(isp, ISP1301_INTERRUPT_LATCH, ~0); +	isp1301_clear_bits(isp, ISP1301_INTERRUPT_FALLING, ~0); +	isp1301_clear_bits(isp, ISP1301_INTERRUPT_RISING, ~0); + +#ifdef	CONFIG_USB_OTG +	status = otg_bind(isp); +	if (status < 0) { +		dev_dbg(&i2c->dev, "can't bind OTG\n"); +		goto fail; +	} +#endif + +	if (machine_is_omap_h2()) { +		/* full speed signaling by default */ +		isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, +			MC1_SPEED); +		isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, +			MC2_SPD_SUSP_CTRL); + +		/* IRQ wired at M14 */ +		omap_cfg_reg(M14_1510_GPIO2); +		if (gpio_request(2, "isp1301") == 0) +			gpio_direction_input(2); +		isp->irq_type = IRQF_TRIGGER_FALLING; +	} + +	status = request_irq(i2c->irq, isp1301_irq, +			isp->irq_type, DRIVER_NAME, isp); +	if (status < 0) { +		dev_dbg(&i2c->dev, "can't get IRQ %d, err %d\n", +				i2c->irq, status); +		goto fail; +	} + +	isp->phy.dev = &i2c->dev; +	isp->phy.label = DRIVER_NAME; +	isp->phy.set_power = isp1301_set_power, + +	isp->phy.otg->phy = &isp->phy; +	isp->phy.otg->set_host = isp1301_set_host, +	isp->phy.otg->set_peripheral = isp1301_set_peripheral, +	isp->phy.otg->start_srp = isp1301_start_srp, +	isp->phy.otg->start_hnp = isp1301_start_hnp, + +	enable_vbus_draw(isp, 0); +	power_down(isp); +	the_transceiver = isp; + +#ifdef	CONFIG_USB_OTG +	update_otg1(isp, isp1301_get_u8(isp, ISP1301_INTERRUPT_SOURCE)); +	update_otg2(isp, isp1301_get_u8(isp, ISP1301_OTG_STATUS)); +#endif + +	dump_regs(isp, __func__); + +#ifdef	VERBOSE +	mod_timer(&isp->timer, jiffies + TIMER_JIFFIES); +	dev_dbg(&i2c->dev, "scheduled timer, %d min\n", TIMER_MINUTES); +#endif + +	status = usb_add_phy(&isp->phy, USB_PHY_TYPE_USB2); +	if (status < 0) +		dev_err(&i2c->dev, "can't register transceiver, %d\n", +			status); + +	return 0; + +fail: +	kfree(isp->phy.otg); +	kfree(isp); +	return -ENODEV; +} + +static const struct i2c_device_id isp1301_id[] = { +	{ "isp1301_omap", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, isp1301_id); + +static struct i2c_driver isp1301_driver = { +	.driver = { +		.name	= "isp1301_omap", +	}, +	.probe		= isp1301_probe, +	.remove		= isp1301_remove, +	.id_table	= isp1301_id, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init isp_init(void) +{ +	return i2c_add_driver(&isp1301_driver); +} +subsys_initcall(isp_init); + +static void __exit isp_exit(void) +{ +	if (the_transceiver) +		usb_remove_phy(&the_transceiver->phy); +	i2c_del_driver(&isp1301_driver); +} +module_exit(isp_exit); + diff --git a/drivers/usb/phy/phy-isp1301.c b/drivers/usb/phy/phy-isp1301.c new file mode 100644 index 00000000000..225ae6c97ee --- /dev/null +++ b/drivers/usb/phy/phy-isp1301.c @@ -0,0 +1,162 @@ +/* + * NXP ISP1301 USB transceiver driver + * + * Copyright (C) 2012 Roland Stigge + * + * Author: Roland Stigge <stigge@antcom.de> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +#include <linux/usb/phy.h> +#include <linux/usb/isp1301.h> + +#define DRV_NAME		"isp1301" + +struct isp1301 { +	struct usb_phy		phy; +	struct mutex		mutex; + +	struct i2c_client	*client; +}; + +#define phy_to_isp(p)		(container_of((p), struct isp1301, phy)) + +static const struct i2c_device_id isp1301_id[] = { +	{ "isp1301", 0 }, +	{ } +}; + +static struct i2c_client *isp1301_i2c_client; + +static int __isp1301_write(struct isp1301 *isp, u8 reg, u8 value, u8 clear) +{ +	return i2c_smbus_write_byte_data(isp->client, reg | clear, value); +} + +static int isp1301_write(struct isp1301 *isp, u8 reg, u8 value) +{ +	return __isp1301_write(isp, reg, value, 0); +} + +static int isp1301_clear(struct isp1301 *isp, u8 reg, u8 value) +{ +	return __isp1301_write(isp, reg, value, ISP1301_I2C_REG_CLEAR_ADDR); +} + +static int isp1301_phy_init(struct usb_phy *phy) +{ +	struct isp1301 *isp = phy_to_isp(phy); + +	/* Disable transparent UART mode first */ +	isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_UART_EN); +	isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, ~MC1_SPEED_REG); +	isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_SPEED_REG); +	isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_2, ~0); +	isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_2, (MC2_BI_DI | MC2_PSW_EN +				| MC2_SPD_SUSP_CTRL)); + +	isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, ~0); +	isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_DAT_SE0); +	isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLDOWN +				| OTG1_DP_PULLDOWN)); +	isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLUP +				| OTG1_DP_PULLUP)); + +	/* mask all interrupts */ +	isp1301_clear(isp, ISP1301_I2C_INTERRUPT_LATCH, ~0); +	isp1301_clear(isp, ISP1301_I2C_INTERRUPT_FALLING, ~0); +	isp1301_clear(isp, ISP1301_I2C_INTERRUPT_RISING, ~0); + +	return 0; +} + +static int isp1301_phy_set_vbus(struct usb_phy *phy, int on) +{ +	struct isp1301 *isp = phy_to_isp(phy); + +	if (on) +		isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV); +	else +		isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV); + +	return 0; +} + +static int isp1301_probe(struct i2c_client *client, +			 const struct i2c_device_id *i2c_id) +{ +	struct isp1301 *isp; +	struct usb_phy *phy; + +	isp = devm_kzalloc(&client->dev, sizeof(*isp), GFP_KERNEL); +	if (!isp) +		return -ENOMEM; + +	isp->client = client; +	mutex_init(&isp->mutex); + +	phy = &isp->phy; +	phy->label = DRV_NAME; +	phy->init = isp1301_phy_init; +	phy->set_vbus = isp1301_phy_set_vbus; +	phy->type = USB_PHY_TYPE_USB2; + +	i2c_set_clientdata(client, isp); +	usb_add_phy_dev(phy); + +	isp1301_i2c_client = client; + +	return 0; +} + +static int isp1301_remove(struct i2c_client *client) +{ +	struct isp1301 *isp = i2c_get_clientdata(client); + +	usb_remove_phy(&isp->phy); +	isp1301_i2c_client = NULL; + +	return 0; +} + +static struct i2c_driver isp1301_driver = { +	.driver = { +		.name = DRV_NAME, +	}, +	.probe = isp1301_probe, +	.remove = isp1301_remove, +	.id_table = isp1301_id, +}; + +module_i2c_driver(isp1301_driver); + +static int match(struct device *dev, void *data) +{ +	struct device_node *node = (struct device_node *)data; +	return (dev->of_node == node) && +		(dev->driver == &isp1301_driver.driver); +} + +struct i2c_client *isp1301_get_client(struct device_node *node) +{ +	if (node) { /* reference of ISP1301 I2C node via DT */ +		struct device *dev = bus_find_device(&i2c_bus_type, NULL, +						     node, match); +		if (!dev) +			return NULL; +		return to_i2c_client(dev); +	} else { /* non-DT: only one ISP1301 chip supported */ +		return isp1301_i2c_client; +	} +} +EXPORT_SYMBOL_GPL(isp1301_get_client); + +MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); +MODULE_DESCRIPTION("NXP ISP1301 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c new file mode 100644 index 00000000000..749fbf41fb6 --- /dev/null +++ b/drivers/usb/phy/phy-msm-usb.c @@ -0,0 +1,1762 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/pm_runtime.h> + +#include <linux/usb.h> +#include <linux/usb/otg.h> +#include <linux/usb/ulpi.h> +#include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> +#include <linux/usb/msm_hsusb.h> +#include <linux/usb/msm_hsusb_hw.h> +#include <linux/regulator/consumer.h> + +#include <mach/clk.h> + +#define MSM_USB_BASE	(motg->regs) +#define DRIVER_NAME	"msm_otg" + +#define ULPI_IO_TIMEOUT_USEC	(10 * 1000) + +#define USB_PHY_3P3_VOL_MIN	3050000 /* uV */ +#define USB_PHY_3P3_VOL_MAX	3300000 /* uV */ +#define USB_PHY_3P3_HPM_LOAD	50000	/* uA */ +#define USB_PHY_3P3_LPM_LOAD	4000	/* uA */ + +#define USB_PHY_1P8_VOL_MIN	1800000 /* uV */ +#define USB_PHY_1P8_VOL_MAX	1800000 /* uV */ +#define USB_PHY_1P8_HPM_LOAD	50000	/* uA */ +#define USB_PHY_1P8_LPM_LOAD	4000	/* uA */ + +#define USB_PHY_VDD_DIG_VOL_MIN	1000000 /* uV */ +#define USB_PHY_VDD_DIG_VOL_MAX	1320000 /* uV */ + +static struct regulator *hsusb_3p3; +static struct regulator *hsusb_1p8; +static struct regulator *hsusb_vddcx; + +static int msm_hsusb_init_vddcx(struct msm_otg *motg, int init) +{ +	int ret = 0; + +	if (init) { +		hsusb_vddcx = regulator_get(motg->phy.dev, "HSUSB_VDDCX"); +		if (IS_ERR(hsusb_vddcx)) { +			dev_err(motg->phy.dev, "unable to get hsusb vddcx\n"); +			return PTR_ERR(hsusb_vddcx); +		} + +		ret = regulator_set_voltage(hsusb_vddcx, +				USB_PHY_VDD_DIG_VOL_MIN, +				USB_PHY_VDD_DIG_VOL_MAX); +		if (ret) { +			dev_err(motg->phy.dev, "unable to set the voltage " +					"for hsusb vddcx\n"); +			regulator_put(hsusb_vddcx); +			return ret; +		} + +		ret = regulator_enable(hsusb_vddcx); +		if (ret) { +			dev_err(motg->phy.dev, "unable to enable hsusb vddcx\n"); +			regulator_put(hsusb_vddcx); +		} +	} else { +		ret = regulator_set_voltage(hsusb_vddcx, 0, +			USB_PHY_VDD_DIG_VOL_MAX); +		if (ret) +			dev_err(motg->phy.dev, "unable to set the voltage " +					"for hsusb vddcx\n"); +		ret = regulator_disable(hsusb_vddcx); +		if (ret) +			dev_err(motg->phy.dev, "unable to disable hsusb vddcx\n"); + +		regulator_put(hsusb_vddcx); +	} + +	return ret; +} + +static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) +{ +	int rc = 0; + +	if (init) { +		hsusb_3p3 = regulator_get(motg->phy.dev, "HSUSB_3p3"); +		if (IS_ERR(hsusb_3p3)) { +			dev_err(motg->phy.dev, "unable to get hsusb 3p3\n"); +			return PTR_ERR(hsusb_3p3); +		} + +		rc = regulator_set_voltage(hsusb_3p3, USB_PHY_3P3_VOL_MIN, +				USB_PHY_3P3_VOL_MAX); +		if (rc) { +			dev_err(motg->phy.dev, "unable to set voltage level " +					"for hsusb 3p3\n"); +			goto put_3p3; +		} +		rc = regulator_enable(hsusb_3p3); +		if (rc) { +			dev_err(motg->phy.dev, "unable to enable the hsusb 3p3\n"); +			goto put_3p3; +		} +		hsusb_1p8 = regulator_get(motg->phy.dev, "HSUSB_1p8"); +		if (IS_ERR(hsusb_1p8)) { +			dev_err(motg->phy.dev, "unable to get hsusb 1p8\n"); +			rc = PTR_ERR(hsusb_1p8); +			goto disable_3p3; +		} +		rc = regulator_set_voltage(hsusb_1p8, USB_PHY_1P8_VOL_MIN, +				USB_PHY_1P8_VOL_MAX); +		if (rc) { +			dev_err(motg->phy.dev, "unable to set voltage level " +					"for hsusb 1p8\n"); +			goto put_1p8; +		} +		rc = regulator_enable(hsusb_1p8); +		if (rc) { +			dev_err(motg->phy.dev, "unable to enable the hsusb 1p8\n"); +			goto put_1p8; +		} + +		return 0; +	} + +	regulator_disable(hsusb_1p8); +put_1p8: +	regulator_put(hsusb_1p8); +disable_3p3: +	regulator_disable(hsusb_3p3); +put_3p3: +	regulator_put(hsusb_3p3); +	return rc; +} + +#ifdef CONFIG_PM_SLEEP +#define USB_PHY_SUSP_DIG_VOL  500000 +static int msm_hsusb_config_vddcx(int high) +{ +	int max_vol = USB_PHY_VDD_DIG_VOL_MAX; +	int min_vol; +	int ret; + +	if (high) +		min_vol = USB_PHY_VDD_DIG_VOL_MIN; +	else +		min_vol = USB_PHY_SUSP_DIG_VOL; + +	ret = regulator_set_voltage(hsusb_vddcx, min_vol, max_vol); +	if (ret) { +		pr_err("%s: unable to set the voltage for regulator " +			"HSUSB_VDDCX\n", __func__); +		return ret; +	} + +	pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol); + +	return ret; +} +#endif + +static int msm_hsusb_ldo_set_mode(int on) +{ +	int ret = 0; + +	if (!hsusb_1p8 || IS_ERR(hsusb_1p8)) { +		pr_err("%s: HSUSB_1p8 is not initialized\n", __func__); +		return -ENODEV; +	} + +	if (!hsusb_3p3 || IS_ERR(hsusb_3p3)) { +		pr_err("%s: HSUSB_3p3 is not initialized\n", __func__); +		return -ENODEV; +	} + +	if (on) { +		ret = regulator_set_optimum_mode(hsusb_1p8, +				USB_PHY_1P8_HPM_LOAD); +		if (ret < 0) { +			pr_err("%s: Unable to set HPM of the regulator " +				"HSUSB_1p8\n", __func__); +			return ret; +		} +		ret = regulator_set_optimum_mode(hsusb_3p3, +				USB_PHY_3P3_HPM_LOAD); +		if (ret < 0) { +			pr_err("%s: Unable to set HPM of the regulator " +				"HSUSB_3p3\n", __func__); +			regulator_set_optimum_mode(hsusb_1p8, +				USB_PHY_1P8_LPM_LOAD); +			return ret; +		} +	} else { +		ret = regulator_set_optimum_mode(hsusb_1p8, +				USB_PHY_1P8_LPM_LOAD); +		if (ret < 0) +			pr_err("%s: Unable to set LPM of the regulator " +				"HSUSB_1p8\n", __func__); +		ret = regulator_set_optimum_mode(hsusb_3p3, +				USB_PHY_3P3_LPM_LOAD); +		if (ret < 0) +			pr_err("%s: Unable to set LPM of the regulator " +				"HSUSB_3p3\n", __func__); +	} + +	pr_debug("reg (%s)\n", on ? "HPM" : "LPM"); +	return ret < 0 ? ret : 0; +} + +static int ulpi_read(struct usb_phy *phy, u32 reg) +{ +	struct msm_otg *motg = container_of(phy, struct msm_otg, phy); +	int cnt = 0; + +	/* initiate read operation */ +	writel(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), +	       USB_ULPI_VIEWPORT); + +	/* wait for completion */ +	while (cnt < ULPI_IO_TIMEOUT_USEC) { +		if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN)) +			break; +		udelay(1); +		cnt++; +	} + +	if (cnt >= ULPI_IO_TIMEOUT_USEC) { +		dev_err(phy->dev, "ulpi_read: timeout %08x\n", +			readl(USB_ULPI_VIEWPORT)); +		return -ETIMEDOUT; +	} +	return ULPI_DATA_READ(readl(USB_ULPI_VIEWPORT)); +} + +static int ulpi_write(struct usb_phy *phy, u32 val, u32 reg) +{ +	struct msm_otg *motg = container_of(phy, struct msm_otg, phy); +	int cnt = 0; + +	/* initiate write operation */ +	writel(ULPI_RUN | ULPI_WRITE | +	       ULPI_ADDR(reg) | ULPI_DATA(val), +	       USB_ULPI_VIEWPORT); + +	/* wait for completion */ +	while (cnt < ULPI_IO_TIMEOUT_USEC) { +		if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN)) +			break; +		udelay(1); +		cnt++; +	} + +	if (cnt >= ULPI_IO_TIMEOUT_USEC) { +		dev_err(phy->dev, "ulpi_write: timeout\n"); +		return -ETIMEDOUT; +	} +	return 0; +} + +static struct usb_phy_io_ops msm_otg_io_ops = { +	.read = ulpi_read, +	.write = ulpi_write, +}; + +static void ulpi_init(struct msm_otg *motg) +{ +	struct msm_otg_platform_data *pdata = motg->pdata; +	int *seq = pdata->phy_init_seq; + +	if (!seq) +		return; + +	while (seq[0] >= 0) { +		dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n", +				seq[0], seq[1]); +		ulpi_write(&motg->phy, seq[0], seq[1]); +		seq += 2; +	} +} + +static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert) +{ +	int ret; + +	if (assert) { +		ret = clk_reset(motg->clk, CLK_RESET_ASSERT); +		if (ret) +			dev_err(motg->phy.dev, "usb hs_clk assert failed\n"); +	} else { +		ret = clk_reset(motg->clk, CLK_RESET_DEASSERT); +		if (ret) +			dev_err(motg->phy.dev, "usb hs_clk deassert failed\n"); +	} +	return ret; +} + +static int msm_otg_phy_clk_reset(struct msm_otg *motg) +{ +	int ret; + +	ret = clk_reset(motg->phy_reset_clk, CLK_RESET_ASSERT); +	if (ret) { +		dev_err(motg->phy.dev, "usb phy clk assert failed\n"); +		return ret; +	} +	usleep_range(10000, 12000); +	ret = clk_reset(motg->phy_reset_clk, CLK_RESET_DEASSERT); +	if (ret) +		dev_err(motg->phy.dev, "usb phy clk deassert failed\n"); +	return ret; +} + +static int msm_otg_phy_reset(struct msm_otg *motg) +{ +	u32 val; +	int ret; +	int retries; + +	ret = msm_otg_link_clk_reset(motg, 1); +	if (ret) +		return ret; +	ret = msm_otg_phy_clk_reset(motg); +	if (ret) +		return ret; +	ret = msm_otg_link_clk_reset(motg, 0); +	if (ret) +		return ret; + +	val = readl(USB_PORTSC) & ~PORTSC_PTS_MASK; +	writel(val | PORTSC_PTS_ULPI, USB_PORTSC); + +	for (retries = 3; retries > 0; retries--) { +		ret = ulpi_write(&motg->phy, ULPI_FUNC_CTRL_SUSPENDM, +				ULPI_CLR(ULPI_FUNC_CTRL)); +		if (!ret) +			break; +		ret = msm_otg_phy_clk_reset(motg); +		if (ret) +			return ret; +	} +	if (!retries) +		return -ETIMEDOUT; + +	/* This reset calibrates the phy, if the above write succeeded */ +	ret = msm_otg_phy_clk_reset(motg); +	if (ret) +		return ret; + +	for (retries = 3; retries > 0; retries--) { +		ret = ulpi_read(&motg->phy, ULPI_DEBUG); +		if (ret != -ETIMEDOUT) +			break; +		ret = msm_otg_phy_clk_reset(motg); +		if (ret) +			return ret; +	} +	if (!retries) +		return -ETIMEDOUT; + +	dev_info(motg->phy.dev, "phy_reset: success\n"); +	return 0; +} + +#define LINK_RESET_TIMEOUT_USEC		(250 * 1000) +static int msm_otg_reset(struct usb_phy *phy) +{ +	struct msm_otg *motg = container_of(phy, struct msm_otg, phy); +	struct msm_otg_platform_data *pdata = motg->pdata; +	int cnt = 0; +	int ret; +	u32 val = 0; +	u32 ulpi_val = 0; + +	ret = msm_otg_phy_reset(motg); +	if (ret) { +		dev_err(phy->dev, "phy_reset failed\n"); +		return ret; +	} + +	ulpi_init(motg); + +	writel(USBCMD_RESET, USB_USBCMD); +	while (cnt < LINK_RESET_TIMEOUT_USEC) { +		if (!(readl(USB_USBCMD) & USBCMD_RESET)) +			break; +		udelay(1); +		cnt++; +	} +	if (cnt >= LINK_RESET_TIMEOUT_USEC) +		return -ETIMEDOUT; + +	/* select ULPI phy */ +	writel(0x80000000, USB_PORTSC); + +	msleep(100); + +	writel(0x0, USB_AHBBURST); +	writel(0x00, USB_AHBMODE); + +	if (pdata->otg_control == OTG_PHY_CONTROL) { +		val = readl(USB_OTGSC); +		if (pdata->mode == USB_OTG) { +			ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; +			val |= OTGSC_IDIE | OTGSC_BSVIE; +		} else if (pdata->mode == USB_PERIPHERAL) { +			ulpi_val = ULPI_INT_SESS_VALID; +			val |= OTGSC_BSVIE; +		} +		writel(val, USB_OTGSC); +		ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_RISE); +		ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL); +	} + +	return 0; +} + +#define PHY_SUSPEND_TIMEOUT_USEC	(500 * 1000) +#define PHY_RESUME_TIMEOUT_USEC	(100 * 1000) + +#ifdef CONFIG_PM_SLEEP +static int msm_otg_suspend(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	struct usb_bus *bus = phy->otg->host; +	struct msm_otg_platform_data *pdata = motg->pdata; +	int cnt = 0; + +	if (atomic_read(&motg->in_lpm)) +		return 0; + +	disable_irq(motg->irq); +	/* +	 * Chipidea 45-nm PHY suspend sequence: +	 * +	 * Interrupt Latch Register auto-clear feature is not present +	 * in all PHY versions. Latch register is clear on read type. +	 * Clear latch register to avoid spurious wakeup from +	 * low power mode (LPM). +	 * +	 * PHY comparators are disabled when PHY enters into low power +	 * mode (LPM). Keep PHY comparators ON in LPM only when we expect +	 * VBUS/Id notifications from USB PHY. Otherwise turn off USB +	 * PHY comparators. This save significant amount of power. +	 * +	 * PLL is not turned off when PHY enters into low power mode (LPM). +	 * Disable PLL for maximum power savings. +	 */ + +	if (motg->pdata->phy_type == CI_45NM_INTEGRATED_PHY) { +		ulpi_read(phy, 0x14); +		if (pdata->otg_control == OTG_PHY_CONTROL) +			ulpi_write(phy, 0x01, 0x30); +		ulpi_write(phy, 0x08, 0x09); +	} + +	/* +	 * PHY may take some time or even fail to enter into low power +	 * mode (LPM). Hence poll for 500 msec and reset the PHY and link +	 * in failure case. +	 */ +	writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); +	while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { +		if (readl(USB_PORTSC) & PORTSC_PHCD) +			break; +		udelay(1); +		cnt++; +	} + +	if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) { +		dev_err(phy->dev, "Unable to suspend PHY\n"); +		msm_otg_reset(phy); +		enable_irq(motg->irq); +		return -ETIMEDOUT; +	} + +	/* +	 * PHY has capability to generate interrupt asynchronously in low +	 * power mode (LPM). This interrupt is level triggered. So USB IRQ +	 * line must be disabled till async interrupt enable bit is cleared +	 * in USBCMD register. Assert STP (ULPI interface STOP signal) to +	 * block data communication from PHY. +	 */ +	writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); + +	if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && +			motg->pdata->otg_control == OTG_PMIC_CONTROL) +		writel(readl(USB_PHY_CTRL) | PHY_RETEN, USB_PHY_CTRL); + +	clk_disable(motg->pclk); +	clk_disable(motg->clk); +	if (motg->core_clk) +		clk_disable(motg->core_clk); + +	if (!IS_ERR(motg->pclk_src)) +		clk_disable(motg->pclk_src); + +	if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && +			motg->pdata->otg_control == OTG_PMIC_CONTROL) { +		msm_hsusb_ldo_set_mode(0); +		msm_hsusb_config_vddcx(0); +	} + +	if (device_may_wakeup(phy->dev)) +		enable_irq_wake(motg->irq); +	if (bus) +		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + +	atomic_set(&motg->in_lpm, 1); +	enable_irq(motg->irq); + +	dev_info(phy->dev, "USB in low power mode\n"); + +	return 0; +} + +static int msm_otg_resume(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	struct usb_bus *bus = phy->otg->host; +	int cnt = 0; +	unsigned temp; + +	if (!atomic_read(&motg->in_lpm)) +		return 0; + +	if (!IS_ERR(motg->pclk_src)) +		clk_enable(motg->pclk_src); + +	clk_enable(motg->pclk); +	clk_enable(motg->clk); +	if (motg->core_clk) +		clk_enable(motg->core_clk); + +	if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && +			motg->pdata->otg_control == OTG_PMIC_CONTROL) { +		msm_hsusb_ldo_set_mode(1); +		msm_hsusb_config_vddcx(1); +		writel(readl(USB_PHY_CTRL) & ~PHY_RETEN, USB_PHY_CTRL); +	} + +	temp = readl(USB_USBCMD); +	temp &= ~ASYNC_INTR_CTRL; +	temp &= ~ULPI_STP_CTRL; +	writel(temp, USB_USBCMD); + +	/* +	 * PHY comes out of low power mode (LPM) in case of wakeup +	 * from asynchronous interrupt. +	 */ +	if (!(readl(USB_PORTSC) & PORTSC_PHCD)) +		goto skip_phy_resume; + +	writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC); +	while (cnt < PHY_RESUME_TIMEOUT_USEC) { +		if (!(readl(USB_PORTSC) & PORTSC_PHCD)) +			break; +		udelay(1); +		cnt++; +	} + +	if (cnt >= PHY_RESUME_TIMEOUT_USEC) { +		/* +		 * This is a fatal error. Reset the link and +		 * PHY. USB state can not be restored. Re-insertion +		 * of USB cable is the only way to get USB working. +		 */ +		dev_err(phy->dev, "Unable to resume USB." +				"Re-plugin the cable\n"); +		msm_otg_reset(phy); +	} + +skip_phy_resume: +	if (device_may_wakeup(phy->dev)) +		disable_irq_wake(motg->irq); +	if (bus) +		set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + +	atomic_set(&motg->in_lpm, 0); + +	if (motg->async_int) { +		motg->async_int = 0; +		pm_runtime_put(phy->dev); +		enable_irq(motg->irq); +	} + +	dev_info(phy->dev, "USB exited from low power mode\n"); + +	return 0; +} +#endif + +static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA) +{ +	if (motg->cur_power == mA) +		return; + +	/* TODO: Notify PMIC about available current */ +	dev_info(motg->phy.dev, "Avail curr from USB = %u\n", mA); +	motg->cur_power = mA; +} + +static int msm_otg_set_power(struct usb_phy *phy, unsigned mA) +{ +	struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + +	/* +	 * Gadget driver uses set_power method to notify about the +	 * available current based on suspend/configured states. +	 * +	 * IDEV_CHG can be drawn irrespective of suspend/un-configured +	 * states when CDP/ACA is connected. +	 */ +	if (motg->chg_type == USB_SDP_CHARGER) +		msm_otg_notify_charger(motg, mA); + +	return 0; +} + +static void msm_otg_start_host(struct usb_phy *phy, int on) +{ +	struct msm_otg *motg = container_of(phy, struct msm_otg, phy); +	struct msm_otg_platform_data *pdata = motg->pdata; +	struct usb_hcd *hcd; + +	if (!phy->otg->host) +		return; + +	hcd = bus_to_hcd(phy->otg->host); + +	if (on) { +		dev_dbg(phy->dev, "host on\n"); + +		if (pdata->vbus_power) +			pdata->vbus_power(1); +		/* +		 * Some boards have a switch cotrolled by gpio +		 * to enable/disable internal HUB. Enable internal +		 * HUB before kicking the host. +		 */ +		if (pdata->setup_gpio) +			pdata->setup_gpio(OTG_STATE_A_HOST); +#ifdef CONFIG_USB +		usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); +#endif +	} else { +		dev_dbg(phy->dev, "host off\n"); + +#ifdef CONFIG_USB +		usb_remove_hcd(hcd); +#endif +		if (pdata->setup_gpio) +			pdata->setup_gpio(OTG_STATE_UNDEFINED); +		if (pdata->vbus_power) +			pdata->vbus_power(0); +	} +} + +static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); +	struct usb_hcd *hcd; + +	/* +	 * Fail host registration if this board can support +	 * only peripheral configuration. +	 */ +	if (motg->pdata->mode == USB_PERIPHERAL) { +		dev_info(otg->phy->dev, "Host mode is not supported\n"); +		return -ENODEV; +	} + +	if (!host) { +		if (otg->phy->state == OTG_STATE_A_HOST) { +			pm_runtime_get_sync(otg->phy->dev); +			msm_otg_start_host(otg->phy, 0); +			otg->host = NULL; +			otg->phy->state = OTG_STATE_UNDEFINED; +			schedule_work(&motg->sm_work); +		} else { +			otg->host = NULL; +		} + +		return 0; +	} + +	hcd = bus_to_hcd(host); +	hcd->power_budget = motg->pdata->power_budget; + +	otg->host = host; +	dev_dbg(otg->phy->dev, "host driver registered w/ tranceiver\n"); + +	/* +	 * Kick the state machine work, if peripheral is not supported +	 * or peripheral is already registered with us. +	 */ +	if (motg->pdata->mode == USB_HOST || otg->gadget) { +		pm_runtime_get_sync(otg->phy->dev); +		schedule_work(&motg->sm_work); +	} + +	return 0; +} + +static void msm_otg_start_peripheral(struct usb_phy *phy, int on) +{ +	struct msm_otg *motg = container_of(phy, struct msm_otg, phy); +	struct msm_otg_platform_data *pdata = motg->pdata; + +	if (!phy->otg->gadget) +		return; + +	if (on) { +		dev_dbg(phy->dev, "gadget on\n"); +		/* +		 * Some boards have a switch cotrolled by gpio +		 * to enable/disable internal HUB. Disable internal +		 * HUB before kicking the gadget. +		 */ +		if (pdata->setup_gpio) +			pdata->setup_gpio(OTG_STATE_B_PERIPHERAL); +		usb_gadget_vbus_connect(phy->otg->gadget); +	} else { +		dev_dbg(phy->dev, "gadget off\n"); +		usb_gadget_vbus_disconnect(phy->otg->gadget); +		if (pdata->setup_gpio) +			pdata->setup_gpio(OTG_STATE_UNDEFINED); +	} + +} + +static int msm_otg_set_peripheral(struct usb_otg *otg, +					struct usb_gadget *gadget) +{ +	struct msm_otg *motg = container_of(otg->phy, struct msm_otg, phy); + +	/* +	 * Fail peripheral registration if this board can support +	 * only host configuration. +	 */ +	if (motg->pdata->mode == USB_HOST) { +		dev_info(otg->phy->dev, "Peripheral mode is not supported\n"); +		return -ENODEV; +	} + +	if (!gadget) { +		if (otg->phy->state == OTG_STATE_B_PERIPHERAL) { +			pm_runtime_get_sync(otg->phy->dev); +			msm_otg_start_peripheral(otg->phy, 0); +			otg->gadget = NULL; +			otg->phy->state = OTG_STATE_UNDEFINED; +			schedule_work(&motg->sm_work); +		} else { +			otg->gadget = NULL; +		} + +		return 0; +	} +	otg->gadget = gadget; +	dev_dbg(otg->phy->dev, "peripheral driver registered w/ tranceiver\n"); + +	/* +	 * Kick the state machine work, if host is not supported +	 * or host is already registered with us. +	 */ +	if (motg->pdata->mode == USB_PERIPHERAL || otg->host) { +		pm_runtime_get_sync(otg->phy->dev); +		schedule_work(&motg->sm_work); +	} + +	return 0; +} + +static bool msm_chg_check_secondary_det(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 chg_det; +	bool ret = false; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		ret = chg_det & (1 << 4); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x87); +		ret = chg_det & 1; +		break; +	default: +		break; +	} +	return ret; +} + +static void msm_chg_enable_secondary_det(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 chg_det; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		/* Turn off charger block */ +		chg_det |= ~(1 << 1); +		ulpi_write(phy, chg_det, 0x34); +		udelay(20); +		/* control chg block via ULPI */ +		chg_det &= ~(1 << 3); +		ulpi_write(phy, chg_det, 0x34); +		/* put it in host mode for enabling D- source */ +		chg_det &= ~(1 << 2); +		ulpi_write(phy, chg_det, 0x34); +		/* Turn on chg detect block */ +		chg_det &= ~(1 << 1); +		ulpi_write(phy, chg_det, 0x34); +		udelay(20); +		/* enable chg detection */ +		chg_det &= ~(1 << 0); +		ulpi_write(phy, chg_det, 0x34); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		/* +		 * Configure DM as current source, DP as current sink +		 * and enable battery charging comparators. +		 */ +		ulpi_write(phy, 0x8, 0x85); +		ulpi_write(phy, 0x2, 0x85); +		ulpi_write(phy, 0x1, 0x85); +		break; +	default: +		break; +	} +} + +static bool msm_chg_check_primary_det(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 chg_det; +	bool ret = false; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		ret = chg_det & (1 << 4); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x87); +		ret = chg_det & 1; +		break; +	default: +		break; +	} +	return ret; +} + +static void msm_chg_enable_primary_det(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 chg_det; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		/* enable chg detection */ +		chg_det &= ~(1 << 0); +		ulpi_write(phy, chg_det, 0x34); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		/* +		 * Configure DP as current source, DM as current sink +		 * and enable battery charging comparators. +		 */ +		ulpi_write(phy, 0x2, 0x85); +		ulpi_write(phy, 0x1, 0x85); +		break; +	default: +		break; +	} +} + +static bool msm_chg_check_dcd(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 line_state; +	bool ret = false; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		line_state = ulpi_read(phy, 0x15); +		ret = !(line_state & 1); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		line_state = ulpi_read(phy, 0x87); +		ret = line_state & 2; +		break; +	default: +		break; +	} +	return ret; +} + +static void msm_chg_disable_dcd(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 chg_det; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		chg_det &= ~(1 << 5); +		ulpi_write(phy, chg_det, 0x34); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		ulpi_write(phy, 0x10, 0x86); +		break; +	default: +		break; +	} +} + +static void msm_chg_enable_dcd(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 chg_det; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		/* Turn on D+ current source */ +		chg_det |= (1 << 5); +		ulpi_write(phy, chg_det, 0x34); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		/* Data contact detection enable */ +		ulpi_write(phy, 0x10, 0x85); +		break; +	default: +		break; +	} +} + +static void msm_chg_block_on(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 func_ctrl, chg_det; + +	/* put the controller in non-driving mode */ +	func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); +	func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; +	func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; +	ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		/* control chg block via ULPI */ +		chg_det &= ~(1 << 3); +		ulpi_write(phy, chg_det, 0x34); +		/* Turn on chg detect block */ +		chg_det &= ~(1 << 1); +		ulpi_write(phy, chg_det, 0x34); +		udelay(20); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		/* Clear charger detecting control bits */ +		ulpi_write(phy, 0x3F, 0x86); +		/* Clear alt interrupt latch and enable bits */ +		ulpi_write(phy, 0x1F, 0x92); +		ulpi_write(phy, 0x1F, 0x95); +		udelay(100); +		break; +	default: +		break; +	} +} + +static void msm_chg_block_off(struct msm_otg *motg) +{ +	struct usb_phy *phy = &motg->phy; +	u32 func_ctrl, chg_det; + +	switch (motg->pdata->phy_type) { +	case CI_45NM_INTEGRATED_PHY: +		chg_det = ulpi_read(phy, 0x34); +		/* Turn off charger block */ +		chg_det |= ~(1 << 1); +		ulpi_write(phy, chg_det, 0x34); +		break; +	case SNPS_28NM_INTEGRATED_PHY: +		/* Clear charger detecting control bits */ +		ulpi_write(phy, 0x3F, 0x86); +		/* Clear alt interrupt latch and enable bits */ +		ulpi_write(phy, 0x1F, 0x92); +		ulpi_write(phy, 0x1F, 0x95); +		break; +	default: +		break; +	} + +	/* put the controller in normal mode */ +	func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); +	func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; +	func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL; +	ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); +} + +#define MSM_CHG_DCD_POLL_TIME		(100 * HZ/1000) /* 100 msec */ +#define MSM_CHG_DCD_MAX_RETRIES		6 /* Tdcd_tmout = 6 * 100 msec */ +#define MSM_CHG_PRIMARY_DET_TIME	(40 * HZ/1000) /* TVDPSRC_ON */ +#define MSM_CHG_SECONDARY_DET_TIME	(40 * HZ/1000) /* TVDMSRC_ON */ +static void msm_chg_detect_work(struct work_struct *w) +{ +	struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work); +	struct usb_phy *phy = &motg->phy; +	bool is_dcd, tmout, vout; +	unsigned long delay; + +	dev_dbg(phy->dev, "chg detection work\n"); +	switch (motg->chg_state) { +	case USB_CHG_STATE_UNDEFINED: +		pm_runtime_get_sync(phy->dev); +		msm_chg_block_on(motg); +		msm_chg_enable_dcd(motg); +		motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; +		motg->dcd_retries = 0; +		delay = MSM_CHG_DCD_POLL_TIME; +		break; +	case USB_CHG_STATE_WAIT_FOR_DCD: +		is_dcd = msm_chg_check_dcd(motg); +		tmout = ++motg->dcd_retries == MSM_CHG_DCD_MAX_RETRIES; +		if (is_dcd || tmout) { +			msm_chg_disable_dcd(motg); +			msm_chg_enable_primary_det(motg); +			delay = MSM_CHG_PRIMARY_DET_TIME; +			motg->chg_state = USB_CHG_STATE_DCD_DONE; +		} else { +			delay = MSM_CHG_DCD_POLL_TIME; +		} +		break; +	case USB_CHG_STATE_DCD_DONE: +		vout = msm_chg_check_primary_det(motg); +		if (vout) { +			msm_chg_enable_secondary_det(motg); +			delay = MSM_CHG_SECONDARY_DET_TIME; +			motg->chg_state = USB_CHG_STATE_PRIMARY_DONE; +		} else { +			motg->chg_type = USB_SDP_CHARGER; +			motg->chg_state = USB_CHG_STATE_DETECTED; +			delay = 0; +		} +		break; +	case USB_CHG_STATE_PRIMARY_DONE: +		vout = msm_chg_check_secondary_det(motg); +		if (vout) +			motg->chg_type = USB_DCP_CHARGER; +		else +			motg->chg_type = USB_CDP_CHARGER; +		motg->chg_state = USB_CHG_STATE_SECONDARY_DONE; +		/* fall through */ +	case USB_CHG_STATE_SECONDARY_DONE: +		motg->chg_state = USB_CHG_STATE_DETECTED; +	case USB_CHG_STATE_DETECTED: +		msm_chg_block_off(motg); +		dev_dbg(phy->dev, "charger = %d\n", motg->chg_type); +		schedule_work(&motg->sm_work); +		return; +	default: +		return; +	} + +	schedule_delayed_work(&motg->chg_work, delay); +} + +/* + * We support OTG, Peripheral only and Host only configurations. In case + * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen + * via Id pin status or user request (debugfs). Id/BSV interrupts are not + * enabled when switch is controlled by user and default mode is supplied + * by board file, which can be changed by userspace later. + */ +static void msm_otg_init_sm(struct msm_otg *motg) +{ +	struct msm_otg_platform_data *pdata = motg->pdata; +	u32 otgsc = readl(USB_OTGSC); + +	switch (pdata->mode) { +	case USB_OTG: +		if (pdata->otg_control == OTG_PHY_CONTROL) { +			if (otgsc & OTGSC_ID) +				set_bit(ID, &motg->inputs); +			else +				clear_bit(ID, &motg->inputs); + +			if (otgsc & OTGSC_BSV) +				set_bit(B_SESS_VLD, &motg->inputs); +			else +				clear_bit(B_SESS_VLD, &motg->inputs); +		} else if (pdata->otg_control == OTG_USER_CONTROL) { +			if (pdata->default_mode == USB_HOST) { +				clear_bit(ID, &motg->inputs); +			} else if (pdata->default_mode == USB_PERIPHERAL) { +				set_bit(ID, &motg->inputs); +				set_bit(B_SESS_VLD, &motg->inputs); +			} else { +				set_bit(ID, &motg->inputs); +				clear_bit(B_SESS_VLD, &motg->inputs); +			} +		} +		break; +	case USB_HOST: +		clear_bit(ID, &motg->inputs); +		break; +	case USB_PERIPHERAL: +		set_bit(ID, &motg->inputs); +		if (otgsc & OTGSC_BSV) +			set_bit(B_SESS_VLD, &motg->inputs); +		else +			clear_bit(B_SESS_VLD, &motg->inputs); +		break; +	default: +		break; +	} +} + +static void msm_otg_sm_work(struct work_struct *w) +{ +	struct msm_otg *motg = container_of(w, struct msm_otg, sm_work); +	struct usb_otg *otg = motg->phy.otg; + +	switch (otg->phy->state) { +	case OTG_STATE_UNDEFINED: +		dev_dbg(otg->phy->dev, "OTG_STATE_UNDEFINED state\n"); +		msm_otg_reset(otg->phy); +		msm_otg_init_sm(motg); +		otg->phy->state = OTG_STATE_B_IDLE; +		/* FALL THROUGH */ +	case OTG_STATE_B_IDLE: +		dev_dbg(otg->phy->dev, "OTG_STATE_B_IDLE state\n"); +		if (!test_bit(ID, &motg->inputs) && otg->host) { +			/* disable BSV bit */ +			writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); +			msm_otg_start_host(otg->phy, 1); +			otg->phy->state = OTG_STATE_A_HOST; +		} else if (test_bit(B_SESS_VLD, &motg->inputs)) { +			switch (motg->chg_state) { +			case USB_CHG_STATE_UNDEFINED: +				msm_chg_detect_work(&motg->chg_work.work); +				break; +			case USB_CHG_STATE_DETECTED: +				switch (motg->chg_type) { +				case USB_DCP_CHARGER: +					msm_otg_notify_charger(motg, +							IDEV_CHG_MAX); +					break; +				case USB_CDP_CHARGER: +					msm_otg_notify_charger(motg, +							IDEV_CHG_MAX); +					msm_otg_start_peripheral(otg->phy, 1); +					otg->phy->state +						= OTG_STATE_B_PERIPHERAL; +					break; +				case USB_SDP_CHARGER: +					msm_otg_notify_charger(motg, IUNIT); +					msm_otg_start_peripheral(otg->phy, 1); +					otg->phy->state +						= OTG_STATE_B_PERIPHERAL; +					break; +				default: +					break; +				} +				break; +			default: +				break; +			} +		} else { +			/* +			 * If charger detection work is pending, decrement +			 * the pm usage counter to balance with the one that +			 * is incremented in charger detection work. +			 */ +			if (cancel_delayed_work_sync(&motg->chg_work)) { +				pm_runtime_put_sync(otg->phy->dev); +				msm_otg_reset(otg->phy); +			} +			msm_otg_notify_charger(motg, 0); +			motg->chg_state = USB_CHG_STATE_UNDEFINED; +			motg->chg_type = USB_INVALID_CHARGER; +		} +		pm_runtime_put_sync(otg->phy->dev); +		break; +	case OTG_STATE_B_PERIPHERAL: +		dev_dbg(otg->phy->dev, "OTG_STATE_B_PERIPHERAL state\n"); +		if (!test_bit(B_SESS_VLD, &motg->inputs) || +				!test_bit(ID, &motg->inputs)) { +			msm_otg_notify_charger(motg, 0); +			msm_otg_start_peripheral(otg->phy, 0); +			motg->chg_state = USB_CHG_STATE_UNDEFINED; +			motg->chg_type = USB_INVALID_CHARGER; +			otg->phy->state = OTG_STATE_B_IDLE; +			msm_otg_reset(otg->phy); +			schedule_work(w); +		} +		break; +	case OTG_STATE_A_HOST: +		dev_dbg(otg->phy->dev, "OTG_STATE_A_HOST state\n"); +		if (test_bit(ID, &motg->inputs)) { +			msm_otg_start_host(otg->phy, 0); +			otg->phy->state = OTG_STATE_B_IDLE; +			msm_otg_reset(otg->phy); +			schedule_work(w); +		} +		break; +	default: +		break; +	} +} + +static irqreturn_t msm_otg_irq(int irq, void *data) +{ +	struct msm_otg *motg = data; +	struct usb_phy *phy = &motg->phy; +	u32 otgsc = 0; + +	if (atomic_read(&motg->in_lpm)) { +		disable_irq_nosync(irq); +		motg->async_int = 1; +		pm_runtime_get(phy->dev); +		return IRQ_HANDLED; +	} + +	otgsc = readl(USB_OTGSC); +	if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) +		return IRQ_NONE; + +	if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) { +		if (otgsc & OTGSC_ID) +			set_bit(ID, &motg->inputs); +		else +			clear_bit(ID, &motg->inputs); +		dev_dbg(phy->dev, "ID set/clear\n"); +		pm_runtime_get_noresume(phy->dev); +	} else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) { +		if (otgsc & OTGSC_BSV) +			set_bit(B_SESS_VLD, &motg->inputs); +		else +			clear_bit(B_SESS_VLD, &motg->inputs); +		dev_dbg(phy->dev, "BSV set/clear\n"); +		pm_runtime_get_noresume(phy->dev); +	} + +	writel(otgsc, USB_OTGSC); +	schedule_work(&motg->sm_work); +	return IRQ_HANDLED; +} + +static int msm_otg_mode_show(struct seq_file *s, void *unused) +{ +	struct msm_otg *motg = s->private; +	struct usb_otg *otg = motg->phy.otg; + +	switch (otg->phy->state) { +	case OTG_STATE_A_HOST: +		seq_printf(s, "host\n"); +		break; +	case OTG_STATE_B_PERIPHERAL: +		seq_printf(s, "peripheral\n"); +		break; +	default: +		seq_printf(s, "none\n"); +		break; +	} + +	return 0; +} + +static int msm_otg_mode_open(struct inode *inode, struct file *file) +{ +	return single_open(file, msm_otg_mode_show, inode->i_private); +} + +static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, +				size_t count, loff_t *ppos) +{ +	struct seq_file *s = file->private_data; +	struct msm_otg *motg = s->private; +	char buf[16]; +	struct usb_otg *otg = motg->phy.otg; +	int status = count; +	enum usb_mode_type req_mode; + +	memset(buf, 0x00, sizeof(buf)); + +	if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) { +		status = -EFAULT; +		goto out; +	} + +	if (!strncmp(buf, "host", 4)) { +		req_mode = USB_HOST; +	} else if (!strncmp(buf, "peripheral", 10)) { +		req_mode = USB_PERIPHERAL; +	} else if (!strncmp(buf, "none", 4)) { +		req_mode = USB_NONE; +	} else { +		status = -EINVAL; +		goto out; +	} + +	switch (req_mode) { +	case USB_NONE: +		switch (otg->phy->state) { +		case OTG_STATE_A_HOST: +		case OTG_STATE_B_PERIPHERAL: +			set_bit(ID, &motg->inputs); +			clear_bit(B_SESS_VLD, &motg->inputs); +			break; +		default: +			goto out; +		} +		break; +	case USB_PERIPHERAL: +		switch (otg->phy->state) { +		case OTG_STATE_B_IDLE: +		case OTG_STATE_A_HOST: +			set_bit(ID, &motg->inputs); +			set_bit(B_SESS_VLD, &motg->inputs); +			break; +		default: +			goto out; +		} +		break; +	case USB_HOST: +		switch (otg->phy->state) { +		case OTG_STATE_B_IDLE: +		case OTG_STATE_B_PERIPHERAL: +			clear_bit(ID, &motg->inputs); +			break; +		default: +			goto out; +		} +		break; +	default: +		goto out; +	} + +	pm_runtime_get_sync(otg->phy->dev); +	schedule_work(&motg->sm_work); +out: +	return status; +} + +const struct file_operations msm_otg_mode_fops = { +	.open = msm_otg_mode_open, +	.read = seq_read, +	.write = msm_otg_mode_write, +	.llseek = seq_lseek, +	.release = single_release, +}; + +static struct dentry *msm_otg_dbg_root; +static struct dentry *msm_otg_dbg_mode; + +static int msm_otg_debugfs_init(struct msm_otg *motg) +{ +	msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL); + +	if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root)) +		return -ENODEV; + +	msm_otg_dbg_mode = debugfs_create_file("mode", S_IRUGO | S_IWUSR, +				msm_otg_dbg_root, motg, &msm_otg_mode_fops); +	if (!msm_otg_dbg_mode) { +		debugfs_remove(msm_otg_dbg_root); +		msm_otg_dbg_root = NULL; +		return -ENODEV; +	} + +	return 0; +} + +static void msm_otg_debugfs_cleanup(void) +{ +	debugfs_remove(msm_otg_dbg_mode); +	debugfs_remove(msm_otg_dbg_root); +} + +static int __init msm_otg_probe(struct platform_device *pdev) +{ +	int ret = 0; +	struct resource *res; +	struct msm_otg *motg; +	struct usb_phy *phy; + +	dev_info(&pdev->dev, "msm_otg probe\n"); +	if (!pdev->dev.platform_data) { +		dev_err(&pdev->dev, "No platform data given. Bailing out\n"); +		return -ENODEV; +	} + +	motg = kzalloc(sizeof(struct msm_otg), GFP_KERNEL); +	if (!motg) { +		dev_err(&pdev->dev, "unable to allocate msm_otg\n"); +		return -ENOMEM; +	} + +	motg->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL); +	if (!motg->phy.otg) { +		dev_err(&pdev->dev, "unable to allocate msm_otg\n"); +		return -ENOMEM; +	} + +	motg->pdata = pdev->dev.platform_data; +	phy = &motg->phy; +	phy->dev = &pdev->dev; + +	motg->phy_reset_clk = clk_get(&pdev->dev, "usb_phy_clk"); +	if (IS_ERR(motg->phy_reset_clk)) { +		dev_err(&pdev->dev, "failed to get usb_phy_clk\n"); +		ret = PTR_ERR(motg->phy_reset_clk); +		goto free_motg; +	} + +	motg->clk = clk_get(&pdev->dev, "usb_hs_clk"); +	if (IS_ERR(motg->clk)) { +		dev_err(&pdev->dev, "failed to get usb_hs_clk\n"); +		ret = PTR_ERR(motg->clk); +		goto put_phy_reset_clk; +	} +	clk_set_rate(motg->clk, 60000000); + +	/* +	 * If USB Core is running its protocol engine based on CORE CLK, +	 * CORE CLK  must be running at >55Mhz for correct HSUSB +	 * operation and USB core cannot tolerate frequency changes on +	 * CORE CLK. For such USB cores, vote for maximum clk frequency +	 * on pclk source +	 */ +	 if (motg->pdata->pclk_src_name) { +		motg->pclk_src = clk_get(&pdev->dev, +			motg->pdata->pclk_src_name); +		if (IS_ERR(motg->pclk_src)) +			goto put_clk; +		clk_set_rate(motg->pclk_src, INT_MAX); +		clk_enable(motg->pclk_src); +	} else +		motg->pclk_src = ERR_PTR(-ENOENT); + + +	motg->pclk = clk_get(&pdev->dev, "usb_hs_pclk"); +	if (IS_ERR(motg->pclk)) { +		dev_err(&pdev->dev, "failed to get usb_hs_pclk\n"); +		ret = PTR_ERR(motg->pclk); +		goto put_pclk_src; +	} + +	/* +	 * USB core clock is not present on all MSM chips. This +	 * clock is introduced to remove the dependency on AXI +	 * bus frequency. +	 */ +	motg->core_clk = clk_get(&pdev->dev, "usb_hs_core_clk"); +	if (IS_ERR(motg->core_clk)) +		motg->core_clk = NULL; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(&pdev->dev, "failed to get platform resource mem\n"); +		ret = -ENODEV; +		goto put_core_clk; +	} + +	motg->regs = ioremap(res->start, resource_size(res)); +	if (!motg->regs) { +		dev_err(&pdev->dev, "ioremap failed\n"); +		ret = -ENOMEM; +		goto put_core_clk; +	} +	dev_info(&pdev->dev, "OTG regs = %p\n", motg->regs); + +	motg->irq = platform_get_irq(pdev, 0); +	if (!motg->irq) { +		dev_err(&pdev->dev, "platform_get_irq failed\n"); +		ret = -ENODEV; +		goto free_regs; +	} + +	clk_enable(motg->clk); +	clk_enable(motg->pclk); + +	ret = msm_hsusb_init_vddcx(motg, 1); +	if (ret) { +		dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); +		goto free_regs; +	} + +	ret = msm_hsusb_ldo_init(motg, 1); +	if (ret) { +		dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); +		goto vddcx_exit; +	} +	ret = msm_hsusb_ldo_set_mode(1); +	if (ret) { +		dev_err(&pdev->dev, "hsusb vreg enable failed\n"); +		goto ldo_exit; +	} + +	if (motg->core_clk) +		clk_enable(motg->core_clk); + +	writel(0, USB_USBINTR); +	writel(0, USB_OTGSC); + +	INIT_WORK(&motg->sm_work, msm_otg_sm_work); +	INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); +	ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED, +					"msm_otg", motg); +	if (ret) { +		dev_err(&pdev->dev, "request irq failed\n"); +		goto disable_clks; +	} + +	phy->init = msm_otg_reset; +	phy->set_power = msm_otg_set_power; + +	phy->io_ops = &msm_otg_io_ops; + +	phy->otg->phy = &motg->phy; +	phy->otg->set_host = msm_otg_set_host; +	phy->otg->set_peripheral = msm_otg_set_peripheral; + +	ret = usb_add_phy(&motg->phy, USB_PHY_TYPE_USB2); +	if (ret) { +		dev_err(&pdev->dev, "usb_add_phy failed\n"); +		goto free_irq; +	} + +	platform_set_drvdata(pdev, motg); +	device_init_wakeup(&pdev->dev, 1); + +	if (motg->pdata->mode == USB_OTG && +			motg->pdata->otg_control == OTG_USER_CONTROL) { +		ret = msm_otg_debugfs_init(motg); +		if (ret) +			dev_dbg(&pdev->dev, "mode debugfs file is" +					"not available\n"); +	} + +	pm_runtime_set_active(&pdev->dev); +	pm_runtime_enable(&pdev->dev); + +	return 0; +free_irq: +	free_irq(motg->irq, motg); +disable_clks: +	clk_disable(motg->pclk); +	clk_disable(motg->clk); +ldo_exit: +	msm_hsusb_ldo_init(motg, 0); +vddcx_exit: +	msm_hsusb_init_vddcx(motg, 0); +free_regs: +	iounmap(motg->regs); +put_core_clk: +	if (motg->core_clk) +		clk_put(motg->core_clk); +	clk_put(motg->pclk); +put_pclk_src: +	if (!IS_ERR(motg->pclk_src)) { +		clk_disable(motg->pclk_src); +		clk_put(motg->pclk_src); +	} +put_clk: +	clk_put(motg->clk); +put_phy_reset_clk: +	clk_put(motg->phy_reset_clk); +free_motg: +	kfree(motg->phy.otg); +	kfree(motg); +	return ret; +} + +static int msm_otg_remove(struct platform_device *pdev) +{ +	struct msm_otg *motg = platform_get_drvdata(pdev); +	struct usb_phy *phy = &motg->phy; +	int cnt = 0; + +	if (phy->otg->host || phy->otg->gadget) +		return -EBUSY; + +	msm_otg_debugfs_cleanup(); +	cancel_delayed_work_sync(&motg->chg_work); +	cancel_work_sync(&motg->sm_work); + +	pm_runtime_resume(&pdev->dev); + +	device_init_wakeup(&pdev->dev, 0); +	pm_runtime_disable(&pdev->dev); + +	usb_remove_phy(phy); +	free_irq(motg->irq, motg); + +	/* +	 * Put PHY in low power mode. +	 */ +	ulpi_read(phy, 0x14); +	ulpi_write(phy, 0x08, 0x09); + +	writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); +	while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { +		if (readl(USB_PORTSC) & PORTSC_PHCD) +			break; +		udelay(1); +		cnt++; +	} +	if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) +		dev_err(phy->dev, "Unable to suspend PHY\n"); + +	clk_disable(motg->pclk); +	clk_disable(motg->clk); +	if (motg->core_clk) +		clk_disable(motg->core_clk); +	if (!IS_ERR(motg->pclk_src)) { +		clk_disable(motg->pclk_src); +		clk_put(motg->pclk_src); +	} +	msm_hsusb_ldo_init(motg, 0); + +	iounmap(motg->regs); +	pm_runtime_set_suspended(&pdev->dev); + +	clk_put(motg->phy_reset_clk); +	clk_put(motg->pclk); +	clk_put(motg->clk); +	if (motg->core_clk) +		clk_put(motg->core_clk); + +	kfree(motg->phy.otg); +	kfree(motg); + +	return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int msm_otg_runtime_idle(struct device *dev) +{ +	struct msm_otg *motg = dev_get_drvdata(dev); +	struct usb_otg *otg = motg->phy.otg; + +	dev_dbg(dev, "OTG runtime idle\n"); + +	/* +	 * It is observed some times that a spurious interrupt +	 * comes when PHY is put into LPM immediately after PHY reset. +	 * This 1 sec delay also prevents entering into LPM immediately +	 * after asynchronous interrupt. +	 */ +	if (otg->phy->state != OTG_STATE_UNDEFINED) +		pm_schedule_suspend(dev, 1000); + +	return -EAGAIN; +} + +static int msm_otg_runtime_suspend(struct device *dev) +{ +	struct msm_otg *motg = dev_get_drvdata(dev); + +	dev_dbg(dev, "OTG runtime suspend\n"); +	return msm_otg_suspend(motg); +} + +static int msm_otg_runtime_resume(struct device *dev) +{ +	struct msm_otg *motg = dev_get_drvdata(dev); + +	dev_dbg(dev, "OTG runtime resume\n"); +	return msm_otg_resume(motg); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int msm_otg_pm_suspend(struct device *dev) +{ +	struct msm_otg *motg = dev_get_drvdata(dev); + +	dev_dbg(dev, "OTG PM suspend\n"); +	return msm_otg_suspend(motg); +} + +static int msm_otg_pm_resume(struct device *dev) +{ +	struct msm_otg *motg = dev_get_drvdata(dev); +	int ret; + +	dev_dbg(dev, "OTG PM resume\n"); + +	ret = msm_otg_resume(motg); +	if (ret) +		return ret; + +	/* +	 * Runtime PM Documentation recommends bringing the +	 * device to full powered state upon resume. +	 */ +	pm_runtime_disable(dev); +	pm_runtime_set_active(dev); +	pm_runtime_enable(dev); + +	return 0; +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops msm_otg_dev_pm_ops = { +	SET_SYSTEM_SLEEP_PM_OPS(msm_otg_pm_suspend, msm_otg_pm_resume) +	SET_RUNTIME_PM_OPS(msm_otg_runtime_suspend, msm_otg_runtime_resume, +				msm_otg_runtime_idle) +}; +#endif + +static struct platform_driver msm_otg_driver = { +	.remove = msm_otg_remove, +	.driver = { +		.name = DRIVER_NAME, +		.owner = THIS_MODULE, +#ifdef CONFIG_PM +		.pm = &msm_otg_dev_pm_ops, +#endif +	}, +}; + +module_platform_driver_probe(msm_otg_driver, msm_otg_probe); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM USB transceiver driver"); diff --git a/drivers/usb/phy/mv_u3d_phy.c b/drivers/usb/phy/phy-mv-u3d-usb.c index 9d8599122aa..f7838a43347 100644 --- a/drivers/usb/phy/mv_u3d_phy.c +++ b/drivers/usb/phy/phy-mv-u3d-usb.c @@ -15,7 +15,7 @@  #include <linux/usb/otg.h>  #include <linux/platform_data/mv_usb.h> -#include "mv_u3d_phy.h" +#include "phy-mv-u3d-usb.h"  /*   * struct mv_u3d_phy - transceiver driver state @@ -313,7 +313,7 @@ err:  	return ret;  } -static int __exit mv_u3d_phy_remove(struct platform_device *pdev) +static int mv_u3d_phy_remove(struct platform_device *pdev)  {  	struct mv_u3d_phy *mv_u3d_phy = platform_get_drvdata(pdev); diff --git a/drivers/usb/phy/mv_u3d_phy.h b/drivers/usb/phy/phy-mv-u3d-usb.h index 2a658cb9a52..2a658cb9a52 100644 --- a/drivers/usb/phy/mv_u3d_phy.h +++ b/drivers/usb/phy/phy-mv-u3d-usb.h diff --git a/drivers/usb/phy/phy-mv-usb.c b/drivers/usb/phy/phy-mv-usb.c new file mode 100644 index 00000000000..c987bbe2785 --- /dev/null +++ b/drivers/usb/phy/phy-mv-usb.c @@ -0,0 +1,909 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Author: Chao Xie <chao.xie@marvell.com> + *	   Neil Zhang <zhangwm@marvell.com> + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of  the GNU General  Public License as published by the + * Free Software Foundation;  either version 2 of the  License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/device.h> +#include <linux/proc_fs.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> + +#include <linux/usb.h> +#include <linux/usb/ch9.h> +#include <linux/usb/otg.h> +#include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> +#include <linux/platform_data/mv_usb.h> + +#include "phy-mv-usb.h" + +#define	DRIVER_DESC	"Marvell USB OTG transceiver driver" +#define	DRIVER_VERSION	"Jan 20, 2010" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +static const char driver_name[] = "mv-otg"; + +static char *state_string[] = { +	"undefined", +	"b_idle", +	"b_srp_init", +	"b_peripheral", +	"b_wait_acon", +	"b_host", +	"a_idle", +	"a_wait_vrise", +	"a_wait_bcon", +	"a_host", +	"a_suspend", +	"a_peripheral", +	"a_wait_vfall", +	"a_vbus_err" +}; + +static int mv_otg_set_vbus(struct usb_otg *otg, bool on) +{ +	struct mv_otg *mvotg = container_of(otg->phy, struct mv_otg, phy); +	if (mvotg->pdata->set_vbus == NULL) +		return -ENODEV; + +	return mvotg->pdata->set_vbus(on); +} + +static int mv_otg_set_host(struct usb_otg *otg, +			   struct usb_bus *host) +{ +	otg->host = host; + +	return 0; +} + +static int mv_otg_set_peripheral(struct usb_otg *otg, +				 struct usb_gadget *gadget) +{ +	otg->gadget = gadget; + +	return 0; +} + +static void mv_otg_run_state_machine(struct mv_otg *mvotg, +				     unsigned long delay) +{ +	dev_dbg(&mvotg->pdev->dev, "transceiver is updated\n"); +	if (!mvotg->qwork) +		return; + +	queue_delayed_work(mvotg->qwork, &mvotg->work, delay); +} + +static void mv_otg_timer_await_bcon(unsigned long data) +{ +	struct mv_otg *mvotg = (struct mv_otg *) data; + +	mvotg->otg_ctrl.a_wait_bcon_timeout = 1; + +	dev_info(&mvotg->pdev->dev, "B Device No Response!\n"); + +	if (spin_trylock(&mvotg->wq_lock)) { +		mv_otg_run_state_machine(mvotg, 0); +		spin_unlock(&mvotg->wq_lock); +	} +} + +static int mv_otg_cancel_timer(struct mv_otg *mvotg, unsigned int id) +{ +	struct timer_list *timer; + +	if (id >= OTG_TIMER_NUM) +		return -EINVAL; + +	timer = &mvotg->otg_ctrl.timer[id]; + +	if (timer_pending(timer)) +		del_timer(timer); + +	return 0; +} + +static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id, +			    unsigned long interval, +			    void (*callback) (unsigned long)) +{ +	struct timer_list *timer; + +	if (id >= OTG_TIMER_NUM) +		return -EINVAL; + +	timer = &mvotg->otg_ctrl.timer[id]; +	if (timer_pending(timer)) { +		dev_err(&mvotg->pdev->dev, "Timer%d is already running\n", id); +		return -EBUSY; +	} + +	init_timer(timer); +	timer->data = (unsigned long) mvotg; +	timer->function = callback; +	timer->expires = jiffies + interval; +	add_timer(timer); + +	return 0; +} + +static int mv_otg_reset(struct mv_otg *mvotg) +{ +	unsigned int loops; +	u32 tmp; + +	/* Stop the controller */ +	tmp = readl(&mvotg->op_regs->usbcmd); +	tmp &= ~USBCMD_RUN_STOP; +	writel(tmp, &mvotg->op_regs->usbcmd); + +	/* Reset the controller to get default values */ +	writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd); + +	loops = 500; +	while (readl(&mvotg->op_regs->usbcmd) & USBCMD_CTRL_RESET) { +		if (loops == 0) { +			dev_err(&mvotg->pdev->dev, +				"Wait for RESET completed TIMEOUT\n"); +			return -ETIMEDOUT; +		} +		loops--; +		udelay(20); +	} + +	writel(0x0, &mvotg->op_regs->usbintr); +	tmp = readl(&mvotg->op_regs->usbsts); +	writel(tmp, &mvotg->op_regs->usbsts); + +	return 0; +} + +static void mv_otg_init_irq(struct mv_otg *mvotg) +{ +	u32 otgsc; + +	mvotg->irq_en = OTGSC_INTR_A_SESSION_VALID +	    | OTGSC_INTR_A_VBUS_VALID; +	mvotg->irq_status = OTGSC_INTSTS_A_SESSION_VALID +	    | OTGSC_INTSTS_A_VBUS_VALID; + +	if (mvotg->pdata->vbus == NULL) { +		mvotg->irq_en |= OTGSC_INTR_B_SESSION_VALID +		    | OTGSC_INTR_B_SESSION_END; +		mvotg->irq_status |= OTGSC_INTSTS_B_SESSION_VALID +		    | OTGSC_INTSTS_B_SESSION_END; +	} + +	if (mvotg->pdata->id == NULL) { +		mvotg->irq_en |= OTGSC_INTR_USB_ID; +		mvotg->irq_status |= OTGSC_INTSTS_USB_ID; +	} + +	otgsc = readl(&mvotg->op_regs->otgsc); +	otgsc |= mvotg->irq_en; +	writel(otgsc, &mvotg->op_regs->otgsc); +} + +static void mv_otg_start_host(struct mv_otg *mvotg, int on) +{ +#ifdef CONFIG_USB +	struct usb_otg *otg = mvotg->phy.otg; +	struct usb_hcd *hcd; + +	if (!otg->host) +		return; + +	dev_info(&mvotg->pdev->dev, "%s host\n", on ? "start" : "stop"); + +	hcd = bus_to_hcd(otg->host); + +	if (on) +		usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); +	else +		usb_remove_hcd(hcd); +#endif /* CONFIG_USB */ +} + +static void mv_otg_start_periphrals(struct mv_otg *mvotg, int on) +{ +	struct usb_otg *otg = mvotg->phy.otg; + +	if (!otg->gadget) +		return; + +	dev_info(mvotg->phy.dev, "gadget %s\n", on ? "on" : "off"); + +	if (on) +		usb_gadget_vbus_connect(otg->gadget); +	else +		usb_gadget_vbus_disconnect(otg->gadget); +} + +static void otg_clock_enable(struct mv_otg *mvotg) +{ +	clk_prepare_enable(mvotg->clk); +} + +static void otg_clock_disable(struct mv_otg *mvotg) +{ +	clk_disable_unprepare(mvotg->clk); +} + +static int mv_otg_enable_internal(struct mv_otg *mvotg) +{ +	int retval = 0; + +	if (mvotg->active) +		return 0; + +	dev_dbg(&mvotg->pdev->dev, "otg enabled\n"); + +	otg_clock_enable(mvotg); +	if (mvotg->pdata->phy_init) { +		retval = mvotg->pdata->phy_init(mvotg->phy_regs); +		if (retval) { +			dev_err(&mvotg->pdev->dev, +				"init phy error %d\n", retval); +			otg_clock_disable(mvotg); +			return retval; +		} +	} +	mvotg->active = 1; + +	return 0; + +} + +static int mv_otg_enable(struct mv_otg *mvotg) +{ +	if (mvotg->clock_gating) +		return mv_otg_enable_internal(mvotg); + +	return 0; +} + +static void mv_otg_disable_internal(struct mv_otg *mvotg) +{ +	if (mvotg->active) { +		dev_dbg(&mvotg->pdev->dev, "otg disabled\n"); +		if (mvotg->pdata->phy_deinit) +			mvotg->pdata->phy_deinit(mvotg->phy_regs); +		otg_clock_disable(mvotg); +		mvotg->active = 0; +	} +} + +static void mv_otg_disable(struct mv_otg *mvotg) +{ +	if (mvotg->clock_gating) +		mv_otg_disable_internal(mvotg); +} + +static void mv_otg_update_inputs(struct mv_otg *mvotg) +{ +	struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl; +	u32 otgsc; + +	otgsc = readl(&mvotg->op_regs->otgsc); + +	if (mvotg->pdata->vbus) { +		if (mvotg->pdata->vbus->poll() == VBUS_HIGH) { +			otg_ctrl->b_sess_vld = 1; +			otg_ctrl->b_sess_end = 0; +		} else { +			otg_ctrl->b_sess_vld = 0; +			otg_ctrl->b_sess_end = 1; +		} +	} else { +		otg_ctrl->b_sess_vld = !!(otgsc & OTGSC_STS_B_SESSION_VALID); +		otg_ctrl->b_sess_end = !!(otgsc & OTGSC_STS_B_SESSION_END); +	} + +	if (mvotg->pdata->id) +		otg_ctrl->id = !!mvotg->pdata->id->poll(); +	else +		otg_ctrl->id = !!(otgsc & OTGSC_STS_USB_ID); + +	if (mvotg->pdata->otg_force_a_bus_req && !otg_ctrl->id) +		otg_ctrl->a_bus_req = 1; + +	otg_ctrl->a_sess_vld = !!(otgsc & OTGSC_STS_A_SESSION_VALID); +	otg_ctrl->a_vbus_vld = !!(otgsc & OTGSC_STS_A_VBUS_VALID); + +	dev_dbg(&mvotg->pdev->dev, "%s: ", __func__); +	dev_dbg(&mvotg->pdev->dev, "id %d\n", otg_ctrl->id); +	dev_dbg(&mvotg->pdev->dev, "b_sess_vld %d\n", otg_ctrl->b_sess_vld); +	dev_dbg(&mvotg->pdev->dev, "b_sess_end %d\n", otg_ctrl->b_sess_end); +	dev_dbg(&mvotg->pdev->dev, "a_vbus_vld %d\n", otg_ctrl->a_vbus_vld); +	dev_dbg(&mvotg->pdev->dev, "a_sess_vld %d\n", otg_ctrl->a_sess_vld); +} + +static void mv_otg_update_state(struct mv_otg *mvotg) +{ +	struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl; +	struct usb_phy *phy = &mvotg->phy; +	int old_state = phy->state; + +	switch (old_state) { +	case OTG_STATE_UNDEFINED: +		phy->state = OTG_STATE_B_IDLE; +		/* FALL THROUGH */ +	case OTG_STATE_B_IDLE: +		if (otg_ctrl->id == 0) +			phy->state = OTG_STATE_A_IDLE; +		else if (otg_ctrl->b_sess_vld) +			phy->state = OTG_STATE_B_PERIPHERAL; +		break; +	case OTG_STATE_B_PERIPHERAL: +		if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0) +			phy->state = OTG_STATE_B_IDLE; +		break; +	case OTG_STATE_A_IDLE: +		if (otg_ctrl->id) +			phy->state = OTG_STATE_B_IDLE; +		else if (!(otg_ctrl->a_bus_drop) && +			 (otg_ctrl->a_bus_req || otg_ctrl->a_srp_det)) +			phy->state = OTG_STATE_A_WAIT_VRISE; +		break; +	case OTG_STATE_A_WAIT_VRISE: +		if (otg_ctrl->a_vbus_vld) +			phy->state = OTG_STATE_A_WAIT_BCON; +		break; +	case OTG_STATE_A_WAIT_BCON: +		if (otg_ctrl->id || otg_ctrl->a_bus_drop +		    || otg_ctrl->a_wait_bcon_timeout) { +			mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); +			mvotg->otg_ctrl.a_wait_bcon_timeout = 0; +			phy->state = OTG_STATE_A_WAIT_VFALL; +			otg_ctrl->a_bus_req = 0; +		} else if (!otg_ctrl->a_vbus_vld) { +			mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); +			mvotg->otg_ctrl.a_wait_bcon_timeout = 0; +			phy->state = OTG_STATE_A_VBUS_ERR; +		} else if (otg_ctrl->b_conn) { +			mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER); +			mvotg->otg_ctrl.a_wait_bcon_timeout = 0; +			phy->state = OTG_STATE_A_HOST; +		} +		break; +	case OTG_STATE_A_HOST: +		if (otg_ctrl->id || !otg_ctrl->b_conn +		    || otg_ctrl->a_bus_drop) +			phy->state = OTG_STATE_A_WAIT_BCON; +		else if (!otg_ctrl->a_vbus_vld) +			phy->state = OTG_STATE_A_VBUS_ERR; +		break; +	case OTG_STATE_A_WAIT_VFALL: +		if (otg_ctrl->id +		    || (!otg_ctrl->b_conn && otg_ctrl->a_sess_vld) +		    || otg_ctrl->a_bus_req) +			phy->state = OTG_STATE_A_IDLE; +		break; +	case OTG_STATE_A_VBUS_ERR: +		if (otg_ctrl->id || otg_ctrl->a_clr_err +		    || otg_ctrl->a_bus_drop) { +			otg_ctrl->a_clr_err = 0; +			phy->state = OTG_STATE_A_WAIT_VFALL; +		} +		break; +	default: +		break; +	} +} + +static void mv_otg_work(struct work_struct *work) +{ +	struct mv_otg *mvotg; +	struct usb_phy *phy; +	struct usb_otg *otg; +	int old_state; + +	mvotg = container_of(to_delayed_work(work), struct mv_otg, work); + +run: +	/* work queue is single thread, or we need spin_lock to protect */ +	phy = &mvotg->phy; +	otg = phy->otg; +	old_state = phy->state; + +	if (!mvotg->active) +		return; + +	mv_otg_update_inputs(mvotg); +	mv_otg_update_state(mvotg); + +	if (old_state != phy->state) { +		dev_info(&mvotg->pdev->dev, "change from state %s to %s\n", +			 state_string[old_state], +			 state_string[phy->state]); + +		switch (phy->state) { +		case OTG_STATE_B_IDLE: +			otg->default_a = 0; +			if (old_state == OTG_STATE_B_PERIPHERAL) +				mv_otg_start_periphrals(mvotg, 0); +			mv_otg_reset(mvotg); +			mv_otg_disable(mvotg); +			break; +		case OTG_STATE_B_PERIPHERAL: +			mv_otg_enable(mvotg); +			mv_otg_start_periphrals(mvotg, 1); +			break; +		case OTG_STATE_A_IDLE: +			otg->default_a = 1; +			mv_otg_enable(mvotg); +			if (old_state == OTG_STATE_A_WAIT_VFALL) +				mv_otg_start_host(mvotg, 0); +			mv_otg_reset(mvotg); +			break; +		case OTG_STATE_A_WAIT_VRISE: +			mv_otg_set_vbus(otg, 1); +			break; +		case OTG_STATE_A_WAIT_BCON: +			if (old_state != OTG_STATE_A_HOST) +				mv_otg_start_host(mvotg, 1); +			mv_otg_set_timer(mvotg, A_WAIT_BCON_TIMER, +					 T_A_WAIT_BCON, +					 mv_otg_timer_await_bcon); +			/* +			 * Now, we directly enter A_HOST. So set b_conn = 1 +			 * here. In fact, it need host driver to notify us. +			 */ +			mvotg->otg_ctrl.b_conn = 1; +			break; +		case OTG_STATE_A_HOST: +			break; +		case OTG_STATE_A_WAIT_VFALL: +			/* +			 * Now, we has exited A_HOST. So set b_conn = 0 +			 * here. In fact, it need host driver to notify us. +			 */ +			mvotg->otg_ctrl.b_conn = 0; +			mv_otg_set_vbus(otg, 0); +			break; +		case OTG_STATE_A_VBUS_ERR: +			break; +		default: +			break; +		} +		goto run; +	} +} + +static irqreturn_t mv_otg_irq(int irq, void *dev) +{ +	struct mv_otg *mvotg = dev; +	u32 otgsc; + +	otgsc = readl(&mvotg->op_regs->otgsc); +	writel(otgsc, &mvotg->op_regs->otgsc); + +	/* +	 * if we have vbus, then the vbus detection for B-device +	 * will be done by mv_otg_inputs_irq(). +	 */ +	if (mvotg->pdata->vbus) +		if ((otgsc & OTGSC_STS_USB_ID) && +		    !(otgsc & OTGSC_INTSTS_USB_ID)) +			return IRQ_NONE; + +	if ((otgsc & mvotg->irq_status) == 0) +		return IRQ_NONE; + +	mv_otg_run_state_machine(mvotg, 0); + +	return IRQ_HANDLED; +} + +static irqreturn_t mv_otg_inputs_irq(int irq, void *dev) +{ +	struct mv_otg *mvotg = dev; + +	/* The clock may disabled at this time */ +	if (!mvotg->active) { +		mv_otg_enable(mvotg); +		mv_otg_init_irq(mvotg); +	} + +	mv_otg_run_state_machine(mvotg, 0); + +	return IRQ_HANDLED; +} + +static ssize_t +get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf) +{ +	struct mv_otg *mvotg = dev_get_drvdata(dev); +	return scnprintf(buf, PAGE_SIZE, "%d\n", +			 mvotg->otg_ctrl.a_bus_req); +} + +static ssize_t +set_a_bus_req(struct device *dev, struct device_attribute *attr, +	      const char *buf, size_t count) +{ +	struct mv_otg *mvotg = dev_get_drvdata(dev); + +	if (count > 2) +		return -1; + +	/* We will use this interface to change to A device */ +	if (mvotg->phy.state != OTG_STATE_B_IDLE +	    && mvotg->phy.state != OTG_STATE_A_IDLE) +		return -1; + +	/* The clock may disabled and we need to set irq for ID detected */ +	mv_otg_enable(mvotg); +	mv_otg_init_irq(mvotg); + +	if (buf[0] == '1') { +		mvotg->otg_ctrl.a_bus_req = 1; +		mvotg->otg_ctrl.a_bus_drop = 0; +		dev_dbg(&mvotg->pdev->dev, +			"User request: a_bus_req = 1\n"); + +		if (spin_trylock(&mvotg->wq_lock)) { +			mv_otg_run_state_machine(mvotg, 0); +			spin_unlock(&mvotg->wq_lock); +		} +	} + +	return count; +} + +static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req, +		   set_a_bus_req); + +static ssize_t +set_a_clr_err(struct device *dev, struct device_attribute *attr, +	      const char *buf, size_t count) +{ +	struct mv_otg *mvotg = dev_get_drvdata(dev); +	if (!mvotg->phy.otg->default_a) +		return -1; + +	if (count > 2) +		return -1; + +	if (buf[0] == '1') { +		mvotg->otg_ctrl.a_clr_err = 1; +		dev_dbg(&mvotg->pdev->dev, +			"User request: a_clr_err = 1\n"); +	} + +	if (spin_trylock(&mvotg->wq_lock)) { +		mv_otg_run_state_machine(mvotg, 0); +		spin_unlock(&mvotg->wq_lock); +	} + +	return count; +} + +static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err); + +static ssize_t +get_a_bus_drop(struct device *dev, struct device_attribute *attr, +	       char *buf) +{ +	struct mv_otg *mvotg = dev_get_drvdata(dev); +	return scnprintf(buf, PAGE_SIZE, "%d\n", +			 mvotg->otg_ctrl.a_bus_drop); +} + +static ssize_t +set_a_bus_drop(struct device *dev, struct device_attribute *attr, +	       const char *buf, size_t count) +{ +	struct mv_otg *mvotg = dev_get_drvdata(dev); +	if (!mvotg->phy.otg->default_a) +		return -1; + +	if (count > 2) +		return -1; + +	if (buf[0] == '0') { +		mvotg->otg_ctrl.a_bus_drop = 0; +		dev_dbg(&mvotg->pdev->dev, +			"User request: a_bus_drop = 0\n"); +	} else if (buf[0] == '1') { +		mvotg->otg_ctrl.a_bus_drop = 1; +		mvotg->otg_ctrl.a_bus_req = 0; +		dev_dbg(&mvotg->pdev->dev, +			"User request: a_bus_drop = 1\n"); +		dev_dbg(&mvotg->pdev->dev, +			"User request: and a_bus_req = 0\n"); +	} + +	if (spin_trylock(&mvotg->wq_lock)) { +		mv_otg_run_state_machine(mvotg, 0); +		spin_unlock(&mvotg->wq_lock); +	} + +	return count; +} + +static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR, +		   get_a_bus_drop, set_a_bus_drop); + +static struct attribute *inputs_attrs[] = { +	&dev_attr_a_bus_req.attr, +	&dev_attr_a_clr_err.attr, +	&dev_attr_a_bus_drop.attr, +	NULL, +}; + +static struct attribute_group inputs_attr_group = { +	.name = "inputs", +	.attrs = inputs_attrs, +}; + +int mv_otg_remove(struct platform_device *pdev) +{ +	struct mv_otg *mvotg = platform_get_drvdata(pdev); + +	sysfs_remove_group(&mvotg->pdev->dev.kobj, &inputs_attr_group); + +	if (mvotg->qwork) { +		flush_workqueue(mvotg->qwork); +		destroy_workqueue(mvotg->qwork); +	} + +	mv_otg_disable(mvotg); + +	usb_remove_phy(&mvotg->phy); +	platform_set_drvdata(pdev, NULL); + +	return 0; +} + +static int mv_otg_probe(struct platform_device *pdev) +{ +	struct mv_usb_platform_data *pdata = pdev->dev.platform_data; +	struct mv_otg *mvotg; +	struct usb_otg *otg; +	struct resource *r; +	int retval = 0, i; + +	if (pdata == NULL) { +		dev_err(&pdev->dev, "failed to get platform data\n"); +		return -ENODEV; +	} + +	mvotg = devm_kzalloc(&pdev->dev, sizeof(*mvotg), GFP_KERNEL); +	if (!mvotg) { +		dev_err(&pdev->dev, "failed to allocate memory!\n"); +		return -ENOMEM; +	} + +	otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); +	if (!otg) +		return -ENOMEM; + +	platform_set_drvdata(pdev, mvotg); + +	mvotg->pdev = pdev; +	mvotg->pdata = pdata; + +	mvotg->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(mvotg->clk)) +		return PTR_ERR(mvotg->clk); + +	mvotg->qwork = create_singlethread_workqueue("mv_otg_queue"); +	if (!mvotg->qwork) { +		dev_dbg(&pdev->dev, "cannot create workqueue for OTG\n"); +		return -ENOMEM; +	} + +	INIT_DELAYED_WORK(&mvotg->work, mv_otg_work); + +	/* OTG common part */ +	mvotg->pdev = pdev; +	mvotg->phy.dev = &pdev->dev; +	mvotg->phy.otg = otg; +	mvotg->phy.label = driver_name; +	mvotg->phy.state = OTG_STATE_UNDEFINED; + +	otg->phy = &mvotg->phy; +	otg->set_host = mv_otg_set_host; +	otg->set_peripheral = mv_otg_set_peripheral; +	otg->set_vbus = mv_otg_set_vbus; + +	for (i = 0; i < OTG_TIMER_NUM; i++) +		init_timer(&mvotg->otg_ctrl.timer[i]); + +	r = platform_get_resource_byname(mvotg->pdev, +					 IORESOURCE_MEM, "phyregs"); +	if (r == NULL) { +		dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); +		retval = -ENODEV; +		goto err_destroy_workqueue; +	} + +	mvotg->phy_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); +	if (mvotg->phy_regs == NULL) { +		dev_err(&pdev->dev, "failed to map phy I/O memory\n"); +		retval = -EFAULT; +		goto err_destroy_workqueue; +	} + +	r = platform_get_resource_byname(mvotg->pdev, +					 IORESOURCE_MEM, "capregs"); +	if (r == NULL) { +		dev_err(&pdev->dev, "no I/O memory resource defined\n"); +		retval = -ENODEV; +		goto err_destroy_workqueue; +	} + +	mvotg->cap_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); +	if (mvotg->cap_regs == NULL) { +		dev_err(&pdev->dev, "failed to map I/O memory\n"); +		retval = -EFAULT; +		goto err_destroy_workqueue; +	} + +	/* we will acces controller register, so enable the udc controller */ +	retval = mv_otg_enable_internal(mvotg); +	if (retval) { +		dev_err(&pdev->dev, "mv otg enable error %d\n", retval); +		goto err_destroy_workqueue; +	} + +	mvotg->op_regs = +		(struct mv_otg_regs __iomem *) ((unsigned long) mvotg->cap_regs +			+ (readl(mvotg->cap_regs) & CAPLENGTH_MASK)); + +	if (pdata->id) { +		retval = devm_request_threaded_irq(&pdev->dev, pdata->id->irq, +						NULL, mv_otg_inputs_irq, +						IRQF_ONESHOT, "id", mvotg); +		if (retval) { +			dev_info(&pdev->dev, +				 "Failed to request irq for ID\n"); +			pdata->id = NULL; +		} +	} + +	if (pdata->vbus) { +		mvotg->clock_gating = 1; +		retval = devm_request_threaded_irq(&pdev->dev, pdata->vbus->irq, +						NULL, mv_otg_inputs_irq, +						IRQF_ONESHOT, "vbus", mvotg); +		if (retval) { +			dev_info(&pdev->dev, +				 "Failed to request irq for VBUS, " +				 "disable clock gating\n"); +			mvotg->clock_gating = 0; +			pdata->vbus = NULL; +		} +	} + +	if (pdata->disable_otg_clock_gating) +		mvotg->clock_gating = 0; + +	mv_otg_reset(mvotg); +	mv_otg_init_irq(mvotg); + +	r = platform_get_resource(mvotg->pdev, IORESOURCE_IRQ, 0); +	if (r == NULL) { +		dev_err(&pdev->dev, "no IRQ resource defined\n"); +		retval = -ENODEV; +		goto err_disable_clk; +	} + +	mvotg->irq = r->start; +	if (devm_request_irq(&pdev->dev, mvotg->irq, mv_otg_irq, IRQF_SHARED, +			driver_name, mvotg)) { +		dev_err(&pdev->dev, "Request irq %d for OTG failed\n", +			mvotg->irq); +		mvotg->irq = 0; +		retval = -ENODEV; +		goto err_disable_clk; +	} + +	retval = usb_add_phy(&mvotg->phy, USB_PHY_TYPE_USB2); +	if (retval < 0) { +		dev_err(&pdev->dev, "can't register transceiver, %d\n", +			retval); +		goto err_disable_clk; +	} + +	retval = sysfs_create_group(&pdev->dev.kobj, &inputs_attr_group); +	if (retval < 0) { +		dev_dbg(&pdev->dev, +			"Can't register sysfs attr group: %d\n", retval); +		goto err_remove_phy; +	} + +	spin_lock_init(&mvotg->wq_lock); +	if (spin_trylock(&mvotg->wq_lock)) { +		mv_otg_run_state_machine(mvotg, 2 * HZ); +		spin_unlock(&mvotg->wq_lock); +	} + +	dev_info(&pdev->dev, +		 "successful probe OTG device %s clock gating.\n", +		 mvotg->clock_gating ? "with" : "without"); + +	return 0; + +err_remove_phy: +	usb_remove_phy(&mvotg->phy); +err_disable_clk: +	mv_otg_disable_internal(mvotg); +err_destroy_workqueue: +	flush_workqueue(mvotg->qwork); +	destroy_workqueue(mvotg->qwork); + +	platform_set_drvdata(pdev, NULL); + +	return retval; +} + +#ifdef CONFIG_PM +static int mv_otg_suspend(struct platform_device *pdev, pm_message_t state) +{ +	struct mv_otg *mvotg = platform_get_drvdata(pdev); + +	if (mvotg->phy.state != OTG_STATE_B_IDLE) { +		dev_info(&pdev->dev, +			 "OTG state is not B_IDLE, it is %d!\n", +			 mvotg->phy.state); +		return -EAGAIN; +	} + +	if (!mvotg->clock_gating) +		mv_otg_disable_internal(mvotg); + +	return 0; +} + +static int mv_otg_resume(struct platform_device *pdev) +{ +	struct mv_otg *mvotg = platform_get_drvdata(pdev); +	u32 otgsc; + +	if (!mvotg->clock_gating) { +		mv_otg_enable_internal(mvotg); + +		otgsc = readl(&mvotg->op_regs->otgsc); +		otgsc |= mvotg->irq_en; +		writel(otgsc, &mvotg->op_regs->otgsc); + +		if (spin_trylock(&mvotg->wq_lock)) { +			mv_otg_run_state_machine(mvotg, 0); +			spin_unlock(&mvotg->wq_lock); +		} +	} +	return 0; +} +#endif + +static struct platform_driver mv_otg_driver = { +	.probe = mv_otg_probe, +	.remove = __exit_p(mv_otg_remove), +	.driver = { +		   .owner = THIS_MODULE, +		   .name = driver_name, +		   }, +#ifdef CONFIG_PM +	.suspend = mv_otg_suspend, +	.resume = mv_otg_resume, +#endif +}; +module_platform_driver(mv_otg_driver); diff --git a/drivers/usb/phy/phy-mv-usb.h b/drivers/usb/phy/phy-mv-usb.h new file mode 100644 index 00000000000..551da6eb0ba --- /dev/null +++ b/drivers/usb/phy/phy-mv-usb.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of  the GNU General  Public License as published by the + * Free Software Foundation;  either version 2 of the  License, or (at your + * option) any later version. + */ + +#ifndef	__MV_USB_OTG_CONTROLLER__ +#define	__MV_USB_OTG_CONTROLLER__ + +#include <linux/types.h> + +/* Command Register Bit Masks */ +#define USBCMD_RUN_STOP			(0x00000001) +#define USBCMD_CTRL_RESET		(0x00000002) + +/* otgsc Register Bit Masks */ +#define OTGSC_CTRL_VUSB_DISCHARGE		0x00000001 +#define OTGSC_CTRL_VUSB_CHARGE			0x00000002 +#define OTGSC_CTRL_OTG_TERM			0x00000008 +#define OTGSC_CTRL_DATA_PULSING			0x00000010 +#define OTGSC_STS_USB_ID			0x00000100 +#define OTGSC_STS_A_VBUS_VALID			0x00000200 +#define OTGSC_STS_A_SESSION_VALID		0x00000400 +#define OTGSC_STS_B_SESSION_VALID		0x00000800 +#define OTGSC_STS_B_SESSION_END			0x00001000 +#define OTGSC_STS_1MS_TOGGLE			0x00002000 +#define OTGSC_STS_DATA_PULSING			0x00004000 +#define OTGSC_INTSTS_USB_ID			0x00010000 +#define OTGSC_INTSTS_A_VBUS_VALID		0x00020000 +#define OTGSC_INTSTS_A_SESSION_VALID		0x00040000 +#define OTGSC_INTSTS_B_SESSION_VALID		0x00080000 +#define OTGSC_INTSTS_B_SESSION_END		0x00100000 +#define OTGSC_INTSTS_1MS			0x00200000 +#define OTGSC_INTSTS_DATA_PULSING		0x00400000 +#define OTGSC_INTR_USB_ID			0x01000000 +#define OTGSC_INTR_A_VBUS_VALID			0x02000000 +#define OTGSC_INTR_A_SESSION_VALID		0x04000000 +#define OTGSC_INTR_B_SESSION_VALID		0x08000000 +#define OTGSC_INTR_B_SESSION_END		0x10000000 +#define OTGSC_INTR_1MS_TIMER			0x20000000 +#define OTGSC_INTR_DATA_PULSING			0x40000000 + +#define CAPLENGTH_MASK		(0xff) + +/* Timer's interval, unit 10ms */ +#define T_A_WAIT_VRISE		100 +#define T_A_WAIT_BCON		2000 +#define T_A_AIDL_BDIS		100 +#define T_A_BIDL_ADIS		20 +#define T_B_ASE0_BRST		400 +#define T_B_SE0_SRP		300 +#define T_B_SRP_FAIL		2000 +#define T_B_DATA_PLS		10 +#define T_B_SRP_INIT		100 +#define T_A_SRP_RSPNS		10 +#define T_A_DRV_RSM		5 + +enum otg_function { +	OTG_B_DEVICE = 0, +	OTG_A_DEVICE +}; + +enum mv_otg_timer { +	A_WAIT_BCON_TIMER = 0, +	OTG_TIMER_NUM +}; + +/* PXA OTG state machine */ +struct mv_otg_ctrl { +	/* internal variables */ +	u8 a_set_b_hnp_en;	/* A-Device set b_hnp_en */ +	u8 b_srp_done; +	u8 b_hnp_en; + +	/* OTG inputs */ +	u8 a_bus_drop; +	u8 a_bus_req; +	u8 a_clr_err; +	u8 a_bus_resume; +	u8 a_bus_suspend; +	u8 a_conn; +	u8 a_sess_vld; +	u8 a_srp_det; +	u8 a_vbus_vld; +	u8 b_bus_req;		/* B-Device Require Bus */ +	u8 b_bus_resume; +	u8 b_bus_suspend; +	u8 b_conn; +	u8 b_se0_srp; +	u8 b_sess_end; +	u8 b_sess_vld; +	u8 id; +	u8 a_suspend_req; + +	/*Timer event */ +	u8 a_aidl_bdis_timeout; +	u8 b_ase0_brst_timeout; +	u8 a_bidl_adis_timeout; +	u8 a_wait_bcon_timeout; + +	struct timer_list timer[OTG_TIMER_NUM]; +}; + +#define VUSBHS_MAX_PORTS	8 + +struct mv_otg_regs { +	u32 usbcmd;		/* Command register */ +	u32 usbsts;		/* Status register */ +	u32 usbintr;		/* Interrupt enable */ +	u32 frindex;		/* Frame index */ +	u32 reserved1[1]; +	u32 deviceaddr;		/* Device Address */ +	u32 eplistaddr;		/* Endpoint List Address */ +	u32 ttctrl;		/* HOST TT status and control */ +	u32 burstsize;		/* Programmable Burst Size */ +	u32 txfilltuning;	/* Host Transmit Pre-Buffer Packet Tuning */ +	u32 reserved[4]; +	u32 epnak;		/* Endpoint NAK */ +	u32 epnaken;		/* Endpoint NAK Enable */ +	u32 configflag;		/* Configured Flag register */ +	u32 portsc[VUSBHS_MAX_PORTS];	/* Port Status/Control x, x = 1..8 */ +	u32 otgsc; +	u32 usbmode;		/* USB Host/Device mode */ +	u32 epsetupstat;	/* Endpoint Setup Status */ +	u32 epprime;		/* Endpoint Initialize */ +	u32 epflush;		/* Endpoint De-initialize */ +	u32 epstatus;		/* Endpoint Status */ +	u32 epcomplete;		/* Endpoint Interrupt On Complete */ +	u32 epctrlx[16];	/* Endpoint Control, where x = 0.. 15 */ +	u32 mcr;		/* Mux Control */ +	u32 isr;		/* Interrupt Status */ +	u32 ier;		/* Interrupt Enable */ +}; + +struct mv_otg { +	struct usb_phy phy; +	struct mv_otg_ctrl otg_ctrl; + +	/* base address */ +	void __iomem *phy_regs; +	void __iomem *cap_regs; +	struct mv_otg_regs __iomem *op_regs; + +	struct platform_device *pdev; +	int irq; +	u32 irq_status; +	u32 irq_en; + +	struct delayed_work work; +	struct workqueue_struct *qwork; + +	spinlock_t wq_lock; + +	struct mv_usb_platform_data *pdata; + +	unsigned int active; +	unsigned int clock_gating; +	struct clk *clk; +}; + +#endif diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c new file mode 100644 index 00000000000..9d4381e64d5 --- /dev/null +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -0,0 +1,220 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Marek Vasut <marex@denx.de> + * on behalf of DENX Software Engineering GmbH + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/usb/otg.h> +#include <linux/stmp_device.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> + +#define DRIVER_NAME "mxs_phy" + +#define HW_USBPHY_PWD				0x00 +#define HW_USBPHY_CTRL				0x30 +#define HW_USBPHY_CTRL_SET			0x34 +#define HW_USBPHY_CTRL_CLR			0x38 + +#define BM_USBPHY_CTRL_SFTRST			BIT(31) +#define BM_USBPHY_CTRL_CLKGATE			BIT(30) +#define BM_USBPHY_CTRL_ENUTMILEVEL3		BIT(15) +#define BM_USBPHY_CTRL_ENUTMILEVEL2		BIT(14) +#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT	BIT(1) + +struct mxs_phy { +	struct usb_phy phy; +	struct clk *clk; +}; + +#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) + +static void mxs_phy_hw_init(struct mxs_phy *mxs_phy) +{ +	void __iomem *base = mxs_phy->phy.io_priv; + +	stmp_reset_block(base + HW_USBPHY_CTRL); + +	/* Power up the PHY */ +	writel(0, base + HW_USBPHY_PWD); + +	/* enable FS/LS device */ +	writel(BM_USBPHY_CTRL_ENUTMILEVEL2 | +	       BM_USBPHY_CTRL_ENUTMILEVEL3, +	       base + HW_USBPHY_CTRL_SET); +} + +static int mxs_phy_init(struct usb_phy *phy) +{ +	struct mxs_phy *mxs_phy = to_mxs_phy(phy); + +	clk_prepare_enable(mxs_phy->clk); +	mxs_phy_hw_init(mxs_phy); + +	return 0; +} + +static void mxs_phy_shutdown(struct usb_phy *phy) +{ +	struct mxs_phy *mxs_phy = to_mxs_phy(phy); + +	writel(BM_USBPHY_CTRL_CLKGATE, +	       phy->io_priv + HW_USBPHY_CTRL_SET); + +	clk_disable_unprepare(mxs_phy->clk); +} + +static int mxs_phy_suspend(struct usb_phy *x, int suspend) +{ +	struct mxs_phy *mxs_phy = to_mxs_phy(x); + +	if (suspend) { +		writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); +		writel(BM_USBPHY_CTRL_CLKGATE, +		       x->io_priv + HW_USBPHY_CTRL_SET); +		clk_disable_unprepare(mxs_phy->clk); +	} else { +		clk_prepare_enable(mxs_phy->clk); +		writel(BM_USBPHY_CTRL_CLKGATE, +		       x->io_priv + HW_USBPHY_CTRL_CLR); +		writel(0, x->io_priv + HW_USBPHY_PWD); +	} + +	return 0; +} + +static int mxs_phy_on_connect(struct usb_phy *phy, +		enum usb_device_speed speed) +{ +	dev_dbg(phy->dev, "%s speed device has connected\n", +		(speed == USB_SPEED_HIGH) ? "high" : "non-high"); + +	if (speed == USB_SPEED_HIGH) +		writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, +		       phy->io_priv + HW_USBPHY_CTRL_SET); + +	return 0; +} + +static int mxs_phy_on_disconnect(struct usb_phy *phy, +		enum usb_device_speed speed) +{ +	dev_dbg(phy->dev, "%s speed device has disconnected\n", +		(speed == USB_SPEED_HIGH) ? "high" : "non-high"); + +	if (speed == USB_SPEED_HIGH) +		writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, +		       phy->io_priv + HW_USBPHY_CTRL_CLR); + +	return 0; +} + +static int mxs_phy_probe(struct platform_device *pdev) +{ +	struct resource *res; +	void __iomem *base; +	struct clk *clk; +	struct mxs_phy *mxs_phy; +	int ret; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(&pdev->dev, "can't get device resources\n"); +		return -ENOENT; +	} + +	base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(base)) +		return PTR_ERR(base); + +	clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(clk)) { +		dev_err(&pdev->dev, +			"can't get the clock, err=%ld", PTR_ERR(clk)); +		return PTR_ERR(clk); +	} + +	mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL); +	if (!mxs_phy) { +		dev_err(&pdev->dev, "Failed to allocate USB PHY structure!\n"); +		return -ENOMEM; +	} + +	mxs_phy->phy.io_priv		= base; +	mxs_phy->phy.dev		= &pdev->dev; +	mxs_phy->phy.label		= DRIVER_NAME; +	mxs_phy->phy.init		= mxs_phy_init; +	mxs_phy->phy.shutdown		= mxs_phy_shutdown; +	mxs_phy->phy.set_suspend	= mxs_phy_suspend; +	mxs_phy->phy.notify_connect	= mxs_phy_on_connect; +	mxs_phy->phy.notify_disconnect	= mxs_phy_on_disconnect; + +	ATOMIC_INIT_NOTIFIER_HEAD(&mxs_phy->phy.notifier); + +	mxs_phy->clk = clk; + +	platform_set_drvdata(pdev, &mxs_phy->phy); + +	ret = usb_add_phy_dev(&mxs_phy->phy); +	if (ret) +		return ret; + +	return 0; +} + +static int mxs_phy_remove(struct platform_device *pdev) +{ +	struct mxs_phy *mxs_phy = platform_get_drvdata(pdev); + +	usb_remove_phy(&mxs_phy->phy); + +	platform_set_drvdata(pdev, NULL); + +	return 0; +} + +static const struct of_device_id mxs_phy_dt_ids[] = { +	{ .compatible = "fsl,imx23-usbphy", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids); + +static struct platform_driver mxs_phy_driver = { +	.probe = mxs_phy_probe, +	.remove = mxs_phy_remove, +	.driver = { +		.name = DRIVER_NAME, +		.owner	= THIS_MODULE, +		.of_match_table = mxs_phy_dt_ids, +	 }, +}; + +static int __init mxs_phy_module_init(void) +{ +	return platform_driver_register(&mxs_phy_driver); +} +postcore_initcall(mxs_phy_module_init); + +static void __exit mxs_phy_module_exit(void) +{ +	platform_driver_unregister(&mxs_phy_driver); +} +module_exit(mxs_phy_module_exit); + +MODULE_ALIAS("platform:mxs-usb-phy"); +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>"); +MODULE_DESCRIPTION("Freescale MXS USB PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-nop.c b/drivers/usb/phy/phy-nop.c new file mode 100644 index 00000000000..2b10cc969bb --- /dev/null +++ b/drivers/usb/phy/phy-nop.c @@ -0,0 +1,294 @@ +/* + * drivers/usb/otg/nop-usb-xceiv.c + * + * NOP USB transceiver for all USB transceiver which are either built-in + * into USB IP or which are mostly autonomous. + * + * Copyright (C) 2009 Texas Instruments Inc + * Author: Ajay Kumar Gupta <ajay.gupta@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + *	This provides a "nop" transceiver for PHYs which are + *	autonomous such as isp1504, isp1707, etc. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/usb/otg.h> +#include <linux/usb/nop-usb-xceiv.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/of.h> + +struct nop_usb_xceiv { +	struct usb_phy phy; +	struct device *dev; +	struct clk *clk; +	struct regulator *vcc; +	struct regulator *reset; +}; + +static struct platform_device *pd; + +void usb_nop_xceiv_register(void) +{ +	if (pd) +		return; +	pd = platform_device_register_simple("nop_usb_xceiv", -1, NULL, 0); +	if (!pd) { +		printk(KERN_ERR "Unable to register usb nop transceiver\n"); +		return; +	} +} +EXPORT_SYMBOL(usb_nop_xceiv_register); + +void usb_nop_xceiv_unregister(void) +{ +	platform_device_unregister(pd); +	pd = NULL; +} +EXPORT_SYMBOL(usb_nop_xceiv_unregister); + +static int nop_set_suspend(struct usb_phy *x, int suspend) +{ +	return 0; +} + +static int nop_init(struct usb_phy *phy) +{ +	struct nop_usb_xceiv *nop = dev_get_drvdata(phy->dev); + +	if (!IS_ERR(nop->vcc)) { +		if (regulator_enable(nop->vcc)) +			dev_err(phy->dev, "Failed to enable power\n"); +	} + +	if (!IS_ERR(nop->clk)) +		clk_enable(nop->clk); + +	if (!IS_ERR(nop->reset)) { +		/* De-assert RESET */ +		if (regulator_enable(nop->reset)) +			dev_err(phy->dev, "Failed to de-assert reset\n"); +	} + +	return 0; +} + +static void nop_shutdown(struct usb_phy *phy) +{ +	struct nop_usb_xceiv *nop = dev_get_drvdata(phy->dev); + +	if (!IS_ERR(nop->reset)) { +		/* Assert RESET */ +		if (regulator_disable(nop->reset)) +			dev_err(phy->dev, "Failed to assert reset\n"); +	} + +	if (!IS_ERR(nop->clk)) +		clk_disable(nop->clk); + +	if (!IS_ERR(nop->vcc)) { +		if (regulator_disable(nop->vcc)) +			dev_err(phy->dev, "Failed to disable power\n"); +	} +} + +static int nop_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) +{ +	if (!otg) +		return -ENODEV; + +	if (!gadget) { +		otg->gadget = NULL; +		return -ENODEV; +	} + +	otg->gadget = gadget; +	otg->phy->state = OTG_STATE_B_IDLE; +	return 0; +} + +static int nop_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	if (!otg) +		return -ENODEV; + +	if (!host) { +		otg->host = NULL; +		return -ENODEV; +	} + +	otg->host = host; +	return 0; +} + +static int nop_usb_xceiv_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; +	struct nop_usb_xceiv_platform_data *pdata = pdev->dev.platform_data; +	struct nop_usb_xceiv	*nop; +	enum usb_phy_type	type = USB_PHY_TYPE_USB2; +	int err; +	u32 clk_rate = 0; +	bool needs_vcc = false; +	bool needs_reset = false; + +	nop = devm_kzalloc(&pdev->dev, sizeof(*nop), GFP_KERNEL); +	if (!nop) +		return -ENOMEM; + +	nop->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*nop->phy.otg), +							GFP_KERNEL); +	if (!nop->phy.otg) +		return -ENOMEM; + +	if (dev->of_node) { +		struct device_node *node = dev->of_node; + +		if (of_property_read_u32(node, "clock-frequency", &clk_rate)) +			clk_rate = 0; + +		needs_vcc = of_property_read_bool(node, "vcc-supply"); +		needs_reset = of_property_read_bool(node, "reset-supply"); + +	} else if (pdata) { +		type = pdata->type; +		clk_rate = pdata->clk_rate; +		needs_vcc = pdata->needs_vcc; +		needs_reset = pdata->needs_reset; +	} + +	nop->clk = devm_clk_get(&pdev->dev, "main_clk"); +	if (IS_ERR(nop->clk)) { +		dev_dbg(&pdev->dev, "Can't get phy clock: %ld\n", +					PTR_ERR(nop->clk)); +	} + +	if (!IS_ERR(nop->clk) && clk_rate) { +		err = clk_set_rate(nop->clk, clk_rate); +		if (err) { +			dev_err(&pdev->dev, "Error setting clock rate\n"); +			return err; +		} +	} + +	if (!IS_ERR(nop->clk)) { +		err = clk_prepare(nop->clk); +		if (err) { +			dev_err(&pdev->dev, "Error preparing clock\n"); +			return err; +		} +	} + +	nop->vcc = devm_regulator_get(&pdev->dev, "vcc"); +	if (IS_ERR(nop->vcc)) { +		dev_dbg(&pdev->dev, "Error getting vcc regulator: %ld\n", +					PTR_ERR(nop->vcc)); +		if (needs_vcc) +			return -EPROBE_DEFER; +	} + +	nop->reset = devm_regulator_get(&pdev->dev, "reset"); +	if (IS_ERR(nop->reset)) { +		dev_dbg(&pdev->dev, "Error getting reset regulator: %ld\n", +					PTR_ERR(nop->reset)); +		if (needs_reset) +			return -EPROBE_DEFER; +	} + +	nop->dev		= &pdev->dev; +	nop->phy.dev		= nop->dev; +	nop->phy.label		= "nop-xceiv"; +	nop->phy.set_suspend	= nop_set_suspend; +	nop->phy.init		= nop_init; +	nop->phy.shutdown	= nop_shutdown; +	nop->phy.state		= OTG_STATE_UNDEFINED; +	nop->phy.type		= type; + +	nop->phy.otg->phy		= &nop->phy; +	nop->phy.otg->set_host		= nop_set_host; +	nop->phy.otg->set_peripheral	= nop_set_peripheral; + +	err = usb_add_phy_dev(&nop->phy); +	if (err) { +		dev_err(&pdev->dev, "can't register transceiver, err: %d\n", +			err); +		goto err_add; +	} + +	platform_set_drvdata(pdev, nop); + +	ATOMIC_INIT_NOTIFIER_HEAD(&nop->phy.notifier); + +	return 0; + +err_add: +	if (!IS_ERR(nop->clk)) +		clk_unprepare(nop->clk); +	return err; +} + +static int nop_usb_xceiv_remove(struct platform_device *pdev) +{ +	struct nop_usb_xceiv *nop = platform_get_drvdata(pdev); + +	if (!IS_ERR(nop->clk)) +		clk_unprepare(nop->clk); + +	usb_remove_phy(&nop->phy); + +	platform_set_drvdata(pdev, NULL); + +	return 0; +} + +static const struct of_device_id nop_xceiv_dt_ids[] = { +	{ .compatible = "usb-nop-xceiv" }, +	{ } +}; + +MODULE_DEVICE_TABLE(of, nop_xceiv_dt_ids); + +static struct platform_driver nop_usb_xceiv_driver = { +	.probe		= nop_usb_xceiv_probe, +	.remove		= nop_usb_xceiv_remove, +	.driver		= { +		.name	= "nop_usb_xceiv", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(nop_xceiv_dt_ids), +	}, +}; + +static int __init nop_usb_xceiv_init(void) +{ +	return platform_driver_register(&nop_usb_xceiv_driver); +} +subsys_initcall(nop_usb_xceiv_init); + +static void __exit nop_usb_xceiv_exit(void) +{ +	platform_driver_unregister(&nop_usb_xceiv_driver); +} +module_exit(nop_usb_xceiv_exit); + +MODULE_ALIAS("platform:nop_usb_xceiv"); +MODULE_AUTHOR("Texas Instruments Inc"); +MODULE_DESCRIPTION("NOP USB Transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/omap-control-usb.c b/drivers/usb/phy/phy-omap-control.c index 1419ceda975..1419ceda975 100644 --- a/drivers/usb/phy/omap-control-usb.c +++ b/drivers/usb/phy/phy-omap-control.c diff --git a/drivers/usb/phy/omap-usb2.c b/drivers/usb/phy/phy-omap-usb2.c index 844ab68f08d..844ab68f08d 100644 --- a/drivers/usb/phy/omap-usb2.c +++ b/drivers/usb/phy/phy-omap-usb2.c diff --git a/drivers/usb/phy/omap-usb3.c b/drivers/usb/phy/phy-omap-usb3.c index a6e60b1e102..a6e60b1e102 100644 --- a/drivers/usb/phy/omap-usb3.c +++ b/drivers/usb/phy/phy-omap-usb3.c diff --git a/drivers/usb/phy/rcar-phy.c b/drivers/usb/phy/phy-rcar-usb.c index a35681b0c50..a35681b0c50 100644 --- a/drivers/usb/phy/rcar-phy.c +++ b/drivers/usb/phy/phy-rcar-usb.c diff --git a/drivers/usb/phy/phy-samsung-usb.c b/drivers/usb/phy/phy-samsung-usb.c new file mode 100644 index 00000000000..7b118ee5f5e --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb.c @@ -0,0 +1,236 @@ +/* linux/drivers/usb/phy/phy-samsung-usb.c + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + *              http://www.samsung.com + * + * Author: Praveen Paneri <p.paneri@samsung.com> + * + * Samsung USB-PHY helper driver with common function calls; + * interacts with Samsung USB 2.0 PHY controller driver and later + * with Samsung USB 3.0 PHY driver. + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/usb/samsung_usb_phy.h> + +#include "phy-samsung-usb.h" + +int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy) +{ +	struct device_node *usbphy_sys; + +	/* Getting node for system controller interface for usb-phy */ +	usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys"); +	if (!usbphy_sys) { +		dev_err(sphy->dev, "No sys-controller interface for usb-phy\n"); +		return -ENODEV; +	} + +	sphy->pmuregs = of_iomap(usbphy_sys, 0); + +	if (sphy->pmuregs == NULL) { +		dev_err(sphy->dev, "Can't get usb-phy pmu control register\n"); +		goto err0; +	} + +	sphy->sysreg = of_iomap(usbphy_sys, 1); + +	/* +	 * Not returning error code here, since this situation is not fatal. +	 * Few SoCs may not have this switch available +	 */ +	if (sphy->sysreg == NULL) +		dev_warn(sphy->dev, "Can't get usb-phy sysreg cfg register\n"); + +	of_node_put(usbphy_sys); + +	return 0; + +err0: +	of_node_put(usbphy_sys); +	return -ENXIO; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_parse_dt); + +/* + * Set isolation here for phy. + * Here 'on = true' would mean USB PHY block is isolated, hence + * de-activated and vice-versa. + */ +void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) +{ +	void __iomem *reg = NULL; +	u32 reg_val; +	u32 en_mask = 0; + +	if (!sphy->pmuregs) { +		dev_warn(sphy->dev, "Can't set pmu isolation\n"); +		return; +	} + +	switch (sphy->drv_data->cpu_type) { +	case TYPE_S3C64XX: +		/* +		 * Do nothing: We will add here once S3C64xx goes for DT support +		 */ +		break; +	case TYPE_EXYNOS4210: +		/* +		 * Fall through since exynos4210 and exynos5250 have similar +		 * register architecture: two separate registers for host and +		 * device phy control with enable bit at position 0. +		 */ +	case TYPE_EXYNOS5250: +		if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { +			reg = sphy->pmuregs + +				sphy->drv_data->devphy_reg_offset; +			en_mask = sphy->drv_data->devphy_en_mask; +		} else if (sphy->phy_type == USB_PHY_TYPE_HOST) { +			reg = sphy->pmuregs + +				sphy->drv_data->hostphy_reg_offset; +			en_mask = sphy->drv_data->hostphy_en_mask; +		} +		break; +	default: +		dev_err(sphy->dev, "Invalid SoC type\n"); +		return; +	} + +	reg_val = readl(reg); + +	if (on) +		reg_val &= ~en_mask; +	else +		reg_val |= en_mask; + +	writel(reg_val, reg); +} +EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation); + +/* + * Configure the mode of working of usb-phy here: HOST/DEVICE. + */ +void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy) +{ +	u32 reg; + +	if (!sphy->sysreg) { +		dev_warn(sphy->dev, "Can't configure specified phy mode\n"); +		return; +	} + +	reg = readl(sphy->sysreg); + +	if (sphy->phy_type == USB_PHY_TYPE_DEVICE) +		reg &= ~EXYNOS_USB20PHY_CFG_HOST_LINK; +	else if (sphy->phy_type == USB_PHY_TYPE_HOST) +		reg |= EXYNOS_USB20PHY_CFG_HOST_LINK; + +	writel(reg, sphy->sysreg); +} +EXPORT_SYMBOL_GPL(samsung_usbphy_cfg_sel); + +/* + * PHYs are different for USB Device and USB Host. + * This make sure that correct PHY type is selected before + * any operation on PHY. + */ +int samsung_usbphy_set_type(struct usb_phy *phy, +				enum samsung_usb_phy_type phy_type) +{ +	struct samsung_usbphy *sphy = phy_to_sphy(phy); + +	sphy->phy_type = phy_type; + +	return 0; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_set_type); + +/* + * Returns reference clock frequency selection value + */ +int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) +{ +	struct clk *ref_clk; +	int refclk_freq = 0; + +	/* +	 * In exynos5250 USB host and device PHY use +	 * external crystal clock XXTI +	 */ +	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) +		ref_clk = devm_clk_get(sphy->dev, "ext_xtal"); +	else +		ref_clk = devm_clk_get(sphy->dev, "xusbxti"); +	if (IS_ERR(ref_clk)) { +		dev_err(sphy->dev, "Failed to get reference clock\n"); +		return PTR_ERR(ref_clk); +	} + +	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) { +		/* set clock frequency for PLL */ +		switch (clk_get_rate(ref_clk)) { +		case 9600 * KHZ: +			refclk_freq = FSEL_CLKSEL_9600K; +			break; +		case 10 * MHZ: +			refclk_freq = FSEL_CLKSEL_10M; +			break; +		case 12 * MHZ: +			refclk_freq = FSEL_CLKSEL_12M; +			break; +		case 19200 * KHZ: +			refclk_freq = FSEL_CLKSEL_19200K; +			break; +		case 20 * MHZ: +			refclk_freq = FSEL_CLKSEL_20M; +			break; +		case 50 * MHZ: +			refclk_freq = FSEL_CLKSEL_50M; +			break; +		case 24 * MHZ: +		default: +			/* default reference clock */ +			refclk_freq = FSEL_CLKSEL_24M; +			break; +		} +	} else { +		switch (clk_get_rate(ref_clk)) { +		case 12 * MHZ: +			refclk_freq = PHYCLK_CLKSEL_12M; +			break; +		case 24 * MHZ: +			refclk_freq = PHYCLK_CLKSEL_24M; +			break; +		case 48 * MHZ: +			refclk_freq = PHYCLK_CLKSEL_48M; +			break; +		default: +			if (sphy->drv_data->cpu_type == TYPE_S3C64XX) +				refclk_freq = PHYCLK_CLKSEL_48M; +			else +				refclk_freq = PHYCLK_CLKSEL_24M; +			break; +		} +	} +	clk_put(ref_clk); + +	return refclk_freq; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_get_refclk_freq); diff --git a/drivers/usb/phy/phy-samsung-usb.h b/drivers/usb/phy/phy-samsung-usb.h new file mode 100644 index 00000000000..70a9cae5e37 --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb.h @@ -0,0 +1,327 @@ +/* linux/drivers/usb/phy/phy-samsung-usb.h + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + *              http://www.samsung.com + * + * Samsung USB-PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and + * OHCI-EXYNOS controllers. + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/usb/phy.h> + +/* Register definitions */ + +#define SAMSUNG_PHYPWR				(0x00) + +#define PHYPWR_NORMAL_MASK			(0x19 << 0) +#define PHYPWR_OTG_DISABLE			(0x1 << 4) +#define PHYPWR_ANALOG_POWERDOWN			(0x1 << 3) +#define PHYPWR_FORCE_SUSPEND			(0x1 << 1) +/* For Exynos4 */ +#define PHYPWR_NORMAL_MASK_PHY0			(0x39 << 0) +#define PHYPWR_SLEEP_PHY0			(0x1 << 5) + +#define SAMSUNG_PHYCLK				(0x04) + +#define PHYCLK_MODE_USB11			(0x1 << 6) +#define PHYCLK_EXT_OSC				(0x1 << 5) +#define PHYCLK_COMMON_ON_N			(0x1 << 4) +#define PHYCLK_ID_PULL				(0x1 << 2) +#define PHYCLK_CLKSEL_MASK			(0x3 << 0) +#define PHYCLK_CLKSEL_48M			(0x0 << 0) +#define PHYCLK_CLKSEL_12M			(0x2 << 0) +#define PHYCLK_CLKSEL_24M			(0x3 << 0) + +#define SAMSUNG_RSTCON				(0x08) + +#define RSTCON_PHYLINK_SWRST			(0x1 << 2) +#define RSTCON_HLINK_SWRST			(0x1 << 1) +#define RSTCON_SWRST				(0x1 << 0) + +/* EXYNOS5 */ +#define EXYNOS5_PHY_HOST_CTRL0			(0x00) + +#define HOST_CTRL0_PHYSWRSTALL			(0x1 << 31) + +#define HOST_CTRL0_REFCLKSEL_MASK		(0x3 << 19) +#define HOST_CTRL0_REFCLKSEL_XTAL		(0x0 << 19) +#define HOST_CTRL0_REFCLKSEL_EXTL		(0x1 << 19) +#define HOST_CTRL0_REFCLKSEL_CLKCORE		(0x2 << 19) + +#define HOST_CTRL0_FSEL_MASK			(0x7 << 16) +#define HOST_CTRL0_FSEL(_x)			((_x) << 16) + +#define FSEL_CLKSEL_50M				(0x7) +#define FSEL_CLKSEL_24M				(0x5) +#define FSEL_CLKSEL_20M				(0x4) +#define FSEL_CLKSEL_19200K			(0x3) +#define FSEL_CLKSEL_12M				(0x2) +#define FSEL_CLKSEL_10M				(0x1) +#define FSEL_CLKSEL_9600K			(0x0) + +#define HOST_CTRL0_TESTBURNIN			(0x1 << 11) +#define HOST_CTRL0_RETENABLE			(0x1 << 10) +#define HOST_CTRL0_COMMONON_N			(0x1 << 9) +#define HOST_CTRL0_SIDDQ			(0x1 << 6) +#define HOST_CTRL0_FORCESLEEP			(0x1 << 5) +#define HOST_CTRL0_FORCESUSPEND			(0x1 << 4) +#define HOST_CTRL0_WORDINTERFACE		(0x1 << 3) +#define HOST_CTRL0_UTMISWRST			(0x1 << 2) +#define HOST_CTRL0_LINKSWRST			(0x1 << 1) +#define HOST_CTRL0_PHYSWRST			(0x1 << 0) + +#define EXYNOS5_PHY_HOST_TUNE0			(0x04) + +#define EXYNOS5_PHY_HSIC_CTRL1			(0x10) + +#define EXYNOS5_PHY_HSIC_TUNE1			(0x14) + +#define EXYNOS5_PHY_HSIC_CTRL2			(0x20) + +#define EXYNOS5_PHY_HSIC_TUNE2			(0x24) + +#define HSIC_CTRL_REFCLKSEL_MASK		(0x3 << 23) +#define HSIC_CTRL_REFCLKSEL			(0x2 << 23) + +#define HSIC_CTRL_REFCLKDIV_MASK		(0x7f << 16) +#define HSIC_CTRL_REFCLKDIV(_x)			((_x) << 16) +#define HSIC_CTRL_REFCLKDIV_12			(0x24 << 16) +#define HSIC_CTRL_REFCLKDIV_15			(0x1c << 16) +#define HSIC_CTRL_REFCLKDIV_16			(0x1a << 16) +#define HSIC_CTRL_REFCLKDIV_19_2		(0x15 << 16) +#define HSIC_CTRL_REFCLKDIV_20			(0x14 << 16) + +#define HSIC_CTRL_SIDDQ				(0x1 << 6) +#define HSIC_CTRL_FORCESLEEP			(0x1 << 5) +#define HSIC_CTRL_FORCESUSPEND			(0x1 << 4) +#define HSIC_CTRL_WORDINTERFACE			(0x1 << 3) +#define HSIC_CTRL_UTMISWRST			(0x1 << 2) +#define HSIC_CTRL_PHYSWRST			(0x1 << 0) + +#define EXYNOS5_PHY_HOST_EHCICTRL		(0x30) + +#define HOST_EHCICTRL_ENAINCRXALIGN		(0x1 << 29) +#define HOST_EHCICTRL_ENAINCR4			(0x1 << 28) +#define HOST_EHCICTRL_ENAINCR8			(0x1 << 27) +#define HOST_EHCICTRL_ENAINCR16			(0x1 << 26) + +#define EXYNOS5_PHY_HOST_OHCICTRL		(0x34) + +#define HOST_OHCICTRL_SUSPLGCY			(0x1 << 3) +#define HOST_OHCICTRL_APPSTARTCLK		(0x1 << 2) +#define HOST_OHCICTRL_CNTSEL			(0x1 << 1) +#define HOST_OHCICTRL_CLKCKTRST			(0x1 << 0) + +#define EXYNOS5_PHY_OTG_SYS			(0x38) + +#define OTG_SYS_PHYLINK_SWRESET			(0x1 << 14) +#define OTG_SYS_LINKSWRST_UOTG			(0x1 << 13) +#define OTG_SYS_PHY0_SWRST			(0x1 << 12) + +#define OTG_SYS_REFCLKSEL_MASK			(0x3 << 9) +#define OTG_SYS_REFCLKSEL_XTAL			(0x0 << 9) +#define OTG_SYS_REFCLKSEL_EXTL			(0x1 << 9) +#define OTG_SYS_REFCLKSEL_CLKCORE		(0x2 << 9) + +#define OTG_SYS_IDPULLUP_UOTG			(0x1 << 8) +#define OTG_SYS_COMMON_ON			(0x1 << 7) + +#define OTG_SYS_FSEL_MASK			(0x7 << 4) +#define OTG_SYS_FSEL(_x)			((_x) << 4) + +#define OTG_SYS_FORCESLEEP			(0x1 << 3) +#define OTG_SYS_OTGDISABLE			(0x1 << 2) +#define OTG_SYS_SIDDQ_UOTG			(0x1 << 1) +#define OTG_SYS_FORCESUSPEND			(0x1 << 0) + +#define EXYNOS5_PHY_OTG_TUNE			(0x40) + +/* EXYNOS5: USB 3.0 DRD */ +#define EXYNOS5_DRD_LINKSYSTEM			(0x04) + +#define LINKSYSTEM_FLADJ_MASK			(0x3f << 1) +#define LINKSYSTEM_FLADJ(_x)			((_x) << 1) +#define LINKSYSTEM_XHCI_VERSION_CONTROL		(0x1 << 27) + +#define EXYNOS5_DRD_PHYUTMI			(0x08) + +#define PHYUTMI_OTGDISABLE			(0x1 << 6) +#define PHYUTMI_FORCESUSPEND			(0x1 << 1) +#define PHYUTMI_FORCESLEEP			(0x1 << 0) + +#define EXYNOS5_DRD_PHYPIPE			(0x0c) + +#define EXYNOS5_DRD_PHYCLKRST			(0x10) + +#define PHYCLKRST_SSC_REFCLKSEL_MASK		(0xff << 23) +#define PHYCLKRST_SSC_REFCLKSEL(_x)		((_x) << 23) + +#define PHYCLKRST_SSC_RANGE_MASK		(0x03 << 21) +#define PHYCLKRST_SSC_RANGE(_x)			((_x) << 21) + +#define PHYCLKRST_SSC_EN			(0x1 << 20) +#define PHYCLKRST_REF_SSP_EN			(0x1 << 19) +#define PHYCLKRST_REF_CLKDIV2			(0x1 << 18) + +#define PHYCLKRST_MPLL_MULTIPLIER_MASK		(0x7f << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF	(0x19 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF	(0x02 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF	(0x68 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF	(0x7d << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF	(0x02 << 11) + +#define PHYCLKRST_FSEL_MASK			(0x3f << 5) +#define PHYCLKRST_FSEL(_x)			((_x) << 5) +#define PHYCLKRST_FSEL_PAD_100MHZ		(0x27 << 5) +#define PHYCLKRST_FSEL_PAD_24MHZ		(0x2a << 5) +#define PHYCLKRST_FSEL_PAD_20MHZ		(0x31 << 5) +#define PHYCLKRST_FSEL_PAD_19_2MHZ		(0x38 << 5) + +#define PHYCLKRST_RETENABLEN			(0x1 << 4) + +#define PHYCLKRST_REFCLKSEL_MASK		(0x03 << 2) +#define PHYCLKRST_REFCLKSEL_PAD_REFCLK		(0x2 << 2) +#define PHYCLKRST_REFCLKSEL_EXT_REFCLK		(0x3 << 2) + +#define PHYCLKRST_PORTRESET			(0x1 << 1) +#define PHYCLKRST_COMMONONN			(0x1 << 0) + +#define EXYNOS5_DRD_PHYREG0			(0x14) +#define EXYNOS5_DRD_PHYREG1			(0x18) + +#define EXYNOS5_DRD_PHYPARAM0			(0x1c) + +#define PHYPARAM0_REF_USE_PAD			(0x1 << 31) +#define PHYPARAM0_REF_LOSLEVEL_MASK		(0x1f << 26) +#define PHYPARAM0_REF_LOSLEVEL			(0x9 << 26) + +#define EXYNOS5_DRD_PHYPARAM1			(0x20) + +#define PHYPARAM1_PCS_TXDEEMPH_MASK		(0x1f << 0) +#define PHYPARAM1_PCS_TXDEEMPH			(0x1c) + +#define EXYNOS5_DRD_PHYTERM			(0x24) + +#define EXYNOS5_DRD_PHYTEST			(0x28) + +#define PHYTEST_POWERDOWN_SSP			(0x1 << 3) +#define PHYTEST_POWERDOWN_HSP			(0x1 << 2) + +#define EXYNOS5_DRD_PHYADP			(0x2c) + +#define EXYNOS5_DRD_PHYBATCHG			(0x30) + +#define PHYBATCHG_UTMI_CLKSEL			(0x1 << 2) + +#define EXYNOS5_DRD_PHYRESUME			(0x34) +#define EXYNOS5_DRD_LINKPORT			(0x44) + +#ifndef MHZ +#define MHZ (1000*1000) +#endif + +#ifndef KHZ +#define KHZ (1000) +#endif + +#define EXYNOS_USBHOST_PHY_CTRL_OFFSET		(0x4) +#define S3C64XX_USBPHY_ENABLE			(0x1 << 16) +#define EXYNOS_USBPHY_ENABLE			(0x1 << 0) +#define EXYNOS_USB20PHY_CFG_HOST_LINK		(0x1 << 0) + +enum samsung_cpu_type { +	TYPE_S3C64XX, +	TYPE_EXYNOS4210, +	TYPE_EXYNOS5250, +}; + +/* + * struct samsung_usbphy_drvdata - driver data for various SoC variants + * @cpu_type: machine identifier + * @devphy_en_mask: device phy enable mask for PHY CONTROL register + * @hostphy_en_mask: host phy enable mask for PHY CONTROL register + * @devphy_reg_offset: offset to DEVICE PHY CONTROL register from + *		       mapped address of system controller. + * @hostphy_reg_offset: offset to HOST PHY CONTROL register from + *		       mapped address of system controller. + * + *	Here we have a separate mask for device type phy. + *	Having different masks for host and device type phy helps + *	in setting independent masks in case of SoCs like S5PV210, + *	in which PHY0 and PHY1 enable bits belong to same register + *	placed at position 0 and 1 respectively. + *	Although for newer SoCs like exynos these bits belong to + *	different registers altogether placed at position 0. + */ +struct samsung_usbphy_drvdata { +	int cpu_type; +	int devphy_en_mask; +	int hostphy_en_mask; +	u32 devphy_reg_offset; +	u32 hostphy_reg_offset; +}; + +/* + * struct samsung_usbphy - transceiver driver state + * @phy: transceiver structure + * @plat: platform data + * @dev: The parent device supplied to the probe function + * @clk: usb phy clock + * @regs: usb phy controller registers memory base + * @pmuregs: USB device PHY_CONTROL register memory base + * @sysreg: USB2.0 PHY_CFG register memory base + * @ref_clk_freq: reference clock frequency selection + * @drv_data: driver data available for different SoCs + * @phy_type: Samsung SoCs specific phy types:	#HOST + *						#DEVICE + * @phy_usage: usage count for phy + * @lock: lock for phy operations + */ +struct samsung_usbphy { +	struct usb_phy	phy; +	struct samsung_usbphy_data *plat; +	struct device	*dev; +	struct clk	*clk; +	void __iomem	*regs; +	void __iomem	*pmuregs; +	void __iomem	*sysreg; +	int		ref_clk_freq; +	const struct samsung_usbphy_drvdata *drv_data; +	enum samsung_usb_phy_type phy_type; +	atomic_t	phy_usage; +	spinlock_t	lock; +}; + +#define phy_to_sphy(x)		container_of((x), struct samsung_usbphy, phy) + +static const struct of_device_id samsung_usbphy_dt_match[]; + +static inline const struct samsung_usbphy_drvdata +*samsung_usbphy_get_driver_data(struct platform_device *pdev) +{ +	if (pdev->dev.of_node) { +		const struct of_device_id *match; +		match = of_match_node(samsung_usbphy_dt_match, +							pdev->dev.of_node); +		return match->data; +	} + +	return (struct samsung_usbphy_drvdata *) +				platform_get_device_id(pdev)->driver_data; +} + +extern int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy); +extern void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on); +extern void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy); +extern int samsung_usbphy_set_type(struct usb_phy *phy, +					enum samsung_usb_phy_type phy_type); +extern int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy); diff --git a/drivers/usb/phy/phy-samsung-usb2.c b/drivers/usb/phy/phy-samsung-usb2.c new file mode 100644 index 00000000000..45ffe036dac --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb2.c @@ -0,0 +1,509 @@ +/* linux/drivers/usb/phy/phy-samsung-usb2.c + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + *              http://www.samsung.com + * + * Author: Praveen Paneri <p.paneri@samsung.com> + * + * Samsung USB2.0 PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and + * OHCI-EXYNOS controllers. + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/usb/otg.h> +#include <linux/usb/samsung_usb_phy.h> +#include <linux/platform_data/samsung-usbphy.h> + +#include "phy-samsung-usb.h" + +static int samsung_usbphy_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	if (!otg) +		return -ENODEV; + +	if (!otg->host) +		otg->host = host; + +	return 0; +} + +static bool exynos5_phyhost_is_on(void __iomem *regs) +{ +	u32 reg; + +	reg = readl(regs + EXYNOS5_PHY_HOST_CTRL0); + +	return !(reg & HOST_CTRL0_SIDDQ); +} + +static void samsung_exynos5_usb2phy_enable(struct samsung_usbphy *sphy) +{ +	void __iomem *regs = sphy->regs; +	u32 phyclk = sphy->ref_clk_freq; +	u32 phyhost; +	u32 phyotg; +	u32 phyhsic; +	u32 ehcictrl; +	u32 ohcictrl; + +	/* +	 * phy_usage helps in keeping usage count for phy +	 * so that the first consumer enabling the phy is also +	 * the last consumer to disable it. +	 */ + +	atomic_inc(&sphy->phy_usage); + +	if (exynos5_phyhost_is_on(regs)) { +		dev_info(sphy->dev, "Already power on PHY\n"); +		return; +	} + +	/* Host configuration */ +	phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0); + +	/* phy reference clock configuration */ +	phyhost &= ~HOST_CTRL0_FSEL_MASK; +	phyhost |= HOST_CTRL0_FSEL(phyclk); + +	/* host phy reset */ +	phyhost &= ~(HOST_CTRL0_PHYSWRST | +			HOST_CTRL0_PHYSWRSTALL | +			HOST_CTRL0_SIDDQ | +			/* Enable normal mode of operation */ +			HOST_CTRL0_FORCESUSPEND | +			HOST_CTRL0_FORCESLEEP); + +	/* Link reset */ +	phyhost |= (HOST_CTRL0_LINKSWRST | +			HOST_CTRL0_UTMISWRST | +			/* COMMON Block configuration during suspend */ +			HOST_CTRL0_COMMONON_N); +	writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); +	udelay(10); +	phyhost &= ~(HOST_CTRL0_LINKSWRST | +			HOST_CTRL0_UTMISWRST); +	writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); + +	/* OTG configuration */ +	phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS); + +	/* phy reference clock configuration */ +	phyotg &= ~OTG_SYS_FSEL_MASK; +	phyotg |= OTG_SYS_FSEL(phyclk); + +	/* Enable normal mode of operation */ +	phyotg &= ~(OTG_SYS_FORCESUSPEND | +			OTG_SYS_SIDDQ_UOTG | +			OTG_SYS_FORCESLEEP | +			OTG_SYS_REFCLKSEL_MASK | +			/* COMMON Block configuration during suspend */ +			OTG_SYS_COMMON_ON); + +	/* OTG phy & link reset */ +	phyotg |= (OTG_SYS_PHY0_SWRST | +			OTG_SYS_LINKSWRST_UOTG | +			OTG_SYS_PHYLINK_SWRESET | +			OTG_SYS_OTGDISABLE | +			/* Set phy refclk */ +			OTG_SYS_REFCLKSEL_CLKCORE); + +	writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); +	udelay(10); +	phyotg &= ~(OTG_SYS_PHY0_SWRST | +			OTG_SYS_LINKSWRST_UOTG | +			OTG_SYS_PHYLINK_SWRESET); +	writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); + +	/* HSIC phy configuration */ +	phyhsic = (HSIC_CTRL_REFCLKDIV_12 | +			HSIC_CTRL_REFCLKSEL | +			HSIC_CTRL_PHYSWRST); +	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); +	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); +	udelay(10); +	phyhsic &= ~HSIC_CTRL_PHYSWRST; +	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); +	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); + +	udelay(80); + +	/* enable EHCI DMA burst */ +	ehcictrl = readl(regs + EXYNOS5_PHY_HOST_EHCICTRL); +	ehcictrl |= (HOST_EHCICTRL_ENAINCRXALIGN | +				HOST_EHCICTRL_ENAINCR4 | +				HOST_EHCICTRL_ENAINCR8 | +				HOST_EHCICTRL_ENAINCR16); +	writel(ehcictrl, regs + EXYNOS5_PHY_HOST_EHCICTRL); + +	/* set ohci_suspend_on_n */ +	ohcictrl = readl(regs + EXYNOS5_PHY_HOST_OHCICTRL); +	ohcictrl |= HOST_OHCICTRL_SUSPLGCY; +	writel(ohcictrl, regs + EXYNOS5_PHY_HOST_OHCICTRL); +} + +static void samsung_usb2phy_enable(struct samsung_usbphy *sphy) +{ +	void __iomem *regs = sphy->regs; +	u32 phypwr; +	u32 phyclk; +	u32 rstcon; + +	/* set clock frequency for PLL */ +	phyclk = sphy->ref_clk_freq; +	phypwr = readl(regs + SAMSUNG_PHYPWR); +	rstcon = readl(regs + SAMSUNG_RSTCON); + +	switch (sphy->drv_data->cpu_type) { +	case TYPE_S3C64XX: +		phyclk &= ~PHYCLK_COMMON_ON_N; +		phypwr &= ~PHYPWR_NORMAL_MASK; +		rstcon |= RSTCON_SWRST; +		break; +	case TYPE_EXYNOS4210: +		phypwr &= ~PHYPWR_NORMAL_MASK_PHY0; +		rstcon |= RSTCON_SWRST; +	default: +		break; +	} + +	writel(phyclk, regs + SAMSUNG_PHYCLK); +	/* Configure PHY0 for normal operation*/ +	writel(phypwr, regs + SAMSUNG_PHYPWR); +	/* reset all ports of PHY and Link */ +	writel(rstcon, regs + SAMSUNG_RSTCON); +	udelay(10); +	rstcon &= ~RSTCON_SWRST; +	writel(rstcon, regs + SAMSUNG_RSTCON); +} + +static void samsung_exynos5_usb2phy_disable(struct samsung_usbphy *sphy) +{ +	void __iomem *regs = sphy->regs; +	u32 phyhost; +	u32 phyotg; +	u32 phyhsic; + +	if (atomic_dec_return(&sphy->phy_usage) > 0) { +		dev_info(sphy->dev, "still being used\n"); +		return; +	} + +	phyhsic = (HSIC_CTRL_REFCLKDIV_12 | +			HSIC_CTRL_REFCLKSEL | +			HSIC_CTRL_SIDDQ | +			HSIC_CTRL_FORCESLEEP | +			HSIC_CTRL_FORCESUSPEND); +	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); +	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); + +	phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0); +	phyhost |= (HOST_CTRL0_SIDDQ | +			HOST_CTRL0_FORCESUSPEND | +			HOST_CTRL0_FORCESLEEP | +			HOST_CTRL0_PHYSWRST | +			HOST_CTRL0_PHYSWRSTALL); +	writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); + +	phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS); +	phyotg |= (OTG_SYS_FORCESUSPEND | +			OTG_SYS_SIDDQ_UOTG | +			OTG_SYS_FORCESLEEP); +	writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); +} + +static void samsung_usb2phy_disable(struct samsung_usbphy *sphy) +{ +	void __iomem *regs = sphy->regs; +	u32 phypwr; + +	phypwr = readl(regs + SAMSUNG_PHYPWR); + +	switch (sphy->drv_data->cpu_type) { +	case TYPE_S3C64XX: +		phypwr |= PHYPWR_NORMAL_MASK; +		break; +	case TYPE_EXYNOS4210: +		phypwr |= PHYPWR_NORMAL_MASK_PHY0; +	default: +		break; +	} + +	/* Disable analog and otg block power */ +	writel(phypwr, regs + SAMSUNG_PHYPWR); +} + +/* + * The function passed to the usb driver for phy initialization + */ +static int samsung_usb2phy_init(struct usb_phy *phy) +{ +	struct samsung_usbphy *sphy; +	struct usb_bus *host = NULL; +	unsigned long flags; +	int ret = 0; + +	sphy = phy_to_sphy(phy); + +	host = phy->otg->host; + +	/* Enable the phy clock */ +	ret = clk_prepare_enable(sphy->clk); +	if (ret) { +		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); +		return ret; +	} + +	spin_lock_irqsave(&sphy->lock, flags); + +	if (host) { +		/* setting default phy-type for USB 2.0 */ +		if (!strstr(dev_name(host->controller), "ehci") || +				!strstr(dev_name(host->controller), "ohci")) +			samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST); +	} else { +		samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); +	} + +	/* Disable phy isolation */ +	if (sphy->plat && sphy->plat->pmu_isolation) +		sphy->plat->pmu_isolation(false); +	else +		samsung_usbphy_set_isolation(sphy, false); + +	/* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */ +	samsung_usbphy_cfg_sel(sphy); + +	/* Initialize usb phy registers */ +	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) +		samsung_exynos5_usb2phy_enable(sphy); +	else +		samsung_usb2phy_enable(sphy); + +	spin_unlock_irqrestore(&sphy->lock, flags); + +	/* Disable the phy clock */ +	clk_disable_unprepare(sphy->clk); + +	return ret; +} + +/* + * The function passed to the usb driver for phy shutdown + */ +static void samsung_usb2phy_shutdown(struct usb_phy *phy) +{ +	struct samsung_usbphy *sphy; +	struct usb_bus *host = NULL; +	unsigned long flags; + +	sphy = phy_to_sphy(phy); + +	host = phy->otg->host; + +	if (clk_prepare_enable(sphy->clk)) { +		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); +		return; +	} + +	spin_lock_irqsave(&sphy->lock, flags); + +	if (host) { +		/* setting default phy-type for USB 2.0 */ +		if (!strstr(dev_name(host->controller), "ehci") || +				!strstr(dev_name(host->controller), "ohci")) +			samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST); +	} else { +		samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); +	} + +	/* De-initialize usb phy registers */ +	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) +		samsung_exynos5_usb2phy_disable(sphy); +	else +		samsung_usb2phy_disable(sphy); + +	/* Enable phy isolation */ +	if (sphy->plat && sphy->plat->pmu_isolation) +		sphy->plat->pmu_isolation(true); +	else +		samsung_usbphy_set_isolation(sphy, true); + +	spin_unlock_irqrestore(&sphy->lock, flags); + +	clk_disable_unprepare(sphy->clk); +} + +static int samsung_usb2phy_probe(struct platform_device *pdev) +{ +	struct samsung_usbphy *sphy; +	struct usb_otg *otg; +	struct samsung_usbphy_data *pdata = pdev->dev.platform_data; +	const struct samsung_usbphy_drvdata *drv_data; +	struct device *dev = &pdev->dev; +	struct resource *phy_mem; +	void __iomem	*phy_base; +	struct clk *clk; +	int ret; + +	phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!phy_mem) { +		dev_err(dev, "%s: missing mem resource\n", __func__); +		return -ENODEV; +	} + +	phy_base = devm_ioremap_resource(dev, phy_mem); +	if (IS_ERR(phy_base)) +		return PTR_ERR(phy_base); + +	sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); +	if (!sphy) +		return -ENOMEM; + +	otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL); +	if (!otg) +		return -ENOMEM; + +	drv_data = samsung_usbphy_get_driver_data(pdev); + +	if (drv_data->cpu_type == TYPE_EXYNOS5250) +		clk = devm_clk_get(dev, "usbhost"); +	else +		clk = devm_clk_get(dev, "otg"); + +	if (IS_ERR(clk)) { +		dev_err(dev, "Failed to get otg clock\n"); +		return PTR_ERR(clk); +	} + +	sphy->dev = dev; + +	if (dev->of_node) { +		ret = samsung_usbphy_parse_dt(sphy); +		if (ret < 0) +			return ret; +	} else { +		if (!pdata) { +			dev_err(dev, "no platform data specified\n"); +			return -EINVAL; +		} +	} + +	sphy->plat		= pdata; +	sphy->regs		= phy_base; +	sphy->clk		= clk; +	sphy->drv_data		= drv_data; +	sphy->phy.dev		= sphy->dev; +	sphy->phy.label		= "samsung-usb2phy"; +	sphy->phy.init		= samsung_usb2phy_init; +	sphy->phy.shutdown	= samsung_usb2phy_shutdown; +	sphy->ref_clk_freq	= samsung_usbphy_get_refclk_freq(sphy); + +	sphy->phy.otg		= otg; +	sphy->phy.otg->phy	= &sphy->phy; +	sphy->phy.otg->set_host = samsung_usbphy_set_host; + +	spin_lock_init(&sphy->lock); + +	platform_set_drvdata(pdev, sphy); + +	return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB2); +} + +static int samsung_usb2phy_remove(struct platform_device *pdev) +{ +	struct samsung_usbphy *sphy = platform_get_drvdata(pdev); + +	usb_remove_phy(&sphy->phy); + +	if (sphy->pmuregs) +		iounmap(sphy->pmuregs); +	if (sphy->sysreg) +		iounmap(sphy->sysreg); + +	return 0; +} + +static const struct samsung_usbphy_drvdata usb2phy_s3c64xx = { +	.cpu_type		= TYPE_S3C64XX, +	.devphy_en_mask		= S3C64XX_USBPHY_ENABLE, +}; + +static const struct samsung_usbphy_drvdata usb2phy_exynos4 = { +	.cpu_type		= TYPE_EXYNOS4210, +	.devphy_en_mask		= EXYNOS_USBPHY_ENABLE, +	.hostphy_en_mask	= EXYNOS_USBPHY_ENABLE, +}; + +static struct samsung_usbphy_drvdata usb2phy_exynos5 = { +	.cpu_type		= TYPE_EXYNOS5250, +	.hostphy_en_mask	= EXYNOS_USBPHY_ENABLE, +	.hostphy_reg_offset	= EXYNOS_USBHOST_PHY_CTRL_OFFSET, +}; + +#ifdef CONFIG_OF +static const struct of_device_id samsung_usbphy_dt_match[] = { +	{ +		.compatible = "samsung,s3c64xx-usb2phy", +		.data = &usb2phy_s3c64xx, +	}, { +		.compatible = "samsung,exynos4210-usb2phy", +		.data = &usb2phy_exynos4, +	}, { +		.compatible = "samsung,exynos5250-usb2phy", +		.data = &usb2phy_exynos5 +	}, +	{}, +}; +MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match); +#endif + +static struct platform_device_id samsung_usbphy_driver_ids[] = { +	{ +		.name		= "s3c64xx-usb2phy", +		.driver_data	= (unsigned long)&usb2phy_s3c64xx, +	}, { +		.name		= "exynos4210-usb2phy", +		.driver_data	= (unsigned long)&usb2phy_exynos4, +	}, { +		.name		= "exynos5250-usb2phy", +		.driver_data	= (unsigned long)&usb2phy_exynos5, +	}, +	{}, +}; + +MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids); + +static struct platform_driver samsung_usb2phy_driver = { +	.probe		= samsung_usb2phy_probe, +	.remove		= samsung_usb2phy_remove, +	.id_table	= samsung_usbphy_driver_ids, +	.driver		= { +		.name	= "samsung-usb2phy", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(samsung_usbphy_dt_match), +	}, +}; + +module_platform_driver(samsung_usb2phy_driver); + +MODULE_DESCRIPTION("Samsung USB 2.0 phy controller"); +MODULE_AUTHOR("Praveen Paneri <p.paneri@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-usb2phy"); diff --git a/drivers/usb/phy/phy-samsung-usb3.c b/drivers/usb/phy/phy-samsung-usb3.c new file mode 100644 index 00000000000..133f3d0c554 --- /dev/null +++ b/drivers/usb/phy/phy-samsung-usb3.c @@ -0,0 +1,347 @@ +/* linux/drivers/usb/phy/phy-samsung-usb3.c + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + *              http://www.samsung.com + * + * Author: Vivek Gautam <gautam.vivek@samsung.com> + * + * Samsung USB 3.0 PHY transceiver; talks to DWC3 controller. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/usb/samsung_usb_phy.h> +#include <linux/platform_data/samsung-usbphy.h> + +#include "phy-samsung-usb.h" + +/* + * Sets the phy clk as EXTREFCLK (XXTI) which is internal clock from clock core. + */ +static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy) +{ +	u32 reg; +	u32 refclk; + +	refclk = sphy->ref_clk_freq; + +	reg = PHYCLKRST_REFCLKSEL_EXT_REFCLK | +		PHYCLKRST_FSEL(refclk); + +	switch (refclk) { +	case FSEL_CLKSEL_50M: +		reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF | +			PHYCLKRST_SSC_REFCLKSEL(0x00)); +		break; +	case FSEL_CLKSEL_20M: +		reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF | +			PHYCLKRST_SSC_REFCLKSEL(0x00)); +		break; +	case FSEL_CLKSEL_19200K: +		reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF | +			PHYCLKRST_SSC_REFCLKSEL(0x88)); +		break; +	case FSEL_CLKSEL_24M: +	default: +		reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF | +			PHYCLKRST_SSC_REFCLKSEL(0x88)); +		break; +	} + +	return reg; +} + +static int samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) +{ +	void __iomem *regs = sphy->regs; +	u32 phyparam0; +	u32 phyparam1; +	u32 linksystem; +	u32 phybatchg; +	u32 phytest; +	u32 phyclkrst; + +	/* Reset USB 3.0 PHY */ +	writel(0x0, regs + EXYNOS5_DRD_PHYREG0); + +	phyparam0 = readl(regs + EXYNOS5_DRD_PHYPARAM0); +	/* Select PHY CLK source */ +	phyparam0 &= ~PHYPARAM0_REF_USE_PAD; +	/* Set Loss-of-Signal Detector sensitivity */ +	phyparam0 &= ~PHYPARAM0_REF_LOSLEVEL_MASK; +	phyparam0 |= PHYPARAM0_REF_LOSLEVEL; +	writel(phyparam0, regs + EXYNOS5_DRD_PHYPARAM0); + +	writel(0x0, regs + EXYNOS5_DRD_PHYRESUME); + +	/* +	 * Setting the Frame length Adj value[6:1] to default 0x20 +	 * See xHCI 1.0 spec, 5.2.4 +	 */ +	linksystem = LINKSYSTEM_XHCI_VERSION_CONTROL | +			LINKSYSTEM_FLADJ(0x20); +	writel(linksystem, regs + EXYNOS5_DRD_LINKSYSTEM); + +	phyparam1 = readl(regs + EXYNOS5_DRD_PHYPARAM1); +	/* Set Tx De-Emphasis level */ +	phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; +	phyparam1 |= PHYPARAM1_PCS_TXDEEMPH; +	writel(phyparam1, regs + EXYNOS5_DRD_PHYPARAM1); + +	phybatchg = readl(regs + EXYNOS5_DRD_PHYBATCHG); +	phybatchg |= PHYBATCHG_UTMI_CLKSEL; +	writel(phybatchg, regs + EXYNOS5_DRD_PHYBATCHG); + +	/* PHYTEST POWERDOWN Control */ +	phytest = readl(regs + EXYNOS5_DRD_PHYTEST); +	phytest &= ~(PHYTEST_POWERDOWN_SSP | +			PHYTEST_POWERDOWN_HSP); +	writel(phytest, regs + EXYNOS5_DRD_PHYTEST); + +	/* UTMI Power Control */ +	writel(PHYUTMI_OTGDISABLE, regs + EXYNOS5_DRD_PHYUTMI); + +	phyclkrst = samsung_usb3phy_set_refclk(sphy); + +	phyclkrst |= PHYCLKRST_PORTRESET | +			/* Digital power supply in normal operating mode */ +			PHYCLKRST_RETENABLEN | +			/* Enable ref clock for SS function */ +			PHYCLKRST_REF_SSP_EN | +			/* Enable spread spectrum */ +			PHYCLKRST_SSC_EN | +			/* Power down HS Bias and PLL blocks in suspend mode */ +			PHYCLKRST_COMMONONN; + +	writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); + +	udelay(10); + +	phyclkrst &= ~(PHYCLKRST_PORTRESET); +	writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); + +	return 0; +} + +static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy) +{ +	u32 phyutmi; +	u32 phyclkrst; +	u32 phytest; +	void __iomem *regs = sphy->regs; + +	phyutmi = PHYUTMI_OTGDISABLE | +			PHYUTMI_FORCESUSPEND | +			PHYUTMI_FORCESLEEP; +	writel(phyutmi, regs + EXYNOS5_DRD_PHYUTMI); + +	/* Resetting the PHYCLKRST enable bits to reduce leakage current */ +	phyclkrst = readl(regs + EXYNOS5_DRD_PHYCLKRST); +	phyclkrst &= ~(PHYCLKRST_REF_SSP_EN | +			PHYCLKRST_SSC_EN | +			PHYCLKRST_COMMONONN); +	writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); + +	/* Control PHYTEST to remove leakage current */ +	phytest = readl(regs + EXYNOS5_DRD_PHYTEST); +	phytest |= (PHYTEST_POWERDOWN_SSP | +			PHYTEST_POWERDOWN_HSP); +	writel(phytest, regs + EXYNOS5_DRD_PHYTEST); +} + +static int samsung_usb3phy_init(struct usb_phy *phy) +{ +	struct samsung_usbphy *sphy; +	unsigned long flags; +	int ret = 0; + +	sphy = phy_to_sphy(phy); + +	/* Enable the phy clock */ +	ret = clk_prepare_enable(sphy->clk); +	if (ret) { +		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); +		return ret; +	} + +	spin_lock_irqsave(&sphy->lock, flags); + +	/* setting default phy-type for USB 3.0 */ +	samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); + +	/* Disable phy isolation */ +	samsung_usbphy_set_isolation(sphy, false); + +	/* Initialize usb phy registers */ +	samsung_exynos5_usb3phy_enable(sphy); + +	spin_unlock_irqrestore(&sphy->lock, flags); + +	/* Disable the phy clock */ +	clk_disable_unprepare(sphy->clk); + +	return ret; +} + +/* + * The function passed to the usb driver for phy shutdown + */ +static void samsung_usb3phy_shutdown(struct usb_phy *phy) +{ +	struct samsung_usbphy *sphy; +	unsigned long flags; + +	sphy = phy_to_sphy(phy); + +	if (clk_prepare_enable(sphy->clk)) { +		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); +		return; +	} + +	spin_lock_irqsave(&sphy->lock, flags); + +	/* setting default phy-type for USB 3.0 */ +	samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); + +	/* De-initialize usb phy registers */ +	samsung_exynos5_usb3phy_disable(sphy); + +	/* Enable phy isolation */ +	samsung_usbphy_set_isolation(sphy, true); + +	spin_unlock_irqrestore(&sphy->lock, flags); + +	clk_disable_unprepare(sphy->clk); +} + +static int samsung_usb3phy_probe(struct platform_device *pdev) +{ +	struct samsung_usbphy *sphy; +	struct samsung_usbphy_data *pdata = pdev->dev.platform_data; +	struct device *dev = &pdev->dev; +	struct resource *phy_mem; +	void __iomem	*phy_base; +	struct clk *clk; +	int ret; + +	phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!phy_mem) { +		dev_err(dev, "%s: missing mem resource\n", __func__); +		return -ENODEV; +	} + +	phy_base = devm_ioremap_resource(dev, phy_mem); +	if (IS_ERR(phy_base)) +		return PTR_ERR(phy_base); + +	sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); +	if (!sphy) +		return -ENOMEM; + +	clk = devm_clk_get(dev, "usbdrd30"); +	if (IS_ERR(clk)) { +		dev_err(dev, "Failed to get device clock\n"); +		return PTR_ERR(clk); +	} + +	sphy->dev = dev; + +	if (dev->of_node) { +		ret = samsung_usbphy_parse_dt(sphy); +		if (ret < 0) +			return ret; +	} else { +		if (!pdata) { +			dev_err(dev, "no platform data specified\n"); +			return -EINVAL; +		} +	} + +	sphy->plat		= pdata; +	sphy->regs		= phy_base; +	sphy->clk		= clk; +	sphy->phy.dev		= sphy->dev; +	sphy->phy.label		= "samsung-usb3phy"; +	sphy->phy.init		= samsung_usb3phy_init; +	sphy->phy.shutdown	= samsung_usb3phy_shutdown; +	sphy->drv_data		= samsung_usbphy_get_driver_data(pdev); +	sphy->ref_clk_freq	= samsung_usbphy_get_refclk_freq(sphy); + +	spin_lock_init(&sphy->lock); + +	platform_set_drvdata(pdev, sphy); + +	return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB3); +} + +static int samsung_usb3phy_remove(struct platform_device *pdev) +{ +	struct samsung_usbphy *sphy = platform_get_drvdata(pdev); + +	usb_remove_phy(&sphy->phy); + +	if (sphy->pmuregs) +		iounmap(sphy->pmuregs); +	if (sphy->sysreg) +		iounmap(sphy->sysreg); + +	return 0; +} + +static struct samsung_usbphy_drvdata usb3phy_exynos5 = { +	.cpu_type		= TYPE_EXYNOS5250, +	.devphy_en_mask		= EXYNOS_USBPHY_ENABLE, +}; + +#ifdef CONFIG_OF +static const struct of_device_id samsung_usbphy_dt_match[] = { +	{ +		.compatible = "samsung,exynos5250-usb3phy", +		.data = &usb3phy_exynos5 +	}, +	{}, +}; +MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match); +#endif + +static struct platform_device_id samsung_usbphy_driver_ids[] = { +	{ +		.name		= "exynos5250-usb3phy", +		.driver_data	= (unsigned long)&usb3phy_exynos5, +	}, +	{}, +}; + +MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids); + +static struct platform_driver samsung_usb3phy_driver = { +	.probe		= samsung_usb3phy_probe, +	.remove		= samsung_usb3phy_remove, +	.id_table	= samsung_usbphy_driver_ids, +	.driver		= { +		.name	= "samsung-usb3phy", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(samsung_usbphy_dt_match), +	}, +}; + +module_platform_driver(samsung_usb3phy_driver); + +MODULE_DESCRIPTION("Samsung USB 3.0 phy controller"); +MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-usb3phy"); diff --git a/drivers/usb/phy/tegra_usb_phy.c b/drivers/usb/phy/phy-tegra-usb.c index 5487d38481a..17d811292f3 100644 --- a/drivers/usb/phy/tegra_usb_phy.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -299,7 +299,7 @@ static void utmi_phy_clk_disable(struct tegra_usb_phy *phy)  		val &= ~USB_SUSP_SET;  		writel(val, base + USB_SUSP_CTRL);  	} else -		tegra_ehci_set_phcd(&phy->u_phy, true); +		phy->set_phcd(&phy->u_phy, true);  	if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) < 0)  		pr_err("%s: timeout waiting for phy to stabilize\n", __func__); @@ -321,7 +321,7 @@ static void utmi_phy_clk_enable(struct tegra_usb_phy *phy)  		val &= ~USB_SUSP_CLR;  		writel(val, base + USB_SUSP_CTRL);  	} else -		tegra_ehci_set_phcd(&phy->u_phy, false); +		phy->set_phcd(&phy->u_phy, false);  	if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID,  						     USB_PHY_CLK_VALID)) @@ -444,7 +444,7 @@ static int utmi_phy_power_on(struct tegra_usb_phy *phy)  	utmi_phy_clk_enable(phy);  	if (!phy->is_legacy_phy) -		tegra_ehci_set_pts(&phy->u_phy, 0); +		phy->set_pts(&phy->u_phy, 0);  	return 0;  } @@ -688,7 +688,10 @@ static int	tegra_usb_phy_suspend(struct usb_phy *x, int suspend)  }  struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance, -	void __iomem *regs, void *config, enum tegra_usb_phy_mode phy_mode) +	void __iomem *regs, void *config, enum tegra_usb_phy_mode phy_mode, +	void (*set_pts)(struct usb_phy *x, u8 pts_val), +	void (*set_phcd)(struct usb_phy *x, bool enable)) +  {  	struct tegra_usb_phy *phy;  	unsigned long parent_rate; @@ -707,6 +710,8 @@ struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance,  	phy->dev = dev;  	phy->is_legacy_phy =  		of_property_read_bool(np, "nvidia,has-legacy-mode"); +	phy->set_pts = set_pts; +	phy->set_phcd = set_phcd;  	err = of_property_match_string(np, "phy_type", "ulpi");  	if (err < 0)  		phy->is_ulpi_phy = false; diff --git a/drivers/usb/phy/phy-twl4030-usb.c b/drivers/usb/phy/phy-twl4030-usb.c new file mode 100644 index 00000000000..8f78d2d4072 --- /dev/null +++ b/drivers/usb/phy/phy-twl4030-usb.c @@ -0,0 +1,794 @@ +/* + * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004-2007 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Contact: Felipe Balbi <felipe.balbi@nokia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + *	- HS USB ULPI mode works. + *	- 3-pin mode support may be added in future. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/usb/otg.h> +#include <linux/usb/musb-omap.h> +#include <linux/usb/ulpi.h> +#include <linux/i2c/twl.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> + +/* Register defines */ + +#define MCPC_CTRL			0x30 +#define MCPC_CTRL_RTSOL			(1 << 7) +#define MCPC_CTRL_EXTSWR		(1 << 6) +#define MCPC_CTRL_EXTSWC		(1 << 5) +#define MCPC_CTRL_VOICESW		(1 << 4) +#define MCPC_CTRL_OUT64K		(1 << 3) +#define MCPC_CTRL_RTSCTSSW		(1 << 2) +#define MCPC_CTRL_HS_UART		(1 << 0) + +#define MCPC_IO_CTRL			0x33 +#define MCPC_IO_CTRL_MICBIASEN		(1 << 5) +#define MCPC_IO_CTRL_CTS_NPU		(1 << 4) +#define MCPC_IO_CTRL_RXD_PU		(1 << 3) +#define MCPC_IO_CTRL_TXDTYP		(1 << 2) +#define MCPC_IO_CTRL_CTSTYP		(1 << 1) +#define MCPC_IO_CTRL_RTSTYP		(1 << 0) + +#define MCPC_CTRL2			0x36 +#define MCPC_CTRL2_MCPC_CK_EN		(1 << 0) + +#define OTHER_FUNC_CTRL			0x80 +#define OTHER_FUNC_CTRL_BDIS_ACON_EN	(1 << 4) +#define OTHER_FUNC_CTRL_FIVEWIRE_MODE	(1 << 2) + +#define OTHER_IFC_CTRL			0x83 +#define OTHER_IFC_CTRL_OE_INT_EN	(1 << 6) +#define OTHER_IFC_CTRL_CEA2011_MODE	(1 << 5) +#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN	(1 << 4) +#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT	(1 << 3) +#define OTHER_IFC_CTRL_HIZ_ULPI		(1 << 2) +#define OTHER_IFC_CTRL_ALT_INT_REROUTE	(1 << 0) + +#define OTHER_INT_EN_RISE		0x86 +#define OTHER_INT_EN_FALL		0x89 +#define OTHER_INT_STS			0x8C +#define OTHER_INT_LATCH			0x8D +#define OTHER_INT_VB_SESS_VLD		(1 << 7) +#define OTHER_INT_DM_HI			(1 << 6) /* not valid for "latch" reg */ +#define OTHER_INT_DP_HI			(1 << 5) /* not valid for "latch" reg */ +#define OTHER_INT_BDIS_ACON		(1 << 3) /* not valid for "fall" regs */ +#define OTHER_INT_MANU			(1 << 1) +#define OTHER_INT_ABNORMAL_STRESS	(1 << 0) + +#define ID_STATUS			0x96 +#define ID_RES_FLOAT			(1 << 4) +#define ID_RES_440K			(1 << 3) +#define ID_RES_200K			(1 << 2) +#define ID_RES_102K			(1 << 1) +#define ID_RES_GND			(1 << 0) + +#define POWER_CTRL			0xAC +#define POWER_CTRL_OTG_ENAB		(1 << 5) + +#define OTHER_IFC_CTRL2			0xAF +#define OTHER_IFC_CTRL2_ULPI_STP_LOW	(1 << 4) +#define OTHER_IFC_CTRL2_ULPI_TXEN_POL	(1 << 3) +#define OTHER_IFC_CTRL2_ULPI_4PIN_2430	(1 << 2) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK	(3 << 0) /* bits 0 and 1 */ +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N	(0 << 0) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N	(1 << 0) + +#define REG_CTRL_EN			0xB2 +#define REG_CTRL_ERROR			0xB5 +#define ULPI_I2C_CONFLICT_INTEN		(1 << 0) + +#define OTHER_FUNC_CTRL2		0xB8 +#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN	(1 << 0) + +/* following registers do not have separate _clr and _set registers */ +#define VBUS_DEBOUNCE			0xC0 +#define ID_DEBOUNCE			0xC1 +#define VBAT_TIMER			0xD3 +#define PHY_PWR_CTRL			0xFD +#define PHY_PWR_PHYPWD			(1 << 0) +#define PHY_CLK_CTRL			0xFE +#define PHY_CLK_CTRL_CLOCKGATING_EN	(1 << 2) +#define PHY_CLK_CTRL_CLK32K_EN		(1 << 1) +#define REQ_PHY_DPLL_CLK		(1 << 0) +#define PHY_CLK_CTRL_STS		0xFF +#define PHY_DPLL_CLK			(1 << 0) + +/* In module TWL_MODULE_PM_MASTER */ +#define STS_HW_CONDITIONS		0x0F + +/* In module TWL_MODULE_PM_RECEIVER */ +#define VUSB_DEDICATED1			0x7D +#define VUSB_DEDICATED2			0x7E +#define VUSB1V5_DEV_GRP			0x71 +#define VUSB1V5_TYPE			0x72 +#define VUSB1V5_REMAP			0x73 +#define VUSB1V8_DEV_GRP			0x74 +#define VUSB1V8_TYPE			0x75 +#define VUSB1V8_REMAP			0x76 +#define VUSB3V1_DEV_GRP			0x77 +#define VUSB3V1_TYPE			0x78 +#define VUSB3V1_REMAP			0x79 + +/* In module TWL4030_MODULE_INTBR */ +#define PMBR1				0x0D +#define GPIO_USB_4PIN_ULPI_2430C	(3 << 0) + +struct twl4030_usb { +	struct usb_phy		phy; +	struct device		*dev; + +	/* TWL4030 internal USB regulator supplies */ +	struct regulator	*usb1v5; +	struct regulator	*usb1v8; +	struct regulator	*usb3v1; + +	/* for vbus reporting with irqs disabled */ +	spinlock_t		lock; + +	/* pin configuration */ +	enum twl4030_usb_mode	usb_mode; + +	int			irq; +	enum omap_musb_vbus_id_status linkstat; +	bool			vbus_supplied; +	u8			asleep; +	bool			irq_enabled; + +	struct delayed_work	id_workaround_work; +}; + +/* internal define on top of container_of */ +#define phy_to_twl(x)		container_of((x), struct twl4030_usb, phy) + +/*-------------------------------------------------------------------------*/ + +static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl, +		u8 module, u8 data, u8 address) +{ +	u8 check; + +	if ((twl_i2c_write_u8(module, data, address) >= 0) && +	    (twl_i2c_read_u8(module, &check, address) >= 0) && +						(check == data)) +		return 0; +	dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", +			1, module, address, check, data); + +	/* Failed once: Try again */ +	if ((twl_i2c_write_u8(module, data, address) >= 0) && +	    (twl_i2c_read_u8(module, &check, address) >= 0) && +						(check == data)) +		return 0; +	dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", +			2, module, address, check, data); + +	/* Failed again: Return error */ +	return -EBUSY; +} + +#define twl4030_usb_write_verify(twl, address, data)	\ +	twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address)) + +static inline int twl4030_usb_write(struct twl4030_usb *twl, +		u8 address, u8 data) +{ +	int ret = 0; + +	ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address); +	if (ret < 0) +		dev_dbg(twl->dev, +			"TWL4030:USB:Write[0x%x] Error %d\n", address, ret); +	return ret; +} + +static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address) +{ +	u8 data; +	int ret = 0; + +	ret = twl_i2c_read_u8(module, &data, address); +	if (ret >= 0) +		ret = data; +	else +		dev_dbg(twl->dev, +			"TWL4030:readb[0x%x,0x%x] Error %d\n", +					module, address, ret); + +	return ret; +} + +static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) +{ +	return twl4030_readb(twl, TWL_MODULE_USB, address); +} + +/*-------------------------------------------------------------------------*/ + +static inline int +twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ +	return twl4030_usb_write(twl, ULPI_SET(reg), bits); +} + +static inline int +twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ +	return twl4030_usb_write(twl, ULPI_CLR(reg), bits); +} + +/*-------------------------------------------------------------------------*/ + +static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) +{ +	int ret; + +	ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS); +	if (ret < 0 || !(ret & PHY_DPLL_CLK)) +		/* +		 * if clocks are off, registers are not updated, +		 * but we can assume we don't drive VBUS in this case +		 */ +		return false; + +	ret = twl4030_usb_read(twl, ULPI_OTG_CTRL); +	if (ret < 0) +		return false; + +	return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; +} + +static enum omap_musb_vbus_id_status +	twl4030_usb_linkstat(struct twl4030_usb *twl) +{ +	int	status; +	enum omap_musb_vbus_id_status linkstat = OMAP_MUSB_UNKNOWN; + +	twl->vbus_supplied = false; + +	/* +	 * For ID/VBUS sensing, see manual section 15.4.8 ... +	 * except when using only battery backup power, two +	 * comparators produce VBUS_PRES and ID_PRES signals, +	 * which don't match docs elsewhere.  But ... BIT(7) +	 * and BIT(2) of STS_HW_CONDITIONS, respectively, do +	 * seem to match up.  If either is true the USB_PRES +	 * signal is active, the OTG module is activated, and +	 * its interrupt may be raised (may wake the system). +	 */ +	status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS); +	if (status < 0) +		dev_err(twl->dev, "USB link status err %d\n", status); +	else if (status & (BIT(7) | BIT(2))) { +		if (status & BIT(7)) { +			if (twl4030_is_driving_vbus(twl)) +				status &= ~BIT(7); +			else +				twl->vbus_supplied = true; +		} + +		if (status & BIT(2)) +			linkstat = OMAP_MUSB_ID_GROUND; +		else if (status & BIT(7)) +			linkstat = OMAP_MUSB_VBUS_VALID; +		else +			linkstat = OMAP_MUSB_VBUS_OFF; +	} else { +		if (twl->linkstat != OMAP_MUSB_UNKNOWN) +			linkstat = OMAP_MUSB_VBUS_OFF; +	} + +	dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", +			status, status, linkstat); + +	/* REVISIT this assumes host and peripheral controllers +	 * are registered, and that both are active... +	 */ + +	return linkstat; +} + +static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) +{ +	twl->usb_mode = mode; + +	switch (mode) { +	case T2_USB_MODE_ULPI: +		twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL, +					ULPI_IFC_CTRL_CARKITMODE); +		twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); +		twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL, +					ULPI_FUNC_CTRL_XCVRSEL_MASK | +					ULPI_FUNC_CTRL_OPMODE_MASK); +		break; +	case -1: +		/* FIXME: power on defaults */ +		break; +	default: +		dev_err(twl->dev, "unsupported T2 transceiver mode %d\n", +				mode); +		break; +	}; +} + +static void twl4030_i2c_access(struct twl4030_usb *twl, int on) +{ +	unsigned long timeout; +	int val = twl4030_usb_read(twl, PHY_CLK_CTRL); + +	if (val >= 0) { +		if (on) { +			/* enable DPLL to access PHY registers over I2C */ +			val |= REQ_PHY_DPLL_CLK; +			WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, +						(u8)val) < 0); + +			timeout = jiffies + HZ; +			while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & +							PHY_DPLL_CLK) +				&& time_before(jiffies, timeout)) +					udelay(10); +			if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & +							PHY_DPLL_CLK)) +				dev_err(twl->dev, "Timeout setting T2 HSUSB " +						"PHY DPLL clock\n"); +		} else { +			/* let ULPI control the DPLL clock */ +			val &= ~REQ_PHY_DPLL_CLK; +			WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, +						(u8)val) < 0); +		} +	} +} + +static void __twl4030_phy_power(struct twl4030_usb *twl, int on) +{ +	u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); + +	if (on) +		pwr &= ~PHY_PWR_PHYPWD; +	else +		pwr |= PHY_PWR_PHYPWD; + +	WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); +} + +static void twl4030_phy_power(struct twl4030_usb *twl, int on) +{ +	int ret; + +	if (on) { +		ret = regulator_enable(twl->usb3v1); +		if (ret) +			dev_err(twl->dev, "Failed to enable usb3v1\n"); + +		ret = regulator_enable(twl->usb1v8); +		if (ret) +			dev_err(twl->dev, "Failed to enable usb1v8\n"); + +		/* +		 * Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP +		 * in twl4030) resets the VUSB_DEDICATED2 register. This reset +		 * enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to +		 * SLEEP. We work around this by clearing the bit after usv3v1 +		 * is re-activated. This ensures that VUSB3V1 is really active. +		 */ +		twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); + +		ret = regulator_enable(twl->usb1v5); +		if (ret) +			dev_err(twl->dev, "Failed to enable usb1v5\n"); + +		__twl4030_phy_power(twl, 1); +		twl4030_usb_write(twl, PHY_CLK_CTRL, +				  twl4030_usb_read(twl, PHY_CLK_CTRL) | +					(PHY_CLK_CTRL_CLOCKGATING_EN | +						PHY_CLK_CTRL_CLK32K_EN)); +	} else { +		__twl4030_phy_power(twl, 0); +		regulator_disable(twl->usb1v5); +		regulator_disable(twl->usb1v8); +		regulator_disable(twl->usb3v1); +	} +} + +static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off) +{ +	if (twl->asleep) +		return; + +	twl4030_phy_power(twl, 0); +	twl->asleep = 1; +	dev_dbg(twl->dev, "%s\n", __func__); +} + +static void __twl4030_phy_resume(struct twl4030_usb *twl) +{ +	twl4030_phy_power(twl, 1); +	twl4030_i2c_access(twl, 1); +	twl4030_usb_set_mode(twl, twl->usb_mode); +	if (twl->usb_mode == T2_USB_MODE_ULPI) +		twl4030_i2c_access(twl, 0); +} + +static void twl4030_phy_resume(struct twl4030_usb *twl) +{ +	if (!twl->asleep) +		return; +	__twl4030_phy_resume(twl); +	twl->asleep = 0; +	dev_dbg(twl->dev, "%s\n", __func__); + +	/* +	 * XXX When VBUS gets driven after musb goes to A mode, +	 * ID_PRES related interrupts no longer arrive, why? +	 * Register itself is updated fine though, so we must poll. +	 */ +	if (twl->linkstat == OMAP_MUSB_ID_GROUND) { +		cancel_delayed_work(&twl->id_workaround_work); +		schedule_delayed_work(&twl->id_workaround_work, HZ); +	} +} + +static int twl4030_usb_ldo_init(struct twl4030_usb *twl) +{ +	/* Enable writing to power configuration registers */ +	twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, +			 TWL4030_PM_MASTER_PROTECT_KEY); + +	twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, +			 TWL4030_PM_MASTER_PROTECT_KEY); + +	/* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/ +	/*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/ + +	/* input to VUSB3V1 LDO is from VBAT, not VBUS */ +	twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); + +	/* Initialize 3.1V regulator */ +	twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP); + +	twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1"); +	if (IS_ERR(twl->usb3v1)) +		return -ENODEV; + +	twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); + +	/* Initialize 1.5V regulator */ +	twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP); + +	twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5"); +	if (IS_ERR(twl->usb1v5)) +		return -ENODEV; + +	twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); + +	/* Initialize 1.8V regulator */ +	twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP); + +	twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8"); +	if (IS_ERR(twl->usb1v8)) +		return -ENODEV; + +	twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); + +	/* disable access to power configuration registers */ +	twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, +			 TWL4030_PM_MASTER_PROTECT_KEY); + +	return 0; +} + +static ssize_t twl4030_usb_vbus_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct twl4030_usb *twl = dev_get_drvdata(dev); +	unsigned long flags; +	int ret = -EINVAL; + +	spin_lock_irqsave(&twl->lock, flags); +	ret = sprintf(buf, "%s\n", +			twl->vbus_supplied ? "on" : "off"); +	spin_unlock_irqrestore(&twl->lock, flags); + +	return ret; +} +static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); + +static irqreturn_t twl4030_usb_irq(int irq, void *_twl) +{ +	struct twl4030_usb *twl = _twl; +	enum omap_musb_vbus_id_status status; +	bool status_changed = false; + +	status = twl4030_usb_linkstat(twl); + +	spin_lock_irq(&twl->lock); +	if (status >= 0 && status != twl->linkstat) { +		twl->linkstat = status; +		status_changed = true; +	} +	spin_unlock_irq(&twl->lock); + +	if (status_changed) { +		/* FIXME add a set_power() method so that B-devices can +		 * configure the charger appropriately.  It's not always +		 * correct to consume VBUS power, and how much current to +		 * consume is a function of the USB configuration chosen +		 * by the host. +		 * +		 * REVISIT usb_gadget_vbus_connect(...) as needed, ditto +		 * its disconnect() sibling, when changing to/from the +		 * USB_LINK_VBUS state.  musb_hdrc won't care until it +		 * starts to handle softconnect right. +		 */ +		omap_musb_mailbox(status); +	} +	sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + +	return IRQ_HANDLED; +} + +static void twl4030_id_workaround_work(struct work_struct *work) +{ +	struct twl4030_usb *twl = container_of(work, struct twl4030_usb, +		id_workaround_work.work); +	enum omap_musb_vbus_id_status status; +	bool status_changed = false; + +	status = twl4030_usb_linkstat(twl); + +	spin_lock_irq(&twl->lock); +	if (status >= 0 && status != twl->linkstat) { +		twl->linkstat = status; +		status_changed = true; +	} +	spin_unlock_irq(&twl->lock); + +	if (status_changed) { +		dev_dbg(twl->dev, "handle missing status change to %d\n", +				status); +		omap_musb_mailbox(status); +	} + +	/* don't schedule during sleep - irq works right then */ +	if (status == OMAP_MUSB_ID_GROUND && !twl->asleep) { +		cancel_delayed_work(&twl->id_workaround_work); +		schedule_delayed_work(&twl->id_workaround_work, HZ); +	} +} + +static int twl4030_usb_phy_init(struct usb_phy *phy) +{ +	struct twl4030_usb *twl = phy_to_twl(phy); +	enum omap_musb_vbus_id_status status; + +	/* +	 * Start in sleep state, we'll get called through set_suspend() +	 * callback when musb is runtime resumed and it's time to start. +	 */ +	__twl4030_phy_power(twl, 0); +	twl->asleep = 1; + +	status = twl4030_usb_linkstat(twl); +	twl->linkstat = status; + +	if (status == OMAP_MUSB_ID_GROUND || status == OMAP_MUSB_VBUS_VALID) +		omap_musb_mailbox(twl->linkstat); + +	sysfs_notify(&twl->dev->kobj, NULL, "vbus"); +	return 0; +} + +static int twl4030_set_suspend(struct usb_phy *x, int suspend) +{ +	struct twl4030_usb *twl = phy_to_twl(x); + +	if (suspend) +		twl4030_phy_suspend(twl, 1); +	else +		twl4030_phy_resume(twl); + +	return 0; +} + +static int twl4030_set_peripheral(struct usb_otg *otg, +					struct usb_gadget *gadget) +{ +	if (!otg) +		return -ENODEV; + +	otg->gadget = gadget; +	if (!gadget) +		otg->phy->state = OTG_STATE_UNDEFINED; + +	return 0; +} + +static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	if (!otg) +		return -ENODEV; + +	otg->host = host; +	if (!host) +		otg->phy->state = OTG_STATE_UNDEFINED; + +	return 0; +} + +static int twl4030_usb_probe(struct platform_device *pdev) +{ +	struct twl4030_usb_data *pdata = pdev->dev.platform_data; +	struct twl4030_usb	*twl; +	int			status, err; +	struct usb_otg		*otg; +	struct device_node	*np = pdev->dev.of_node; + +	twl = devm_kzalloc(&pdev->dev, sizeof *twl, GFP_KERNEL); +	if (!twl) +		return -ENOMEM; + +	if (np) +		of_property_read_u32(np, "usb_mode", +				(enum twl4030_usb_mode *)&twl->usb_mode); +	else if (pdata) +		twl->usb_mode = pdata->usb_mode; +	else { +		dev_err(&pdev->dev, "twl4030 initialized without pdata\n"); +		return -EINVAL; +	} + +	otg = devm_kzalloc(&pdev->dev, sizeof *otg, GFP_KERNEL); +	if (!otg) +		return -ENOMEM; + +	twl->dev		= &pdev->dev; +	twl->irq		= platform_get_irq(pdev, 0); +	twl->vbus_supplied	= false; +	twl->asleep		= 1; +	twl->linkstat		= OMAP_MUSB_UNKNOWN; + +	twl->phy.dev		= twl->dev; +	twl->phy.label		= "twl4030"; +	twl->phy.otg		= otg; +	twl->phy.type		= USB_PHY_TYPE_USB2; +	twl->phy.set_suspend	= twl4030_set_suspend; +	twl->phy.init		= twl4030_usb_phy_init; + +	otg->phy		= &twl->phy; +	otg->set_host		= twl4030_set_host; +	otg->set_peripheral	= twl4030_set_peripheral; + +	/* init spinlock for workqueue */ +	spin_lock_init(&twl->lock); + +	INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work); + +	err = twl4030_usb_ldo_init(twl); +	if (err) { +		dev_err(&pdev->dev, "ldo init failed\n"); +		return err; +	} +	usb_add_phy_dev(&twl->phy); + +	platform_set_drvdata(pdev, twl); +	if (device_create_file(&pdev->dev, &dev_attr_vbus)) +		dev_warn(&pdev->dev, "could not create sysfs file\n"); + +	/* Our job is to use irqs and status from the power module +	 * to keep the transceiver disabled when nothing's connected. +	 * +	 * FIXME we actually shouldn't start enabling it until the +	 * USB controller drivers have said they're ready, by calling +	 * set_host() and/or set_peripheral() ... OTG_capable boards +	 * need both handles, otherwise just one suffices. +	 */ +	twl->irq_enabled = true; +	status = devm_request_threaded_irq(twl->dev, twl->irq, NULL, +			twl4030_usb_irq, IRQF_TRIGGER_FALLING | +			IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl); +	if (status < 0) { +		dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n", +			twl->irq, status); +		return status; +	} + +	dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); +	return 0; +} + +static int twl4030_usb_remove(struct platform_device *pdev) +{ +	struct twl4030_usb *twl = platform_get_drvdata(pdev); +	int val; + +	cancel_delayed_work(&twl->id_workaround_work); +	device_remove_file(twl->dev, &dev_attr_vbus); + +	/* set transceiver mode to power on defaults */ +	twl4030_usb_set_mode(twl, -1); + +	/* autogate 60MHz ULPI clock, +	 * clear dpll clock request for i2c access, +	 * disable 32KHz +	 */ +	val = twl4030_usb_read(twl, PHY_CLK_CTRL); +	if (val >= 0) { +		val |= PHY_CLK_CTRL_CLOCKGATING_EN; +		val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK); +		twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val); +	} + +	/* disable complete OTG block */ +	twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + +	if (!twl->asleep) +		twl4030_phy_power(twl, 0); + +	return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id twl4030_usb_id_table[] = { +	{ .compatible = "ti,twl4030-usb" }, +	{} +}; +MODULE_DEVICE_TABLE(of, twl4030_usb_id_table); +#endif + +static struct platform_driver twl4030_usb_driver = { +	.probe		= twl4030_usb_probe, +	.remove		= twl4030_usb_remove, +	.driver		= { +		.name	= "twl4030_usb", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(twl4030_usb_id_table), +	}, +}; + +static int __init twl4030_usb_init(void) +{ +	return platform_driver_register(&twl4030_usb_driver); +} +subsys_initcall(twl4030_usb_init); + +static void __exit twl4030_usb_exit(void) +{ +	platform_driver_unregister(&twl4030_usb_driver); +} +module_exit(twl4030_usb_exit); + +MODULE_ALIAS("platform:twl4030_usb"); +MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation"); +MODULE_DESCRIPTION("TWL4030 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c new file mode 100644 index 00000000000..9de7ada90a8 --- /dev/null +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -0,0 +1,453 @@ +/* + * twl6030_usb - TWL6030 USB transceiver, talking to OMAP OTG driver. + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Hema HK <hemahk@ti.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/usb/musb-omap.h> +#include <linux/usb/phy_companion.h> +#include <linux/usb/omap_usb.h> +#include <linux/i2c/twl.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/delay.h> + +/* usb register definitions */ +#define USB_VENDOR_ID_LSB		0x00 +#define USB_VENDOR_ID_MSB		0x01 +#define USB_PRODUCT_ID_LSB		0x02 +#define USB_PRODUCT_ID_MSB		0x03 +#define USB_VBUS_CTRL_SET		0x04 +#define USB_VBUS_CTRL_CLR		0x05 +#define USB_ID_CTRL_SET			0x06 +#define USB_ID_CTRL_CLR			0x07 +#define USB_VBUS_INT_SRC		0x08 +#define USB_VBUS_INT_LATCH_SET		0x09 +#define USB_VBUS_INT_LATCH_CLR		0x0A +#define USB_VBUS_INT_EN_LO_SET		0x0B +#define USB_VBUS_INT_EN_LO_CLR		0x0C +#define USB_VBUS_INT_EN_HI_SET		0x0D +#define USB_VBUS_INT_EN_HI_CLR		0x0E +#define USB_ID_INT_SRC			0x0F +#define USB_ID_INT_LATCH_SET		0x10 +#define USB_ID_INT_LATCH_CLR		0x11 + +#define USB_ID_INT_EN_LO_SET		0x12 +#define USB_ID_INT_EN_LO_CLR		0x13 +#define USB_ID_INT_EN_HI_SET		0x14 +#define USB_ID_INT_EN_HI_CLR		0x15 +#define USB_OTG_ADP_CTRL		0x16 +#define USB_OTG_ADP_HIGH		0x17 +#define USB_OTG_ADP_LOW			0x18 +#define USB_OTG_ADP_RISE		0x19 +#define USB_OTG_REVISION		0x1A + +/* to be moved to LDO */ +#define TWL6030_MISC2			0xE5 +#define TWL6030_CFG_LDO_PD2		0xF5 +#define TWL6030_BACKUP_REG		0xFA + +#define STS_HW_CONDITIONS		0x21 + +/* In module TWL6030_MODULE_PM_MASTER */ +#define STS_HW_CONDITIONS		0x21 +#define STS_USB_ID			BIT(2) + +/* In module TWL6030_MODULE_PM_RECEIVER */ +#define VUSB_CFG_TRANS			0x71 +#define VUSB_CFG_STATE			0x72 +#define VUSB_CFG_VOLTAGE		0x73 + +/* in module TWL6030_MODULE_MAIN_CHARGE */ + +#define CHARGERUSB_CTRL1		0x8 + +#define CONTROLLER_STAT1		0x03 +#define	VBUS_DET			BIT(2) + +struct twl6030_usb { +	struct phy_companion	comparator; +	struct device		*dev; + +	/* for vbus reporting with irqs disabled */ +	spinlock_t		lock; + +	struct regulator		*usb3v3; + +	/* used to set vbus, in atomic path */ +	struct work_struct	set_vbus_work; + +	int			irq1; +	int			irq2; +	enum omap_musb_vbus_id_status linkstat; +	u8			asleep; +	bool			irq_enabled; +	bool			vbus_enable; +	const char		*regulator; +}; + +#define	comparator_to_twl(x) container_of((x), struct twl6030_usb, comparator) + +/*-------------------------------------------------------------------------*/ + +static inline int twl6030_writeb(struct twl6030_usb *twl, u8 module, +						u8 data, u8 address) +{ +	int ret = 0; + +	ret = twl_i2c_write_u8(module, data, address); +	if (ret < 0) +		dev_err(twl->dev, +			"Write[0x%x] Error %d\n", address, ret); +	return ret; +} + +static inline u8 twl6030_readb(struct twl6030_usb *twl, u8 module, u8 address) +{ +	u8 data, ret = 0; + +	ret = twl_i2c_read_u8(module, &data, address); +	if (ret >= 0) +		ret = data; +	else +		dev_err(twl->dev, +			"readb[0x%x,0x%x] Error %d\n", +					module, address, ret); +	return ret; +} + +static int twl6030_start_srp(struct phy_companion *comparator) +{ +	struct twl6030_usb *twl = comparator_to_twl(comparator); + +	twl6030_writeb(twl, TWL_MODULE_USB, 0x24, USB_VBUS_CTRL_SET); +	twl6030_writeb(twl, TWL_MODULE_USB, 0x84, USB_VBUS_CTRL_SET); + +	mdelay(100); +	twl6030_writeb(twl, TWL_MODULE_USB, 0xa0, USB_VBUS_CTRL_CLR); + +	return 0; +} + +static int twl6030_usb_ldo_init(struct twl6030_usb *twl) +{ +	/* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */ +	twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG); + +	/* Program CFG_LDO_PD2 register and set VUSB bit */ +	twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_CFG_LDO_PD2); + +	/* Program MISC2 register and set bit VUSB_IN_VBAT */ +	twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2); + +	twl->usb3v3 = regulator_get(twl->dev, twl->regulator); +	if (IS_ERR(twl->usb3v3)) +		return -ENODEV; + +	/* Program the USB_VBUS_CTRL_SET and set VBUS_ACT_COMP bit */ +	twl6030_writeb(twl, TWL_MODULE_USB, 0x4, USB_VBUS_CTRL_SET); + +	/* +	 * Program the USB_ID_CTRL_SET register to enable GND drive +	 * and the ID comparators +	 */ +	twl6030_writeb(twl, TWL_MODULE_USB, 0x14, USB_ID_CTRL_SET); + +	return 0; +} + +static ssize_t twl6030_usb_vbus_show(struct device *dev, +			struct device_attribute *attr, char *buf) +{ +	struct twl6030_usb *twl = dev_get_drvdata(dev); +	unsigned long flags; +	int ret = -EINVAL; + +	spin_lock_irqsave(&twl->lock, flags); + +	switch (twl->linkstat) { +	case OMAP_MUSB_VBUS_VALID: +	       ret = snprintf(buf, PAGE_SIZE, "vbus\n"); +	       break; +	case OMAP_MUSB_ID_GROUND: +	       ret = snprintf(buf, PAGE_SIZE, "id\n"); +	       break; +	case OMAP_MUSB_VBUS_OFF: +	       ret = snprintf(buf, PAGE_SIZE, "none\n"); +	       break; +	default: +	       ret = snprintf(buf, PAGE_SIZE, "UNKNOWN\n"); +	} +	spin_unlock_irqrestore(&twl->lock, flags); + +	return ret; +} +static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL); + +static irqreturn_t twl6030_usb_irq(int irq, void *_twl) +{ +	struct twl6030_usb *twl = _twl; +	enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; +	u8 vbus_state, hw_state; +	int ret; + +	hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); + +	vbus_state = twl6030_readb(twl, TWL_MODULE_MAIN_CHARGE, +						CONTROLLER_STAT1); +	if (!(hw_state & STS_USB_ID)) { +		if (vbus_state & VBUS_DET) { +			ret = regulator_enable(twl->usb3v3); +			if (ret) +				dev_err(twl->dev, "Failed to enable usb3v3\n"); + +			twl->asleep = 1; +			status = OMAP_MUSB_VBUS_VALID; +			twl->linkstat = status; +			omap_musb_mailbox(status); +		} else { +			if (twl->linkstat != OMAP_MUSB_UNKNOWN) { +				status = OMAP_MUSB_VBUS_OFF; +				twl->linkstat = status; +				omap_musb_mailbox(status); +				if (twl->asleep) { +					regulator_disable(twl->usb3v3); +					twl->asleep = 0; +				} +			} +		} +	} +	sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + +	return IRQ_HANDLED; +} + +static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl) +{ +	struct twl6030_usb *twl = _twl; +	enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN; +	u8 hw_state; +	int ret; + +	hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS); + +	if (hw_state & STS_USB_ID) { +		ret = regulator_enable(twl->usb3v3); +		if (ret) +			dev_err(twl->dev, "Failed to enable usb3v3\n"); + +		twl->asleep = 1; +		twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_CLR); +		twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_SET); +		status = OMAP_MUSB_ID_GROUND; +		twl->linkstat = status; +		omap_musb_mailbox(status); +	} else  { +		twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_CLR); +		twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); +	} +	twl6030_writeb(twl, TWL_MODULE_USB, status, USB_ID_INT_LATCH_CLR); + +	return IRQ_HANDLED; +} + +static int twl6030_enable_irq(struct twl6030_usb *twl) +{ +	twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET); +	twl6030_interrupt_unmask(0x05, REG_INT_MSK_LINE_C); +	twl6030_interrupt_unmask(0x05, REG_INT_MSK_STS_C); + +	twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, +				REG_INT_MSK_LINE_C); +	twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, +				REG_INT_MSK_STS_C); +	twl6030_usb_irq(twl->irq2, twl); +	twl6030_usbotg_irq(twl->irq1, twl); + +	return 0; +} + +static void otg_set_vbus_work(struct work_struct *data) +{ +	struct twl6030_usb *twl = container_of(data, struct twl6030_usb, +								set_vbus_work); + +	/* +	 * Start driving VBUS. Set OPA_MODE bit in CHARGERUSB_CTRL1 +	 * register. This enables boost mode. +	 */ + +	if (twl->vbus_enable) +		twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x40, +							CHARGERUSB_CTRL1); +	else +		twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x00, +							CHARGERUSB_CTRL1); +} + +static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled) +{ +	struct twl6030_usb *twl = comparator_to_twl(comparator); + +	twl->vbus_enable = enabled; +	schedule_work(&twl->set_vbus_work); + +	return 0; +} + +static int twl6030_usb_probe(struct platform_device *pdev) +{ +	u32 ret; +	struct twl6030_usb	*twl; +	int			status, err; +	struct device_node	*np = pdev->dev.of_node; +	struct device		*dev = &pdev->dev; +	struct twl4030_usb_data	*pdata = dev->platform_data; + +	twl = devm_kzalloc(dev, sizeof *twl, GFP_KERNEL); +	if (!twl) +		return -ENOMEM; + +	twl->dev		= &pdev->dev; +	twl->irq1		= platform_get_irq(pdev, 0); +	twl->irq2		= platform_get_irq(pdev, 1); +	twl->linkstat		= OMAP_MUSB_UNKNOWN; + +	twl->comparator.set_vbus	= twl6030_set_vbus; +	twl->comparator.start_srp	= twl6030_start_srp; + +	ret = omap_usb2_set_comparator(&twl->comparator); +	if (ret == -ENODEV) { +		dev_info(&pdev->dev, "phy not ready, deferring probe"); +		return -EPROBE_DEFER; +	} + +	if (np) { +		twl->regulator = "usb"; +	} else if (pdata) { +		if (pdata->features & TWL6025_SUBCLASS) +			twl->regulator = "ldousb"; +		else +			twl->regulator = "vusb"; +	} else { +		dev_err(&pdev->dev, "twl6030 initialized without pdata\n"); +		return -EINVAL; +	} + +	/* init spinlock for workqueue */ +	spin_lock_init(&twl->lock); + +	err = twl6030_usb_ldo_init(twl); +	if (err) { +		dev_err(&pdev->dev, "ldo init failed\n"); +		return err; +	} + +	platform_set_drvdata(pdev, twl); +	if (device_create_file(&pdev->dev, &dev_attr_vbus)) +		dev_warn(&pdev->dev, "could not create sysfs file\n"); + +	INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work); + +	twl->irq_enabled = true; +	status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq, +			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, +			"twl6030_usb", twl); +	if (status < 0) { +		dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", +			twl->irq1, status); +		device_remove_file(twl->dev, &dev_attr_vbus); +		return status; +	} + +	status = request_threaded_irq(twl->irq2, NULL, twl6030_usb_irq, +			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, +			"twl6030_usb", twl); +	if (status < 0) { +		dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", +			twl->irq2, status); +		free_irq(twl->irq1, twl); +		device_remove_file(twl->dev, &dev_attr_vbus); +		return status; +	} + +	twl->asleep = 0; +	twl6030_enable_irq(twl); +	dev_info(&pdev->dev, "Initialized TWL6030 USB module\n"); + +	return 0; +} + +static int twl6030_usb_remove(struct platform_device *pdev) +{ +	struct twl6030_usb *twl = platform_get_drvdata(pdev); + +	twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, +		REG_INT_MSK_LINE_C); +	twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK, +			REG_INT_MSK_STS_C); +	free_irq(twl->irq1, twl); +	free_irq(twl->irq2, twl); +	regulator_put(twl->usb3v3); +	device_remove_file(twl->dev, &dev_attr_vbus); +	cancel_work_sync(&twl->set_vbus_work); + +	return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id twl6030_usb_id_table[] = { +	{ .compatible = "ti,twl6030-usb" }, +	{} +}; +MODULE_DEVICE_TABLE(of, twl6030_usb_id_table); +#endif + +static struct platform_driver twl6030_usb_driver = { +	.probe		= twl6030_usb_probe, +	.remove		= twl6030_usb_remove, +	.driver		= { +		.name	= "twl6030_usb", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(twl6030_usb_id_table), +	}, +}; + +static int __init twl6030_usb_init(void) +{ +	return platform_driver_register(&twl6030_usb_driver); +} +subsys_initcall(twl6030_usb_init); + +static void __exit twl6030_usb_exit(void) +{ +	platform_driver_unregister(&twl6030_usb_driver); +} +module_exit(twl6030_usb_exit); + +MODULE_ALIAS("platform:twl6030_usb"); +MODULE_AUTHOR("Hema HK <hemahk@ti.com>"); +MODULE_DESCRIPTION("TWL6030 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-ulpi-viewport.c b/drivers/usb/phy/phy-ulpi-viewport.c new file mode 100644 index 00000000000..c5ba7e5423f --- /dev/null +++ b/drivers/usb/phy/phy-ulpi-viewport.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/usb.h> +#include <linux/io.h> +#include <linux/usb/otg.h> +#include <linux/usb/ulpi.h> + +#define ULPI_VIEW_WAKEUP	(1 << 31) +#define ULPI_VIEW_RUN		(1 << 30) +#define ULPI_VIEW_WRITE		(1 << 29) +#define ULPI_VIEW_READ		(0 << 29) +#define ULPI_VIEW_ADDR(x)	(((x) & 0xff) << 16) +#define ULPI_VIEW_DATA_READ(x)	(((x) >> 8) & 0xff) +#define ULPI_VIEW_DATA_WRITE(x)	((x) & 0xff) + +static int ulpi_viewport_wait(void __iomem *view, u32 mask) +{ +	unsigned long usec = 2000; + +	while (usec--) { +		if (!(readl(view) & mask)) +			return 0; + +		udelay(1); +	}; + +	return -ETIMEDOUT; +} + +static int ulpi_viewport_read(struct usb_phy *otg, u32 reg) +{ +	int ret; +	void __iomem *view = otg->io_priv; + +	writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view); +	ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP); +	if (ret) +		return ret; + +	writel(ULPI_VIEW_RUN | ULPI_VIEW_READ | ULPI_VIEW_ADDR(reg), view); +	ret = ulpi_viewport_wait(view, ULPI_VIEW_RUN); +	if (ret) +		return ret; + +	return ULPI_VIEW_DATA_READ(readl(view)); +} + +static int ulpi_viewport_write(struct usb_phy *otg, u32 val, u32 reg) +{ +	int ret; +	void __iomem *view = otg->io_priv; + +	writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view); +	ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP); +	if (ret) +		return ret; + +	writel(ULPI_VIEW_RUN | ULPI_VIEW_WRITE | ULPI_VIEW_DATA_WRITE(val) | +						 ULPI_VIEW_ADDR(reg), view); + +	return ulpi_viewport_wait(view, ULPI_VIEW_RUN); +} + +struct usb_phy_io_ops ulpi_viewport_access_ops = { +	.read	= ulpi_viewport_read, +	.write	= ulpi_viewport_write, +}; diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c new file mode 100644 index 00000000000..217339dd7a9 --- /dev/null +++ b/drivers/usb/phy/phy-ulpi.c @@ -0,0 +1,283 @@ +/* + * Generic ULPI USB transceiver support + * + * Copyright (C) 2009 Daniel Mack <daniel@caiaq.de> + * + * Based on sources from + * + *   Sascha Hauer <s.hauer@pengutronix.de> + *   Freescale Semiconductors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/usb.h> +#include <linux/usb/otg.h> +#include <linux/usb/ulpi.h> + + +struct ulpi_info { +	unsigned int	id; +	char		*name; +}; + +#define ULPI_ID(vendor, product) (((vendor) << 16) | (product)) +#define ULPI_INFO(_id, _name)		\ +	{				\ +		.id	= (_id),	\ +		.name	= (_name),	\ +	} + +/* ULPI hardcoded IDs, used for probing */ +static struct ulpi_info ulpi_ids[] = { +	ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"), +	ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"), +}; + +static int ulpi_set_otg_flags(struct usb_phy *phy) +{ +	unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN | +			     ULPI_OTG_CTRL_DM_PULLDOWN; + +	if (phy->flags & ULPI_OTG_ID_PULLUP) +		flags |= ULPI_OTG_CTRL_ID_PULLUP; + +	/* +	 * ULPI Specification rev.1.1 default +	 * for Dp/DmPulldown is enabled. +	 */ +	if (phy->flags & ULPI_OTG_DP_PULLDOWN_DIS) +		flags &= ~ULPI_OTG_CTRL_DP_PULLDOWN; + +	if (phy->flags & ULPI_OTG_DM_PULLDOWN_DIS) +		flags &= ~ULPI_OTG_CTRL_DM_PULLDOWN; + +	if (phy->flags & ULPI_OTG_EXTVBUSIND) +		flags |= ULPI_OTG_CTRL_EXTVBUSIND; + +	return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); +} + +static int ulpi_set_fc_flags(struct usb_phy *phy) +{ +	unsigned int flags = 0; + +	/* +	 * ULPI Specification rev.1.1 default +	 * for XcvrSelect is Full Speed. +	 */ +	if (phy->flags & ULPI_FC_HS) +		flags |= ULPI_FUNC_CTRL_HIGH_SPEED; +	else if (phy->flags & ULPI_FC_LS) +		flags |= ULPI_FUNC_CTRL_LOW_SPEED; +	else if (phy->flags & ULPI_FC_FS4LS) +		flags |= ULPI_FUNC_CTRL_FS4LS; +	else +		flags |= ULPI_FUNC_CTRL_FULL_SPEED; + +	if (phy->flags & ULPI_FC_TERMSEL) +		flags |= ULPI_FUNC_CTRL_TERMSELECT; + +	/* +	 * ULPI Specification rev.1.1 default +	 * for OpMode is Normal Operation. +	 */ +	if (phy->flags & ULPI_FC_OP_NODRV) +		flags |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; +	else if (phy->flags & ULPI_FC_OP_DIS_NRZI) +		flags |= ULPI_FUNC_CTRL_OPMODE_DISABLE_NRZI; +	else if (phy->flags & ULPI_FC_OP_NSYNC_NEOP) +		flags |= ULPI_FUNC_CTRL_OPMODE_NOSYNC_NOEOP; +	else +		flags |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + +	/* +	 * ULPI Specification rev.1.1 default +	 * for SuspendM is Powered. +	 */ +	flags |= ULPI_FUNC_CTRL_SUSPENDM; + +	return usb_phy_io_write(phy, flags, ULPI_FUNC_CTRL); +} + +static int ulpi_set_ic_flags(struct usb_phy *phy) +{ +	unsigned int flags = 0; + +	if (phy->flags & ULPI_IC_AUTORESUME) +		flags |= ULPI_IFC_CTRL_AUTORESUME; + +	if (phy->flags & ULPI_IC_EXTVBUS_INDINV) +		flags |= ULPI_IFC_CTRL_EXTERNAL_VBUS; + +	if (phy->flags & ULPI_IC_IND_PASSTHRU) +		flags |= ULPI_IFC_CTRL_PASSTHRU; + +	if (phy->flags & ULPI_IC_PROTECT_DIS) +		flags |= ULPI_IFC_CTRL_PROTECT_IFC_DISABLE; + +	return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); +} + +static int ulpi_set_flags(struct usb_phy *phy) +{ +	int ret; + +	ret = ulpi_set_otg_flags(phy); +	if (ret) +		return ret; + +	ret = ulpi_set_ic_flags(phy); +	if (ret) +		return ret; + +	return ulpi_set_fc_flags(phy); +} + +static int ulpi_check_integrity(struct usb_phy *phy) +{ +	int ret, i; +	unsigned int val = 0x55; + +	for (i = 0; i < 2; i++) { +		ret = usb_phy_io_write(phy, val, ULPI_SCRATCH); +		if (ret < 0) +			return ret; + +		ret = usb_phy_io_read(phy, ULPI_SCRATCH); +		if (ret < 0) +			return ret; + +		if (ret != val) { +			pr_err("ULPI integrity check: failed!"); +			return -ENODEV; +		} +		val = val << 1; +	} + +	pr_info("ULPI integrity check: passed.\n"); + +	return 0; +} + +static int ulpi_init(struct usb_phy *phy) +{ +	int i, vid, pid, ret; +	u32 ulpi_id = 0; + +	for (i = 0; i < 4; i++) { +		ret = usb_phy_io_read(phy, ULPI_PRODUCT_ID_HIGH - i); +		if (ret < 0) +			return ret; +		ulpi_id = (ulpi_id << 8) | ret; +	} +	vid = ulpi_id & 0xffff; +	pid = ulpi_id >> 16; + +	pr_info("ULPI transceiver vendor/product ID 0x%04x/0x%04x\n", vid, pid); + +	for (i = 0; i < ARRAY_SIZE(ulpi_ids); i++) { +		if (ulpi_ids[i].id == ULPI_ID(vid, pid)) { +			pr_info("Found %s ULPI transceiver.\n", +				ulpi_ids[i].name); +			break; +		} +	} + +	ret = ulpi_check_integrity(phy); +	if (ret) +		return ret; + +	return ulpi_set_flags(phy); +} + +static int ulpi_set_host(struct usb_otg *otg, struct usb_bus *host) +{ +	struct usb_phy *phy = otg->phy; +	unsigned int flags = usb_phy_io_read(phy, ULPI_IFC_CTRL); + +	if (!host) { +		otg->host = NULL; +		return 0; +	} + +	otg->host = host; + +	flags &= ~(ULPI_IFC_CTRL_6_PIN_SERIAL_MODE | +		   ULPI_IFC_CTRL_3_PIN_SERIAL_MODE | +		   ULPI_IFC_CTRL_CARKITMODE); + +	if (phy->flags & ULPI_IC_6PIN_SERIAL) +		flags |= ULPI_IFC_CTRL_6_PIN_SERIAL_MODE; +	else if (phy->flags & ULPI_IC_3PIN_SERIAL) +		flags |= ULPI_IFC_CTRL_3_PIN_SERIAL_MODE; +	else if (phy->flags & ULPI_IC_CARKIT) +		flags |= ULPI_IFC_CTRL_CARKITMODE; + +	return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); +} + +static int ulpi_set_vbus(struct usb_otg *otg, bool on) +{ +	struct usb_phy *phy = otg->phy; +	unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL); + +	flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT); + +	if (on) { +		if (phy->flags & ULPI_OTG_DRVVBUS) +			flags |= ULPI_OTG_CTRL_DRVVBUS; + +		if (phy->flags & ULPI_OTG_DRVVBUS_EXT) +			flags |= ULPI_OTG_CTRL_DRVVBUS_EXT; +	} + +	return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); +} + +struct usb_phy * +otg_ulpi_create(struct usb_phy_io_ops *ops, +		unsigned int flags) +{ +	struct usb_phy *phy; +	struct usb_otg *otg; + +	phy = kzalloc(sizeof(*phy), GFP_KERNEL); +	if (!phy) +		return NULL; + +	otg = kzalloc(sizeof(*otg), GFP_KERNEL); +	if (!otg) { +		kfree(phy); +		return NULL; +	} + +	phy->label	= "ULPI"; +	phy->flags	= flags; +	phy->io_ops	= ops; +	phy->otg	= otg; +	phy->init	= ulpi_init; + +	otg->phy	= phy; +	otg->set_host	= ulpi_set_host; +	otg->set_vbus	= ulpi_set_vbus; + +	return phy; +} +EXPORT_SYMBOL_GPL(otg_ulpi_create); + diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c new file mode 100644 index 00000000000..a9984c700d2 --- /dev/null +++ b/drivers/usb/phy/phy.c @@ -0,0 +1,438 @@ +/* + * phy.c -- USB phy handling + * + * Copyright (C) 2004-2013 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> + +#include <linux/usb/phy.h> + +static LIST_HEAD(phy_list); +static LIST_HEAD(phy_bind_list); +static DEFINE_SPINLOCK(phy_lock); + +static struct usb_phy *__usb_find_phy(struct list_head *list, +	enum usb_phy_type type) +{ +	struct usb_phy  *phy = NULL; + +	list_for_each_entry(phy, list, head) { +		if (phy->type != type) +			continue; + +		return phy; +	} + +	return ERR_PTR(-ENODEV); +} + +static struct usb_phy *__usb_find_phy_dev(struct device *dev, +	struct list_head *list, u8 index) +{ +	struct usb_phy_bind *phy_bind = NULL; + +	list_for_each_entry(phy_bind, list, list) { +		if (!(strcmp(phy_bind->dev_name, dev_name(dev))) && +				phy_bind->index == index) { +			if (phy_bind->phy) +				return phy_bind->phy; +			else +				return ERR_PTR(-EPROBE_DEFER); +		} +	} + +	return ERR_PTR(-ENODEV); +} + +static struct usb_phy *__of_usb_find_phy(struct device_node *node) +{ +	struct usb_phy  *phy; + +	list_for_each_entry(phy, &phy_list, head) { +		if (node != phy->dev->of_node) +			continue; + +		return phy; +	} + +	return ERR_PTR(-ENODEV); +} + +static void devm_usb_phy_release(struct device *dev, void *res) +{ +	struct usb_phy *phy = *(struct usb_phy **)res; + +	usb_put_phy(phy); +} + +static int devm_usb_phy_match(struct device *dev, void *res, void *match_data) +{ +	return res == match_data; +} + +/** + * devm_usb_get_phy - find the USB PHY + * @dev - device that requests this phy + * @type - the type of the phy the controller requires + * + * Gets the phy using usb_get_phy(), and associates a device with it using + * devres. On driver detach, release function is invoked on the devres data, + * then, devres data is freed. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *devm_usb_get_phy(struct device *dev, enum usb_phy_type type) +{ +	struct usb_phy **ptr, *phy; + +	ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); +	if (!ptr) +		return NULL; + +	phy = usb_get_phy(type); +	if (!IS_ERR(phy)) { +		*ptr = phy; +		devres_add(dev, ptr); +	} else +		devres_free(ptr); + +	return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy); + +/** + * usb_get_phy - find the USB PHY + * @type - the type of the phy the controller requires + * + * Returns the phy driver, after getting a refcount to it; or + * -ENODEV if there is no such phy.  The caller is responsible for + * calling usb_put_phy() to release that count. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *usb_get_phy(enum usb_phy_type type) +{ +	struct usb_phy	*phy = NULL; +	unsigned long	flags; + +	spin_lock_irqsave(&phy_lock, flags); + +	phy = __usb_find_phy(&phy_list, type); +	if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { +		pr_err("unable to find transceiver of type %s\n", +			usb_phy_type_string(type)); +		goto err0; +	} + +	get_device(phy->dev); + +err0: +	spin_unlock_irqrestore(&phy_lock, flags); + +	return phy; +} +EXPORT_SYMBOL_GPL(usb_get_phy); + + /** + * devm_usb_get_phy_by_phandle - find the USB PHY by phandle + * @dev - device that requests this phy + * @phandle - name of the property holding the phy phandle value + * @index - the index of the phy + * + * Returns the phy driver associated with the given phandle value, + * after getting a refcount to it, -ENODEV if there is no such phy or + * -EPROBE_DEFER if there is a phandle to the phy, but the device is + * not yet loaded. While at that, it also associates the device with + * the phy using devres. On driver detach, release function is invoked + * on the devres data, then, devres data is freed. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, +	const char *phandle, u8 index) +{ +	struct usb_phy	*phy = ERR_PTR(-ENOMEM), **ptr; +	unsigned long	flags; +	struct device_node *node; + +	if (!dev->of_node) { +		dev_dbg(dev, "device does not have a device node entry\n"); +		return ERR_PTR(-EINVAL); +	} + +	node = of_parse_phandle(dev->of_node, phandle, index); +	if (!node) { +		dev_dbg(dev, "failed to get %s phandle in %s node\n", phandle, +			dev->of_node->full_name); +		return ERR_PTR(-ENODEV); +	} + +	ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); +	if (!ptr) { +		dev_dbg(dev, "failed to allocate memory for devres\n"); +		goto err0; +	} + +	spin_lock_irqsave(&phy_lock, flags); + +	phy = __of_usb_find_phy(node); +	if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { +		phy = ERR_PTR(-EPROBE_DEFER); +		devres_free(ptr); +		goto err1; +	} + +	*ptr = phy; +	devres_add(dev, ptr); + +	get_device(phy->dev); + +err1: +	spin_unlock_irqrestore(&phy_lock, flags); + +err0: +	of_node_put(node); + +	return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle); + +/** + * usb_get_phy_dev - find the USB PHY + * @dev - device that requests this phy + * @index - the index of the phy + * + * Returns the phy driver, after getting a refcount to it; or + * -ENODEV if there is no such phy.  The caller is responsible for + * calling usb_put_phy() to release that count. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index) +{ +	struct usb_phy	*phy = NULL; +	unsigned long	flags; + +	spin_lock_irqsave(&phy_lock, flags); + +	phy = __usb_find_phy_dev(dev, &phy_bind_list, index); +	if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { +		pr_err("unable to find transceiver\n"); +		goto err0; +	} + +	get_device(phy->dev); + +err0: +	spin_unlock_irqrestore(&phy_lock, flags); + +	return phy; +} +EXPORT_SYMBOL_GPL(usb_get_phy_dev); + +/** + * devm_usb_get_phy_dev - find the USB PHY using device ptr and index + * @dev - device that requests this phy + * @index - the index of the phy + * + * Gets the phy using usb_get_phy_dev(), and associates a device with it using + * devres. On driver detach, release function is invoked on the devres data, + * then, devres data is freed. + * + * For use by USB host and peripheral drivers. + */ +struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index) +{ +	struct usb_phy **ptr, *phy; + +	ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); +	if (!ptr) +		return NULL; + +	phy = usb_get_phy_dev(dev, index); +	if (!IS_ERR(phy)) { +		*ptr = phy; +		devres_add(dev, ptr); +	} else +		devres_free(ptr); + +	return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy_dev); + +/** + * devm_usb_put_phy - release the USB PHY + * @dev - device that wants to release this phy + * @phy - the phy returned by devm_usb_get_phy() + * + * destroys the devres associated with this phy and invokes usb_put_phy + * to release the phy. + * + * For use by USB host and peripheral drivers. + */ +void devm_usb_put_phy(struct device *dev, struct usb_phy *phy) +{ +	int r; + +	r = devres_destroy(dev, devm_usb_phy_release, devm_usb_phy_match, phy); +	dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n"); +} +EXPORT_SYMBOL_GPL(devm_usb_put_phy); + +/** + * usb_put_phy - release the USB PHY + * @x: the phy returned by usb_get_phy() + * + * Releases a refcount the caller received from usb_get_phy(). + * + * For use by USB host and peripheral drivers. + */ +void usb_put_phy(struct usb_phy *x) +{ +	if (x) { +		struct module *owner = x->dev->driver->owner; + +		put_device(x->dev); +		module_put(owner); +	} +} +EXPORT_SYMBOL_GPL(usb_put_phy); + +/** + * usb_add_phy - declare the USB PHY + * @x: the USB phy to be used; or NULL + * @type - the type of this PHY + * + * This call is exclusively for use by phy drivers, which + * coordinate the activities of drivers for host and peripheral + * controllers, and in some cases for VBUS current regulation. + */ +int usb_add_phy(struct usb_phy *x, enum usb_phy_type type) +{ +	int		ret = 0; +	unsigned long	flags; +	struct usb_phy	*phy; + +	if (x->type != USB_PHY_TYPE_UNDEFINED) { +		dev_err(x->dev, "not accepting initialized PHY %s\n", x->label); +		return -EINVAL; +	} + +	spin_lock_irqsave(&phy_lock, flags); + +	list_for_each_entry(phy, &phy_list, head) { +		if (phy->type == type) { +			ret = -EBUSY; +			dev_err(x->dev, "transceiver type %s already exists\n", +						usb_phy_type_string(type)); +			goto out; +		} +	} + +	x->type = type; +	list_add_tail(&x->head, &phy_list); + +out: +	spin_unlock_irqrestore(&phy_lock, flags); +	return ret; +} +EXPORT_SYMBOL_GPL(usb_add_phy); + +/** + * usb_add_phy_dev - declare the USB PHY + * @x: the USB phy to be used; or NULL + * + * This call is exclusively for use by phy drivers, which + * coordinate the activities of drivers for host and peripheral + * controllers, and in some cases for VBUS current regulation. + */ +int usb_add_phy_dev(struct usb_phy *x) +{ +	struct usb_phy_bind *phy_bind; +	unsigned long flags; + +	if (!x->dev) { +		dev_err(x->dev, "no device provided for PHY\n"); +		return -EINVAL; +	} + +	spin_lock_irqsave(&phy_lock, flags); +	list_for_each_entry(phy_bind, &phy_bind_list, list) +		if (!(strcmp(phy_bind->phy_dev_name, dev_name(x->dev)))) +			phy_bind->phy = x; + +	list_add_tail(&x->head, &phy_list); + +	spin_unlock_irqrestore(&phy_lock, flags); +	return 0; +} +EXPORT_SYMBOL_GPL(usb_add_phy_dev); + +/** + * usb_remove_phy - remove the OTG PHY + * @x: the USB OTG PHY to be removed; + * + * This reverts the effects of usb_add_phy + */ +void usb_remove_phy(struct usb_phy *x) +{ +	unsigned long	flags; +	struct usb_phy_bind *phy_bind; + +	spin_lock_irqsave(&phy_lock, flags); +	if (x) { +		list_for_each_entry(phy_bind, &phy_bind_list, list) +			if (phy_bind->phy == x) +				phy_bind->phy = NULL; +		list_del(&x->head); +	} +	spin_unlock_irqrestore(&phy_lock, flags); +} +EXPORT_SYMBOL_GPL(usb_remove_phy); + +/** + * usb_bind_phy - bind the phy and the controller that uses the phy + * @dev_name: the device name of the device that will bind to the phy + * @index: index to specify the port number + * @phy_dev_name: the device name of the phy + * + * Fills the phy_bind structure with the dev_name and phy_dev_name. This will + * be used when the phy driver registers the phy and when the controller + * requests this phy. + * + * To be used by platform specific initialization code. + */ +int usb_bind_phy(const char *dev_name, u8 index, +				const char *phy_dev_name) +{ +	struct usb_phy_bind *phy_bind; +	unsigned long flags; + +	phy_bind = kzalloc(sizeof(*phy_bind), GFP_KERNEL); +	if (!phy_bind) { +		pr_err("phy_bind(): No memory for phy_bind"); +		return -ENOMEM; +	} + +	phy_bind->dev_name = dev_name; +	phy_bind->phy_dev_name = phy_dev_name; +	phy_bind->index = index; + +	spin_lock_irqsave(&phy_lock, flags); +	list_add_tail(&phy_bind->list, &phy_bind_list); +	spin_unlock_irqrestore(&phy_lock, flags); + +	return 0; +} +EXPORT_SYMBOL_GPL(usb_bind_phy); diff --git a/drivers/usb/phy/samsung-usbphy.c b/drivers/usb/phy/samsung-usbphy.c deleted file mode 100644 index 967101ec15f..00000000000 --- a/drivers/usb/phy/samsung-usbphy.c +++ /dev/null @@ -1,928 +0,0 @@ -/* linux/drivers/usb/phy/samsung-usbphy.c - * - * Copyright (c) 2012 Samsung Electronics Co., Ltd. - *              http://www.samsung.com - * - * Author: Praveen Paneri <p.paneri@samsung.com> - * - * Samsung USB2.0 PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and - * OHCI-EXYNOS controllers. - * - * 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 program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - */ - -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/clk.h> -#include <linux/delay.h> -#include <linux/device.h> -#include <linux/err.h> -#include <linux/io.h> -#include <linux/of.h> -#include <linux/of_address.h> -#include <linux/usb/otg.h> -#include <linux/usb/samsung_usb_phy.h> -#include <linux/platform_data/samsung-usbphy.h> - -/* Register definitions */ - -#define SAMSUNG_PHYPWR				(0x00) - -#define PHYPWR_NORMAL_MASK			(0x19 << 0) -#define PHYPWR_OTG_DISABLE			(0x1 << 4) -#define PHYPWR_ANALOG_POWERDOWN			(0x1 << 3) -#define PHYPWR_FORCE_SUSPEND			(0x1 << 1) -/* For Exynos4 */ -#define PHYPWR_NORMAL_MASK_PHY0			(0x39 << 0) -#define PHYPWR_SLEEP_PHY0			(0x1 << 5) - -#define SAMSUNG_PHYCLK				(0x04) - -#define PHYCLK_MODE_USB11			(0x1 << 6) -#define PHYCLK_EXT_OSC				(0x1 << 5) -#define PHYCLK_COMMON_ON_N			(0x1 << 4) -#define PHYCLK_ID_PULL				(0x1 << 2) -#define PHYCLK_CLKSEL_MASK			(0x3 << 0) -#define PHYCLK_CLKSEL_48M			(0x0 << 0) -#define PHYCLK_CLKSEL_12M			(0x2 << 0) -#define PHYCLK_CLKSEL_24M			(0x3 << 0) - -#define SAMSUNG_RSTCON				(0x08) - -#define RSTCON_PHYLINK_SWRST			(0x1 << 2) -#define RSTCON_HLINK_SWRST			(0x1 << 1) -#define RSTCON_SWRST				(0x1 << 0) - -/* EXYNOS5 */ -#define EXYNOS5_PHY_HOST_CTRL0			(0x00) - -#define HOST_CTRL0_PHYSWRSTALL			(0x1 << 31) - -#define HOST_CTRL0_REFCLKSEL_MASK		(0x3 << 19) -#define HOST_CTRL0_REFCLKSEL_XTAL		(0x0 << 19) -#define HOST_CTRL0_REFCLKSEL_EXTL		(0x1 << 19) -#define HOST_CTRL0_REFCLKSEL_CLKCORE		(0x2 << 19) - -#define HOST_CTRL0_FSEL_MASK			(0x7 << 16) -#define HOST_CTRL0_FSEL(_x)			((_x) << 16) - -#define FSEL_CLKSEL_50M				(0x7) -#define FSEL_CLKSEL_24M				(0x5) -#define FSEL_CLKSEL_20M				(0x4) -#define FSEL_CLKSEL_19200K			(0x3) -#define FSEL_CLKSEL_12M				(0x2) -#define FSEL_CLKSEL_10M				(0x1) -#define FSEL_CLKSEL_9600K			(0x0) - -#define HOST_CTRL0_TESTBURNIN			(0x1 << 11) -#define HOST_CTRL0_RETENABLE			(0x1 << 10) -#define HOST_CTRL0_COMMONON_N			(0x1 << 9) -#define HOST_CTRL0_SIDDQ			(0x1 << 6) -#define HOST_CTRL0_FORCESLEEP			(0x1 << 5) -#define HOST_CTRL0_FORCESUSPEND			(0x1 << 4) -#define HOST_CTRL0_WORDINTERFACE		(0x1 << 3) -#define HOST_CTRL0_UTMISWRST			(0x1 << 2) -#define HOST_CTRL0_LINKSWRST			(0x1 << 1) -#define HOST_CTRL0_PHYSWRST			(0x1 << 0) - -#define EXYNOS5_PHY_HOST_TUNE0			(0x04) - -#define EXYNOS5_PHY_HSIC_CTRL1			(0x10) - -#define EXYNOS5_PHY_HSIC_TUNE1			(0x14) - -#define EXYNOS5_PHY_HSIC_CTRL2			(0x20) - -#define EXYNOS5_PHY_HSIC_TUNE2			(0x24) - -#define HSIC_CTRL_REFCLKSEL_MASK		(0x3 << 23) -#define HSIC_CTRL_REFCLKSEL			(0x2 << 23) - -#define HSIC_CTRL_REFCLKDIV_MASK		(0x7f << 16) -#define HSIC_CTRL_REFCLKDIV(_x)			((_x) << 16) -#define HSIC_CTRL_REFCLKDIV_12			(0x24 << 16) -#define HSIC_CTRL_REFCLKDIV_15			(0x1c << 16) -#define HSIC_CTRL_REFCLKDIV_16			(0x1a << 16) -#define HSIC_CTRL_REFCLKDIV_19_2		(0x15 << 16) -#define HSIC_CTRL_REFCLKDIV_20			(0x14 << 16) - -#define HSIC_CTRL_SIDDQ				(0x1 << 6) -#define HSIC_CTRL_FORCESLEEP			(0x1 << 5) -#define HSIC_CTRL_FORCESUSPEND			(0x1 << 4) -#define HSIC_CTRL_WORDINTERFACE			(0x1 << 3) -#define HSIC_CTRL_UTMISWRST			(0x1 << 2) -#define HSIC_CTRL_PHYSWRST			(0x1 << 0) - -#define EXYNOS5_PHY_HOST_EHCICTRL		(0x30) - -#define HOST_EHCICTRL_ENAINCRXALIGN		(0x1 << 29) -#define HOST_EHCICTRL_ENAINCR4			(0x1 << 28) -#define HOST_EHCICTRL_ENAINCR8			(0x1 << 27) -#define HOST_EHCICTRL_ENAINCR16			(0x1 << 26) - -#define EXYNOS5_PHY_HOST_OHCICTRL		(0x34) - -#define HOST_OHCICTRL_SUSPLGCY			(0x1 << 3) -#define HOST_OHCICTRL_APPSTARTCLK		(0x1 << 2) -#define HOST_OHCICTRL_CNTSEL			(0x1 << 1) -#define HOST_OHCICTRL_CLKCKTRST			(0x1 << 0) - -#define EXYNOS5_PHY_OTG_SYS			(0x38) - -#define OTG_SYS_PHYLINK_SWRESET			(0x1 << 14) -#define OTG_SYS_LINKSWRST_UOTG			(0x1 << 13) -#define OTG_SYS_PHY0_SWRST			(0x1 << 12) - -#define OTG_SYS_REFCLKSEL_MASK			(0x3 << 9) -#define OTG_SYS_REFCLKSEL_XTAL			(0x0 << 9) -#define OTG_SYS_REFCLKSEL_EXTL			(0x1 << 9) -#define OTG_SYS_REFCLKSEL_CLKCORE		(0x2 << 9) - -#define OTG_SYS_IDPULLUP_UOTG			(0x1 << 8) -#define OTG_SYS_COMMON_ON			(0x1 << 7) - -#define OTG_SYS_FSEL_MASK			(0x7 << 4) -#define OTG_SYS_FSEL(_x)			((_x) << 4) - -#define OTG_SYS_FORCESLEEP			(0x1 << 3) -#define OTG_SYS_OTGDISABLE			(0x1 << 2) -#define OTG_SYS_SIDDQ_UOTG			(0x1 << 1) -#define OTG_SYS_FORCESUSPEND			(0x1 << 0) - -#define EXYNOS5_PHY_OTG_TUNE			(0x40) - -#ifndef MHZ -#define MHZ (1000*1000) -#endif - -#ifndef KHZ -#define KHZ (1000) -#endif - -#define EXYNOS_USBHOST_PHY_CTRL_OFFSET		(0x4) -#define S3C64XX_USBPHY_ENABLE			(0x1 << 16) -#define EXYNOS_USBPHY_ENABLE			(0x1 << 0) -#define EXYNOS_USB20PHY_CFG_HOST_LINK		(0x1 << 0) - -enum samsung_cpu_type { -	TYPE_S3C64XX, -	TYPE_EXYNOS4210, -	TYPE_EXYNOS5250, -}; - -/* - * struct samsung_usbphy_drvdata - driver data for various SoC variants - * @cpu_type: machine identifier - * @devphy_en_mask: device phy enable mask for PHY CONTROL register - * @hostphy_en_mask: host phy enable mask for PHY CONTROL register - * @devphy_reg_offset: offset to DEVICE PHY CONTROL register from - *		       mapped address of system controller. - * @hostphy_reg_offset: offset to HOST PHY CONTROL register from - *		       mapped address of system controller. - * - *	Here we have a separate mask for device type phy. - *	Having different masks for host and device type phy helps - *	in setting independent masks in case of SoCs like S5PV210, - *	in which PHY0 and PHY1 enable bits belong to same register - *	placed at position 0 and 1 respectively. - *	Although for newer SoCs like exynos these bits belong to - *	different registers altogether placed at position 0. - */ -struct samsung_usbphy_drvdata { -	int cpu_type; -	int devphy_en_mask; -	int hostphy_en_mask; -	u32 devphy_reg_offset; -	u32 hostphy_reg_offset; -}; - -/* - * struct samsung_usbphy - transceiver driver state - * @phy: transceiver structure - * @plat: platform data - * @dev: The parent device supplied to the probe function - * @clk: usb phy clock - * @regs: usb phy controller registers memory base - * @pmuregs: USB device PHY_CONTROL register memory base - * @sysreg: USB2.0 PHY_CFG register memory base - * @ref_clk_freq: reference clock frequency selection - * @drv_data: driver data available for different SoCs - * @phy_type: Samsung SoCs specific phy types:	#HOST - *						#DEVICE - * @phy_usage: usage count for phy - * @lock: lock for phy operations - */ -struct samsung_usbphy { -	struct usb_phy	phy; -	struct samsung_usbphy_data *plat; -	struct device	*dev; -	struct clk	*clk; -	void __iomem	*regs; -	void __iomem	*pmuregs; -	void __iomem	*sysreg; -	int		ref_clk_freq; -	const struct samsung_usbphy_drvdata *drv_data; -	enum samsung_usb_phy_type phy_type; -	atomic_t	phy_usage; -	spinlock_t	lock; -}; - -#define phy_to_sphy(x)		container_of((x), struct samsung_usbphy, phy) - -int samsung_usbphy_set_host(struct usb_otg *otg, struct usb_bus *host) -{ -	if (!otg) -		return -ENODEV; - -	if (!otg->host) -		otg->host = host; - -	return 0; -} - -static int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy) -{ -	struct device_node *usbphy_sys; - -	/* Getting node for system controller interface for usb-phy */ -	usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys"); -	if (!usbphy_sys) { -		dev_err(sphy->dev, "No sys-controller interface for usb-phy\n"); -		return -ENODEV; -	} - -	sphy->pmuregs = of_iomap(usbphy_sys, 0); - -	if (sphy->pmuregs == NULL) { -		dev_err(sphy->dev, "Can't get usb-phy pmu control register\n"); -		goto err0; -	} - -	sphy->sysreg = of_iomap(usbphy_sys, 1); - -	/* -	 * Not returning error code here, since this situation is not fatal. -	 * Few SoCs may not have this switch available -	 */ -	if (sphy->sysreg == NULL) -		dev_warn(sphy->dev, "Can't get usb-phy sysreg cfg register\n"); - -	of_node_put(usbphy_sys); - -	return 0; - -err0: -	of_node_put(usbphy_sys); -	return -ENXIO; -} - -/* - * Set isolation here for phy. - * Here 'on = true' would mean USB PHY block is isolated, hence - * de-activated and vice-versa. - */ -static void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) -{ -	void __iomem *reg = NULL; -	u32 reg_val; -	u32 en_mask = 0; - -	if (!sphy->pmuregs) { -		dev_warn(sphy->dev, "Can't set pmu isolation\n"); -		return; -	} - -	switch (sphy->drv_data->cpu_type) { -	case TYPE_S3C64XX: -		/* -		 * Do nothing: We will add here once S3C64xx goes for DT support -		 */ -		break; -	case TYPE_EXYNOS4210: -		/* -		 * Fall through since exynos4210 and exynos5250 have similar -		 * register architecture: two separate registers for host and -		 * device phy control with enable bit at position 0. -		 */ -	case TYPE_EXYNOS5250: -		if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { -			reg = sphy->pmuregs + -				sphy->drv_data->devphy_reg_offset; -			en_mask = sphy->drv_data->devphy_en_mask; -		} else if (sphy->phy_type == USB_PHY_TYPE_HOST) { -			reg = sphy->pmuregs + -				sphy->drv_data->hostphy_reg_offset; -			en_mask = sphy->drv_data->hostphy_en_mask; -		} -		break; -	default: -		dev_err(sphy->dev, "Invalid SoC type\n"); -		return; -	} - -	reg_val = readl(reg); - -	if (on) -		reg_val &= ~en_mask; -	else -		reg_val |= en_mask; - -	writel(reg_val, reg); -} - -/* - * Configure the mode of working of usb-phy here: HOST/DEVICE. - */ -static void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy) -{ -	u32 reg; - -	if (!sphy->sysreg) { -		dev_warn(sphy->dev, "Can't configure specified phy mode\n"); -		return; -	} - -	reg = readl(sphy->sysreg); - -	if (sphy->phy_type == USB_PHY_TYPE_DEVICE) -		reg &= ~EXYNOS_USB20PHY_CFG_HOST_LINK; -	else if (sphy->phy_type == USB_PHY_TYPE_HOST) -		reg |= EXYNOS_USB20PHY_CFG_HOST_LINK; - -	writel(reg, sphy->sysreg); -} - -/* - * PHYs are different for USB Device and USB Host. - * This make sure that correct PHY type is selected before - * any operation on PHY. - */ -static int samsung_usbphy_set_type(struct usb_phy *phy, -				enum samsung_usb_phy_type phy_type) -{ -	struct samsung_usbphy *sphy = phy_to_sphy(phy); - -	sphy->phy_type = phy_type; - -	return 0; -} - -/* - * Returns reference clock frequency selection value - */ -static int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) -{ -	struct clk *ref_clk; -	int refclk_freq = 0; - -	/* -	 * In exynos5250 USB host and device PHY use -	 * external crystal clock XXTI -	 */ -	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) -		ref_clk = clk_get(sphy->dev, "ext_xtal"); -	else -		ref_clk = clk_get(sphy->dev, "xusbxti"); -	if (IS_ERR(ref_clk)) { -		dev_err(sphy->dev, "Failed to get reference clock\n"); -		return PTR_ERR(ref_clk); -	} - -	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) { -		/* set clock frequency for PLL */ -		switch (clk_get_rate(ref_clk)) { -		case 9600 * KHZ: -			refclk_freq = FSEL_CLKSEL_9600K; -			break; -		case 10 * MHZ: -			refclk_freq = FSEL_CLKSEL_10M; -			break; -		case 12 * MHZ: -			refclk_freq = FSEL_CLKSEL_12M; -			break; -		case 19200 * KHZ: -			refclk_freq = FSEL_CLKSEL_19200K; -			break; -		case 20 * MHZ: -			refclk_freq = FSEL_CLKSEL_20M; -			break; -		case 50 * MHZ: -			refclk_freq = FSEL_CLKSEL_50M; -			break; -		case 24 * MHZ: -		default: -			/* default reference clock */ -			refclk_freq = FSEL_CLKSEL_24M; -			break; -		} -	} else { -		switch (clk_get_rate(ref_clk)) { -		case 12 * MHZ: -			refclk_freq = PHYCLK_CLKSEL_12M; -			break; -		case 24 * MHZ: -			refclk_freq = PHYCLK_CLKSEL_24M; -			break; -		case 48 * MHZ: -			refclk_freq = PHYCLK_CLKSEL_48M; -			break; -		default: -			if (sphy->drv_data->cpu_type == TYPE_S3C64XX) -				refclk_freq = PHYCLK_CLKSEL_48M; -			else -				refclk_freq = PHYCLK_CLKSEL_24M; -			break; -		} -	} -	clk_put(ref_clk); - -	return refclk_freq; -} - -static bool exynos5_phyhost_is_on(void *regs) -{ -	u32 reg; - -	reg = readl(regs + EXYNOS5_PHY_HOST_CTRL0); - -	return !(reg & HOST_CTRL0_SIDDQ); -} - -static void samsung_exynos5_usbphy_enable(struct samsung_usbphy *sphy) -{ -	void __iomem *regs = sphy->regs; -	u32 phyclk = sphy->ref_clk_freq; -	u32 phyhost; -	u32 phyotg; -	u32 phyhsic; -	u32 ehcictrl; -	u32 ohcictrl; - -	/* -	 * phy_usage helps in keeping usage count for phy -	 * so that the first consumer enabling the phy is also -	 * the last consumer to disable it. -	 */ - -	atomic_inc(&sphy->phy_usage); - -	if (exynos5_phyhost_is_on(regs)) { -		dev_info(sphy->dev, "Already power on PHY\n"); -		return; -	} - -	/* Host configuration */ -	phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0); - -	/* phy reference clock configuration */ -	phyhost &= ~HOST_CTRL0_FSEL_MASK; -	phyhost |= HOST_CTRL0_FSEL(phyclk); - -	/* host phy reset */ -	phyhost &= ~(HOST_CTRL0_PHYSWRST | -			HOST_CTRL0_PHYSWRSTALL | -			HOST_CTRL0_SIDDQ | -			/* Enable normal mode of operation */ -			HOST_CTRL0_FORCESUSPEND | -			HOST_CTRL0_FORCESLEEP); - -	/* Link reset */ -	phyhost |= (HOST_CTRL0_LINKSWRST | -			HOST_CTRL0_UTMISWRST | -			/* COMMON Block configuration during suspend */ -			HOST_CTRL0_COMMONON_N); -	writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); -	udelay(10); -	phyhost &= ~(HOST_CTRL0_LINKSWRST | -			HOST_CTRL0_UTMISWRST); -	writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); - -	/* OTG configuration */ -	phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS); - -	/* phy reference clock configuration */ -	phyotg &= ~OTG_SYS_FSEL_MASK; -	phyotg |= OTG_SYS_FSEL(phyclk); - -	/* Enable normal mode of operation */ -	phyotg &= ~(OTG_SYS_FORCESUSPEND | -			OTG_SYS_SIDDQ_UOTG | -			OTG_SYS_FORCESLEEP | -			OTG_SYS_REFCLKSEL_MASK | -			/* COMMON Block configuration during suspend */ -			OTG_SYS_COMMON_ON); - -	/* OTG phy & link reset */ -	phyotg |= (OTG_SYS_PHY0_SWRST | -			OTG_SYS_LINKSWRST_UOTG | -			OTG_SYS_PHYLINK_SWRESET | -			OTG_SYS_OTGDISABLE | -			/* Set phy refclk */ -			OTG_SYS_REFCLKSEL_CLKCORE); - -	writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); -	udelay(10); -	phyotg &= ~(OTG_SYS_PHY0_SWRST | -			OTG_SYS_LINKSWRST_UOTG | -			OTG_SYS_PHYLINK_SWRESET); -	writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); - -	/* HSIC phy configuration */ -	phyhsic = (HSIC_CTRL_REFCLKDIV_12 | -			HSIC_CTRL_REFCLKSEL | -			HSIC_CTRL_PHYSWRST); -	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); -	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); -	udelay(10); -	phyhsic &= ~HSIC_CTRL_PHYSWRST; -	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); -	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); - -	udelay(80); - -	/* enable EHCI DMA burst */ -	ehcictrl = readl(regs + EXYNOS5_PHY_HOST_EHCICTRL); -	ehcictrl |= (HOST_EHCICTRL_ENAINCRXALIGN | -				HOST_EHCICTRL_ENAINCR4 | -				HOST_EHCICTRL_ENAINCR8 | -				HOST_EHCICTRL_ENAINCR16); -	writel(ehcictrl, regs + EXYNOS5_PHY_HOST_EHCICTRL); - -	/* set ohci_suspend_on_n */ -	ohcictrl = readl(regs + EXYNOS5_PHY_HOST_OHCICTRL); -	ohcictrl |= HOST_OHCICTRL_SUSPLGCY; -	writel(ohcictrl, regs + EXYNOS5_PHY_HOST_OHCICTRL); -} - -static void samsung_usbphy_enable(struct samsung_usbphy *sphy) -{ -	void __iomem *regs = sphy->regs; -	u32 phypwr; -	u32 phyclk; -	u32 rstcon; - -	/* set clock frequency for PLL */ -	phyclk = sphy->ref_clk_freq; -	phypwr = readl(regs + SAMSUNG_PHYPWR); -	rstcon = readl(regs + SAMSUNG_RSTCON); - -	switch (sphy->drv_data->cpu_type) { -	case TYPE_S3C64XX: -		phyclk &= ~PHYCLK_COMMON_ON_N; -		phypwr &= ~PHYPWR_NORMAL_MASK; -		rstcon |= RSTCON_SWRST; -		break; -	case TYPE_EXYNOS4210: -		phypwr &= ~PHYPWR_NORMAL_MASK_PHY0; -		rstcon |= RSTCON_SWRST; -	default: -		break; -	} - -	writel(phyclk, regs + SAMSUNG_PHYCLK); -	/* Configure PHY0 for normal operation*/ -	writel(phypwr, regs + SAMSUNG_PHYPWR); -	/* reset all ports of PHY and Link */ -	writel(rstcon, regs + SAMSUNG_RSTCON); -	udelay(10); -	rstcon &= ~RSTCON_SWRST; -	writel(rstcon, regs + SAMSUNG_RSTCON); -} - -static void samsung_exynos5_usbphy_disable(struct samsung_usbphy *sphy) -{ -	void __iomem *regs = sphy->regs; -	u32 phyhost; -	u32 phyotg; -	u32 phyhsic; - -	if (atomic_dec_return(&sphy->phy_usage) > 0) { -		dev_info(sphy->dev, "still being used\n"); -		return; -	} - -	phyhsic = (HSIC_CTRL_REFCLKDIV_12 | -			HSIC_CTRL_REFCLKSEL | -			HSIC_CTRL_SIDDQ | -			HSIC_CTRL_FORCESLEEP | -			HSIC_CTRL_FORCESUSPEND); -	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1); -	writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2); - -	phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0); -	phyhost |= (HOST_CTRL0_SIDDQ | -			HOST_CTRL0_FORCESUSPEND | -			HOST_CTRL0_FORCESLEEP | -			HOST_CTRL0_PHYSWRST | -			HOST_CTRL0_PHYSWRSTALL); -	writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0); - -	phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS); -	phyotg |= (OTG_SYS_FORCESUSPEND | -			OTG_SYS_SIDDQ_UOTG | -			OTG_SYS_FORCESLEEP); -	writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS); -} - -static void samsung_usbphy_disable(struct samsung_usbphy *sphy) -{ -	void __iomem *regs = sphy->regs; -	u32 phypwr; - -	phypwr = readl(regs + SAMSUNG_PHYPWR); - -	switch (sphy->drv_data->cpu_type) { -	case TYPE_S3C64XX: -		phypwr |= PHYPWR_NORMAL_MASK; -		break; -	case TYPE_EXYNOS4210: -		phypwr |= PHYPWR_NORMAL_MASK_PHY0; -	default: -		break; -	} - -	/* Disable analog and otg block power */ -	writel(phypwr, regs + SAMSUNG_PHYPWR); -} - -/* - * The function passed to the usb driver for phy initialization - */ -static int samsung_usbphy_init(struct usb_phy *phy) -{ -	struct samsung_usbphy *sphy; -	struct usb_bus *host = NULL; -	unsigned long flags; -	int ret = 0; - -	sphy = phy_to_sphy(phy); - -	host = phy->otg->host; - -	/* Enable the phy clock */ -	ret = clk_prepare_enable(sphy->clk); -	if (ret) { -		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); -		return ret; -	} - -	spin_lock_irqsave(&sphy->lock, flags); - -	if (host) { -		/* setting default phy-type for USB 2.0 */ -		if (!strstr(dev_name(host->controller), "ehci") || -				!strstr(dev_name(host->controller), "ohci")) -			samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST); -	} else { -		samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); -	} - -	/* Disable phy isolation */ -	if (sphy->plat && sphy->plat->pmu_isolation) -		sphy->plat->pmu_isolation(false); -	else -		samsung_usbphy_set_isolation(sphy, false); - -	/* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */ -	samsung_usbphy_cfg_sel(sphy); - -	/* Initialize usb phy registers */ -	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) -		samsung_exynos5_usbphy_enable(sphy); -	else -		samsung_usbphy_enable(sphy); - -	spin_unlock_irqrestore(&sphy->lock, flags); - -	/* Disable the phy clock */ -	clk_disable_unprepare(sphy->clk); - -	return ret; -} - -/* - * The function passed to the usb driver for phy shutdown - */ -static void samsung_usbphy_shutdown(struct usb_phy *phy) -{ -	struct samsung_usbphy *sphy; -	struct usb_bus *host = NULL; -	unsigned long flags; - -	sphy = phy_to_sphy(phy); - -	host = phy->otg->host; - -	if (clk_prepare_enable(sphy->clk)) { -		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); -		return; -	} - -	spin_lock_irqsave(&sphy->lock, flags); - -	if (host) { -		/* setting default phy-type for USB 2.0 */ -		if (!strstr(dev_name(host->controller), "ehci") || -				!strstr(dev_name(host->controller), "ohci")) -			samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST); -	} else { -		samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); -	} - -	/* De-initialize usb phy registers */ -	if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) -		samsung_exynos5_usbphy_disable(sphy); -	else -		samsung_usbphy_disable(sphy); - -	/* Enable phy isolation */ -	if (sphy->plat && sphy->plat->pmu_isolation) -		sphy->plat->pmu_isolation(true); -	else -		samsung_usbphy_set_isolation(sphy, true); - -	spin_unlock_irqrestore(&sphy->lock, flags); - -	clk_disable_unprepare(sphy->clk); -} - -static const struct of_device_id samsung_usbphy_dt_match[]; - -static inline const struct samsung_usbphy_drvdata -*samsung_usbphy_get_driver_data(struct platform_device *pdev) -{ -	if (pdev->dev.of_node) { -		const struct of_device_id *match; -		match = of_match_node(samsung_usbphy_dt_match, -							pdev->dev.of_node); -		return match->data; -	} - -	return (struct samsung_usbphy_drvdata *) -				platform_get_device_id(pdev)->driver_data; -} - -static int samsung_usbphy_probe(struct platform_device *pdev) -{ -	struct samsung_usbphy *sphy; -	struct usb_otg *otg; -	struct samsung_usbphy_data *pdata = pdev->dev.platform_data; -	const struct samsung_usbphy_drvdata *drv_data; -	struct device *dev = &pdev->dev; -	struct resource *phy_mem; -	void __iomem	*phy_base; -	struct clk *clk; -	int ret; - -	phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	if (!phy_mem) { -		dev_err(dev, "%s: missing mem resource\n", __func__); -		return -ENODEV; -	} - -	phy_base = devm_ioremap_resource(dev, phy_mem); -	if (IS_ERR(phy_base)) -		return PTR_ERR(phy_base); - -	sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); -	if (!sphy) -		return -ENOMEM; - -	otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL); -	if (!otg) -		return -ENOMEM; - -	drv_data = samsung_usbphy_get_driver_data(pdev); - -	if (drv_data->cpu_type == TYPE_EXYNOS5250) -		clk = devm_clk_get(dev, "usbhost"); -	else -		clk = devm_clk_get(dev, "otg"); - -	if (IS_ERR(clk)) { -		dev_err(dev, "Failed to get otg clock\n"); -		return PTR_ERR(clk); -	} - -	sphy->dev = dev; - -	if (dev->of_node) { -		ret = samsung_usbphy_parse_dt(sphy); -		if (ret < 0) -			return ret; -	} else { -		if (!pdata) { -			dev_err(dev, "no platform data specified\n"); -			return -EINVAL; -		} -	} - -	sphy->plat		= pdata; -	sphy->regs		= phy_base; -	sphy->clk		= clk; -	sphy->drv_data		= drv_data; -	sphy->phy.dev		= sphy->dev; -	sphy->phy.label		= "samsung-usbphy"; -	sphy->phy.init		= samsung_usbphy_init; -	sphy->phy.shutdown	= samsung_usbphy_shutdown; -	sphy->ref_clk_freq	= samsung_usbphy_get_refclk_freq(sphy); - -	sphy->phy.otg		= otg; -	sphy->phy.otg->phy	= &sphy->phy; -	sphy->phy.otg->set_host = samsung_usbphy_set_host; - -	spin_lock_init(&sphy->lock); - -	platform_set_drvdata(pdev, sphy); - -	return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB2); -} - -static int samsung_usbphy_remove(struct platform_device *pdev) -{ -	struct samsung_usbphy *sphy = platform_get_drvdata(pdev); - -	usb_remove_phy(&sphy->phy); - -	if (sphy->pmuregs) -		iounmap(sphy->pmuregs); -	if (sphy->sysreg) -		iounmap(sphy->sysreg); - -	return 0; -} - -static const struct samsung_usbphy_drvdata usbphy_s3c64xx = { -	.cpu_type		= TYPE_S3C64XX, -	.devphy_en_mask		= S3C64XX_USBPHY_ENABLE, -}; - -static const struct samsung_usbphy_drvdata usbphy_exynos4 = { -	.cpu_type		= TYPE_EXYNOS4210, -	.devphy_en_mask		= EXYNOS_USBPHY_ENABLE, -	.hostphy_en_mask	= EXYNOS_USBPHY_ENABLE, -}; - -static struct samsung_usbphy_drvdata usbphy_exynos5 = { -	.cpu_type		= TYPE_EXYNOS5250, -	.hostphy_en_mask	= EXYNOS_USBPHY_ENABLE, -	.hostphy_reg_offset	= EXYNOS_USBHOST_PHY_CTRL_OFFSET, -}; - -#ifdef CONFIG_OF -static const struct of_device_id samsung_usbphy_dt_match[] = { -	{ -		.compatible = "samsung,s3c64xx-usbphy", -		.data = &usbphy_s3c64xx, -	}, { -		.compatible = "samsung,exynos4210-usbphy", -		.data = &usbphy_exynos4, -	}, { -		.compatible = "samsung,exynos5250-usbphy", -		.data = &usbphy_exynos5 -	}, -	{}, -}; -MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match); -#endif - -static struct platform_device_id samsung_usbphy_driver_ids[] = { -	{ -		.name		= "s3c64xx-usbphy", -		.driver_data	= (unsigned long)&usbphy_s3c64xx, -	}, { -		.name		= "exynos4210-usbphy", -		.driver_data	= (unsigned long)&usbphy_exynos4, -	}, { -		.name		= "exynos5250-usbphy", -		.driver_data	= (unsigned long)&usbphy_exynos5, -	}, -	{}, -}; - -MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids); - -static struct platform_driver samsung_usbphy_driver = { -	.probe		= samsung_usbphy_probe, -	.remove		= samsung_usbphy_remove, -	.id_table	= samsung_usbphy_driver_ids, -	.driver		= { -		.name	= "samsung-usbphy", -		.owner	= THIS_MODULE, -		.of_match_table = of_match_ptr(samsung_usbphy_dt_match), -	}, -}; - -module_platform_driver(samsung_usbphy_driver); - -MODULE_DESCRIPTION("Samsung USB phy controller"); -MODULE_AUTHOR("Praveen Paneri <p.paneri@samsung.com>"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:samsung-usbphy");  |