diff options
Diffstat (limited to 'drivers/usb/phy/phy-tegra-usb.c')
| -rw-r--r-- | drivers/usb/phy/phy-tegra-usb.c | 1529 |
1 files changed, 1020 insertions, 509 deletions
diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index cec0855ed248..fb9031628d39 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2010 Google, Inc. * Copyright (C) 2013 NVIDIA Corporation @@ -6,59 +7,71 @@ * Erik Gilling <konkers@google.com> * Benoit Goby <benoit@android.com> * Venu Byravarasu <vbyravarasu@nvidia.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ -#include <linux/resource.h> #include <linux/delay.h> -#include <linux/slab.h> #include <linux/err.h> #include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/iopoll.h> #include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/io.h> -#include <linux/gpio.h> #include <linux/of.h> -#include <linux/of_gpio.h> -#include <linux/usb/otg.h> -#include <linux/usb/ulpi.h> -#include <asm/mach-types.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 ULPI_VIEWPORT 0x170 -#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) -/* PORTSC registers */ -#define TEGRA_USB_PORTSC1 0x184 -#define TEGRA_USB_PORTSC1_PTS(x) (((x) & 0x3) << 30) -#define TEGRA_USB_PORTSC1_PHCD (1 << 23) +/* HOSTPC1 PTS/PHCD bits, Tegra30 and above */ +#define TEGRA_USB_HOSTPC1_DEVLC 0x1b4 +#define TEGRA_USB_HOSTPC1_DEVLC_PTS(x) (((x) & 0x7) << 29) +#define TEGRA_USB_HOSTPC1_DEVLC_PHCD BIT(22) /* Bits of PORTSC1, which will get cleared by writing 1 into them */ #define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) -#define USB_SUSP_CTRL 0x400 -#define USB_WAKE_ON_CNNT_EN_DEV (1 << 3) -#define USB_WAKE_ON_DISCON_EN_DEV (1 << 4) -#define USB_SUSP_CLR (1 << 5) -#define USB_PHY_CLK_VALID (1 << 7) -#define UTMIP_RESET (1 << 11) -#define UHSIC_RESET (1 << 11) -#define UTMIP_PHY_ENABLE (1 << 12) -#define ULPI_PHY_ENABLE (1 << 13) -#define USB_SUSP_SET (1 << 14) -#define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) - -#define USB1_LEGACY_CTRL 0x410 -#define USB1_NO_LEGACY_MODE (1 << 0) +#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 UHSIC_RESET BIT(11) +#define UTMIP_PHY_ENABLE BIT(12) +#define ULPI_PHY_ENABLE BIT(13) +#define USB_SUSP_SET BIT(14) +#define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) + +#define 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 \ @@ -66,82 +79,98 @@ #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 (1 << 10) -#define ULPI_CLKOUT_PINMUX_BYP (1 << 11) +#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 (1 << 0) -#define ULPI_DATA_TRIMMER_SEL(x) (((x) & 0x7) << 1) -#define ULPI_STPDIRNXT_TRIMMER_LOAD (1 << 16) -#define ULPI_STPDIRNXT_TRIMMER_SEL(x) (((x) & 0x7) << 17) -#define ULPI_DIR_TRIMMER_LOAD (1 << 24) -#define ULPI_DIR_TRIMMER_SEL(x) (((x) & 0x7) << 25) +#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_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_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 (1 << 14) -#define UTMIP_FORCE_PD2_POWERDOWN (1 << 16) -#define UTMIP_FORCE_PDZI_POWERDOWN (1 << 18) -#define UTMIP_XCVR_HSSLEW_MSB(x) (((x) & 0x7f) << 25) - -#define UTMIP_BIAS_CFG0 0x80c -#define UTMIP_OTGPD (1 << 11) -#define UTMIP_BIASPD (1 << 10) - -#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 (1 << 19) -#define UTMIP_HS_DISCON_DISABLE (1 << 8) - -#define UTMIP_MISC_CFG0 0x824 -#define UTMIP_DPDM_OBSERVE (1 << 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 (1 << 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 (1 << 0) - -#define UTMIP_SPARE_CFG0 0x834 -#define FUSE_SETUP_SEL (1 << 3) - -#define UTMIP_XCVR_CFG1 0x838 -#define UTMIP_FORCE_PDDISC_POWERDOWN (1 << 0) -#define UTMIP_FORCE_PDCHRP_POWERDOWN (1 << 2) -#define UTMIP_FORCE_PDDR_POWERDOWN (1 << 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) +#define UTMIP_FORCE_PD_POWERDOWN BIT(14) +#define UTMIP_FORCE_PD2_POWERDOWN BIT(16) +#define UTMIP_FORCE_PDZI_POWERDOWN BIT(18) +#define UTMIP_XCVR_LSBIAS_SEL BIT(21) +#define UTMIP_XCVR_HSSLEW(x) (((x) & 0x3) << 4) +#define UTMIP_XCVR_HSSLEW_MSB(x) ((((x) & 0x1fc) >> 2) << 25) + +#define UTMIP_BIAS_CFG0 0x80c +#define UTMIP_OTGPD BIT(11) +#define UTMIP_BIASPD BIT(10) +#define UTMIP_HSSQUELCH_LEVEL(x) (((x) & 0x3) << 0) +#define UTMIP_HSDISCON_LEVEL(x) (((x) & 0x3) << 2) +#define UTMIP_HSDISCON_LEVEL_MSB(x) ((((x) & 0x4) >> 2) << 24) + +#define UTMIP_HSRX_CFG0 0x810 +#define UTMIP_ELASTIC_LIMIT(x) (((x) & 0x1f) << 10) +#define UTMIP_IDLE_WAIT(x) (((x) & 0x1f) << 15) + +#define UTMIP_HSRX_CFG1 0x814 +#define UTMIP_HS_SYNC_START_DLY(x) (((x) & 0x1f) << 1) + +#define UTMIP_TX_CFG0 0x820 +#define UTMIP_FS_PREABMLE_J BIT(19) +#define UTMIP_HS_DISCON_DISABLE BIT(8) + +#define UTMIP_MISC_CFG0 0x824 +#define UTMIP_DPDM_OBSERVE BIT(26) +#define UTMIP_DPDM_OBSERVE_SEL(x) (((x) & 0xf) << 27) +#define UTMIP_DPDM_OBSERVE_SEL_FS_J UTMIP_DPDM_OBSERVE_SEL(0xf) +#define UTMIP_DPDM_OBSERVE_SEL_FS_K UTMIP_DPDM_OBSERVE_SEL(0xe) +#define UTMIP_DPDM_OBSERVE_SEL_FS_SE1 UTMIP_DPDM_OBSERVE_SEL(0xd) +#define UTMIP_DPDM_OBSERVE_SEL_FS_SE0 UTMIP_DPDM_OBSERVE_SEL(0xc) +#define UTMIP_SUSPEND_EXIT_ON_EDGE BIT(22) + +#define UTMIP_MISC_CFG1 0x828 +#define UTMIP_PLL_ACTIVE_DLY_COUNT(x) (((x) & 0x1f) << 18) +#define UTMIP_PLLU_STABLE_COUNT(x) (((x) & 0xfff) << 6) + +#define UTMIP_DEBOUNCE_CFG0 0x82c +#define UTMIP_BIAS_DEBOUNCE_A(x) (((x) & 0xffff) << 0) + +#define UTMIP_BAT_CHRG_CFG0 0x830 +#define UTMIP_PD_CHRG BIT(0) + +#define UTMIP_SPARE_CFG0 0x834 +#define FUSE_SETUP_SEL BIT(3) + +#define UTMIP_XCVR_CFG1 0x838 +#define UTMIP_FORCE_PDDISC_POWERDOWN BIT(0) +#define UTMIP_FORCE_PDCHRP_POWERDOWN BIT(2) +#define UTMIP_FORCE_PDDR_POWERDOWN BIT(4) +#define UTMIP_XCVR_TERM_RANGE_ADJ(x) (((x) & 0xf) << 18) + +#define UTMIP_BIAS_CFG1 0x83c +#define UTMIP_BIAS_PDTRK_COUNT(x) (((x) & 0x1f) << 3) + +/* For Tegra30 and above only, the address is different in Tegra20 */ +#define USB_USBMODE 0x1f8 +#define USB_USBMODE_MASK (3 << 0) +#define USB_USBMODE_HOST (3 << 0) +#define USB_USBMODE_DEVICE (2 << 0) + +#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 int utmip_pad_count; +static unsigned int utmip_pad_count; struct tegra_xtal_freq { - int freq; + unsigned int freq; u8 enable_delay; u8 stable_count; u8 active_delay; @@ -184,281 +213,433 @@ static const struct tegra_xtal_freq tegra_freq_table[] = { }, }; -static struct tegra_utmip_config utmip_default[] = { - [0] = { - .hssync_start_delay = 9, - .idle_wait_delay = 17, - .elastic_limit = 16, - .term_range_adj = 6, - .xcvr_setup = 9, - .xcvr_lsfslew = 1, - .xcvr_lsrslew = 1, - }, - [2] = { - .hssync_start_delay = 9, - .idle_wait_delay = 17, - .elastic_limit = 16, - .term_range_adj = 6, - .xcvr_setup = 9, - .xcvr_lsfslew = 2, - .xcvr_lsrslew = 2, - }, -}; +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; - unsigned long val; + u32 val; - val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; - val &= ~TEGRA_USB_PORTSC1_PTS(3); - val |= TEGRA_USB_PORTSC1_PTS(pts_val & 3); - writel(val, base + TEGRA_USB_PORTSC1); + 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; - unsigned long val; + u32 val; - val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; - if (enable) - val |= TEGRA_USB_PORTSC1_PHCD; - else - val &= ~TEGRA_USB_PORTSC1_PHCD; - writel(val, base + TEGRA_USB_PORTSC1); + 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) { - phy->pad_clk = devm_clk_get(phy->dev, "utmi-pads"); - if (IS_ERR(phy->pad_clk)) { - pr_err("%s: can't get utmip pad clock\n", __func__); - return PTR_ERR(phy->pad_clk); + 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; } - return 0; + 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 void utmip_pad_power_on(struct tegra_usb_phy *phy) +static int utmip_pad_close(struct tegra_usb_phy *phy) { - unsigned long val, flags; + 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; - clk_prepare_enable(phy->pad_clk); + err = clk_prepare_enable(phy->pad_clk); + if (err) + return err; - spin_lock_irqsave(&utmip_pad_lock, flags); + spin_lock(&utmip_pad_lock); if (utmip_pad_count++ == 0) { - val = readl(base + UTMIP_BIAS_CFG0); + val = readl_relaxed(base + UTMIP_BIAS_CFG0); val &= ~(UTMIP_OTGPD | UTMIP_BIASPD); - writel(val, base + UTMIP_BIAS_CFG0); + + 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_irqrestore(&utmip_pad_lock, flags); + spin_unlock(&utmip_pad_lock); clk_disable_unprepare(phy->pad_clk); + + return 0; } static int utmip_pad_power_off(struct tegra_usb_phy *phy) { - unsigned long val, flags; 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) { - pr_err("%s: utmip pad already powered off\n", __func__); - return -EINVAL; + dev_err(phy->u_phy.dev, "UTMIP pad already powered off\n"); + ret = -EINVAL; + goto ulock; } - clk_prepare_enable(phy->pad_clk); - - spin_lock_irqsave(&utmip_pad_lock, flags); + /* + * 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(base + UTMIP_BIAS_CFG0); + val = readl_relaxed(base + UTMIP_BIAS_CFG0); val |= UTMIP_OTGPD | UTMIP_BIASPD; - writel(val, base + UTMIP_BIAS_CFG0); + writel_relaxed(val, base + UTMIP_BIAS_CFG0); } - - spin_unlock_irqrestore(&utmip_pad_lock, flags); +ulock: + spin_unlock(&utmip_pad_lock); clk_disable_unprepare(phy->pad_clk); - return 0; + return ret; } static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result) { - unsigned long timeout = 2000; - do { - if ((readl(reg) & mask) == result) - return 0; - udelay(1); - timeout--; - } while (timeout); - return -1; + 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) { - unsigned long val; 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(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val |= USB_SUSP_SET; - writel(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); - udelay(10); + usleep_range(10, 100); - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val &= ~USB_SUSP_SET; - writel(val, base + USB_SUSP_CTRL); - } else + 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) < 0) - pr_err("%s: timeout waiting for phy to stabilize\n", __func__); + 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) { - unsigned long val; 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(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val |= USB_SUSP_CLR; - writel(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); - udelay(10); + usleep_range(10, 100); - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val &= ~USB_SUSP_CLR; - writel(val, base + USB_SUSP_CTRL); - } else + 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)) - pr_err("%s: timeout waiting for phy to stabilize\n", __func__); + 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) { - unsigned long val; - void __iomem *base = phy->regs; struct tegra_utmip_config *config = phy->config; + void __iomem *base = phy->regs; + u32 val; + int err; - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val |= UTMIP_RESET; - writel(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); if (phy->is_legacy_phy) { - val = readl(base + USB1_LEGACY_CTRL); + val = readl_relaxed(base + USB1_LEGACY_CTRL); val |= USB1_NO_LEGACY_MODE; - writel(val, base + USB1_LEGACY_CTRL); + writel_relaxed(val, base + USB1_LEGACY_CTRL); } - val = readl(base + UTMIP_TX_CFG0); - val &= ~UTMIP_FS_PREABMLE_J; - writel(val, base + UTMIP_TX_CFG0); + val = readl_relaxed(base + UTMIP_TX_CFG0); + val |= UTMIP_FS_PREABMLE_J; + writel_relaxed(val, base + UTMIP_TX_CFG0); - val = readl(base + UTMIP_HSRX_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(val, base + UTMIP_HSRX_CFG0); + writel_relaxed(val, base + UTMIP_HSRX_CFG0); - val = readl(base + UTMIP_HSRX_CFG1); + 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(val, base + UTMIP_HSRX_CFG1); + writel_relaxed(val, base + UTMIP_HSRX_CFG1); - val = readl(base + UTMIP_DEBOUNCE_CFG0); + val = readl_relaxed(base + UTMIP_DEBOUNCE_CFG0); val &= ~UTMIP_BIAS_DEBOUNCE_A(~0); val |= UTMIP_BIAS_DEBOUNCE_A(phy->freq->debounce); - writel(val, base + UTMIP_DEBOUNCE_CFG0); + writel_relaxed(val, base + UTMIP_DEBOUNCE_CFG0); - val = readl(base + UTMIP_MISC_CFG0); + val = readl_relaxed(base + UTMIP_MISC_CFG0); val &= ~UTMIP_SUSPEND_EXIT_ON_EDGE; - writel(val, base + UTMIP_MISC_CFG0); - - val = readl(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(val, base + UTMIP_MISC_CFG1); - - val = readl(base + UTMIP_PLL_CFG1); - val &= ~(UTMIP_XTAL_FREQ_COUNT(~0) | UTMIP_PLLU_ENABLE_DLY_COUNT(~0)); - val |= UTMIP_XTAL_FREQ_COUNT(phy->freq->xtal_freq_count) | - UTMIP_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay); - writel(val, base + UTMIP_PLL_CFG1); - - if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) { - val = readl(base + USB_SUSP_CTRL); + writel_relaxed(val, base + UTMIP_MISC_CFG0); + + if (!phy->soc_config->utmi_pll_config_in_car_module) { + val = readl_relaxed(base + UTMIP_MISC_CFG1); + val &= ~(UTMIP_PLL_ACTIVE_DLY_COUNT(~0) | + UTMIP_PLLU_STABLE_COUNT(~0)); + val |= UTMIP_PLL_ACTIVE_DLY_COUNT(phy->freq->active_delay) | + UTMIP_PLLU_STABLE_COUNT(phy->freq->stable_count); + writel_relaxed(val, base + UTMIP_MISC_CFG1); + + val = readl_relaxed(base + UTMIP_PLL_CFG1); + val &= ~(UTMIP_XTAL_FREQ_COUNT(~0) | + UTMIP_PLLU_ENABLE_DLY_COUNT(~0)); + val |= UTMIP_XTAL_FREQ_COUNT(phy->freq->xtal_freq_count) | + UTMIP_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay); + writel_relaxed(val, base + UTMIP_PLL_CFG1); + } + + 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(val, base + USB_SUSP_CTRL); + 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); } - utmip_pad_power_on(phy); + err = utmip_pad_power_on(phy); + if (err) + return err; - val = readl(base + UTMIP_XCVR_CFG0); + val = readl_relaxed(base + UTMIP_XCVR_CFG0); val &= ~(UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | - UTMIP_FORCE_PDZI_POWERDOWN | UTMIP_XCVR_SETUP(~0) | - UTMIP_XCVR_LSFSLEW(~0) | UTMIP_XCVR_LSRSLEW(~0) | - UTMIP_XCVR_HSSLEW_MSB(~0)); - val |= UTMIP_XCVR_SETUP(config->xcvr_setup); + 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); - writel(val, base + UTMIP_XCVR_CFG0); - val = readl(base + UTMIP_XCVR_CFG1); + 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(val, base + UTMIP_XCVR_CFG1); - - val = readl(base + UTMIP_BAT_CHRG_CFG0); - val &= ~UTMIP_PD_CHRG; - writel(val, base + UTMIP_BAT_CHRG_CFG0); + writel_relaxed(val, base + UTMIP_XCVR_CFG1); - val = readl(base + UTMIP_BIAS_CFG1); + val = readl_relaxed(base + UTMIP_BIAS_CFG1); val &= ~UTMIP_BIAS_PDTRK_COUNT(~0); val |= UTMIP_BIAS_PDTRK_COUNT(0x5); - writel(val, base + UTMIP_BIAS_CFG1); + writel_relaxed(val, base + UTMIP_BIAS_CFG1); - if (phy->is_legacy_phy) { - val = readl(base + UTMIP_SPARE_CFG0); - if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) - val &= ~FUSE_SETUP_SEL; - else - val |= FUSE_SETUP_SEL; - writel(val, base + UTMIP_SPARE_CFG0); - } else { - val = readl(base + USB_SUSP_CTRL); + 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(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); } - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val &= ~UTMIP_RESET; - writel(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); if (phy->is_legacy_phy) { - val = readl(base + USB1_LEGACY_CTRL); + val = readl_relaxed(base + USB1_LEGACY_CTRL); val &= ~USB1_VBUS_SENSE_CTL_MASK; val |= USB1_VBUS_SENSE_CTL_A_SESS_VLD; - writel(val, base + USB1_LEGACY_CTRL); + writel_relaxed(val, base + USB1_LEGACY_CTRL); - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val &= ~USB_SUSP_SET; - writel(val, base + USB_SUSP_CTRL); + 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); @@ -467,254 +648,372 @@ static int utmi_phy_power_on(struct tegra_usb_phy *phy) static int utmi_phy_power_off(struct tegra_usb_phy *phy) { - unsigned long val; 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); - if (phy->mode == TEGRA_USB_PHY_MODE_DEVICE) { - val = readl(base + USB_SUSP_CTRL); - val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0); - val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKEUP_DEBOUNCE_COUNT(5); - writel(val, base + USB_SUSP_CTRL); + /* 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(base + USB_SUSP_CTRL); - val |= UTMIP_RESET; - writel(val, base + USB_SUSP_CTRL); - - val = readl(base + UTMIP_BAT_CHRG_CFG0); + val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0); val |= UTMIP_PD_CHRG; - writel(val, base + UTMIP_BAT_CHRG_CFG0); + writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0); - val = readl(base + UTMIP_XCVR_CFG0); - val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | - UTMIP_FORCE_PDZI_POWERDOWN; - writel(val, base + UTMIP_XCVR_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(base + UTMIP_XCVR_CFG1); + val = readl_relaxed(base + UTMIP_XCVR_CFG1); val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN | UTMIP_FORCE_PDDR_POWERDOWN; - writel(val, base + UTMIP_XCVR_CFG1); + writel_relaxed(val, base + UTMIP_XCVR_CFG1); - return utmip_pad_power_off(phy); -} - -static void utmi_phy_preresume(struct tegra_usb_phy *phy) -{ - unsigned long val; - void __iomem *base = phy->regs; - - val = readl(base + UTMIP_TX_CFG0); - val |= UTMIP_HS_DISCON_DISABLE; - writel(val, base + UTMIP_TX_CFG0); -} - -static void utmi_phy_postresume(struct tegra_usb_phy *phy) -{ - unsigned long val; - void __iomem *base = phy->regs; + 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); + } + } - val = readl(base + UTMIP_TX_CFG0); - val &= ~UTMIP_HS_DISCON_DISABLE; - writel(val, base + UTMIP_TX_CFG0); + return utmip_pad_power_off(phy); } -static void utmi_phy_restore_start(struct tegra_usb_phy *phy, - enum tegra_usb_phy_port_speed port_speed) +static int ulpi_phy_power_on(struct tegra_usb_phy *phy) { - unsigned long val; void __iomem *base = phy->regs; + u32 val; + int err; - val = readl(base + UTMIP_MISC_CFG0); - val &= ~UTMIP_DPDM_OBSERVE_SEL(~0); - if (port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW) - val |= UTMIP_DPDM_OBSERVE_SEL_FS_K; - else - val |= UTMIP_DPDM_OBSERVE_SEL_FS_J; - writel(val, base + UTMIP_MISC_CFG0); - udelay(1); - - val = readl(base + UTMIP_MISC_CFG0); - val |= UTMIP_DPDM_OBSERVE; - writel(val, base + UTMIP_MISC_CFG0); - udelay(10); -} - -static void utmi_phy_restore_end(struct tegra_usb_phy *phy) -{ - unsigned long val; - void __iomem *base = phy->regs; + gpiod_set_value_cansleep(phy->reset_gpio, 1); - val = readl(base + UTMIP_MISC_CFG0); - val &= ~UTMIP_DPDM_OBSERVE; - writel(val, base + UTMIP_MISC_CFG0); - udelay(10); -} + err = clk_prepare_enable(phy->clk); + if (err) + return err; -static int ulpi_phy_power_on(struct tegra_usb_phy *phy) -{ - int ret; - unsigned long val; - void __iomem *base = phy->regs; + usleep_range(5000, 6000); - ret = gpio_direction_output(phy->reset_gpio, 0); - if (ret < 0) { - dev_err(phy->dev, "gpio %d not set to 0\n", phy->reset_gpio); - return ret; - } - msleep(5); - ret = gpio_direction_output(phy->reset_gpio, 1); - if (ret < 0) { - dev_err(phy->dev, "gpio %d not set to 1\n", phy->reset_gpio); - return ret; - } + gpiod_set_value_cansleep(phy->reset_gpio, 0); - clk_prepare_enable(phy->clk); - msleep(1); + usleep_range(1000, 2000); - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val |= UHSIC_RESET; - writel(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); - val = readl(base + ULPI_TIMING_CTRL_0); + val = readl_relaxed(base + ULPI_TIMING_CTRL_0); val |= ULPI_OUTPUT_PINMUX_BYP | ULPI_CLKOUT_PINMUX_BYP; - writel(val, base + ULPI_TIMING_CTRL_0); + writel_relaxed(val, base + ULPI_TIMING_CTRL_0); - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val |= ULPI_PHY_ENABLE; - writel(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); val = 0; - writel(val, base + ULPI_TIMING_CTRL_1); + 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(val, base + ULPI_TIMING_CTRL_1); - udelay(10); + 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(val, base + ULPI_TIMING_CTRL_1); + writel_relaxed(val, base + ULPI_TIMING_CTRL_1); /* Fix VbusInvalid due to floating VBUS */ - ret = usb_phy_io_write(phy->ulpi, 0x40, 0x08); - if (ret) { - pr_err("%s: ulpi write failed\n", __func__); - return ret; + 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; } - ret = usb_phy_io_write(phy->ulpi, 0x80, 0x0B); - if (ret) { - pr_err("%s: ulpi write failed\n", __func__); - return ret; + 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(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val |= USB_SUSP_CLR; - writel(val, base + USB_SUSP_CTRL); - udelay(100); + writel_relaxed(val, base + USB_SUSP_CTRL); + usleep_range(100, 1000); - val = readl(base + USB_SUSP_CTRL); + val = readl_relaxed(base + USB_SUSP_CTRL); val &= ~USB_SUSP_CLR; - writel(val, base + USB_SUSP_CTRL); + writel_relaxed(val, base + USB_SUSP_CTRL); return 0; -} -static int ulpi_phy_power_off(struct tegra_usb_phy *phy) -{ - clk_disable(phy->clk); - return gpio_direction_output(phy->reset_gpio, 0); +disable_clk: + clk_disable_unprepare(phy->clk); + + return err; } -static void tegra_usb_phy_close(struct usb_phy *x) +static int ulpi_phy_power_off(struct tegra_usb_phy *phy) { - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_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; + } - clk_disable_unprepare(phy->pll_u); + return 0; } static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) { + int err; + + if (phy->powered_on) + return 0; + if (phy->is_ulpi_phy) - return ulpi_phy_power_on(phy); + err = ulpi_phy_power_on(phy); else - return utmi_phy_power_on(phy); + err = utmi_phy_power_on(phy); + 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; + + if (!phy->powered_on) + return 0; + if (phy->is_ulpi_phy) - return ulpi_phy_power_off(phy); + err = ulpi_phy_power_off(phy); else - return utmi_phy_power_off(phy); + err = utmi_phy_power_off(phy); + if (err) + return err; + + phy->powered_on = false; + + return 0; } -static int tegra_usb_phy_suspend(struct usb_phy *x, int suspend) +static void tegra_usb_phy_shutdown(struct usb_phy *u_phy) { - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); - if (suspend) - return tegra_usb_phy_power_off(phy); - else - return tegra_usb_phy_power_on(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->is_ulpi_phy) + utmip_pad_close(phy); + + regulator_disable(phy->vbus); + clk_disable_unprepare(phy->pll_u); + + phy->freq = NULL; } -static int ulpi_open(struct tegra_usb_phy *phy) +static irqreturn_t tegra_usb_phy_isr(int irq, void *data) { - int err; + 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); - phy->clk = devm_clk_get(phy->dev, "ulpi-link"); - if (IS_ERR(phy->clk)) { - pr_err("%s: can't get ulpi clock\n", __func__); - return PTR_ERR(phy->clk); + enable_irq(phy->irq); + + free_irq(phy->irq, phy); + + phy->wakeup_enabled = false; } - err = devm_gpio_request(phy->dev, phy->reset_gpio, "ulpi_phy_reset_b"); - if (err < 0) { - dev_err(phy->dev, "request failed for gpio: %d\n", - phy->reset_gpio); - return err; + 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; + } } - err = gpio_direction_output(phy->reset_gpio, 0); - if (err < 0) { - dev_err(phy->dev, "gpio %d direction not set to output\n", - phy->reset_gpio); + 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; } - phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); - if (!phy->ulpi) { - dev_err(phy->dev, "otg_ulpi_create returned NULL\n"); - err = -ENOMEM; + 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; } - phy->ulpi->io_priv = phy->regs + ULPI_VIEWPORT; + /* detectors starts to work after 10ms */ + usleep_range(10000, 15000); + return 0; } -static int tegra_usb_phy_init(struct tegra_usb_phy *phy) +static int tegra_usb_phy_init(struct usb_phy *u_phy) { + struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); unsigned long parent_rate; - int i; + unsigned int i; int err; - if (!phy->is_ulpi_phy) { - if (phy->is_legacy_phy) - phy->config = &utmip_default[0]; - else - phy->config = &utmip_default[2]; - } - - phy->pll_u = devm_clk_get(phy->dev, "pll_u"); - if (IS_ERR(phy->pll_u)) { - pr_err("Can't get pll_u clock\n"); - return PTR_ERR(phy->pll_u); - } + if (WARN_ON(phy->freq)) + return 0; err = clk_prepare_enable(phy->pll_u); if (err) @@ -728,74 +1027,244 @@ static int tegra_usb_phy_init(struct tegra_usb_phy *phy) } } if (!phy->freq) { - pr_err("invalid pll_u parent rate %ld\n", parent_rate); + dev_err(phy->u_phy.dev, "Invalid pll_u parent rate %ld\n", + parent_rate); err = -EINVAL; - goto fail; + goto disable_clk; } - if (phy->is_ulpi_phy) - err = ulpi_open(phy); - else + err = regulator_enable(phy->vbus); + if (err) { + dev_err(phy->u_phy.dev, + "Failed to enable USB VBUS regulator: %d\n", err); + goto disable_clk; + } + + if (!phy->is_ulpi_phy) { err = utmip_pad_open(phy); - if (err < 0) - goto fail; + 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; -fail: +close_phy: + if (!phy->is_ulpi_phy) + utmip_pad_close(phy); + +disable_vbus: + regulator_disable(phy->vbus); + +disable_clk: clk_disable_unprepare(phy->pll_u); + + phy->freq = NULL; + return err; } -void tegra_usb_phy_preresume(struct usb_phy *x) +static int read_utmi_param(struct platform_device *pdev, const char *param, + u8 *dest) { - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + u32 value; + int err; - if (!phy->is_ulpi_phy) - utmi_phy_preresume(phy); + 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; } -EXPORT_SYMBOL_GPL(tegra_usb_phy_preresume); -void tegra_usb_phy_postresume(struct usb_phy *x) +static int utmi_phy_probe(struct tegra_usb_phy *tegra_phy, + struct platform_device *pdev) { - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + struct tegra_utmip_config *config; + struct resource *res; + int err; - if (!phy->is_ulpi_phy) - utmi_phy_postresume(phy); + tegra_phy->is_ulpi_phy = false; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "Failed to get UTMI pad regs\n"); + return -ENXIO; + } + + /* + * Note that UTMI pad registers are shared by all PHYs, therefore + * devm_platform_ioremap_resource() can't be used here. + */ + tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tegra_phy->pad_regs) { + dev_err(&pdev->dev, "Failed to remap UTMI pad regs\n"); + return -ENOMEM; + } + + tegra_phy->config = devm_kzalloc(&pdev->dev, sizeof(*config), + GFP_KERNEL); + if (!tegra_phy->config) + return -ENOMEM; + + config = tegra_phy->config; + + err = read_utmi_param(pdev, "nvidia,hssync-start-delay", + &config->hssync_start_delay); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,elastic-limit", + &config->elastic_limit); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,idle-wait-delay", + &config->idle_wait_delay); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,term-range-adj", + &config->term_range_adj); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,xcvr-lsfslew", + &config->xcvr_lsfslew); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,xcvr-lsrslew", + &config->xcvr_lsrslew); + if (err) + return err; + + if (tegra_phy->soc_config->requires_extra_tuning_parameters) { + err = read_utmi_param(pdev, "nvidia,xcvr-hsslew", + &config->xcvr_hsslew); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,hssquelch-level", + &config->hssquelch_level); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,hsdiscon-level", + &config->hsdiscon_level); + if (err) + return err; + } + + config->xcvr_setup_use_fuses = of_property_read_bool( + pdev->dev.of_node, "nvidia,xcvr-setup-use-fuses"); + + if (!config->xcvr_setup_use_fuses) { + err = read_utmi_param(pdev, "nvidia,xcvr-setup", + &config->xcvr_setup); + if (err) + return err; + } + + return 0; } -EXPORT_SYMBOL_GPL(tegra_usb_phy_postresume); -void tegra_ehci_phy_restore_start(struct usb_phy *x, - enum tegra_usb_phy_port_speed port_speed) +static void tegra_usb_phy_put_pmc_device(void *dev) { - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); - - if (!phy->is_ulpi_phy) - utmi_phy_restore_start(phy, port_speed); + put_device(dev); } -EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_start); -void tegra_ehci_phy_restore_end(struct usb_phy *x) +static int tegra_usb_phy_parse_pmc(struct device *dev, + struct tegra_usb_phy *phy) { - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); + struct platform_device *pmc_pdev; + struct of_phandle_args args; + int err; - if (!phy->is_ulpi_phy) - utmi_phy_restore_end(phy); + 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; } -EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_end); + +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, +}; + +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, +}; + +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 resource *res; - struct tegra_usb_phy *tegra_phy = NULL; struct device_node *np = pdev->dev.of_node; + struct tegra_usb_phy *tegra_phy; + enum usb_phy_interface phy_type; + struct reset_control *reset; + struct gpio_desc *gpiod; + struct resource *res; + struct usb_phy *phy; int err; tegra_phy = devm_kzalloc(&pdev->dev, sizeof(*tegra_phy), GFP_KERNEL); - if (!tegra_phy) { - dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n"); + 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) { @@ -803,8 +1272,12 @@ static int tegra_usb_phy_probe(struct platform_device *pdev) 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)); + resource_size(res)); if (!tegra_phy->regs) { dev_err(&pdev->dev, "Failed to remap I/O memory\n"); return -ENOMEM; @@ -813,95 +1286,133 @@ static int tegra_usb_phy_probe(struct platform_device *pdev) tegra_phy->is_legacy_phy = of_property_read_bool(np, "nvidia,has-legacy-mode"); - err = of_property_match_string(np, "phy_type", "ulpi"); - if (err < 0) { - tegra_phy->is_ulpi_phy = false; + 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; + } - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!res) { - dev_err(&pdev->dev, "Failed to get UTMI Pad regs\n"); - return -ENXIO; + phy_type = of_usb_get_phy_mode(np); + switch (phy_type) { + case USBPHY_INTERFACE_MODE_UTMI: + err = utmi_phy_probe(tegra_phy, pdev); + if (err) + return err; + + tegra_phy->pad_clk = devm_clk_get(&pdev->dev, "utmi-pads"); + err = PTR_ERR_OR_ZERO(tegra_phy->pad_clk); + if (err) { + dev_err(&pdev->dev, + "Failed to get UTMIP pad clock: %d\n", err); + return err; } - tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (!tegra_phy->regs) { - dev_err(&pdev->dev, "Failed to remap UTMI Pad regs\n"); - return -ENOMEM; + 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; } - } else { + tegra_phy->pad_rst = reset; + break; + + case USBPHY_INTERFACE_MODE_ULPI: tegra_phy->is_ulpi_phy = true; - tegra_phy->reset_gpio = - of_get_named_gpio(np, "nvidia,phy-reset-gpio", 0); - if (!gpio_is_valid(tegra_phy->reset_gpio)) { - dev_err(&pdev->dev, "invalid gpio: %d\n", - tegra_phy->reset_gpio); - return tegra_phy->reset_gpio; + 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; } - } - err = of_property_match_string(np, "dr_mode", "otg"); - if (err < 0) { - err = of_property_match_string(np, "dr_mode", "peripheral"); - if (err < 0) - tegra_phy->mode = TEGRA_USB_PHY_MODE_HOST; - else - tegra_phy->mode = TEGRA_USB_PHY_MODE_DEVICE; - } else - tegra_phy->mode = TEGRA_USB_PHY_MODE_OTG; + 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; + } - tegra_phy->dev = &pdev->dev; - err = tegra_usb_phy_init(tegra_phy); - if (err < 0) - 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->u_phy.shutdown = tegra_usb_phy_close; - tegra_phy->u_phy.set_suspend = tegra_usb_phy_suspend; + tegra_phy->reset_gpio = gpiod; - dev_set_drvdata(&pdev->dev, tegra_phy); - return 0; + phy = devm_otg_ulpi_create(&pdev->dev, + &ulpi_viewport_access_ops, 0); + if (!phy) { + dev_err(&pdev->dev, "Failed to create ULPI OTG\n"); + return -ENOMEM; + } + + tegra_phy->ulpi = phy; + tegra_phy->ulpi->io_priv = tegra_phy->regs + ULPI_VIEWPORT; + break; + + default: + dev_err(&pdev->dev, "phy_type %u is invalid or unsupported\n", + phy_type); + return -EINVAL; + } + + tegra_phy->u_phy.dev = &pdev->dev; + tegra_phy->u_phy.init = tegra_usb_phy_init; + tegra_phy->u_phy.shutdown = tegra_usb_phy_shutdown; + tegra_phy->u_phy.set_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 struct of_device_id tegra_usb_phy_id_table[] = { - { .compatible = "nvidia,tegra20-usb-phy", }, - { }, -}; -MODULE_DEVICE_TABLE(of, tegra_usb_phy_id_table); +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", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(tegra_usb_phy_id_table), + .of_match_table = tegra_usb_phy_id_table, }, }; module_platform_driver(tegra_usb_phy_driver); -static int tegra_usb_phy_match(struct device *dev, void *data) -{ - struct tegra_usb_phy *tegra_phy = dev_get_drvdata(dev); - struct device_node *dn = data; - - return (tegra_phy->dev->of_node == dn) ? 1 : 0; -} - -struct usb_phy *tegra_usb_get_phy(struct device_node *dn) -{ - struct device *dev; - struct tegra_usb_phy *tegra_phy; - - dev = driver_find_device(&tegra_usb_phy_driver.driver, NULL, dn, - tegra_usb_phy_match); - if (!dev) - return ERR_PTR(-EPROBE_DEFER); - - tegra_phy = dev_get_drvdata(dev); - - return &tegra_phy->u_phy; -} -EXPORT_SYMBOL_GPL(tegra_usb_get_phy); - MODULE_DESCRIPTION("Tegra USB PHY driver"); MODULE_LICENSE("GPL v2"); |
