diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
| commit | 4352b8aee98005853aa63f57d6377282de17a33f (patch) | |
| tree | 27614d7a9ef05c32d089004e104364ce42324837 /drivers/usb/phy | |
| download | linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip | |
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
Diffstat (limited to 'drivers/usb/phy')
| -rw-r--r-- | drivers/usb/phy/Kconfig | 163 | ||||
| -rw-r--r-- | drivers/usb/phy/Makefile | 24 | ||||
| -rw-r--r-- | drivers/usb/phy/of.c | 43 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-ab8500-usb.c | 1011 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-am335x-control.c | 193 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-am335x-control.h | 24 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-am335x.c | 146 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-fsl-usb.c | 1017 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-fsl-usb.h | 378 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-generic.c | 348 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-generic.h | 27 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-gpio-vbus-usb.c | 395 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-isp1301.c | 163 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-keystone.c | 118 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-mv-usb.h | 160 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-mxs-usb.c | 977 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-omap-otg.c | 150 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-tahvo.c | 439 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-tegra-usb.c | 1655 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-twl6030-usb.c | 457 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-ulpi-viewport.c | 67 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-ulpi.c | 280 | ||||
| -rw-r--r-- | drivers/usb/phy/phy.c | 748 |
23 files changed, 8983 insertions, 0 deletions
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig new file mode 100644 index 000000000..b7acf3966 --- /dev/null +++ b/drivers/usb/phy/Kconfig @@ -0,0 +1,163 @@ +# 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 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' + depends on !USB_CONN_GPIO + 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. This driver is for devices that do + NOT support role switch. OTG devices that can do role switch + (master/peripheral) shall use the USB based connection + detection driver USB_CONN_GPIO. + +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_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). + +endmenu diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile new file mode 100644 index 000000000..ceae46c53 --- /dev/null +++ b/drivers/usb/phy/Makefile @@ -0,0 +1,24 @@ +# 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_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_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 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..6a98aeeea --- /dev/null +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -0,0 +1,1011 @@ +// 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, ®); + 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, ®); + 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 void 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); +} + +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..ada508be0 --- /dev/null +++ b/drivers/usb/phy/phy-am335x-control.c @@ -0,0 +1,193 @@ +// 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_DESCRIPTION("AM335x USB PHY 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..ca9353dad --- /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 void 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); +} + +#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_DESCRIPTION("AM335x USB 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..35d79f11b --- /dev/null +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -0,0 +1,1017 @@ +// 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/string_choices.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 <linux/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", str_on_off(on)); + 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_obj(struct fsl_otg); + if (!fsl_otg_tc) + return -ENOMEM; + + fsl_otg_tc->phy.otg = kzalloc_obj(struct usb_otg); + 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 void fsl_otg_remove(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); + + disable_delayed_work_sync(&fsl_otg_dev->otg_event); + 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); +} + +struct platform_driver fsl_otg_driver = { + .probe = fsl_otg_probe, + .remove = fsl_otg_remove, + .driver = { + .name = driver_name, + }, +}; + +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..95bfe7f1b --- /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_obj(struct fsl_otg_timer); + 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..de26b3023 --- /dev/null +++ b/drivers/usb/phy/phy-generic.c @@ -0,0 +1,348 @@ +// 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/property.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); + int ret = 0; + + if (suspend) { + clk_disable_unprepare(nop->clk); + if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev)) + ret = regulator_disable(nop->vcc); + } else { + if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev)) + ret = regulator_enable(nop->vcc); + clk_prepare_enable(nop->clk); + } + + return ret; +} + +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); + usleep_range(10000, 30000); +} + +/* interface to regulator framework */ +static int nop_set_vbus(struct usb_otg *otg, bool enable) +{ + int ret = 0; + struct usb_phy_generic *nop = dev_get_drvdata(otg->usb_phy->dev); + + if (!nop->vbus_draw) + return 0; + + if (enable && !nop->vbus_draw_enabled) { + ret = regulator_enable(nop->vbus_draw); + if (ret) + nop->vbus_draw_enabled = false; + else + nop->vbus_draw_enabled = true; + + } else if (!enable && nop->vbus_draw_enabled) { + ret = regulator_disable(nop->vbus_draw); + nop->vbus_draw_enabled = false; + } + return ret; +} + + +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; + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } else { + 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"); + } + + 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); + + 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; + + device_property_read_u32(dev, "clock-frequency", &clk_rate); + 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) + return dev_err_probe(dev, err, + "Error requesting RESET or VBUS GPIO\n"); + 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_optional(dev, "main_clk"); + if (IS_ERR(nop->clk)) + return dev_err_probe(dev, PTR_ERR(nop->clk), + "Can't get phy clock\n"); + + if (clk_rate) { + err = clk_set_rate(nop->clk, clk_rate); + if (err) + return dev_err_probe(dev, err, + "Error setting clock rate\n"); + } + + nop->vcc = devm_regulator_get_optional(dev, "vcc"); + if (IS_ERR(nop->vcc) && PTR_ERR(nop->vcc) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(nop->vcc), + "could not get vcc regulator\n"); + + 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; + nop->phy.otg->set_vbus = nop_set_vbus; + + 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(dev, + gpiod_to_irq(nop->gpiod_vbus), + NULL, nop_gpio_vbus_thread, + VBUS_IRQ_FLAGS, "vbus_detect", + nop); + if (err) + return dev_err_probe(dev, err, "can't request irq %i\n", + gpiod_to_irq(nop->gpiod_vbus)); + + 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) + return dev_err_probe(dev, err, "can't register transceiver\n"); + + platform_set_drvdata(pdev, nop); + + device_set_wakeup_capable(dev, + device_property_read_bool(dev, "wakeup-source")); + + return 0; +} + +static void usb_phy_generic_remove(struct platform_device *pdev) +{ + struct usb_phy_generic *nop = platform_get_drvdata(pdev); + + usb_remove_phy(&nop->phy); + + if (nop->vbus_draw && nop->vbus_draw_enabled) + regulator_disable(nop->vbus_draw); +} + +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..ce09e789a --- /dev/null +++ b/drivers/usb/phy/phy-gpio-vbus-usb.c @@ -0,0 +1,395 @@ +// 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 void 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); +} + +#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"); + +/* + * NOTE: this driver matches against "gpio-usb-b-connector" for + * devices that do NOT support role switch. + */ +static const struct of_device_id gpio_vbus_of_match[] = { + { + .compatible = "gpio-usb-b-connector", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, gpio_vbus_of_match); + +static struct platform_driver gpio_vbus_driver = { + .driver = { + .name = "gpio-vbus", +#ifdef CONFIG_PM + .pm = &gpio_vbus_dev_pm_ops, +#endif + .of_match_table = gpio_vbus_of_match, + }, + .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.c b/drivers/usb/phy/phy-isp1301.c new file mode 100644 index 000000000..73cc70e95 --- /dev/null +++ b/drivers/usb/phy/phy-isp1301.c @@ -0,0 +1,163 @@ +// 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[] = { + { .name = "isp1301" }, + { } +}; +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) +{ + 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 void isp1301_remove(struct i2c_client *client) +{ + struct isp1301 *isp = i2c_get_clientdata(client); + + usb_remove_phy(&isp->phy); + isp1301_i2c_client = NULL; +} + +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); + +struct i2c_client *isp1301_get_client(struct device_node *node) +{ + struct i2c_client *client; + + /* reference of ISP1301 I2C node via DT */ + client = of_find_i2c_device_by_node(node); + if (client) + return client; + + /* non-DT: only one ISP1301 chip supported */ + if (isp1301_i2c_client) { + get_device(&isp1301_i2c_client->dev); + return isp1301_i2c_client; + } + + return NULL; +} +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-keystone.c b/drivers/usb/phy/phy-keystone.c new file mode 100644 index 000000000..51155c551 --- /dev/null +++ b/drivers/usb/phy/phy-keystone.c @@ -0,0 +1,118 @@ +// 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 void 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); +} + +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.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..7069dd3f4 --- /dev/null +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -0,0 +1,977 @@ +// 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.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/iopoll.h> +#include <linux/regulator/consumer.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_REG_1P1_SET 0x114 +#define ANADIG_REG_1P1_CLR 0x118 + +#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) + +/* System Integration Module (SIM) Registers */ +#define SIM_GPR1 0x30 + +#define USB_PHY_VLLS_WAKEUP_EN BIT(0) + +#define BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG BIT(18) +#define BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP BIT(19) + +#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 35 +#define MXS_PHY_TX_CAL45_MAX 54 +#define MXS_PHY_TX_D_CAL_MIN 79 +#define MXS_PHY_TX_D_CAL_MAX 119 + +/* + * At imx6q/6sl/6sx, the PHY2's clock is controlled by hardware directly, + * eg, according to PHY's suspend status. In these PHYs, we only need to + * open the clock at the initialization and close it at its shutdown routine. + * These PHYs can send resume signal without software interfere if not + * gate clock. + */ +#define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4) + +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 | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +static const struct mxs_phy_data imx6sl_phy_data = { + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_NEED_IP_FIX | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +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 | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +static const struct mxs_phy_data imx6ul_phy_data = { + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +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; + struct regmap *regmap_sim; + int port_id; + u32 tx_reg_set; + u32 tx_reg_mask; + struct regulator *phy_3p0; +}; + +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; +} + +static inline bool is_imx6ul_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6ul_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; + + if (mxs_phy->phy_3p0) { + ret = regulator_enable(mxs_phy->phy_3p0); + if (ret) { + dev_err(mxs_phy->phy.dev, + "Failed to enable 3p0 regulator, ret=%d\n", + ret); + return ret; + } + } + + /* 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; + enum usb_phy_events last_event = mxs_phy->phy.last_event; + + /* 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)) + || (last_event == USB_EVENT_VBUS))) + __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); + + if (mxs_phy->phy_3p0) + regulator_disable(mxs_phy->phy_3p0); + + 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); + if (!(mxs_phy->port_id == 1 && + (mxs_phy->data->flags & + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK))) + clk_disable_unprepare(mxs_phy->clk); + } else { + mxs_phy_clock_switch_delay(); + if (!(mxs_phy->port_id == 1 && + (mxs_phy->data->flags & + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK))) { + 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; + struct device_node *np = pdev->dev.of_node; + u32 val; + + 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)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), + "can't get the clock\n"); + + 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_property_present(np, "fsl,anatop")) { + 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); + } + } + + /* Currently, only imx7ulp has SIM module */ + if (of_get_property(np, "nxp,sim", NULL)) { + mxs_phy->regmap_sim = syscon_regmap_lookup_by_phandle + (np, "nxp,sim"); + if (IS_ERR(mxs_phy->regmap_sim)) { + dev_dbg(&pdev->dev, + "failed to find regmap for sim\n"); + return PTR_ERR(mxs_phy->regmap_sim); + } + } + + /* 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_device_get_match_data(&pdev->dev); + + mxs_phy->phy_3p0 = devm_regulator_get(&pdev->dev, "phy-3p0"); + if (PTR_ERR(mxs_phy->phy_3p0) == -ENODEV) + /* not exist */ + mxs_phy->phy_3p0 = NULL; + else if (IS_ERR(mxs_phy->phy_3p0)) + return dev_err_probe(&pdev->dev, PTR_ERR(mxs_phy->phy_3p0), + "Getting regulator error\n"); + + if (mxs_phy->phy_3p0) + regulator_set_voltage(mxs_phy->phy_3p0, 3200000, 3200000); + + platform_set_drvdata(pdev, mxs_phy); + + device_set_wakeup_capable(&pdev->dev, true); + + return usb_add_phy_dev(&mxs_phy->phy); +} + +static void mxs_phy_remove(struct platform_device *pdev) +{ + struct mxs_phy *mxs_phy = platform_get_drvdata(pdev); + + usb_remove_phy(&mxs_phy->phy); +} + +#ifdef CONFIG_PM_SLEEP +static void mxs_phy_wakeup_enable(struct mxs_phy *mxs_phy, bool on) +{ + u32 mask = USB_PHY_VLLS_WAKEUP_EN; + + /* If the SoCs don't have SIM, quit */ + if (!mxs_phy->regmap_sim) + return; + + if (on) { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, mask); + udelay(500); + } else { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, 0); + } +} + +static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on) +{ + unsigned int reg; + u32 value; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return; + + if (is_imx6q_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; + regmap_write(mxs_phy->regmap_anatop, reg, + BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG); + } else if (is_imx6sl_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; + regmap_write(mxs_phy->regmap_anatop, + reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL); + } else if (is_imx6ul_phy(mxs_phy)) { + reg = on ? ANADIG_REG_1P1_SET : ANADIG_REG_1P1_CLR; + value = BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG | + BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP; + if (mxs_phy_get_vbus_status(mxs_phy) && on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + else if (!on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + } +} + +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); + mxs_phy_wakeup_enable(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); + mxs_phy_wakeup_enable(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..6e6ef8c0b --- /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 (IS_ERR(extcon)) + return PTR_ERR(extcon); + + 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..88607d0ed --- /dev/null +++ b/drivers/usb/phy/phy-tahvo.c @@ -0,0 +1,439 @@ +// 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/string_choices.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", str_on_off(tu->vbus_state)); +} +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); + + 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); + + 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 void 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); +} + +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..00443a7be --- /dev/null +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -0,0 +1,1655 @@ +// 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_platform.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 USB_TXFILLTUNING 0x154 +#define USB_FIFO_TXFILL_THRES(x) (((x) & 0x1f) << 16) +#define USB_FIFO_TXFILL_MASK 0x1f0000 + +#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) +#define TEGRA_USB_PORTSC1_WKOC BIT(22) +#define TEGRA_USB_PORTSC1_WKDS BIT(21) +#define TEGRA_USB_PORTSC1_WKCN BIT(20) + +/* HOSTPC1 PTS/PHCD bits, Tegra30 and above */ +#define TEGRA30_USB_PORTSC1 0x174 +#define TEGRA_USB_HOSTPC1_DEVLC 0x1b4 +#define TEGRA_USB_HOSTPC1_DEVLC_PTS(x) (((x) & 0x7) << 29) +#define TEGRA_USB_HOSTPC1_DEVLC_PHCD BIT(22) +#define TEGRA_USB_HOSTPC1_DEVLC_PTS_HSIC 4 + +/* 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_RESUME_EN BIT(2) +#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 UTMIP_PHY_ENABLE BIT(12) +#define ULPI_PHY_ENABLE BIT(13) +#define USB_SUSP_SET BIT(14) +#define UHSIC_RESET BIT(14) +#define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) +#define UHSIC_PHY_ENABLE BIT(19) + +#define USB_PHY_VBUS_SENSORS 0x404 +#define B_SESS_VLD_WAKEUP_EN BIT(14) +#define A_SESS_VLD_WAKEUP_EN BIT(22) +#define A_VBUS_VLD_WAKEUP_EN BIT(30) + +#define USB_PHY_VBUS_WAKEUP_ID 0x408 +#define ID_INT_EN BIT(0) +#define ID_CHG_DET BIT(1) +#define VBUS_WAKEUP_INT_EN BIT(8) +#define VBUS_WAKEUP_CHG_DET BIT(9) +#define VBUS_WAKEUP_STS BIT(10) +#define VBUS_WAKEUP_WAKEUP_EN BIT(30) + +#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) + +/* + * Tegra20 has no UTMIP registers on PHY2 and UHSIC registers start from 0x800 + * just where UTMIP registers should have been. This is the case only with Tegra20 + * Tegra30+ have UTMIP registers at 0x800 and UHSIC registers are shifted by 0x400 + * to 0xc00, but register layout is preserved. + */ +#define UHSIC_PLL_CFG1 0x804 +#define UHSIC_XTAL_FREQ_COUNT(x) (((x) & 0xfff) << 0) +#define UHSIC_PLLU_ENABLE_DLY_COUNT(x) (((x) & 0x1f) << 14) + +#define UHSIC_HSRX_CFG0 0x808 +#define UHSIC_ELASTIC_UNDERRUN_LIMIT(x) (((x) & 0x1f) << 2) +#define UHSIC_ELASTIC_OVERRUN_LIMIT(x) (((x) & 0x1f) << 8) +#define UHSIC_IDLE_WAIT(x) (((x) & 0x1f) << 13) + +#define UHSIC_HSRX_CFG1 0x80c +#define UHSIC_HS_SYNC_START_DLY(x) (((x) & 0x1f) << 1) + +#define UHSIC_TX_CFG0 0x810 +#define UHSIC_HS_READY_WAIT_FOR_VALID BIT(9) + +#define UHSIC_MISC_CFG0 0x814 +#define UHSIC_SUSPEND_EXIT_ON_EDGE BIT(7) +#define UHSIC_DETECT_SHORT_CONNECT BIT(8) +#define UHSIC_FORCE_XCVR_MODE BIT(15) +#define UHSIC_DISABLE_BUSRESET BIT(20) + +#define UHSIC_MISC_CFG1 0x818 +#define UHSIC_PLLU_STABLE_COUNT(x) (((x) & 0xfff) << 2) + +#define UHSIC_PADS_CFG0 0x81c +#define UHSIC_TX_RTUNEN 0xf000 +#define UHSIC_TX_RTUNE(x) (((x) & 0xf) << 12) + +#define UHSIC_PADS_CFG1 0x820 +#define UHSIC_PD_BG BIT(2) +#define UHSIC_PD_TX BIT(3) +#define UHSIC_PD_TRK BIT(4) +#define UHSIC_PD_RX BIT(5) +#define UHSIC_PD_ZI BIT(6) +#define UHSIC_RX_SEL BIT(7) +#define UHSIC_RPD_DATA BIT(9) +#define UHSIC_RPD_STROBE BIT(10) +#define UHSIC_RPU_DATA BIT(11) +#define UHSIC_RPU_STROBE BIT(12) + +#define UHSIC_CMD_CFG0 0x824 +#define UHSIC_PRETEND_CONNECT_DETECT BIT(5) + +#define UHSIC_STAT_CFG0 0x828 +#define UHSIC_CONNECT_DETECT BIT(0) + +/* 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) + +#define PMC_USB_AO 0xf0 +#define VBUS_WAKEUP_PD_P0 BIT(2) +#define ID_PD_P0 BIT(3) + +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 utmi_xtal_freq_count; + u16 hsic_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, + .utmi_xtal_freq_count = 0x76, + .hsic_xtal_freq_count = 0x1CA, + .debounce = 0x7530, + }, + { + .freq = 13000000, + .enable_delay = 0x02, + .stable_count = 0x33, + .active_delay = 0x05, + .utmi_xtal_freq_count = 0x7F, + .hsic_xtal_freq_count = 0x1F0, + .debounce = 0x7EF4, + }, + { + .freq = 19200000, + .enable_delay = 0x03, + .stable_count = 0x4B, + .active_delay = 0x06, + .utmi_xtal_freq_count = 0xBB, + .hsic_xtal_freq_count = 0x2DD, + .debounce = 0xBB80, + }, + { + .freq = 26000000, + .enable_delay = 0x04, + .stable_count = 0x66, + .active_delay = 0x09, + .utmi_xtal_freq_count = 0xFE, + .hsic_xtal_freq_count = 0x3E0, + .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); + } + + if (phy->pad_wakeup) { + phy->pad_wakeup = false; + utmip_pad_count--; + } + + 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; + } + + /* + * In accordance to TRM, OTG and Bias pad circuits could be turned off + * to save power if wake is enabled, but the VBUS-change detection + * method is board-specific and these circuits may need to be enabled + * to generate wakeup event, hence we will just keep them both enabled. + */ + if (phy->wakeup_enabled) { + phy->pad_wakeup = true; + utmip_pad_count++; + } + + 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->utmi_xtal_freq_count) | + UTMIP_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay); + writel_relaxed(val, base + UTMIP_PLL_CFG1); + } + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_WAKE_ON_RESUME_EN; + writel_relaxed(val, base + USB_SUSP_CTRL); + + if (phy->mode != USB_DR_MODE_HOST) { + 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 + USB_PHY_VBUS_WAKEUP_ID); + val &= ~VBUS_WAKEUP_WAKEUP_EN; + val &= ~(ID_CHG_DET | VBUS_WAKEUP_CHG_DET); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + val = readl_relaxed(base + USB_PHY_VBUS_SENSORS); + val &= ~(A_VBUS_VLD_WAKEUP_EN | A_SESS_VLD_WAKEUP_EN); + val &= ~(B_SESS_VLD_WAKEUP_EN); + writel_relaxed(val, base + USB_PHY_VBUS_SENSORS); + + 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; + + /* + * Give hardware time to settle down after VBUS disconnection, + * otherwise PHY will immediately wake up from suspend. + */ + if (phy->wakeup_enabled && phy->mode != USB_DR_MODE_HOST) + readl_relaxed_poll_timeout(base + USB_PHY_VBUS_WAKEUP_ID, + val, !(val & VBUS_WAKEUP_STS), + 5000, 100000); + + utmi_phy_clk_disable(phy); + + /* PHY won't resume if reset is asserted */ + if (!phy->wakeup_enabled) { + 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); + + if (!phy->wakeup_enabled) { + 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); + + if (phy->wakeup_enabled) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0); + val |= USB_WAKEUP_DEBOUNCE_COUNT(5); + val |= USB_WAKE_ON_RESUME_EN; + writel_relaxed(val, base + USB_SUSP_CTRL); + + /* + * Ask VBUS sensor to generate wake event once cable is + * connected. + */ + if (phy->mode != USB_DR_MODE_HOST) { + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val |= VBUS_WAKEUP_WAKEUP_EN; + val &= ~(ID_CHG_DET | VBUS_WAKEUP_CHG_DET); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + val = readl_relaxed(base + USB_PHY_VBUS_SENSORS); + val |= A_VBUS_VLD_WAKEUP_EN; + writel_relaxed(val, base + USB_PHY_VBUS_SENSORS); + } + } + + return utmip_pad_power_off(phy); +} + +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); + + /* + * Wakeup currently unimplemented for ULPI, thus PHY needs to be + * force-resumed. + */ + if (WARN_ON_ONCE(phy->wakeup_enabled)) { + ulpi_phy_power_on(phy); + return -EOPNOTSUPP; + } + + return 0; +} + +static u32 tegra_hsic_readl(struct tegra_usb_phy *phy, u32 reg) +{ + void __iomem *base = phy->regs; + u32 shift = phy->soc_config->uhsic_registers_offset; + + return readl_relaxed(base + shift + reg); +} + +static void tegra_hsic_writel(struct tegra_usb_phy *phy, u32 reg, u32 value) +{ + void __iomem *base = phy->regs; + u32 shift = phy->soc_config->uhsic_registers_offset; + + writel_relaxed(value, base + shift + reg); +} + +static int uhsic_phy_power_on(struct tegra_usb_phy *phy) +{ + struct tegra_utmip_config *config = phy->config; + void __iomem *base = phy->regs; + u32 val; + int err = 0; + + val = tegra_hsic_readl(phy, UHSIC_PADS_CFG1); + val &= ~(UHSIC_PD_BG | UHSIC_PD_TX | UHSIC_PD_TRK | UHSIC_PD_RX | + UHSIC_PD_ZI | UHSIC_RPD_DATA | UHSIC_RPD_STROBE); + val |= UHSIC_RX_SEL; + tegra_hsic_writel(phy, UHSIC_PADS_CFG1, val); + + udelay(2); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UHSIC_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + udelay(30); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UHSIC_PHY_ENABLE; + writel_relaxed(val, base + USB_SUSP_CTRL); + + val = tegra_hsic_readl(phy, UHSIC_HSRX_CFG0); + val &= ~(UHSIC_IDLE_WAIT(~0) | + UHSIC_ELASTIC_UNDERRUN_LIMIT(~0) | + UHSIC_ELASTIC_OVERRUN_LIMIT(~0)); + val |= UHSIC_IDLE_WAIT(config->idle_wait_delay) | + UHSIC_ELASTIC_UNDERRUN_LIMIT(config->elastic_limit) | + UHSIC_ELASTIC_OVERRUN_LIMIT(config->elastic_limit); + tegra_hsic_writel(phy, UHSIC_HSRX_CFG0, val); + + val = tegra_hsic_readl(phy, UHSIC_HSRX_CFG1); + val &= ~UHSIC_HS_SYNC_START_DLY(~0); + val |= UHSIC_HS_SYNC_START_DLY(config->hssync_start_delay); + tegra_hsic_writel(phy, UHSIC_HSRX_CFG1, val); + + val = tegra_hsic_readl(phy, UHSIC_MISC_CFG0); + val |= UHSIC_SUSPEND_EXIT_ON_EDGE; + tegra_hsic_writel(phy, UHSIC_MISC_CFG0, val); + + val = tegra_hsic_readl(phy, UHSIC_MISC_CFG1); + val &= ~UHSIC_PLLU_STABLE_COUNT(~0); + val |= UHSIC_PLLU_STABLE_COUNT(phy->freq->stable_count); + tegra_hsic_writel(phy, UHSIC_MISC_CFG1, val); + + val = tegra_hsic_readl(phy, UHSIC_PLL_CFG1); + val &= ~(UHSIC_XTAL_FREQ_COUNT(~0) | + UHSIC_PLLU_ENABLE_DLY_COUNT(~0)); + val |= UHSIC_XTAL_FREQ_COUNT(phy->freq->hsic_xtal_freq_count) | + UHSIC_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay); + tegra_hsic_writel(phy, UHSIC_PLL_CFG1, val); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~UHSIC_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + udelay(2); + + 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); + } + + set_pts(phy, phy->soc_config->uhsic_pts_value); + + val = readl_relaxed(base + USB_TXFILLTUNING); + if ((val & USB_FIFO_TXFILL_MASK) != USB_FIFO_TXFILL_THRES(0x10)) { + val = USB_FIFO_TXFILL_THRES(0x10); + writel_relaxed(val, base + USB_TXFILLTUNING); + } + + val = readl_relaxed(base + phy->soc_config->portsc1_offset); + val &= ~(TEGRA_USB_PORTSC1_WKOC | TEGRA_USB_PORTSC1_WKDS | + TEGRA_USB_PORTSC1_WKCN); + writel_relaxed(val, base + phy->soc_config->portsc1_offset); + + val = tegra_hsic_readl(phy, UHSIC_PADS_CFG0); + val &= ~UHSIC_TX_RTUNEN; + val |= UHSIC_TX_RTUNE(phy->soc_config->uhsic_tx_rtune); + tegra_hsic_writel(phy, UHSIC_PADS_CFG0, val); + + err = utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, + USB_PHY_CLK_VALID); + + if (err) + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on enable (HSIC)\n"); + + return err; +} + +static int uhsic_phy_power_off(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + u32 val; + + set_phcd(phy, true); + + val = tegra_hsic_readl(phy, UHSIC_PADS_CFG1); + val |= (UHSIC_PD_BG | UHSIC_PD_TX | UHSIC_PD_TRK | UHSIC_PD_RX | + UHSIC_PD_ZI | UHSIC_RPD_DATA | UHSIC_RPD_STROBE); + tegra_hsic_writel(phy, UHSIC_PADS_CFG1, val); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UHSIC_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + udelay(30); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~UHSIC_PHY_ENABLE; + writel_relaxed(val, base + USB_SUSP_CTRL); + + return 0; +} + +static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) +{ + int err = 0; + + if (phy->powered_on) + return 0; + + switch (phy->phy_type) { + case USBPHY_INTERFACE_MODE_UTMI: + err = utmi_phy_power_on(phy); + break; + + case USBPHY_INTERFACE_MODE_ULPI: + err = ulpi_phy_power_on(phy); + break; + + case USBPHY_INTERFACE_MODE_HSIC: + err = uhsic_phy_power_on(phy); + break; + + default: + break; + } + + if (err) + return err; + + phy->powered_on = true; + + /* Let PHY settle down */ + usleep_range(2000, 2500); + + return 0; +} + +static int tegra_usb_phy_power_off(struct tegra_usb_phy *phy) +{ + int err = 0; + + if (!phy->powered_on) + return 0; + + switch (phy->phy_type) { + case USBPHY_INTERFACE_MODE_UTMI: + err = utmi_phy_power_off(phy); + break; + + case USBPHY_INTERFACE_MODE_ULPI: + err = ulpi_phy_power_off(phy); + break; + + case USBPHY_INTERFACE_MODE_HSIC: + err = uhsic_phy_power_off(phy); + break; + + default: + break; + } + + 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; + + usb_phy_set_wakeup(u_phy, false); + tegra_usb_phy_power_off(phy); + + if (phy->phy_type == USBPHY_INTERFACE_MODE_UTMI) + utmip_pad_close(phy); + + regulator_disable(phy->vbus); + clk_disable_unprepare(phy->pll_u); + + phy->freq = NULL; +} + +static irqreturn_t tegra_usb_phy_isr(int irq, void *data) +{ + u32 val, int_mask = ID_CHG_DET | VBUS_WAKEUP_CHG_DET; + struct tegra_usb_phy *phy = data; + void __iomem *base = phy->regs; + + /* + * The PHY interrupt also wakes the USB controller driver since + * interrupt is shared. We don't do anything in the PHY driver, + * so just clear the interrupt. + */ + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + return val & int_mask ? IRQ_HANDLED : IRQ_NONE; +} + +static int tegra_usb_phy_set_wakeup(struct usb_phy *u_phy, bool enable) +{ + struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); + void __iomem *base = phy->regs; + int ret = 0; + u32 val; + + if (phy->wakeup_enabled && phy->mode != USB_DR_MODE_HOST && + phy->irq > 0) { + disable_irq(phy->irq); + + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val &= ~(ID_INT_EN | VBUS_WAKEUP_INT_EN); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + enable_irq(phy->irq); + + free_irq(phy->irq, phy); + + phy->wakeup_enabled = false; + } + + if (enable && phy->mode != USB_DR_MODE_HOST && phy->irq > 0) { + ret = request_irq(phy->irq, tegra_usb_phy_isr, IRQF_SHARED, + dev_name(phy->u_phy.dev), phy); + if (!ret) { + disable_irq(phy->irq); + + /* + * USB clock will be resumed once wake event will be + * generated. The ID-change event requires to have + * interrupts enabled, otherwise it won't be generated. + */ + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val |= ID_INT_EN | VBUS_WAKEUP_INT_EN; + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + enable_irq(phy->irq); + } else { + dev_err(phy->u_phy.dev, + "Failed to request interrupt: %d", ret); + enable = false; + } + } + + phy->wakeup_enabled = enable; + + return ret; +} + +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); + int ret; + + if (WARN_ON(!phy->freq)) + return -EINVAL; + + /* + * PHY is sharing IRQ with the CI driver, hence here we either + * disable interrupt for both PHY and CI or for CI only. The + * interrupt needs to be disabled while hardware is reprogrammed + * because interrupt touches the programmed registers, and thus, + * there could be a race condition. + */ + if (phy->irq > 0) + disable_irq(phy->irq); + + if (suspend) + ret = tegra_usb_phy_power_off(phy); + else + ret = tegra_usb_phy_power_on(phy); + + if (phy->irq > 0) + enable_irq(phy->irq); + + return ret; +} + +static int tegra_usb_phy_configure_pmc(struct tegra_usb_phy *phy) +{ + int err, val = 0; + + /* older device-trees don't have PMC regmap */ + if (!phy->pmc_regmap) + return 0; + + /* + * Tegra20 has a different layout of PMC USB register bits and AO is + * enabled by default after system reset on Tegra20, so assume nothing + * to do on Tegra20. + */ + if (!phy->soc_config->requires_pmc_ao_power_up) + return 0; + + /* enable VBUS wake-up detector */ + if (phy->mode != USB_DR_MODE_HOST) + val |= VBUS_WAKEUP_PD_P0 << phy->instance * 4; + + /* enable ID-pin ACC detector for OTG mode switching */ + if (phy->mode == USB_DR_MODE_OTG) + val |= ID_PD_P0 << phy->instance * 4; + + /* disable detectors to reset them */ + err = regmap_set_bits(phy->pmc_regmap, PMC_USB_AO, val); + if (err) { + dev_err(phy->u_phy.dev, "Failed to disable PMC AO: %d\n", err); + return err; + } + + usleep_range(10, 100); + + /* enable detectors */ + err = regmap_clear_bits(phy->pmc_regmap, PMC_USB_AO, val); + if (err) { + dev_err(phy->u_phy.dev, "Failed to enable PMC AO: %d\n", err); + return err; + } + + /* detectors starts to work after 10ms */ + usleep_range(10000, 15000); + + return 0; +} + +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->phy_type == USBPHY_INTERFACE_MODE_UTMI) { + err = utmip_pad_open(phy); + if (err) + goto disable_vbus; + } + + err = tegra_usb_phy_configure_pmc(phy); + if (err) + goto close_phy; + + err = tegra_usb_phy_power_on(phy); + if (err) + goto close_phy; + + return 0; + +close_phy: + if (phy->phy_type == USBPHY_INTERFACE_MODE_UTMI) + utmip_pad_close(phy); + +disable_vbus: + regulator_disable(phy->vbus); + +disable_clk: + clk_disable_unprepare(phy->pll_u); + + phy->freq = NULL; + + return err; +} + +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; + + 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 void tegra_usb_phy_put_pmc_device(void *dev) +{ + put_device(dev); +} + +static int tegra_usb_phy_parse_pmc(struct device *dev, + struct tegra_usb_phy *phy) +{ + struct platform_device *pmc_pdev; + struct of_phandle_args args; + int err; + + err = of_parse_phandle_with_fixed_args(dev->of_node, "nvidia,pmc", + 1, 0, &args); + if (err) { + if (err != -ENOENT) + return err; + + dev_warn_once(dev, "nvidia,pmc is missing, please update your device-tree\n"); + return 0; + } + + pmc_pdev = of_find_device_by_node(args.np); + of_node_put(args.np); + if (!pmc_pdev) + return -ENODEV; + + err = devm_add_action_or_reset(dev, tegra_usb_phy_put_pmc_device, + &pmc_pdev->dev); + if (err) + return err; + + if (!platform_get_drvdata(pmc_pdev)) + return -EPROBE_DEFER; + + phy->pmc_regmap = dev_get_regmap(&pmc_pdev->dev, "usb_sleepwalk"); + if (!phy->pmc_regmap) + return -EINVAL; + + phy->instance = args.args[0]; + + 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, + .requires_pmc_ao_power_up = false, + .uhsic_registers_offset = 0, + .uhsic_tx_rtune = 0, /* 40 ohm */ + .uhsic_pts_value = 0, /* UTMI */ + .portsc1_offset = TEGRA_USB_PORTSC1, +}; + +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, + .requires_pmc_ao_power_up = true, + .uhsic_registers_offset = 0x400, + .uhsic_tx_rtune = 8, /* 50 ohm */ + .uhsic_pts_value = TEGRA_USB_HOSTPC1_DEVLC_PTS_HSIC, + .portsc1_offset = TEGRA30_USB_PORTSC1, +}; + +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; + 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); + tegra_phy->irq = platform_get_irq_optional(pdev, 0); + + 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_property_present(np, "dr_mode")) + 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; + } + + err = tegra_usb_phy_parse_pmc(&pdev->dev, tegra_phy); + if (err) { + dev_err_probe(&pdev->dev, err, "Failed to get PMC regmap\n"); + return err; + } + + tegra_phy->phy_type = of_usb_get_phy_mode(np); + switch (tegra_phy->phy_type) { + case USBPHY_INTERFACE_MODE_UTMI: + case USBPHY_INTERFACE_MODE_HSIC: + 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->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(&pdev->dev, "nvidia,phy-reset", + GPIOD_OUT_HIGH); + err = PTR_ERR_OR_ZERO(gpiod); + if (err) { + dev_err(&pdev->dev, + "Request failed for reset GPIO: %d\n", err); + return err; + } + + err = gpiod_set_consumer_name(gpiod, "ulpi_phy_reset_b"); + if (err) { + dev_err(&pdev->dev, + "Failed to set up reset GPIO name: %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", + tegra_phy->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_wakeup = tegra_usb_phy_set_wakeup; + 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 void 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); +} + +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..8c09db750 --- /dev/null +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -0,0 +1,457 @@ +// 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 = sysfs_emit(buf, "vbus\n"); + break; + case MUSB_ID_GROUND: + ret = sysfs_emit(buf, "id\n"); + break; + case MUSB_VBUS_OFF: + ret = sysfs_emit(buf, "none\n"); + break; + default: + ret = sysfs_emit(buf, "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) +{ + struct twl6030_usb *twl; + int status, err, ret; + 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 void 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); +} + +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..4df63e67b --- /dev/null +++ b/drivers/usb/phy/phy-ulpi.c @@ -0,0 +1,280 @@ +// 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 * +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..5a9b9353f --- /dev/null +++ b/drivers/usb/phy/phy.c @@ -0,0 +1,748 @@ +// 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 const char *const usb_chger_state[] = { + [USB_CHARGER_DEFAULT] = "USB_CHARGER_DEFAULT", + [USB_CHARGER_PRESENT] = "USB_CHARGER_PRESENT", + [USB_CHARGER_ABSENT] = "USB_CHARGER_ABSENT", +}; + +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 struct usb_phy *__device_to_usb_phy(const struct device *dev) +{ + struct usb_phy *usb_phy; + + list_for_each_entry(usb_phy, &phy_list, head) { + if (usb_phy->dev == dev) + return usb_phy; + } + + return NULL; +} + +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); + 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); + break; + case USB_CHARGER_ABSENT: + usb_phy_set_default_current(usb_phy); + + atomic_notifier_call_chain(&usb_phy->notifier, 0, usb_phy); + break; + default: + dev_warn(usb_phy->dev, "Unknown USB charger state: %d\n", + usb_phy->chg_state); + return; + } + + kobject_uevent(&usb_phy->dev->kobj, KOBJ_CHANGE); +} + +static int usb_phy_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct usb_phy *usb_phy; + char uchger_state[50] = { 0 }; + char uchger_type[50] = { 0 }; + unsigned long flags; + + spin_lock_irqsave(&phy_lock, flags); + usb_phy = __device_to_usb_phy(dev); + spin_unlock_irqrestore(&phy_lock, flags); + + if (!usb_phy) + return -ENODEV; + + snprintf(uchger_state, ARRAY_SIZE(uchger_state), + "USB_CHARGER_STATE=%s", usb_chger_state[usb_phy->chg_state]); + + snprintf(uchger_type, ARRAY_SIZE(uchger_type), + "USB_CHARGER_TYPE=%s", usb_chger_type[usb_phy->chg_type]); + + if (add_uevent_var(env, uchger_state)) + return -ENOMEM; + + if (add_uevent_var(env, uchger_type)) + return -ENOMEM; + + return 0; +} + +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 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_present(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); + +/** + * 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; + } + + INIT_LIST_HEAD(&x->head); + + 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); + +static const struct device_type usb_phy_dev_type = { + .name = "usb_phy", + .uevent = usb_phy_uevent, +}; + +/** + * 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; + } + + INIT_LIST_HEAD(&x->head); + + usb_charger_init(x); + ret = usb_add_extcon(x); + if (ret) + return ret; + + x->dev->type = &usb_phy_dev_type; + + 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); |
