diff options
Diffstat (limited to 'drivers/usb/phy')
| -rw-r--r-- | drivers/usb/phy/Kconfig | 40 | ||||
| -rw-r--r-- | drivers/usb/phy/Makefile | 4 | ||||
| -rw-r--r-- | drivers/usb/phy/class-dual-role.c | 21 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-msm-hsusb.c | 858 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-msm-qusb-v2.c | 1141 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-msm-qusb.c | 1204 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-msm-ssusb-qmp.c | 833 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-msm-ssusb.c | 595 | ||||
| -rw-r--r-- | drivers/usb/phy/phy-msm-usb.c | 4 |
9 files changed, 4689 insertions, 11 deletions
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 795485eac7b0..732fa214f608 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -173,6 +173,46 @@ config USB_QCOM_8X16_PHY To compile this driver as a module, choose M here: the module will be called phy-qcom-8x16-usb. +config USB_MSM_HSPHY + tristate "MSM HSUSB PHY Driver" + depends on ARCH_QCOM + select USB_PHY + help + Enable this to support the High-speed USB transceiver on MSM chips. + This driver supports the PHY which uses the QSCRATCH-based register + set for its control sequences, normally paired with newer DWC3-based + SuperSpeed controllers. + +config USB_MSM_SSPHY + tristate "MSM SSUSB PHY Driver" + depends on ARCH_QCOM + select USB_PHY + help + Enable this to support the SuperSpeed USB transceiver on MSM chips. + This driver supports the PHY which uses the QSCRATCH-based register + set for its control sequences, normally paired with newer DWC3-based + SuperSpeed controllers. + +config USB_MSM_SSPHY_QMP + tristate "MSM SSUSB QMP PHY Driver" + depends on ARCH_QCOM + select USB_PHY + help + Enable this to support the SuperSpeed USB transceiver on MSM chips. + This driver supports the PHY which uses the QSCRATCH-based register + set for its control sequences, normally paired with newer DWC3-based + SuperSpeed controllers. + +config MSM_QUSB_PHY + tristate "MSM QUSB2 PHY Driver" + depends on ARCH_QCOM + select USB_PHY + help + Enable this to support the QUSB2 PHY on MSM chips. This driver supports + the high-speed PHY which is usually paired with either the ChipIdea or + Synopsys DWC3 USB IPs on MSM SOCs. This driver expects to configure the + PHY with a dedicated register I/O memory region. + config USB_MV_OTG tristate "Marvell USB OTG support" depends on USB_EHCI_MV && USB_MV_UDC && PM && USB_OTG diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index f7543f3b9943..170f3ce3e881 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -23,6 +23,10 @@ obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o obj-$(CONFIG_USB_QCOM_8X16_PHY) += phy-qcom-8x16-usb.o +obj-$(CONFIG_USB_MSM_HSPHY) += phy-msm-hsusb.o +obj-$(CONFIG_USB_MSM_SSPHY) += phy-msm-ssusb.o +obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o +obj-$(CONFIG_MSM_QUSB_PHY) += phy-msm-qusb.o phy-msm-qusb-v2.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o diff --git a/drivers/usb/phy/class-dual-role.c b/drivers/usb/phy/class-dual-role.c index 51fcb545a9d5..9ef889593ef5 100644 --- a/drivers/usb/phy/class-dual-role.c +++ b/drivers/usb/phy/class-dual-role.c @@ -70,15 +70,7 @@ static char *kstrdupcase(const char *str, gfp_t gfp, bool to_upper) return ret; } -static void dual_role_changed_work(struct work_struct *work) -{ - struct dual_role_phy_instance *dual_role = - container_of(work, struct dual_role_phy_instance, - changed_work); - - dev_dbg(&dual_role->dev, "%s\n", __func__); - kobject_uevent(&dual_role->dev.kobj, KOBJ_CHANGE); -} +static void dual_role_changed_work(struct work_struct *work); void dual_role_instance_changed(struct dual_role_phy_instance *dual_role) { @@ -505,6 +497,17 @@ out: return ret; } +static void dual_role_changed_work(struct work_struct *work) +{ + struct dual_role_phy_instance *dual_role = + container_of(work, struct dual_role_phy_instance, + changed_work); + + dev_dbg(&dual_role->dev, "%s\n", __func__); + sysfs_update_group(&dual_role->dev.kobj, &dual_role_attr_group); + kobject_uevent(&dual_role->dev.kobj, KOBJ_CHANGE); +} + /******************* Module Init ***********************************/ static int __init dual_role_class_init(void) diff --git a/drivers/usb/phy/phy-msm-hsusb.c b/drivers/usb/phy/phy-msm-hsusb.c new file mode 100644 index 000000000000..56832adf8716 --- /dev/null +++ b/drivers/usb/phy/phy-msm-hsusb.c @@ -0,0 +1,858 @@ +/* + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/phy.h> +#include <linux/usb/msm_hsusb.h> + +static int override_phy_init; +module_param(override_phy_init, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(override_phy_init, "Override HSPHY Init Seq"); + + +#define PORT_OFFSET(i) ((i == 0) ? 0x0 : ((i == 1) ? 0x6c : 0x88)) + +/* QSCRATCH register settings differ based on MSM core ver */ +#define MSM_CORE_VER_120 0x10020061 +#define MSM_CORE_VER_160 0x10060000 +#define MSM_CORE_VER_161 0x10060001 + +/* QSCRATCH register offsets */ +#define GENERAL_CFG_REG (0x08) +#define HS_PHY_CTRL_REG(i) (0x10 + PORT_OFFSET(i)) +#define PARAMETER_OVERRIDE_X_REG(i) (0x14 + PORT_OFFSET(i)) +#define ALT_INTERRUPT_EN_REG(i) (0x20 + PORT_OFFSET(i)) +#define HS_PHY_IRQ_STAT_REG(i) (0x24 + PORT_OFFSET(i)) +#define HS_PHY_CTRL_COMMON_REG (0xEC) /* ver >= MSM_CORE_VER_120 */ + +/* GENERAL_CFG_REG bits */ +#define SEC_UTMI_FREE_CLK_GFM_SEL1 (0x80) + +/* HS_PHY_CTRL_REG bits */ +#define RETENABLEN BIT(1) +#define FSEL_MASK (0x7 << 4) +#define FSEL_DEFAULT (0x3 << 4) +#define CLAMP_EN_N BIT(7) +#define OTGSESSVLD_HV_CLAMP_EN_N BIT(8) +#define ID_HV_CLAMP_EN_N BIT(9) +#define COMMONONN BIT(11) +#define OTGDISABLE0 BIT(12) +#define VBUSVLDEXT0 BIT(13) +#define VBUSVLDEXTSEL0 BIT(14) +#define OTGSESSVLDHV_INTEN BIT(15) +#define IDHV_INTEN BIT(16) +#define DPSEHV_CLAMP_EN_N BIT(17) +#define UTMI_OTG_VBUS_VALID BIT(20) +#define USB2_UTMI_CLK_EN BIT(21) +#define USB2_SUSPEND_N BIT(22) +#define USB2_SUSPEND_N_SEL BIT(23) +#define DMSEHV_CLAMP_EN_N BIT(24) +#define CLAMP_MPM_DPSE_DMSE_EN_N BIT(26) +/* Following exist only when core_ver >= MSM_CORE_VER_120 */ +#define FREECLK_DIS_WHEN_SUSP BIT(27) +#define SW_SESSVLD_SEL BIT(28) +#define FREECLOCK_SEL BIT(29) + +/* HS_PHY_CTRL_COMMON_REG bits used when core_ver >= MSM_CORE_VER_120 */ +#define COMMON_PLLITUNE_1 BIT(18) +#define COMMON_PLLBTUNE BIT(15) +#define COMMON_CLKCORE BIT(14) +#define COMMON_VBUSVLDEXTSEL0 BIT(12) +#define COMMON_OTGDISABLE0 BIT(11) +#define COMMON_OTGTUNE0_MASK (0x7 << 8) +#define COMMON_OTGTUNE0_DEFAULT (0x4 << 8) +#define COMMON_COMMONONN BIT(7) +#define COMMON_FSEL (0x7 << 4) +#define COMMON_RETENABLEN BIT(3) + +/* ALT_INTERRUPT_EN/HS_PHY_IRQ_STAT bits */ +#define ACAINTEN BIT(0) +#define DMINTEN BIT(1) +#define DCDINTEN BIT(1) +#define DPINTEN BIT(3) +#define CHGDETINTEN BIT(4) +#define RIDFLOATNINTEN BIT(5) +#define DPSEHV_INTEN BIT(6) +#define DMSEHV_INTEN BIT(7) +#define DPSEHV_HI_INTEN BIT(8) +#define DPSEHV_LO_INTEN BIT(9) +#define DMSEHV_HI_INTEN BIT(10) +#define DMSEHV_LO_INTEN BIT(11) +#define LINESTATE_INTEN BIT(12) +#define DPDMHV_INT_MASK (0xFC0) +#define ALT_INTERRUPT_MASK (0x1FFF) + +#define TCSR_USB30_CONTROL BIT(8) +#define TCSR_HSPHY_ARES BIT(11) + +#define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_HSPHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_HSPHY_3P3_HPM_LOAD 16000 /* uA */ +#define USB_HSPHY_3P3_VOL_FSHOST 3150000 /* uV */ + +#define USB_HSPHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_HSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_HSPHY_1P8_HPM_LOAD 19000 /* uA */ + +struct msm_hsphy { + struct usb_phy phy; + void __iomem *base; + void __iomem *tcsr; + int hsphy_init_seq; + bool set_pllbtune; + u32 core_ver; + + struct clk *sleep_clk; + bool sleep_clk_reset; + + struct regulator *vdd; + struct regulator *vdda33; + struct regulator *vdda18; + int vdd_levels[3]; /* none, low, high */ + u32 lpm_flags; + bool suspended; + bool vdda_force_on; + + /* Using external VBUS/ID notification */ + bool ext_vbus_id; + int num_ports; + bool cable_connected; +}; + +/* global reference counter between all HSPHY instances */ +static atomic_t hsphy_active_count; + +static int msm_hsusb_config_vdd(struct msm_hsphy *phy, int high) +{ + int min, ret; + + min = high ? 1 : 0; /* low or none? */ + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[min], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + return ret; + } + + dev_dbg(phy->phy.dev, "%s: min_vol:%d max_vol:%d\n", __func__, + phy->vdd_levels[min], phy->vdd_levels[2]); + + return ret; +} + +static int msm_hsusb_ldo_enable(struct msm_hsphy *phy, int on) +{ + int rc = 0; + + dev_dbg(phy->phy.dev, "reg (%s)\n", on ? "HPM" : "LPM"); + + if (!on) + goto disable_regulators; + + + rc = regulator_set_load(phy->vdda18, USB_HSPHY_1P8_HPM_LOAD); + if (rc < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda18\n"); + return rc; + } + + rc = regulator_set_voltage(phy->vdda18, USB_HSPHY_1P8_VOL_MIN, + USB_HSPHY_1P8_VOL_MAX); + if (rc) { + dev_err(phy->phy.dev, "unable to set voltage for vdda18\n"); + goto put_vdda18_lpm; + } + + rc = regulator_enable(phy->vdda18); + if (rc) { + dev_err(phy->phy.dev, "Unable to enable vdda18\n"); + goto unset_vdda18; + } + + rc = regulator_set_load(phy->vdda33, USB_HSPHY_3P3_HPM_LOAD); + if (rc < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda33\n"); + goto disable_vdda18; + } + + rc = regulator_set_voltage(phy->vdda33, USB_HSPHY_3P3_VOL_MIN, + USB_HSPHY_3P3_VOL_MAX); + if (rc) { + dev_err(phy->phy.dev, "unable to set voltage for vdda33\n"); + goto put_vdda33_lpm; + } + + rc = regulator_enable(phy->vdda33); + if (rc) { + dev_err(phy->phy.dev, "Unable to enable vdda33\n"); + goto unset_vdda33; + } + + return 0; + +disable_regulators: + rc = regulator_disable(phy->vdda33); + if (rc) + dev_err(phy->phy.dev, "Unable to disable vdda33\n"); + +unset_vdda33: + rc = regulator_set_voltage(phy->vdda33, 0, USB_HSPHY_3P3_VOL_MAX); + if (rc) + dev_err(phy->phy.dev, "unable to set voltage for vdda33\n"); + +put_vdda33_lpm: + rc = regulator_set_load(phy->vdda33, 0); + if (rc < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdda33\n"); + +disable_vdda18: + rc = regulator_disable(phy->vdda18); + if (rc) + dev_err(phy->phy.dev, "Unable to disable vdda18\n"); + +unset_vdda18: + rc = regulator_set_voltage(phy->vdda18, 0, USB_HSPHY_1P8_VOL_MAX); + if (rc) + dev_err(phy->phy.dev, "unable to set voltage for vdda18\n"); + +put_vdda18_lpm: + rc = regulator_set_load(phy->vdda18, 0); + if (rc < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdda18\n"); + + return rc < 0 ? rc : 0; +} + +static void msm_usb_write_readback(void *base, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp = readl_relaxed(base + offset); + + tmp &= ~mask; /* retain other bits */ + write_val = tmp | val; + + writel_relaxed(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = readl_relaxed(base + offset); + tmp &= mask; /* clear other bits */ + + if (tmp != val) + pr_err("%s: write: %x to QSCRATCH: %x FAILED\n", + __func__, val, offset); +} + +static int msm_hsphy_reset(struct usb_phy *uphy) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + u32 val; + int ret; + + /* skip reset if there are other active PHY instances */ + ret = atomic_read(&hsphy_active_count); + if (ret > 1) { + dev_dbg(uphy->dev, "skipping reset, inuse count=%d\n", ret); + return 0; + } + + if (phy->tcsr) { + val = readl_relaxed(phy->tcsr); + + /* Assert/deassert TCSR Reset */ + writel_relaxed((val | TCSR_HSPHY_ARES), phy->tcsr); + usleep_range(1000, 1200); + writel_relaxed((val & ~TCSR_HSPHY_ARES), phy->tcsr); + } else if (phy->sleep_clk_reset) { + /* Reset PHY using sleep clock */ + ret = clk_reset(phy->sleep_clk, CLK_RESET_ASSERT); + if (ret) { + dev_err(uphy->dev, "hsphy_sleep_clk assert failed\n"); + return ret; + } + + usleep_range(1000, 1200); + ret = clk_reset(phy->sleep_clk, CLK_RESET_DEASSERT); + if (ret) { + dev_err(uphy->dev, "hsphy_sleep_clk reset deassert failed\n"); + return ret; + } + } + + return 0; +} + +static int msm_hsphy_init(struct usb_phy *uphy) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + u32 val; + + msm_hsphy_reset(uphy); + + /* different sequences based on core version */ + phy->core_ver = readl_relaxed(phy->base); + + /* + * HSPHY Initialization: Enable UTMI clock and clamp enable HVINTs, + * and disable RETENTION (power-on default is ENABLED) + */ + val = readl_relaxed(phy->base + HS_PHY_CTRL_REG(0)); + val |= (USB2_UTMI_CLK_EN | CLAMP_MPM_DPSE_DMSE_EN_N | RETENABLEN); + + if (uphy->flags & ENABLE_SECONDARY_PHY) { + val &= ~(USB2_UTMI_CLK_EN | FREECLOCK_SEL); + val |= FREECLK_DIS_WHEN_SUSP; + } + + writel_relaxed(val, phy->base + HS_PHY_CTRL_REG(0)); + usleep_range(2000, 2200); + + if (uphy->flags & ENABLE_SECONDARY_PHY) + msm_usb_write_readback(phy->base, GENERAL_CFG_REG, + SEC_UTMI_FREE_CLK_GFM_SEL1, + SEC_UTMI_FREE_CLK_GFM_SEL1); + + if (phy->core_ver >= MSM_CORE_VER_120) { + if (phy->set_pllbtune) { + val = readl_relaxed(phy->base + HS_PHY_CTRL_COMMON_REG); + val |= COMMON_PLLBTUNE | COMMON_CLKCORE; + val &= ~COMMON_FSEL; + writel_relaxed(val, phy->base + HS_PHY_CTRL_COMMON_REG); + } else { + writel_relaxed(COMMON_OTGDISABLE0 | + COMMON_OTGTUNE0_DEFAULT | + COMMON_COMMONONN | FSEL_DEFAULT | + COMMON_RETENABLEN, + phy->base + HS_PHY_CTRL_COMMON_REG); + } + } + + /* + * write HSPHY init value to QSCRATCH reg to set HSPHY parameters like + * VBUS valid threshold, disconnect valid threshold, DC voltage level, + * preempasis and rise/fall time. + */ + if (override_phy_init) + phy->hsphy_init_seq = override_phy_init; + if (phy->hsphy_init_seq) + msm_usb_write_readback(phy->base, + PARAMETER_OVERRIDE_X_REG(0), 0x03FFFFFF, + phy->hsphy_init_seq & 0x03FFFFFF); + + return 0; +} + +static int msm_hsphy_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + bool host = uphy->flags & PHY_HOST_MODE; + bool chg_connected = uphy->flags & PHY_CHARGER_CONNECTED; + int i, count; + + if (!!suspend == phy->suspended) { + dev_dbg(uphy->dev, "%s\n", suspend ? "already suspended" + : "already resumed"); + return 0; + } + + if (suspend) { + for (i = 0; i < phy->num_ports; i++) { + /* Clear interrupt latch register */ + writel_relaxed(ALT_INTERRUPT_MASK, + phy->base + HS_PHY_IRQ_STAT_REG(i)); + + if (host) { + /* Enable DP and DM HV interrupts */ + if (phy->core_ver >= MSM_CORE_VER_120) + msm_usb_write_readback(phy->base, + ALT_INTERRUPT_EN_REG(i), + (LINESTATE_INTEN | + DPINTEN | DMINTEN), + (LINESTATE_INTEN | + DPINTEN | DMINTEN)); + else + msm_usb_write_readback(phy->base, + ALT_INTERRUPT_EN_REG(i), + DPDMHV_INT_MASK, + DPDMHV_INT_MASK); + + udelay(5); + } else { + /* set the following: + * OTGDISABLE0=1 + * USB2_SUSPEND_N_SEL=1, USB2_SUSPEND_N=0 + */ + if (phy->core_ver >= MSM_CORE_VER_120) + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_COMMON_REG, + COMMON_OTGDISABLE0, + COMMON_OTGDISABLE0); + else + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + OTGDISABLE0, OTGDISABLE0); + + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + (USB2_SUSPEND_N_SEL | USB2_SUSPEND_N), + USB2_SUSPEND_N_SEL); + /* + * Enable PHY retention + * RETENABLEN bit is not available on few platforms. + */ + if (!chg_connected) { + if (phy->set_pllbtune) + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_COMMON_REG, + COMMON_PLLITUNE_1, + COMMON_PLLITUNE_1); + else + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + RETENABLEN, 0); + phy->lpm_flags |= PHY_RETENTIONED; + } + } + + if (!phy->ext_vbus_id) + /* Enable PHY-based IDHV and + *OTGSESSVLD HV interrupts + */ + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + (OTGSESSVLDHV_INTEN | IDHV_INTEN), + (OTGSESSVLDHV_INTEN | IDHV_INTEN)); + } + /* can turn off regulators if disconnected in device mode */ + if (phy->lpm_flags & PHY_RETENTIONED && !phy->cable_connected) { + if (phy->ext_vbus_id) { + msm_hsusb_ldo_enable(phy, 0); + phy->lpm_flags |= PHY_PWR_COLLAPSED; + } + msm_hsusb_config_vdd(phy, 0); + } + + count = atomic_dec_return(&hsphy_active_count); + if (count < 0) { + dev_WARN(uphy->dev, "hsphy_active_count=%d, something wrong?\n", + count); + atomic_set(&hsphy_active_count, 0); + } + } else { + atomic_inc(&hsphy_active_count); + if (phy->lpm_flags & PHY_RETENTIONED && !phy->cable_connected) { + msm_hsusb_config_vdd(phy, 1); + if (phy->ext_vbus_id) { + msm_hsusb_ldo_enable(phy, 1); + phy->lpm_flags &= ~PHY_PWR_COLLAPSED; + } + phy->lpm_flags &= ~PHY_RETENTIONED; + } + + if (phy->core_ver >= MSM_CORE_VER_120) { + if (phy->set_pllbtune) { + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_COMMON_REG, + FSEL_MASK, 0); + } else { + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_COMMON_REG, + FSEL_MASK, FSEL_DEFAULT); + } + } + for (i = 0; i < phy->num_ports; i++) { + if (!phy->ext_vbus_id) + /* Disable HV interrupts */ + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + (OTGSESSVLDHV_INTEN | IDHV_INTEN), + 0); + if (host) { + /* Clear interrupt latch register */ + writel_relaxed(ALT_INTERRUPT_MASK, + phy->base + HS_PHY_IRQ_STAT_REG(i)); + /* Disable DP and DM HV interrupt */ + if (phy->core_ver >= MSM_CORE_VER_120) + msm_usb_write_readback(phy->base, + ALT_INTERRUPT_EN_REG(i), + LINESTATE_INTEN, 0); + else + msm_usb_write_readback(phy->base, + ALT_INTERRUPT_EN_REG(i), + DPDMHV_INT_MASK, 0); + } else { + /* Disable PHY retention */ + if (phy->set_pllbtune) + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_COMMON_REG, + COMMON_PLLITUNE_1, 0); + else + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + RETENABLEN, RETENABLEN); + + /* Bring PHY out of suspend */ + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + USB2_SUSPEND_N_SEL, 0); + + if (phy->core_ver >= MSM_CORE_VER_120) + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_COMMON_REG, + COMMON_OTGDISABLE0, + 0); + else + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(i), + OTGDISABLE0, 0); + } + } + /* + * write HSPHY init value to QSCRATCH reg to set HSPHY + * parameters like VBUS valid threshold, disconnect valid + * threshold, DC voltage level,preempasis and rise/fall time + */ + if (override_phy_init) + phy->hsphy_init_seq = override_phy_init; + if (phy->hsphy_init_seq) + msm_usb_write_readback(phy->base, + PARAMETER_OVERRIDE_X_REG(0), + 0x03FFFFFF, + phy->hsphy_init_seq & 0x03FFFFFF); + } + + phy->suspended = !!suspend; /* double-NOT coerces to bool value */ + return 0; +} + +static int msm_hsphy_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + int rc = 0; + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->cable_connected = true; + + if (uphy->flags & PHY_HOST_MODE) { + if (phy->core_ver == MSM_CORE_VER_160 || + phy->core_ver == MSM_CORE_VER_161) { + /* Some snps usb2 picophy revisions require 3.15 V to + * operate correctly during full speed host mode at + * sub zero temperature. + */ + rc = regulator_set_voltage(phy->vdda33, + USB_HSPHY_3P3_VOL_FSHOST, + USB_HSPHY_3P3_VOL_MAX); + if (rc) + dev_err(phy->phy.dev, + "unable to set voltage for vdda33\n"); + } + return 0; + } + + if (!(uphy->flags & PHY_VBUS_VALID_OVERRIDE)) + return 0; + + /* Set External VBUS Valid Select. Set once, can be left on */ + if (phy->core_ver >= MSM_CORE_VER_120) { + msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, + COMMON_VBUSVLDEXTSEL0, + COMMON_VBUSVLDEXTSEL0); + } else { + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(0), + VBUSVLDEXTSEL0, VBUSVLDEXTSEL0); + } + + /* Enable D+ pull-up resistor */ + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(0), + VBUSVLDEXT0, VBUSVLDEXT0); + + /* Set OTG VBUS Valid from HSPHY to controller */ + msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0), + UTMI_OTG_VBUS_VALID, + UTMI_OTG_VBUS_VALID); + + /* Indicate value is driven by UTMI_OTG_VBUS_VALID bit */ + if (phy->core_ver >= MSM_CORE_VER_120) + msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0), + SW_SESSVLD_SEL, SW_SESSVLD_SEL); + + return 0; +} + +static int msm_hsphy_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + int rc = 0; + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->cable_connected = false; + + if (uphy->flags & PHY_HOST_MODE) { + if (phy->core_ver == MSM_CORE_VER_160 || + phy->core_ver == MSM_CORE_VER_161) { + rc = regulator_set_voltage(phy->vdda33, + USB_HSPHY_3P3_VOL_MIN, + USB_HSPHY_3P3_VOL_MAX); + if (rc) + dev_err(phy->phy.dev, + "unable to set voltage for vdda33\n"); + } + return 0; + } + + if (!(uphy->flags & PHY_VBUS_VALID_OVERRIDE)) + return 0; + + /* Clear OTG VBUS Valid to Controller */ + msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0), + UTMI_OTG_VBUS_VALID, 0); + + /* Disable D+ pull-up resistor */ + msm_usb_write_readback(phy->base, + HS_PHY_CTRL_REG(0), VBUSVLDEXT0, 0); + + /* Indicate value is no longer driven by UTMI_OTG_VBUS_VALID bit */ + if (phy->core_ver >= MSM_CORE_VER_120) + msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0), + SW_SESSVLD_SEL, 0); + + return 0; +} + +static int msm_hsphy_probe(struct platform_device *pdev) +{ + struct msm_hsphy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + ret = -ENOMEM; + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->base = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (!phy->base) { + dev_err(dev, "ioremap failed\n"); + ret = -ENODEV; + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr"); + if (res) { + phy->tcsr = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (!phy->tcsr) { + dev_err(dev, "tcsr ioremap failed\n"); + return -ENODEV; + } + + /* switch MUX to let SNPS controller use the primary HSPHY */ + writel_relaxed(readl_relaxed(phy->tcsr) | TCSR_USB30_CONTROL, + phy->tcsr); + } + + if (of_get_property(dev->of_node, "qcom,primary-phy", NULL)) { + dev_dbg(dev, "secondary HSPHY\n"); + phy->phy.flags |= ENABLE_SECONDARY_PHY; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + ARRAY_SIZE(phy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + goto err_ret; + } + + phy->ext_vbus_id = of_property_read_bool(dev->of_node, + "qcom,ext-vbus-id"); + phy->phy.dev = dev; + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err_ret; + } + + phy->vdda33 = devm_regulator_get(dev, "vdda33"); + if (IS_ERR(phy->vdda33)) { + dev_err(dev, "unable to get vdda33 supply\n"); + ret = PTR_ERR(phy->vdda33); + goto err_ret; + } + + phy->vdda18 = devm_regulator_get(dev, "vdda18"); + if (IS_ERR(phy->vdda18)) { + dev_err(dev, "unable to get vdda18 supply\n"); + ret = PTR_ERR(phy->vdda18); + goto err_ret; + } + + ret = msm_hsusb_config_vdd(phy, 1); + if (ret) { + dev_err(dev, "hsusb vdd_dig configuration failed\n"); + goto err_ret; + } + + ret = regulator_enable(phy->vdd); + if (ret) { + dev_err(dev, "unable to enable the hsusb vdd_dig\n"); + goto unconfig_hs_vdd; + } + + ret = msm_hsusb_ldo_enable(phy, 1); + if (ret) { + dev_err(dev, "hsusb vreg enable failed\n"); + goto disable_hs_vdd; + } + + phy->sleep_clk = devm_clk_get(&pdev->dev, "phy_sleep_clk"); + if (IS_ERR(phy->sleep_clk)) { + dev_err(&pdev->dev, "failed to get phy_sleep_clk\n"); + ret = PTR_ERR(phy->sleep_clk); + goto disable_hs_ldo; + } + clk_prepare_enable(phy->sleep_clk); + phy->sleep_clk_reset = of_property_read_bool(dev->of_node, + "qcom,sleep-clk-reset"); + + if (of_property_read_u32(dev->of_node, "qcom,hsphy-init", + &phy->hsphy_init_seq)) + dev_dbg(dev, "unable to read hsphy init seq\n"); + else if (!phy->hsphy_init_seq) + dev_warn(dev, "hsphy init seq cannot be 0. Using POR value\n"); + + if (of_property_read_u32(dev->of_node, "qcom,num-ports", + &phy->num_ports)) + phy->num_ports = 1; + else if (phy->num_ports > 3) { + dev_err(dev, " number of ports more that 3 is not supported\n"); + goto disable_clk; + } + + phy->set_pllbtune = of_property_read_bool(dev->of_node, + "qcom,set-pllbtune"); + + /* + * If this workaround flag is enabled, the HW requires the 1.8 and 3.x + * regulators to be kept ON when entering suspend. The easiest way to + * do that is to call regulator_enable() an additional time here, + * since it will keep the regulators' reference counts nonzero. + */ + phy->vdda_force_on = of_property_read_bool(dev->of_node, + "qcom,vdda-force-on"); + if (phy->vdda_force_on) { + ret = msm_hsusb_ldo_enable(phy, 1); + if (ret) + goto disable_clk; + } + + platform_set_drvdata(pdev, phy); + + if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override")) + phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE; + + phy->phy.init = msm_hsphy_init; + phy->phy.set_suspend = msm_hsphy_set_suspend; + phy->phy.notify_connect = msm_hsphy_notify_connect; + phy->phy.notify_disconnect = msm_hsphy_notify_disconnect; + phy->phy.reset = msm_hsphy_reset; + /*FIXME: this conflicts with dwc3_otg */ + /*phy->phy.type = USB_PHY_TYPE_USB2; */ + + ret = usb_add_phy_dev(&phy->phy); + if (ret) + goto disable_clk; + + atomic_inc(&hsphy_active_count); + return 0; + +disable_clk: + clk_disable_unprepare(phy->sleep_clk); +disable_hs_ldo: + msm_hsusb_ldo_enable(phy, 0); +disable_hs_vdd: + regulator_disable(phy->vdd); +unconfig_hs_vdd: + msm_hsusb_config_vdd(phy, 0); +err_ret: + return ret; +} + +static int msm_hsphy_remove(struct platform_device *pdev) +{ + struct msm_hsphy *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + usb_remove_phy(&phy->phy); + clk_disable_unprepare(phy->sleep_clk); + + /* Undo the additional regulator enable */ + if (phy->vdda_force_on) + msm_hsusb_ldo_enable(phy, 0); + msm_hsusb_ldo_enable(phy, 0); + regulator_disable(phy->vdd); + msm_hsusb_config_vdd(phy, 0); + if (!phy->suspended) + atomic_dec(&hsphy_active_count); + kfree(phy); + + return 0; +} + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-hsphy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static struct platform_driver msm_hsphy_driver = { + .probe = msm_hsphy_probe, + .remove = msm_hsphy_remove, + .driver = { + .name = "msm-usb-hsphy", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_hsphy_driver); + +MODULE_DESCRIPTION("MSM USB HS PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-qusb-v2.c b/drivers/usb/phy/phy-msm-qusb-v2.c new file mode 100644 index 000000000000..e5f38e42e165 --- /dev/null +++ b/drivers/usb/phy/phy-msm-qusb-v2.c @@ -0,0 +1,1141 @@ +/* + * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/usb/phy.h> +#include <linux/usb/msm_hsusb.h> +#include <linux/reset.h> + +#define QUSB2PHY_PWR_CTRL1 0x210 +#define PWR_CTRL1_POWR_DOWN BIT(0) + +#define QUSB2PHY_PLL_COMMON_STATUS_ONE 0x1A0 +#define CORE_READY_STATUS BIT(0) + +/* Get TUNE value from efuse bit-mask */ +#define TUNE_VAL_MASK(val, pos, mask) ((val >> pos) & mask) + +#define QUSB2PHY_INTR_CTRL 0x22C +#define DMSE_INTR_HIGH_SEL BIT(4) +#define DPSE_INTR_HIGH_SEL BIT(3) +#define CHG_DET_INTR_EN BIT(2) +#define DMSE_INTR_EN BIT(1) +#define DPSE_INTR_EN BIT(0) + +#define QUSB2PHY_INTR_STAT 0x230 +#define DMSE_INTERRUPT BIT(1) +#define DPSE_INTERRUPT BIT(0) + +#define QUSB2PHY_PORT_TUNE1 0x23c +#define QUSB2PHY_TEST1 0x24C + +#define QUSB2PHY_1P2_VOL_MIN 1200000 /* uV */ +#define QUSB2PHY_1P2_VOL_MAX 1200000 /* uV */ +#define QUSB2PHY_1P2_HPM_LOAD 23000 + +#define QUSB2PHY_1P8_VOL_MIN 1800000 /* uV */ +#define QUSB2PHY_1P8_VOL_MAX 1800000 /* uV */ +#define QUSB2PHY_1P8_HPM_LOAD 30000 /* uA */ + +#define QUSB2PHY_3P3_VOL_MIN 3075000 /* uV */ +#define QUSB2PHY_3P3_VOL_MAX 3200000 /* uV */ +#define QUSB2PHY_3P3_HPM_LOAD 30000 /* uA */ + +#define LINESTATE_DP BIT(0) +#define LINESTATE_DM BIT(1) + +#define QUSB2PHY_PLL_ANALOG_CONTROLS_ONE 0x0 +#define QUSB2PHY_PLL_ANALOG_CONTROLS_TWO 0x4 + +unsigned int phy_tune1; +module_param(phy_tune1, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(phy_tune1, "QUSB PHY v2 TUNE1"); + +struct qusb_phy { + struct usb_phy phy; + struct mutex lock; + void __iomem *base; + void __iomem *efuse_reg; + void __iomem *tcsr_clamp_dig_n; + + struct clk *ref_clk_src; + struct clk *ref_clk; + struct clk *cfg_ahb_clk; + struct reset_control *phy_reset; + + struct regulator *vdd; + struct regulator *vdda33; + struct regulator *vdda18; + struct regulator *vdda12; + int vdd_levels[3]; /* none, low, high */ + int vdda33_levels[3]; + int init_seq_len; + int *qusb_phy_init_seq; + int host_init_seq_len; + int *qusb_phy_host_init_seq; + + u32 tune_val; + int efuse_bit_pos; + int efuse_num_of_bits; + + int power_enabled_ref; + bool clocks_enabled; + bool cable_connected; + bool suspended; + bool rm_pulldown; + + struct regulator_desc dpdm_rdesc; + struct regulator_dev *dpdm_rdev; + + /* emulation targets specific */ + void __iomem *emu_phy_base; + bool emulation; + int *emu_init_seq; + int emu_init_seq_len; + int *phy_pll_reset_seq; + int phy_pll_reset_seq_len; + int *emu_dcm_reset_seq; + int emu_dcm_reset_seq_len; +}; + +static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on) +{ + dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d on:%d\n", + __func__, qphy->clocks_enabled, on); + + if (!qphy->clocks_enabled && on) { + clk_prepare_enable(qphy->ref_clk_src); + clk_prepare_enable(qphy->ref_clk); + clk_prepare_enable(qphy->cfg_ahb_clk); + qphy->clocks_enabled = true; + } + + if (qphy->clocks_enabled && !on) { + clk_disable_unprepare(qphy->ref_clk); + clk_disable_unprepare(qphy->ref_clk_src); + clk_disable_unprepare(qphy->cfg_ahb_clk); + qphy->clocks_enabled = false; + } + + dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d\n", __func__, + qphy->clocks_enabled); +} + +static int qusb_phy_config_vdd(struct qusb_phy *qphy, int high) +{ + int min, ret; + + min = high ? 1 : 0; /* low or none? */ + ret = regulator_set_voltage(qphy->vdd, qphy->vdd_levels[min], + qphy->vdd_levels[2]); + if (ret) { + dev_err(qphy->phy.dev, "unable to set voltage for qusb vdd\n"); + return ret; + } + + dev_dbg(qphy->phy.dev, "min_vol:%d max_vol:%d\n", + qphy->vdd_levels[min], qphy->vdd_levels[2]); + return ret; +} + +static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on) +{ + int ret = 0; + + mutex_lock(&qphy->lock); + + dev_dbg(qphy->phy.dev, + "%s:req to turn %s regulators. power_enabled_ref:%d\n", + __func__, on ? "on" : "off", qphy->power_enabled_ref); + + if (on && ++qphy->power_enabled_ref > 1) { + dev_dbg(qphy->phy.dev, "PHYs' regulators are already on\n"); + goto done; + } + + if (!on) { + if (on == qphy->power_enabled_ref) { + dev_dbg(qphy->phy.dev, + "PHYs' regulators are already off\n"); + goto done; + } + + qphy->power_enabled_ref--; + if (!qphy->power_enabled_ref) + goto disable_vdda33; + + dev_dbg(qphy->phy.dev, "Skip turning off PHYs' regulators\n"); + goto done; + } + + ret = qusb_phy_config_vdd(qphy, true); + if (ret) { + dev_err(qphy->phy.dev, "Unable to config VDD:%d\n", + ret); + goto err_vdd; + } + + ret = regulator_enable(qphy->vdd); + if (ret) { + dev_err(qphy->phy.dev, "Unable to enable VDD\n"); + goto unconfig_vdd; + } + + ret = regulator_set_load(qphy->vdda12, QUSB2PHY_1P2_HPM_LOAD); + if (ret < 0) { + dev_err(qphy->phy.dev, "Unable to set HPM of vdda12:%d\n", ret); + goto disable_vdd; + } + + ret = regulator_set_voltage(qphy->vdda12, QUSB2PHY_1P2_VOL_MIN, + QUSB2PHY_1P2_VOL_MAX); + if (ret) { + dev_err(qphy->phy.dev, + "Unable to set voltage for vdda12:%d\n", ret); + goto put_vdda12_lpm; + } + + ret = regulator_enable(qphy->vdda12); + if (ret) { + dev_err(qphy->phy.dev, "Unable to enable vdda12:%d\n", ret); + goto unset_vdda12; + } + + ret = regulator_set_load(qphy->vdda18, QUSB2PHY_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(qphy->phy.dev, "Unable to set HPM of vdda18:%d\n", ret); + goto disable_vdda12; + } + + ret = regulator_set_voltage(qphy->vdda18, QUSB2PHY_1P8_VOL_MIN, + QUSB2PHY_1P8_VOL_MAX); + if (ret) { + dev_err(qphy->phy.dev, + "Unable to set voltage for vdda18:%d\n", ret); + goto put_vdda18_lpm; + } + + ret = regulator_enable(qphy->vdda18); + if (ret) { + dev_err(qphy->phy.dev, "Unable to enable vdda18:%d\n", ret); + goto unset_vdda18; + } + + ret = regulator_set_load(qphy->vdda33, QUSB2PHY_3P3_HPM_LOAD); + if (ret < 0) { + dev_err(qphy->phy.dev, "Unable to set HPM of vdda33:%d\n", ret); + goto disable_vdda18; + } + + ret = regulator_set_voltage(qphy->vdda33, qphy->vdda33_levels[0], + qphy->vdda33_levels[2]); + if (ret) { + dev_err(qphy->phy.dev, + "Unable to set voltage for vdda33:%d\n", ret); + goto put_vdda33_lpm; + } + + ret = regulator_enable(qphy->vdda33); + if (ret) { + dev_err(qphy->phy.dev, "Unable to enable vdda33:%d\n", ret); + goto unset_vdd33; + } + + pr_debug("%s(): QUSB PHY's regulators are turned ON.\n", __func__); + + mutex_unlock(&qphy->lock); + return ret; + +disable_vdda33: + ret = regulator_disable(qphy->vdda33); + if (ret) + dev_err(qphy->phy.dev, "Unable to disable vdda33:%d\n", ret); + +unset_vdd33: + ret = regulator_set_voltage(qphy->vdda33, 0, qphy->vdda33_levels[2]); + if (ret) + dev_err(qphy->phy.dev, + "Unable to set (0) voltage for vdda33:%d\n", ret); + +put_vdda33_lpm: + ret = regulator_set_load(qphy->vdda33, 0); + if (ret < 0) + dev_err(qphy->phy.dev, "Unable to set (0) HPM of vdda33\n"); + +disable_vdda18: + ret = regulator_disable(qphy->vdda18); + if (ret) + dev_err(qphy->phy.dev, "Unable to disable vdda18:%d\n", ret); + +unset_vdda18: + ret = regulator_set_voltage(qphy->vdda18, 0, QUSB2PHY_1P8_VOL_MAX); + if (ret) + dev_err(qphy->phy.dev, + "Unable to set (0) voltage for vdda18:%d\n", ret); + +put_vdda18_lpm: + ret = regulator_set_load(qphy->vdda18, 0); + if (ret < 0) + dev_err(qphy->phy.dev, "Unable to set LPM of vdda18\n"); + +disable_vdda12: + ret = regulator_disable(qphy->vdda12); + if (ret) + dev_err(qphy->phy.dev, "Unable to disable vdda12:%d\n", ret); +unset_vdda12: + ret = regulator_set_voltage(qphy->vdda12, 0, QUSB2PHY_1P2_VOL_MAX); + if (ret) + dev_err(qphy->phy.dev, + "Unable to set (0) voltage for vdda12:%d\n", ret); +put_vdda12_lpm: + ret = regulator_set_load(qphy->vdda12, 0); + if (ret < 0) + dev_err(qphy->phy.dev, "Unable to set LPM of vdda12\n"); + +disable_vdd: + ret = regulator_disable(qphy->vdd); + if (ret) + dev_err(qphy->phy.dev, "Unable to disable vdd:%d\n", + ret); + +unconfig_vdd: + ret = qusb_phy_config_vdd(qphy, false); + if (ret) + dev_err(qphy->phy.dev, "Unable unconfig VDD:%d\n", + ret); +err_vdd: + dev_dbg(qphy->phy.dev, "QUSB PHY's regulators are turned OFF.\n"); + + /* in case of error in turning on regulators */ + if (qphy->power_enabled_ref) + qphy->power_enabled_ref--; +done: + mutex_unlock(&qphy->lock); + return ret; +} + +static int qusb_phy_update_dpdm(struct usb_phy *phy, int value) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + int ret = 0; + + dev_dbg(phy->dev, "%s value:%d rm_pulldown:%d\n", + __func__, value, qphy->rm_pulldown); + + switch (value) { + case POWER_SUPPLY_DP_DM_DPF_DMF: + dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPF_DMF\n"); + if (!qphy->rm_pulldown) { + ret = qusb_phy_enable_power(qphy, true); + if (ret >= 0) { + qphy->rm_pulldown = true; + dev_dbg(phy->dev, "DP_DM_F: rm_pulldown:%d\n", + qphy->rm_pulldown); + } + } + + break; + + case POWER_SUPPLY_DP_DM_DPR_DMR: + dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPR_DMR\n"); + if (qphy->rm_pulldown) { + ret = qusb_phy_enable_power(qphy, false); + if (ret >= 0) { + qphy->rm_pulldown = false; + dev_dbg(phy->dev, "DP_DM_R: rm_pulldown:%d\n", + qphy->rm_pulldown); + } + } + break; + + default: + ret = -EINVAL; + dev_err(phy->dev, "Invalid power supply property(%d)\n", value); + break; + } + + return ret; +} + +static void qusb_phy_get_tune1_param(struct qusb_phy *qphy) +{ + u8 reg; + u32 bit_mask = 1; + + pr_debug("%s(): num_of_bits:%d bit_pos:%d\n", __func__, + qphy->efuse_num_of_bits, + qphy->efuse_bit_pos); + + /* get bit mask based on number of bits to use with efuse reg */ + bit_mask = (bit_mask << qphy->efuse_num_of_bits) - 1; + + /* + * if efuse reg is updated (i.e non-zero) then use it to program + * tune parameters + */ + qphy->tune_val = readl_relaxed(qphy->efuse_reg); + pr_debug("%s(): bit_mask:%d efuse based tune1 value:%d\n", + __func__, bit_mask, qphy->tune_val); + + qphy->tune_val = TUNE_VAL_MASK(qphy->tune_val, + qphy->efuse_bit_pos, bit_mask); + reg = readb_relaxed(qphy->base + QUSB2PHY_PORT_TUNE1); + if (qphy->tune_val) { + reg = reg & 0x0f; + reg |= (qphy->tune_val << 4); + } + qphy->tune_val = reg; +} + +static void qusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt, + unsigned long delay) +{ + int i; + + pr_debug("Seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + pr_debug("write 0x%02x to 0x%02x\n", seq[i], seq[i+1]); + writel_relaxed(seq[i], base + seq[i+1]); + if (delay) + usleep_range(delay, (delay + 2000)); + } +} + +static void qusb_phy_host_init(struct usb_phy *phy) +{ + u8 reg; + int ret; + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + + dev_dbg(phy->dev, "%s\n", __func__); + + /* Perform phy reset */ + ret = reset_control_assert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__); + usleep_range(100, 150); + ret = reset_control_deassert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__); + + qusb_phy_write_seq(qphy->base, qphy->qusb_phy_host_init_seq, + qphy->host_init_seq_len, 0); + + /* Ensure above write is completed before turning ON ref clk */ + wmb(); + + /* Require to get phy pll lock successfully */ + usleep_range(150, 160); + + reg = readb_relaxed(qphy->base + QUSB2PHY_PLL_COMMON_STATUS_ONE); + dev_dbg(phy->dev, "QUSB2PHY_PLL_COMMON_STATUS_ONE:%x\n", reg); + if (!(reg & CORE_READY_STATUS)) { + dev_err(phy->dev, "QUSB PHY PLL LOCK fails:%x\n", reg); + WARN_ON(1); + } +} + +static int qusb_phy_init(struct usb_phy *phy) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + int ret; + u8 reg; + + dev_dbg(phy->dev, "%s\n", __func__); + + /* bump up vdda33 voltage to operating level*/ + ret = regulator_set_voltage(qphy->vdda33, qphy->vdda33_levels[1], + qphy->vdda33_levels[2]); + if (ret) { + dev_err(qphy->phy.dev, + "Unable to set voltage for vdda33:%d\n", ret); + return ret; + } + + qusb_phy_enable_clocks(qphy, true); + + /* Perform phy reset */ + ret = reset_control_assert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__); + usleep_range(100, 150); + ret = reset_control_deassert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__); + + if (qphy->emulation) { + if (qphy->emu_init_seq) + qusb_phy_write_seq(qphy->emu_phy_base, + qphy->emu_init_seq, qphy->emu_init_seq_len, 0); + + if (qphy->qusb_phy_init_seq) + qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq, + qphy->init_seq_len, 0); + + /* Wait for 5ms as per QUSB2 RUMI sequence */ + usleep_range(5000, 7000); + + if (qphy->phy_pll_reset_seq) + qusb_phy_write_seq(qphy->base, qphy->phy_pll_reset_seq, + qphy->phy_pll_reset_seq_len, 10000); + + if (qphy->emu_dcm_reset_seq) + qusb_phy_write_seq(qphy->emu_phy_base, + qphy->emu_dcm_reset_seq, + qphy->emu_dcm_reset_seq_len, 10000); + + return 0; + } + + /* Disable the PHY */ + writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) | + PWR_CTRL1_POWR_DOWN, + qphy->base + QUSB2PHY_PWR_CTRL1); + + if (qphy->qusb_phy_init_seq) + qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq, + qphy->init_seq_len, 0); + if (qphy->efuse_reg) { + if (!qphy->tune_val) + qusb_phy_get_tune1_param(qphy); + + pr_debug("%s(): Programming TUNE1 parameter as:%x\n", __func__, + qphy->tune_val); + writel_relaxed(qphy->tune_val, + qphy->base + QUSB2PHY_PORT_TUNE1); + } + + /* If phy_tune1 modparam set, override tune1 value */ + if (phy_tune1) { + pr_debug("%s(): (modparam) TUNE1 val:0x%02x\n", + __func__, phy_tune1); + writel_relaxed(phy_tune1, + qphy->base + QUSB2PHY_PORT_TUNE1); + } + + /* ensure above writes are completed before re-enabling PHY */ + wmb(); + + /* Enable the PHY */ + writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) & + ~PWR_CTRL1_POWR_DOWN, + qphy->base + QUSB2PHY_PWR_CTRL1); + + /* Ensure above write is completed before turning ON ref clk */ + wmb(); + + /* Require to get phy pll lock successfully */ + usleep_range(150, 160); + + reg = readb_relaxed(qphy->base + QUSB2PHY_PLL_COMMON_STATUS_ONE); + dev_dbg(phy->dev, "QUSB2PHY_PLL_COMMON_STATUS_ONE:%x\n", reg); + if (!(reg & CORE_READY_STATUS)) { + dev_err(phy->dev, "QUSB PHY PLL LOCK fails:%x\n", reg); + WARN_ON(1); + } + return 0; +} + +static void qusb_phy_shutdown(struct usb_phy *phy) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + + dev_dbg(phy->dev, "%s\n", __func__); + + qusb_phy_enable_clocks(qphy, true); + + /* Disable the PHY */ + writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) | + PWR_CTRL1_POWR_DOWN, + qphy->base + QUSB2PHY_PWR_CTRL1); + + /* Makes sure that above write goes through */ + wmb(); + + qusb_phy_enable_clocks(qphy, false); +} + +static u32 qusb_phy_get_linestate(struct qusb_phy *qphy) +{ + u32 linestate = 0; + + if (qphy->cable_connected) { + if (qphy->phy.flags & PHY_HSFS_MODE) + linestate |= LINESTATE_DP; + else if (qphy->phy.flags & PHY_LS_MODE) + linestate |= LINESTATE_DM; + } + return linestate; +} + +/** + * Performs QUSB2 PHY suspend/resume functionality. + * + * @uphy - usb phy pointer. + * @suspend - to enable suspend or not. 1 - suspend, 0 - resume + * + */ +static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + u32 linestate = 0, intr_mask = 0; + static u8 analog_ctrl_two; + int ret; + + if (qphy->suspended && suspend) { + dev_dbg(phy->dev, "%s: USB PHY is already suspended\n", + __func__); + return 0; + } + + if (suspend) { + /* Bus suspend case */ + if (qphy->cable_connected || + (qphy->phy.flags & PHY_HOST_MODE)) { + + /* store clock settings like cmos/cml */ + analog_ctrl_two = + readl_relaxed(qphy->base + + QUSB2PHY_PLL_ANALOG_CONTROLS_TWO); + + /* use CSR & switch to SE clk */ + writel_relaxed(0xb, + qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_TWO); + + /* enable clock bypass */ + writel_relaxed(0x90, + qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_ONE); + + /* Disable all interrupts */ + writel_relaxed(0x00, + qphy->base + QUSB2PHY_INTR_CTRL); + + linestate = qusb_phy_get_linestate(qphy); + /* + * D+/D- interrupts are level-triggered, but we are + * only interested if the line state changes, so enable + * the high/low trigger based on current state. In + * other words, enable the triggers _opposite_ of what + * the current D+/D- levels are. + * e.g. if currently D+ high, D- low (HS 'J'/Suspend), + * configure the mask to trigger on D+ low OR D- high + */ + intr_mask = DMSE_INTERRUPT | DPSE_INTERRUPT; + if (!(linestate & LINESTATE_DP)) /* D+ low */ + intr_mask |= DPSE_INTR_HIGH_SEL; + if (!(linestate & LINESTATE_DM)) /* D- low */ + intr_mask |= DMSE_INTR_HIGH_SEL; + + writel_relaxed(intr_mask, + qphy->base + QUSB2PHY_INTR_CTRL); + + if (linestate & (LINESTATE_DP | LINESTATE_DM)) { + + /* enable phy auto-resume */ + writel_relaxed(0x91, + qphy->base + QUSB2PHY_TEST1); + /* flush the previous write before next write */ + wmb(); + writel_relaxed(0x90, + qphy->base + QUSB2PHY_TEST1); + } + + dev_dbg(phy->dev, "%s: intr_mask = %x\n", + __func__, intr_mask); + + /* Makes sure that above write goes through */ + wmb(); + qusb_phy_enable_clocks(qphy, false); + } else { /* Cable disconnect case */ + + ret = reset_control_assert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset assert failed\n", + __func__); + usleep_range(100, 150); + ret = reset_control_deassert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset deassert failed\n", + __func__); + + writel_relaxed(0x1b, + qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_TWO); + + /* enable clock bypass */ + writel_relaxed(0x90, + qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_ONE); + + writel_relaxed(0x0, qphy->tcsr_clamp_dig_n); + /* + * clamp needs asserted before + * power/clocks can be turned off + */ + wmb(); + + qusb_phy_enable_clocks(qphy, false); + qusb_phy_enable_power(qphy, false); + } + qphy->suspended = true; + } else { + /* Bus resume case */ + if (qphy->cable_connected || + (qphy->phy.flags & PHY_HOST_MODE)) { + qusb_phy_enable_clocks(qphy, true); + + /* restore the default clock settings */ + writel_relaxed(analog_ctrl_two, + qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_TWO); + + /* disable clock bypass */ + writel_relaxed(0x80, + qphy->base + QUSB2PHY_PLL_ANALOG_CONTROLS_ONE); + + /* Clear all interrupts on resume */ + writel_relaxed(0x00, + qphy->base + QUSB2PHY_INTR_CTRL); + + /* Makes sure that above write goes through */ + wmb(); + } else { /* Cable connect case */ + writel_relaxed(0x1, qphy->tcsr_clamp_dig_n); + + /* + * clamp needs de-asserted before + * power/clocks can be turned on + */ + wmb(); + + qusb_phy_enable_power(qphy, true); + ret = reset_control_assert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset assert failed\n", + __func__); + usleep_range(100, 150); + ret = reset_control_deassert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset deassert failed\n", + __func__); + + qusb_phy_enable_clocks(qphy, true); + } + qphy->suspended = false; + } + + return 0; +} + +static int qusb_phy_notify_connect(struct usb_phy *phy, + enum usb_device_speed speed) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + + qphy->cable_connected = true; + + if (qphy->qusb_phy_host_init_seq && qphy->phy.flags & PHY_HOST_MODE) + qusb_phy_host_init(phy); + + dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n", + qphy->cable_connected); + return 0; +} + +static int qusb_phy_notify_disconnect(struct usb_phy *phy, + enum usb_device_speed speed) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + + qphy->cable_connected = false; + + dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n", + qphy->cable_connected); + return 0; +} + +static int qusb_phy_dpdm_regulator_enable(struct regulator_dev *rdev) +{ + struct qusb_phy *qphy = rdev_get_drvdata(rdev); + + dev_dbg(qphy->phy.dev, "%s\n", __func__); + return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPF_DMF); +} + +static int qusb_phy_dpdm_regulator_disable(struct regulator_dev *rdev) +{ + struct qusb_phy *qphy = rdev_get_drvdata(rdev); + + dev_dbg(qphy->phy.dev, "%s\n", __func__); + return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPR_DMR); +} + +static int qusb_phy_dpdm_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct qusb_phy *qphy = rdev_get_drvdata(rdev); + + dev_dbg(qphy->phy.dev, "%s qphy->rm_pulldown = %d\n", __func__, + qphy->rm_pulldown); + return qphy->rm_pulldown; +} + +static struct regulator_ops qusb_phy_dpdm_regulator_ops = { + .enable = qusb_phy_dpdm_regulator_enable, + .disable = qusb_phy_dpdm_regulator_disable, + .is_enabled = qusb_phy_dpdm_regulator_is_enabled, +}; + +static int qusb_phy_regulator_init(struct qusb_phy *qphy) +{ + struct device *dev = qphy->phy.dev; + struct regulator_config cfg = {}; + struct regulator_init_data *init_data; + + init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL); + if (!init_data) + return -ENOMEM; + + init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS; + qphy->dpdm_rdesc.owner = THIS_MODULE; + qphy->dpdm_rdesc.type = REGULATOR_VOLTAGE; + qphy->dpdm_rdesc.ops = &qusb_phy_dpdm_regulator_ops; + qphy->dpdm_rdesc.name = kbasename(dev->of_node->full_name); + + cfg.dev = dev; + cfg.init_data = init_data; + cfg.driver_data = qphy; + cfg.of_node = dev->of_node; + + qphy->dpdm_rdev = devm_regulator_register(dev, &qphy->dpdm_rdesc, &cfg); + if (IS_ERR(qphy->dpdm_rdev)) + return PTR_ERR(qphy->dpdm_rdev); + + return 0; +} + +static int qusb_phy_probe(struct platform_device *pdev) +{ + struct qusb_phy *qphy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0, size = 0; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + qphy->phy.dev = dev; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qusb_phy_base"); + qphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->base)) + return PTR_ERR(qphy->base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "emu_phy_base"); + if (res) { + qphy->emu_phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->emu_phy_base)) { + dev_dbg(dev, "couldn't ioremap emu_phy_base\n"); + qphy->emu_phy_base = NULL; + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "tcsr_clamp_dig_n_1p8"); + if (res) { + qphy->tcsr_clamp_dig_n = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->tcsr_clamp_dig_n)) { + dev_dbg(dev, "couldn't ioremap tcsr_clamp_dig_n\n"); + return PTR_ERR(qphy->tcsr_clamp_dig_n); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "efuse_addr"); + if (res) { + qphy->efuse_reg = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (!IS_ERR_OR_NULL(qphy->efuse_reg)) { + ret = of_property_read_u32(dev->of_node, + "qcom,efuse-bit-pos", + &qphy->efuse_bit_pos); + if (!ret) { + ret = of_property_read_u32(dev->of_node, + "qcom,efuse-num-bits", + &qphy->efuse_num_of_bits); + } + + if (ret) { + dev_err(dev, + "DT Value for efuse is invalid.\n"); + return -EINVAL; + } + } + } + + qphy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(qphy->ref_clk_src)) + dev_dbg(dev, "clk get failed for ref_clk_src\n"); + + qphy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(qphy->ref_clk)) + dev_dbg(dev, "clk get failed for ref_clk\n"); + else + clk_set_rate(qphy->ref_clk, 19200000); + + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk"); + if (IS_ERR(qphy->cfg_ahb_clk)) { + ret = PTR_ERR(qphy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "clk get failed for cfg_ahb_clk ret %d\n", ret); + return ret; + } + } + + qphy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(qphy->phy_reset)) + return PTR_ERR(qphy->phy_reset); + + qphy->emulation = of_property_read_bool(dev->of_node, + "qcom,emulation"); + + of_get_property(dev->of_node, "qcom,emu-init-seq", &size); + if (size) { + qphy->emu_init_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->emu_init_seq) { + qphy->emu_init_seq_len = + (size / sizeof(*qphy->emu_init_seq)); + if (qphy->emu_init_seq_len % 2) { + dev_err(dev, "invalid emu_init_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,emu-init-seq", + qphy->emu_init_seq, + qphy->emu_init_seq_len); + } else { + dev_dbg(dev, + "error allocating memory for emu_init_seq\n"); + } + } + + size = 0; + of_get_property(dev->of_node, "qcom,phy-pll-reset-seq", &size); + if (size) { + qphy->phy_pll_reset_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->phy_pll_reset_seq) { + qphy->phy_pll_reset_seq_len = + (size / sizeof(*qphy->phy_pll_reset_seq)); + if (qphy->phy_pll_reset_seq_len % 2) { + dev_err(dev, "invalid phy_pll_reset_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,phy-pll-reset-seq", + qphy->phy_pll_reset_seq, + qphy->phy_pll_reset_seq_len); + } else { + dev_dbg(dev, + "error allocating memory for phy_pll_reset_seq\n"); + } + } + + size = 0; + of_get_property(dev->of_node, "qcom,emu-dcm-reset-seq", &size); + if (size) { + qphy->emu_dcm_reset_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->emu_dcm_reset_seq) { + qphy->emu_dcm_reset_seq_len = + (size / sizeof(*qphy->emu_dcm_reset_seq)); + if (qphy->emu_dcm_reset_seq_len % 2) { + dev_err(dev, "invalid emu_dcm_reset_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,emu-dcm-reset-seq", + qphy->emu_dcm_reset_seq, + qphy->emu_dcm_reset_seq_len); + } else { + dev_dbg(dev, + "error allocating memory for emu_dcm_reset_seq\n"); + } + } + + size = 0; + of_get_property(dev->of_node, "qcom,qusb-phy-init-seq", &size); + if (size) { + qphy->qusb_phy_init_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->qusb_phy_init_seq) { + qphy->init_seq_len = + (size / sizeof(*qphy->qusb_phy_init_seq)); + if (qphy->init_seq_len % 2) { + dev_err(dev, "invalid init_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,qusb-phy-init-seq", + qphy->qusb_phy_init_seq, + qphy->init_seq_len); + } else { + dev_err(dev, + "error allocating memory for phy_init_seq\n"); + } + } + + qphy->host_init_seq_len = of_property_count_elems_of_size(dev->of_node, + "qcom,qusb-phy-host-init-seq", + sizeof(*qphy->qusb_phy_host_init_seq)); + if (qphy->host_init_seq_len > 0) { + qphy->qusb_phy_host_init_seq = devm_kcalloc(dev, + qphy->host_init_seq_len, + sizeof(*qphy->qusb_phy_host_init_seq), + GFP_KERNEL); + if (qphy->qusb_phy_host_init_seq) + of_property_read_u32_array(dev->of_node, + "qcom,qusb-phy-host-init-seq", + qphy->qusb_phy_host_init_seq, + qphy->host_init_seq_len); + else + return -ENOMEM; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) qphy->vdd_levels, + ARRAY_SIZE(qphy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + return ret; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,vdda33-voltage-level", + (u32 *) qphy->vdda33_levels, + ARRAY_SIZE(qphy->vdda33_levels)); + if (ret == -EINVAL) { + qphy->vdda33_levels[0] = QUSB2PHY_3P3_VOL_MIN; + qphy->vdda33_levels[1] = QUSB2PHY_3P3_VOL_MIN; + qphy->vdda33_levels[2] = QUSB2PHY_3P3_VOL_MAX; + } else if (ret) { + dev_err(dev, "error reading qcom,vdda33-voltage-level property\n"); + return ret; + } + + qphy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(qphy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + return PTR_ERR(qphy->vdd); + } + + qphy->vdda33 = devm_regulator_get(dev, "vdda33"); + if (IS_ERR(qphy->vdda33)) { + dev_err(dev, "unable to get vdda33 supply\n"); + return PTR_ERR(qphy->vdda33); + } + + qphy->vdda18 = devm_regulator_get(dev, "vdda18"); + if (IS_ERR(qphy->vdda18)) { + dev_err(dev, "unable to get vdda18 supply\n"); + return PTR_ERR(qphy->vdda18); + } + + qphy->vdda12 = devm_regulator_get(dev, "vdda12"); + if (IS_ERR(qphy->vdda12)) { + dev_err(dev, "unable to get vdda12 supply\n"); + return PTR_ERR(qphy->vdda12); + } + + mutex_init(&qphy->lock); + + platform_set_drvdata(pdev, qphy); + + qphy->phy.label = "msm-qusb-phy-v2"; + qphy->phy.init = qusb_phy_init; + qphy->phy.set_suspend = qusb_phy_set_suspend; + qphy->phy.shutdown = qusb_phy_shutdown; + qphy->phy.type = USB_PHY_TYPE_USB2; + qphy->phy.notify_connect = qusb_phy_notify_connect; + qphy->phy.notify_disconnect = qusb_phy_notify_disconnect; + + ret = usb_add_phy_dev(&qphy->phy); + if (ret) + return ret; + + ret = qusb_phy_regulator_init(qphy); + if (ret) + usb_remove_phy(&qphy->phy); + + /* de-asseert clamp dig n to reduce leakage on 1p8 upon boot up */ + writel_relaxed(0x0, qphy->tcsr_clamp_dig_n); + + return ret; +} + +static int qusb_phy_remove(struct platform_device *pdev) +{ + struct qusb_phy *qphy = platform_get_drvdata(pdev); + + usb_remove_phy(&qphy->phy); + + if (qphy->clocks_enabled) { + clk_disable_unprepare(qphy->cfg_ahb_clk); + clk_disable_unprepare(qphy->ref_clk); + clk_disable_unprepare(qphy->ref_clk_src); + qphy->clocks_enabled = false; + } + + qusb_phy_enable_power(qphy, false); + + return 0; +} + +static const struct of_device_id qusb_phy_id_table[] = { + { .compatible = "qcom,qusb2phy-v2", }, + { }, +}; +MODULE_DEVICE_TABLE(of, qusb_phy_id_table); + +static struct platform_driver qusb_phy_driver = { + .probe = qusb_phy_probe, + .remove = qusb_phy_remove, + .driver = { + .name = "msm-qusb-phy-v2", + .of_match_table = of_match_ptr(qusb_phy_id_table), + }, +}; + +module_platform_driver(qusb_phy_driver); + +MODULE_DESCRIPTION("MSM QUSB2 PHY v2 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-qusb.c b/drivers/usb/phy/phy-msm-qusb.c new file mode 100644 index 000000000000..bd2722e8fc48 --- /dev/null +++ b/drivers/usb/phy/phy-msm-qusb.c @@ -0,0 +1,1204 @@ +/* + * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/usb/phy.h> +#include <linux/usb/msm_hsusb.h> +#include <linux/reset.h> + +#define QUSB2PHY_PLL_STATUS 0x38 +#define QUSB2PHY_PLL_LOCK BIT(5) + +#define QUSB2PHY_PORT_QC1 0x70 +#define VDM_SRC_EN BIT(4) +#define VDP_SRC_EN BIT(2) + +#define QUSB2PHY_PORT_QC2 0x74 +#define RDM_UP_EN BIT(1) +#define RDP_UP_EN BIT(3) +#define RPUM_LOW_EN BIT(4) +#define RPUP_LOW_EN BIT(5) + +#define QUSB2PHY_PORT_POWERDOWN 0xB4 +#define CLAMP_N_EN BIT(5) +#define FREEZIO_N BIT(1) +#define POWER_DOWN BIT(0) + +#define QUSB2PHY_PORT_TEST_CTRL 0xB8 + +#define QUSB2PHY_PWR_CTRL1 0x210 +#define PWR_CTRL1_CLAMP_N_EN BIT(1) +#define PWR_CTRL1_POWR_DOWN BIT(0) + +#define QUSB2PHY_PLL_COMMON_STATUS_ONE 0x1A0 +#define CORE_READY_STATUS BIT(0) + +#define QUSB2PHY_PORT_UTMI_CTRL1 0xC0 +#define TERM_SELECT BIT(4) +#define XCVR_SELECT_FS BIT(2) +#define OP_MODE_NON_DRIVE BIT(0) + +#define QUSB2PHY_PORT_UTMI_CTRL2 0xC4 +#define UTMI_ULPI_SEL BIT(7) +#define UTMI_TEST_MUX_SEL BIT(6) + +#define QUSB2PHY_PLL_TEST 0x04 +#define CLK_REF_SEL BIT(7) + +#define QUSB2PHY_PORT_TUNE1 0x80 +#define QUSB2PHY_PORT_TUNE2 0x84 +#define QUSB2PHY_PORT_TUNE3 0x88 +#define QUSB2PHY_PORT_TUNE4 0x8C +#define QUSB2PHY_PORT_TUNE5 0x90 + +/* In case Efuse register shows zero, use this value */ +#define TUNE2_DEFAULT_HIGH_NIBBLE 0xB +#define TUNE2_DEFAULT_LOW_NIBBLE 0x3 + +/* Get TUNE2's high nibble value read from efuse */ +#define TUNE2_HIGH_NIBBLE_VAL(val, pos, mask) ((val >> pos) & mask) + +#define QUSB2PHY_PORT_INTR_CTRL 0xBC +#define CHG_DET_INTR_EN BIT(4) +#define DMSE_INTR_HIGH_SEL BIT(3) +#define DMSE_INTR_EN BIT(2) +#define DPSE_INTR_HIGH_SEL BIT(1) +#define DPSE_INTR_EN BIT(0) + +#define QUSB2PHY_PORT_UTMI_STATUS 0xF4 +#define LINESTATE_DP BIT(0) +#define LINESTATE_DM BIT(1) + + +#define QUSB2PHY_1P8_VOL_MIN 1800000 /* uV */ +#define QUSB2PHY_1P8_VOL_MAX 1800000 /* uV */ +#define QUSB2PHY_1P8_HPM_LOAD 30000 /* uA */ + +#define QUSB2PHY_3P3_VOL_MIN 3075000 /* uV */ +#define QUSB2PHY_3P3_VOL_MAX 3200000 /* uV */ +#define QUSB2PHY_3P3_HPM_LOAD 30000 /* uA */ + +#define QUSB2PHY_REFCLK_ENABLE BIT(0) + +unsigned int tune1; +module_param(tune1, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tune1, "QUSB PHY TUNE1"); + +unsigned int tune2; +module_param(tune2, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tune2, "QUSB PHY TUNE2"); + +unsigned int tune3; +module_param(tune3, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tune3, "QUSB PHY TUNE3"); + +unsigned int tune4; +module_param(tune4, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tune4, "QUSB PHY TUNE4"); + +unsigned int tune5; +module_param(tune5, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tune5, "QUSB PHY TUNE5"); + + +struct qusb_phy { + struct usb_phy phy; + void __iomem *base; + void __iomem *tune2_efuse_reg; + void __iomem *ref_clk_base; + void __iomem *tcsr_clamp_dig_n; + + struct clk *ref_clk_src; + struct clk *ref_clk; + struct clk *cfg_ahb_clk; + struct reset_control *phy_reset; + + struct regulator *vdd; + struct regulator *vdda33; + struct regulator *vdda18; + int vdd_levels[3]; /* none, low, high */ + int init_seq_len; + int *qusb_phy_init_seq; + u32 major_rev; + + u32 tune2_val; + int tune2_efuse_bit_pos; + int tune2_efuse_num_of_bits; + + bool power_enabled; + bool clocks_enabled; + bool cable_connected; + bool suspended; + bool ulpi_mode; + bool rm_pulldown; + bool is_se_clk; + + struct regulator_desc dpdm_rdesc; + struct regulator_dev *dpdm_rdev; + + /* emulation targets specific */ + void __iomem *emu_phy_base; + bool emulation; + int *emu_init_seq; + int emu_init_seq_len; + int *phy_pll_reset_seq; + int phy_pll_reset_seq_len; + int *emu_dcm_reset_seq; + int emu_dcm_reset_seq_len; + bool put_into_high_z_state; + struct mutex phy_lock; +}; + +static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on) +{ + dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d on:%d\n", + __func__, qphy->clocks_enabled, on); + + if (!qphy->clocks_enabled && on) { + clk_prepare_enable(qphy->ref_clk_src); + clk_prepare_enable(qphy->ref_clk); + clk_prepare_enable(qphy->cfg_ahb_clk); + qphy->clocks_enabled = true; + } + + if (qphy->clocks_enabled && !on) { + clk_disable_unprepare(qphy->ref_clk); + clk_disable_unprepare(qphy->ref_clk_src); + clk_disable_unprepare(qphy->cfg_ahb_clk); + qphy->clocks_enabled = false; + } + + dev_dbg(qphy->phy.dev, "%s(): clocks_enabled:%d\n", __func__, + qphy->clocks_enabled); +} + +static int qusb_phy_config_vdd(struct qusb_phy *qphy, int high) +{ + int min, ret; + + min = high ? 1 : 0; /* low or none? */ + ret = regulator_set_voltage(qphy->vdd, qphy->vdd_levels[min], + qphy->vdd_levels[2]); + if (ret) { + dev_err(qphy->phy.dev, "unable to set voltage for qusb vdd\n"); + return ret; + } + + dev_dbg(qphy->phy.dev, "min_vol:%d max_vol:%d\n", + qphy->vdd_levels[min], qphy->vdd_levels[2]); + return ret; +} + +static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on) +{ + int ret = 0; + + dev_dbg(qphy->phy.dev, "%s turn %s regulators. power_enabled:%d\n", + __func__, on ? "on" : "off", qphy->power_enabled); + + if (qphy->power_enabled == on) { + dev_dbg(qphy->phy.dev, "PHYs' regulators are already ON.\n"); + return 0; + } + + if (!on) + goto disable_vdda33; + + ret = qusb_phy_config_vdd(qphy, true); + if (ret) { + dev_err(qphy->phy.dev, "Unable to config VDD:%d\n", + ret); + goto err_vdd; + } + + ret = regulator_enable(qphy->vdd); + if (ret) { + dev_err(qphy->phy.dev, "Unable to enable VDD\n"); + goto unconfig_vdd; + } + + ret = regulator_set_load(qphy->vdda18, QUSB2PHY_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(qphy->phy.dev, "Unable to set HPM of vdda18:%d\n", ret); + goto disable_vdd; + } + + ret = regulator_set_voltage(qphy->vdda18, QUSB2PHY_1P8_VOL_MIN, + QUSB2PHY_1P8_VOL_MAX); + if (ret) { + dev_err(qphy->phy.dev, + "Unable to set voltage for vdda18:%d\n", ret); + goto put_vdda18_lpm; + } + + ret = regulator_enable(qphy->vdda18); + if (ret) { + dev_err(qphy->phy.dev, "Unable to enable vdda18:%d\n", ret); + goto unset_vdda18; + } + + ret = regulator_set_load(qphy->vdda33, QUSB2PHY_3P3_HPM_LOAD); + if (ret < 0) { + dev_err(qphy->phy.dev, "Unable to set HPM of vdda33:%d\n", ret); + goto disable_vdda18; + } + + ret = regulator_set_voltage(qphy->vdda33, QUSB2PHY_3P3_VOL_MIN, + QUSB2PHY_3P3_VOL_MAX); + if (ret) { + dev_err(qphy->phy.dev, + "Unable to set voltage for vdda33:%d\n", ret); + goto put_vdda33_lpm; + } + + ret = regulator_enable(qphy->vdda33); + if (ret) { + dev_err(qphy->phy.dev, "Unable to enable vdda33:%d\n", ret); + goto unset_vdd33; + } + + qphy->power_enabled = true; + + pr_debug("%s(): QUSB PHY's regulators are turned ON.\n", __func__); + return ret; + +disable_vdda33: + ret = regulator_disable(qphy->vdda33); + if (ret) + dev_err(qphy->phy.dev, "Unable to disable vdda33:%d\n", ret); + +unset_vdd33: + ret = regulator_set_voltage(qphy->vdda33, 0, QUSB2PHY_3P3_VOL_MAX); + if (ret) + dev_err(qphy->phy.dev, + "Unable to set (0) voltage for vdda33:%d\n", ret); + +put_vdda33_lpm: + ret = regulator_set_load(qphy->vdda33, 0); + if (ret < 0) + dev_err(qphy->phy.dev, "Unable to set (0) HPM of vdda33\n"); + +disable_vdda18: + ret = regulator_disable(qphy->vdda18); + if (ret) + dev_err(qphy->phy.dev, "Unable to disable vdda18:%d\n", ret); + +unset_vdda18: + ret = regulator_set_voltage(qphy->vdda18, 0, QUSB2PHY_1P8_VOL_MAX); + if (ret) + dev_err(qphy->phy.dev, + "Unable to set (0) voltage for vdda18:%d\n", ret); + +put_vdda18_lpm: + ret = regulator_set_load(qphy->vdda18, 0); + if (ret < 0) + dev_err(qphy->phy.dev, "Unable to set LPM of vdda18\n"); + +disable_vdd: + ret = regulator_disable(qphy->vdd); + if (ret) + dev_err(qphy->phy.dev, "Unable to disable vdd:%d\n", + ret); + +unconfig_vdd: + ret = qusb_phy_config_vdd(qphy, false); + if (ret) + dev_err(qphy->phy.dev, "Unable unconfig VDD:%d\n", + ret); +err_vdd: + qphy->power_enabled = false; + dev_dbg(qphy->phy.dev, "QUSB PHY's regulators are turned OFF.\n"); + return ret; +} + +static int qusb_phy_update_dpdm(struct usb_phy *phy, int value) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + int ret = 0; + + dev_dbg(phy->dev, "%s value:%d rm_pulldown:%d\n", + __func__, value, qphy->rm_pulldown); + + switch (value) { + case POWER_SUPPLY_DP_DM_DPF_DMF: + dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPF_DMF\n"); + mutex_lock(&qphy->phy_lock); + if (!qphy->rm_pulldown) { + ret = qusb_phy_enable_power(qphy, true); + if (ret >= 0) { + qphy->rm_pulldown = true; + dev_dbg(phy->dev, "DP_DM_F: rm_pulldown:%d\n", + qphy->rm_pulldown); + } + + if (qphy->put_into_high_z_state) { + if (qphy->tcsr_clamp_dig_n) + writel_relaxed(0x1, + qphy->tcsr_clamp_dig_n); + + qusb_phy_enable_clocks(qphy, true); + + dev_dbg(phy->dev, "RESET QUSB PHY\n"); + ret = reset_control_assert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "phyassert failed\n"); + usleep_range(100, 150); + ret = reset_control_deassert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "deassert failed\n"); + + /* + * Phy in non-driving mode leaves Dp and Dm + * lines in high-Z state. Controller power + * collapse is not switching phy to non-driving + * mode causing charger detection failure. Bring + * phy to non-driving mode by overriding + * controller output via UTMI interface. + */ + writel_relaxed(TERM_SELECT | XCVR_SELECT_FS | + OP_MODE_NON_DRIVE, + qphy->base + QUSB2PHY_PORT_UTMI_CTRL1); + writel_relaxed(UTMI_ULPI_SEL | + UTMI_TEST_MUX_SEL, + qphy->base + QUSB2PHY_PORT_UTMI_CTRL2); + + /* Disable PHY */ + writel_relaxed(CLAMP_N_EN | FREEZIO_N | + POWER_DOWN, + qphy->base + QUSB2PHY_PORT_POWERDOWN); + /* Make sure that above write is completed */ + wmb(); + + if (qphy->suspended) + qusb_phy_enable_clocks(qphy, false); + } + } + mutex_unlock(&qphy->phy_lock); + + break; + + case POWER_SUPPLY_DP_DM_DPR_DMR: + dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPR_DMR\n"); + mutex_lock(&qphy->phy_lock); + if (qphy->rm_pulldown) { + if (!qphy->cable_connected) { + if (qphy->tcsr_clamp_dig_n) + writel_relaxed(0x0, + qphy->tcsr_clamp_dig_n); + dev_dbg(phy->dev, "turn off for HVDCP case\n"); + ret = qusb_phy_enable_power(qphy, false); + } + if (ret >= 0) { + qphy->rm_pulldown = false; + dev_dbg(phy->dev, "DP_DM_R: rm_pulldown:%d\n", + qphy->rm_pulldown); + } + } + mutex_unlock(&qphy->phy_lock); + break; + + default: + ret = -EINVAL; + dev_err(phy->dev, "Invalid power supply property(%d)\n", value); + break; + } + + return ret; +} + +static void qusb_phy_get_tune2_param(struct qusb_phy *qphy) +{ + u8 num_of_bits; + u32 bit_mask = 1; + + pr_debug("%s(): num_of_bits:%d bit_pos:%d\n", __func__, + qphy->tune2_efuse_num_of_bits, + qphy->tune2_efuse_bit_pos); + + /* get bit mask based on number of bits to use with efuse reg */ + if (qphy->tune2_efuse_num_of_bits) { + num_of_bits = qphy->tune2_efuse_num_of_bits; + bit_mask = (bit_mask << num_of_bits) - 1; + } + + /* + * Read EFUSE register having TUNE2 parameter's high nibble. + * If efuse register shows value as 0x0, then use default value + * as 0xB as high nibble. Otherwise use efuse register based + * value for this purpose. + */ + qphy->tune2_val = readl_relaxed(qphy->tune2_efuse_reg); + pr_debug("%s(): bit_mask:%d efuse based tune2 value:%d\n", + __func__, bit_mask, qphy->tune2_val); + + qphy->tune2_val = TUNE2_HIGH_NIBBLE_VAL(qphy->tune2_val, + qphy->tune2_efuse_bit_pos, bit_mask); + + if (!qphy->tune2_val) + qphy->tune2_val = TUNE2_DEFAULT_HIGH_NIBBLE; + + /* Get TUNE2 byte value using high and low nibble value */ + qphy->tune2_val = ((qphy->tune2_val << 0x4) | + TUNE2_DEFAULT_LOW_NIBBLE); +} + +static void qusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt, + unsigned long delay) +{ + int i; + + pr_debug("Seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + pr_debug("write 0x%02x to 0x%02x\n", seq[i], seq[i+1]); + writel_relaxed(seq[i], base + seq[i+1]); + if (delay) + usleep_range(delay, (delay + 2000)); + } +} + +static int qusb_phy_init(struct usb_phy *phy) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + int ret, reset_val = 0; + u8 reg; + bool pll_lock_fail = false; + + dev_dbg(phy->dev, "%s\n", __func__); + + ret = qusb_phy_enable_power(qphy, true); + if (ret) + return ret; + + qusb_phy_enable_clocks(qphy, true); + + /* + * ref clock is enabled by default after power on reset. Linux clock + * driver will disable this clock as part of late init if peripheral + * driver(s) does not explicitly votes for it. Linux clock driver also + * does not disable the clock until late init even if peripheral + * driver explicitly requests it and cannot defer the probe until late + * init. Hence, Explicitly disable the clock using register write to + * allow QUSB PHY PLL to lock properly. + */ + if (qphy->ref_clk_base) { + writel_relaxed((readl_relaxed(qphy->ref_clk_base) & + ~QUSB2PHY_REFCLK_ENABLE), + qphy->ref_clk_base); + /* Make sure that above write complete to get ref clk OFF */ + wmb(); + } + + /* Perform phy reset */ + ret = reset_control_assert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__); + usleep_range(100, 150); + ret = reset_control_deassert(qphy->phy_reset); + if (ret) + dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__); + + if (qphy->emulation) { + if (qphy->emu_init_seq) + qusb_phy_write_seq(qphy->emu_phy_base, + qphy->emu_init_seq, qphy->emu_init_seq_len, 0); + + if (qphy->qusb_phy_init_seq) + qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq, + qphy->init_seq_len, 0); + + /* Wait for 5ms as per QUSB2 RUMI sequence */ + usleep_range(5000, 7000); + + if (qphy->phy_pll_reset_seq) + qusb_phy_write_seq(qphy->base, qphy->phy_pll_reset_seq, + qphy->phy_pll_reset_seq_len, 10000); + + if (qphy->emu_dcm_reset_seq) + qusb_phy_write_seq(qphy->emu_phy_base, + qphy->emu_dcm_reset_seq, + qphy->emu_dcm_reset_seq_len, 10000); + + return 0; + } + + /* Disable the PHY */ + if (qphy->major_rev < 2) + writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN, + qphy->base + QUSB2PHY_PORT_POWERDOWN); + else + writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) | + PWR_CTRL1_POWR_DOWN, + qphy->base + QUSB2PHY_PWR_CTRL1); + + /* configure for ULPI mode if requested */ + if (qphy->ulpi_mode) + writel_relaxed(0x0, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2); + + /* save reset value to override based on clk scheme */ + if (qphy->ref_clk_base) + reset_val = readl_relaxed(qphy->base + QUSB2PHY_PLL_TEST); + + if (qphy->qusb_phy_init_seq) + qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq, + qphy->init_seq_len, 0); + + /* + * Check for EFUSE value only if tune2_efuse_reg is available + * and try to read EFUSE value only once i.e. not every USB + * cable connect case. + */ + if (qphy->tune2_efuse_reg) { + if (!qphy->tune2_val) + qusb_phy_get_tune2_param(qphy); + + pr_debug("%s(): Programming TUNE2 parameter as:%x\n", __func__, + qphy->tune2_val); + writel_relaxed(qphy->tune2_val, + qphy->base + QUSB2PHY_PORT_TUNE2); + } + + /* If tune modparam set, override tune value */ + + pr_debug("%s():userspecified modparams TUNEX val:0x%x %x %x %x %x\n", + __func__, tune1, tune2, tune3, tune4, tune5); + if (tune1) + writel_relaxed(tune1, + qphy->base + QUSB2PHY_PORT_TUNE1); + + if (tune2) + writel_relaxed(tune2, + qphy->base + QUSB2PHY_PORT_TUNE2); + + if (tune3) + writel_relaxed(tune3, + qphy->base + QUSB2PHY_PORT_TUNE3); + + if (tune4) + writel_relaxed(tune4, + qphy->base + QUSB2PHY_PORT_TUNE4); + + if (tune5) + writel_relaxed(tune5, + qphy->base + QUSB2PHY_PORT_TUNE5); + + /* ensure above writes are completed before re-enabling PHY */ + wmb(); + + /* Enable the PHY */ + if (qphy->major_rev < 2) + writel_relaxed(CLAMP_N_EN | FREEZIO_N, + qphy->base + QUSB2PHY_PORT_POWERDOWN); + else + writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) & + ~PWR_CTRL1_POWR_DOWN, + qphy->base + QUSB2PHY_PWR_CTRL1); + + /* Ensure above write is completed before turning ON ref clk */ + wmb(); + + /* Require to get phy pll lock successfully */ + usleep_range(150, 160); + + /* Turn on phy ref_clk if DIFF_CLK else select SE_CLK */ + if (qphy->ref_clk_base) { + if (!qphy->is_se_clk) { + reset_val &= ~CLK_REF_SEL; + writel_relaxed((readl_relaxed(qphy->ref_clk_base) | + QUSB2PHY_REFCLK_ENABLE), + qphy->ref_clk_base); + } else { + reset_val |= CLK_REF_SEL; + writel_relaxed(reset_val, + qphy->base + QUSB2PHY_PLL_TEST); + } + + /* Make sure above write is completed to get PLL source clock */ + wmb(); + + /* Required to get PHY PLL lock successfully */ + usleep_range(100, 110); + } + + if (qphy->major_rev < 2) { + reg = readb_relaxed(qphy->base + QUSB2PHY_PLL_STATUS); + dev_dbg(phy->dev, "QUSB2PHY_PLL_STATUS:%x\n", reg); + if (!(reg & QUSB2PHY_PLL_LOCK)) + pll_lock_fail = true; + } else { + reg = readb_relaxed(qphy->base + + QUSB2PHY_PLL_COMMON_STATUS_ONE); + dev_dbg(phy->dev, "QUSB2PHY_PLL_COMMON_STATUS_ONE:%x\n", reg); + if (!(reg & CORE_READY_STATUS)) + pll_lock_fail = true; + } + + if (pll_lock_fail) { + dev_err(phy->dev, "QUSB PHY PLL LOCK fails:%x\n", reg); + WARN_ON(1); + } + + return 0; +} + +static void qusb_phy_shutdown(struct usb_phy *phy) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + + dev_dbg(phy->dev, "%s\n", __func__); + + qusb_phy_enable_clocks(qphy, true); + + /* Disable the PHY */ + if (qphy->major_rev < 2) + writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN, + qphy->base + QUSB2PHY_PORT_POWERDOWN); + else + writel_relaxed(readl_relaxed(qphy->base + QUSB2PHY_PWR_CTRL1) | + PWR_CTRL1_POWR_DOWN, + qphy->base + QUSB2PHY_PWR_CTRL1); + wmb(); + + qusb_phy_enable_clocks(qphy, false); +} +/** + * Performs QUSB2 PHY suspend/resume functionality. + * + * @uphy - usb phy pointer. + * @suspend - to enable suspend or not. 1 - suspend, 0 - resume + * + */ +static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + u32 linestate = 0, intr_mask = 0; + + if (qphy->suspended && suspend) { + dev_dbg(phy->dev, "%s: USB PHY is already suspended\n", + __func__); + return 0; + } + + if (suspend) { + /* Bus suspend case */ + if (qphy->cable_connected || + (qphy->phy.flags & PHY_HOST_MODE)) { + /* Clear all interrupts */ + writel_relaxed(0x00, + qphy->base + QUSB2PHY_PORT_INTR_CTRL); + + linestate = readl_relaxed(qphy->base + + QUSB2PHY_PORT_UTMI_STATUS); + + /* + * D+/D- interrupts are level-triggered, but we are + * only interested if the line state changes, so enable + * the high/low trigger based on current state. In + * other words, enable the triggers _opposite_ of what + * the current D+/D- levels are. + * e.g. if currently D+ high, D- low (HS 'J'/Suspend), + * configure the mask to trigger on D+ low OR D- high + */ + intr_mask = DPSE_INTR_EN | DMSE_INTR_EN; + if (!(linestate & LINESTATE_DP)) /* D+ low */ + intr_mask |= DPSE_INTR_HIGH_SEL; + if (!(linestate & LINESTATE_DM)) /* D- low */ + intr_mask |= DMSE_INTR_HIGH_SEL; + + writel_relaxed(intr_mask, + qphy->base + QUSB2PHY_PORT_INTR_CTRL); + + if (linestate & (LINESTATE_DP | LINESTATE_DM)) { + /* enable phy auto-resume */ + writel_relaxed(0x0C, + qphy->base + QUSB2PHY_PORT_TEST_CTRL); + /* flush the previous write before next write */ + wmb(); + writel_relaxed(0x04, + qphy->base + QUSB2PHY_PORT_TEST_CTRL); + } + + + dev_dbg(phy->dev, "%s: intr_mask = %x\n", + __func__, intr_mask); + + /* Makes sure that above write goes through */ + wmb(); + + qusb_phy_enable_clocks(qphy, false); + } else { /* Disconnect case */ + mutex_lock(&qphy->phy_lock); + /* Disable all interrupts */ + writel_relaxed(0x00, + qphy->base + QUSB2PHY_PORT_INTR_CTRL); + + /* Disable PHY */ + writel_relaxed(POWER_DOWN, + qphy->base + QUSB2PHY_PORT_POWERDOWN); + /* Make sure that above write is completed */ + wmb(); + + qusb_phy_enable_clocks(qphy, false); + if (qphy->tcsr_clamp_dig_n) + writel_relaxed(0x0, + qphy->tcsr_clamp_dig_n); + /* Do not disable power rails if there is vote for it */ + if (!qphy->rm_pulldown) + qusb_phy_enable_power(qphy, false); + else + dev_dbg(phy->dev, "race with rm_pulldown. Keep ldo ON\n"); + mutex_unlock(&qphy->phy_lock); + + /* + * Set put_into_high_z_state to true so next USB + * cable connect, DPF_DMF request performs PHY + * reset and put it into high-z state. For bootup + * with or without USB cable, it doesn't require + * to put QUSB PHY into high-z state. + */ + qphy->put_into_high_z_state = true; + } + qphy->suspended = true; + } else { + /* Bus suspend case */ + if (qphy->cable_connected || + (qphy->phy.flags & PHY_HOST_MODE)) { + qusb_phy_enable_clocks(qphy, true); + /* Clear all interrupts on resume */ + writel_relaxed(0x00, + qphy->base + QUSB2PHY_PORT_INTR_CTRL); + } else { + qusb_phy_enable_power(qphy, true); + if (qphy->tcsr_clamp_dig_n) + writel_relaxed(0x1, + qphy->tcsr_clamp_dig_n); + qusb_phy_enable_clocks(qphy, true); + } + qphy->suspended = false; + } + + return 0; +} + +static int qusb_phy_notify_connect(struct usb_phy *phy, + enum usb_device_speed speed) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + + qphy->cable_connected = true; + + dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n", + qphy->cable_connected); + return 0; +} + +static int qusb_phy_notify_disconnect(struct usb_phy *phy, + enum usb_device_speed speed) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + + qphy->cable_connected = false; + + dev_dbg(phy->dev, "QUSB PHY: connect notification cable_connected=%d\n", + qphy->cable_connected); + return 0; +} + +static int qusb_phy_dpdm_regulator_enable(struct regulator_dev *rdev) +{ + struct qusb_phy *qphy = rdev_get_drvdata(rdev); + + dev_dbg(qphy->phy.dev, "%s\n", __func__); + return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPF_DMF); +} + +static int qusb_phy_dpdm_regulator_disable(struct regulator_dev *rdev) +{ + struct qusb_phy *qphy = rdev_get_drvdata(rdev); + + dev_dbg(qphy->phy.dev, "%s\n", __func__); + return qusb_phy_update_dpdm(&qphy->phy, POWER_SUPPLY_DP_DM_DPR_DMR); +} + +static int qusb_phy_dpdm_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct qusb_phy *qphy = rdev_get_drvdata(rdev); + + dev_dbg(qphy->phy.dev, "%s qphy->rm_pulldown = %d\n", __func__, + qphy->rm_pulldown); + return qphy->rm_pulldown; +} + +static struct regulator_ops qusb_phy_dpdm_regulator_ops = { + .enable = qusb_phy_dpdm_regulator_enable, + .disable = qusb_phy_dpdm_regulator_disable, + .is_enabled = qusb_phy_dpdm_regulator_is_enabled, +}; + +static int qusb_phy_regulator_init(struct qusb_phy *qphy) +{ + struct device *dev = qphy->phy.dev; + struct regulator_config cfg = {}; + struct regulator_init_data *init_data; + + init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL); + if (!init_data) + return -ENOMEM; + + init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS; + qphy->dpdm_rdesc.owner = THIS_MODULE; + qphy->dpdm_rdesc.type = REGULATOR_VOLTAGE; + qphy->dpdm_rdesc.ops = &qusb_phy_dpdm_regulator_ops; + qphy->dpdm_rdesc.name = kbasename(dev->of_node->full_name); + + cfg.dev = dev; + cfg.init_data = init_data; + cfg.driver_data = qphy; + cfg.of_node = dev->of_node; + + qphy->dpdm_rdev = devm_regulator_register(dev, &qphy->dpdm_rdesc, &cfg); + if (IS_ERR(qphy->dpdm_rdev)) + return PTR_ERR(qphy->dpdm_rdev); + + return 0; +} + +static int qusb_phy_probe(struct platform_device *pdev) +{ + struct qusb_phy *qphy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0, size = 0; + const char *phy_type; + bool hold_phy_reset; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + qphy->phy.dev = dev; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qusb_phy_base"); + qphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->base)) + return PTR_ERR(qphy->base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "emu_phy_base"); + if (res) { + qphy->emu_phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->emu_phy_base)) { + dev_dbg(dev, "couldn't ioremap emu_phy_base\n"); + qphy->emu_phy_base = NULL; + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "tune2_efuse_addr"); + if (res) { + qphy->tune2_efuse_reg = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (!IS_ERR_OR_NULL(qphy->tune2_efuse_reg)) { + ret = of_property_read_u32(dev->of_node, + "qcom,tune2-efuse-bit-pos", + &qphy->tune2_efuse_bit_pos); + if (!ret) { + ret = of_property_read_u32(dev->of_node, + "qcom,tune2-efuse-num-bits", + &qphy->tune2_efuse_num_of_bits); + } + + if (ret) { + dev_err(dev, "DT Value for tune2 efuse is invalid.\n"); + return -EINVAL; + } + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "ref_clk_addr"); + if (res) { + qphy->ref_clk_base = devm_ioremap_nocache(dev, + res->start, resource_size(res)); + if (IS_ERR(qphy->ref_clk_base)) { + dev_dbg(dev, "ref_clk_address is not available.\n"); + return PTR_ERR(qphy->ref_clk_base); + } + + ret = of_property_read_string(dev->of_node, + "qcom,phy-clk-scheme", &phy_type); + if (ret) { + dev_err(dev, "error need qsub_phy_clk_scheme.\n"); + return ret; + } + + if (!strcasecmp(phy_type, "cml")) { + qphy->is_se_clk = false; + } else if (!strcasecmp(phy_type, "cmos")) { + qphy->is_se_clk = true; + } else { + dev_err(dev, "erro invalid qusb_phy_clk_scheme\n"); + return -EINVAL; + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "tcsr_clamp_dig_n_1p8"); + if (res) { + qphy->tcsr_clamp_dig_n = devm_ioremap_nocache(dev, + res->start, resource_size(res)); + if (IS_ERR(qphy->tcsr_clamp_dig_n)) { + dev_err(dev, "err reading tcsr_clamp_dig_n\n"); + qphy->tcsr_clamp_dig_n = NULL; + } + } + + + qphy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(qphy->ref_clk_src)) + dev_dbg(dev, "clk get failed for ref_clk_src\n"); + + qphy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(qphy->ref_clk)) + dev_dbg(dev, "clk get failed for ref_clk\n"); + else + clk_set_rate(qphy->ref_clk, 19200000); + + qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk"); + if (IS_ERR(qphy->cfg_ahb_clk)) + return PTR_ERR(qphy->cfg_ahb_clk); + + qphy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(qphy->phy_reset)) + return PTR_ERR(qphy->phy_reset); + + qphy->emulation = of_property_read_bool(dev->of_node, + "qcom,emulation"); + + of_get_property(dev->of_node, "qcom,emu-init-seq", &size); + if (size) { + qphy->emu_init_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->emu_init_seq) { + qphy->emu_init_seq_len = + (size / sizeof(*qphy->emu_init_seq)); + if (qphy->emu_init_seq_len % 2) { + dev_err(dev, "invalid emu_init_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,emu-init-seq", + qphy->emu_init_seq, + qphy->emu_init_seq_len); + } else { + dev_dbg(dev, "error allocating memory for emu_init_seq\n"); + } + } + + size = 0; + of_get_property(dev->of_node, "qcom,phy-pll-reset-seq", &size); + if (size) { + qphy->phy_pll_reset_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->phy_pll_reset_seq) { + qphy->phy_pll_reset_seq_len = + (size / sizeof(*qphy->phy_pll_reset_seq)); + if (qphy->phy_pll_reset_seq_len % 2) { + dev_err(dev, "invalid phy_pll_reset_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,phy-pll-reset-seq", + qphy->phy_pll_reset_seq, + qphy->phy_pll_reset_seq_len); + } else { + dev_dbg(dev, "error allocating memory for phy_pll_reset_seq\n"); + } + } + + size = 0; + of_get_property(dev->of_node, "qcom,emu-dcm-reset-seq", &size); + if (size) { + qphy->emu_dcm_reset_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->emu_dcm_reset_seq) { + qphy->emu_dcm_reset_seq_len = + (size / sizeof(*qphy->emu_dcm_reset_seq)); + if (qphy->emu_dcm_reset_seq_len % 2) { + dev_err(dev, "invalid emu_dcm_reset_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,emu-dcm-reset-seq", + qphy->emu_dcm_reset_seq, + qphy->emu_dcm_reset_seq_len); + } else { + dev_dbg(dev, "error allocating memory for emu_dcm_reset_seq\n"); + } + } + + size = 0; + of_get_property(dev->of_node, "qcom,qusb-phy-init-seq", &size); + if (size) { + qphy->qusb_phy_init_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (qphy->qusb_phy_init_seq) { + qphy->init_seq_len = + (size / sizeof(*qphy->qusb_phy_init_seq)); + if (qphy->init_seq_len % 2) { + dev_err(dev, "invalid init_seq_len\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,qusb-phy-init-seq", + qphy->qusb_phy_init_seq, + qphy->init_seq_len); + } else { + dev_err(dev, "error allocating memory for phy_init_seq\n"); + } + } + + qphy->ulpi_mode = false; + ret = of_property_read_string(dev->of_node, "phy_type", &phy_type); + + if (!ret) { + if (!strcasecmp(phy_type, "ulpi")) + qphy->ulpi_mode = true; + } else { + dev_err(dev, "error reading phy_type property\n"); + return ret; + } + + hold_phy_reset = of_property_read_bool(dev->of_node, "qcom,hold-reset"); + + /* use default major revision as 2 */ + qphy->major_rev = 2; + ret = of_property_read_u32(dev->of_node, "qcom,major-rev", + &qphy->major_rev); + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) qphy->vdd_levels, + ARRAY_SIZE(qphy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + return ret; + } + + qphy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(qphy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + return PTR_ERR(qphy->vdd); + } + + qphy->vdda33 = devm_regulator_get(dev, "vdda33"); + if (IS_ERR(qphy->vdda33)) { + dev_err(dev, "unable to get vdda33 supply\n"); + return PTR_ERR(qphy->vdda33); + } + + qphy->vdda18 = devm_regulator_get(dev, "vdda18"); + if (IS_ERR(qphy->vdda18)) { + dev_err(dev, "unable to get vdda18 supply\n"); + return PTR_ERR(qphy->vdda18); + } + + mutex_init(&qphy->phy_lock); + platform_set_drvdata(pdev, qphy); + + qphy->phy.label = "msm-qusb-phy"; + qphy->phy.init = qusb_phy_init; + qphy->phy.set_suspend = qusb_phy_set_suspend; + qphy->phy.shutdown = qusb_phy_shutdown; + qphy->phy.type = USB_PHY_TYPE_USB2; + qphy->phy.notify_connect = qusb_phy_notify_connect; + qphy->phy.notify_disconnect = qusb_phy_notify_disconnect; + + /* + * On some platforms multiple QUSB PHYs are available. If QUSB PHY is + * not used, there is leakage current seen with QUSB PHY related voltage + * rail. Hence keep QUSB PHY into reset state explicitly here. + */ + if (hold_phy_reset) { + ret = reset_control_assert(qphy->phy_reset); + if (ret) + dev_err(dev, "%s:phy_reset assert failed\n", __func__); + } + + ret = usb_add_phy_dev(&qphy->phy); + if (ret) + return ret; + + ret = qusb_phy_regulator_init(qphy); + if (ret) + usb_remove_phy(&qphy->phy); + + /* de-assert clamp dig n to reduce leakage on 1p8 upon boot up */ + if (qphy->tcsr_clamp_dig_n) + writel_relaxed(0x0, qphy->tcsr_clamp_dig_n); + + return ret; +} + +static int qusb_phy_remove(struct platform_device *pdev) +{ + struct qusb_phy *qphy = platform_get_drvdata(pdev); + + usb_remove_phy(&qphy->phy); + + if (qphy->clocks_enabled) { + clk_disable_unprepare(qphy->cfg_ahb_clk); + clk_disable_unprepare(qphy->ref_clk); + clk_disable_unprepare(qphy->ref_clk_src); + qphy->clocks_enabled = false; + } + + qusb_phy_enable_power(qphy, false); + + return 0; +} + +static const struct of_device_id qusb_phy_id_table[] = { + { .compatible = "qcom,qusb2phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, qusb_phy_id_table); + +static struct platform_driver qusb_phy_driver = { + .probe = qusb_phy_probe, + .remove = qusb_phy_remove, + .driver = { + .name = "msm-qusb-phy", + .of_match_table = of_match_ptr(qusb_phy_id_table), + }, +}; + +module_platform_driver(qusb_phy_driver); + +MODULE_DESCRIPTION("MSM QUSB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-ssusb-qmp.c b/drivers/usb/phy/phy-msm-ssusb-qmp.c new file mode 100644 index 000000000000..2bc3c6fa417a --- /dev/null +++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c @@ -0,0 +1,833 @@ +/* + * Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/phy.h> +#include <linux/usb/msm_hsusb.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk.h> +#include <linux/reset.h> + +enum ldo_levels { + VOLTAGE_LEVEL_NONE = 0, + VOLTAGE_LEVEL_MIN, + VOLTAGE_LEVEL_MAX, +}; + +#define INIT_MAX_TIME_USEC 1000 + +/* default CORE votlage and load values */ +#define USB_SSPHY_1P2_VOL_MIN 1200000 /* uV */ +#define USB_SSPHY_1P2_VOL_MAX 1200000 /* uV */ +#define USB_SSPHY_HPM_LOAD 23000 /* uA */ + +#define USB_SSPHY_LOAD_DEFAULT -1 + +/* USB3PHY_PCIE_USB3_PCS_PCS_STATUS bit */ +#define PHYSTATUS BIT(6) + +/* PCIE_USB3_PHY_AUTONOMOUS_MODE_CTRL bits */ +#define ARCVR_DTCT_EN BIT(0) +#define ALFPS_DTCT_EN BIT(1) +#define ARCVR_DTCT_EVENT_SEL BIT(4) + +/* PCIE_USB3_PHY_PCS_MISC_TYPEC_CTRL bits */ + +/* 0 - selects Lane A. 1 - selects Lane B */ +#define SW_PORTSELECT BIT(0) +/* port select mux: 1 - sw control. 0 - HW control*/ +#define SW_PORTSELECT_MX BIT(1) + +enum qmp_phy_rev_reg { + USB3_PHY_PCS_STATUS, + USB3_PHY_AUTONOMOUS_MODE_CTRL, + USB3_PHY_LFPS_RXTERM_IRQ_CLEAR, + USB3_PHY_POWER_DOWN_CONTROL, + USB3_PHY_SW_RESET, + USB3_PHY_START, + USB3_PHY_PCS_MISC_TYPEC_CTRL, + USB3_PHY_REG_MAX, +}; + +/* reg values to write */ +struct qmp_reg_val { + u32 offset; + u32 val; + u32 delay; +}; + +struct msm_ssphy_qmp { + struct usb_phy phy; + void __iomem *base; + void __iomem *vls_clamp_reg; + void __iomem *tcsr_usb3_dp_phymode; + + struct regulator *vdd; + int vdd_levels[3]; /* none, low, high */ + struct regulator *core_ldo; + int core_voltage_levels[3]; + struct regulator *fpc_redrive_ldo; + int redrive_voltage_levels[3]; + int redrive_load; + struct clk *ref_clk_src; + struct clk *ref_clk; + struct clk *aux_clk; + struct clk *cfg_ahb_clk; + struct clk *pipe_clk; + bool power_enabled; + struct reset_control *phy_reset; + struct reset_control *phy_phy_reset; + + bool clk_enabled; + bool cable_connected; + bool in_suspend; + bool emulation; + unsigned int *phy_reg; /* revision based offset */ + unsigned int *qmp_phy_init_seq; + int init_seq_len; + unsigned int *qmp_phy_reg_offset; + int reg_offset_cnt; +}; + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-ssphy-qmp", + }, + { + .compatible = "qcom,usb-ssphy-qmp-v1", + }, + { + .compatible = "qcom,usb-ssphy-qmp-v2", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static inline char *get_cable_status_str(struct msm_ssphy_qmp *phy) +{ + return phy->cable_connected ? "connected" : "disconnected"; +} + +static void msm_ssusb_qmp_clr_lfps_rxterm_int(struct msm_ssphy_qmp *phy) +{ + writeb_relaxed(1, phy->base + + phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]); + /* flush the previous write before next write */ + wmb(); + writeb_relaxed(0, phy->base + + phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]); +} + +static void msm_ssusb_qmp_enable_autonomous(struct msm_ssphy_qmp *phy, + int enable) +{ + u8 val; + unsigned int autonomous_mode_offset = + phy->phy_reg[USB3_PHY_AUTONOMOUS_MODE_CTRL]; + + dev_dbg(phy->phy.dev, "enabling QMP autonomous mode with cable %s\n", + get_cable_status_str(phy)); + + if (enable) { + msm_ssusb_qmp_clr_lfps_rxterm_int(phy); + val = readb_relaxed(phy->base + autonomous_mode_offset); + val |= ARCVR_DTCT_EN; + if (phy->phy.flags & DEVICE_IN_SS_MODE) { + val |= ALFPS_DTCT_EN; + val &= ~ARCVR_DTCT_EVENT_SEL; + } else { + val &= ~ALFPS_DTCT_EN; + val |= ARCVR_DTCT_EVENT_SEL; + } + + writeb_relaxed(val, phy->base + autonomous_mode_offset); + /* clamp phy level shifter to perform autonomous detection */ + writel_relaxed(0x1, phy->vls_clamp_reg); + } else { + writel_relaxed(0x0, phy->vls_clamp_reg); + writeb_relaxed(0, phy->base + autonomous_mode_offset); + msm_ssusb_qmp_clr_lfps_rxterm_int(phy); + } +} + +static int msm_ldo_enable(struct msm_ssphy_qmp *phy, + struct regulator *ldo, int *voltage_levels, int load) +{ + int ret = 0; + + dev_dbg(phy->phy.dev, + "ldo: min_vol:%duV max_vol:%duV\n", + voltage_levels[VOLTAGE_LEVEL_MIN], + voltage_levels[VOLTAGE_LEVEL_MAX]); + + if (load > 0) { + ret = regulator_set_load(ldo, load); + if (ret < 0) + return ret; + } + + ret = regulator_set_voltage(ldo, + voltage_levels[VOLTAGE_LEVEL_MIN], + voltage_levels[VOLTAGE_LEVEL_MAX]); + if (ret) + return ret; + + ret = regulator_enable(ldo); + + return ret; +} + +static int msm_ssusb_qmp_ldo_enable(struct msm_ssphy_qmp *phy, int on) +{ + int min, rc = 0; + + dev_dbg(phy->phy.dev, "reg (%s)\n", on ? "HPM" : "LPM"); + + if (phy->power_enabled == on) { + dev_dbg(phy->phy.dev, "PHYs' regulators status %d\n", + phy->power_enabled); + return 0; + } + + phy->power_enabled = on; + + min = on ? 1 : 0; /* low or none? */ + + if (!on) + goto disable_regulators; + + if (phy->fpc_redrive_ldo) { + rc = msm_ldo_enable(phy, phy->fpc_redrive_ldo, + phy->redrive_voltage_levels, + phy->redrive_load); + if (rc < 0) { + dev_err(phy->phy.dev, + "enable phy->fpc_redrive_ldo failed\n"); + return rc; + } + } + + rc = msm_ldo_enable(phy, phy->vdd, phy->vdd_levels, + USB_SSPHY_LOAD_DEFAULT); + if (rc < 0) { + dev_err(phy->phy.dev, "enable phy->vdd failed\n"); + goto disable_fpc_redrive; + } + + rc = msm_ldo_enable(phy, phy->core_ldo, phy->core_voltage_levels, + USB_SSPHY_HPM_LOAD); + if (rc < 0) { + dev_err(phy->phy.dev, "enable phy->core_ldo failed\n"); + goto disable_vdd; + } + + return 0; + +disable_regulators: + rc = regulator_disable(phy->core_ldo); + if (rc) + dev_err(phy->phy.dev, "disable phy->core_ldo failed\n"); + +disable_vdd: + rc = regulator_disable(phy->vdd); + if (rc) + dev_err(phy->phy.dev, "disable phy->vdd failed\n"); + +disable_fpc_redrive: + if (phy->fpc_redrive_ldo) { + rc = regulator_disable(phy->fpc_redrive_ldo); + if (rc) + dev_err(phy->phy.dev, + "disable phy->fpc_redrive_ldo failed\n"); + } + + return rc < 0 ? rc : 0; +} + +static int configure_phy_regs(struct usb_phy *uphy, + const struct qmp_reg_val *reg) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + if (!reg) { + dev_err(uphy->dev, "NULL PHY configuration\n"); + return -EINVAL; + } + + while (reg->offset != -1) { + writel_relaxed(reg->val, phy->base + reg->offset); + if (reg->delay) + usleep_range(reg->delay, reg->delay + 10); + reg++; + } + return 0; +} + +/* SSPHY Initialization */ +static int msm_ssphy_qmp_init(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret, val; + unsigned init_timeout_usec = INIT_MAX_TIME_USEC; + const struct qmp_reg_val *reg = NULL; + + dev_dbg(uphy->dev, "Initializing QMP phy\n"); + + if (phy->emulation) + return 0; + + ret = msm_ssusb_qmp_ldo_enable(phy, 1); + if (ret) { + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(1) failed, ret=%d\n", + ret); + return ret; + } + + if (!phy->clk_enabled) { + if (phy->ref_clk_src) + clk_prepare_enable(phy->ref_clk_src); + if (phy->ref_clk) + clk_prepare_enable(phy->ref_clk); + clk_prepare_enable(phy->aux_clk); + clk_prepare_enable(phy->cfg_ahb_clk); + clk_set_rate(phy->pipe_clk, 125000000); + clk_prepare_enable(phy->pipe_clk); + phy->clk_enabled = true; + } + + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + + /* Make sure that above write completed to get PHY into POWER DOWN */ + mb(); + + reg = (struct qmp_reg_val *)phy->qmp_phy_init_seq; + + /* Main configuration */ + ret = configure_phy_regs(uphy, reg); + if (ret) { + dev_err(uphy->dev, "Failed the main PHY configuration\n"); + return ret; + } + + /* perform lane selection */ + val = -EINVAL; + if (phy->phy.flags & PHY_LANE_A) + val = SW_PORTSELECT_MX; + + if (phy->phy.flags & PHY_LANE_B) + val = SW_PORTSELECT | SW_PORTSELECT_MX; + + if (val > 0) + writel_relaxed(val, + phy->base + phy->phy_reg[USB3_PHY_PCS_MISC_TYPEC_CTRL]); + + writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]); + writel_relaxed(0x00, phy->base + phy->phy_reg[USB3_PHY_SW_RESET]); + + /* Make sure above write completed to bring PHY out of reset */ + mb(); + + /* Wait for PHY initialization to be done */ + do { + if (readl_relaxed(phy->base + + phy->phy_reg[USB3_PHY_PCS_STATUS]) & PHYSTATUS) + usleep_range(1, 2); + else + break; + } while (--init_timeout_usec); + + if (!init_timeout_usec) { + dev_err(uphy->dev, "QMP PHY initialization timeout\n"); + dev_err(uphy->dev, "USB3_PHY_PCS_STATUS:%x\n", + readl_relaxed(phy->base + + phy->phy_reg[USB3_PHY_PCS_STATUS])); + return -EBUSY; + }; + + return 0; +} + +static int msm_ssphy_qmp_reset(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret; + + dev_dbg(uphy->dev, "Resetting QMP phy\n"); + + /* Assert USB3 PHY reset */ + ret = reset_control_assert(phy->phy_phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_phy_reset assert failed\n"); + goto exit; + } + + /* Assert USB3 PHY CSR reset */ + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset assert failed\n"); + goto deassert_phy_phy_reset; + } + + /* select usb3 phy mode */ + if (phy->tcsr_usb3_dp_phymode) + writel_relaxed(0x0, phy->tcsr_usb3_dp_phymode); + + /* Deassert USB3 PHY CSR reset */ + ret = reset_control_deassert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset deassert failed\n"); + goto deassert_phy_phy_reset; + } + + /* Deassert USB3 PHY reset */ + ret = reset_control_deassert(phy->phy_phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_phy_reset deassert failed\n"); + goto exit; + } + + return 0; + +deassert_phy_phy_reset: + ret = reset_control_deassert(phy->phy_phy_reset); + if (ret) + dev_err(uphy->dev, "phy_phy_reset deassert failed\n"); +exit: + phy->in_suspend = false; + + return ret; +} + +static int msm_ssphy_power_enable(struct msm_ssphy_qmp *phy, bool on) +{ + bool host = phy->phy.flags & PHY_HOST_MODE; + int ret = 0; + + /* + * Turn off the phy's LDOs when cable is disconnected for device mode + * with external vbus_id indication. + */ + if (!host && !phy->cable_connected) { + if (on) { + ret = msm_ssusb_qmp_ldo_enable(phy, 1); + if (ret) + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(1) failed, ret=%d\n", + ret); + } else { + ret = msm_ssusb_qmp_ldo_enable(phy, 0); + if (ret) + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(0) failed, ret=%d\n", + ret); + } + } + + return ret; +} + +/** + * Performs QMP PHY suspend/resume functionality. + * + * @uphy - usb phy pointer. + * @suspend - to enable suspend or not. 1 - suspend, 0 - resume + * + */ +static int msm_ssphy_qmp_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + dev_dbg(uphy->dev, "QMP PHY set_suspend for %s called with cable %s\n", + (suspend ? "suspend" : "resume"), + get_cable_status_str(phy)); + + if (phy->in_suspend == suspend) { + dev_dbg(uphy->dev, "%s: USB PHY is already %s.\n", + __func__, (suspend ? "suspended" : "resumed")); + return 0; + } + + if (suspend) { + if (!phy->cable_connected) + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + else + msm_ssusb_qmp_enable_autonomous(phy, 1); + + /* Make sure above write completed with PHY */ + wmb(); + + clk_disable_unprepare(phy->cfg_ahb_clk); + clk_disable_unprepare(phy->aux_clk); + clk_disable_unprepare(phy->pipe_clk); + if (phy->ref_clk) + clk_disable_unprepare(phy->ref_clk); + if (phy->ref_clk_src) + clk_disable_unprepare(phy->ref_clk_src); + phy->clk_enabled = false; + phy->in_suspend = true; + msm_ssphy_power_enable(phy, 0); + dev_dbg(uphy->dev, "QMP PHY is suspend\n"); + } else { + msm_ssphy_power_enable(phy, 1); + clk_prepare_enable(phy->pipe_clk); + if (!phy->clk_enabled) { + if (phy->ref_clk_src) + clk_prepare_enable(phy->ref_clk_src); + if (phy->ref_clk) + clk_prepare_enable(phy->ref_clk); + clk_prepare_enable(phy->aux_clk); + clk_prepare_enable(phy->cfg_ahb_clk); + phy->clk_enabled = true; + } + if (!phy->cable_connected) { + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + } else { + msm_ssusb_qmp_enable_autonomous(phy, 0); + } + + /* Make sure that above write completed with PHY */ + wmb(); + + phy->in_suspend = false; + dev_dbg(uphy->dev, "QMP PHY is resumed\n"); + } + + return 0; +} + +static int msm_ssphy_qmp_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + dev_dbg(uphy->dev, "QMP phy connect notification\n"); + phy->cable_connected = true; + dev_dbg(uphy->dev, "cable_connected=%d\n", phy->cable_connected); + return 0; +} + +static int msm_ssphy_qmp_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + dev_dbg(uphy->dev, "QMP phy disconnect notification\n"); + dev_dbg(uphy->dev, " cable_connected=%d\n", phy->cable_connected); + phy->cable_connected = false; + return 0; +} + +static int msm_ssphy_qmp_probe(struct platform_device *pdev) +{ + struct msm_ssphy_qmp *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0, size = 0, len; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->aux_clk = devm_clk_get(dev, "aux_clk"); + if (IS_ERR(phy->aux_clk)) { + ret = PTR_ERR(phy->aux_clk); + phy->aux_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get aux_clk\n"); + goto err; + } + + clk_set_rate(phy->aux_clk, clk_round_rate(phy->aux_clk, ULONG_MAX)); + + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + phy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk"); + if (IS_ERR(phy->cfg_ahb_clk)) { + ret = PTR_ERR(phy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to get cfg_ahb_clk ret %d\n", ret); + goto err; + } + } + + phy->pipe_clk = devm_clk_get(dev, "pipe_clk"); + if (IS_ERR(phy->pipe_clk)) { + ret = PTR_ERR(phy->pipe_clk); + phy->pipe_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get pipe_clk\n"); + goto err; + } + + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) { + ret = PTR_ERR(phy->phy_reset); + dev_dbg(dev, "failed to get phy_reset\n"); + goto err; + } + + phy->phy_phy_reset = devm_reset_control_get(dev, "phy_phy_reset"); + if (IS_ERR(phy->phy_phy_reset)) { + ret = PTR_ERR(phy->phy_phy_reset); + dev_dbg(dev, "failed to get phy_phy_reset\n"); + goto err; + } + + of_get_property(dev->of_node, "qcom,qmp-phy-reg-offset", &size); + if (size) { + phy->qmp_phy_reg_offset = devm_kzalloc(dev, + size, GFP_KERNEL); + if (phy->qmp_phy_reg_offset) { + phy->reg_offset_cnt = + (size / sizeof(*phy->qmp_phy_reg_offset)); + if (phy->reg_offset_cnt > USB3_PHY_REG_MAX) { + dev_err(dev, "invalid reg offset count\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,qmp-phy-reg-offset", + phy->qmp_phy_reg_offset, + phy->reg_offset_cnt); + } else { + dev_err(dev, "err mem alloc for qmp_phy_reg_offset\n"); + return -ENOMEM; + } + phy->phy_reg = phy->qmp_phy_reg_offset; + } else { + dev_err(dev, "err provide qcom,qmp-phy-reg-offset\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qmp_phy_base"); + if (!res) { + dev_err(dev, "failed getting qmp_phy_base\n"); + return -ENODEV; + } + phy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->base)) { + ret = PTR_ERR(phy->base); + goto err; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "vls_clamp_reg"); + if (!res) { + dev_err(dev, "failed getting vls_clamp_reg\n"); + return -ENODEV; + } + phy->vls_clamp_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->vls_clamp_reg)) { + dev_err(dev, "couldn't find vls_clamp_reg address.\n"); + return PTR_ERR(phy->vls_clamp_reg); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "tcsr_usb3_dp_phymode"); + if (res) { + phy->tcsr_usb3_dp_phymode = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->tcsr_usb3_dp_phymode)) { + dev_err(dev, "err getting tcsr_usb3_dp_phymode addr\n"); + return PTR_ERR(phy->tcsr_usb3_dp_phymode); + } + } + + phy->emulation = of_property_read_bool(dev->of_node, + "qcom,emulation"); + if (!phy->emulation) { + of_get_property(dev->of_node, "qcom,qmp-phy-init-seq", &size); + if (size) { + if (size % sizeof(*phy->qmp_phy_init_seq)) { + dev_err(dev, "invalid init_seq_len\n"); + return -EINVAL; + } + phy->qmp_phy_init_seq = devm_kzalloc(dev, + size, GFP_KERNEL); + if (phy->qmp_phy_init_seq) { + phy->init_seq_len = + (size / sizeof(*phy->qmp_phy_init_seq)); + + of_property_read_u32_array(dev->of_node, + "qcom,qmp-phy-init-seq", + phy->qmp_phy_init_seq, + phy->init_seq_len); + } else { + dev_err(dev, "error allocating memory for phy_init_seq\n"); + return -EINVAL; + } + } else { + dev_err(dev, "error need qmp-phy-init-seq\n"); + return -EINVAL; + } + } + + /* Set default core voltage values */ + phy->core_voltage_levels[VOLTAGE_LEVEL_NONE] = 0; + phy->core_voltage_levels[VOLTAGE_LEVEL_MIN] = USB_SSPHY_1P2_VOL_MIN; + phy->core_voltage_levels[VOLTAGE_LEVEL_MAX] = USB_SSPHY_1P2_VOL_MAX; + + if (of_get_property(dev->of_node, "qcom,core-voltage-level", &len) && + len == sizeof(phy->core_voltage_levels)) { + ret = of_property_read_u32_array(dev->of_node, + "qcom,core-voltage-level", + (u32 *)phy->core_voltage_levels, + len / sizeof(u32)); + if (ret) { + dev_err(dev, "err qcom,core-voltage-level property\n"); + goto err; + } + } + + if (of_get_property(dev->of_node, "qcom,vdd-voltage-level", &len) && + len == sizeof(phy->vdd_levels)) { + ret = of_property_read_u32_array(dev->of_node, + "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + len / sizeof(u32)); + if (ret) { + dev_err(dev, "err qcom,vdd-voltage-level property\n"); + goto err; + } + } else { + ret = -EINVAL; + dev_err(dev, "error invalid inputs for vdd-voltage-level\n"); + goto err; + } + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err; + } + + phy->core_ldo = devm_regulator_get(dev, "core"); + if (IS_ERR(phy->core_ldo)) { + dev_err(dev, "unable to get core ldo supply\n"); + ret = PTR_ERR(phy->core_ldo); + goto err; + } + + phy->fpc_redrive_ldo = devm_regulator_get_optional(dev, "fpc-redrive"); + if (IS_ERR(phy->fpc_redrive_ldo)) { + phy->fpc_redrive_ldo = NULL; + dev_dbg(dev, "no FPC re-drive ldo regulator\n"); + } else { + if (of_get_property(dev->of_node, + "qcom,redrive-voltage-level", &len) && + len == sizeof(phy->redrive_voltage_levels)) { + ret = of_property_read_u32_array(dev->of_node, + "qcom,redrive-voltage-level", + (u32 *) phy->redrive_voltage_levels, + len / sizeof(u32)); + if (ret) { + dev_err(dev, + "err qcom,redrive-voltage-level\n"); + goto err; + } + } else { + ret = -EINVAL; + dev_err(dev, "err inputs for redrive-voltage-level\n"); + goto err; + } + + ret = of_property_read_u32(dev->of_node, "qcom,redrive-load", + &phy->redrive_load); + if (ret) { + dev_err(&pdev->dev, "unable to read redrive load\n"); + goto err; + } + + dev_dbg(dev, "Get FPC re-drive ldo regulator\n"); + } + + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) + phy->ref_clk_src = NULL; + phy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(phy->ref_clk)) + phy->ref_clk = NULL; + + platform_set_drvdata(pdev, phy); + + if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override")) + phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE; + + phy->phy.dev = dev; + phy->phy.init = msm_ssphy_qmp_init; + phy->phy.set_suspend = msm_ssphy_qmp_set_suspend; + phy->phy.notify_connect = msm_ssphy_qmp_notify_connect; + phy->phy.notify_disconnect = msm_ssphy_qmp_notify_disconnect; + phy->phy.reset = msm_ssphy_qmp_reset; + phy->phy.type = USB_PHY_TYPE_USB3; + + ret = usb_add_phy_dev(&phy->phy); + +err: + return ret; +} + +static int msm_ssphy_qmp_remove(struct platform_device *pdev) +{ + struct msm_ssphy_qmp *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + usb_remove_phy(&phy->phy); + if (phy->ref_clk) + clk_disable_unprepare(phy->ref_clk); + if (phy->ref_clk_src) + clk_disable_unprepare(phy->ref_clk_src); + msm_ssusb_qmp_ldo_enable(phy, 0); + clk_disable_unprepare(phy->aux_clk); + clk_disable_unprepare(phy->cfg_ahb_clk); + clk_disable_unprepare(phy->pipe_clk); + kfree(phy); + return 0; +} + +static struct platform_driver msm_ssphy_qmp_driver = { + .probe = msm_ssphy_qmp_probe, + .remove = msm_ssphy_qmp_remove, + .driver = { + .name = "msm-usb-ssphy-qmp", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_ssphy_qmp_driver); + +MODULE_DESCRIPTION("MSM USB SS QMP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-ssusb.c b/drivers/usb/phy/phy-msm-ssusb.c new file mode 100644 index 000000000000..5ea19deb8219 --- /dev/null +++ b/drivers/usb/phy/phy-msm-ssusb.c @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/phy.h> +#include <linux/usb/msm_hsusb.h> + +static int ss_phy_override_deemphasis; +module_param(ss_phy_override_deemphasis, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(ss_phy_override_deemphasis, "Override SSPHY demphasis value"); + +/* QSCRATCH SSPHY control registers */ +#define SS_PHY_CTRL_REG 0x30 +#define SS_PHY_PARAM_CTRL_1 0x34 +#define SS_PHY_PARAM_CTRL_2 0x38 +#define SS_CR_PROTOCOL_DATA_IN_REG 0x3C +#define SS_CR_PROTOCOL_DATA_OUT_REG 0x40 +#define SS_CR_PROTOCOL_CAP_ADDR_REG 0x44 +#define SS_CR_PROTOCOL_CAP_DATA_REG 0x48 +#define SS_CR_PROTOCOL_READ_REG 0x4C +#define SS_CR_PROTOCOL_WRITE_REG 0x50 + +/* SS_PHY_CTRL_REG bits */ +#define SS_PHY_RESET BIT(7) +#define REF_SS_PHY_EN BIT(8) +#define LANE0_PWR_PRESENT BIT(24) +#define TEST_POWERDOWN BIT(26) +#define REF_USE_PAD BIT(28) + +#define USB_SSPHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_SSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_SSPHY_1P8_HPM_LOAD 23000 /* uA */ + +struct msm_ssphy { + struct usb_phy phy; + void __iomem *base; + struct clk *core_clk; /* USB3 master clock */ + struct clk *com_reset_clk; /* PHY common block reset */ + struct clk *reset_clk; /* SS PHY reset */ + struct regulator *vdd; + struct regulator *vdda18; + atomic_t active_count; /* num of active instances */ + bool suspended; + int vdd_levels[3]; /* none, low, high */ + int deemphasis_val; +}; + +static int msm_ssusb_config_vdd(struct msm_ssphy *phy, int high) +{ + int min, ret; + + min = high ? 1 : 0; /* low or none? */ + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[min], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "unable to set voltage for ssusb vdd\n"); + return ret; + } + + dev_dbg(phy->phy.dev, "%s: min_vol:%d max_vol:%d\n", __func__, + phy->vdd_levels[min], phy->vdd_levels[2]); + return ret; +} + +static int msm_ssusb_ldo_enable(struct msm_ssphy *phy, int on) +{ + int rc = 0; + + dev_dbg(phy->phy.dev, "reg (%s)\n", on ? "HPM" : "LPM"); + + if (!on) + goto disable_regulators; + + + rc = regulator_set_load(phy->vdda18, USB_SSPHY_1P8_HPM_LOAD); + if (rc < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda18\n"); + return rc; + } + + rc = regulator_set_voltage(phy->vdda18, USB_SSPHY_1P8_VOL_MIN, + USB_SSPHY_1P8_VOL_MAX); + if (rc) { + dev_err(phy->phy.dev, "unable to set voltage for vdda18\n"); + goto put_vdda18_lpm; + } + + rc = regulator_enable(phy->vdda18); + if (rc) { + dev_err(phy->phy.dev, "Unable to enable vdda18\n"); + goto unset_vdda18; + } + + return 0; + +disable_regulators: + rc = regulator_disable(phy->vdda18); + if (rc) + dev_err(phy->phy.dev, "Unable to disable vdda18\n"); + +unset_vdda18: + rc = regulator_set_voltage(phy->vdda18, 0, USB_SSPHY_1P8_VOL_MAX); + if (rc) + dev_err(phy->phy.dev, "unable to set voltage for vdda18\n"); + +put_vdda18_lpm: + rc = regulator_set_load(phy->vdda18, 0); + if (rc < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdda18\n"); + + return rc < 0 ? rc : 0; +} + +static void msm_usb_write_readback(void *base, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp = readl_relaxed(base + offset); + + tmp &= ~mask; /* retain other bits */ + write_val = tmp | val; + + writel_relaxed(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = readl_relaxed(base + offset); + tmp &= mask; /* clear other bits */ + + if (tmp != val) + pr_err("%s: write: %x to QSCRATCH: %x FAILED\n", + __func__, val, offset); +} + +/** + * Write SSPHY register with debug info. + * + * @base - base virtual address. + * @addr - SSPHY address to write. + * @val - value to write. + * + */ +static void msm_ssusb_write_phycreg(void *base, u32 addr, u32 val) +{ + writel_relaxed(addr, base + SS_CR_PROTOCOL_DATA_IN_REG); + writel_relaxed(0x1, base + SS_CR_PROTOCOL_CAP_ADDR_REG); + while (readl_relaxed(base + SS_CR_PROTOCOL_CAP_ADDR_REG)) + cpu_relax(); + + writel_relaxed(val, base + SS_CR_PROTOCOL_DATA_IN_REG); + writel_relaxed(0x1, base + SS_CR_PROTOCOL_CAP_DATA_REG); + while (readl_relaxed(base + SS_CR_PROTOCOL_CAP_DATA_REG)) + cpu_relax(); + + writel_relaxed(0x1, base + SS_CR_PROTOCOL_WRITE_REG); + while (readl_relaxed(base + SS_CR_PROTOCOL_WRITE_REG)) + cpu_relax(); +} + +/** + * Read SSPHY register with debug info. + * + * @base - base virtual address. + * @addr - SSPHY address to read. + * + */ +static u32 msm_ssusb_read_phycreg(void *base, u32 addr) +{ + bool first_read = true; + + writel_relaxed(addr, base + SS_CR_PROTOCOL_DATA_IN_REG); + writel_relaxed(0x1, base + SS_CR_PROTOCOL_CAP_ADDR_REG); + while (readl_relaxed(base + SS_CR_PROTOCOL_CAP_ADDR_REG)) + cpu_relax(); + + /* + * Due to hardware bug, first read of SSPHY register might be + * incorrect. Hence as workaround, SW should perform SSPHY register + * read twice, but use only second read and ignore first read. + */ +retry: + writel_relaxed(0x1, base + SS_CR_PROTOCOL_READ_REG); + while (readl_relaxed(base + SS_CR_PROTOCOL_READ_REG)) + cpu_relax(); + + if (first_read) { + readl_relaxed(base + SS_CR_PROTOCOL_DATA_OUT_REG); + first_read = false; + goto retry; + } + + return readl_relaxed(base + SS_CR_PROTOCOL_DATA_OUT_REG); +} + +static int msm_ssphy_set_params(struct usb_phy *uphy) +{ + struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy); + u32 data = 0; + + /* + * WORKAROUND: There is SSPHY suspend bug due to which USB enumerates + * in HS mode instead of SS mode. Workaround it by asserting + * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus mode + */ + data = msm_ssusb_read_phycreg(phy->base, 0x102D); + data |= (1 << 7); + msm_ssusb_write_phycreg(phy->base, 0x102D, data); + + data = msm_ssusb_read_phycreg(phy->base, 0x1010); + data &= ~0xFF0; + data |= 0x20; + msm_ssusb_write_phycreg(phy->base, 0x1010, data); + + /* + * Fix RX Equalization setting as follows + * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0 + * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1 + * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3 + * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1 + */ + data = msm_ssusb_read_phycreg(phy->base, 0x1006); + data &= ~(1 << 6); + data |= (1 << 7); + data &= ~(0x7 << 8); + data |= (0x3 << 8); + data |= (0x1 << 11); + msm_ssusb_write_phycreg(phy->base, 0x1006, data); + + /* + * Set EQ and TX launch amplitudes as follows + * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22 + * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127 + * LANE0.TX_OVRD_DRV_LO.EN set to 1. + */ + data = msm_ssusb_read_phycreg(phy->base, 0x1002); + data &= ~0x3F80; + if (ss_phy_override_deemphasis) + phy->deemphasis_val = ss_phy_override_deemphasis; + if (phy->deemphasis_val) + data |= (phy->deemphasis_val << 7); + else + data |= (0x16 << 7); + data &= ~0x7F; + data |= (0x7F | (1 << 14)); + msm_ssusb_write_phycreg(phy->base, 0x1002, data); + + /* + * Set the QSCRATCH SS_PHY_PARAM_CTRL1 parameters as follows + * TX_FULL_SWING [26:20] amplitude to 127 + * TX_DEEMPH_3_5DB [13:8] to 22 + * LOS_BIAS [2:0] to 0x5 + */ + msm_usb_write_readback(phy->base, SS_PHY_PARAM_CTRL_1, + 0x07f03f07, 0x07f01605); + + return 0; +} + +/* SSPHY Initialization */ +static int msm_ssphy_init(struct usb_phy *uphy) +{ + struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy); + u32 val; + + /* Ensure clock is on before accessing QSCRATCH registers */ + clk_prepare_enable(phy->core_clk); + + /* read initial value */ + val = readl_relaxed(phy->base + SS_PHY_CTRL_REG); + + /* Use clk reset, if available; otherwise use SS_PHY_RESET bit */ + if (phy->com_reset_clk) { + clk_reset(phy->com_reset_clk, CLK_RESET_ASSERT); + clk_reset(phy->reset_clk, CLK_RESET_ASSERT); + udelay(10); /* 10us required before de-asserting */ + clk_reset(phy->com_reset_clk, CLK_RESET_DEASSERT); + clk_reset(phy->reset_clk, CLK_RESET_DEASSERT); + } else { + writel_relaxed(val | SS_PHY_RESET, phy->base + SS_PHY_CTRL_REG); + udelay(10); /* 10us required before de-asserting */ + writel_relaxed(val, phy->base + SS_PHY_CTRL_REG); + } + + /* Use ref_clk from pads and set its parameters */ + val |= REF_USE_PAD; + writel_relaxed(val, phy->base + SS_PHY_CTRL_REG); + msleep(30); + + /* Ref clock must be stable now, enable ref clock for HS mode */ + val |= LANE0_PWR_PRESENT | REF_SS_PHY_EN; + writel_relaxed(val, phy->base + SS_PHY_CTRL_REG); + usleep_range(2000, 2200); + + /* + * Reinitialize SSPHY parameters as SS_PHY RESET will reset + * the internal registers to default values. + */ + msm_ssphy_set_params(uphy); + + clk_disable_unprepare(phy->core_clk); + + return 0; +} + +static int msm_ssphy_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy); + void __iomem *base = phy->base; + int count; + + /* Ensure clock is on before accessing QSCRATCH registers */ + clk_prepare_enable(phy->core_clk); + + if (suspend) { + count = atomic_dec_return(&phy->active_count); + if (count > 0 || phy->suspended) { + dev_dbg(uphy->dev, "Skipping suspend, active_count=%d phy->suspended=%d\n", + count, phy->suspended); + goto done; + } + + if (count < 0) { + dev_WARN(uphy->dev, "Suspended too many times! active_count=%d\n", + count); + atomic_set(&phy->active_count, 0); + } + + /* Clear REF_SS_PHY_EN */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_SS_PHY_EN, 0); + /* Clear REF_USE_PAD */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_USE_PAD, 0); + /* Set TEST_POWERDOWN (enables PHY retention) */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, TEST_POWERDOWN, + TEST_POWERDOWN); + if (phy->com_reset_clk && + !(phy->phy.flags & ENABLE_SECONDARY_PHY)) { + /* leave these asserted until resuming */ + clk_reset(phy->com_reset_clk, CLK_RESET_ASSERT); + clk_reset(phy->reset_clk, CLK_RESET_ASSERT); + } + + msm_ssusb_ldo_enable(phy, 0); + msm_ssusb_config_vdd(phy, 0); + phy->suspended = true; + } else { + count = atomic_inc_return(&phy->active_count); + if (count > 1 || !phy->suspended) { + dev_dbg(uphy->dev, "Skipping resume, active_count=%d phy->suspended=%d\n", + count, phy->suspended); + goto done; + } + + phy->suspended = false; + msm_ssusb_config_vdd(phy, 1); + msm_ssusb_ldo_enable(phy, 1); + + if (phy->phy.flags & ENABLE_SECONDARY_PHY) { + dev_err(uphy->dev, "secondary PHY, skipping reset\n"); + goto done; + } + + if (phy->com_reset_clk) { + clk_reset(phy->com_reset_clk, CLK_RESET_DEASSERT); + clk_reset(phy->reset_clk, CLK_RESET_DEASSERT); + } else { + /* Assert SS PHY RESET */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, + SS_PHY_RESET, SS_PHY_RESET); + } + + /* Set REF_USE_PAD */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_USE_PAD, + REF_USE_PAD); + /* Set REF_SS_PHY_EN */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_SS_PHY_EN, + REF_SS_PHY_EN); + /* Clear TEST_POWERDOWN */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, TEST_POWERDOWN, + 0); + if (!phy->com_reset_clk) { + udelay(10); /* 10us required before de-asserting */ + msm_usb_write_readback(base, SS_PHY_CTRL_REG, + SS_PHY_RESET, 0); + } + } + +done: + clk_disable_unprepare(phy->core_clk); + return 0; +} + +static int msm_ssphy_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy); + + if (uphy->flags & PHY_HOST_MODE) + return 0; + + if (uphy->flags & PHY_VBUS_VALID_OVERRIDE) + /* Indicate power present to SS phy */ + msm_usb_write_readback(phy->base, SS_PHY_CTRL_REG, + LANE0_PWR_PRESENT, LANE0_PWR_PRESENT); + + return 0; +} + +static int msm_ssphy_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy); + + if (uphy->flags & PHY_HOST_MODE) + return 0; + + if (uphy->flags & PHY_VBUS_VALID_OVERRIDE) + /* Clear power indication to SS phy */ + msm_usb_write_readback(phy->base, SS_PHY_CTRL_REG, + LANE0_PWR_PRESENT, 0); + + return 0; +} + +static int msm_ssphy_probe(struct platform_device *pdev) +{ + struct msm_ssphy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + return -ENODEV; + } + + phy->base = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (!phy->base) { + dev_err(dev, "ioremap failed\n"); + return -ENODEV; + } + + phy->core_clk = devm_clk_get(dev, "core_clk"); + if (IS_ERR(phy->core_clk)) { + dev_err(dev, "unable to get core_clk\n"); + return PTR_ERR(phy->core_clk); + } + + phy->com_reset_clk = devm_clk_get(dev, "com_reset_clk"); + if (IS_ERR(phy->com_reset_clk)) { + dev_dbg(dev, "com_reset_clk unavailable\n"); + phy->com_reset_clk = NULL; + } + + phy->reset_clk = devm_clk_get(dev, "reset_clk"); + if (IS_ERR(phy->reset_clk)) { + dev_dbg(dev, "reset_clk unavailable\n"); + phy->reset_clk = NULL; + } + + if (of_get_property(dev->of_node, "qcom,primary-phy", NULL)) { + dev_dbg(dev, "secondary HSPHY\n"); + phy->phy.flags |= ENABLE_SECONDARY_PHY; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + ARRAY_SIZE(phy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + return ret; + } + + phy->phy.dev = dev; + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + return PTR_ERR(phy->vdd); + } + + phy->vdda18 = devm_regulator_get(dev, "vdda18"); + if (IS_ERR(phy->vdda18)) { + dev_err(dev, "unable to get vdda18 supply\n"); + return PTR_ERR(phy->vdda18); + } + + ret = msm_ssusb_config_vdd(phy, 1); + if (ret) { + dev_err(dev, "ssusb vdd_dig configuration failed\n"); + return ret; + } + + ret = regulator_enable(phy->vdd); + if (ret) { + dev_err(dev, "unable to enable the ssusb vdd_dig\n"); + goto unconfig_ss_vdd; + } + + ret = msm_ssusb_ldo_enable(phy, 1); + if (ret) { + dev_err(dev, "ssusb vreg enable failed\n"); + goto disable_ss_vdd; + } + + platform_set_drvdata(pdev, phy); + + if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override")) + phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE; + + if (of_property_read_u32(dev->of_node, "qcom,deemphasis-value", + &phy->deemphasis_val)) + dev_dbg(dev, "unable to read ssphy deemphasis value\n"); + + phy->phy.init = msm_ssphy_init; + phy->phy.set_suspend = msm_ssphy_set_suspend; + phy->phy.notify_connect = msm_ssphy_notify_connect; + phy->phy.notify_disconnect = msm_ssphy_notify_disconnect; + phy->phy.type = USB_PHY_TYPE_USB3; + + ret = usb_add_phy_dev(&phy->phy); + if (ret) + goto disable_ss_ldo; + + return 0; + +disable_ss_ldo: + msm_ssusb_ldo_enable(phy, 0); +disable_ss_vdd: + regulator_disable(phy->vdd); +unconfig_ss_vdd: + msm_ssusb_config_vdd(phy, 0); + + return ret; +} + +static int msm_ssphy_remove(struct platform_device *pdev) +{ + struct msm_ssphy *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + usb_remove_phy(&phy->phy); + msm_ssusb_ldo_enable(phy, 0); + regulator_disable(phy->vdd); + msm_ssusb_config_vdd(phy, 0); + kfree(phy); + + return 0; +} + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-ssphy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static struct platform_driver msm_ssphy_driver = { + .probe = msm_ssphy_probe, + .remove = msm_ssphy_remove, + .driver = { + .name = "msm-usb-ssphy", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_ssphy_driver); + +MODULE_DESCRIPTION("MSM USB SS PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c index 970a30e155cb..f1360f20ffe4 100644 --- a/drivers/usb/phy/phy-msm-usb.c +++ b/drivers/usb/phy/phy-msm-usb.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2009-2011, 2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1727,7 +1727,7 @@ static int msm_otg_probe(struct platform_device *pdev) writel(0x1, phy_select); } - dev_info(&pdev->dev, "OTG regs = %p\n", motg->regs); + dev_info(&pdev->dev, "OTG regs = %pK\n", motg->regs); motg->irq = platform_get_irq(pdev, 0); if (motg->irq < 0) { |
