summaryrefslogtreecommitdiffstats
path: root/drivers/usb/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/phy')
-rw-r--r--drivers/usb/phy/Kconfig195
-rw-r--r--drivers/usb/phy/Makefile27
-rw-r--r--drivers/usb/phy/of.c43
-rw-r--r--drivers/usb/phy/phy-ab8500-usb.c1013
-rw-r--r--drivers/usb/phy/phy-am335x-control.c192
-rw-r--r--drivers/usb/phy/phy-am335x-control.h24
-rw-r--r--drivers/usb/phy/phy-am335x.c146
-rw-r--r--drivers/usb/phy/phy-fsl-usb.c1018
-rw-r--r--drivers/usb/phy/phy-fsl-usb.h378
-rw-r--r--drivers/usb/phy/phy-generic.c376
-rw-r--r--drivers/usb/phy/phy-generic.h27
-rw-r--r--drivers/usb/phy/phy-gpio-vbus-usb.c384
-rw-r--r--drivers/usb/phy/phy-isp1301-omap.c1636
-rw-r--r--drivers/usb/phy/phy-isp1301.c168
-rw-r--r--drivers/usb/phy/phy-jz4770.c362
-rw-r--r--drivers/usb/phy/phy-keystone.c120
-rw-r--r--drivers/usb/phy/phy-mv-usb.c885
-rw-r--r--drivers/usb/phy/phy-mv-usb.h160
-rw-r--r--drivers/usb/phy/phy-mxs-usb.c878
-rw-r--r--drivers/usb/phy/phy-omap-otg.c150
-rw-r--r--drivers/usb/phy/phy-tahvo.c444
-rw-r--r--drivers/usb/phy/phy-tegra-usb.c1225
-rw-r--r--drivers/usb/phy/phy-twl6030-usb.c460
-rw-r--r--drivers/usb/phy/phy-ulpi-viewport.c67
-rw-r--r--drivers/usb/phy/phy-ulpi.c303
-rw-r--r--drivers/usb/phy/phy.c725
26 files changed, 11406 insertions, 0 deletions
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
new file mode 100644
index 000000000..ef4787cd3
--- /dev/null
+++ b/drivers/usb/phy/Kconfig
@@ -0,0 +1,195 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Physical Layer USB driver configuration
+#
+menu "USB Physical Layer drivers"
+
+config USB_PHY
+ select EXTCON
+ def_bool n
+
+#
+# USB Transceiver Drivers
+#
+config AB8500_USB
+ tristate "AB8500 USB Transceiver Driver"
+ depends on AB8500_CORE
+ select USB_PHY
+ 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
+ tristate "Freescale USB OTG Transceiver Driver"
+ depends on USB_EHCI_FSL && USB_FSL_USB2 && USB_OTG_FSM=y && PM
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
+ select USB_PHY
+ help
+ Enable this to support Freescale USB OTG transceiver.
+
+config ISP1301_OMAP
+ tristate "Philips ISP1301 with OMAP OTG"
+ depends on I2C && ARCH_OMAP_OTG
+ depends on USB
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
+ select USB_PHY
+ 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 phy-isp1301-omap.
+
+config KEYSTONE_USB_PHY
+ tristate "Keystone USB PHY Driver"
+ depends on ARCH_KEYSTONE || COMPILE_TEST
+ depends on NOP_USB_XCEIV
+ help
+ Enable this to support Keystone USB phy. This driver provides
+ interface to interact with USB 2.0 and USB 3.0 PHY that is part
+ of the Keystone SOC.
+
+config NOP_USB_XCEIV
+ tristate "NOP USB Transceiver Driver"
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, NOP can't be built-in
+ select USB_PHY
+ 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 AM335X_CONTROL_USB
+ tristate
+
+config AM335X_PHY_USB
+ tristate "AM335x USB PHY Driver"
+ depends on ARM || COMPILE_TEST
+ depends on NOP_USB_XCEIV
+ select USB_PHY
+ select AM335X_CONTROL_USB
+ select USB_COMMON
+ help
+ This driver provides PHY support for that phy which part for the
+ AM335x SoC.
+
+config TWL6030_USB
+ tristate "TWL6030 USB Transceiver Driver"
+ depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS
+ depends on OF
+ 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 GPIOLIB || COMPILE_TEST
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
+ select USB_PHY
+ 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 OMAP_OTG
+ tristate "OMAP USB OTG controller driver"
+ depends on ARCH_OMAP_OTG && EXTCON
+ help
+ Enable this to support some transceivers on OMAP1 platforms. OTG
+ controller is needed to switch between host and peripheral modes.
+
+ This driver can also be built as a module. If so, the module
+ will be called phy-omap-otg.
+
+config TAHVO_USB
+ tristate "Tahvo USB transceiver driver"
+ depends on MFD_RETU
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
+ select USB_PHY
+ help
+ Enable this to support USB transceiver on Tahvo. This is used
+ at least on Nokia 770.
+
+config TAHVO_USB_HOST_BY_DEFAULT
+ depends on TAHVO_USB
+ bool "Device in USB host mode by default"
+ help
+ Say Y here, if you want the device to enter USB host mode
+ by default on bootup.
+
+config USB_ISP1301
+ tristate "NXP ISP1301 USB transceiver support"
+ depends on USB || USB_GADGET
+ depends on I2C
+ select USB_PHY
+ 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
+ and OTG drivers (to be selected separately).
+
+ To compile this driver as a module, choose M here: the
+ module will be called phy-isp1301.
+
+config USB_MV_OTG
+ tristate "Marvell USB OTG support"
+ depends on USB_EHCI_MV && USB_MV_UDC && PM && USB_OTG
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
+ select USB_PHY
+ 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
+ select USB_PHY
+ 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_TEGRA_PHY
+ tristate "NVIDIA Tegra USB PHY Driver"
+ depends on ARCH_TEGRA || COMPILE_TEST
+ select USB_COMMON
+ select USB_PHY
+ select USB_ULPI
+ help
+ This driver provides PHY support for the USB controllers found
+ on NVIDIA Tegra SoC's.
+
+config USB_ULPI
+ bool "Generic ULPI Transceiver Driver"
+ depends on ARM || ARM64 || COMPILE_TEST
+ select USB_ULPI_VIEWPORT
+ help
+ Enable this to support ULPI connected USB OTG transceivers which
+ are likely found on embedded boards.
+
+config USB_ULPI_VIEWPORT
+ bool
+ help
+ Provides read/write operations to the ULPI phy register set for
+ controllers with a viewport register (e.g. Chipidea/ARC controllers).
+
+config JZ4770_PHY
+ tristate "Ingenic SoCs Transceiver Driver"
+ depends on MIPS || COMPILE_TEST
+ select USB_PHY
+ help
+ This driver provides PHY support for the USB controller found
+ on the JZ-series and X-series SoCs from Ingenic.
+
+endmenu
diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile
new file mode 100644
index 000000000..b352bdbe8
--- /dev/null
+++ b/drivers/usb/phy/Makefile
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for physical layer USB drivers
+#
+obj-$(CONFIG_USB_PHY) += phy.o
+obj-$(CONFIG_OF) += of.o
+
+# transceiver drivers, keep the list sorted
+
+obj-$(CONFIG_AB8500_USB) += phy-ab8500-usb.o
+obj-$(CONFIG_FSL_USB2_OTG) += phy-fsl-usb.o
+obj-$(CONFIG_ISP1301_OMAP) += phy-isp1301-omap.o
+obj-$(CONFIG_NOP_USB_XCEIV) += phy-generic.o
+obj-$(CONFIG_TAHVO_USB) += phy-tahvo.o
+obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o
+obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o
+obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o
+obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o
+obj-$(CONFIG_USB_TEGRA_PHY) += phy-tegra-usb.o
+obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o
+obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o
+obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o
+obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o
+obj-$(CONFIG_USB_ULPI) += phy-ulpi.o
+obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o
+obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o
+obj-$(CONFIG_JZ4770_PHY) += phy-jz4770.o
diff --git a/drivers/usb/phy/of.c b/drivers/usb/phy/of.c
new file mode 100644
index 000000000..1ab134f45
--- /dev/null
+++ b/drivers/usb/phy/of.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB of helper code
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/usb/of.h>
+#include <linux/usb/otg.h>
+
+static const char *const usbphy_modes[] = {
+ [USBPHY_INTERFACE_MODE_UNKNOWN] = "",
+ [USBPHY_INTERFACE_MODE_UTMI] = "utmi",
+ [USBPHY_INTERFACE_MODE_UTMIW] = "utmi_wide",
+ [USBPHY_INTERFACE_MODE_ULPI] = "ulpi",
+ [USBPHY_INTERFACE_MODE_SERIAL] = "serial",
+ [USBPHY_INTERFACE_MODE_HSIC] = "hsic",
+};
+
+/**
+ * of_usb_get_phy_mode - Get phy mode for given device_node
+ * @np: Pointer to the given device_node
+ *
+ * The function gets phy interface string from property 'phy_type',
+ * and returns the corresponding enum usb_phy_interface
+ */
+enum usb_phy_interface of_usb_get_phy_mode(struct device_node *np)
+{
+ const char *phy_type;
+ int err, i;
+
+ err = of_property_read_string(np, "phy_type", &phy_type);
+ if (err < 0)
+ return USBPHY_INTERFACE_MODE_UNKNOWN;
+
+ for (i = 0; i < ARRAY_SIZE(usbphy_modes); i++)
+ if (!strcmp(phy_type, usbphy_modes[i]))
+ return i;
+
+ return USBPHY_INTERFACE_MODE_UNKNOWN;
+}
+EXPORT_SYMBOL_GPL(of_usb_get_phy_mode);
diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c
new file mode 100644
index 000000000..4c52ba96f
--- /dev/null
+++ b/drivers/usb/phy/phy-ab8500-usb.c
@@ -0,0 +1,1013 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB transceiver driver for AB8500 family chips
+ *
+ * Copyright (C) 2010-2013 ST-Ericsson AB
+ * Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
+ * Avinash Kumar <avinash.kumar@stericsson.com>
+ * Thirupathi Chippakurthy <thirupathi.chippakurthy@stericsson.com>
+ */
+
+#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/clk.h>
+#include <linux/err.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
+
+/* Bank AB8500_INTERRUPT */
+#define AB8500_IT_SOURCE2_REG 0x01
+
+#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_BIT_SOURCE2_VBUSDET (1 << 7)
+
+#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,
+ USB_UART
+};
+
+/* Register USB_LINK_STATUS interrupt */
+#define AB8500_USB_FLAG_USE_LINK_STATUS_IRQ (1 << 0)
+/* Register ID_WAKEUP_F interrupt */
+#define AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ (1 << 1)
+/* Register VBUS_DET_F interrupt */
+#define AB8500_USB_FLAG_USE_VBUS_DET_IRQ (1 << 2)
+/* Driver is using the ab-iddet driver*/
+#define AB8500_USB_FLAG_USE_AB_IDDET (1 << 3)
+/* Enable setting regulators voltage */
+#define AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE (1 << 4)
+
+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 clk *sysclk;
+ 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;
+ bool enabled_charging_detection;
+ unsigned int flags;
+};
+
+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 (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) {
+ 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_load(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 (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) {
+ 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 (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) {
+ 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_load(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");
+
+ if (clk_prepare_enable(ab->sysclk))
+ dev_err(ab->dev, "can't prepare/enable clock\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);
+
+ clk_disable_unprepare(ab->sysclk);
+
+ 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 iddet, 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;
+ fallthrough;
+ 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.otg->state = OTG_STATE_B_IDLE;
+ usb_phy_set_event(&ab->phy, USB_EVENT_NONE);
+ break;
+
+ case USB_LINK_ACA_RID_C_NM_8505:
+ event = UX500_MUSB_RIDC;
+ fallthrough;
+ 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);
+ usb_phy_set_event(&ab->phy, USB_EVENT_ENUMERATED);
+ }
+ 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;
+ fallthrough;
+ 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);
+ usb_phy_set_event(&ab->phy, USB_EVENT_CHARGER);
+ break;
+
+ /*
+ * FIXME: For now we rely on the boot firmware to set up the necessary
+ * PHY/pin configuration for UART mode.
+ *
+ * AB8505 does not seem to report any status change for UART cables,
+ * possibly because it cannot detect them autonomously.
+ * We may need to measure the ID resistance manually to reliably
+ * detect UART cables after bootup.
+ */
+ case USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8505:
+ case USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8505:
+ if (ab->mode == USB_IDLE) {
+ ab->mode = USB_UART;
+ ab8500_usb_peri_phy_en(ab);
+ }
+
+ 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;
+ fallthrough;
+ 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.otg->state = OTG_STATE_B_IDLE;
+ usb_phy_set_event(&ab->phy, USB_EVENT_NONE);
+ 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;
+ fallthrough;
+ 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);
+ usb_phy_set_event(&ab->phy, USB_EVENT_ENUMERATED);
+ }
+ if (event != UX500_MUSB_RIDC)
+ event = UX500_MUSB_VBUS;
+ break;
+
+ case USB_LINK_ACA_RID_A_8500:
+ event = UX500_MUSB_RIDA;
+ fallthrough;
+ 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);
+ usb_phy_set_event(&ab->phy, USB_EVENT_CHARGER);
+ 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 function 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;
+
+ ret = abx500_get_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_LINE_STAT_REG, &reg);
+ if (ret < 0)
+ return ret;
+ lsts = (reg >> 3) & 0x0F;
+ ret = ab8500_usb_link_status_update(ab, lsts);
+ } else if (is_ab8505(ab->ab8500)) {
+ enum ab8505_usb_link_status lsts;
+
+ ret = abx500_get_register_interruptible(ab->dev,
+ AB8500_USB, AB8505_USB_LINE_STAT_REG, &reg);
+ if (ret < 0)
+ return ret;
+ lsts = (reg >> 3) & 0x1F;
+ ret = ab8505_usb_link_status_update(ab, lsts);
+ }
+
+ return ret;
+}
+
+/*
+ * Disconnection Sequence:
+ * 1. Disconnect 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 = USB_EVENT_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 (ab->mode == USB_UART) {
+ ab8500_usb_peri_phy_dis(ab);
+ ab->mode = USB_IDLE;
+ }
+
+ 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 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->usb_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->usb_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 void ab8500_usb_restart_phy(struct ab8500_usb *ab)
+{
+ abx500_mask_and_set_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_PHY_CTRL_REG,
+ AB8500_BIT_PHY_CTRL_DEVICE_EN,
+ AB8500_BIT_PHY_CTRL_DEVICE_EN);
+
+ udelay(100);
+
+ abx500_mask_and_set_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_PHY_CTRL_REG,
+ AB8500_BIT_PHY_CTRL_DEVICE_EN,
+ 0);
+
+ abx500_mask_and_set_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_PHY_CTRL_REG,
+ AB8500_BIT_PHY_CTRL_HOST_EN,
+ AB8500_BIT_PHY_CTRL_HOST_EN);
+
+ udelay(100);
+
+ abx500_mask_and_set_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_PHY_CTRL_REG,
+ AB8500_BIT_PHY_CTRL_HOST_EN,
+ 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;
+
+ if (ab->flags & AB8500_USB_FLAG_USE_LINK_STATUS_IRQ) {
+ irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS");
+ if (irq < 0)
+ return irq;
+ err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ ab8500_usb_link_status_irq,
+ IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT,
+ "usb-link-status", ab);
+ if (err < 0) {
+ dev_err(ab->dev, "request_irq failed for link status irq\n");
+ return err;
+ }
+ }
+
+ if (ab->flags & AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ) {
+ irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F");
+ if (irq < 0)
+ return irq;
+ err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ ab8500_usb_disconnect_irq,
+ IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT,
+ "usb-id-fall", ab);
+ if (err < 0) {
+ dev_err(ab->dev, "request_irq failed for ID fall irq\n");
+ return err;
+ }
+ }
+
+ if (ab->flags & AB8500_USB_FLAG_USE_VBUS_DET_IRQ) {
+ irq = platform_get_irq_byname(pdev, "VBUS_DET_F");
+ if (irq < 0)
+ return irq;
+ err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ ab8500_usb_disconnect_irq,
+ IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT,
+ "usb-vbus-fall", ab);
+ if (err < 0) {
+ dev_err(ab->dev, "request_irq failed for Vbus fall irq\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void ab8500_usb_set_ab8500_tuning_values(struct ab8500_usb *ab)
+{
+ int err;
+
+ /* 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 register 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);
+}
+
+static void ab8500_usb_set_ab8505_tuning_values(struct ab8500_usb *ab)
+{
+ int err;
+
+ /* 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 register 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);
+}
+
+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.otg->state = OTG_STATE_UNDEFINED;
+
+ otg->usb_phy = &ab->phy;
+ otg->set_host = ab8500_usb_set_host;
+ otg->set_peripheral = ab8500_usb_set_peripheral;
+
+ if (is_ab8500(ab->ab8500)) {
+ ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ |
+ AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ |
+ AB8500_USB_FLAG_USE_VBUS_DET_IRQ |
+ AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE;
+ } else if (is_ab8505(ab->ab8500)) {
+ ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ |
+ AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ |
+ AB8500_USB_FLAG_USE_VBUS_DET_IRQ |
+ AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE;
+ }
+
+ /* Disable regulator voltage setting for AB8500 <= v2.0 */
+ if (is_ab8500_2p0_or_earlier(ab->ab8500))
+ ab->flags &= ~AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE;
+
+ platform_set_drvdata(pdev, ab);
+
+ /* 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;
+
+ ab->sysclk = devm_clk_get(ab->dev, "sysclk");
+ if (IS_ERR(ab->sysclk)) {
+ dev_err(ab->dev, "Could not get sysclk.\n");
+ return PTR_ERR(ab->sysclk);
+ }
+
+ 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;
+ }
+
+ if (is_ab8500(ab->ab8500) && !is_ab8500_2p0_or_earlier(ab->ab8500))
+ /* Phy tuning values for AB8500 > v2.0 */
+ ab8500_usb_set_ab8500_tuning_values(ab);
+ else if (is_ab8505(ab->ab8500))
+ /* Phy tuning values for AB8505 */
+ ab8500_usb_set_ab8505_tuning_values(ab);
+
+ /* Needed to enable ID detection. */
+ ab8500_usb_wd_workaround(ab);
+
+ /*
+ * This is required for usb-link-status to work properly when a
+ * cable is connected at boot time.
+ */
+ ab8500_usb_restart_phy(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);
+
+ return 0;
+}
+
+static const struct platform_device_id ab8500_usb_devtype[] = {
+ { .name = "ab8500-usb", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, ab8500_usb_devtype);
+
+static struct platform_driver ab8500_usb_driver = {
+ .probe = ab8500_usb_probe,
+ .remove = ab8500_usb_remove,
+ .id_table = ab8500_usb_devtype,
+ .driver = {
+ .name = "abx5x0-usb",
+ },
+};
+
+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_AUTHOR("ST-Ericsson AB");
+MODULE_DESCRIPTION("AB8500 family usb transceiver driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/phy/phy-am335x-control.c b/drivers/usb/phy/phy-am335x-control.c
new file mode 100644
index 000000000..97e6603c7
--- /dev/null
+++ b/drivers/usb/phy/phy-am335x-control.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/usb/otg.h>
+#include "phy-am335x-control.h"
+
+struct am335x_control_usb {
+ struct device *dev;
+ void __iomem *phy_reg;
+ void __iomem *wkup;
+ spinlock_t lock;
+ struct phy_control phy_ctrl;
+};
+
+#define AM335X_USB0_CTRL 0x0
+#define AM335X_USB1_CTRL 0x8
+#define AM335x_USB_WKUP 0x0
+
+#define USBPHY_CM_PWRDN (1 << 0)
+#define USBPHY_OTG_PWRDN (1 << 1)
+#define USBPHY_OTGVDET_EN (1 << 19)
+#define USBPHY_OTGSESSEND_EN (1 << 20)
+
+#define AM335X_PHY0_WK_EN (1 << 0)
+#define AM335X_PHY1_WK_EN (1 << 8)
+
+static void am335x_phy_wkup(struct phy_control *phy_ctrl, u32 id, bool on)
+{
+ struct am335x_control_usb *usb_ctrl;
+ u32 val;
+ u32 reg;
+
+ usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl);
+
+ switch (id) {
+ case 0:
+ reg = AM335X_PHY0_WK_EN;
+ break;
+ case 1:
+ reg = AM335X_PHY1_WK_EN;
+ break;
+ default:
+ WARN_ON(1);
+ return;
+ }
+
+ spin_lock(&usb_ctrl->lock);
+ val = readl(usb_ctrl->wkup);
+
+ if (on)
+ val |= reg;
+ else
+ val &= ~reg;
+
+ writel(val, usb_ctrl->wkup);
+ spin_unlock(&usb_ctrl->lock);
+}
+
+static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id,
+ enum usb_dr_mode dr_mode, bool on)
+{
+ struct am335x_control_usb *usb_ctrl;
+ u32 val;
+ u32 reg;
+
+ usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl);
+
+ switch (id) {
+ case 0:
+ reg = AM335X_USB0_CTRL;
+ break;
+ case 1:
+ reg = AM335X_USB1_CTRL;
+ break;
+ default:
+ WARN_ON(1);
+ return;
+ }
+
+ val = readl(usb_ctrl->phy_reg + reg);
+ if (on) {
+ if (dr_mode == USB_DR_MODE_HOST) {
+ val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN |
+ USBPHY_OTGVDET_EN);
+ val |= USBPHY_OTGSESSEND_EN;
+ } else {
+ val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN);
+ val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN;
+ }
+ } else {
+ val |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN;
+ }
+
+ writel(val, usb_ctrl->phy_reg + reg);
+
+ /*
+ * Give the PHY ~1ms to complete the power up operation.
+ * Tests have shown unstable behaviour if other USB PHY related
+ * registers are written too shortly after such a transition.
+ */
+ if (on)
+ mdelay(1);
+}
+
+static const struct phy_control ctrl_am335x = {
+ .phy_power = am335x_phy_power,
+ .phy_wkup = am335x_phy_wkup,
+};
+
+static const struct of_device_id omap_control_usb_id_table[] = {
+ { .compatible = "ti,am335x-usb-ctrl-module", .data = &ctrl_am335x },
+ {}
+};
+MODULE_DEVICE_TABLE(of, omap_control_usb_id_table);
+
+static struct platform_driver am335x_control_driver;
+static int match(struct device *dev, const void *data)
+{
+ const struct device_node *node = (const struct device_node *)data;
+ return dev->of_node == node &&
+ dev->driver == &am335x_control_driver.driver;
+}
+
+struct phy_control *am335x_get_phy_control(struct device *dev)
+{
+ struct device_node *node;
+ struct am335x_control_usb *ctrl_usb;
+
+ node = of_parse_phandle(dev->of_node, "ti,ctrl_mod", 0);
+ if (!node)
+ return NULL;
+
+ dev = bus_find_device(&platform_bus_type, NULL, node, match);
+ of_node_put(node);
+ if (!dev)
+ return NULL;
+
+ ctrl_usb = dev_get_drvdata(dev);
+ put_device(dev);
+ if (!ctrl_usb)
+ return NULL;
+ return &ctrl_usb->phy_ctrl;
+}
+EXPORT_SYMBOL_GPL(am335x_get_phy_control);
+
+static int am335x_control_usb_probe(struct platform_device *pdev)
+{
+ struct am335x_control_usb *ctrl_usb;
+ const struct of_device_id *of_id;
+ const struct phy_control *phy_ctrl;
+
+ of_id = of_match_node(omap_control_usb_id_table, pdev->dev.of_node);
+ if (!of_id)
+ return -EINVAL;
+
+ phy_ctrl = of_id->data;
+
+ ctrl_usb = devm_kzalloc(&pdev->dev, sizeof(*ctrl_usb), GFP_KERNEL);
+ if (!ctrl_usb)
+ return -ENOMEM;
+
+ ctrl_usb->dev = &pdev->dev;
+
+ ctrl_usb->phy_reg = devm_platform_ioremap_resource_byname(pdev, "phy_ctrl");
+ if (IS_ERR(ctrl_usb->phy_reg))
+ return PTR_ERR(ctrl_usb->phy_reg);
+
+ ctrl_usb->wkup = devm_platform_ioremap_resource_byname(pdev, "wakeup");
+ if (IS_ERR(ctrl_usb->wkup))
+ return PTR_ERR(ctrl_usb->wkup);
+
+ spin_lock_init(&ctrl_usb->lock);
+ ctrl_usb->phy_ctrl = *phy_ctrl;
+
+ dev_set_drvdata(ctrl_usb->dev, ctrl_usb);
+ return 0;
+}
+
+static struct platform_driver am335x_control_driver = {
+ .probe = am335x_control_usb_probe,
+ .driver = {
+ .name = "am335x-control-usb",
+ .of_match_table = omap_control_usb_id_table,
+ },
+};
+
+module_platform_driver(am335x_control_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/phy/phy-am335x-control.h b/drivers/usb/phy/phy-am335x-control.h
new file mode 100644
index 000000000..cd4acfc6e
--- /dev/null
+++ b/drivers/usb/phy/phy-am335x-control.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _AM335x_PHY_CONTROL_H_
+#define _AM335x_PHY_CONTROL_H_
+
+struct phy_control {
+ void (*phy_power)(struct phy_control *phy_ctrl, u32 id,
+ enum usb_dr_mode dr_mode, bool on);
+ void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on);
+};
+
+static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id,
+ enum usb_dr_mode dr_mode, bool on)
+{
+ phy_ctrl->phy_power(phy_ctrl, id, dr_mode, on);
+}
+
+static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on)
+{
+ phy_ctrl->phy_wkup(phy_ctrl, id, on);
+}
+
+struct phy_control *am335x_get_phy_control(struct device *dev);
+
+#endif
diff --git a/drivers/usb/phy/phy-am335x.c b/drivers/usb/phy/phy-am335x.c
new file mode 100644
index 000000000..8524475d9
--- /dev/null
+++ b/drivers/usb/phy/phy-am335x.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/usb_phy_generic.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/usb/of.h>
+
+#include "phy-am335x-control.h"
+#include "phy-generic.h"
+
+struct am335x_phy {
+ struct usb_phy_generic usb_phy_gen;
+ struct phy_control *phy_ctrl;
+ int id;
+ enum usb_dr_mode dr_mode;
+};
+
+static int am335x_init(struct usb_phy *phy)
+{
+ struct am335x_phy *am_phy = dev_get_drvdata(phy->dev);
+
+ phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true);
+ return 0;
+}
+
+static void am335x_shutdown(struct usb_phy *phy)
+{
+ struct am335x_phy *am_phy = dev_get_drvdata(phy->dev);
+
+ phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false);
+}
+
+static int am335x_phy_probe(struct platform_device *pdev)
+{
+ struct am335x_phy *am_phy;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ am_phy = devm_kzalloc(dev, sizeof(*am_phy), GFP_KERNEL);
+ if (!am_phy)
+ return -ENOMEM;
+
+ am_phy->phy_ctrl = am335x_get_phy_control(dev);
+ if (!am_phy->phy_ctrl)
+ return -EPROBE_DEFER;
+
+ am_phy->id = of_alias_get_id(pdev->dev.of_node, "phy");
+ if (am_phy->id < 0) {
+ dev_err(&pdev->dev, "Missing PHY id: %d\n", am_phy->id);
+ return am_phy->id;
+ }
+
+ am_phy->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node, -1);
+
+ ret = usb_phy_gen_create_phy(dev, &am_phy->usb_phy_gen);
+ if (ret)
+ return ret;
+
+ am_phy->usb_phy_gen.phy.init = am335x_init;
+ am_phy->usb_phy_gen.phy.shutdown = am335x_shutdown;
+
+ platform_set_drvdata(pdev, am_phy);
+ device_init_wakeup(dev, true);
+
+ /*
+ * If we leave PHY wakeup enabled then AM33XX wakes up
+ * immediately from DS0. To avoid this we mark dev->power.can_wakeup
+ * to false. The same is checked in suspend routine to decide
+ * on whether to enable PHY wakeup or not.
+ * PHY wakeup works fine in standby mode, there by allowing us to
+ * handle remote wakeup, wakeup on disconnect and connect.
+ */
+
+ device_set_wakeup_enable(dev, false);
+ phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false);
+
+ return usb_add_phy_dev(&am_phy->usb_phy_gen.phy);
+}
+
+static int am335x_phy_remove(struct platform_device *pdev)
+{
+ struct am335x_phy *am_phy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&am_phy->usb_phy_gen.phy);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int am335x_phy_suspend(struct device *dev)
+{
+ struct am335x_phy *am_phy = dev_get_drvdata(dev);
+
+ /*
+ * Enable phy wakeup only if dev->power.can_wakeup is true.
+ * Make sure to enable wakeup to support remote wakeup in
+ * standby mode ( same is not supported in OFF(DS0) mode).
+ * Enable it by doing
+ * echo enabled > /sys/bus/platform/devices/<usb-phy-id>/power/wakeup
+ */
+
+ if (device_may_wakeup(dev))
+ phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, true);
+
+ phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false);
+
+ return 0;
+}
+
+static int am335x_phy_resume(struct device *dev)
+{
+ struct am335x_phy *am_phy = dev_get_drvdata(dev);
+
+ phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true);
+
+ if (device_may_wakeup(dev))
+ phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, false);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(am335x_pm_ops, am335x_phy_suspend, am335x_phy_resume);
+
+static const struct of_device_id am335x_phy_ids[] = {
+ { .compatible = "ti,am335x-usb-phy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, am335x_phy_ids);
+
+static struct platform_driver am335x_phy_driver = {
+ .probe = am335x_phy_probe,
+ .remove = am335x_phy_remove,
+ .driver = {
+ .name = "am335x-phy-driver",
+ .pm = &am335x_pm_ops,
+ .of_match_table = am335x_phy_ids,
+ },
+};
+
+module_platform_driver(am335x_phy_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c
new file mode 100644
index 000000000..972704262
--- /dev/null
+++ b/drivers/usb/phy/phy-fsl-usb.c
@@ -0,0 +1,1018 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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.
+ */
+
+#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/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"
+
+#ifdef VERBOSE
+#define VDBG(fmt, args...) pr_debug("[%s] " fmt, \
+ __func__, ## args)
+#else
+#define VDBG(stuff...) do {} while (0)
+#endif
+
+#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 const 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 */
+
+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(struct otg_fsm *fsm, 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(struct otg_fsm *fsm, 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(struct otg_fsm *fsm, 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(struct otg_fsm *fsm, 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(struct otg_fsm *fsm)
+{
+ 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(fsm, 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(&fsl_otg_dev->fsm, 1);
+ /* start the timer to end vbus charge */
+ fsl_otg_add_timer(&fsl_otg_dev->fsm, b_vbus_pulse_tmr);
+}
+
+void b_vbus_pulse_end(unsigned long foo)
+{
+ fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 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(&fsl_otg_dev->fsm, 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.otg->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(&fsl_otg_dev->fsm, 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);
+}
+
+static struct fsl_otg_timer *fsl_otg_get_timer(enum otg_fsm_timer t)
+{
+ struct fsl_otg_timer *timer;
+
+ /* REVISIT: use array of pointers to timers instead */
+ switch (t) {
+ case A_WAIT_VRISE:
+ timer = a_wait_vrise_tmr;
+ break;
+ case A_WAIT_BCON:
+ timer = a_wait_vrise_tmr;
+ break;
+ case A_AIDL_BDIS:
+ timer = a_wait_vrise_tmr;
+ break;
+ case B_ASE0_BRST:
+ timer = a_wait_vrise_tmr;
+ break;
+ case B_SE0_SRP:
+ timer = a_wait_vrise_tmr;
+ break;
+ case B_SRP_FAIL:
+ timer = a_wait_vrise_tmr;
+ break;
+ case A_WAIT_ENUM:
+ timer = a_wait_vrise_tmr;
+ break;
+ default:
+ timer = NULL;
+ }
+
+ return timer;
+}
+
+/* Add timer to timer list */
+void fsl_otg_add_timer(struct otg_fsm *fsm, 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);
+}
+
+static void fsl_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
+{
+ struct fsl_otg_timer *timer;
+
+ timer = fsl_otg_get_timer(t);
+ if (!timer)
+ return;
+
+ fsl_otg_add_timer(fsm, timer);
+}
+
+/* Remove timer from the timer list; clear timeout status */
+void fsl_otg_del_timer(struct otg_fsm *fsm, 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);
+}
+
+static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
+{
+ struct fsl_otg_timer *timer;
+
+ timer = fsl_otg_get_timer(t);
+ if (!timer)
+ return;
+
+ fsl_otg_del_timer(fsm, timer);
+}
+
+/* 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->usb_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(fsm, 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(fsm, 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->usb_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 hub_wq 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->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->usb_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 */
+ pr_debug("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;
+}
+
+/*
+ * 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->state != OTG_STATE_B_IDLE)
+ return -ENODEV;
+
+ otg_dev = container_of(otg->usb_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->usb_phy, struct fsl_otg, phy);
+ if (otg_dev != fsl_otg_dev)
+ return -ENODEV;
+
+ pr_debug("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_fsm_add_timer,
+ .del_timer = fsl_otg_fsm_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;
+ }
+ mutex_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.dev = &pdev->dev;
+
+ fsl_otg_tc->phy.otg->usb_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 = dev_get_platdata(&pdev->dev);
+
+ 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;
+
+#ifdef CONFIG_PPC32
+ 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;
+ }
+#endif
+
+ /* request irq */
+ p_otg->irq = platform_get_irq(pdev, 0);
+ if (p_otg->irq < 0)
+ return p_otg->irq;
+ 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;
+ fallthrough;
+ case FSL_USB2_PHY_UTMI:
+ temp |= PORTSC_PTS_UTMI;
+ fallthrough;
+ 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.otg->state = OTG_STATE_UNDEFINED;
+ p_otg->fsm.id = 1;
+ } else {
+ p_otg->phy.otg->state = OTG_STATE_A_IDLE;
+ p_otg->fsm.id = 0;
+ }
+
+ pr_debug("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;
+}
+
+static int fsl_otg_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ if (!dev_get_platdata(&pdev->dev))
+ 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;
+ }
+
+ return ret;
+}
+
+static int fsl_otg_remove(struct platform_device *pdev)
+{
+ struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+ 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);
+
+ 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 000000000..d70341ae5
--- /dev/null
+++ b/drivers/usb/phy/phy-fsl-usb.h
@@ -0,0 +1,378 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. */
+
+#include <linux/usb/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(phy, newstate) ((phy)->otg->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;
+};
+
+#define FSL_OTG_NAME "fsl-usb2-otg"
+
+void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer);
+void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer);
+void fsl_otg_pulse_vbus(void);
diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c
new file mode 100644
index 000000000..34b9f8140
--- /dev/null
+++ b/drivers/usb/phy/phy-generic.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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>
+ *
+ * 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/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/usb_phy_generic.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#include "phy-generic.h"
+
+#define VBUS_IRQ_FLAGS \
+ (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \
+ IRQF_ONESHOT)
+
+struct platform_device *usb_phy_generic_register(void)
+{
+ return platform_device_register_simple("usb_phy_generic",
+ PLATFORM_DEVID_AUTO, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(usb_phy_generic_register);
+
+void usb_phy_generic_unregister(struct platform_device *pdev)
+{
+ platform_device_unregister(pdev);
+}
+EXPORT_SYMBOL_GPL(usb_phy_generic_unregister);
+
+static int nop_set_suspend(struct usb_phy *x, int suspend)
+{
+ struct usb_phy_generic *nop = dev_get_drvdata(x->dev);
+
+ if (!IS_ERR(nop->clk)) {
+ if (suspend)
+ clk_disable_unprepare(nop->clk);
+ else
+ clk_prepare_enable(nop->clk);
+ }
+
+ return 0;
+}
+
+static void nop_reset(struct usb_phy_generic *nop)
+{
+ if (!nop->gpiod_reset)
+ return;
+
+ gpiod_set_value_cansleep(nop->gpiod_reset, 1);
+ usleep_range(10000, 20000);
+ gpiod_set_value_cansleep(nop->gpiod_reset, 0);
+}
+
+/* interface to regulator framework */
+static void nop_set_vbus_draw(struct usb_phy_generic *nop, unsigned mA)
+{
+ struct regulator *vbus_draw = nop->vbus_draw;
+ int enabled;
+ int ret;
+
+ if (!vbus_draw)
+ return;
+
+ enabled = nop->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;
+ nop->vbus_draw_enabled = 1;
+ }
+ } else {
+ if (enabled) {
+ ret = regulator_disable(vbus_draw);
+ if (ret < 0)
+ return;
+ nop->vbus_draw_enabled = 0;
+ }
+ }
+ nop->mA = mA;
+}
+
+
+static irqreturn_t nop_gpio_vbus_thread(int irq, void *data)
+{
+ struct usb_phy_generic *nop = data;
+ struct usb_otg *otg = nop->phy.otg;
+ int vbus, status;
+
+ vbus = gpiod_get_value(nop->gpiod_vbus);
+ if ((vbus ^ nop->vbus) == 0)
+ return IRQ_HANDLED;
+ nop->vbus = vbus;
+
+ if (vbus) {
+ status = USB_EVENT_VBUS;
+ otg->state = OTG_STATE_B_PERIPHERAL;
+ nop->phy.last_event = status;
+
+ /* drawing a "unit load" is *always* OK, except for OTG */
+ nop_set_vbus_draw(nop, 100);
+
+ atomic_notifier_call_chain(&nop->phy.notifier, status,
+ otg->gadget);
+ } else {
+ nop_set_vbus_draw(nop, 0);
+
+ status = USB_EVENT_NONE;
+ otg->state = OTG_STATE_B_IDLE;
+ nop->phy.last_event = status;
+
+ atomic_notifier_call_chain(&nop->phy.notifier, status,
+ otg->gadget);
+ }
+ return IRQ_HANDLED;
+}
+
+int usb_gen_phy_init(struct usb_phy *phy)
+{
+ struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
+ int ret;
+
+ if (!IS_ERR(nop->vcc)) {
+ if (regulator_enable(nop->vcc))
+ dev_err(phy->dev, "Failed to enable power\n");
+ }
+
+ if (!IS_ERR(nop->clk)) {
+ ret = clk_prepare_enable(nop->clk);
+ if (ret)
+ return ret;
+ }
+
+ nop_reset(nop);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_gen_phy_init);
+
+void usb_gen_phy_shutdown(struct usb_phy *phy)
+{
+ struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
+
+ gpiod_set_value_cansleep(nop->gpiod_reset, 1);
+
+ if (!IS_ERR(nop->clk))
+ clk_disable_unprepare(nop->clk);
+
+ if (!IS_ERR(nop->vcc)) {
+ if (regulator_disable(nop->vcc))
+ dev_err(phy->dev, "Failed to disable power\n");
+ }
+}
+EXPORT_SYMBOL_GPL(usb_gen_phy_shutdown);
+
+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;
+ if (otg->state == OTG_STATE_B_PERIPHERAL)
+ atomic_notifier_call_chain(&otg->usb_phy->notifier,
+ USB_EVENT_VBUS, otg->gadget);
+ else
+ otg->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;
+}
+
+int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop)
+{
+ enum usb_phy_type type = USB_PHY_TYPE_USB2;
+ int err = 0;
+
+ u32 clk_rate = 0;
+ bool needs_vcc = false, needs_clk = false;
+
+ 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_clk = of_property_read_bool(node, "clocks");
+ }
+ nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_ASIS);
+ err = PTR_ERR_OR_ZERO(nop->gpiod_reset);
+ if (!err) {
+ nop->gpiod_vbus = devm_gpiod_get_optional(dev,
+ "vbus-detect",
+ GPIOD_ASIS);
+ err = PTR_ERR_OR_ZERO(nop->gpiod_vbus);
+ }
+
+ if (err == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ if (err) {
+ dev_err(dev, "Error requesting RESET or VBUS GPIO\n");
+ return err;
+ }
+ if (nop->gpiod_reset)
+ gpiod_direction_output(nop->gpiod_reset, 1);
+
+ nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg),
+ GFP_KERNEL);
+ if (!nop->phy.otg)
+ return -ENOMEM;
+
+ nop->clk = devm_clk_get(dev, "main_clk");
+ if (IS_ERR(nop->clk)) {
+ dev_dbg(dev, "Can't get phy clock: %ld\n",
+ PTR_ERR(nop->clk));
+ if (needs_clk)
+ return PTR_ERR(nop->clk);
+ }
+
+ if (!IS_ERR(nop->clk) && clk_rate) {
+ err = clk_set_rate(nop->clk, clk_rate);
+ if (err) {
+ dev_err(dev, "Error setting clock rate\n");
+ return err;
+ }
+ }
+
+ nop->vcc = devm_regulator_get(dev, "vcc");
+ if (IS_ERR(nop->vcc)) {
+ dev_dbg(dev, "Error getting vcc regulator: %ld\n",
+ PTR_ERR(nop->vcc));
+ if (needs_vcc)
+ return -EPROBE_DEFER;
+ }
+
+ nop->vbus_draw = devm_regulator_get_exclusive(dev, "vbus");
+ if (PTR_ERR(nop->vbus_draw) == -ENODEV)
+ nop->vbus_draw = NULL;
+ if (IS_ERR(nop->vbus_draw))
+ return dev_err_probe(dev, PTR_ERR(nop->vbus_draw),
+ "could not get vbus regulator\n");
+
+ nop->dev = dev;
+ nop->phy.dev = nop->dev;
+ nop->phy.label = "nop-xceiv";
+ nop->phy.set_suspend = nop_set_suspend;
+ nop->phy.type = type;
+
+ nop->phy.otg->state = OTG_STATE_UNDEFINED;
+ nop->phy.otg->usb_phy = &nop->phy;
+ nop->phy.otg->set_host = nop_set_host;
+ nop->phy.otg->set_peripheral = nop_set_peripheral;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_phy_gen_create_phy);
+
+static int usb_phy_generic_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct usb_phy_generic *nop;
+ int err;
+
+ nop = devm_kzalloc(dev, sizeof(*nop), GFP_KERNEL);
+ if (!nop)
+ return -ENOMEM;
+
+ err = usb_phy_gen_create_phy(dev, nop);
+ if (err)
+ return err;
+ if (nop->gpiod_vbus) {
+ err = devm_request_threaded_irq(&pdev->dev,
+ gpiod_to_irq(nop->gpiod_vbus),
+ NULL, nop_gpio_vbus_thread,
+ VBUS_IRQ_FLAGS, "vbus_detect",
+ nop);
+ if (err) {
+ dev_err(&pdev->dev, "can't request irq %i, err: %d\n",
+ gpiod_to_irq(nop->gpiod_vbus), err);
+ return err;
+ }
+ nop->phy.otg->state = gpiod_get_value(nop->gpiod_vbus) ?
+ OTG_STATE_B_PERIPHERAL : OTG_STATE_B_IDLE;
+ }
+
+ nop->phy.init = usb_gen_phy_init;
+ nop->phy.shutdown = usb_gen_phy_shutdown;
+
+ err = usb_add_phy_dev(&nop->phy);
+ if (err) {
+ dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
+ err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, nop);
+
+ return 0;
+}
+
+static int usb_phy_generic_remove(struct platform_device *pdev)
+{
+ struct usb_phy_generic *nop = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&nop->phy);
+
+ 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 usb_phy_generic_driver = {
+ .probe = usb_phy_generic_probe,
+ .remove = usb_phy_generic_remove,
+ .driver = {
+ .name = "usb_phy_generic",
+ .of_match_table = nop_xceiv_dt_ids,
+ },
+};
+
+static int __init usb_phy_generic_init(void)
+{
+ return platform_driver_register(&usb_phy_generic_driver);
+}
+subsys_initcall(usb_phy_generic_init);
+
+static void __exit usb_phy_generic_exit(void)
+{
+ platform_driver_unregister(&usb_phy_generic_driver);
+}
+module_exit(usb_phy_generic_exit);
+
+MODULE_ALIAS("platform:usb_phy_generic");
+MODULE_AUTHOR("Texas Instruments Inc");
+MODULE_DESCRIPTION("NOP USB Transceiver driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h
new file mode 100644
index 000000000..7ee80211a
--- /dev/null
+++ b/drivers/usb/phy/phy-generic.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PHY_GENERIC_H_
+#define _PHY_GENERIC_H_
+
+#include <linux/usb/usb_phy_generic.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+struct usb_phy_generic {
+ struct usb_phy phy;
+ struct device *dev;
+ struct clk *clk;
+ struct regulator *vcc;
+ struct gpio_desc *gpiod_reset;
+ struct gpio_desc *gpiod_vbus;
+ struct regulator *vbus_draw;
+ bool vbus_draw_enabled;
+ unsigned long mA;
+ unsigned int vbus;
+};
+
+int usb_gen_phy_init(struct usb_phy *phy);
+void usb_gen_phy_shutdown(struct usb_phy *phy);
+
+int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop);
+
+#endif
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 000000000..f13f55307
--- /dev/null
+++ b/drivers/usb/phy/phy-gpio-vbus-usb.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices
+ *
+ * Copyright (c) 2008 Philipp Zabel <philipp.zabel@gmail.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.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/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 gpio_desc *vbus_gpiod;
+ struct gpio_desc *pullup_gpiod;
+ 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_data *gpio_vbus)
+{
+ return gpiod_get_value(gpio_vbus->vbus_gpiod);
+}
+
+static void gpio_vbus_work(struct work_struct *work)
+{
+ struct gpio_vbus_data *gpio_vbus =
+ container_of(work, struct gpio_vbus_data, work.work);
+ int status, vbus;
+
+ if (!gpio_vbus->phy.otg->gadget)
+ return;
+
+ vbus = is_vbus_powered(gpio_vbus);
+ if ((vbus ^ gpio_vbus->vbus) == 0)
+ return;
+ gpio_vbus->vbus = vbus;
+
+ /* Peripheral controllers which manage the pullup themselves won't have
+ * a pullup GPIO 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.
+ */
+
+ if (vbus) {
+ status = USB_EVENT_VBUS;
+ gpio_vbus->phy.otg->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_vbus->pullup_gpiod)
+ gpiod_set_value(gpio_vbus->pullup_gpiod, 1);
+
+ atomic_notifier_call_chain(&gpio_vbus->phy.notifier,
+ status, gpio_vbus->phy.otg->gadget);
+ usb_phy_set_event(&gpio_vbus->phy, USB_EVENT_ENUMERATED);
+ } else {
+ /* optionally disable D+ pullup */
+ if (gpio_vbus->pullup_gpiod)
+ gpiod_set_value(gpio_vbus->pullup_gpiod, 0);
+
+ set_vbus_draw(gpio_vbus, 0);
+
+ usb_gadget_vbus_disconnect(gpio_vbus->phy.otg->gadget);
+ status = USB_EVENT_NONE;
+ gpio_vbus->phy.otg->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);
+ usb_phy_set_event(&gpio_vbus->phy, USB_EVENT_NONE);
+ }
+}
+
+/* VBUS change IRQ handler */
+static irqreturn_t gpio_vbus_irq(int irq, void *data)
+{
+ struct platform_device *pdev = 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(gpio_vbus) ? "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 platform_device *pdev;
+
+ gpio_vbus = container_of(otg->usb_phy, struct gpio_vbus_data, phy);
+ pdev = to_platform_device(gpio_vbus->dev);
+
+ if (!gadget) {
+ dev_dbg(&pdev->dev, "unregistering gadget '%s'\n",
+ otg->gadget->name);
+
+ /* optionally disable D+ pullup */
+ if (gpio_vbus->pullup_gpiod)
+ gpiod_set_value(gpio_vbus->pullup_gpiod, 0);
+
+ set_vbus_draw(gpio_vbus, 0);
+
+ usb_gadget_vbus_disconnect(otg->gadget);
+ otg->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->otg->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 gpio_vbus_probe(struct platform_device *pdev)
+{
+ struct gpio_vbus_data *gpio_vbus;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ int err, irq;
+ unsigned long irqflags;
+
+ gpio_vbus = devm_kzalloc(&pdev->dev, sizeof(struct gpio_vbus_data),
+ GFP_KERNEL);
+ if (!gpio_vbus)
+ return -ENOMEM;
+
+ gpio_vbus->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg),
+ GFP_KERNEL);
+ if (!gpio_vbus->phy.otg)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, gpio_vbus);
+ gpio_vbus->dev = &pdev->dev;
+ gpio_vbus->phy.label = "gpio-vbus";
+ gpio_vbus->phy.dev = gpio_vbus->dev;
+ gpio_vbus->phy.set_power = gpio_vbus_set_power;
+ gpio_vbus->phy.set_suspend = gpio_vbus_set_suspend;
+
+ gpio_vbus->phy.otg->state = OTG_STATE_UNDEFINED;
+ gpio_vbus->phy.otg->usb_phy = &gpio_vbus->phy;
+ gpio_vbus->phy.otg->set_peripheral = gpio_vbus_set_peripheral;
+
+ /* Look up the VBUS sensing GPIO */
+ gpio_vbus->vbus_gpiod = devm_gpiod_get(dev, "vbus", GPIOD_IN);
+ if (IS_ERR(gpio_vbus->vbus_gpiod)) {
+ err = PTR_ERR(gpio_vbus->vbus_gpiod);
+ dev_err(&pdev->dev, "can't request vbus gpio, err: %d\n", err);
+ return err;
+ }
+ gpiod_set_consumer_name(gpio_vbus->vbus_gpiod, "vbus_detect");
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (res) {
+ irq = res->start;
+ irqflags = (res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED;
+ } else {
+ irq = gpiod_to_irq(gpio_vbus->vbus_gpiod);
+ irqflags = VBUS_IRQ_FLAGS;
+ }
+
+ gpio_vbus->irq = irq;
+
+ /*
+ * The VBUS sensing GPIO should have a pulldown, which will normally be
+ * part of a resistor ladder turning a 4.0V-5.25V level on VBUS into a
+ * value the GPIO detects as active. Some systems will use comparators.
+ * Get the optional D+ or D- pullup GPIO. If the data line pullup is
+ * in use, initialize it to "not pulling up"
+ */
+ gpio_vbus->pullup_gpiod = devm_gpiod_get_optional(dev, "pullup",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(gpio_vbus->pullup_gpiod)) {
+ err = PTR_ERR(gpio_vbus->pullup_gpiod);
+ dev_err(&pdev->dev, "can't request pullup gpio, err: %d\n",
+ err);
+ return err;
+ }
+ if (gpio_vbus->pullup_gpiod)
+ gpiod_set_consumer_name(gpio_vbus->pullup_gpiod, "udc_pullup");
+
+ err = devm_request_irq(&pdev->dev, irq, gpio_vbus_irq, irqflags,
+ "vbus_detect", pdev);
+ if (err) {
+ dev_err(&pdev->dev, "can't request irq %i, err: %d\n",
+ irq, err);
+ return err;
+ }
+
+ INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work);
+
+ gpio_vbus->vbus_draw = devm_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);
+ return err;
+ }
+
+ /* TODO: wakeup could be enabled here with device_init_wakeup(dev, 1) */
+
+ return 0;
+}
+
+static int gpio_vbus_remove(struct platform_device *pdev)
+{
+ struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ cancel_delayed_work_sync(&gpio_vbus->work);
+
+ usb_remove_phy(&gpio_vbus->phy);
+
+ 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
+
+MODULE_ALIAS("platform:gpio-vbus");
+
+static struct platform_driver gpio_vbus_driver = {
+ .driver = {
+ .name = "gpio-vbus",
+#ifdef CONFIG_PM
+ .pm = &gpio_vbus_dev_pm_ops,
+#endif
+ },
+ .probe = gpio_vbus_probe,
+ .remove = gpio_vbus_remove,
+};
+
+module_platform_driver(gpio_vbus_driver);
+
+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 000000000..4a6462c92
--- /dev/null
+++ b/drivers/usb/phy/phy-isp1301-omap.c
@@ -0,0 +1,1636 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * isp1301_omap - ISP 1301 USB transceiver, talking to OMAP OTG controller
+ *
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2004 David Brownell
+ */
+
+#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>
+
+#undef VERBOSE
+
+
+#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 IS_REACHABLE(CONFIG_TPS65010)
+
+#include <linux/mfd/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.otg->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.otg->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 __maybe_unused 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.otg->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.otg->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.otg->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.otg->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)
+{
+ 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 */
+}
+
+/*-------------------------------------------------------------------------*/
+
+#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.otg->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.otg->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.otg->state == OTG_STATE_B_IDLE
+ || isp->phy.otg->state
+ == OTG_STATE_UNDEFINED) {
+ a_idle(isp, "init");
+ return;
+ }
+ } else { /* default-B */
+ otg_ctrl |= OTG_ID;
+ if (isp->phy.otg->state == OTG_STATE_A_IDLE
+ || isp->phy.otg->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.otg->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.otg->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.otg->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.otg->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.otg->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.otg->state) {
+ case OTG_STATE_B_IDLE:
+ if (clr & OTG1_DP_PULLUP)
+ break;
+ isp->phy.otg->state = OTG_STATE_B_PERIPHERAL;
+ pr_debug(" --> b_peripheral\n");
+ break;
+ case OTG_STATE_A_SUSPEND:
+ if (clr & OTG1_DP_PULLUP)
+ break;
+ isp->phy.otg->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.otg->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.otg->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.otg->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.otg->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.otg->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.otg->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.otg->state) {
+ case OTG_STATE_B_WAIT_ACON:
+ isp->phy.otg->state = OTG_STATE_B_HOST;
+ pr_debug(" --> b_host\n");
+ kick = 1;
+ break;
+ case OTG_STATE_A_WAIT_BCON:
+ isp->phy.otg->state = OTG_STATE_A_HOST;
+ pr_debug(" --> a_host\n");
+ break;
+ case OTG_STATE_A_PERIPHERAL:
+ isp->phy.otg->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 = {
+ .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.otg->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.otg->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.otg->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.otg->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.otg->state = OTG_STATE_A_WAIT_VFALL;
+ break;
+ case OTG_STATE_A_WAIT_VFALL:
+ state = OTG_STATE_A_IDLE;
+ /* hub_wq 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.otg->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.otg->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.otg->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.otg->state) {
+ case OTG_STATE_A_WAIT_BCON:
+ case OTG_STATE_A_WAIT_VRISE:
+ isp->phy.otg->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.otg->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(struct timer_list *t)
+{
+ struct isp1301 *isp = from_timer(isp, t, timer);
+
+ isp1301_defer_work(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);
+
+ 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->usb_phy, struct isp1301, phy);
+
+ if (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 !IS_ENABLED(CONFIG_USB_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->usb_phy, struct isp1301, phy);
+
+ if (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.otg->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->otg->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->usb_phy, struct isp1301, phy);
+ u32 otg_ctrl;
+
+ if (isp != the_transceiver || isp->phy.otg->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.otg->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->usb_phy, struct isp1301, phy);
+ u32 l;
+
+ if (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.otg->state) {
+ case OTG_STATE_B_HOST:
+ isp->phy.otg->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);
+ timer_setup(&isp->timer, isp1301_timer, 0);
+
+ 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->usb_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 000000000..6cf6fbd39
--- /dev/null
+++ b/drivers/usb/phy/phy-isp1301.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NXP ISP1301 USB transceiver driver
+ *
+ * Copyright (C) 2012 Roland Stigge
+ *
+ * Author: Roland Stigge <stigge@antcom.de>
+ */
+
+#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 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, isp1301_id);
+
+static const struct of_device_id isp1301_of_match[] = {
+ {.compatible = "nxp,isp1301" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, isp1301_of_match);
+
+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->dev = &client->dev;
+ 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,
+ .of_match_table = isp1301_of_match,
+ },
+ .probe = isp1301_probe,
+ .remove = isp1301_remove,
+ .id_table = isp1301_id,
+};
+
+module_i2c_driver(isp1301_driver);
+
+static int match(struct device *dev, const void *data)
+{
+ const struct device_node *node = (const 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-jz4770.c b/drivers/usb/phy/phy-jz4770.c
new file mode 100644
index 000000000..f6d373158
--- /dev/null
+++ b/drivers/usb/phy/phy-jz4770.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ingenic SoCs USB PHY driver
+ * Copyright (c) Paul Cercueil <paul@crapouillou.net>
+ * Copyright (c) 漆鹏振 (Qi Pengzhen) <aric.pzqi@ingenic.com>
+ * Copyright (c) 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/phy.h>
+
+/* OTGPHY register offsets */
+#define REG_USBPCR_OFFSET 0x00
+#define REG_USBRDT_OFFSET 0x04
+#define REG_USBVBFIL_OFFSET 0x08
+#define REG_USBPCR1_OFFSET 0x0c
+
+/* bits within the USBPCR register */
+#define USBPCR_USB_MODE BIT(31)
+#define USBPCR_AVLD_REG BIT(30)
+#define USBPCR_COMMONONN BIT(25)
+#define USBPCR_VBUSVLDEXT BIT(24)
+#define USBPCR_VBUSVLDEXTSEL BIT(23)
+#define USBPCR_POR BIT(22)
+#define USBPCR_SIDDQ BIT(21)
+#define USBPCR_OTG_DISABLE BIT(20)
+#define USBPCR_TXPREEMPHTUNE BIT(6)
+
+#define USBPCR_IDPULLUP_LSB 28
+#define USBPCR_IDPULLUP_MASK GENMASK(29, USBPCR_IDPULLUP_LSB)
+#define USBPCR_IDPULLUP_ALWAYS (0x2 << USBPCR_IDPULLUP_LSB)
+#define USBPCR_IDPULLUP_SUSPEND (0x1 << USBPCR_IDPULLUP_LSB)
+#define USBPCR_IDPULLUP_OTG (0x0 << USBPCR_IDPULLUP_LSB)
+
+#define USBPCR_COMPDISTUNE_LSB 17
+#define USBPCR_COMPDISTUNE_MASK GENMASK(19, USBPCR_COMPDISTUNE_LSB)
+#define USBPCR_COMPDISTUNE_DFT (0x4 << USBPCR_COMPDISTUNE_LSB)
+
+#define USBPCR_OTGTUNE_LSB 14
+#define USBPCR_OTGTUNE_MASK GENMASK(16, USBPCR_OTGTUNE_LSB)
+#define USBPCR_OTGTUNE_DFT (0x4 << USBPCR_OTGTUNE_LSB)
+
+#define USBPCR_SQRXTUNE_LSB 11
+#define USBPCR_SQRXTUNE_MASK GENMASK(13, USBPCR_SQRXTUNE_LSB)
+#define USBPCR_SQRXTUNE_DCR_20PCT (0x7 << USBPCR_SQRXTUNE_LSB)
+#define USBPCR_SQRXTUNE_DFT (0x3 << USBPCR_SQRXTUNE_LSB)
+
+#define USBPCR_TXFSLSTUNE_LSB 7
+#define USBPCR_TXFSLSTUNE_MASK GENMASK(10, USBPCR_TXFSLSTUNE_LSB)
+#define USBPCR_TXFSLSTUNE_DCR_50PPT (0xf << USBPCR_TXFSLSTUNE_LSB)
+#define USBPCR_TXFSLSTUNE_DCR_25PPT (0x7 << USBPCR_TXFSLSTUNE_LSB)
+#define USBPCR_TXFSLSTUNE_DFT (0x3 << USBPCR_TXFSLSTUNE_LSB)
+#define USBPCR_TXFSLSTUNE_INC_25PPT (0x1 << USBPCR_TXFSLSTUNE_LSB)
+#define USBPCR_TXFSLSTUNE_INC_50PPT (0x0 << USBPCR_TXFSLSTUNE_LSB)
+
+#define USBPCR_TXHSXVTUNE_LSB 4
+#define USBPCR_TXHSXVTUNE_MASK GENMASK(5, USBPCR_TXHSXVTUNE_LSB)
+#define USBPCR_TXHSXVTUNE_DFT (0x3 << USBPCR_TXHSXVTUNE_LSB)
+#define USBPCR_TXHSXVTUNE_DCR_15MV (0x1 << USBPCR_TXHSXVTUNE_LSB)
+
+#define USBPCR_TXRISETUNE_LSB 4
+#define USBPCR_TXRISETUNE_MASK GENMASK(5, USBPCR_TXRISETUNE_LSB)
+#define USBPCR_TXRISETUNE_DFT (0x3 << USBPCR_TXRISETUNE_LSB)
+
+#define USBPCR_TXVREFTUNE_LSB 0
+#define USBPCR_TXVREFTUNE_MASK GENMASK(3, USBPCR_TXVREFTUNE_LSB)
+#define USBPCR_TXVREFTUNE_INC_25PPT (0x7 << USBPCR_TXVREFTUNE_LSB)
+#define USBPCR_TXVREFTUNE_DFT (0x5 << USBPCR_TXVREFTUNE_LSB)
+
+/* bits within the USBRDTR register */
+#define USBRDT_UTMI_RST BIT(27)
+#define USBRDT_HB_MASK BIT(26)
+#define USBRDT_VBFIL_LD_EN BIT(25)
+#define USBRDT_IDDIG_EN BIT(24)
+#define USBRDT_IDDIG_REG BIT(23)
+#define USBRDT_VBFIL_EN BIT(2)
+
+/* bits within the USBPCR1 register */
+#define USBPCR1_BVLD_REG BIT(31)
+#define USBPCR1_DPPD BIT(29)
+#define USBPCR1_DMPD BIT(28)
+#define USBPCR1_USB_SEL BIT(28)
+#define USBPCR1_WORD_IF_16BIT BIT(19)
+
+enum ingenic_usb_phy_version {
+ ID_JZ4770,
+ ID_JZ4780,
+ ID_X1000,
+ ID_X1830,
+};
+
+struct ingenic_soc_info {
+ enum ingenic_usb_phy_version version;
+
+ void (*usb_phy_init)(struct usb_phy *phy);
+};
+
+struct jz4770_phy {
+ const struct ingenic_soc_info *soc_info;
+
+ struct usb_phy phy;
+ struct usb_otg otg;
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct regulator *vcc_supply;
+};
+
+static inline struct jz4770_phy *otg_to_jz4770_phy(struct usb_otg *otg)
+{
+ return container_of(otg, struct jz4770_phy, otg);
+}
+
+static inline struct jz4770_phy *phy_to_jz4770_phy(struct usb_phy *phy)
+{
+ return container_of(phy, struct jz4770_phy, phy);
+}
+
+static int ingenic_usb_phy_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ struct jz4770_phy *priv = otg_to_jz4770_phy(otg);
+ u32 reg;
+
+ if (priv->soc_info->version >= ID_X1000) {
+ reg = readl(priv->base + REG_USBPCR1_OFFSET);
+ reg |= USBPCR1_BVLD_REG;
+ writel(reg, priv->base + REG_USBPCR1_OFFSET);
+ }
+
+ reg = readl(priv->base + REG_USBPCR_OFFSET);
+ reg &= ~USBPCR_USB_MODE;
+ reg |= USBPCR_VBUSVLDEXT | USBPCR_VBUSVLDEXTSEL | USBPCR_OTG_DISABLE;
+ writel(reg, priv->base + REG_USBPCR_OFFSET);
+
+ return 0;
+}
+
+static int ingenic_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ struct jz4770_phy *priv = otg_to_jz4770_phy(otg);
+ u32 reg;
+
+ reg = readl(priv->base + REG_USBPCR_OFFSET);
+ reg &= ~(USBPCR_VBUSVLDEXT | USBPCR_VBUSVLDEXTSEL | USBPCR_OTG_DISABLE);
+ reg |= USBPCR_USB_MODE;
+ writel(reg, priv->base + REG_USBPCR_OFFSET);
+
+ return 0;
+}
+
+static int ingenic_usb_phy_init(struct usb_phy *phy)
+{
+ struct jz4770_phy *priv = phy_to_jz4770_phy(phy);
+ int err;
+ u32 reg;
+
+ err = regulator_enable(priv->vcc_supply);
+ if (err) {
+ dev_err(priv->dev, "Unable to enable VCC: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(priv->clk);
+ if (err) {
+ dev_err(priv->dev, "Unable to start clock: %d\n", err);
+ return err;
+ }
+
+ priv->soc_info->usb_phy_init(phy);
+
+ /* Wait for PHY to reset */
+ usleep_range(30, 300);
+ reg = readl(priv->base + REG_USBPCR_OFFSET);
+ writel(reg & ~USBPCR_POR, priv->base + REG_USBPCR_OFFSET);
+ usleep_range(300, 1000);
+
+ return 0;
+}
+
+static void ingenic_usb_phy_shutdown(struct usb_phy *phy)
+{
+ struct jz4770_phy *priv = phy_to_jz4770_phy(phy);
+
+ clk_disable_unprepare(priv->clk);
+ regulator_disable(priv->vcc_supply);
+}
+
+static void ingenic_usb_phy_remove(void *phy)
+{
+ usb_remove_phy(phy);
+}
+
+static void jz4770_usb_phy_init(struct usb_phy *phy)
+{
+ struct jz4770_phy *priv = phy_to_jz4770_phy(phy);
+ u32 reg;
+
+ reg = USBPCR_AVLD_REG | USBPCR_COMMONONN | USBPCR_IDPULLUP_ALWAYS |
+ USBPCR_COMPDISTUNE_DFT | USBPCR_OTGTUNE_DFT | USBPCR_SQRXTUNE_DFT |
+ USBPCR_TXFSLSTUNE_DFT | USBPCR_TXRISETUNE_DFT | USBPCR_TXVREFTUNE_DFT |
+ USBPCR_POR;
+ writel(reg, priv->base + REG_USBPCR_OFFSET);
+}
+
+static void jz4780_usb_phy_init(struct usb_phy *phy)
+{
+ struct jz4770_phy *priv = phy_to_jz4770_phy(phy);
+ u32 reg;
+
+ reg = readl(priv->base + REG_USBPCR1_OFFSET) | USBPCR1_USB_SEL |
+ USBPCR1_WORD_IF_16BIT;
+ writel(reg, priv->base + REG_USBPCR1_OFFSET);
+
+ reg = USBPCR_TXPREEMPHTUNE | USBPCR_COMMONONN | USBPCR_POR;
+ writel(reg, priv->base + REG_USBPCR_OFFSET);
+}
+
+static void x1000_usb_phy_init(struct usb_phy *phy)
+{
+ struct jz4770_phy *priv = phy_to_jz4770_phy(phy);
+ u32 reg;
+
+ reg = readl(priv->base + REG_USBPCR1_OFFSET) | USBPCR1_WORD_IF_16BIT;
+ writel(reg, priv->base + REG_USBPCR1_OFFSET);
+
+ reg = USBPCR_SQRXTUNE_DCR_20PCT | USBPCR_TXPREEMPHTUNE |
+ USBPCR_TXHSXVTUNE_DCR_15MV | USBPCR_TXVREFTUNE_INC_25PPT |
+ USBPCR_COMMONONN | USBPCR_POR;
+ writel(reg, priv->base + REG_USBPCR_OFFSET);
+}
+
+static void x1830_usb_phy_init(struct usb_phy *phy)
+{
+ struct jz4770_phy *priv = phy_to_jz4770_phy(phy);
+ u32 reg;
+
+ /* rdt */
+ writel(USBRDT_VBFIL_EN | USBRDT_UTMI_RST, priv->base + REG_USBRDT_OFFSET);
+
+ reg = readl(priv->base + REG_USBPCR1_OFFSET) | USBPCR1_WORD_IF_16BIT |
+ USBPCR1_DMPD | USBPCR1_DPPD;
+ writel(reg, priv->base + REG_USBPCR1_OFFSET);
+
+ reg = USBPCR_IDPULLUP_OTG | USBPCR_VBUSVLDEXT | USBPCR_TXPREEMPHTUNE |
+ USBPCR_COMMONONN | USBPCR_POR;
+ writel(reg, priv->base + REG_USBPCR_OFFSET);
+}
+
+static const struct ingenic_soc_info jz4770_soc_info = {
+ .version = ID_JZ4770,
+
+ .usb_phy_init = jz4770_usb_phy_init,
+};
+
+static const struct ingenic_soc_info jz4780_soc_info = {
+ .version = ID_JZ4780,
+
+ .usb_phy_init = jz4780_usb_phy_init,
+};
+
+static const struct ingenic_soc_info x1000_soc_info = {
+ .version = ID_X1000,
+
+ .usb_phy_init = x1000_usb_phy_init,
+};
+
+static const struct ingenic_soc_info x1830_soc_info = {
+ .version = ID_X1830,
+
+ .usb_phy_init = x1830_usb_phy_init,
+};
+
+static const struct of_device_id ingenic_usb_phy_of_matches[] = {
+ { .compatible = "ingenic,jz4770-phy", .data = &jz4770_soc_info },
+ { .compatible = "ingenic,jz4780-phy", .data = &jz4780_soc_info },
+ { .compatible = "ingenic,x1000-phy", .data = &x1000_soc_info },
+ { .compatible = "ingenic,x1830-phy", .data = &x1830_soc_info },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ingenic_usb_phy_of_matches);
+
+static int jz4770_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct jz4770_phy *priv;
+ int err;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->soc_info = device_get_match_data(&pdev->dev);
+ if (!priv->soc_info) {
+ dev_err(&pdev->dev, "Error: No device match found\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ priv->dev = dev;
+ priv->phy.dev = dev;
+ priv->phy.otg = &priv->otg;
+ priv->phy.label = "ingenic-usb-phy";
+ priv->phy.init = ingenic_usb_phy_init;
+ priv->phy.shutdown = ingenic_usb_phy_shutdown;
+
+ priv->otg.state = OTG_STATE_UNDEFINED;
+ priv->otg.usb_phy = &priv->phy;
+ priv->otg.set_host = ingenic_usb_phy_set_host;
+ priv->otg.set_peripheral = ingenic_usb_phy_set_peripheral;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base)) {
+ dev_err(dev, "Failed to map registers\n");
+ return PTR_ERR(priv->base);
+ }
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk)) {
+ err = PTR_ERR(priv->clk);
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get clock\n");
+ return err;
+ }
+
+ priv->vcc_supply = devm_regulator_get(dev, "vcc");
+ if (IS_ERR(priv->vcc_supply)) {
+ err = PTR_ERR(priv->vcc_supply);
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get regulator\n");
+ return err;
+ }
+
+ err = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "Unable to register PHY\n");
+ return err;
+ }
+
+ return devm_add_action_or_reset(dev, ingenic_usb_phy_remove, &priv->phy);
+}
+
+static struct platform_driver ingenic_phy_driver = {
+ .probe = jz4770_phy_probe,
+ .driver = {
+ .name = "jz4770-phy",
+ .of_match_table = of_match_ptr(ingenic_usb_phy_of_matches),
+ },
+};
+module_platform_driver(ingenic_phy_driver);
+
+MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
+MODULE_AUTHOR("漆鹏振 (Qi Pengzhen) <aric.pzqi@ingenic.com>");
+MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
+MODULE_DESCRIPTION("Ingenic SoCs USB PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/phy/phy-keystone.c b/drivers/usb/phy/phy-keystone.c
new file mode 100644
index 000000000..358d05cb6
--- /dev/null
+++ b/drivers/usb/phy/phy-keystone.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * phy-keystone - USB PHY, talking to dwc3 controller in Keystone.
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com
+ *
+ * Author: WingMan Kwok <w-kwok2@ti.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/usb/usb_phy_generic.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#include "phy-generic.h"
+
+/* USB PHY control register offsets */
+#define USB_PHY_CTL_UTMI 0x0000
+#define USB_PHY_CTL_PIPE 0x0004
+#define USB_PHY_CTL_PARAM_1 0x0008
+#define USB_PHY_CTL_PARAM_2 0x000c
+#define USB_PHY_CTL_CLOCK 0x0010
+#define USB_PHY_CTL_PLL 0x0014
+
+#define PHY_REF_SSP_EN BIT(29)
+
+struct keystone_usbphy {
+ struct usb_phy_generic usb_phy_gen;
+ void __iomem *phy_ctrl;
+};
+
+static inline u32 keystone_usbphy_readl(void __iomem *base, u32 offset)
+{
+ return readl(base + offset);
+}
+
+static inline void keystone_usbphy_writel(void __iomem *base,
+ u32 offset, u32 value)
+{
+ writel(value, base + offset);
+}
+
+static int keystone_usbphy_init(struct usb_phy *phy)
+{
+ struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev);
+ u32 val;
+
+ val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK);
+ keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK,
+ val | PHY_REF_SSP_EN);
+ return 0;
+}
+
+static void keystone_usbphy_shutdown(struct usb_phy *phy)
+{
+ struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev);
+ u32 val;
+
+ val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK);
+ keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK,
+ val &= ~PHY_REF_SSP_EN);
+}
+
+static int keystone_usbphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct keystone_usbphy *k_phy;
+ int ret;
+
+ k_phy = devm_kzalloc(dev, sizeof(*k_phy), GFP_KERNEL);
+ if (!k_phy)
+ return -ENOMEM;
+
+ k_phy->phy_ctrl = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(k_phy->phy_ctrl))
+ return PTR_ERR(k_phy->phy_ctrl);
+
+ ret = usb_phy_gen_create_phy(dev, &k_phy->usb_phy_gen);
+ if (ret)
+ return ret;
+
+ k_phy->usb_phy_gen.phy.init = keystone_usbphy_init;
+ k_phy->usb_phy_gen.phy.shutdown = keystone_usbphy_shutdown;
+
+ platform_set_drvdata(pdev, k_phy);
+
+ return usb_add_phy_dev(&k_phy->usb_phy_gen.phy);
+}
+
+static int keystone_usbphy_remove(struct platform_device *pdev)
+{
+ struct keystone_usbphy *k_phy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&k_phy->usb_phy_gen.phy);
+
+ return 0;
+}
+
+static const struct of_device_id keystone_usbphy_ids[] = {
+ { .compatible = "ti,keystone-usbphy" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, keystone_usbphy_ids);
+
+static struct platform_driver keystone_usbphy_driver = {
+ .probe = keystone_usbphy_probe,
+ .remove = keystone_usbphy_remove,
+ .driver = {
+ .name = "keystone-usbphy",
+ .of_match_table = keystone_usbphy_ids,
+ },
+};
+
+module_platform_driver(keystone_usbphy_driver);
+
+MODULE_ALIAS("platform:keystone-usbphy");
+MODULE_AUTHOR("Texas Instruments Inc.");
+MODULE_DESCRIPTION("Keystone USB phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/phy/phy-mv-usb.c b/drivers/usb/phy/phy-mv-usb.c
new file mode 100644
index 000000000..576d925af
--- /dev/null
+++ b/drivers/usb/phy/phy-mv-usb.c
@@ -0,0 +1,885 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 Marvell International Ltd. All rights reserved.
+ * Author: Chao Xie <chao.xie@marvell.com>
+ * Neil Zhang <zhangwm@marvell.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/iopoll.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"
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+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->usb_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(struct timer_list *t)
+{
+ struct mv_otg *mvotg = from_timer(mvotg, t,
+ otg_ctrl.timer[A_WAIT_BCON_TIMER]);
+
+ 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)
+{
+ 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;
+ }
+
+ timer->expires = jiffies + interval;
+ add_timer(timer);
+
+ return 0;
+}
+
+static int mv_otg_reset(struct mv_otg *mvotg)
+{
+ u32 tmp;
+ int ret;
+
+ /* 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);
+
+ ret = readl_poll_timeout_atomic(&mvotg->op_regs->usbcmd, tmp,
+ (tmp & USBCMD_CTRL_RESET), 10, 10000);
+ if (ret < 0) {
+ dev_err(&mvotg->pdev->dev,
+ "Wait for RESET completed TIMEOUT\n");
+ return ret;
+ }
+
+ 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);
+ device_wakeup_enable(hcd->self.controller);
+ } 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;
+ int old_state = mvotg->phy.otg->state;
+
+ switch (old_state) {
+ case OTG_STATE_UNDEFINED:
+ mvotg->phy.otg->state = OTG_STATE_B_IDLE;
+ fallthrough;
+ case OTG_STATE_B_IDLE:
+ if (otg_ctrl->id == 0)
+ mvotg->phy.otg->state = OTG_STATE_A_IDLE;
+ else if (otg_ctrl->b_sess_vld)
+ mvotg->phy.otg->state = OTG_STATE_B_PERIPHERAL;
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0)
+ mvotg->phy.otg->state = OTG_STATE_B_IDLE;
+ break;
+ case OTG_STATE_A_IDLE:
+ if (otg_ctrl->id)
+ mvotg->phy.otg->state = OTG_STATE_B_IDLE;
+ else if (!(otg_ctrl->a_bus_drop) &&
+ (otg_ctrl->a_bus_req || otg_ctrl->a_srp_det))
+ mvotg->phy.otg->state = OTG_STATE_A_WAIT_VRISE;
+ break;
+ case OTG_STATE_A_WAIT_VRISE:
+ if (otg_ctrl->a_vbus_vld)
+ mvotg->phy.otg->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;
+ mvotg->phy.otg->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;
+ mvotg->phy.otg->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;
+ mvotg->phy.otg->state = OTG_STATE_A_HOST;
+ }
+ break;
+ case OTG_STATE_A_HOST:
+ if (otg_ctrl->id || !otg_ctrl->b_conn
+ || otg_ctrl->a_bus_drop)
+ mvotg->phy.otg->state = OTG_STATE_A_WAIT_BCON;
+ else if (!otg_ctrl->a_vbus_vld)
+ mvotg->phy.otg->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)
+ mvotg->phy.otg->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;
+ mvotg->phy.otg->state = OTG_STATE_A_WAIT_VFALL;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void mv_otg_work(struct work_struct *work)
+{
+ struct mv_otg *mvotg;
+ 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 */
+ otg = mvotg->phy.otg;
+ old_state = otg->state;
+
+ if (!mvotg->active)
+ return;
+
+ mv_otg_update_inputs(mvotg);
+ mv_otg_update_state(mvotg);
+
+ if (old_state != mvotg->phy.otg->state) {
+ dev_info(&mvotg->pdev->dev, "change from state %s to %s\n",
+ state_string[old_state],
+ state_string[mvotg->phy.otg->state]);
+
+ switch (mvotg->phy.otg->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);
+ usb_phy_set_event(&mvotg->phy, USB_EVENT_NONE);
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ mv_otg_enable(mvotg);
+ mv_otg_start_periphrals(mvotg, 1);
+ usb_phy_set_event(&mvotg->phy, USB_EVENT_ENUMERATED);
+ 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);
+ /*
+ * 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
+a_bus_req_show(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
+a_bus_req_store(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.otg->state != OTG_STATE_B_IDLE
+ && mvotg->phy.otg->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_RW(a_bus_req);
+
+static ssize_t
+a_clr_err_store(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_WO(a_clr_err);
+
+static ssize_t
+a_bus_drop_show(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
+a_bus_drop_store(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_RW(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 const struct attribute_group inputs_attr_group = {
+ .name = "inputs",
+ .attrs = inputs_attrs,
+};
+
+static const struct attribute_group *mv_otg_groups[] = {
+ &inputs_attr_group,
+ NULL,
+};
+
+static int mv_otg_remove(struct platform_device *pdev)
+{
+ struct mv_otg *mvotg = platform_get_drvdata(pdev);
+
+ if (mvotg->qwork) {
+ flush_workqueue(mvotg->qwork);
+ destroy_workqueue(mvotg->qwork);
+ }
+
+ mv_otg_disable(mvotg);
+
+ usb_remove_phy(&mvotg->phy);
+
+ return 0;
+}
+
+static int mv_otg_probe(struct platform_device *pdev)
+{
+ struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ 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)
+ 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;
+
+ otg->state = OTG_STATE_UNDEFINED;
+ otg->usb_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++)
+ timer_setup(&mvotg->otg_ctrl.timer[i],
+ mv_otg_timer_await_bcon, 0);
+
+ 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;
+ }
+
+ 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_disable_clk:
+ mv_otg_disable_internal(mvotg);
+err_destroy_workqueue:
+ flush_workqueue(mvotg->qwork);
+ destroy_workqueue(mvotg->qwork);
+
+ 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.otg->state != OTG_STATE_B_IDLE) {
+ dev_info(&pdev->dev,
+ "OTG state is not B_IDLE, it is %d!\n",
+ mvotg->phy.otg->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 = mv_otg_remove,
+ .driver = {
+ .name = driver_name,
+ .dev_groups = mv_otg_groups,
+ },
+#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 000000000..5d5c0abb0
--- /dev/null
+++ b/drivers/usb/phy/phy-mv-usb.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2011 Marvell International Ltd. All rights reserved.
+ */
+
+#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 000000000..e3cddcac3
--- /dev/null
+++ b/drivers/usb/phy/phy-mxs-usb.c
@@ -0,0 +1,878 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2012-2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2012 Marek Vasut <marex@denx.de>
+ * on behalf of DENX Software Engineering GmbH
+ */
+
+#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>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/iopoll.h>
+
+#define DRIVER_NAME "mxs_phy"
+
+/* Register Macro */
+#define HW_USBPHY_PWD 0x00
+#define HW_USBPHY_TX 0x10
+#define HW_USBPHY_CTRL 0x30
+#define HW_USBPHY_CTRL_SET 0x34
+#define HW_USBPHY_CTRL_CLR 0x38
+
+#define HW_USBPHY_DEBUG_SET 0x54
+#define HW_USBPHY_DEBUG_CLR 0x58
+
+#define HW_USBPHY_IP 0x90
+#define HW_USBPHY_IP_SET 0x94
+#define HW_USBPHY_IP_CLR 0x98
+
+#define GM_USBPHY_TX_TXCAL45DP(x) (((x) & 0xf) << 16)
+#define GM_USBPHY_TX_TXCAL45DN(x) (((x) & 0xf) << 8)
+#define GM_USBPHY_TX_D_CAL(x) (((x) & 0xf) << 0)
+
+/* imx7ulp */
+#define HW_USBPHY_PLL_SIC 0xa0
+#define HW_USBPHY_PLL_SIC_SET 0xa4
+#define HW_USBPHY_PLL_SIC_CLR 0xa8
+
+#define BM_USBPHY_CTRL_SFTRST BIT(31)
+#define BM_USBPHY_CTRL_CLKGATE BIT(30)
+#define BM_USBPHY_CTRL_OTG_ID_VALUE BIT(27)
+#define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26)
+#define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25)
+#define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23)
+#define BM_USBPHY_CTRL_ENIDCHG_WKUP BIT(22)
+#define BM_USBPHY_CTRL_ENDPDMCHG_WKUP BIT(21)
+#define BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD BIT(20)
+#define BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE BIT(19)
+#define BM_USBPHY_CTRL_ENAUTO_PWRON_PLL BIT(18)
+#define BM_USBPHY_CTRL_ENUTMILEVEL3 BIT(15)
+#define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14)
+#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1)
+
+#define BM_USBPHY_IP_FIX (BIT(17) | BIT(18))
+
+#define BM_USBPHY_DEBUG_CLKGATE BIT(30)
+/* imx7ulp */
+#define BM_USBPHY_PLL_LOCK BIT(31)
+#define BM_USBPHY_PLL_REG_ENABLE BIT(21)
+#define BM_USBPHY_PLL_BYPASS BIT(16)
+#define BM_USBPHY_PLL_POWER BIT(12)
+#define BM_USBPHY_PLL_EN_USB_CLKS BIT(6)
+
+/* Anatop Registers */
+#define ANADIG_ANA_MISC0 0x150
+#define ANADIG_ANA_MISC0_SET 0x154
+#define ANADIG_ANA_MISC0_CLR 0x158
+
+#define ANADIG_USB1_CHRG_DETECT_SET 0x1b4
+#define ANADIG_USB1_CHRG_DETECT_CLR 0x1b8
+#define ANADIG_USB2_CHRG_DETECT_SET 0x214
+#define ANADIG_USB1_CHRG_DETECT_EN_B BIT(20)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B BIT(19)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CONTACT BIT(18)
+
+#define ANADIG_USB1_VBUS_DET_STAT 0x1c0
+#define ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3)
+
+#define ANADIG_USB1_CHRG_DET_STAT 0x1d0
+#define ANADIG_USB1_CHRG_DET_STAT_DM_STATE BIT(2)
+#define ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED BIT(1)
+#define ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT BIT(0)
+
+#define ANADIG_USB2_VBUS_DET_STAT 0x220
+
+#define ANADIG_USB1_LOOPBACK_SET 0x1e4
+#define ANADIG_USB1_LOOPBACK_CLR 0x1e8
+#define ANADIG_USB1_LOOPBACK_UTMI_TESTSTART BIT(0)
+
+#define ANADIG_USB2_LOOPBACK_SET 0x244
+#define ANADIG_USB2_LOOPBACK_CLR 0x248
+
+#define ANADIG_USB1_MISC 0x1f0
+#define ANADIG_USB2_MISC 0x250
+
+#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12)
+#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11)
+
+#define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3)
+#define BM_ANADIG_USB2_VBUS_DET_STAT_VBUS_VALID BIT(3)
+
+#define BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 BIT(2)
+#define BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN BIT(5)
+#define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2)
+#define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5)
+
+#define BM_ANADIG_USB1_MISC_RX_VPIN_FS BIT(29)
+#define BM_ANADIG_USB1_MISC_RX_VMIN_FS BIT(28)
+#define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29)
+#define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28)
+
+#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
+
+/* Do disconnection between PHY and controller without vbus */
+#define MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS BIT(0)
+
+/*
+ * The PHY will be in messy if there is a wakeup after putting
+ * bus to suspend (set portsc.suspendM) but before setting PHY to low
+ * power mode (set portsc.phcd).
+ */
+#define MXS_PHY_ABNORMAL_IN_SUSPEND BIT(1)
+
+/*
+ * The SOF sends too fast after resuming, it will cause disconnection
+ * between host and high speed device.
+ */
+#define MXS_PHY_SENDING_SOF_TOO_FAST BIT(2)
+
+/*
+ * IC has bug fixes logic, they include
+ * MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST
+ * which are described at above flags, the RTL will handle it
+ * according to different versions.
+ */
+#define MXS_PHY_NEED_IP_FIX BIT(3)
+
+/* Minimum and maximum values for device tree entries */
+#define MXS_PHY_TX_CAL45_MIN 30
+#define MXS_PHY_TX_CAL45_MAX 55
+#define MXS_PHY_TX_D_CAL_MIN 79
+#define MXS_PHY_TX_D_CAL_MAX 119
+
+struct mxs_phy_data {
+ unsigned int flags;
+};
+
+static const struct mxs_phy_data imx23_phy_data = {
+ .flags = MXS_PHY_ABNORMAL_IN_SUSPEND | MXS_PHY_SENDING_SOF_TOO_FAST,
+};
+
+static const struct mxs_phy_data imx6q_phy_data = {
+ .flags = MXS_PHY_SENDING_SOF_TOO_FAST |
+ MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
+ MXS_PHY_NEED_IP_FIX,
+};
+
+static const struct mxs_phy_data imx6sl_phy_data = {
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
+ MXS_PHY_NEED_IP_FIX,
+};
+
+static const struct mxs_phy_data vf610_phy_data = {
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
+ MXS_PHY_NEED_IP_FIX,
+};
+
+static const struct mxs_phy_data imx6sx_phy_data = {
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS,
+};
+
+static const struct mxs_phy_data imx6ul_phy_data = {
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS,
+};
+
+static const struct mxs_phy_data imx7ulp_phy_data = {
+};
+
+static const struct of_device_id mxs_phy_dt_ids[] = {
+ { .compatible = "fsl,imx6sx-usbphy", .data = &imx6sx_phy_data, },
+ { .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, },
+ { .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, },
+ { .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, },
+ { .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, },
+ { .compatible = "fsl,imx6ul-usbphy", .data = &imx6ul_phy_data, },
+ { .compatible = "fsl,imx7ulp-usbphy", .data = &imx7ulp_phy_data, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);
+
+struct mxs_phy {
+ struct usb_phy phy;
+ struct clk *clk;
+ const struct mxs_phy_data *data;
+ struct regmap *regmap_anatop;
+ int port_id;
+ u32 tx_reg_set;
+ u32 tx_reg_mask;
+};
+
+static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->data == &imx6q_phy_data;
+}
+
+static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->data == &imx6sl_phy_data;
+}
+
+static inline bool is_imx7ulp_phy(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->data == &imx7ulp_phy_data;
+}
+
+/*
+ * PHY needs some 32K cycles to switch from 32K clock to
+ * bus (such as AHB/AXI, etc) clock.
+ */
+static void mxs_phy_clock_switch_delay(void)
+{
+ usleep_range(300, 400);
+}
+
+static void mxs_phy_tx_init(struct mxs_phy *mxs_phy)
+{
+ void __iomem *base = mxs_phy->phy.io_priv;
+ u32 phytx;
+
+ /* Update TX register if there is anything to write */
+ if (mxs_phy->tx_reg_mask) {
+ phytx = readl(base + HW_USBPHY_TX);
+ phytx &= ~mxs_phy->tx_reg_mask;
+ phytx |= mxs_phy->tx_reg_set;
+ writel(phytx, base + HW_USBPHY_TX);
+ }
+}
+
+static int mxs_phy_pll_enable(void __iomem *base, bool enable)
+{
+ int ret = 0;
+
+ if (enable) {
+ u32 value;
+
+ writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_SET);
+ writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_CLR);
+ writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_SET);
+ ret = readl_poll_timeout(base + HW_USBPHY_PLL_SIC,
+ value, (value & BM_USBPHY_PLL_LOCK) != 0,
+ 100, 10000);
+ if (ret)
+ return ret;
+
+ writel(BM_USBPHY_PLL_EN_USB_CLKS, base +
+ HW_USBPHY_PLL_SIC_SET);
+ } else {
+ writel(BM_USBPHY_PLL_EN_USB_CLKS, base +
+ HW_USBPHY_PLL_SIC_CLR);
+ writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_CLR);
+ writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_SET);
+ writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_CLR);
+ }
+
+ return ret;
+}
+
+static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
+{
+ int ret;
+ void __iomem *base = mxs_phy->phy.io_priv;
+
+ if (is_imx7ulp_phy(mxs_phy)) {
+ ret = mxs_phy_pll_enable(base, true);
+ if (ret)
+ return ret;
+ }
+
+ ret = stmp_reset_block(base + HW_USBPHY_CTRL);
+ if (ret)
+ goto disable_pll;
+
+ /* Power up the PHY */
+ writel(0, base + HW_USBPHY_PWD);
+
+ /*
+ * USB PHY Ctrl Setting
+ * - Auto clock/power on
+ * - Enable full/low speed support
+ */
+ writel(BM_USBPHY_CTRL_ENAUTOSET_USBCLKS |
+ BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE |
+ BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD |
+ BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE |
+ BM_USBPHY_CTRL_ENAUTO_PWRON_PLL |
+ BM_USBPHY_CTRL_ENUTMILEVEL2 |
+ BM_USBPHY_CTRL_ENUTMILEVEL3,
+ base + HW_USBPHY_CTRL_SET);
+
+ if (mxs_phy->data->flags & MXS_PHY_NEED_IP_FIX)
+ writel(BM_USBPHY_IP_FIX, base + HW_USBPHY_IP_SET);
+
+ if (mxs_phy->regmap_anatop) {
+ unsigned int reg = mxs_phy->port_id ?
+ ANADIG_USB1_CHRG_DETECT_SET :
+ ANADIG_USB2_CHRG_DETECT_SET;
+ /*
+ * The external charger detector needs to be disabled,
+ * or the signal at DP will be poor
+ */
+ regmap_write(mxs_phy->regmap_anatop, reg,
+ ANADIG_USB1_CHRG_DETECT_EN_B |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+ }
+
+ mxs_phy_tx_init(mxs_phy);
+
+ return 0;
+
+disable_pll:
+ if (is_imx7ulp_phy(mxs_phy))
+ mxs_phy_pll_enable(base, false);
+ return ret;
+}
+
+/* Return true if the vbus is there */
+static bool mxs_phy_get_vbus_status(struct mxs_phy *mxs_phy)
+{
+ unsigned int vbus_value = 0;
+
+ if (!mxs_phy->regmap_anatop)
+ return false;
+
+ if (mxs_phy->port_id == 0)
+ regmap_read(mxs_phy->regmap_anatop,
+ ANADIG_USB1_VBUS_DET_STAT,
+ &vbus_value);
+ else if (mxs_phy->port_id == 1)
+ regmap_read(mxs_phy->regmap_anatop,
+ ANADIG_USB2_VBUS_DET_STAT,
+ &vbus_value);
+
+ if (vbus_value & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)
+ return true;
+ else
+ return false;
+}
+
+static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect)
+{
+ void __iomem *base = mxs_phy->phy.io_priv;
+ u32 reg;
+
+ if (disconnect)
+ writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
+ base + HW_USBPHY_DEBUG_CLR);
+
+ if (mxs_phy->port_id == 0) {
+ reg = disconnect ? ANADIG_USB1_LOOPBACK_SET
+ : ANADIG_USB1_LOOPBACK_CLR;
+ regmap_write(mxs_phy->regmap_anatop, reg,
+ BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 |
+ BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN);
+ } else if (mxs_phy->port_id == 1) {
+ reg = disconnect ? ANADIG_USB2_LOOPBACK_SET
+ : ANADIG_USB2_LOOPBACK_CLR;
+ regmap_write(mxs_phy->regmap_anatop, reg,
+ BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 |
+ BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN);
+ }
+
+ if (!disconnect)
+ writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
+ base + HW_USBPHY_DEBUG_SET);
+
+ /* Delay some time, and let Linestate be SE0 for controller */
+ if (disconnect)
+ usleep_range(500, 1000);
+}
+
+static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->phy.last_event == USB_EVENT_ID;
+}
+
+static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
+{
+ bool vbus_is_on = false;
+
+ /* If the SoCs don't need to disconnect line without vbus, quit */
+ if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS))
+ return;
+
+ /* If the SoCs don't have anatop, quit */
+ if (!mxs_phy->regmap_anatop)
+ return;
+
+ vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
+
+ if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy))
+ __mxs_phy_disconnect_line(mxs_phy, true);
+ else
+ __mxs_phy_disconnect_line(mxs_phy, false);
+
+}
+
+static int mxs_phy_init(struct usb_phy *phy)
+{
+ int ret;
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+
+ mxs_phy_clock_switch_delay();
+ ret = clk_prepare_enable(mxs_phy->clk);
+ if (ret)
+ return ret;
+
+ return mxs_phy_hw_init(mxs_phy);
+}
+
+static void mxs_phy_shutdown(struct usb_phy *phy)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+ u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP |
+ BM_USBPHY_CTRL_ENDPDMCHG_WKUP |
+ BM_USBPHY_CTRL_ENIDCHG_WKUP |
+ BM_USBPHY_CTRL_ENAUTOSET_USBCLKS |
+ BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE |
+ BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD |
+ BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE |
+ BM_USBPHY_CTRL_ENAUTO_PWRON_PLL;
+
+ writel(value, phy->io_priv + HW_USBPHY_CTRL_CLR);
+ writel(0xffffffff, phy->io_priv + HW_USBPHY_PWD);
+
+ writel(BM_USBPHY_CTRL_CLKGATE,
+ phy->io_priv + HW_USBPHY_CTRL_SET);
+
+ if (is_imx7ulp_phy(mxs_phy))
+ mxs_phy_pll_enable(phy->io_priv, false);
+
+ clk_disable_unprepare(mxs_phy->clk);
+}
+
+static bool mxs_phy_is_low_speed_connection(struct mxs_phy *mxs_phy)
+{
+ unsigned int line_state;
+ /* bit definition is the same for all controllers */
+ unsigned int dp_bit = BM_ANADIG_USB1_MISC_RX_VPIN_FS,
+ dm_bit = BM_ANADIG_USB1_MISC_RX_VMIN_FS;
+ unsigned int reg = ANADIG_USB1_MISC;
+
+ /* If the SoCs don't have anatop, quit */
+ if (!mxs_phy->regmap_anatop)
+ return false;
+
+ if (mxs_phy->port_id == 0)
+ reg = ANADIG_USB1_MISC;
+ else if (mxs_phy->port_id == 1)
+ reg = ANADIG_USB2_MISC;
+
+ regmap_read(mxs_phy->regmap_anatop, reg, &line_state);
+
+ if ((line_state & (dp_bit | dm_bit)) == dm_bit)
+ return true;
+ else
+ return false;
+}
+
+static int mxs_phy_suspend(struct usb_phy *x, int suspend)
+{
+ int ret;
+ struct mxs_phy *mxs_phy = to_mxs_phy(x);
+ bool low_speed_connection, vbus_is_on;
+
+ low_speed_connection = mxs_phy_is_low_speed_connection(mxs_phy);
+ vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
+
+ if (suspend) {
+ /*
+ * FIXME: Do not power down RXPWD1PT1 bit for low speed
+ * connect. The low speed connection will have problem at
+ * very rare cases during usb suspend and resume process.
+ */
+ if (low_speed_connection & vbus_is_on) {
+ /*
+ * If value to be set as pwd value is not 0xffffffff,
+ * several 32Khz cycles are needed.
+ */
+ mxs_phy_clock_switch_delay();
+ writel(0xffbfffff, x->io_priv + HW_USBPHY_PWD);
+ } else {
+ 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 {
+ mxs_phy_clock_switch_delay();
+ ret = clk_prepare_enable(mxs_phy->clk);
+ if (ret)
+ return ret;
+ 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_set_wakeup(struct usb_phy *x, bool enabled)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(x);
+ u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP |
+ BM_USBPHY_CTRL_ENDPDMCHG_WKUP |
+ BM_USBPHY_CTRL_ENIDCHG_WKUP;
+ if (enabled) {
+ mxs_phy_disconnect_line(mxs_phy, true);
+ writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_SET);
+ } else {
+ writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_CLR);
+ mxs_phy_disconnect_line(mxs_phy, false);
+ }
+
+ return 0;
+}
+
+static int mxs_phy_on_connect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ dev_dbg(phy->dev, "%s device has connected\n",
+ (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
+
+ 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 device has disconnected\n",
+ (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
+
+ /* Sometimes, the speed is not high speed when the error occurs */
+ if (readl(phy->io_priv + HW_USBPHY_CTRL) &
+ BM_USBPHY_CTRL_ENHOSTDISCONDETECT)
+ writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
+ phy->io_priv + HW_USBPHY_CTRL_CLR);
+
+ return 0;
+}
+
+#define MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT 100
+static int mxs_charger_data_contact_detect(struct mxs_phy *x)
+{
+ struct regmap *regmap = x->regmap_anatop;
+ int i, stable_contact_count = 0;
+ u32 val;
+
+ /* Check if vbus is valid */
+ regmap_read(regmap, ANADIG_USB1_VBUS_DET_STAT, &val);
+ if (!(val & ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) {
+ dev_err(x->phy.dev, "vbus is not valid\n");
+ return -EINVAL;
+ }
+
+ /* Enable charger detector */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
+ ANADIG_USB1_CHRG_DETECT_EN_B);
+ /*
+ * - Do not check whether a charger is connected to the USB port
+ * - Check whether the USB plug has been in contact with each other
+ */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+ ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+ /* Check if plug is connected */
+ for (i = 0; i < MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT; i++) {
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (val & ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) {
+ stable_contact_count++;
+ if (stable_contact_count > 5)
+ /* Data pin makes contact */
+ break;
+ else
+ usleep_range(5000, 10000);
+ } else {
+ stable_contact_count = 0;
+ usleep_range(5000, 6000);
+ }
+ }
+
+ if (i == MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT) {
+ dev_err(x->phy.dev,
+ "Data pin can't make good contact.\n");
+ /* Disable charger detector */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+ ANADIG_USB1_CHRG_DETECT_EN_B |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static enum usb_charger_type mxs_charger_primary_detection(struct mxs_phy *x)
+{
+ struct regmap *regmap = x->regmap_anatop;
+ enum usb_charger_type chgr_type = UNKNOWN_TYPE;
+ u32 val;
+
+ /*
+ * - Do check whether a charger is connected to the USB port
+ * - Do not Check whether the USB plug has been in contact with
+ * each other
+ */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
+ ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+ msleep(100);
+
+ /* Check if it is a charger */
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) {
+ chgr_type = SDP_TYPE;
+ dev_dbg(x->phy.dev, "It is a standard downstream port\n");
+ }
+
+ /* Disable charger detector */
+ regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+ ANADIG_USB1_CHRG_DETECT_EN_B |
+ ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+ return chgr_type;
+}
+
+/*
+ * It must be called after DP is pulled up, which is used to
+ * differentiate DCP and CDP.
+ */
+static enum usb_charger_type mxs_charger_secondary_detection(struct mxs_phy *x)
+{
+ struct regmap *regmap = x->regmap_anatop;
+ int val;
+
+ msleep(80);
+
+ regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+ if (val & ANADIG_USB1_CHRG_DET_STAT_DM_STATE) {
+ dev_dbg(x->phy.dev, "It is a dedicate charging port\n");
+ return DCP_TYPE;
+ } else {
+ dev_dbg(x->phy.dev, "It is a charging downstream port\n");
+ return CDP_TYPE;
+ }
+}
+
+static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+ struct regmap *regmap = mxs_phy->regmap_anatop;
+ void __iomem *base = phy->io_priv;
+ enum usb_charger_type chgr_type = UNKNOWN_TYPE;
+
+ if (!regmap)
+ return UNKNOWN_TYPE;
+
+ if (mxs_charger_data_contact_detect(mxs_phy))
+ return chgr_type;
+
+ chgr_type = mxs_charger_primary_detection(mxs_phy);
+
+ if (chgr_type != SDP_TYPE) {
+ /* Pull up DP via test */
+ writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
+ base + HW_USBPHY_DEBUG_CLR);
+ regmap_write(regmap, ANADIG_USB1_LOOPBACK_SET,
+ ANADIG_USB1_LOOPBACK_UTMI_TESTSTART);
+
+ chgr_type = mxs_charger_secondary_detection(mxs_phy);
+
+ /* Stop the test */
+ regmap_write(regmap, ANADIG_USB1_LOOPBACK_CLR,
+ ANADIG_USB1_LOOPBACK_UTMI_TESTSTART);
+ writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
+ base + HW_USBPHY_DEBUG_SET);
+ }
+
+ return chgr_type;
+}
+
+static int mxs_phy_probe(struct platform_device *pdev)
+{
+ void __iomem *base;
+ struct clk *clk;
+ struct mxs_phy *mxs_phy;
+ int ret;
+ const struct of_device_id *of_id;
+ struct device_node *np = pdev->dev.of_node;
+ u32 val;
+
+ of_id = of_match_device(mxs_phy_dt_ids, &pdev->dev);
+ if (!of_id)
+ return -ENODEV;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ 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)
+ return -ENOMEM;
+
+ /* Some SoCs don't have anatop registers */
+ if (of_get_property(np, "fsl,anatop", NULL)) {
+ mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
+ (np, "fsl,anatop");
+ if (IS_ERR(mxs_phy->regmap_anatop)) {
+ dev_dbg(&pdev->dev,
+ "failed to find regmap for anatop\n");
+ return PTR_ERR(mxs_phy->regmap_anatop);
+ }
+ }
+
+ /* Precompute which bits of the TX register are to be updated, if any */
+ if (!of_property_read_u32(np, "fsl,tx-cal-45-dn-ohms", &val) &&
+ val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) {
+ /* Scale to a 4-bit value */
+ val = (MXS_PHY_TX_CAL45_MAX - val) * 0xF
+ / (MXS_PHY_TX_CAL45_MAX - MXS_PHY_TX_CAL45_MIN);
+ mxs_phy->tx_reg_mask |= GM_USBPHY_TX_TXCAL45DN(~0);
+ mxs_phy->tx_reg_set |= GM_USBPHY_TX_TXCAL45DN(val);
+ }
+
+ if (!of_property_read_u32(np, "fsl,tx-cal-45-dp-ohms", &val) &&
+ val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) {
+ /* Scale to a 4-bit value. */
+ val = (MXS_PHY_TX_CAL45_MAX - val) * 0xF
+ / (MXS_PHY_TX_CAL45_MAX - MXS_PHY_TX_CAL45_MIN);
+ mxs_phy->tx_reg_mask |= GM_USBPHY_TX_TXCAL45DP(~0);
+ mxs_phy->tx_reg_set |= GM_USBPHY_TX_TXCAL45DP(val);
+ }
+
+ if (!of_property_read_u32(np, "fsl,tx-d-cal", &val) &&
+ val >= MXS_PHY_TX_D_CAL_MIN && val <= MXS_PHY_TX_D_CAL_MAX) {
+ /* Scale to a 4-bit value. Round up the values and heavily
+ * weight the rounding by adding 2/3 of the denominator.
+ */
+ val = ((MXS_PHY_TX_D_CAL_MAX - val) * 0xF
+ + (MXS_PHY_TX_D_CAL_MAX - MXS_PHY_TX_D_CAL_MIN) * 2/3)
+ / (MXS_PHY_TX_D_CAL_MAX - MXS_PHY_TX_D_CAL_MIN);
+ mxs_phy->tx_reg_mask |= GM_USBPHY_TX_D_CAL(~0);
+ mxs_phy->tx_reg_set |= GM_USBPHY_TX_D_CAL(val);
+ }
+
+ ret = of_alias_get_id(np, "usbphy");
+ if (ret < 0)
+ dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret);
+ mxs_phy->port_id = ret;
+
+ 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;
+ mxs_phy->phy.type = USB_PHY_TYPE_USB2;
+ mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup;
+ mxs_phy->phy.charger_detect = mxs_phy_charger_detect;
+
+ mxs_phy->clk = clk;
+ mxs_phy->data = of_id->data;
+
+ platform_set_drvdata(pdev, mxs_phy);
+
+ device_set_wakeup_capable(&pdev->dev, true);
+
+ return usb_add_phy_dev(&mxs_phy->phy);
+}
+
+static int mxs_phy_remove(struct platform_device *pdev)
+{
+ struct mxs_phy *mxs_phy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&mxs_phy->phy);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on)
+{
+ unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
+
+ /* If the SoCs don't have anatop, quit */
+ if (!mxs_phy->regmap_anatop)
+ return;
+
+ if (is_imx6q_phy(mxs_phy))
+ regmap_write(mxs_phy->regmap_anatop, reg,
+ BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG);
+ else if (is_imx6sl_phy(mxs_phy))
+ regmap_write(mxs_phy->regmap_anatop,
+ reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL);
+}
+
+static int mxs_phy_system_suspend(struct device *dev)
+{
+ struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ mxs_phy_enable_ldo_in_suspend(mxs_phy, true);
+
+ return 0;
+}
+
+static int mxs_phy_system_resume(struct device *dev)
+{
+ struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ mxs_phy_enable_ldo_in_suspend(mxs_phy, false);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static SIMPLE_DEV_PM_OPS(mxs_phy_pm, mxs_phy_system_suspend,
+ mxs_phy_system_resume);
+
+static struct platform_driver mxs_phy_driver = {
+ .probe = mxs_phy_probe,
+ .remove = mxs_phy_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = mxs_phy_dt_ids,
+ .pm = &mxs_phy_pm,
+ },
+};
+
+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-omap-otg.c b/drivers/usb/phy/phy-omap-otg.c
new file mode 100644
index 000000000..ee0863c65
--- /dev/null
+++ b/drivers/usb/phy/phy-omap-otg.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * OMAP OTG controller driver
+ *
+ * Based on code from tahvo-usb.c and isp1301_omap.c drivers.
+ *
+ * Copyright (C) 2005-2006 Nokia Corporation
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2004 David Brownell
+ */
+
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/extcon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/usb-omap1.h>
+
+struct otg_device {
+ void __iomem *base;
+ bool id;
+ bool vbus;
+ struct extcon_dev *extcon;
+ struct notifier_block vbus_nb;
+ struct notifier_block id_nb;
+};
+
+#define OMAP_OTG_CTRL 0x0c
+#define OMAP_OTG_ASESSVLD (1 << 20)
+#define OMAP_OTG_BSESSEND (1 << 19)
+#define OMAP_OTG_BSESSVLD (1 << 18)
+#define OMAP_OTG_VBUSVLD (1 << 17)
+#define OMAP_OTG_ID (1 << 16)
+#define OMAP_OTG_XCEIV_OUTPUTS \
+ (OMAP_OTG_ASESSVLD | OMAP_OTG_BSESSEND | OMAP_OTG_BSESSVLD | \
+ OMAP_OTG_VBUSVLD | OMAP_OTG_ID)
+
+static void omap_otg_ctrl(struct otg_device *otg_dev, u32 outputs)
+{
+ u32 l;
+
+ l = readl(otg_dev->base + OMAP_OTG_CTRL);
+ l &= ~OMAP_OTG_XCEIV_OUTPUTS;
+ l |= outputs;
+ writel(l, otg_dev->base + OMAP_OTG_CTRL);
+}
+
+static void omap_otg_set_mode(struct otg_device *otg_dev)
+{
+ if (!otg_dev->id && otg_dev->vbus)
+ /* Set B-session valid. */
+ omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSVLD);
+ else if (otg_dev->vbus)
+ /* Set A-session valid. */
+ omap_otg_ctrl(otg_dev, OMAP_OTG_ASESSVLD);
+ else if (!otg_dev->id)
+ /* Set B-session end to indicate no VBUS. */
+ omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSEND);
+}
+
+static int omap_otg_id_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct otg_device *otg_dev = container_of(nb, struct otg_device, id_nb);
+
+ otg_dev->id = event;
+ omap_otg_set_mode(otg_dev);
+
+ return NOTIFY_DONE;
+}
+
+static int omap_otg_vbus_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct otg_device *otg_dev = container_of(nb, struct otg_device,
+ vbus_nb);
+
+ otg_dev->vbus = event;
+ omap_otg_set_mode(otg_dev);
+
+ return NOTIFY_DONE;
+}
+
+static int omap_otg_probe(struct platform_device *pdev)
+{
+ const struct omap_usb_config *config = pdev->dev.platform_data;
+ struct otg_device *otg_dev;
+ struct extcon_dev *extcon;
+ int ret;
+ u32 rev;
+
+ if (!config || !config->extcon)
+ return -ENODEV;
+
+ extcon = extcon_get_extcon_dev(config->extcon);
+ if (!extcon)
+ return -EPROBE_DEFER;
+
+ otg_dev = devm_kzalloc(&pdev->dev, sizeof(*otg_dev), GFP_KERNEL);
+ if (!otg_dev)
+ return -ENOMEM;
+
+ otg_dev->base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]);
+ if (IS_ERR(otg_dev->base))
+ return PTR_ERR(otg_dev->base);
+
+ otg_dev->extcon = extcon;
+ otg_dev->id_nb.notifier_call = omap_otg_id_notifier;
+ otg_dev->vbus_nb.notifier_call = omap_otg_vbus_notifier;
+
+ ret = devm_extcon_register_notifier(&pdev->dev, extcon,
+ EXTCON_USB_HOST, &otg_dev->id_nb);
+ if (ret)
+ return ret;
+
+ ret = devm_extcon_register_notifier(&pdev->dev, extcon,
+ EXTCON_USB, &otg_dev->vbus_nb);
+ if (ret) {
+ return ret;
+ }
+
+ otg_dev->id = extcon_get_state(extcon, EXTCON_USB_HOST);
+ otg_dev->vbus = extcon_get_state(extcon, EXTCON_USB);
+ omap_otg_set_mode(otg_dev);
+
+ rev = readl(otg_dev->base);
+
+ dev_info(&pdev->dev,
+ "OMAP USB OTG controller rev %d.%d (%s, id=%d, vbus=%d)\n",
+ (rev >> 4) & 0xf, rev & 0xf, config->extcon, otg_dev->id,
+ otg_dev->vbus);
+
+ platform_set_drvdata(pdev, otg_dev);
+
+ return 0;
+}
+
+static struct platform_driver omap_otg_driver = {
+ .probe = omap_otg_probe,
+ .driver = {
+ .name = "omap_otg",
+ },
+};
+module_platform_driver(omap_otg_driver);
+
+MODULE_DESCRIPTION("OMAP USB OTG controller driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
diff --git a/drivers/usb/phy/phy-tahvo.c b/drivers/usb/phy/phy-tahvo.c
new file mode 100644
index 000000000..d0672b671
--- /dev/null
+++ b/drivers/usb/phy/phy-tahvo.c
@@ -0,0 +1,444 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tahvo USB transceiver driver
+ *
+ * Copyright (C) 2005-2006 Nokia Corporation
+ *
+ * Parts copied from isp1301_omap.c.
+ * Copyright (C) 2004 Texas Instruments
+ * Copyright (C) 2004 David Brownell
+ *
+ * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs.
+ * Modified for Retu/Tahvo MFD by Aaro Koskinen.
+ */
+
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/usb.h>
+#include <linux/extcon-provider.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/usb/otg.h>
+#include <linux/mfd/retu.h>
+#include <linux/usb/gadget.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "tahvo-usb"
+
+#define TAHVO_REG_IDSR 0x02
+#define TAHVO_REG_USBR 0x06
+
+#define USBR_SLAVE_CONTROL (1 << 8)
+#define USBR_VPPVIO_SW (1 << 7)
+#define USBR_SPEED (1 << 6)
+#define USBR_REGOUT (1 << 5)
+#define USBR_MASTER_SW2 (1 << 4)
+#define USBR_MASTER_SW1 (1 << 3)
+#define USBR_SLAVE_SW (1 << 2)
+#define USBR_NSUSPEND (1 << 1)
+#define USBR_SEMODE (1 << 0)
+
+#define TAHVO_MODE_HOST 0
+#define TAHVO_MODE_PERIPHERAL 1
+
+struct tahvo_usb {
+ struct platform_device *pt_dev;
+ struct usb_phy phy;
+ int vbus_state;
+ struct mutex serialize;
+ struct clk *ick;
+ int irq;
+ int tahvo_mode;
+ struct extcon_dev *extcon;
+};
+
+static const unsigned int tahvo_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+
+ EXTCON_NONE,
+};
+
+static ssize_t vbus_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct tahvo_usb *tu = dev_get_drvdata(device);
+ return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off");
+}
+static DEVICE_ATTR_RO(vbus);
+
+static void check_vbus_state(struct tahvo_usb *tu)
+{
+ struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
+ int reg, prev_state;
+
+ reg = retu_read(rdev, TAHVO_REG_IDSR);
+ if (reg & TAHVO_STAT_VBUS) {
+ switch (tu->phy.otg->state) {
+ case OTG_STATE_B_IDLE:
+ /* Enable the gadget driver */
+ if (tu->phy.otg->gadget)
+ usb_gadget_vbus_connect(tu->phy.otg->gadget);
+ tu->phy.otg->state = OTG_STATE_B_PERIPHERAL;
+ usb_phy_set_event(&tu->phy, USB_EVENT_ENUMERATED);
+ break;
+ case OTG_STATE_A_IDLE:
+ /*
+ * Session is now valid assuming the USB hub is driving
+ * Vbus.
+ */
+ tu->phy.otg->state = OTG_STATE_A_HOST;
+ break;
+ default:
+ break;
+ }
+ dev_info(&tu->pt_dev->dev, "USB cable connected\n");
+ } else {
+ switch (tu->phy.otg->state) {
+ case OTG_STATE_B_PERIPHERAL:
+ if (tu->phy.otg->gadget)
+ usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
+ tu->phy.otg->state = OTG_STATE_B_IDLE;
+ usb_phy_set_event(&tu->phy, USB_EVENT_NONE);
+ break;
+ case OTG_STATE_A_HOST:
+ tu->phy.otg->state = OTG_STATE_A_IDLE;
+ break;
+ default:
+ break;
+ }
+ dev_info(&tu->pt_dev->dev, "USB cable disconnected\n");
+ }
+
+ prev_state = tu->vbus_state;
+ tu->vbus_state = reg & TAHVO_STAT_VBUS;
+ if (prev_state != tu->vbus_state) {
+ extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
+ sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state");
+ }
+}
+
+static void tahvo_usb_become_host(struct tahvo_usb *tu)
+{
+ struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
+
+ extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, true);
+
+ /* Power up the transceiver in USB host mode */
+ retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND |
+ USBR_MASTER_SW2 | USBR_MASTER_SW1);
+ tu->phy.otg->state = OTG_STATE_A_IDLE;
+
+ check_vbus_state(tu);
+}
+
+static void tahvo_usb_stop_host(struct tahvo_usb *tu)
+{
+ tu->phy.otg->state = OTG_STATE_A_IDLE;
+}
+
+static void tahvo_usb_become_peripheral(struct tahvo_usb *tu)
+{
+ struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
+
+ extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, false);
+
+ /* Power up transceiver and set it in USB peripheral mode */
+ retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT |
+ USBR_NSUSPEND | USBR_SLAVE_SW);
+ tu->phy.otg->state = OTG_STATE_B_IDLE;
+
+ check_vbus_state(tu);
+}
+
+static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu)
+{
+ if (tu->phy.otg->gadget)
+ usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
+ tu->phy.otg->state = OTG_STATE_B_IDLE;
+}
+
+static void tahvo_usb_power_off(struct tahvo_usb *tu)
+{
+ struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
+
+ /* Disable gadget controller if any */
+ if (tu->phy.otg->gadget)
+ usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
+
+ /* Power off transceiver */
+ retu_write(rdev, TAHVO_REG_USBR, 0);
+ tu->phy.otg->state = OTG_STATE_UNDEFINED;
+}
+
+static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend)
+{
+ struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy);
+ struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
+ u16 w;
+
+ dev_dbg(&tu->pt_dev->dev, "%s\n", __func__);
+
+ w = retu_read(rdev, TAHVO_REG_USBR);
+ if (suspend)
+ w &= ~USBR_NSUSPEND;
+ else
+ w |= USBR_NSUSPEND;
+ retu_write(rdev, TAHVO_REG_USBR, w);
+
+ return 0;
+}
+
+static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
+ phy);
+
+ dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, host);
+
+ mutex_lock(&tu->serialize);
+
+ if (host == NULL) {
+ if (tu->tahvo_mode == TAHVO_MODE_HOST)
+ tahvo_usb_power_off(tu);
+ otg->host = NULL;
+ mutex_unlock(&tu->serialize);
+ return 0;
+ }
+
+ if (tu->tahvo_mode == TAHVO_MODE_HOST) {
+ otg->host = NULL;
+ tahvo_usb_become_host(tu);
+ }
+
+ otg->host = host;
+
+ mutex_unlock(&tu->serialize);
+
+ return 0;
+}
+
+static int tahvo_usb_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
+ phy);
+
+ dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, gadget);
+
+ mutex_lock(&tu->serialize);
+
+ if (!gadget) {
+ if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
+ tahvo_usb_power_off(tu);
+ tu->phy.otg->gadget = NULL;
+ mutex_unlock(&tu->serialize);
+ return 0;
+ }
+
+ tu->phy.otg->gadget = gadget;
+ if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
+ tahvo_usb_become_peripheral(tu);
+
+ mutex_unlock(&tu->serialize);
+
+ return 0;
+}
+
+static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu)
+{
+ struct tahvo_usb *tu = _tu;
+
+ mutex_lock(&tu->serialize);
+ check_vbus_state(tu);
+ mutex_unlock(&tu->serialize);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t otg_mode_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct tahvo_usb *tu = dev_get_drvdata(device);
+
+ switch (tu->tahvo_mode) {
+ case TAHVO_MODE_HOST:
+ return sprintf(buf, "host\n");
+ case TAHVO_MODE_PERIPHERAL:
+ return sprintf(buf, "peripheral\n");
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t otg_mode_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tahvo_usb *tu = dev_get_drvdata(device);
+ int r;
+
+ mutex_lock(&tu->serialize);
+ if (count >= 4 && strncmp(buf, "host", 4) == 0) {
+ if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
+ tahvo_usb_stop_peripheral(tu);
+ tu->tahvo_mode = TAHVO_MODE_HOST;
+ if (tu->phy.otg->host) {
+ dev_info(device, "HOST mode: host controller present\n");
+ tahvo_usb_become_host(tu);
+ } else {
+ dev_info(device, "HOST mode: no host controller, powering off\n");
+ tahvo_usb_power_off(tu);
+ }
+ r = strlen(buf);
+ } else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) {
+ if (tu->tahvo_mode == TAHVO_MODE_HOST)
+ tahvo_usb_stop_host(tu);
+ tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
+ if (tu->phy.otg->gadget) {
+ dev_info(device, "PERIPHERAL mode: gadget driver present\n");
+ tahvo_usb_become_peripheral(tu);
+ } else {
+ dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n");
+ tahvo_usb_power_off(tu);
+ }
+ r = strlen(buf);
+ } else {
+ r = -EINVAL;
+ }
+ mutex_unlock(&tu->serialize);
+
+ return r;
+}
+static DEVICE_ATTR_RW(otg_mode);
+
+static struct attribute *tahvo_attrs[] = {
+ &dev_attr_vbus.attr,
+ &dev_attr_otg_mode.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(tahvo);
+
+static int tahvo_usb_probe(struct platform_device *pdev)
+{
+ struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
+ struct tahvo_usb *tu;
+ int ret;
+
+ tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL);
+ if (!tu)
+ return -ENOMEM;
+
+ tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg),
+ GFP_KERNEL);
+ if (!tu->phy.otg)
+ return -ENOMEM;
+
+ tu->pt_dev = pdev;
+
+ /* Default mode */
+#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT
+ tu->tahvo_mode = TAHVO_MODE_HOST;
+#else
+ tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
+#endif
+
+ mutex_init(&tu->serialize);
+
+ tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick");
+ if (!IS_ERR(tu->ick))
+ clk_enable(tu->ick);
+
+ /*
+ * Set initial state, so that we generate kevents only on state changes.
+ */
+ tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS;
+
+ tu->extcon = devm_extcon_dev_allocate(&pdev->dev, tahvo_cable);
+ if (IS_ERR(tu->extcon)) {
+ dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
+ ret = PTR_ERR(tu->extcon);
+ goto err_disable_clk;
+ }
+
+ ret = devm_extcon_dev_register(&pdev->dev, tu->extcon);
+ if (ret) {
+ dev_err(&pdev->dev, "could not register extcon device: %d\n",
+ ret);
+ goto err_disable_clk;
+ }
+
+ /* Set the initial cable state. */
+ extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST,
+ tu->tahvo_mode == TAHVO_MODE_HOST);
+ extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
+
+ /* Create OTG interface */
+ tahvo_usb_power_off(tu);
+ tu->phy.dev = &pdev->dev;
+ tu->phy.otg->state = OTG_STATE_UNDEFINED;
+ tu->phy.label = DRIVER_NAME;
+ tu->phy.set_suspend = tahvo_usb_set_suspend;
+
+ tu->phy.otg->usb_phy = &tu->phy;
+ tu->phy.otg->set_host = tahvo_usb_set_host;
+ tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral;
+
+ ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot register USB transceiver: %d\n",
+ ret);
+ goto err_disable_clk;
+ }
+
+ dev_set_drvdata(&pdev->dev, tu);
+
+ tu->irq = ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ goto err_remove_phy;
+ ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt,
+ IRQF_ONESHOT,
+ "tahvo-vbus", tu);
+ if (ret) {
+ dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n",
+ ret);
+ goto err_remove_phy;
+ }
+
+ return 0;
+
+err_remove_phy:
+ usb_remove_phy(&tu->phy);
+err_disable_clk:
+ if (!IS_ERR(tu->ick))
+ clk_disable(tu->ick);
+
+ return ret;
+}
+
+static int tahvo_usb_remove(struct platform_device *pdev)
+{
+ struct tahvo_usb *tu = platform_get_drvdata(pdev);
+
+ free_irq(tu->irq, tu);
+ usb_remove_phy(&tu->phy);
+ if (!IS_ERR(tu->ick))
+ clk_disable(tu->ick);
+
+ return 0;
+}
+
+static struct platform_driver tahvo_usb_driver = {
+ .probe = tahvo_usb_probe,
+ .remove = tahvo_usb_remove,
+ .driver = {
+ .name = "tahvo-usb",
+ .dev_groups = tahvo_groups,
+ },
+};
+module_platform_driver(tahvo_usb_driver);
+
+MODULE_DESCRIPTION("Tahvo USB transceiver driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs");
+MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c
new file mode 100644
index 000000000..03a333797
--- /dev/null
+++ b/drivers/usb/phy/phy-tegra-usb.c
@@ -0,0 +1,1225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Author:
+ * Erik Gilling <konkers@google.com>
+ * Benoit Goby <benoit@android.com>
+ * Venu Byravarasu <vbyravarasu@nvidia.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/resource.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <linux/regulator/consumer.h>
+
+#include <linux/usb/ehci_def.h>
+#include <linux/usb/of.h>
+#include <linux/usb/tegra_usb_phy.h>
+#include <linux/usb/ulpi.h>
+
+#define ULPI_VIEWPORT 0x170
+
+/* PORTSC PTS/PHCD bits, Tegra20 only */
+#define TEGRA_USB_PORTSC1 0x184
+#define TEGRA_USB_PORTSC1_PTS(x) (((x) & 0x3) << 30)
+#define TEGRA_USB_PORTSC1_PHCD BIT(23)
+
+/* HOSTPC1 PTS/PHCD bits, Tegra30 and above */
+#define TEGRA_USB_HOSTPC1_DEVLC 0x1b4
+#define TEGRA_USB_HOSTPC1_DEVLC_PTS(x) (((x) & 0x7) << 29)
+#define TEGRA_USB_HOSTPC1_DEVLC_PHCD BIT(22)
+
+/* Bits of PORTSC1, which will get cleared by writing 1 into them */
+#define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC)
+
+#define USB_SUSP_CTRL 0x400
+#define USB_WAKE_ON_CNNT_EN_DEV BIT(3)
+#define USB_WAKE_ON_DISCON_EN_DEV BIT(4)
+#define USB_SUSP_CLR BIT(5)
+#define USB_PHY_CLK_VALID BIT(7)
+#define UTMIP_RESET BIT(11)
+#define UHSIC_RESET BIT(11)
+#define UTMIP_PHY_ENABLE BIT(12)
+#define ULPI_PHY_ENABLE BIT(13)
+#define USB_SUSP_SET BIT(14)
+#define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16)
+
+#define USB1_LEGACY_CTRL 0x410
+#define USB1_NO_LEGACY_MODE BIT(0)
+#define USB1_VBUS_SENSE_CTL_MASK (3 << 1)
+#define USB1_VBUS_SENSE_CTL_VBUS_WAKEUP (0 << 1)
+#define USB1_VBUS_SENSE_CTL_AB_SESS_VLD_OR_VBUS_WAKEUP \
+ (1 << 1)
+#define USB1_VBUS_SENSE_CTL_AB_SESS_VLD (2 << 1)
+#define USB1_VBUS_SENSE_CTL_A_SESS_VLD (3 << 1)
+
+#define ULPI_TIMING_CTRL_0 0x424
+#define ULPI_OUTPUT_PINMUX_BYP BIT(10)
+#define ULPI_CLKOUT_PINMUX_BYP BIT(11)
+
+#define ULPI_TIMING_CTRL_1 0x428
+#define ULPI_DATA_TRIMMER_LOAD BIT(0)
+#define ULPI_DATA_TRIMMER_SEL(x) (((x) & 0x7) << 1)
+#define ULPI_STPDIRNXT_TRIMMER_LOAD BIT(16)
+#define ULPI_STPDIRNXT_TRIMMER_SEL(x) (((x) & 0x7) << 17)
+#define ULPI_DIR_TRIMMER_LOAD BIT(24)
+#define ULPI_DIR_TRIMMER_SEL(x) (((x) & 0x7) << 25)
+
+#define UTMIP_PLL_CFG1 0x804
+#define UTMIP_XTAL_FREQ_COUNT(x) (((x) & 0xfff) << 0)
+#define UTMIP_PLLU_ENABLE_DLY_COUNT(x) (((x) & 0x1f) << 27)
+
+#define UTMIP_XCVR_CFG0 0x808
+#define UTMIP_XCVR_SETUP(x) (((x) & 0xf) << 0)
+#define UTMIP_XCVR_SETUP_MSB(x) ((((x) & 0x70) >> 4) << 22)
+#define UTMIP_XCVR_LSRSLEW(x) (((x) & 0x3) << 8)
+#define UTMIP_XCVR_LSFSLEW(x) (((x) & 0x3) << 10)
+#define UTMIP_FORCE_PD_POWERDOWN BIT(14)
+#define UTMIP_FORCE_PD2_POWERDOWN BIT(16)
+#define UTMIP_FORCE_PDZI_POWERDOWN BIT(18)
+#define UTMIP_XCVR_LSBIAS_SEL BIT(21)
+#define UTMIP_XCVR_HSSLEW(x) (((x) & 0x3) << 4)
+#define UTMIP_XCVR_HSSLEW_MSB(x) ((((x) & 0x1fc) >> 2) << 25)
+
+#define UTMIP_BIAS_CFG0 0x80c
+#define UTMIP_OTGPD BIT(11)
+#define UTMIP_BIASPD BIT(10)
+#define UTMIP_HSSQUELCH_LEVEL(x) (((x) & 0x3) << 0)
+#define UTMIP_HSDISCON_LEVEL(x) (((x) & 0x3) << 2)
+#define UTMIP_HSDISCON_LEVEL_MSB(x) ((((x) & 0x4) >> 2) << 24)
+
+#define UTMIP_HSRX_CFG0 0x810
+#define UTMIP_ELASTIC_LIMIT(x) (((x) & 0x1f) << 10)
+#define UTMIP_IDLE_WAIT(x) (((x) & 0x1f) << 15)
+
+#define UTMIP_HSRX_CFG1 0x814
+#define UTMIP_HS_SYNC_START_DLY(x) (((x) & 0x1f) << 1)
+
+#define UTMIP_TX_CFG0 0x820
+#define UTMIP_FS_PREABMLE_J BIT(19)
+#define UTMIP_HS_DISCON_DISABLE BIT(8)
+
+#define UTMIP_MISC_CFG0 0x824
+#define UTMIP_DPDM_OBSERVE BIT(26)
+#define UTMIP_DPDM_OBSERVE_SEL(x) (((x) & 0xf) << 27)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_J UTMIP_DPDM_OBSERVE_SEL(0xf)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_K UTMIP_DPDM_OBSERVE_SEL(0xe)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_SE1 UTMIP_DPDM_OBSERVE_SEL(0xd)
+#define UTMIP_DPDM_OBSERVE_SEL_FS_SE0 UTMIP_DPDM_OBSERVE_SEL(0xc)
+#define UTMIP_SUSPEND_EXIT_ON_EDGE BIT(22)
+
+#define UTMIP_MISC_CFG1 0x828
+#define UTMIP_PLL_ACTIVE_DLY_COUNT(x) (((x) & 0x1f) << 18)
+#define UTMIP_PLLU_STABLE_COUNT(x) (((x) & 0xfff) << 6)
+
+#define UTMIP_DEBOUNCE_CFG0 0x82c
+#define UTMIP_BIAS_DEBOUNCE_A(x) (((x) & 0xffff) << 0)
+
+#define UTMIP_BAT_CHRG_CFG0 0x830
+#define UTMIP_PD_CHRG BIT(0)
+
+#define UTMIP_SPARE_CFG0 0x834
+#define FUSE_SETUP_SEL BIT(3)
+
+#define UTMIP_XCVR_CFG1 0x838
+#define UTMIP_FORCE_PDDISC_POWERDOWN BIT(0)
+#define UTMIP_FORCE_PDCHRP_POWERDOWN BIT(2)
+#define UTMIP_FORCE_PDDR_POWERDOWN BIT(4)
+#define UTMIP_XCVR_TERM_RANGE_ADJ(x) (((x) & 0xf) << 18)
+
+#define UTMIP_BIAS_CFG1 0x83c
+#define UTMIP_BIAS_PDTRK_COUNT(x) (((x) & 0x1f) << 3)
+
+/* For Tegra30 and above only, the address is different in Tegra20 */
+#define USB_USBMODE 0x1f8
+#define USB_USBMODE_MASK (3 << 0)
+#define USB_USBMODE_HOST (3 << 0)
+#define USB_USBMODE_DEVICE (2 << 0)
+
+static DEFINE_SPINLOCK(utmip_pad_lock);
+static unsigned int utmip_pad_count;
+
+struct tegra_xtal_freq {
+ unsigned int freq;
+ u8 enable_delay;
+ u8 stable_count;
+ u8 active_delay;
+ u8 xtal_freq_count;
+ u16 debounce;
+};
+
+static const struct tegra_xtal_freq tegra_freq_table[] = {
+ {
+ .freq = 12000000,
+ .enable_delay = 0x02,
+ .stable_count = 0x2F,
+ .active_delay = 0x04,
+ .xtal_freq_count = 0x76,
+ .debounce = 0x7530,
+ },
+ {
+ .freq = 13000000,
+ .enable_delay = 0x02,
+ .stable_count = 0x33,
+ .active_delay = 0x05,
+ .xtal_freq_count = 0x7F,
+ .debounce = 0x7EF4,
+ },
+ {
+ .freq = 19200000,
+ .enable_delay = 0x03,
+ .stable_count = 0x4B,
+ .active_delay = 0x06,
+ .xtal_freq_count = 0xBB,
+ .debounce = 0xBB80,
+ },
+ {
+ .freq = 26000000,
+ .enable_delay = 0x04,
+ .stable_count = 0x66,
+ .active_delay = 0x09,
+ .xtal_freq_count = 0xFE,
+ .debounce = 0xFDE8,
+ },
+};
+
+static inline struct tegra_usb_phy *to_tegra_usb_phy(struct usb_phy *u_phy)
+{
+ return container_of(u_phy, struct tegra_usb_phy, u_phy);
+}
+
+static void set_pts(struct tegra_usb_phy *phy, u8 pts_val)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ if (phy->soc_config->has_hostpc) {
+ val = readl_relaxed(base + TEGRA_USB_HOSTPC1_DEVLC);
+ val &= ~TEGRA_USB_HOSTPC1_DEVLC_PTS(~0);
+ val |= TEGRA_USB_HOSTPC1_DEVLC_PTS(pts_val);
+ writel_relaxed(val, base + TEGRA_USB_HOSTPC1_DEVLC);
+ } else {
+ val = readl_relaxed(base + TEGRA_USB_PORTSC1);
+ val &= ~TEGRA_PORTSC1_RWC_BITS;
+ val &= ~TEGRA_USB_PORTSC1_PTS(~0);
+ val |= TEGRA_USB_PORTSC1_PTS(pts_val);
+ writel_relaxed(val, base + TEGRA_USB_PORTSC1);
+ }
+}
+
+static void set_phcd(struct tegra_usb_phy *phy, bool enable)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ if (phy->soc_config->has_hostpc) {
+ val = readl_relaxed(base + TEGRA_USB_HOSTPC1_DEVLC);
+ if (enable)
+ val |= TEGRA_USB_HOSTPC1_DEVLC_PHCD;
+ else
+ val &= ~TEGRA_USB_HOSTPC1_DEVLC_PHCD;
+ writel_relaxed(val, base + TEGRA_USB_HOSTPC1_DEVLC);
+ } else {
+ val = readl_relaxed(base + TEGRA_USB_PORTSC1) & ~PORT_RWC_BITS;
+ if (enable)
+ val |= TEGRA_USB_PORTSC1_PHCD;
+ else
+ val &= ~TEGRA_USB_PORTSC1_PHCD;
+ writel_relaxed(val, base + TEGRA_USB_PORTSC1);
+ }
+}
+
+static int utmip_pad_open(struct tegra_usb_phy *phy)
+{
+ int ret;
+
+ ret = clk_prepare_enable(phy->pad_clk);
+ if (ret) {
+ dev_err(phy->u_phy.dev,
+ "Failed to enable UTMI-pads clock: %d\n", ret);
+ return ret;
+ }
+
+ spin_lock(&utmip_pad_lock);
+
+ ret = reset_control_deassert(phy->pad_rst);
+ if (ret) {
+ dev_err(phy->u_phy.dev,
+ "Failed to initialize UTMI-pads reset: %d\n", ret);
+ goto unlock;
+ }
+
+ ret = reset_control_assert(phy->pad_rst);
+ if (ret) {
+ dev_err(phy->u_phy.dev,
+ "Failed to assert UTMI-pads reset: %d\n", ret);
+ goto unlock;
+ }
+
+ udelay(1);
+
+ ret = reset_control_deassert(phy->pad_rst);
+ if (ret)
+ dev_err(phy->u_phy.dev,
+ "Failed to deassert UTMI-pads reset: %d\n", ret);
+unlock:
+ spin_unlock(&utmip_pad_lock);
+
+ clk_disable_unprepare(phy->pad_clk);
+
+ return ret;
+}
+
+static int utmip_pad_close(struct tegra_usb_phy *phy)
+{
+ int ret;
+
+ ret = clk_prepare_enable(phy->pad_clk);
+ if (ret) {
+ dev_err(phy->u_phy.dev,
+ "Failed to enable UTMI-pads clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_assert(phy->pad_rst);
+ if (ret)
+ dev_err(phy->u_phy.dev,
+ "Failed to assert UTMI-pads reset: %d\n", ret);
+
+ udelay(1);
+
+ clk_disable_unprepare(phy->pad_clk);
+
+ return ret;
+}
+
+static int utmip_pad_power_on(struct tegra_usb_phy *phy)
+{
+ struct tegra_utmip_config *config = phy->config;
+ void __iomem *base = phy->pad_regs;
+ u32 val;
+ int err;
+
+ err = clk_prepare_enable(phy->pad_clk);
+ if (err)
+ return err;
+
+ spin_lock(&utmip_pad_lock);
+
+ if (utmip_pad_count++ == 0) {
+ val = readl_relaxed(base + UTMIP_BIAS_CFG0);
+ val &= ~(UTMIP_OTGPD | UTMIP_BIASPD);
+
+ if (phy->soc_config->requires_extra_tuning_parameters) {
+ val &= ~(UTMIP_HSSQUELCH_LEVEL(~0) |
+ UTMIP_HSDISCON_LEVEL(~0) |
+ UTMIP_HSDISCON_LEVEL_MSB(~0));
+
+ val |= UTMIP_HSSQUELCH_LEVEL(config->hssquelch_level);
+ val |= UTMIP_HSDISCON_LEVEL(config->hsdiscon_level);
+ val |= UTMIP_HSDISCON_LEVEL_MSB(config->hsdiscon_level);
+ }
+ writel_relaxed(val, base + UTMIP_BIAS_CFG0);
+ }
+
+ spin_unlock(&utmip_pad_lock);
+
+ clk_disable_unprepare(phy->pad_clk);
+
+ return 0;
+}
+
+static int utmip_pad_power_off(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->pad_regs;
+ u32 val;
+ int ret;
+
+ ret = clk_prepare_enable(phy->pad_clk);
+ if (ret)
+ return ret;
+
+ spin_lock(&utmip_pad_lock);
+
+ if (!utmip_pad_count) {
+ dev_err(phy->u_phy.dev, "UTMIP pad already powered off\n");
+ ret = -EINVAL;
+ goto ulock;
+ }
+
+ if (--utmip_pad_count == 0) {
+ val = readl_relaxed(base + UTMIP_BIAS_CFG0);
+ val |= UTMIP_OTGPD | UTMIP_BIASPD;
+ writel_relaxed(val, base + UTMIP_BIAS_CFG0);
+ }
+ulock:
+ spin_unlock(&utmip_pad_lock);
+
+ clk_disable_unprepare(phy->pad_clk);
+
+ return ret;
+}
+
+static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result)
+{
+ u32 tmp;
+
+ return readl_relaxed_poll_timeout(reg, tmp, (tmp & mask) == result,
+ 2000, 6000);
+}
+
+static void utmi_phy_clk_disable(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ /*
+ * The USB driver may have already initiated the phy clock
+ * disable so wait to see if the clock turns off and if not
+ * then proceed with gating the clock.
+ */
+ if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) == 0)
+ return;
+
+ if (phy->is_legacy_phy) {
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= USB_SUSP_SET;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ usleep_range(10, 100);
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val &= ~USB_SUSP_SET;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+ } else {
+ set_phcd(phy, true);
+ }
+
+ if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0))
+ dev_err(phy->u_phy.dev,
+ "Timeout waiting for PHY to stabilize on disable\n");
+}
+
+static void utmi_phy_clk_enable(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ /*
+ * The USB driver may have already initiated the phy clock
+ * enable so wait to see if the clock turns on and if not
+ * then proceed with ungating the clock.
+ */
+ if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID,
+ USB_PHY_CLK_VALID) == 0)
+ return;
+
+ if (phy->is_legacy_phy) {
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= USB_SUSP_CLR;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ usleep_range(10, 100);
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val &= ~USB_SUSP_CLR;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+ } else {
+ set_phcd(phy, false);
+ }
+
+ if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID,
+ USB_PHY_CLK_VALID))
+ dev_err(phy->u_phy.dev,
+ "Timeout waiting for PHY to stabilize on enable\n");
+}
+
+static int utmi_phy_power_on(struct tegra_usb_phy *phy)
+{
+ struct tegra_utmip_config *config = phy->config;
+ void __iomem *base = phy->regs;
+ u32 val;
+ int err;
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= UTMIP_RESET;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ if (phy->is_legacy_phy) {
+ val = readl_relaxed(base + USB1_LEGACY_CTRL);
+ val |= USB1_NO_LEGACY_MODE;
+ writel_relaxed(val, base + USB1_LEGACY_CTRL);
+ }
+
+ val = readl_relaxed(base + UTMIP_TX_CFG0);
+ val |= UTMIP_FS_PREABMLE_J;
+ writel_relaxed(val, base + UTMIP_TX_CFG0);
+
+ val = readl_relaxed(base + UTMIP_HSRX_CFG0);
+ val &= ~(UTMIP_IDLE_WAIT(~0) | UTMIP_ELASTIC_LIMIT(~0));
+ val |= UTMIP_IDLE_WAIT(config->idle_wait_delay);
+ val |= UTMIP_ELASTIC_LIMIT(config->elastic_limit);
+ writel_relaxed(val, base + UTMIP_HSRX_CFG0);
+
+ val = readl_relaxed(base + UTMIP_HSRX_CFG1);
+ val &= ~UTMIP_HS_SYNC_START_DLY(~0);
+ val |= UTMIP_HS_SYNC_START_DLY(config->hssync_start_delay);
+ writel_relaxed(val, base + UTMIP_HSRX_CFG1);
+
+ val = readl_relaxed(base + UTMIP_DEBOUNCE_CFG0);
+ val &= ~UTMIP_BIAS_DEBOUNCE_A(~0);
+ val |= UTMIP_BIAS_DEBOUNCE_A(phy->freq->debounce);
+ writel_relaxed(val, base + UTMIP_DEBOUNCE_CFG0);
+
+ val = readl_relaxed(base + UTMIP_MISC_CFG0);
+ val &= ~UTMIP_SUSPEND_EXIT_ON_EDGE;
+ writel_relaxed(val, base + UTMIP_MISC_CFG0);
+
+ if (!phy->soc_config->utmi_pll_config_in_car_module) {
+ val = readl_relaxed(base + UTMIP_MISC_CFG1);
+ val &= ~(UTMIP_PLL_ACTIVE_DLY_COUNT(~0) |
+ UTMIP_PLLU_STABLE_COUNT(~0));
+ val |= UTMIP_PLL_ACTIVE_DLY_COUNT(phy->freq->active_delay) |
+ UTMIP_PLLU_STABLE_COUNT(phy->freq->stable_count);
+ writel_relaxed(val, base + UTMIP_MISC_CFG1);
+
+ val = readl_relaxed(base + UTMIP_PLL_CFG1);
+ val &= ~(UTMIP_XTAL_FREQ_COUNT(~0) |
+ UTMIP_PLLU_ENABLE_DLY_COUNT(~0));
+ val |= UTMIP_XTAL_FREQ_COUNT(phy->freq->xtal_freq_count) |
+ UTMIP_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay);
+ writel_relaxed(val, base + UTMIP_PLL_CFG1);
+ }
+
+ if (phy->mode == USB_DR_MODE_PERIPHERAL) {
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val &= ~(USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV);
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0);
+ val &= ~UTMIP_PD_CHRG;
+ writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0);
+ } else {
+ val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0);
+ val |= UTMIP_PD_CHRG;
+ writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0);
+ }
+
+ err = utmip_pad_power_on(phy);
+ if (err)
+ return err;
+
+ val = readl_relaxed(base + UTMIP_XCVR_CFG0);
+ val &= ~(UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN |
+ UTMIP_FORCE_PDZI_POWERDOWN | UTMIP_XCVR_LSBIAS_SEL |
+ UTMIP_XCVR_SETUP(~0) | UTMIP_XCVR_SETUP_MSB(~0) |
+ UTMIP_XCVR_LSFSLEW(~0) | UTMIP_XCVR_LSRSLEW(~0));
+
+ if (!config->xcvr_setup_use_fuses) {
+ val |= UTMIP_XCVR_SETUP(config->xcvr_setup);
+ val |= UTMIP_XCVR_SETUP_MSB(config->xcvr_setup);
+ }
+ val |= UTMIP_XCVR_LSFSLEW(config->xcvr_lsfslew);
+ val |= UTMIP_XCVR_LSRSLEW(config->xcvr_lsrslew);
+
+ if (phy->soc_config->requires_extra_tuning_parameters) {
+ val &= ~(UTMIP_XCVR_HSSLEW(~0) | UTMIP_XCVR_HSSLEW_MSB(~0));
+ val |= UTMIP_XCVR_HSSLEW(config->xcvr_hsslew);
+ val |= UTMIP_XCVR_HSSLEW_MSB(config->xcvr_hsslew);
+ }
+ writel_relaxed(val, base + UTMIP_XCVR_CFG0);
+
+ val = readl_relaxed(base + UTMIP_XCVR_CFG1);
+ val &= ~(UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN |
+ UTMIP_FORCE_PDDR_POWERDOWN | UTMIP_XCVR_TERM_RANGE_ADJ(~0));
+ val |= UTMIP_XCVR_TERM_RANGE_ADJ(config->term_range_adj);
+ writel_relaxed(val, base + UTMIP_XCVR_CFG1);
+
+ val = readl_relaxed(base + UTMIP_BIAS_CFG1);
+ val &= ~UTMIP_BIAS_PDTRK_COUNT(~0);
+ val |= UTMIP_BIAS_PDTRK_COUNT(0x5);
+ writel_relaxed(val, base + UTMIP_BIAS_CFG1);
+
+ val = readl_relaxed(base + UTMIP_SPARE_CFG0);
+ if (config->xcvr_setup_use_fuses)
+ val |= FUSE_SETUP_SEL;
+ else
+ val &= ~FUSE_SETUP_SEL;
+ writel_relaxed(val, base + UTMIP_SPARE_CFG0);
+
+ if (!phy->is_legacy_phy) {
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= UTMIP_PHY_ENABLE;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+ }
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val &= ~UTMIP_RESET;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ if (phy->is_legacy_phy) {
+ val = readl_relaxed(base + USB1_LEGACY_CTRL);
+ val &= ~USB1_VBUS_SENSE_CTL_MASK;
+ val |= USB1_VBUS_SENSE_CTL_A_SESS_VLD;
+ writel_relaxed(val, base + USB1_LEGACY_CTRL);
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val &= ~USB_SUSP_SET;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+ }
+
+ utmi_phy_clk_enable(phy);
+
+ if (phy->soc_config->requires_usbmode_setup) {
+ val = readl_relaxed(base + USB_USBMODE);
+ val &= ~USB_USBMODE_MASK;
+ if (phy->mode == USB_DR_MODE_HOST)
+ val |= USB_USBMODE_HOST;
+ else
+ val |= USB_USBMODE_DEVICE;
+ writel_relaxed(val, base + USB_USBMODE);
+ }
+
+ if (!phy->is_legacy_phy)
+ set_pts(phy, 0);
+
+ return 0;
+}
+
+static int utmi_phy_power_off(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ utmi_phy_clk_disable(phy);
+
+ if (phy->mode == USB_DR_MODE_PERIPHERAL) {
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0);
+ val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKEUP_DEBOUNCE_COUNT(5);
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+ }
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= UTMIP_RESET;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0);
+ val |= UTMIP_PD_CHRG;
+ writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0);
+
+ val = readl_relaxed(base + UTMIP_XCVR_CFG0);
+ val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN |
+ UTMIP_FORCE_PDZI_POWERDOWN;
+ writel_relaxed(val, base + UTMIP_XCVR_CFG0);
+
+ val = readl_relaxed(base + UTMIP_XCVR_CFG1);
+ val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN |
+ UTMIP_FORCE_PDDR_POWERDOWN;
+ writel_relaxed(val, base + UTMIP_XCVR_CFG1);
+
+ return utmip_pad_power_off(phy);
+}
+
+static void utmi_phy_preresume(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ val = readl_relaxed(base + UTMIP_TX_CFG0);
+ val |= UTMIP_HS_DISCON_DISABLE;
+ writel_relaxed(val, base + UTMIP_TX_CFG0);
+}
+
+static void utmi_phy_postresume(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ val = readl_relaxed(base + UTMIP_TX_CFG0);
+ val &= ~UTMIP_HS_DISCON_DISABLE;
+ writel_relaxed(val, base + UTMIP_TX_CFG0);
+}
+
+static void utmi_phy_restore_start(struct tegra_usb_phy *phy,
+ enum tegra_usb_phy_port_speed port_speed)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ val = readl_relaxed(base + UTMIP_MISC_CFG0);
+ val &= ~UTMIP_DPDM_OBSERVE_SEL(~0);
+ if (port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW)
+ val |= UTMIP_DPDM_OBSERVE_SEL_FS_K;
+ else
+ val |= UTMIP_DPDM_OBSERVE_SEL_FS_J;
+ writel_relaxed(val, base + UTMIP_MISC_CFG0);
+ usleep_range(1, 10);
+
+ val = readl_relaxed(base + UTMIP_MISC_CFG0);
+ val |= UTMIP_DPDM_OBSERVE;
+ writel_relaxed(val, base + UTMIP_MISC_CFG0);
+ usleep_range(10, 100);
+}
+
+static void utmi_phy_restore_end(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+
+ val = readl_relaxed(base + UTMIP_MISC_CFG0);
+ val &= ~UTMIP_DPDM_OBSERVE;
+ writel_relaxed(val, base + UTMIP_MISC_CFG0);
+ usleep_range(10, 100);
+}
+
+static int ulpi_phy_power_on(struct tegra_usb_phy *phy)
+{
+ void __iomem *base = phy->regs;
+ u32 val;
+ int err;
+
+ gpiod_set_value_cansleep(phy->reset_gpio, 1);
+
+ err = clk_prepare_enable(phy->clk);
+ if (err)
+ return err;
+
+ usleep_range(5000, 6000);
+
+ gpiod_set_value_cansleep(phy->reset_gpio, 0);
+
+ usleep_range(1000, 2000);
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= UHSIC_RESET;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ val = readl_relaxed(base + ULPI_TIMING_CTRL_0);
+ val |= ULPI_OUTPUT_PINMUX_BYP | ULPI_CLKOUT_PINMUX_BYP;
+ writel_relaxed(val, base + ULPI_TIMING_CTRL_0);
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= ULPI_PHY_ENABLE;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ val = 0;
+ writel_relaxed(val, base + ULPI_TIMING_CTRL_1);
+
+ val |= ULPI_DATA_TRIMMER_SEL(4);
+ val |= ULPI_STPDIRNXT_TRIMMER_SEL(4);
+ val |= ULPI_DIR_TRIMMER_SEL(4);
+ writel_relaxed(val, base + ULPI_TIMING_CTRL_1);
+ usleep_range(10, 100);
+
+ val |= ULPI_DATA_TRIMMER_LOAD;
+ val |= ULPI_STPDIRNXT_TRIMMER_LOAD;
+ val |= ULPI_DIR_TRIMMER_LOAD;
+ writel_relaxed(val, base + ULPI_TIMING_CTRL_1);
+
+ /* Fix VbusInvalid due to floating VBUS */
+ err = usb_phy_io_write(phy->ulpi, 0x40, 0x08);
+ if (err) {
+ dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", err);
+ goto disable_clk;
+ }
+
+ err = usb_phy_io_write(phy->ulpi, 0x80, 0x0B);
+ if (err) {
+ dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", err);
+ goto disable_clk;
+ }
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val |= USB_SUSP_CLR;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+ usleep_range(100, 1000);
+
+ val = readl_relaxed(base + USB_SUSP_CTRL);
+ val &= ~USB_SUSP_CLR;
+ writel_relaxed(val, base + USB_SUSP_CTRL);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(phy->clk);
+
+ return err;
+}
+
+static int ulpi_phy_power_off(struct tegra_usb_phy *phy)
+{
+ gpiod_set_value_cansleep(phy->reset_gpio, 1);
+ usleep_range(5000, 6000);
+ clk_disable_unprepare(phy->clk);
+
+ return 0;
+}
+
+static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy)
+{
+ int err;
+
+ if (phy->powered_on)
+ return 0;
+
+ if (phy->is_ulpi_phy)
+ err = ulpi_phy_power_on(phy);
+ else
+ err = utmi_phy_power_on(phy);
+ if (err)
+ return err;
+
+ phy->powered_on = true;
+
+ return 0;
+}
+
+static int tegra_usb_phy_power_off(struct tegra_usb_phy *phy)
+{
+ int err;
+
+ if (!phy->powered_on)
+ return 0;
+
+ if (phy->is_ulpi_phy)
+ err = ulpi_phy_power_off(phy);
+ else
+ err = utmi_phy_power_off(phy);
+ if (err)
+ return err;
+
+ phy->powered_on = false;
+
+ return 0;
+}
+
+static void tegra_usb_phy_shutdown(struct usb_phy *u_phy)
+{
+ struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+
+ if (WARN_ON(!phy->freq))
+ return;
+
+ tegra_usb_phy_power_off(phy);
+
+ if (!phy->is_ulpi_phy)
+ utmip_pad_close(phy);
+
+ regulator_disable(phy->vbus);
+ clk_disable_unprepare(phy->pll_u);
+
+ phy->freq = NULL;
+}
+
+static int tegra_usb_phy_set_suspend(struct usb_phy *u_phy, int suspend)
+{
+ struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+
+ if (WARN_ON(!phy->freq))
+ return -EINVAL;
+
+ if (suspend)
+ return tegra_usb_phy_power_off(phy);
+ else
+ return tegra_usb_phy_power_on(phy);
+}
+
+static int tegra_usb_phy_init(struct usb_phy *u_phy)
+{
+ struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+ unsigned long parent_rate;
+ unsigned int i;
+ int err;
+
+ if (WARN_ON(phy->freq))
+ return 0;
+
+ err = clk_prepare_enable(phy->pll_u);
+ if (err)
+ return err;
+
+ parent_rate = clk_get_rate(clk_get_parent(phy->pll_u));
+ for (i = 0; i < ARRAY_SIZE(tegra_freq_table); i++) {
+ if (tegra_freq_table[i].freq == parent_rate) {
+ phy->freq = &tegra_freq_table[i];
+ break;
+ }
+ }
+ if (!phy->freq) {
+ dev_err(phy->u_phy.dev, "Invalid pll_u parent rate %ld\n",
+ parent_rate);
+ err = -EINVAL;
+ goto disable_clk;
+ }
+
+ err = regulator_enable(phy->vbus);
+ if (err) {
+ dev_err(phy->u_phy.dev,
+ "Failed to enable USB VBUS regulator: %d\n", err);
+ goto disable_clk;
+ }
+
+ if (!phy->is_ulpi_phy) {
+ err = utmip_pad_open(phy);
+ if (err)
+ goto disable_vbus;
+ }
+
+ err = tegra_usb_phy_power_on(phy);
+ if (err)
+ goto close_phy;
+
+ return 0;
+
+close_phy:
+ if (!phy->is_ulpi_phy)
+ utmip_pad_close(phy);
+
+disable_vbus:
+ regulator_disable(phy->vbus);
+
+disable_clk:
+ clk_disable_unprepare(phy->pll_u);
+
+ phy->freq = NULL;
+
+ return err;
+}
+
+void tegra_usb_phy_preresume(struct usb_phy *u_phy)
+{
+ struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+
+ if (!phy->is_ulpi_phy)
+ utmi_phy_preresume(phy);
+}
+EXPORT_SYMBOL_GPL(tegra_usb_phy_preresume);
+
+void tegra_usb_phy_postresume(struct usb_phy *u_phy)
+{
+ struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+
+ if (!phy->is_ulpi_phy)
+ utmi_phy_postresume(phy);
+}
+EXPORT_SYMBOL_GPL(tegra_usb_phy_postresume);
+
+void tegra_ehci_phy_restore_start(struct usb_phy *u_phy,
+ enum tegra_usb_phy_port_speed port_speed)
+{
+ struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+
+ if (!phy->is_ulpi_phy)
+ utmi_phy_restore_start(phy, port_speed);
+}
+EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_start);
+
+void tegra_ehci_phy_restore_end(struct usb_phy *u_phy)
+{
+ struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+
+ if (!phy->is_ulpi_phy)
+ utmi_phy_restore_end(phy);
+}
+EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_end);
+
+static int read_utmi_param(struct platform_device *pdev, const char *param,
+ u8 *dest)
+{
+ u32 value;
+ int err;
+
+ err = of_property_read_u32(pdev->dev.of_node, param, &value);
+ if (err)
+ dev_err(&pdev->dev,
+ "Failed to read USB UTMI parameter %s: %d\n",
+ param, err);
+ else
+ *dest = value;
+
+ return err;
+}
+
+static int utmi_phy_probe(struct tegra_usb_phy *tegra_phy,
+ struct platform_device *pdev)
+{
+ struct tegra_utmip_config *config;
+ struct resource *res;
+ int err;
+
+ tegra_phy->is_ulpi_phy = false;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get UTMI pad regs\n");
+ return -ENXIO;
+ }
+
+ /*
+ * Note that UTMI pad registers are shared by all PHYs, therefore
+ * devm_platform_ioremap_resource() can't be used here.
+ */
+ tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!tegra_phy->pad_regs) {
+ dev_err(&pdev->dev, "Failed to remap UTMI pad regs\n");
+ return -ENOMEM;
+ }
+
+ tegra_phy->config = devm_kzalloc(&pdev->dev, sizeof(*config),
+ GFP_KERNEL);
+ if (!tegra_phy->config)
+ return -ENOMEM;
+
+ config = tegra_phy->config;
+
+ err = read_utmi_param(pdev, "nvidia,hssync-start-delay",
+ &config->hssync_start_delay);
+ if (err)
+ return err;
+
+ err = read_utmi_param(pdev, "nvidia,elastic-limit",
+ &config->elastic_limit);
+ if (err)
+ return err;
+
+ err = read_utmi_param(pdev, "nvidia,idle-wait-delay",
+ &config->idle_wait_delay);
+ if (err)
+ return err;
+
+ err = read_utmi_param(pdev, "nvidia,term-range-adj",
+ &config->term_range_adj);
+ if (err)
+ return err;
+
+ err = read_utmi_param(pdev, "nvidia,xcvr-lsfslew",
+ &config->xcvr_lsfslew);
+ if (err)
+ return err;
+
+ err = read_utmi_param(pdev, "nvidia,xcvr-lsrslew",
+ &config->xcvr_lsrslew);
+ if (err)
+ return err;
+
+ if (tegra_phy->soc_config->requires_extra_tuning_parameters) {
+ err = read_utmi_param(pdev, "nvidia,xcvr-hsslew",
+ &config->xcvr_hsslew);
+ if (err)
+ return err;
+
+ err = read_utmi_param(pdev, "nvidia,hssquelch-level",
+ &config->hssquelch_level);
+ if (err)
+ return err;
+
+ err = read_utmi_param(pdev, "nvidia,hsdiscon-level",
+ &config->hsdiscon_level);
+ if (err)
+ return err;
+ }
+
+ config->xcvr_setup_use_fuses = of_property_read_bool(
+ pdev->dev.of_node, "nvidia,xcvr-setup-use-fuses");
+
+ if (!config->xcvr_setup_use_fuses) {
+ err = read_utmi_param(pdev, "nvidia,xcvr-setup",
+ &config->xcvr_setup);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct tegra_phy_soc_config tegra20_soc_config = {
+ .utmi_pll_config_in_car_module = false,
+ .has_hostpc = false,
+ .requires_usbmode_setup = false,
+ .requires_extra_tuning_parameters = false,
+};
+
+static const struct tegra_phy_soc_config tegra30_soc_config = {
+ .utmi_pll_config_in_car_module = true,
+ .has_hostpc = true,
+ .requires_usbmode_setup = true,
+ .requires_extra_tuning_parameters = true,
+};
+
+static const struct of_device_id tegra_usb_phy_id_table[] = {
+ { .compatible = "nvidia,tegra30-usb-phy", .data = &tegra30_soc_config },
+ { .compatible = "nvidia,tegra20-usb-phy", .data = &tegra20_soc_config },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tegra_usb_phy_id_table);
+
+static int tegra_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct tegra_usb_phy *tegra_phy;
+ enum usb_phy_interface phy_type;
+ struct reset_control *reset;
+ struct gpio_desc *gpiod;
+ struct resource *res;
+ struct usb_phy *phy;
+ int err;
+
+ tegra_phy = devm_kzalloc(&pdev->dev, sizeof(*tegra_phy), GFP_KERNEL);
+ if (!tegra_phy)
+ return -ENOMEM;
+
+ tegra_phy->soc_config = of_device_get_match_data(&pdev->dev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get I/O memory\n");
+ return -ENXIO;
+ }
+
+ /*
+ * Note that PHY and USB controller are using shared registers,
+ * therefore devm_platform_ioremap_resource() can't be used here.
+ */
+ tegra_phy->regs = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!tegra_phy->regs) {
+ dev_err(&pdev->dev, "Failed to remap I/O memory\n");
+ return -ENOMEM;
+ }
+
+ tegra_phy->is_legacy_phy =
+ of_property_read_bool(np, "nvidia,has-legacy-mode");
+
+ if (of_find_property(np, "dr_mode", NULL))
+ tegra_phy->mode = usb_get_dr_mode(&pdev->dev);
+ else
+ tegra_phy->mode = USB_DR_MODE_HOST;
+
+ if (tegra_phy->mode == USB_DR_MODE_UNKNOWN) {
+ dev_err(&pdev->dev, "dr_mode is invalid\n");
+ return -EINVAL;
+ }
+
+ /* On some boards, the VBUS regulator doesn't need to be controlled */
+ tegra_phy->vbus = devm_regulator_get(&pdev->dev, "vbus");
+ if (IS_ERR(tegra_phy->vbus))
+ return PTR_ERR(tegra_phy->vbus);
+
+ tegra_phy->pll_u = devm_clk_get(&pdev->dev, "pll_u");
+ err = PTR_ERR_OR_ZERO(tegra_phy->pll_u);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to get pll_u clock: %d\n", err);
+ return err;
+ }
+
+ phy_type = of_usb_get_phy_mode(np);
+ switch (phy_type) {
+ case USBPHY_INTERFACE_MODE_UTMI:
+ err = utmi_phy_probe(tegra_phy, pdev);
+ if (err)
+ return err;
+
+ tegra_phy->pad_clk = devm_clk_get(&pdev->dev, "utmi-pads");
+ err = PTR_ERR_OR_ZERO(tegra_phy->pad_clk);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Failed to get UTMIP pad clock: %d\n", err);
+ return err;
+ }
+
+ reset = devm_reset_control_get_optional_shared(&pdev->dev,
+ "utmi-pads");
+ err = PTR_ERR_OR_ZERO(reset);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Failed to get UTMI-pads reset: %d\n", err);
+ return err;
+ }
+ tegra_phy->pad_rst = reset;
+ break;
+
+ case USBPHY_INTERFACE_MODE_ULPI:
+ tegra_phy->is_ulpi_phy = true;
+
+ tegra_phy->clk = devm_clk_get(&pdev->dev, "ulpi-link");
+ err = PTR_ERR_OR_ZERO(tegra_phy->clk);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Failed to get ULPI clock: %d\n", err);
+ return err;
+ }
+
+ gpiod = devm_gpiod_get_from_of_node(&pdev->dev, np,
+ "nvidia,phy-reset-gpio",
+ 0, GPIOD_OUT_HIGH,
+ "ulpi_phy_reset_b");
+ err = PTR_ERR_OR_ZERO(gpiod);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Request failed for reset GPIO: %d\n", err);
+ return err;
+ }
+ tegra_phy->reset_gpio = gpiod;
+
+ phy = devm_otg_ulpi_create(&pdev->dev,
+ &ulpi_viewport_access_ops, 0);
+ if (!phy) {
+ dev_err(&pdev->dev, "Failed to create ULPI OTG\n");
+ return -ENOMEM;
+ }
+
+ tegra_phy->ulpi = phy;
+ tegra_phy->ulpi->io_priv = tegra_phy->regs + ULPI_VIEWPORT;
+ break;
+
+ default:
+ dev_err(&pdev->dev, "phy_type %u is invalid or unsupported\n",
+ phy_type);
+ return -EINVAL;
+ }
+
+ tegra_phy->u_phy.dev = &pdev->dev;
+ tegra_phy->u_phy.init = tegra_usb_phy_init;
+ tegra_phy->u_phy.shutdown = tegra_usb_phy_shutdown;
+ tegra_phy->u_phy.set_suspend = tegra_usb_phy_set_suspend;
+
+ platform_set_drvdata(pdev, tegra_phy);
+
+ return usb_add_phy_dev(&tegra_phy->u_phy);
+}
+
+static int tegra_usb_phy_remove(struct platform_device *pdev)
+{
+ struct tegra_usb_phy *tegra_phy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&tegra_phy->u_phy);
+
+ return 0;
+}
+
+static struct platform_driver tegra_usb_phy_driver = {
+ .probe = tegra_usb_phy_probe,
+ .remove = tegra_usb_phy_remove,
+ .driver = {
+ .name = "tegra-phy",
+ .of_match_table = tegra_usb_phy_id_table,
+ },
+};
+module_platform_driver(tegra_usb_phy_driver);
+
+MODULE_DESCRIPTION("Tegra USB PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c
new file mode 100644
index 000000000..ab3c38a7d
--- /dev/null
+++ b/drivers/usb/phy/phy-twl6030-usb.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * twl6030_usb - TWL6030 USB transceiver, talking to OMAP OTG driver.
+ *
+ * Copyright (C) 2010 Texas Instruments Incorporated - https://www.ti.com
+ *
+ * Author: Hema HK <hemahk@ti.com>
+ */
+
+#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.h>
+#include <linux/usb/phy_companion.h>
+#include <linux/phy/omap_usb.h>
+#include <linux/mfd/twl.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/of.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 check initial cable status after probe */
+ struct delayed_work get_status_work;
+
+ /* used to set vbus, in atomic path */
+ struct work_struct set_vbus_work;
+
+ int irq1;
+ int irq2;
+ enum musb_vbus_id_status linkstat;
+ u8 asleep;
+ bool vbus_enable;
+};
+
+#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;
+ int ret;
+
+ 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, "usb");
+ 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 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 MUSB_VBUS_VALID:
+ ret = snprintf(buf, PAGE_SIZE, "vbus\n");
+ break;
+ case MUSB_ID_GROUND:
+ ret = snprintf(buf, PAGE_SIZE, "id\n");
+ break;
+ case 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_RO(vbus);
+
+static struct attribute *twl6030_attrs[] = {
+ &dev_attr_vbus.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(twl6030);
+
+static irqreturn_t twl6030_usb_irq(int irq, void *_twl)
+{
+ struct twl6030_usb *twl = _twl;
+ enum musb_vbus_id_status status = 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 = MUSB_VBUS_VALID;
+ twl->linkstat = status;
+ ret = musb_mailbox(status);
+ if (ret)
+ twl->linkstat = MUSB_UNKNOWN;
+ } else {
+ if (twl->linkstat != MUSB_UNKNOWN) {
+ status = MUSB_VBUS_OFF;
+ twl->linkstat = status;
+ ret = musb_mailbox(status);
+ if (ret)
+ twl->linkstat = MUSB_UNKNOWN;
+ 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 musb_vbus_id_status status = 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 = MUSB_ID_GROUND;
+ twl->linkstat = status;
+ ret = musb_mailbox(status);
+ if (ret)
+ twl->linkstat = MUSB_UNKNOWN;
+ } 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 void twl6030_status_work(struct work_struct *work)
+{
+ struct twl6030_usb *twl = container_of(work, struct twl6030_usb,
+ get_status_work.work);
+
+ twl6030_usb_irq(twl->irq2, twl);
+ twl6030_usbotg_irq(twl->irq1, twl);
+}
+
+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);
+
+ 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;
+
+ if (!np) {
+ dev_err(dev, "no DT info\n");
+ return -EINVAL;
+ }
+
+ 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 = MUSB_UNKNOWN;
+
+ if (twl->irq1 < 0)
+ return twl->irq1;
+ if (twl->irq2 < 0)
+ return twl->irq2;
+
+ 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;
+ }
+
+ /* 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);
+
+ INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work);
+ INIT_DELAYED_WORK(&twl->get_status_work, twl6030_status_work);
+
+ 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);
+ goto err_put_regulator;
+ }
+
+ 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);
+ goto err_free_irq1;
+ }
+
+ twl->asleep = 0;
+ twl6030_enable_irq(twl);
+ schedule_delayed_work(&twl->get_status_work, HZ);
+ dev_info(&pdev->dev, "Initialized TWL6030 USB module\n");
+
+ return 0;
+
+err_free_irq1:
+ free_irq(twl->irq1, twl);
+err_put_regulator:
+ regulator_put(twl->usb3v3);
+
+ return status;
+}
+
+static int twl6030_usb_remove(struct platform_device *pdev)
+{
+ struct twl6030_usb *twl = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&twl->get_status_work);
+ 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);
+ cancel_work_sync(&twl->set_vbus_work);
+
+ return 0;
+}
+
+static const struct of_device_id twl6030_usb_id_table[] = {
+ { .compatible = "ti,twl6030-usb" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, twl6030_usb_id_table);
+
+static struct platform_driver twl6030_usb_driver = {
+ .probe = twl6030_usb_probe,
+ .remove = twl6030_usb_remove,
+ .driver = {
+ .name = "twl6030_usb",
+ .of_match_table = of_match_ptr(twl6030_usb_id_table),
+ .dev_groups = twl6030_groups,
+ },
+};
+
+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 000000000..0f61e328e
--- /dev/null
+++ b/drivers/usb/phy/phy-ulpi-viewport.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2011 Google, Inc.
+ */
+
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/io.h>
+#include <linux/iopoll.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)
+{
+ u32 val;
+
+ return readl_poll_timeout_atomic(view, val, !(val & mask), 1, 2000);
+}
+
+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,
+};
+EXPORT_SYMBOL_GPL(ulpi_viewport_access_ops);
diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c
new file mode 100644
index 000000000..e683a37e3
--- /dev/null
+++ b/drivers/usb/phy/phy-ulpi.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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
+ */
+
+#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"),
+ ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"),
+ ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"),
+ ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"),
+};
+
+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->usb_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->usb_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);
+}
+
+static void otg_ulpi_init(struct usb_phy *phy, struct usb_otg *otg,
+ struct usb_phy_io_ops *ops,
+ unsigned int flags)
+{
+ phy->label = "ULPI";
+ phy->flags = flags;
+ phy->io_ops = ops;
+ phy->otg = otg;
+ phy->init = ulpi_init;
+
+ otg->usb_phy = phy;
+ otg->set_host = ulpi_set_host;
+ otg->set_vbus = ulpi_set_vbus;
+}
+
+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;
+ }
+
+ otg_ulpi_init(phy, otg, ops, flags);
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(otg_ulpi_create);
+
+struct usb_phy *
+devm_otg_ulpi_create(struct device *dev,
+ struct usb_phy_io_ops *ops,
+ unsigned int flags)
+{
+ struct usb_phy *phy;
+ struct usb_otg *otg;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return NULL;
+
+ otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
+ if (!otg) {
+ devm_kfree(dev, phy);
+ return NULL;
+ }
+
+ otg_ulpi_init(phy, otg, ops, flags);
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_otg_ulpi_create);
diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c
new file mode 100644
index 000000000..b47285f02
--- /dev/null
+++ b/drivers/usb/phy/phy.c
@@ -0,0 +1,725 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * phy.c -- USB phy handling
+ *
+ * Copyright (C) 2004-2013 Texas Instruments
+ */
+#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>
+
+/* Default current range by charger type. */
+#define DEFAULT_SDP_CUR_MIN 2
+#define DEFAULT_SDP_CUR_MAX 500
+#define DEFAULT_SDP_CUR_MIN_SS 150
+#define DEFAULT_SDP_CUR_MAX_SS 900
+#define DEFAULT_DCP_CUR_MIN 500
+#define DEFAULT_DCP_CUR_MAX 5000
+#define DEFAULT_CDP_CUR_MIN 1500
+#define DEFAULT_CDP_CUR_MAX 5000
+#define DEFAULT_ACA_CUR_MIN 1500
+#define DEFAULT_ACA_CUR_MAX 5000
+
+static LIST_HEAD(phy_list);
+static DEFINE_SPINLOCK(phy_lock);
+
+struct phy_devm {
+ struct usb_phy *phy;
+ struct notifier_block *nb;
+};
+
+static const char *const usb_chger_type[] = {
+ [UNKNOWN_TYPE] = "USB_CHARGER_UNKNOWN_TYPE",
+ [SDP_TYPE] = "USB_CHARGER_SDP_TYPE",
+ [CDP_TYPE] = "USB_CHARGER_CDP_TYPE",
+ [DCP_TYPE] = "USB_CHARGER_DCP_TYPE",
+ [ACA_TYPE] = "USB_CHARGER_ACA_TYPE",
+};
+
+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 *__of_usb_find_phy(struct device_node *node)
+{
+ struct usb_phy *phy;
+
+ if (!of_device_is_available(node))
+ return ERR_PTR(-ENODEV);
+
+ list_for_each_entry(phy, &phy_list, head) {
+ if (node != phy->dev->of_node)
+ continue;
+
+ return phy;
+ }
+
+ return ERR_PTR(-EPROBE_DEFER);
+}
+
+static void usb_phy_set_default_current(struct usb_phy *usb_phy)
+{
+ usb_phy->chg_cur.sdp_min = DEFAULT_SDP_CUR_MIN;
+ usb_phy->chg_cur.sdp_max = DEFAULT_SDP_CUR_MAX;
+ usb_phy->chg_cur.dcp_min = DEFAULT_DCP_CUR_MIN;
+ usb_phy->chg_cur.dcp_max = DEFAULT_DCP_CUR_MAX;
+ usb_phy->chg_cur.cdp_min = DEFAULT_CDP_CUR_MIN;
+ usb_phy->chg_cur.cdp_max = DEFAULT_CDP_CUR_MAX;
+ usb_phy->chg_cur.aca_min = DEFAULT_ACA_CUR_MIN;
+ usb_phy->chg_cur.aca_max = DEFAULT_ACA_CUR_MAX;
+}
+
+/**
+ * usb_phy_notify_charger_work - notify the USB charger state
+ * @work: the charger work to notify the USB charger state
+ *
+ * This work can be issued when USB charger state has been changed or
+ * USB charger current has been changed, then we can notify the current
+ * what can be drawn to power user and the charger state to userspace.
+ *
+ * If we get the charger type from extcon subsystem, we can notify the
+ * charger state to power user automatically by usb_phy_get_charger_type()
+ * issuing from extcon subsystem.
+ *
+ * If we get the charger type from ->charger_detect() instead of extcon
+ * subsystem, the usb phy driver should issue usb_phy_set_charger_state()
+ * to set charger state when the charger state has been changed.
+ */
+static void usb_phy_notify_charger_work(struct work_struct *work)
+{
+ struct usb_phy *usb_phy = container_of(work, struct usb_phy, chg_work);
+ char uchger_state[50] = { 0 };
+ char uchger_type[50] = { 0 };
+ char *envp[] = { uchger_state, uchger_type, NULL };
+ unsigned int min, max;
+
+ switch (usb_phy->chg_state) {
+ case USB_CHARGER_PRESENT:
+ usb_phy_get_charger_current(usb_phy, &min, &max);
+
+ atomic_notifier_call_chain(&usb_phy->notifier, max, usb_phy);
+ snprintf(uchger_state, ARRAY_SIZE(uchger_state),
+ "USB_CHARGER_STATE=%s", "USB_CHARGER_PRESENT");
+ break;
+ case USB_CHARGER_ABSENT:
+ usb_phy_set_default_current(usb_phy);
+
+ atomic_notifier_call_chain(&usb_phy->notifier, 0, usb_phy);
+ snprintf(uchger_state, ARRAY_SIZE(uchger_state),
+ "USB_CHARGER_STATE=%s", "USB_CHARGER_ABSENT");
+ break;
+ default:
+ dev_warn(usb_phy->dev, "Unknown USB charger state: %d\n",
+ usb_phy->chg_state);
+ return;
+ }
+
+ snprintf(uchger_type, ARRAY_SIZE(uchger_type),
+ "USB_CHARGER_TYPE=%s", usb_chger_type[usb_phy->chg_type]);
+ kobject_uevent_env(&usb_phy->dev->kobj, KOBJ_CHANGE, envp);
+}
+
+static void __usb_phy_get_charger_type(struct usb_phy *usb_phy)
+{
+ if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_SDP) > 0) {
+ usb_phy->chg_type = SDP_TYPE;
+ usb_phy->chg_state = USB_CHARGER_PRESENT;
+ } else if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_CDP) > 0) {
+ usb_phy->chg_type = CDP_TYPE;
+ usb_phy->chg_state = USB_CHARGER_PRESENT;
+ } else if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_DCP) > 0) {
+ usb_phy->chg_type = DCP_TYPE;
+ usb_phy->chg_state = USB_CHARGER_PRESENT;
+ } else if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_ACA) > 0) {
+ usb_phy->chg_type = ACA_TYPE;
+ usb_phy->chg_state = USB_CHARGER_PRESENT;
+ } else {
+ usb_phy->chg_type = UNKNOWN_TYPE;
+ usb_phy->chg_state = USB_CHARGER_ABSENT;
+ }
+
+ schedule_work(&usb_phy->chg_work);
+}
+
+/**
+ * usb_phy_get_charger_type - get charger type from extcon subsystem
+ * @nb: the notifier block to determine charger type
+ * @state: the cable state
+ * @data: private data
+ *
+ * Determin the charger type from extcon subsystem which also means the
+ * charger state has been chaned, then we should notify this event.
+ */
+static int usb_phy_get_charger_type(struct notifier_block *nb,
+ unsigned long state, void *data)
+{
+ struct usb_phy *usb_phy = container_of(nb, struct usb_phy, type_nb);
+
+ __usb_phy_get_charger_type(usb_phy);
+ return NOTIFY_OK;
+}
+
+/**
+ * usb_phy_set_charger_current - set the USB charger current
+ * @usb_phy: the USB phy to be used
+ * @mA: the current need to be set
+ *
+ * Usually we only change the charger default current when USB finished the
+ * enumeration as one SDP charger. As one SDP charger, usb_phy_set_power()
+ * will issue this function to change charger current when after setting USB
+ * configuration, or suspend/resume USB. For other type charger, we should
+ * use the default charger current and we do not suggest to issue this function
+ * to change the charger current.
+ *
+ * When USB charger current has been changed, we need to notify the power users.
+ */
+void usb_phy_set_charger_current(struct usb_phy *usb_phy, unsigned int mA)
+{
+ switch (usb_phy->chg_type) {
+ case SDP_TYPE:
+ if (usb_phy->chg_cur.sdp_max == mA)
+ return;
+
+ usb_phy->chg_cur.sdp_max = (mA > DEFAULT_SDP_CUR_MAX_SS) ?
+ DEFAULT_SDP_CUR_MAX_SS : mA;
+ break;
+ case DCP_TYPE:
+ if (usb_phy->chg_cur.dcp_max == mA)
+ return;
+
+ usb_phy->chg_cur.dcp_max = (mA > DEFAULT_DCP_CUR_MAX) ?
+ DEFAULT_DCP_CUR_MAX : mA;
+ break;
+ case CDP_TYPE:
+ if (usb_phy->chg_cur.cdp_max == mA)
+ return;
+
+ usb_phy->chg_cur.cdp_max = (mA > DEFAULT_CDP_CUR_MAX) ?
+ DEFAULT_CDP_CUR_MAX : mA;
+ break;
+ case ACA_TYPE:
+ if (usb_phy->chg_cur.aca_max == mA)
+ return;
+
+ usb_phy->chg_cur.aca_max = (mA > DEFAULT_ACA_CUR_MAX) ?
+ DEFAULT_ACA_CUR_MAX : mA;
+ break;
+ default:
+ return;
+ }
+
+ schedule_work(&usb_phy->chg_work);
+}
+EXPORT_SYMBOL_GPL(usb_phy_set_charger_current);
+
+/**
+ * usb_phy_get_charger_current - get the USB charger current
+ * @usb_phy: the USB phy to be used
+ * @min: the minimum current
+ * @max: the maximum current
+ *
+ * Usually we will notify the maximum current to power user, but for some
+ * special case, power user also need the minimum current value. Then the
+ * power user can issue this function to get the suitable current.
+ */
+void usb_phy_get_charger_current(struct usb_phy *usb_phy,
+ unsigned int *min, unsigned int *max)
+{
+ switch (usb_phy->chg_type) {
+ case SDP_TYPE:
+ *min = usb_phy->chg_cur.sdp_min;
+ *max = usb_phy->chg_cur.sdp_max;
+ break;
+ case DCP_TYPE:
+ *min = usb_phy->chg_cur.dcp_min;
+ *max = usb_phy->chg_cur.dcp_max;
+ break;
+ case CDP_TYPE:
+ *min = usb_phy->chg_cur.cdp_min;
+ *max = usb_phy->chg_cur.cdp_max;
+ break;
+ case ACA_TYPE:
+ *min = usb_phy->chg_cur.aca_min;
+ *max = usb_phy->chg_cur.aca_max;
+ break;
+ default:
+ *min = 0;
+ *max = 0;
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(usb_phy_get_charger_current);
+
+/**
+ * usb_phy_set_charger_state - set the USB charger state
+ * @usb_phy: the USB phy to be used
+ * @state: the new state need to be set for charger
+ *
+ * The usb phy driver can issue this function when the usb phy driver
+ * detected the charger state has been changed, in this case the charger
+ * type should be get from ->charger_detect().
+ */
+void usb_phy_set_charger_state(struct usb_phy *usb_phy,
+ enum usb_charger_state state)
+{
+ if (usb_phy->chg_state == state || !usb_phy->charger_detect)
+ return;
+
+ usb_phy->chg_state = state;
+ if (usb_phy->chg_state == USB_CHARGER_PRESENT)
+ usb_phy->chg_type = usb_phy->charger_detect(usb_phy);
+ else
+ usb_phy->chg_type = UNKNOWN_TYPE;
+
+ schedule_work(&usb_phy->chg_work);
+}
+EXPORT_SYMBOL_GPL(usb_phy_set_charger_state);
+
+static void devm_usb_phy_release(struct device *dev, void *res)
+{
+ struct usb_phy *phy = *(struct usb_phy **)res;
+
+ usb_put_phy(phy);
+}
+
+static void devm_usb_phy_release2(struct device *dev, void *_res)
+{
+ struct phy_devm *res = _res;
+
+ if (res->nb)
+ usb_unregister_notifier(res->phy, res->nb);
+ usb_put_phy(res->phy);
+}
+
+static int devm_usb_phy_match(struct device *dev, void *res, void *match_data)
+{
+ struct usb_phy **phy = res;
+
+ return *phy == match_data;
+}
+
+static void usb_charger_init(struct usb_phy *usb_phy)
+{
+ usb_phy->chg_type = UNKNOWN_TYPE;
+ usb_phy->chg_state = USB_CHARGER_DEFAULT;
+ usb_phy_set_default_current(usb_phy);
+ INIT_WORK(&usb_phy->chg_work, usb_phy_notify_charger_work);
+}
+
+static int usb_add_extcon(struct usb_phy *x)
+{
+ int ret;
+
+ if (of_property_read_bool(x->dev->of_node, "extcon")) {
+ x->edev = extcon_get_edev_by_phandle(x->dev, 0);
+ if (IS_ERR(x->edev))
+ return PTR_ERR(x->edev);
+
+ x->id_edev = extcon_get_edev_by_phandle(x->dev, 1);
+ if (IS_ERR(x->id_edev)) {
+ x->id_edev = NULL;
+ dev_info(x->dev, "No separate ID extcon device\n");
+ }
+
+ if (x->vbus_nb.notifier_call) {
+ ret = devm_extcon_register_notifier(x->dev, x->edev,
+ EXTCON_USB,
+ &x->vbus_nb);
+ if (ret < 0) {
+ dev_err(x->dev,
+ "register VBUS notifier failed\n");
+ return ret;
+ }
+ } else {
+ x->type_nb.notifier_call = usb_phy_get_charger_type;
+
+ ret = devm_extcon_register_notifier(x->dev, x->edev,
+ EXTCON_CHG_USB_SDP,
+ &x->type_nb);
+ if (ret) {
+ dev_err(x->dev,
+ "register extcon USB SDP failed.\n");
+ return ret;
+ }
+
+ ret = devm_extcon_register_notifier(x->dev, x->edev,
+ EXTCON_CHG_USB_CDP,
+ &x->type_nb);
+ if (ret) {
+ dev_err(x->dev,
+ "register extcon USB CDP failed.\n");
+ return ret;
+ }
+
+ ret = devm_extcon_register_notifier(x->dev, x->edev,
+ EXTCON_CHG_USB_DCP,
+ &x->type_nb);
+ if (ret) {
+ dev_err(x->dev,
+ "register extcon USB DCP failed.\n");
+ return ret;
+ }
+
+ ret = devm_extcon_register_notifier(x->dev, x->edev,
+ EXTCON_CHG_USB_ACA,
+ &x->type_nb);
+ if (ret) {
+ dev_err(x->dev,
+ "register extcon USB ACA failed.\n");
+ return ret;
+ }
+ }
+
+ if (x->id_nb.notifier_call) {
+ struct extcon_dev *id_ext;
+
+ if (x->id_edev)
+ id_ext = x->id_edev;
+ else
+ id_ext = x->edev;
+
+ ret = devm_extcon_register_notifier(x->dev, id_ext,
+ EXTCON_USB_HOST,
+ &x->id_nb);
+ if (ret < 0) {
+ dev_err(x->dev,
+ "register ID notifier failed\n");
+ return ret;
+ }
+ }
+ }
+
+ if (x->type_nb.notifier_call)
+ __usb_phy_get_charger_type(x);
+
+ return 0;
+}
+
+/**
+ * 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 ERR_PTR(-ENOMEM);
+
+ 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_debug("PHY: unable to find transceiver of type %s\n",
+ usb_phy_type_string(type));
+ if (!IS_ERR(phy))
+ phy = ERR_PTR(-ENODEV);
+
+ 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_node - find the USB PHY by device_node
+ * @dev: device that requests this phy
+ * @node: the device_node for the phy device.
+ * @nb: a notifier_block to register with the phy.
+ *
+ * Returns the phy driver associated with the given device_node,
+ * after getting a refcount to it, -ENODEV if there is no such phy or
+ * -EPROBE_DEFER if 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 peripheral drivers for devices related to a phy,
+ * such as a charger.
+ */
+struct usb_phy *devm_usb_get_phy_by_node(struct device *dev,
+ struct device_node *node,
+ struct notifier_block *nb)
+{
+ struct usb_phy *phy = ERR_PTR(-ENOMEM);
+ struct phy_devm *ptr;
+ unsigned long flags;
+
+ ptr = devres_alloc(devm_usb_phy_release2, 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)) {
+ devres_free(ptr);
+ goto err1;
+ }
+
+ if (!try_module_get(phy->dev->driver->owner)) {
+ phy = ERR_PTR(-ENODEV);
+ devres_free(ptr);
+ goto err1;
+ }
+ if (nb)
+ usb_register_notifier(phy, nb);
+ ptr->phy = phy;
+ ptr->nb = nb;
+ devres_add(dev, ptr);
+
+ get_device(phy->dev);
+
+err1:
+ spin_unlock_irqrestore(&phy_lock, flags);
+
+err0:
+
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_node);
+
+/**
+ * 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 device_node *node;
+ struct usb_phy *phy;
+
+ 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 %pOF node\n", phandle,
+ dev->of_node);
+ return ERR_PTR(-ENODEV);
+ }
+ phy = devm_usb_get_phy_by_node(dev, node, NULL);
+ of_node_put(node);
+ return phy;
+}
+EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle);
+
+/**
+ * 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;
+ }
+
+ usb_charger_init(x);
+ ret = usb_add_extcon(x);
+ if (ret)
+ return ret;
+
+ ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
+
+ 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)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!x->dev) {
+ dev_err(x->dev, "no device provided for PHY\n");
+ return -EINVAL;
+ }
+
+ usb_charger_init(x);
+ ret = usb_add_extcon(x);
+ if (ret)
+ return ret;
+
+ ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
+
+ spin_lock_irqsave(&phy_lock, flags);
+ 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;
+
+ spin_lock_irqsave(&phy_lock, flags);
+ if (x)
+ list_del(&x->head);
+ spin_unlock_irqrestore(&phy_lock, flags);
+}
+EXPORT_SYMBOL_GPL(usb_remove_phy);
+
+/**
+ * usb_phy_set_event - set event to phy event
+ * @x: the phy returned by usb_get_phy();
+ * @event: event to set
+ *
+ * This sets event to phy event
+ */
+void usb_phy_set_event(struct usb_phy *x, unsigned long event)
+{
+ x->last_event = event;
+}
+EXPORT_SYMBOL_GPL(usb_phy_set_event);