usb: phy: Add snapshot of USB PHY drivers

Snapshot of the downstream USB PHY drivers based on msm-5.15.
commit 00801f75c3b8 ("Merge "msm: Add initial support for
Khaje in Kconfig platform"").

Change-Id: If7560e3f279d6d14e7356dd911a69cec3165ae05
Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
This commit is contained in:
Wesley Cheng 2022-04-08 18:48:01 -07:00
parent 0a2f06a0a3
commit a5f8942a16
6 changed files with 3431 additions and 0 deletions

View File

@ -192,4 +192,47 @@ config JZ4770_PHY
This driver provides PHY support for the USB controller found
on the JZ-series and X-series SoCs from Ingenic.
config USB_QCOM_EMU_PHY
tristate "Qualcomm Technologies, Inc. emulation USB PHY driver"
depends on ARCH_QCOM || COMPILE_TEST
select USB_PHY
help
Enable this to support the USB transceiver used on
Qualcomm Technologies, Inc. emulation platforms. It simply performs
PHY initialization given a basic register write sequence.
To compile this driver as a module, choose M here: the
module will be called phy-qcom-emu.
config USB_MSM_SSPHY_QMP
tristate "MSM SSUSB QMP PHY Driver"
depends on ARCH_QCOM || COMPILE_TEST
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_HSUSB_PHY
tristate "MSM HSUSB PHY Driver"
depends on ARCH_QCOM || COMPILE_TEST
select USB_PHY
help
Enable this to support the HSUSB 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_MSM_EUSB2_PHY
tristate "MSM EUSB2 PHY Driver"
depends on ARCH_QCOM || COMPILE_TEST
select USB_PHY
help
Enable this to support the USB EUSB2 PHY on MSM chips. This driver
supports reset and initialization sequence, and also perform required
set of operations with used repeater for USB HS/FS/LS functionality.
To compile this driver as a module, choose M here.
endmenu

View File

