msm-4.14/drivers/usb/phy/phy-msm-ssusb-qmp.c
Chandana Kishori Chiluveru fe533a386b usb: phy: qmp: Do not reset phy mode in DP+USB concurrent mode
QMP phy driver resets phy mode to usb in phy suspend path for
cable disconnect case. When USB3 controller stops responding
to stop endpoint command driver performs USB host mode recovery.
During this QMP PHY set_suspend for suspend called with cable
disconnected case and reset the phy mode to usb. But DP driver
is not notified of this reset which results DP is doing to bad sate
and causing unmapped access of registers from DP driver.

Fix this issue by adding proper check to do not reset the
phy mode in DP+USB concurrent mode.

Change-Id: I00ec1850060c74c7c76529b4f39476e384ce1022
Signed-off-by: Chandana Kishori Chiluveru <cchiluve@codeaurora.org>
2019-02-01 13:15:10 +05:30

1294 lines
33 KiB
C

/*
* Copyright (c) 2013-2019, 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/clk.h>
#include <linux/extcon.h>
#include <linux/reset.h>
#include <linux/hrtimer.h>
#include <soc/qcom/socinfo.h>
enum core_ldo_levels {
CORE_LEVEL_NONE = 0,
CORE_LEVEL_MIN,
CORE_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 30000 /* uA */
/* 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)
/*
* register bits
* PCIE_USB3_PHY_PCS_MISC_TYPEC_CTRL - for QMP USB PHY
* USB3_DP_COM_PHY_MODE_CTRL - for QMP USB DP Combo PHY
*/
/* 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)
/* USB3_DP_PHY_USB3_DP_COM_SWI_CTRL bits */
/* LANE related register read/write with USB3 */
#define USB3_SWI_ACT_ACCESS_EN BIT(0)
/* LANE related register read/write with DP */
#define DP_SWI_ACT_ACCESS_EN BIT(1)
/* USB3_DP_COM_RESET_OVRD_CTRL bits */
/* DP PHY soft reset */
#define SW_DPPHY_RESET BIT(0)
/* mux to select DP PHY reset control, 0:HW control, 1: software reset */
#define SW_DPPHY_RESET_MUX BIT(1)
/* USB3 PHY soft reset */
#define SW_USB3PHY_RESET BIT(2)
/* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */
#define SW_USB3PHY_RESET_MUX BIT(3)
/* USB3_DP_COM_PHY_MODE_CTRL bits */
#define USB3_MODE BIT(0) /* enables USB3 mode */
#define DP_MODE BIT(1) /* enables DP mode */
#define USB3_DP_COMBO_MODE (USB3_MODE | DP_MODE) /*enables combo mode */
/* PCS_STATUS2 link training indicator */
#define RX_EQUALIZATION_IN_PROGRESS BIT(3)
/* PCS_CONFIG5 register offsets for Gen2 link training SW WA */
#define USB3_DP_PCS_EQ_CONFIG5 0x1DEC
#define USB3_UNI_PCS_EQ_CONFIG5 0x09EC
#define RXEQ_RETRAIN_MODE_SEL BIT(6)
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,
/* TypeC port select configuration (optional) */
USB3_PHY_PCS_MISC_TYPEC_CTRL,
/* USB DP Combo PHY related */
USB3_DP_DP_PHY_PD_CTL,
USB3_DP_COM_POWER_DOWN_CTRL,
USB3_DP_COM_SW_RESET,
USB3_DP_COM_RESET_OVRD_CTRL,
USB3_DP_COM_PHY_MODE_CTRL,
USB3_DP_COM_TYPEC_CTRL,
USB3_DP_COM_SWI_CTRL,
USB3_PCS_MISC_CLAMP_ENABLE,
USB3_DP_PCS_PCS_STATUS2,
USB3_DP_PCS_INSIG_SW_CTRL3,
USB3_DP_PCS_INSIG_MX_CTRL3,
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 *pcs_clamp_enable_reg;
void __iomem *tcsr_usb3_dp_phymode;
struct regulator *vdd;
int vdd_levels[3]; /* none, low, high */
int vdd_max_uA;
struct regulator *core_ldo;
int core_voltage_levels[3];
int core_max_uA;
struct clk *ref_clk_src;
struct clk *ref_clk;
struct clk *aux_clk;
struct clk *com_aux_clk;
struct clk *cfg_ahb_clk;
struct clk *pipe_clk;
struct reset_control *phy_reset;
struct reset_control *phy_phy_reset;
struct reset_control *global_phy_reset;
struct extcon_dev *extcon_dp;
struct notifier_block dp_nb;
bool power_enabled;
bool clk_enabled;
bool cable_connected;
bool in_suspend;
u32 *phy_reg; /* revision based offset */
int reg_offset_cnt;
u32 *qmp_phy_init_seq;
int init_seq_len;
struct hrtimer timer;
bool link_training_reset;
u32 eq_config5_offset;
};
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",
},
{
.compatible = "qcom,usb-ssphy-qmp-dp-combo",
},
{
.compatible = "qcom,usb-ssphy-qmp-usb3-or-dp",
},
{ },
};
MODULE_DEVICE_TABLE(of, msm_usb_id_table);
static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy);
static void msm_ssphy_qmp_enable_clks(struct msm_ssphy_qmp *phy, bool on);
static int msm_ssphy_qmp_link_training(struct usb_phy *uphy, bool start);
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)
{
writel_relaxed(1, phy->base +
phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]);
/* flush the previous write before next write */
wmb();
writel_relaxed(0, phy->base +
phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]);
}
static void msm_ssusb_qmp_clamp_enable(struct msm_ssphy_qmp *phy, bool val)
{
switch (phy->phy.type) {
case USB_PHY_TYPE_USB3_AND_DP:
writel_relaxed(!val, phy->base +
phy->phy_reg[USB3_PCS_MISC_CLAMP_ENABLE]);
break;
case USB_PHY_TYPE_USB3_OR_DP:
case USB_PHY_TYPE_USB3:
if (phy->vls_clamp_reg)
writel_relaxed(!!val, phy->vls_clamp_reg);
if (phy->pcs_clamp_enable_reg)
writel_relaxed(!val, phy->pcs_clamp_enable_reg);
break;
default:
break;
}
}
static void msm_ssusb_qmp_enable_autonomous(struct msm_ssphy_qmp *phy,
int enable)
{
u32 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 = readl_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;
}
writel_relaxed(val, phy->base + autonomous_mode_offset);
msm_ssusb_qmp_clamp_enable(phy, true);
} else {
msm_ssusb_qmp_clamp_enable(phy, false);
writel_relaxed(0, phy->base + autonomous_mode_offset);
msm_ssusb_qmp_clr_lfps_rxterm_int(phy);
}
}
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;
rc = regulator_set_load(phy->vdd, phy->vdd_max_uA);
if (rc < 0) {
dev_err(phy->phy.dev, "Unable to set HPM of %s\n", "vdd");
return rc;
}
rc = regulator_set_voltage(phy->vdd, phy->vdd_levels[min],
phy->vdd_levels[2]);
if (rc) {
dev_err(phy->phy.dev, "Unable to set voltage for %s\n", "vdd");
goto put_vdd_lpm;
}
dev_dbg(phy->phy.dev, "min_vol:%d max_vol:%d\n",
phy->vdd_levels[min], phy->vdd_levels[2]);
rc = regulator_enable(phy->vdd);
if (rc) {
dev_err(phy->phy.dev, "Unable to enable %s\n", "vdd");
goto unconfig_vdd;
}
rc = regulator_set_load(phy->core_ldo, phy->core_max_uA);
if (rc < 0) {
dev_err(phy->phy.dev, "Unable to set HPM of %s\n", "core_ldo");
goto disable_vdd;
}
rc = regulator_set_voltage(phy->core_ldo,
phy->core_voltage_levels[CORE_LEVEL_MIN],
phy->core_voltage_levels[CORE_LEVEL_MAX]);
if (rc) {
dev_err(phy->phy.dev, "Unable to set voltage for %s\n",
"core_ldo");
goto put_core_ldo_lpm;
}
rc = regulator_enable(phy->core_ldo);
if (rc) {
dev_err(phy->phy.dev, "Unable to enable %s\n", "core_ldo");
goto unset_core_ldo;
}
return 0;
disable_regulators:
rc = regulator_disable(phy->core_ldo);
if (rc)
dev_err(phy->phy.dev, "Unable to disable %s\n", "core_ldo");
unset_core_ldo:
rc = regulator_set_voltage(phy->core_ldo,
phy->core_voltage_levels[CORE_LEVEL_NONE],
phy->core_voltage_levels[CORE_LEVEL_MAX]);
if (rc)
dev_err(phy->phy.dev, "Unable to set voltage for %s\n",
"core_ldo");
put_core_ldo_lpm:
rc = regulator_set_load(phy->core_ldo, 0);
if (rc < 0)
dev_err(phy->phy.dev, "Unable to set LPM of %s\n", "core_ldo");
disable_vdd:
rc = regulator_disable(phy->vdd);
if (rc)
dev_err(phy->phy.dev, "Unable to disable %s\n", "vdd");
unconfig_vdd:
rc = regulator_set_voltage(phy->vdd, phy->vdd_levels[min],
phy->vdd_levels[2]);
if (rc)
dev_err(phy->phy.dev, "Unable to set voltage for %s\n", "vdd");
put_vdd_lpm:
rc = regulator_set_load(phy->vdd, 0);
if (rc < 0)
dev_err(phy->phy.dev, "Unable to set LPM of %s\n", "vdd");
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;
}
static void msm_ssphy_qmp_setmode(struct msm_ssphy_qmp *phy, u32 mode)
{
mode = mode & USB3_DP_COMBO_MODE;
writel_relaxed(mode,
phy->base + phy->phy_reg[USB3_DP_COM_PHY_MODE_CTRL]);
/* flush the write by reading it */
readl_relaxed(phy->base + phy->phy_reg[USB3_DP_COM_PHY_MODE_CTRL]);
}
static void usb_qmp_update_portselect_phymode(struct msm_ssphy_qmp *phy)
{
int val;
/* perform lane selection */
val = -EINVAL;
if (phy->phy.flags & PHY_LANE_A)
val = SW_PORTSELECT_MX;
else if (phy->phy.flags & PHY_LANE_B)
val = SW_PORTSELECT | SW_PORTSELECT_MX;
/* PHY must be powered up before updating portselect and phymode. */
usb_qmp_powerup_phy(phy);
switch (phy->phy.type) {
case USB_PHY_TYPE_USB3_AND_DP:
/* override hardware control for reset of qmp phy */
if (!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE))
writel_relaxed(SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET,
phy->base +
phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]);
/* update port select */
if (val > 0) {
dev_err(phy->phy.dev,
"USB DP QMP PHY: Update TYPEC CTRL(%d)\n", val);
writel_relaxed(val, phy->base +
phy->phy_reg[USB3_DP_COM_TYPEC_CTRL]);
}
if (!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE)) {
msm_ssphy_qmp_setmode(phy, USB3_DP_COMBO_MODE);
/* bring both USB and DP PHYs PCS block out of reset */
writel_relaxed(0x00, phy->base +
phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]);
}
break;
case USB_PHY_TYPE_USB3_OR_DP:
if (val > 0) {
dev_err(phy->phy.dev,
"USB QMP PHY: Update TYPEC CTRL(%d)\n", val);
writel_relaxed(val, phy->base +
phy->phy_reg[USB3_PHY_PCS_MISC_TYPEC_CTRL]);
}
break;
default:
dev_dbg(phy->phy.dev, "no portselect for phy type %d\n",
phy->phy.type);
break;
}
/* Make sure above selection and reset sequence is gone through */
mb();
}
static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy)
{
switch (phy->phy.type) {
case USB_PHY_TYPE_USB3_AND_DP:
/* power up USB3 and DP common logic block */
writel_relaxed(0x01,
phy->base + phy->phy_reg[USB3_DP_COM_POWER_DOWN_CTRL]);
/*
* Don't write 0x0 to DP_COM_SW_RESET here as portselect and
* phymode operation needs DP_COM_SW_RESET as 0x1.
* msm_ssphy_qmp_init() writes 0x0 to DP_COM_SW_RESET before
* initializing PHY.
*/
/* intentional fall-through */
case USB_PHY_TYPE_USB3_OR_DP:
case USB_PHY_TYPE_USB3:
/* power up USB3 PHY */
writel_relaxed(0x01,
phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
break;
default:
dev_err(phy->phy.dev, "phy_powerup: Unknown USB QMP PHY type\n");
break;
}
/* Make sure that above write completed to power up PHY */
mb();
}
static void usb_qmp_apply_link_training_workarounds(struct msm_ssphy_qmp *phy)
{
u32 version, major, minor, val;
if (!phy->link_training_reset)
return;
version = socinfo_get_version();
minor = SOCINFO_VERSION_MINOR(version);
major = SOCINFO_VERSION_MAJOR(version);
/* sw workaround is needed only for hw reviosions below 2.1 */
if ((major < 2) || (major == 2 && minor == 0)) {
val = readl_relaxed(phy->base + phy->eq_config5_offset);
val |= RXEQ_RETRAIN_MODE_SEL;
writel_relaxed(val, phy->base + phy->eq_config5_offset);
phy->phy.link_training = msm_ssphy_qmp_link_training;
return;
}
}
/* 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;
unsigned int init_timeout_usec = INIT_MAX_TIME_USEC;
const struct qmp_reg_val *reg = NULL;
dev_dbg(uphy->dev, "Initializing QMP phy\n");
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;
}
msm_ssphy_qmp_enable_clks(phy, true);
/* select appropriate port select and PHY mode if applicable */
usb_qmp_update_portselect_phymode(phy);
/* power up PHY */
usb_qmp_powerup_phy(phy);
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;
}
usb_qmp_apply_link_training_workarounds(phy);
/* perform software reset of PHY common logic */
if (phy->phy.type == USB_PHY_TYPE_USB3_AND_DP &&
!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE))
writel_relaxed(0x00,
phy->base + phy->phy_reg[USB3_DP_COM_SW_RESET]);
/* perform software reset of PCS/Serdes */
writel_relaxed(0x00, phy->base + phy->phy_reg[USB3_PHY_SW_RESET]);
/* start PCS/Serdes to operation mode */
writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]);
/* 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_dp_combo_reset(struct usb_phy *uphy)
{
struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp,
phy);
int ret = 0;
if (phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE) {
dev_dbg(uphy->dev, "Resetting USB part of QMP phy\n");
/* Assert USB3 PHY CSR reset */
ret = reset_control_assert(phy->phy_reset);
if (ret) {
dev_err(uphy->dev, "phy_reset assert failed\n");
goto exit;
}
/* Deassert USB3 PHY CSR reset */
ret = reset_control_deassert(phy->phy_reset);
if (ret) {
dev_err(uphy->dev, "phy_reset deassert failed\n");
goto exit;
}
return 0;
}
dev_dbg(uphy->dev, "Global reset of QMP DP combo phy\n");
/* Assert global PHY reset */
ret = reset_control_assert(phy->global_phy_reset);
if (ret) {
dev_err(uphy->dev, "global_phy_reset assert failed\n");
goto exit;
}
/* Assert QMP USB PHY reset */
ret = reset_control_assert(phy->phy_reset);
if (ret) {
dev_err(uphy->dev, "phy_reset assert failed\n");
goto exit;
}
/* De-Assert QMP USB PHY reset */
ret = reset_control_deassert(phy->phy_reset);
if (ret)
dev_err(uphy->dev, "phy_reset deassert failed\n");
/* De-Assert global PHY reset */
ret = reset_control_deassert(phy->global_phy_reset);
if (ret)
dev_err(uphy->dev, "global_phy_reset deassert failed\n");
exit:
return ret;
}
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) {
msm_ssusb_qmp_enable_autonomous(phy, 1);
} else {
/* Reset phy mode to USB only if DP not connected */
if (uphy->type == USB_PHY_TYPE_USB3_AND_DP &&
!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE))
msm_ssphy_qmp_setmode(phy, USB3_MODE);
writel_relaxed(0x00,
phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
}
/* Make sure above write completed with PHY */
wmb();
hrtimer_cancel(&phy->timer);
msm_ssphy_qmp_enable_clks(phy, 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);
msm_ssphy_qmp_enable_clks(phy, 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 enum hrtimer_restart timer_fn(struct hrtimer *timer)
{
struct msm_ssphy_qmp *phy =
container_of(timer, struct msm_ssphy_qmp, timer);
u8 status2, status2_1, sw1, mx1, sw2, mx2;
int timeout = 15000;
status2_1 = sw1 = sw2 = mx1 = mx2 = 0;
status2 = readl_relaxed(phy->base +
phy->phy_reg[USB3_DP_PCS_PCS_STATUS2]);
if (status2 & RX_EQUALIZATION_IN_PROGRESS) {
while (timeout > 0) {
status2_1 = readl_relaxed(phy->base +
phy->phy_reg[USB3_DP_PCS_PCS_STATUS2]);
if (status2_1 & RX_EQUALIZATION_IN_PROGRESS) {
timeout -= 500;
udelay(500);
continue;
}
writel_relaxed(0x08, phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]);
writel_relaxed(0x08, phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]);
sw1 = readl_relaxed(phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]);
mx1 = readl_relaxed(phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]);
udelay(1);
writel_relaxed(0x0, phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]);
writel_relaxed(0x0, phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]);
sw2 = readl_relaxed(phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_SW_CTRL3]);
mx2 = readl_relaxed(phy->base +
phy->phy_reg[USB3_DP_PCS_INSIG_MX_CTRL3]);
break;
}
}
dev_dbg(phy->phy.dev,
"st=%x st2=%x sw1=%x sw2=%x mx1=%x mx2=%x timeout=%d\n",
status2, status2_1, sw1, sw2, mx1, mx2, timeout);
hrtimer_forward_now(timer, ms_to_ktime(1));
return HRTIMER_RESTART;
}
static int msm_ssphy_qmp_link_training(struct usb_phy *uphy, bool start)
{
struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp,
phy);
if (start) {
hrtimer_start(&phy->timer, 0, HRTIMER_MODE_REL);
dev_dbg(uphy->dev, "link training start\n");
} else {
hrtimer_cancel(&phy->timer);
dev_dbg(uphy->dev, "link training stop\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);
writel_relaxed(0x00,
phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
readl_relaxed(phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
hrtimer_cancel(&phy->timer);
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_powerup(struct usb_phy *uphy, bool powerup)
{
struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp,
phy);
u8 reg = powerup ? 1 : 0;
u8 temp;
if (!(uphy->flags & PHY_WAKEUP_WA_EN))
return 0;
temp = readl_relaxed(phy->base +
phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
if (temp == powerup)
return 0;
writel_relaxed(reg,
phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
temp = readl_relaxed(phy->base +
phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
dev_dbg(uphy->dev, "P3 powerup:%x\n", temp);
return 0;
}
static int msm_ssphy_qmp_vbus_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
return 0;
}
static int msm_ssphy_qmp_dp_notifier(struct notifier_block *nb,
unsigned long dp_lane, void *ptr)
{
struct msm_ssphy_qmp *phy = container_of(nb,
struct msm_ssphy_qmp, dp_nb);
if (dp_lane == 2 || dp_lane == 4)
phy->phy.flags |= PHY_USB_DP_CONCURRENT_MODE;
else
phy->phy.flags &= ~PHY_USB_DP_CONCURRENT_MODE;
return 0;
}
static int msm_ssphy_qmp_extcon_register(struct msm_ssphy_qmp *phy,
struct device *dev)
{
struct device_node *node = dev->of_node;
struct extcon_dev *edev;
int ret = 0;
if (!of_property_read_bool(node, "extcon"))
return 0;
edev = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(edev)) {
dev_err(dev, "failed to get phandle for msm_ssphy_qmp\n");
return PTR_ERR(edev);
}
phy->extcon_dp = edev;
phy->phy.vbus_nb.notifier_call = msm_ssphy_qmp_vbus_notifier;
phy->dp_nb.notifier_call = msm_ssphy_qmp_dp_notifier;
ret = extcon_register_blocking_notifier(edev, EXTCON_DISP_DP,
&phy->dp_nb);
if (ret < 0) {
dev_err(dev, "failed to register blocking notifier\n");
return ret;
}
return 0;
}
static int msm_ssphy_qmp_get_clks(struct msm_ssphy_qmp *phy, struct device *dev)
{
int ret = 0;
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(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->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;
if (of_property_match_string(dev->of_node,
"clock-names", "com_aux_clk") >= 0) {
phy->com_aux_clk = devm_clk_get(dev, "com_aux_clk");
if (IS_ERR(phy->com_aux_clk)) {
ret = PTR_ERR(phy->com_aux_clk);
if (ret != -EPROBE_DEFER)
dev_err(dev,
"failed to get com_aux_clk ret %d\n", ret);
goto err;
}
}
err:
return ret;
}
static void msm_ssphy_qmp_enable_clks(struct msm_ssphy_qmp *phy, bool on)
{
dev_dbg(phy->phy.dev, "%s(): clk_enabled:%d on:%d\n", __func__,
phy->clk_enabled, on);
if (!phy->clk_enabled && on) {
if (phy->ref_clk_src)
clk_prepare_enable(phy->ref_clk_src);
if (phy->ref_clk)
clk_prepare_enable(phy->ref_clk);
if (phy->com_aux_clk)
clk_prepare_enable(phy->com_aux_clk);
clk_prepare_enable(phy->aux_clk);
if (phy->cfg_ahb_clk)
clk_prepare_enable(phy->cfg_ahb_clk);
clk_prepare_enable(phy->pipe_clk);
phy->clk_enabled = true;
}
if (phy->clk_enabled && !on) {
clk_disable_unprepare(phy->pipe_clk);
if (phy->cfg_ahb_clk)
clk_disable_unprepare(phy->cfg_ahb_clk);
clk_disable_unprepare(phy->aux_clk);
if (phy->com_aux_clk)
clk_disable_unprepare(phy->com_aux_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;
}
}
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->phy.type = USB_PHY_TYPE_USB3;
if (of_device_is_compatible(dev->of_node,
"qcom,usb-ssphy-qmp-dp-combo"))
phy->phy.type = USB_PHY_TYPE_USB3_AND_DP;
if (of_device_is_compatible(dev->of_node,
"qcom,usb-ssphy-qmp-usb3-or-dp"))
phy->phy.type = USB_PHY_TYPE_USB3_OR_DP;
ret = msm_ssphy_qmp_get_clks(phy, dev);
if (ret)
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;
}
if (phy->phy.type == USB_PHY_TYPE_USB3_AND_DP) {
phy->global_phy_reset = devm_reset_control_get(dev,
"global_phy_reset");
if (IS_ERR(phy->global_phy_reset)) {
ret = PTR_ERR(phy->global_phy_reset);
dev_dbg(dev, "failed to get global_phy_reset\n");
goto err;
}
} else {
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->phy_reg = devm_kzalloc(dev, size, GFP_KERNEL);
if (phy->phy_reg) {
phy->reg_offset_cnt = (size / sizeof(*phy->phy_reg));
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->phy_reg, phy->reg_offset_cnt);
} else {
dev_err(dev, "err mem alloc for qmp_phy_reg_offset\n");
return -ENOMEM;
}
} 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;
}
/*
* For USB QMP DP combo PHY, common set of registers shall be accessed
* by DP driver as well.
*/
phy->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
if (IS_ERR_OR_NULL(phy->base)) {
ret = PTR_ERR(phy->base);
goto err;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"vls_clamp_reg");
if (res) {
phy->vls_clamp_reg = devm_ioremap_resource(dev, res);
if (IS_ERR(phy->vls_clamp_reg)) {
dev_err(dev, "err getting vls_clamp_reg address\n");
return PTR_ERR(phy->vls_clamp_reg);
}
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"pcs_clamp_enable_reg");
if (res) {
phy->pcs_clamp_enable_reg = devm_ioremap_resource(dev, res);
if (IS_ERR(phy->pcs_clamp_enable_reg)) {
dev_err(dev, "err getting pcs_clamp_enable_reg address.\n");
return PTR_ERR(phy->pcs_clamp_enable_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);
}
}
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)
return -ENOMEM;
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 need qmp-phy-init-seq\n");
return -EINVAL;
}
/* Set default core voltage values */
phy->core_voltage_levels[CORE_LEVEL_NONE] = 0;
phy->core_voltage_levels[CORE_LEVEL_MIN] = USB_SSPHY_1P2_VOL_MIN;
phy->core_voltage_levels[CORE_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_property_read_s32(dev->of_node, "qcom,core-max-load-uA",
&phy->core_max_uA) || !phy->core_max_uA)
phy->core_max_uA = USB_SSPHY_HPM_LOAD;
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;
}
if (of_property_read_s32(dev->of_node, "qcom,vdd-max-load-uA",
&phy->vdd_max_uA) || !phy->vdd_max_uA)
phy->vdd_max_uA = USB_SSPHY_HPM_LOAD;
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;
}
platform_set_drvdata(pdev, phy);
if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override"))
phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE;
hrtimer_init(&phy->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
phy->timer.function = timer_fn;
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.powerup = msm_ssphy_qmp_powerup;
phy->phy.reset = msm_ssphy_qmp_reset;
if (phy->phy.type == USB_PHY_TYPE_USB3_AND_DP) {
phy->eq_config5_offset = USB3_DP_PCS_EQ_CONFIG5;
phy->phy.reset = msm_ssphy_qmp_dp_combo_reset;
} else if (phy->phy.type == USB_PHY_TYPE_USB3) {
phy->eq_config5_offset = USB3_UNI_PCS_EQ_CONFIG5;
}
phy->link_training_reset = of_property_read_bool(dev->of_node,
"qcom,link-training-reset");
ret = msm_ssphy_qmp_extcon_register(phy, dev);
if (ret)
goto err;
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);
msm_ssphy_qmp_enable_clks(phy, false);
msm_ssusb_qmp_ldo_enable(phy, 0);
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");