Files
android_kernel_samsung_sm8750/drivers/usb/phy/phy-msm-snps-eusb2.c
2025-08-11 14:29:00 +02:00

1155 lines
31 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "eusb2_phy: %s: " fmt, __func__
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/dwc3-msm.h>
#include <linux/usb/phy.h>
#include <linux/usb/repeater.h>
#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET)
#include <linux/usb/f_ss_mon_gadget.h>
#endif
#define USB_PHY_UTMI_CTRL0 (0x3c)
#define OPMODE_MASK (0x3 << 3)
#define OPMODE_NONDRIVING (0x1 << 3)
#define SLEEPM BIT(0)
#define USB_PHY_UTMI_CTRL5 (0x50)
#define POR BIT(1)
#define USB_PHY_HS_PHY_CTRL_COMMON0 (0x54)
#define PHY_ENABLE BIT(0)
#define SIDDQ_SEL BIT(1)
#define SIDDQ BIT(2)
#define RETENABLEN BIT(3)
#define FSEL (0x7 << 4)
#define FSEL_19_2_MHZ_VAL (0x0 << 4)
#define FSEL_38_4_MHZ_VAL (0x4 << 4)
#define USB_PHY_CFG_CTRL_1 (0x58)
#define PHY_CFG_PLL_CPBIAS_CNTRL (0xfe)
#define PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT (0x1)
#define USB_PHY_CFG_CTRL_2 (0x5c)
#define PHY_CFG_PLL_FB_DIV_7_0 (0xff)
#define DIV_7_0_19_2_MHZ_VAL (0x90)
#define DIV_7_0_38_4_MHZ_VAL (0xc8)
#define USB_PHY_CFG_CTRL_3 (0x60)
#define PHY_CFG_PLL_FB_DIV_11_8 (0xf)
#define DIV_11_8_19_2_MHZ_VAL (0x1)
#define DIV_11_8_38_4_MHZ_VAL (0x0)
#define PHY_CFG_PLL_REF_DIV (0xf << 4)
#define PLL_REF_DIV_VAL (0x0)
#define USB_PHY_HS_PHY_CTRL2 (0x64)
#define VBUSVLDEXT0 BIT(0)
#define USB2_SUSPEND_N BIT(2)
#define USB2_SUSPEND_N_SEL BIT(3)
#define VBUS_DET_EXT_SEL BIT(4)
#define USB_PHY_CFG_CTRL_4 (0x68)
#define PHY_CFG_PLL_GMP_CNTRL (0x3)
#define PHY_CFG_PLL_GMP_CNTRL_SHIFT (0x0)
#define PHY_CFG_PLL_INT_CNTRL (0xfc)
#define PHY_CFG_PLL_INT_CNTRL_SHIFT (0x2)
#define USB_PHY_CFG_CTRL_5 (0x6c)
#define PHY_CFG_PLL_PROP_CNTRL (0x1f)
#define PHY_CFG_PLL_PROP_CNTRL_SHIFT (0x0)
#define PHY_CFG_PLL_VREF_TUNE (0x3 << 6)
#define PHY_CFG_PLL_VREF_TUNE_SHIFT (6)
#define USB_PHY_CFG_CTRL_6 (0x70)
#define PHY_CFG_PLL_VCO_CNTRL (0x7)
#define PHY_CFG_PLL_VCO_CNTRL_SHIFT (0x0)
#define USB_PHY_CFG_CTRL_7 (0x74)
#define USB_PHY_CFG_CTRL_8 (0x78)
#define PHY_CFG_TX_FSLS_VREF_TUNE (0x3)
#define PHY_CFG_TX_FSLS_VREG_BYPASS BIT(2)
#define PHY_CFG_TX_HS_VREF_TUNE (0x7 << 3)
#define PHY_CFG_TX_HS_VREF_TUNE_SHIFT (0x3)
#define PHY_CFG_TX_HS_XV_TUNE (0x3 << 6)
#define PHY_CFG_TX_HS_XV_TUNE_SHIFT (6)
#define USB_PHY_CFG_CTRL_9 (0x7c)
#define PHY_CFG_TX_PREEMP_TUNE (0x7)
#define PHY_CFG_TX_PREEMP_TUNE_SHIFT (0x0)
#define PHY_CFG_TX_RES_TUNE (0x3 << 3)
#define PHY_CFG_TX_RES_TUNE_SHIFT (0x3)
#define PHY_CFG_TX_RISE_TUNE (0x3 << 5)
#define PHY_CFG_TX_RISE_TUNE_SHIFT (0x5)
#define PHY_CFG_RCAL_BYPASS BIT(7)
#define PHY_CFG_RCAL_BYPASS_SHIFT (0x7)
#define USB_PHY_CFG_CTRL_10 (0x80)
#define USB_PHY_CFG0 (0x94)
#define DATAPATH_CTRL_OVERRIDE_EN BIT(0)
#define CMN_CTRL_OVERRIDE_EN BIT(1)
#define UTMI_PHY_CMN_CTRL0 (0x98)
#define TESTBURNIN BIT(6)
#define USB_PHY_FSEL_SEL (0xb8)
#define FSEL_SEL BIT(0)
#define USB_PHY_APB_ACCESS_CMD (0x130)
#define RW_ACCESS BIT(0)
#define APB_START_CMD BIT(1)
#define APB_LOGIC_RESET BIT(2)
#define USB_PHY_APB_ACCESS_STATUS (0x134)
#define ACCESS_DONE BIT(0)
#define TIMED_OUT BIT(1)
#define ACCESS_ERROR BIT(2)
#define ACCESS_IN_PROGRESS BIT(3)
#define USB_PHY_APB_ADDRESS (0x138)
#define APB_REG_ADDR (0xff)
#define USB_PHY_APB_WRDATA_LSB (0x13c)
#define APB_REG_WRDATA_7_0 (0xf)
#define USB_PHY_APB_WRDATA_MSB (0x140)
#define APB_REG_WRDATA_15_8 (0xf0)
#define USB_PHY_APB_RDDATA_LSB (0x144)
#define APB_REG_RDDATA_7_0 (0xf)
#define USB_PHY_APB_RDDATA_MSB (0x148)
#define APB_REG_RDDATA_15_8 (0xf0)
/* EUD CSR field */
#define EUD_EN2 BIT(0)
/* VIOCTL_EUD_DETECT register based EUD_DETECT field */
#define EUD_DETECT BIT(0)
#define USB_HSPHY_1P2_VOL_MIN 1200000 /* uV */
#define USB_HSPHY_1P2_VOL_MAX 1200000 /* uV */
#define USB_HSPHY_1P2_HPM_LOAD 5905 /* uA */
#define USB_HSPHY_VDD_HPM_LOAD 7757 /* uA */
#undef dev_dbg
#define dev_dbg dev_err
struct msm_eusb2_phy {
struct usb_phy phy;
void __iomem *base;
/* EUD related parameters */
phys_addr_t eud_reg;
void __iomem *eud_enable_reg;
void __iomem *eud_detect_reg;
bool re_enable_eud;
struct clk *ref_clk_src;
struct clk *ref_clk;
struct reset_control *phy_reset;
struct regulator *vdd;
struct regulator *vdda12;
int vdd_levels[3]; /* none, low, high */
bool clocks_enabled;
bool power_enabled;
bool suspended;
bool cable_connected;
struct power_supply *usb_psy;
unsigned int vbus_draw;
struct work_struct vbus_draw_work;
int *param_override_seq;
int param_override_seq_cnt;
/* debugfs entries */
struct dentry *root;
u8 tx_pre_emphasis;
u8 tx_rise_fall_time;
u8 tx_src_impedence;
u8 tx_dc_vref;
u8 tx_xv;
struct usb_repeater *ur;
};
static inline bool is_eud_debug_mode_active(struct msm_eusb2_phy *phy)
{
if (phy->eud_enable_reg &&
(readl_relaxed(phy->eud_enable_reg) & EUD_EN2))
return true;
return false;
}
static void msm_eusb2_phy_clocks(struct msm_eusb2_phy *phy, bool on)
{
dev_dbg(phy->phy.dev, "clocks_enabled:%d on:%d\n",
phy->clocks_enabled, on);
if (phy->clocks_enabled == on)
return;
if (on) {
clk_prepare_enable(phy->ref_clk_src);
clk_prepare_enable(phy->ref_clk);
} else {
clk_disable_unprepare(phy->ref_clk);
clk_disable_unprepare(phy->ref_clk_src);
}
phy->clocks_enabled = on;
}
static void msm_eusb2_phy_update_eud_detect(struct msm_eusb2_phy *phy, bool set)
{
if (set)
writel_relaxed(EUD_DETECT, phy->eud_detect_reg);
else
writel_relaxed(readl_relaxed(phy->eud_detect_reg) & ~EUD_DETECT,
phy->eud_detect_reg);
}
static int msm_eusb2_phy_power(struct msm_eusb2_phy *phy, bool on)
{
int ret = 0;
dev_dbg(phy->phy.dev, "turn %s regulators. power_enabled:%d\n",
on ? "on" : "off", phy->power_enabled);
if (phy->power_enabled == on)
return 0;
if (!on)
goto clear_eud_det;
ret = regulator_set_load(phy->vdd, USB_HSPHY_VDD_HPM_LOAD);
if (ret < 0) {
dev_err(phy->phy.dev, "Unable to set HPM of vdd:%d\n", ret);
goto err_vdd;
}
ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[1],
phy->vdd_levels[2]);
if (ret) {
dev_err(phy->phy.dev, "Unable to set voltage for hsusb vdd\n");
goto put_vdd_lpm;
}
ret = regulator_enable(phy->vdd);
if (ret) {
dev_err(phy->phy.dev, "Unable to enable VDD\n");
goto unconfig_vdd;
}
ret = regulator_set_load(phy->vdda12, USB_HSPHY_1P2_HPM_LOAD);
if (ret < 0) {
dev_err(phy->phy.dev, "Unable to set HPM of vdda12:%d\n", ret);
goto disable_vdd;
}
ret = regulator_set_voltage(phy->vdda12, USB_HSPHY_1P2_VOL_MIN,
USB_HSPHY_1P2_VOL_MAX);
if (ret) {
dev_err(phy->phy.dev,
"Unable to set voltage for vdda12:%d\n", ret);
goto put_vdda12_lpm;
}
ret = regulator_enable(phy->vdda12);
if (ret) {
dev_err(phy->phy.dev, "Unable to enable vdda12:%d\n", ret);
goto unset_vdda12;
}
/* Make sure all the writes are processed before setting EUD_DETECT */
mb();
/* Set eud_detect_reg after powering on eUSB PHY rails to bring EUD out of reset */
msm_eusb2_phy_update_eud_detect(phy, true);
phy->power_enabled = true;
pr_debug("eUSB2_PHY's regulators are turned ON.\n");
return ret;
clear_eud_det:
/* Clear eud_detect_reg to put EUD in reset */
msm_eusb2_phy_update_eud_detect(phy, false);
/* Make sure clearing EUD_DETECT is completed before turning off the regulators */
mb();
ret = regulator_disable(phy->vdda12);
if (ret)
dev_err(phy->phy.dev, "Unable to disable vdda12:%d\n", ret);
unset_vdda12:
ret = regulator_set_voltage(phy->vdda12, 0, USB_HSPHY_1P2_VOL_MAX);
if (ret)
dev_err(phy->phy.dev,
"Unable to set (0) voltage for vdda12:%d\n", ret);
put_vdda12_lpm:
ret = regulator_set_load(phy->vdda12, 0);
if (ret < 0)
dev_err(phy->phy.dev, "Unable to set LPM of vdda12\n");
disable_vdd:
ret = regulator_disable(phy->vdd);
if (ret)
dev_err(phy->phy.dev, "Unable to disable vdd:%d\n", ret);
unconfig_vdd:
ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[0],
phy->vdd_levels[2]);
if (ret)
dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n");
put_vdd_lpm:
ret = regulator_set_load(phy->vdd, 0);
if (ret < 0)
dev_err(phy->phy.dev, "Unable to set LPM of vdd\n");
/* case handling when regulator turning on failed */
if (!phy->power_enabled)
return -EINVAL;
err_vdd:
phy->power_enabled = false;
dev_dbg(phy->phy.dev, "eusb2_PHY's regulators are turned OFF.\n");
return ret;
}
static void msm_eusb2_write_readback(void __iomem *base, u32 offset,
const u32 mask, u32 val)
{
u32 write_val, tmp = readl_relaxed(base + offset);
tmp &= ~mask;
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;
if (tmp != val)
pr_err("write: %x to offset: %x FAILED\n", val, offset);
}
static void eusb2_phy_reset_seq(struct msm_eusb2_phy *phy)
{
writel(APB_LOGIC_RESET, phy->base + USB_PHY_APB_ACCESS_CMD);
writel(0x00, phy->base + USB_PHY_APB_ACCESS_CMD);
}
#define APB_ACCESS_TIMEOUT 10 /* in us */
#define APB_ACCESS_POLL_DELAY 1 /* in us */
#define APB_READ_ACCESS_DONE 1
static int eusb2_phy_apb_cmd_wait(struct msm_eusb2_phy *phy)
{
int ret;
u8 val; /* to read cmd status */
/* poll for cmd completion */
ret = readl_poll_timeout(phy->base + USB_PHY_APB_ACCESS_STATUS,
val, APB_READ_ACCESS_DONE,
APB_ACCESS_POLL_DELAY, APB_ACCESS_TIMEOUT);
if (ret < 0) {
dev_err(phy->phy.dev, "APB_ACCESS_STAUS(%x) timeout\n", val);
eusb2_phy_reset_seq(phy);
return val;
}
return 0;
}
static void eusb2_phy_apb_reg_write(struct msm_eusb2_phy *phy,
u8 reg_index, u16 val)
{
int ret;
/* program register index to update requested register */
writel(reg_index, phy->base + USB_PHY_APB_ADDRESS);
/* value to be program */
writel(((val >> 8) & 0xF), phy->base + USB_PHY_APB_WRDATA_MSB);
writel((val & 0xF), phy->base + USB_PHY_APB_WRDATA_LSB);
/* send cmd to update reg_index with above programmed value */
writel(RW_ACCESS | APB_START_CMD, phy->base + USB_PHY_APB_ACCESS_CMD);
/* poll for cmd completion */
ret = eusb2_phy_apb_cmd_wait(phy);
if (ret) {
dev_err(phy->phy.dev, "APB reg(%x) write failed\n", reg_index);
return;
}
/* write access completed */
writel(0x0, phy->base + USB_PHY_APB_ACCESS_CMD);
dev_info(phy->phy.dev, "APB reg(%x) updated with %x\n", reg_index, val);
}
static void eusb2_phy_apb_reg_read(struct msm_eusb2_phy *phy, u8 reg_index)
{
int ret;
u32 rddata_lsb;
u32 rddata_msb;
/* program register which is required to read */
writel(reg_index, phy->base + USB_PHY_APB_ADDRESS);
/* send cmd to read reg_index based register value */
writel(APB_START_CMD, phy->base + USB_PHY_APB_ACCESS_CMD);
/* poll for cmd completion */
ret = eusb2_phy_apb_cmd_wait(phy);
if (ret) {
dev_err(phy->phy.dev, "APB reg(%x) read failed\n", reg_index);
return;
}
/* read data of reg_index register */
rddata_lsb = readl(phy->base + USB_PHY_APB_RDDATA_LSB);
rddata_msb = readl(phy->base + USB_PHY_APB_RDDATA_MSB);
/* read access completed */
writel(0x0, phy->base + USB_PHY_APB_ACCESS_CMD);
dev_info(phy->phy.dev, "APB reg(%x) read success, val:%x\n",
reg_index, ((rddata_msb << 8) | rddata_lsb));
}
static int apb_reg_rw_open(struct inode *inode, struct file *file)
{
return single_open(file, NULL, inode->i_private);
}
static ssize_t apb_reg_rw_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct msm_eusb2_phy *phy = s->private;
char buf[32];
bool read;
char *field, *p;
u8 reg_index;
u16 val;
if (!phy->cable_connected) {
dev_err(phy->phy.dev, "eUSB2 PHY is not out of reset.\n");
return -EINVAL;
}
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
/*
* For reading APB register: read <offset> e.g. read 0x33
* For writing value into APB register: write <offset> <value>
* e.g. write 0xff 0xd
* Valid range for offset: 0x00 - 0xFF
* Valid range for value: 0x0000 - 0xFFFF
*/
p = buf;
field = strsep(&p, " ");
if (!field || !*field)
goto err_out;
if (strcmp(field, "read") == 0)
read = true;
else if (strcmp(field, "write") == 0)
read = false;
else
goto err_out;
/* get register offset */
field = strsep(&p, " ");
if (!field) {
dev_err(phy->phy.dev, "Provide register offset\n");
goto err_out;
}
if (kstrtou8(field, 16, &reg_index)) {
dev_err(phy->phy.dev, "Invalid register offset\n");
goto err_out;
}
/* read apb register */
if (read) {
eusb2_phy_apb_reg_read(phy, reg_index);
goto done;
}
/* get value to update register for write */
field = strsep(&p, " ");
if (!field) {
dev_err(phy->phy.dev, "Provide value to update register\n");
goto err_out;
}
if (kstrtou16(field, 16, &val)) {
dev_err(phy->phy.dev, "Invalid value\n");
goto err_out;
}
eusb2_phy_apb_reg_write(phy, reg_index, val);
goto done;
err_out:
pr_info("Error: enter proper values to read OR write APB register.\n");
pr_info("To read APB register: read <offset>\n");
pr_info("To write APB register: write <offset> <value>\n");
pr_info("Valid range for offset: 0x00 - 0xFF\n");
pr_info("Valid range for value: 0x0000 - 0xFFFF\n");
done:
return count;
}
static const struct file_operations apb_reg_rw_fops = {
.open = apb_reg_rw_open,
.write = apb_reg_rw_write,
.release = single_release,
};
static void msm_eusb2_phy_reset(struct msm_eusb2_phy *phy)
{
int ret;
ret = reset_control_assert(phy->phy_reset);
if (ret)
dev_err(phy->phy.dev, "phy reset assert failed\n");
usleep_range(100, 150);
ret = reset_control_deassert(phy->phy_reset);
if (ret)
dev_err(phy->phy.dev, "phy reset deassert failed\n");
}
static void eusb2_phy_write_seq(void __iomem *base, u32 *seq, int cnt)
{
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]);
}
}
static void msm_eusb2_parameter_override(struct msm_eusb2_phy *phy)
{
/* default parameters: tx pre-emphasis */
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9,
PHY_CFG_TX_PREEMP_TUNE, (0 << PHY_CFG_TX_PREEMP_TUNE_SHIFT));
/* tx rise/fall time */
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9,
PHY_CFG_TX_RISE_TUNE, (0x2 << PHY_CFG_TX_RISE_TUNE_SHIFT));
/* source impedance adjustment */
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9,
PHY_CFG_TX_RES_TUNE, (0x1 << PHY_CFG_TX_RES_TUNE_SHIFT));
/* dc voltage level adjustement */
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8,
PHY_CFG_TX_HS_VREF_TUNE, (0x3 << PHY_CFG_TX_HS_VREF_TUNE_SHIFT));
/* transmitter HS crossover adjustement */
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8,
PHY_CFG_TX_HS_XV_TUNE, (0x0 << PHY_CFG_TX_HS_XV_TUNE_SHIFT));
/* override init sequence using devicetree based values */
eusb2_phy_write_seq(phy->base, phy->param_override_seq,
phy->param_override_seq_cnt);
/* override tune params using debugfs based values */
if (phy->tx_pre_emphasis && phy->tx_pre_emphasis <= 7)
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9,
PHY_CFG_TX_PREEMP_TUNE,
(phy->tx_pre_emphasis << PHY_CFG_TX_PREEMP_TUNE_SHIFT));
if (phy->tx_rise_fall_time && phy->tx_rise_fall_time <= 4)
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9,
PHY_CFG_TX_RISE_TUNE,
(phy->tx_rise_fall_time << PHY_CFG_TX_RISE_TUNE_SHIFT));
if (phy->tx_src_impedence && phy->tx_src_impedence <= 4)
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9,
PHY_CFG_TX_RES_TUNE,
(phy->tx_src_impedence << PHY_CFG_TX_RES_TUNE_SHIFT));
if (phy->tx_dc_vref && phy->tx_dc_vref <= 7)
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8,
PHY_CFG_TX_HS_VREF_TUNE,
(phy->tx_dc_vref << PHY_CFG_TX_HS_VREF_TUNE_SHIFT));
if (phy->tx_xv && phy->tx_xv <= 4)
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8,
PHY_CFG_TX_HS_XV_TUNE,
(phy->tx_xv << PHY_CFG_TX_HS_XV_TUNE_SHIFT));
}
static void msm_eusb2_ref_clk_init(struct usb_phy *uphy)
{
unsigned long ref_clk_freq;
struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy);
ref_clk_freq = clk_get_rate(phy->ref_clk_src);
switch (ref_clk_freq) {
case 19200000:
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0,
FSEL, FSEL_19_2_MHZ_VAL);
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_2,
PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_19_2_MHZ_VAL);
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3,
PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_19_2_MHZ_VAL);
break;
case 38400000:
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0,
FSEL, FSEL_38_4_MHZ_VAL);
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_2,
PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_38_4_MHZ_VAL);
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3,
PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_38_4_MHZ_VAL);
break;
default:
dev_err(uphy->dev, "unsupported ref_clk_freq:%lu\n",
ref_clk_freq);
}
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3,
PHY_CFG_PLL_REF_DIV, PLL_REF_DIV_VAL);
}
static int msm_eusb2_repeater_reset_and_init(struct msm_eusb2_phy *phy)
{
int ret;
if (phy->ur)
phy->ur->flags = phy->phy.flags;
ret = usb_repeater_powerup(phy->ur);
if (ret)
dev_err(phy->phy.dev, "repeater powerup failed.\n");
ret = usb_repeater_reset(phy->ur, true);
if (ret)
dev_err(phy->phy.dev, "repeater reset failed.\n");
#if IS_ENABLED(CONFIG_USB_PHY_SETTING_QCOM)
if (phy->ur) {
if (phy->phy.flags & PHY_HOST_MODE)
phy->ur->is_host = true;
else
phy->ur->is_host = false;
} else
dev_err(phy->phy.dev, "phy->ur is null.\n");
/* device start up time. TI 3ms, NXP 1ms */
usleep_range(3000, 3500);
#endif
ret = usb_repeater_init(phy->ur);
if (ret)
dev_err(phy->phy.dev, "repeater init failed.\n");
return ret;
}
static int msm_eusb2_phy_init(struct usb_phy *uphy)
{
struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy);
int ret;
dev_dbg(uphy->dev, "phy_flags:%x\n", phy->phy.flags);
if (is_eud_debug_mode_active(phy)) {
/* if in host mode, disable EUD debug mode */
if (phy->phy.flags & PHY_HOST_MODE) {
qcom_scm_io_writel(phy->eud_reg, 0x0);
phy->re_enable_eud = true;
} else {
msm_eusb2_phy_power(phy, true);
msm_eusb2_phy_clocks(phy, true);
return msm_eusb2_repeater_reset_and_init(phy);
}
}
ret = msm_eusb2_phy_power(phy, true);
if (ret)
return ret;
/* Bring eUSB2 repeater out of reset and initialized before eUSB2 PHY */
ret = msm_eusb2_repeater_reset_and_init(phy);
if (ret)
return ret;
msm_eusb2_phy_clocks(phy, true);
msm_eusb2_phy_reset(phy);
msm_eusb2_write_readback(phy->base, USB_PHY_CFG0,
CMN_CTRL_OVERRIDE_EN, CMN_CTRL_OVERRIDE_EN);
msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL5, POR, POR);
udelay(10);
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0,
PHY_ENABLE | RETENABLEN, PHY_ENABLE | RETENABLEN);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD,
APB_LOGIC_RESET, APB_LOGIC_RESET);
msm_eusb2_write_readback(phy->base, UTMI_PHY_CMN_CTRL0, TESTBURNIN, 0);
msm_eusb2_write_readback(phy->base, USB_PHY_FSEL_SEL,
FSEL_SEL, FSEL_SEL);
/* update ref_clk related registers */
msm_eusb2_ref_clk_init(uphy);
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_1,
PHY_CFG_PLL_CPBIAS_CNTRL,
(0x1 << PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT));
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_4,
PHY_CFG_PLL_INT_CNTRL,
(0x8 << PHY_CFG_PLL_INT_CNTRL_SHIFT));
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_4,
PHY_CFG_PLL_GMP_CNTRL,
(0x1 << PHY_CFG_PLL_GMP_CNTRL_SHIFT));
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_5,
PHY_CFG_PLL_PROP_CNTRL,
(0x10 << PHY_CFG_PLL_PROP_CNTRL_SHIFT));
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_6,
PHY_CFG_PLL_VCO_CNTRL,
(0x0 << PHY_CFG_PLL_VCO_CNTRL_SHIFT));
msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_5,
PHY_CFG_PLL_VREF_TUNE,
(0x1 << PHY_CFG_PLL_VREF_TUNE_SHIFT));
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2,
VBUS_DET_EXT_SEL, VBUS_DET_EXT_SEL);
/* set parameter override if needed */
msm_eusb2_parameter_override(phy);
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N);
msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL0, SLEEPM, SLEEPM);
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0,
SIDDQ_SEL, SIDDQ_SEL);
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0,
SIDDQ, 0);
msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL5, POR, 0);
msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2,
USB2_SUSPEND_N_SEL, 0);
msm_eusb2_write_readback(phy->base, USB_PHY_CFG0,
CMN_CTRL_OVERRIDE_EN, 0x00);
return 0;
}
static int msm_eusb2_phy_set_suspend(struct usb_phy *uphy, int suspend)
{
struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy);
if (phy->suspended && suspend) {
dev_dbg(uphy->dev, "USB PHY is already suspended\n");
return 0;
}
dev_dbg(uphy->dev, "phy->flags:0x%x\n", phy->phy.flags);
if (suspend) {
/* Bus suspend handling */
if (phy->cable_connected ||
(phy->phy.flags & PHY_HOST_MODE)) {
msm_eusb2_phy_clocks(phy, false);
goto suspend_exit;
}
/* Cable disconnect handling */
if (phy->re_enable_eud) {
dev_dbg(uphy->dev, "re-enabling EUD\n");
qcom_scm_io_writel(phy->eud_reg, 0x1);
phy->re_enable_eud = false;
}
/* With EUD spoof disconnect, keep clk and ldos on */
if ((phy->phy.flags & EUD_SPOOF_DISCONNECT) || is_eud_debug_mode_active(phy))
goto suspend_exit;
msm_eusb2_phy_clocks(phy, false);
msm_eusb2_phy_power(phy, false);
/* Hold repeater into reset after powering down PHY */
usb_repeater_reset(phy->ur, false);
usb_repeater_powerdown(phy->ur);
} else {
/* Bus resume and cable connect handling */
msm_eusb2_phy_power(phy, true);
msm_eusb2_phy_clocks(phy, true);
}
suspend_exit:
phy->suspended = !!suspend;
return 0;
}
static int msm_eusb2_phy_notify_connect(struct usb_phy *uphy,
enum usb_device_speed speed)
{
struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy);
phy->cable_connected = true;
/*
* SW WA for CV9 RESET DEVICE TEST(TD 9.23) compliance test failure.
* During HS to SS transitions UTMI_TX Valid signal remains high causing
* the next HS connect to fail. The below sequence sends an extra TX_READY
* signal when the link transitions from HS to SS mode to lower down the
* TX_VALID signal.
*/
if (!(phy->phy.flags & PHY_HOST_MODE) && (speed >= USB_SPEED_SUPER)) {
msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD, 0xff, 0x0);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_ADDRESS, 0xff, 0x5);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_WRDATA_LSB, 0xff, 0xc0);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD, 0xff, 0x3);
udelay(2);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD, 0xff, 0x0);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_WRDATA_LSB, 0xff, 0x0);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD, 0xff, 0x3);
udelay(2);
msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD, 0xff, 0x0);
}
return 0;
}
static int msm_eusb2_phy_notify_disconnect(struct usb_phy *uphy,
enum usb_device_speed speed)
{
struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy);
if (is_eud_debug_mode_active(phy) && !(phy->phy.flags & EUD_SPOOF_DISCONNECT)) {
msm_eusb2_phy_update_eud_detect(phy, false);
/* Ensure that EUD disable occurs before re-enabling */
mb();
msm_eusb2_phy_update_eud_detect(phy, true);
}
phy->cable_connected = false;
return 0;
}
static void msm_eusb2_phy_vbus_draw_work(struct work_struct *w)
{
struct msm_eusb2_phy *phy = container_of(w, struct msm_eusb2_phy,
vbus_draw_work);
union power_supply_propval val = {0};
int ret;
if (!phy->usb_psy) {
phy->usb_psy = power_supply_get_by_name("usb");
if (!phy->usb_psy) {
dev_err(phy->phy.dev, "Could not get usb psy\n");
return;
}
}
#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET)
/* USB SUSPEND CURRENT SETTINGS */
if (phy->vbus_draw == 2) {
pr_err("[USB] make suspend currrent event\n");
make_suspend_current_event();
}
#endif
dev_info(phy->phy.dev, "Avail curr from USB = %u\n", phy->vbus_draw);
/* Set max current limit in uA */
val.intval = 1000 * phy->vbus_draw;
ret = power_supply_set_property(phy->usb_psy,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
if (ret) {
dev_dbg(phy->phy.dev, "Error setting ICL:(%d)\n", ret);
return;
}
}
static int msm_eusb2_phy_set_power(struct usb_phy *uphy, unsigned int mA)
{
struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy);
if (phy->cable_connected && (mA == 0))
return 0;
phy->vbus_draw = mA;
schedule_work(&phy->vbus_draw_work);
return 0;
}
static void msm_eusb2_phy_create_debugfs(struct msm_eusb2_phy *phy)
{
phy->root = debugfs_create_dir(dev_name(phy->phy.dev), NULL);
debugfs_create_x8("tx_pre_emphasis", 0644, phy->root,
&phy->tx_pre_emphasis);
debugfs_create_x8("tx_rise_fall_time", 0644, phy->root,
&phy->tx_rise_fall_time);
debugfs_create_x8("tx_src_imp", 0644, phy->root,
&phy->tx_src_impedence);
debugfs_create_x8("tx_dc_vref", 0644, phy->root, &phy->tx_dc_vref);
debugfs_create_x8("tx_xv", 0644, phy->root, &phy->tx_xv);
debugfs_create_file("apb_reg_rw", 0200, phy->root, phy,
&apb_reg_rw_fops);
}
static int msm_eusb2_phy_probe(struct platform_device *pdev)
{
struct msm_eusb2_phy *phy;
struct device *dev = &pdev->dev;
struct resource *res;
int ret = 0;
struct usb_repeater *ur = NULL;
pr_info("%s\n", __func__);
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
ret = -ENOMEM;
goto err_ret;
}
ur = devm_usb_get_repeater_by_phandle(dev, "usb-repeater", 0);
if (IS_ERR(ur)) {
ret = PTR_ERR(ur);
goto err_ret;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"eusb2_phy_base");
if (!res) {
dev_err(dev, "missing eusb2phy memory resource\n");
ret = -ENODEV;
goto err_ret;
}
phy->base = devm_ioremap_resource(dev, res);
if (IS_ERR(phy->base)) {
dev_err(dev, "ioremap failed\n");
ret = -ENODEV;
goto err_ret;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_enable_reg");
if (res) {
phy->eud_enable_reg = devm_ioremap_resource(dev, res);
if (IS_ERR(phy->eud_enable_reg)) {
ret = PTR_ERR(phy->eud_enable_reg);
dev_err(dev, "eud_enable_reg ioremap err:%d\n", ret);
goto err_ret;
}
phy->eud_reg = res->start;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_detect_reg");
if (!res) {
dev_err(dev, "missing eud_detect register address\n");
ret = -ENODEV;
goto err_ret;
}
phy->eud_detect_reg = devm_ioremap_resource(dev, res);
if (IS_ERR(phy->eud_detect_reg)) {
ret = PTR_ERR(phy->eud_detect_reg);
dev_err(dev, "eud_detect_reg ioremap err:%d\n", ret);
goto err_ret;
}
phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src");
if (IS_ERR(phy->ref_clk_src)) {
dev_err(dev, "clk get failed for ref_clk_src\n");
ret = PTR_ERR(phy->ref_clk_src);
goto err_ret;
}
phy->ref_clk = devm_clk_get_optional(dev, "ref_clk");
if (IS_ERR(phy->ref_clk)) {
dev_err(dev, "clk get failed for ref_clk\n");
ret = PTR_ERR(phy->ref_clk);
goto err_ret;
}
phy->phy_reset = devm_reset_control_get(dev, "phy_reset");
if (IS_ERR(phy->phy_reset)) {
ret = PTR_ERR(phy->phy_reset);
goto err_ret;
}
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->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->vdda12 = devm_regulator_get(dev, "vdda12");
if (IS_ERR(phy->vdda12)) {
dev_err(dev, "unable to get vdda12 supply\n");
ret = PTR_ERR(phy->vdda12);
goto err_ret;
}
phy->param_override_seq_cnt = of_property_count_elems_of_size(
dev->of_node, "qcom,param-override-seq",
sizeof(*phy->param_override_seq));
if (phy->param_override_seq_cnt % 2) {
dev_err(dev, "invalid param_override_seq_len\n");
ret = -EINVAL;
goto err_ret;
}
if (phy->param_override_seq_cnt > 0) {
phy->param_override_seq = devm_kcalloc(dev,
phy->param_override_seq_cnt,
sizeof(*phy->param_override_seq),
GFP_KERNEL);
if (!phy->param_override_seq) {
ret = -ENOMEM;
goto err_ret;
}
ret = of_property_read_u32_array(dev->of_node,
"qcom,param-override-seq",
phy->param_override_seq,
phy->param_override_seq_cnt);
if (ret) {
dev_err(dev, "qcom,param-override-seq read failed %d\n",
ret);
goto err_ret;
}
}
phy->ur = ur;
phy->phy.dev = dev;
platform_set_drvdata(pdev, phy);
phy->phy.init = msm_eusb2_phy_init;
phy->phy.set_suspend = msm_eusb2_phy_set_suspend;
phy->phy.notify_connect = msm_eusb2_phy_notify_connect;
phy->phy.notify_disconnect = msm_eusb2_phy_notify_disconnect;
phy->phy.set_power = msm_eusb2_phy_set_power;
phy->phy.type = USB_PHY_TYPE_USB2;
ret = usb_add_phy_dev(&phy->phy);
if (ret)
goto err_ret;
INIT_WORK(&phy->vbus_draw_work, msm_eusb2_phy_vbus_draw_work);
msm_eusb2_phy_create_debugfs(phy);
/*
* EUD may be enable in boot loader and to keep EUD session alive across
* kernel boot till USB phy driver is initialized based on cable status,
* keep LDOs, clocks and repeater on here.
*/
if (is_eud_debug_mode_active(phy)) {
msm_eusb2_phy_power(phy, true);
msm_eusb2_phy_clocks(phy, true);
msm_eusb2_repeater_reset_and_init(phy);
}
pr_info("%s done\n", __func__);
return 0;
err_ret:
pr_info("%s failed. ret(%d)\n", __func__, ret);
return ret;
}
static int msm_eusb2_phy_remove(struct platform_device *pdev)
{
struct msm_eusb2_phy *phy = platform_get_drvdata(pdev);
if (!phy)
return 0;
flush_work(&phy->vbus_draw_work);
if (phy->usb_psy)
power_supply_put(phy->usb_psy);
debugfs_remove_recursive(phy->root);
usb_remove_phy(&phy->phy);
clk_disable_unprepare(phy->ref_clk);
clk_disable_unprepare(phy->ref_clk_src);
msm_eusb2_phy_clocks(phy, false);
msm_eusb2_phy_power(phy, false);
return 0;
}
static const struct of_device_id msm_usb_id_table[] = {
{
.compatible = "qcom,usb-snps-eusb2-phy",
},
{ },
};
MODULE_DEVICE_TABLE(of, msm_usb_id_table);
static struct platform_driver msm_eusb2_phy_driver = {
.probe = msm_eusb2_phy_probe,
.remove = msm_eusb2_phy_remove,
.driver = {
.name = "msm_eusb2_phy",
.of_match_table = of_match_ptr(msm_usb_id_table),
},
};
module_platform_driver(msm_eusb2_phy_driver);
MODULE_DESCRIPTION("MSM USB eUSB2 PHY driver");
MODULE_LICENSE("GPL");