@ -25,3 +25,7 @@ obj-$(CONFIG_USB_ULPI) += phy-ulpi.o
obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o
obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o
obj-$(CONFIG_JZ4770_PHY) += phy-jz4770.o
obj-$(CONFIG_USB_QCOM_EMU_PHY) += phy-qcom-emu.o
obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o
obj-$(CONFIG_MSM_HSUSB_PHY) += phy-msm-snps-hs.o
obj-$(CONFIG_USB_MSM_EUSB2_PHY) += phy-msm-snps-eusb2.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,939 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mutex.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/dwc3-msm.h>
#include <linux/reset.h>
#include <linux/debugfs.h>
#include <linux/qcom_scm.h>
#include <linux/types.h>
#define USB2_PHY_USB_PHY_UTMI_CTRL0 (0x3c)
#define OPMODE_MASK (0x3 << 3)
#define OPMODE_NONDRIVING (0x1 << 3)
#define SLEEPM BIT(0)
#define USB2_PHY_USB_PHY_UTMI_CTRL5 (0x50)
#define POR BIT(1)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0 (0x54)
#define SIDDQ BIT(2)
#define RETENABLEN BIT(3)
#define FSEL_MASK (0x7 << 4)
#define FSEL_DEFAULT (0x3 << 4)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1 (0x58)
#define VBUSVLDEXTSEL0 BIT(4)
#define PLLBTUNE BIT(5)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2 (0x5c)
#define VREGBYPASS BIT(0)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL1 (0x60)
#define VBUSVLDEXT0 BIT(0)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL2 (0x64)
#define USB2_AUTO_RESUME BIT(0)
#define USB2_SUSPEND_N BIT(2)
#define USB2_SUSPEND_N_SEL BIT(3)
#define USB2_PHY_USB_PHY_CFG0 (0x94)
#define UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN BIT(0)
#define UTMI_PHY_CMN_CTRL_OVERRIDE_EN BIT(1)
#define USB2_PHY_USB_PHY_REFCLK_CTRL (0xa0)
#define REFCLK_SEL_MASK (0x3 << 0)
#define REFCLK_SEL_DEFAULT (0x2 << 0)
#define USB2_PHY_USB_PHY_PWRDOWN_CTRL (0xa4)
#define PWRDOWN_B BIT(0)
#define USB2PHY_USB_PHY_RTUNE_SEL (0xb4)
#define RTUNE_SEL BIT(0)
#define TXPREEMPAMPTUNE0(x) (x << 6)
#define TXPREEMPAMPTUNE0_MASK (BIT(7) | BIT(6))
#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0 0x6c
#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1 0x70
#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2 0x74
#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3 0x78
#define TXVREFTUNE0_MASK 0xF
#define PARAM_OVRD_MASK 0xFF
#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 1704000 /* uV */
#define USB_HSPHY_1P8_VOL_MAX 1800000 /* uV */
#define USB_HSPHY_1P8_HPM_LOAD 19000 /* uA */
#define USB_HSPHY_VDD_HPM_LOAD 30000 /* uA */
struct msm_hsphy {
struct usb_phy phy;
void __iomem *base;
phys_addr_t eud_reg;
void __iomem *eud_enable_reg;
bool re_enable_eud;
struct clk *ref_clk_src;
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 */
bool clocks_enabled;
bool power_enabled;
bool suspended;
bool cable_connected;
bool dpdm_enable;
int *param_override_seq;
int param_override_seq_cnt;
void __iomem *phy_rcal_reg;
u32 rcal_mask;
struct mutex phy_lock;
struct regulator_desc dpdm_rdesc;
struct regulator_dev *dpdm_rdev;
struct power_supply *usb_psy;
unsigned int vbus_draw;
struct work_struct vbus_draw_work;
/* debugfs entries */
struct dentry *root;
u8 txvref_tune0;
u8 pre_emphasis;
u8 param_ovrd0;
u8 param_ovrd1;
u8 param_ovrd2;
u8 param_ovrd3;
};
static void msm_hsphy_enable_clocks(struct msm_hsphy *phy, bool on)
{
dev_dbg(phy->phy.dev, "%s(): clocks_enabled:%d on:%d\n",
__func__, phy->clocks_enabled, on);
if (!phy->clocks_enabled && on) {
clk_prepare_enable(phy->ref_clk_src);
if (phy->cfg_ahb_clk)
clk_prepare_enable(phy->cfg_ahb_clk);
phy->clocks_enabled = true;
}
if (phy->clocks_enabled && !on) {
if (phy->cfg_ahb_clk)
clk_disable_unprepare(phy->cfg_ahb_clk);
clk_disable_unprepare(phy->ref_clk_src);
phy->clocks_enabled = false;
}
}
static int msm_hsphy_enable_power(struct msm_hsphy *phy, bool on)
{
int ret = 0;
dev_dbg(phy->phy.dev, "%s turn %s regulators. power_enabled:%d\n",
__func__, on ? "on" : "off", phy->power_enabled);
if (phy->power_enabled == on) {
dev_dbg(phy->phy.dev, "PHYs' regulators are already ON.\n");
return 0;
}
if (!on)
goto disable_vdda33;
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->vdda18, USB_HSPHY_1P8_HPM_LOAD);
if (ret < 0) {
dev_err(phy->phy.dev, "Unable to set HPM of vdda18:%d\n", ret);
goto disable_vdd;
}
ret = regulator_set_voltage(phy->vdda18, USB_HSPHY_1P8_VOL_MIN,
USB_HSPHY_1P8_VOL_MAX);
if (ret) {
dev_err(phy->phy.dev,
"Unable to set voltage for vdda18:%d\n", ret);
goto put_vdda18_lpm;
}
ret = regulator_enable(phy->vdda18);
if (ret) {
dev_err(phy->phy.dev, "Unable to enable vdda18:%d\n", ret);
goto unset_vdda18;
}
ret = regulator_set_load(phy->vdda33, USB_HSPHY_3P3_HPM_LOAD);
if (ret < 0) {
dev_err(phy->phy.dev, "Unable to set HPM of vdda33:%d\n", ret);
goto disable_vdda18;
}
ret = regulator_set_voltage(phy->vdda33, USB_HSPHY_3P3_VOL_MIN,
USB_HSPHY_3P3_VOL_MAX);
if (ret) {
dev_err(phy->phy.dev,
"Unable to set voltage for vdda33:%d\n", ret);
goto put_vdda33_lpm;
}
ret = regulator_enable(phy->vdda33);
if (ret) {
dev_err(phy->phy.dev, "Unable to enable vdda33:%d\n", ret);
goto unset_vdd33;
}
phy->power_enabled = true;
pr_debug("%s(): HSUSB PHY's regulators are turned ON.\n", __func__);
return ret;
disable_vdda33:
ret = regulator_disable(phy->vdda33);
if (ret)
dev_err(phy->phy.dev, "Unable to disable vdda33:%d\n", ret);
unset_vdd33:
ret = regulator_set_voltage(phy->vdda33, 0, USB_HSPHY_3P3_VOL_MAX);
if (ret)
dev_err(phy->phy.dev,
"Unable to set (0) voltage for vdda33:%d\n", ret);
put_vdda33_lpm:
ret = regulator_set_load(phy->vdda33, 0);
if (ret < 0)
dev_err(phy->phy.dev, "Unable to set (0) HPM of vdda33\n");
disable_vdda18:
ret = regulator_disable(phy->vdda18);
if (ret)
dev_err(phy->phy.dev, "Unable to disable vdda18:%d\n", ret);
unset_vdda18:
ret = regulator_set_voltage(phy->vdda18, 0, USB_HSPHY_1P8_VOL_MAX);
if (ret)
dev_err(phy->phy.dev,
"Unable to set (0) voltage for vdda18:%d\n", ret);
put_vdda18_lpm:
ret = regulator_set_load(phy->vdda18, 0);
if (ret < 0)
dev_err(phy->phy.dev, "Unable to set LPM of vdda18\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");
err_vdd:
phy->power_enabled = false;
dev_dbg(phy->phy.dev, "HSUSB PHY's regulators are turned OFF.\n");
return ret;
}
static void msm_usb_write_readback(void __iomem *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 void msm_hsphy_reset(struct msm_hsphy *phy)
{
int ret;
ret = reset_control_assert(phy->phy_reset);
if (ret)
dev_err(phy->phy.dev, "%s: phy_reset assert failed\n",
__func__);
usleep_range(100, 150);
ret = reset_control_deassert(phy->phy_reset);
if (ret)
dev_err(phy->phy.dev, "%s: phy_reset deassert failed\n",
__func__);
}
static void hsusb_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));
}
}
#define EUD_EN2 BIT(0)
static int msm_hsphy_init(struct usb_phy *uphy)
{
struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
int ret;
u32 rcal_code = 0, eud_csr_reg = 0;
dev_dbg(uphy->dev, "%s phy_flags:0x%x\n", __func__, phy->phy.flags);
if (phy->eud_enable_reg) {
eud_csr_reg = readl_relaxed(phy->eud_enable_reg);
if (eud_csr_reg & EUD_EN2) {
dev_dbg(phy->phy.dev, "csr:0x%x eud is enabled\n",
eud_csr_reg);
/* if in host mode, disable EUD */
if (phy->phy.flags & PHY_HOST_MODE) {
qcom_scm_io_writel(phy->eud_reg, 0x0);
phy->re_enable_eud = true;
} else {
ret = msm_hsphy_enable_power(phy, true);
return ret;
}
}
}
ret = msm_hsphy_enable_power(phy, true);
if (ret)
return ret;
msm_hsphy_enable_clocks(phy, true);
msm_hsphy_reset(phy);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0,
UTMI_PHY_CMN_CTRL_OVERRIDE_EN,
UTMI_PHY_CMN_CTRL_OVERRIDE_EN);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL5,
POR, POR);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0,
FSEL_MASK, 0);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1,
PLLBTUNE, PLLBTUNE);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_REFCLK_CTRL,
REFCLK_SEL_MASK, REFCLK_SEL_DEFAULT);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1,
VBUSVLDEXTSEL0, VBUSVLDEXTSEL0);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL1,
VBUSVLDEXT0, VBUSVLDEXT0);
/* set parameter ovrride if needed */
if (phy->param_override_seq)
hsusb_phy_write_seq(phy->base, phy->param_override_seq,
phy->param_override_seq_cnt, 0);
if (phy->pre_emphasis) {
u8 val = TXPREEMPAMPTUNE0(phy->pre_emphasis) &
TXPREEMPAMPTUNE0_MASK;
if (val)
msm_usb_write_readback(phy->base,
USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1,
TXPREEMPAMPTUNE0_MASK, val);
}
if (phy->txvref_tune0) {
u8 val = phy->txvref_tune0 & TXVREFTUNE0_MASK;
msm_usb_write_readback(phy->base,
USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1,
TXVREFTUNE0_MASK, val);
}
if (phy->param_ovrd0) {
msm_usb_write_readback(phy->base,
USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0,
PARAM_OVRD_MASK, phy->param_ovrd0);
}
if (phy->param_ovrd1) {
msm_usb_write_readback(phy->base,
USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1,
PARAM_OVRD_MASK, phy->param_ovrd1);
}
if (phy->param_ovrd2) {
msm_usb_write_readback(phy->base,
USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2,
PARAM_OVRD_MASK, phy->param_ovrd2);
}
if (phy->param_ovrd3) {
msm_usb_write_readback(phy->base,
USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3,
PARAM_OVRD_MASK, phy->param_ovrd3);
}
dev_dbg(uphy->dev, "x0:%08x x1:%08x x2:%08x x3:%08x\n",
readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0),
readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1),
readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2),
readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3));
if (phy->phy_rcal_reg) {
rcal_code = readl_relaxed(phy->phy_rcal_reg) & phy->rcal_mask;
dev_dbg(uphy->dev, "rcal_mask:%08x reg:%pK code:%08x\n",
phy->rcal_mask, phy->phy_rcal_reg, rcal_code);
}
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2,
VREGBYPASS, VREGBYPASS);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL0,
SLEEPM, SLEEPM);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0,
SIDDQ, 0);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL5,
POR, 0);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_SUSPEND_N_SEL, 0);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0,
UTMI_PHY_CMN_CTRL_OVERRIDE_EN, 0);
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);
if (phy->suspended && suspend) {
if (phy->phy.flags & PHY_SUS_OVERRIDE)
goto suspend;
dev_dbg(uphy->dev, "%s: USB PHY is already suspended\n",
__func__);
return 0;
}
suspend:
if (suspend) { /* Bus suspend */
if (phy->cable_connected) {
/* Enable auto-resume functionality during host mode
* bus suspend with some FS/HS peripheral connected.
*/
if ((phy->phy.flags & PHY_HOST_MODE) &&
(phy->phy.flags & PHY_HSFS_MODE)) {
/* Enable auto-resume functionality by pulsing
* signal
*/
msm_usb_write_readback(phy->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_AUTO_RESUME, USB2_AUTO_RESUME);
usleep_range(500, 1000);
msm_usb_write_readback(phy->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_AUTO_RESUME, 0);
}
msm_hsphy_enable_clocks(phy, false);
} else {/* Cable disconnect */
mutex_lock(&phy->phy_lock);
dev_dbg(uphy->dev, "phy->flags:0x%x\n", phy->phy.flags);
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;
}
if (!phy->dpdm_enable) {
if (!(phy->phy.flags & EUD_SPOOF_DISCONNECT)) {
dev_dbg(uphy->dev, "turning off clocks/ldo\n");
msm_usb_write_readback(phy->base,
USB2_PHY_USB_PHY_PWRDOWN_CTRL,
PWRDOWN_B, 0);
msm_hsphy_enable_clocks(phy, false);
msm_hsphy_enable_power(phy, false);
}
} else {
dev_dbg(uphy->dev, "dpdm reg still active. Keep clocks/ldo ON\n");
}
mutex_unlock(&phy->phy_lock);
}
phy->suspended = true;
} else { /* Bus resume and cable connect */
msm_hsphy_enable_clocks(phy, true);
phy->suspended = false;
}
return 0;
}
static int msm_hsphy_notify_connect(struct usb_phy *uphy,
enum usb_device_speed speed)
{
struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
phy->cable_connected = true;
return 0;
}
static int msm_hsphy_notify_disconnect(struct usb_phy *uphy,
enum usb_device_speed speed)
{
struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
phy->cable_connected = false;
return 0;
}
static void msm_hsphy_vbus_draw_work(struct work_struct *w)
{
struct msm_hsphy *phy = container_of(w, struct msm_hsphy,
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;
}
}
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 (%d) setting input current limit\n", ret);
return;
}
}
static int msm_hsphy_set_power(struct usb_phy *uphy, unsigned int mA)
{
struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
phy->vbus_draw = mA;
schedule_work(&phy->vbus_draw_work);
return 0;
}
static int msm_hsphy_dpdm_regulator_enable(struct regulator_dev *rdev)
{
int ret = 0;
struct msm_hsphy *phy = rdev_get_drvdata(rdev);
dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n",
__func__, phy->dpdm_enable);
if (phy->eud_enable_reg && readl_relaxed(phy->eud_enable_reg)) {
dev_err(phy->phy.dev, "eud is enabled\n");
return 0;
}
mutex_lock(&phy->phy_lock);
if (!phy->dpdm_enable) {
ret = msm_hsphy_enable_power(phy, true);
if (ret) {
mutex_unlock(&phy->phy_lock);
return ret;
}
msm_hsphy_enable_clocks(phy, true);
msm_hsphy_reset(phy);
/*
* For PMIC charger detection, place PHY in UTMI non-driving
* mode which leaves Dp and Dm lines in high-Z state.
*/
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL0,
OPMODE_MASK, OPMODE_NONDRIVING);
msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0,
UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN,
UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN);
phy->dpdm_enable = true;
}
mutex_unlock(&phy->phy_lock);
return ret;
}
static int msm_hsphy_dpdm_regulator_disable(struct regulator_dev *rdev)
{
int ret = 0;
struct msm_hsphy *phy = rdev_get_drvdata(rdev);
dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n",
__func__, phy->dpdm_enable);
mutex_lock(&phy->phy_lock);
if (phy->dpdm_enable) {
if (!phy->cable_connected) {
msm_hsphy_enable_clocks(phy, false);
ret = msm_hsphy_enable_power(phy, false);
if (ret < 0) {
mutex_unlock(&phy->phy_lock);
return ret;
}
}
phy->dpdm_enable = false;
}
mutex_unlock(&phy->phy_lock);
return ret;
}
static int msm_hsphy_dpdm_regulator_is_enabled(struct regulator_dev *rdev)
{
struct msm_hsphy *phy = rdev_get_drvdata(rdev);
dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n",
__func__, phy->dpdm_enable);
return phy->dpdm_enable;
}
static const struct regulator_ops msm_hsphy_dpdm_regulator_ops = {
.enable = msm_hsphy_dpdm_regulator_enable,
.disable = msm_hsphy_dpdm_regulator_disable,
.is_enabled = msm_hsphy_dpdm_regulator_is_enabled,
};
static int msm_hsphy_regulator_init(struct msm_hsphy *phy)
{
struct device *dev = phy->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;
phy->dpdm_rdesc.owner = THIS_MODULE;
phy->dpdm_rdesc.type = REGULATOR_VOLTAGE;
phy->dpdm_rdesc.ops = &msm_hsphy_dpdm_regulator_ops;
phy->dpdm_rdesc.name = kbasename(dev->of_node->full_name);
cfg.dev = dev;
cfg.init_data = init_data;
cfg.driver_data = phy;
cfg.of_node = dev->of_node;
phy->dpdm_rdev = devm_regulator_register(dev, &phy->dpdm_rdesc, &cfg);
return PTR_ERR_OR_ZERO(phy->dpdm_rdev);
}
static void msm_hsphy_create_debugfs(struct msm_hsphy *phy)
{
phy->root = debugfs_create_dir(dev_name(phy->phy.dev), NULL);
debugfs_create_x8("pre_emphasis", 0644, phy->root, &phy->pre_emphasis);
debugfs_create_x8("txvref_tune0", 0644, phy->root, &phy->txvref_tune0);
debugfs_create_x8("param_ovrd0", 0644, phy->root, &phy->param_ovrd0);
debugfs_create_x8("param_ovrd1", 0644, phy->root, &phy->param_ovrd1);
debugfs_create_x8("param_ovrd2", 0644, phy->root, &phy->param_ovrd2);
debugfs_create_x8("param_ovrd3", 0644, phy->root, &phy->param_ovrd3);
}
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;
}
phy->phy.dev = dev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"hsusb_phy_base");
if (!res) {
dev_err(dev, "missing memory base 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,
"phy_rcal_reg");
if (res) {
phy->phy_rcal_reg = devm_ioremap(dev,
res->start, resource_size(res));
if (IS_ERR(phy->phy_rcal_reg)) {
dev_err(dev, "couldn't ioremap phy_rcal_reg\n");
phy->phy_rcal_reg = NULL;
}
if (of_property_read_u32(dev->of_node,
"qcom,rcal-mask", &phy->rcal_mask)) {
dev_err(dev, "unable to read phy rcal mask\n");
phy->phy_rcal_reg = NULL;
}
dev_dbg(dev, "rcal_mask:%08x reg:%pK\n", phy->rcal_mask,
phy->phy_rcal_reg);
}
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)) {
dev_err(dev, "err getting eud_enable_reg address\n");
return PTR_ERR(phy->eud_enable_reg);
}
phy->eud_reg = res->start;
}
/* ref_clk_src is needed irrespective of SE_CLK or DIFF_CLK usage */
phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src");
if (IS_ERR(phy->ref_clk_src)) {
dev_dbg(dev, "clk get failed for ref_clk_src\n");
ret = PTR_ERR(phy->ref_clk_src);
return ret;
}
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,
"clk get failed for cfg_ahb_clk ret %d\n", ret);
return ret;
}
}
phy->phy_reset = devm_reset_control_get(dev, "phy_reset");
if (IS_ERR(phy->phy_reset))
return PTR_ERR(phy->phy_reset);
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 > 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)
return -ENOMEM;
if (phy->param_override_seq_cnt % 2) {
dev_err(dev, "invalid param_override_seq_len\n");
return -EINVAL;
}
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);
return 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->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;
}
mutex_init(&phy->phy_lock);
platform_set_drvdata(pdev, phy);
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.set_power = msm_hsphy_set_power;
phy->phy.type = USB_PHY_TYPE_USB2;
ret = usb_add_phy_dev(&phy->phy);
if (ret)
return ret;
ret = msm_hsphy_regulator_init(phy);
if (ret) {
usb_remove_phy(&phy->phy);
return ret;
}
INIT_WORK(&phy->vbus_draw_work, msm_hsphy_vbus_draw_work);
msm_hsphy_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 on here.
*/
if (phy->eud_enable_reg && readl_relaxed(phy->eud_enable_reg))
msm_hsphy_enable_power(phy, true);
return 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;
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_src);
msm_hsphy_enable_clocks(phy, false);
msm_hsphy_enable_power(phy, false);
return 0;
}
static const struct of_device_id msm_usb_id_table[] = {
{
.compatible = "qcom,usb-hsphy-snps-femto",
},
{ },
};
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");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2018, 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/usb/phy.h>
/* QSCRATCH registers */
#define HS_PHY_CTRL_REG 0x10
#define SW_SESSVLD_SEL BIT(28)
/* HSPHY registers */
#define HS2_LOCAL_RESET_REG_ADDR 0x04
#define HS2_CLK_STATUS_ADDR 0x10
#define HS2_CLK_STATUS_SEL_ADDR 0x14
#define HS2_USB30_CTRL_ADDR 0x34
#define HS2_USB30_PHY_POWER_OFF BIT(25)
struct qcusb_emu_phy {
struct usb_phy phy;
struct device *dev;
void __iomem *base;
void __iomem *qscratch_base;
int *emu_init_seq;
int emu_init_seq_len;
};
static int qcusb_emu_phy_init(struct usb_phy *phy)
{
struct qcusb_emu_phy *qphy = container_of(phy,
struct qcusb_emu_phy, phy);
u32 tmp;
int i;
/* reset everything */
writel_relaxed(0xffffffff, qphy->base + HS2_LOCAL_RESET_REG_ADDR);
usleep_range(10000, 12000);
/* power down HS phy */
tmp = readl_relaxed(qphy->base + HS2_USB30_CTRL_ADDR) |
HS2_USB30_PHY_POWER_OFF;
writel_relaxed(tmp, qphy->base + HS2_USB30_CTRL_ADDR);
usleep_range(10000, 12000);
/* power up HS phy */
tmp = readl_relaxed(qphy->base + HS2_USB30_CTRL_ADDR) &
(~HS2_USB30_PHY_POWER_OFF);
writel_relaxed(tmp, qphy->base + HS2_USB30_CTRL_ADDR);
usleep_range(10000, 12000);
writel_relaxed(0xfffffff3, qphy->base + HS2_LOCAL_RESET_REG_ADDR);
usleep_range(10000, 12000);
/* put phy out of reset */
writel_relaxed(0xfffffff0, qphy->base + HS2_LOCAL_RESET_REG_ADDR);
usleep_range(10000, 12000);
/* selection of HS phy clock MMCM value */
for (i = 0; i < qphy->emu_init_seq_len; i = i+2) {
dev_dbg(phy->dev, "write 0x%02x to 0x%02x\n",
qphy->emu_init_seq[i], qphy->emu_init_seq[i+1]);
writel_relaxed(qphy->emu_init_seq[i],
qphy->base + qphy->emu_init_seq[i+1]);
/* 10ms to ensure write propagates across bus */
usleep_range(10000, 12000);
}
/* clear other reset */
writel_relaxed(0x0, qphy->base + HS2_LOCAL_RESET_REG_ADDR);
usleep_range(10000, 12000);
/* clock select to read UTMI/ULPI clock */
writel_relaxed(0x9, qphy->base + HS2_CLK_STATUS_SEL_ADDR);
usleep_range(10000, 12000);
dev_info(phy->dev, "PHY UTMI/ULPI CLK frequency:%d MHz\n",
(readl_relaxed(qphy->base + HS2_CLK_STATUS_ADDR) / 1000));
if (qphy->qscratch_base) {
/* Use UTMI VBUS signal from HW */
tmp = readl_relaxed(qphy->qscratch_base + HS_PHY_CTRL_REG);
tmp &= ~SW_SESSVLD_SEL;
writel_relaxed(tmp, qphy->qscratch_base + HS_PHY_CTRL_REG);
}
return 0;
}
static int qcusb_emu_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct qcusb_emu_phy *qphy;
struct resource *res;
int ret, size;
qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
if (!qphy)
return -ENOMEM;
qphy->phy.dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
qphy->base = devm_ioremap_resource(dev, res);
if (IS_ERR(qphy->base))
return PTR_ERR(qphy->base);
of_get_property(dev->of_node, "qcom,emu-init-seq", &size);
if (!size) {
dev_err(dev, "emu-init-seq not specified\n");
return -EINVAL;
}
qphy->emu_init_seq = devm_kzalloc(dev, size, GFP_KERNEL);
if (!qphy->emu_init_seq)
return -ENOMEM;
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, must be in <data,addr> pairs\n");
return -EINVAL;
}
ret = of_property_read_u32_array(dev->of_node, "qcom,emu-init-seq",
qphy->emu_init_seq, qphy->emu_init_seq_len);
if (ret) {
dev_err(dev, "could not read emu-init-seq, returned %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, qphy);
qphy->phy.label = "qcom-usb-emu-phy";
qphy->phy.init = qcusb_emu_phy_init;
qphy->phy.type = USB_PHY_TYPE_USB2;
ret = usb_add_phy_dev(&qphy->phy);
if (ret)
return ret;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"qscratch_base");
if (res) {
qphy->qscratch_base = devm_ioremap(dev, res->start,
resource_size(res));
if (IS_ERR(qphy->qscratch_base)) {
dev_dbg(dev, "error mapping qscratch\n");
qphy->qscratch_base = NULL;
}
}
return 0;
}
static int qcusb_emu_phy_remove(struct platform_device *pdev)
{
struct qcusb_emu_phy *qcphy = platform_get_drvdata(pdev);
usb_remove_phy(&qcphy->phy);
return 0;
}
static const struct of_device_id emu_phy_dt_ids[] = {
{ .compatible = "qcom,usb-emu-phy" },
{ }
};
MODULE_DEVICE_TABLE(of, emu_phy_dt_ids);
static struct platform_driver qcusb_emu_phy_driver = {
.probe = qcusb_emu_phy_probe,
.remove = qcusb_emu_phy_remove,
.driver = {
.name = "usb_emu_phy",
.of_match_table = emu_phy_dt_ids,
},
};
module_platform_driver(qcusb_emu_phy_driver);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. USB Emulation PHY driver");
MODULE_LICENSE("GPL v2");