summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/phy/phy-msm-qusb.c238
1 files changed, 236 insertions, 2 deletions
diff --git a/drivers/usb/phy/phy-msm-qusb.c b/drivers/usb/phy/phy-msm-qusb.c
index f97d550e0381..ae72ec6b3d19 100644
--- a/drivers/usb/phy/phy-msm-qusb.c
+++ b/drivers/usb/phy/phy-msm-qusb.c
@@ -170,6 +170,10 @@ struct qusb_phy {
struct regulator_desc dpdm_rdesc;
struct regulator_dev *dpdm_rdev;
+ bool dpdm_pulsing_enabled;
+ struct power_supply *dpdm_psy;
+ struct power_supply_desc dpdm_psy_desc;
+
/* emulation targets specific */
void __iomem *emu_phy_base;
bool emulation;
@@ -181,6 +185,11 @@ struct qusb_phy {
int emu_dcm_reset_seq_len;
bool put_into_high_z_state;
struct mutex phy_lock;
+ spinlock_t pulse_lock;
+};
+
+static enum power_supply_property dpdm_props[] = {
+ POWER_SUPPLY_PROP_DP_DM,
};
static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
@@ -345,10 +354,13 @@ err_vdd:
return ret;
}
+#define PHY_PULSE_TIME_USEC 250
static int qusb_phy_update_dpdm(struct usb_phy *phy, int value)
{
struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
int ret = 0;
+ unsigned long flags;
+ u32 reg;
dev_dbg(phy->dev, "%s value:%d rm_pulldown:%d\n",
__func__, value, qphy->rm_pulldown);
@@ -407,6 +419,21 @@ static int qusb_phy_update_dpdm(struct usb_phy *phy, int value)
qusb_phy_enable_clocks(qphy, false);
}
}
+
+ /* Clear QC1 and QC2 registers when rm_pulldown = 1 */
+ if (qphy->dpdm_pulsing_enabled && qphy->rm_pulldown) {
+ dev_dbg(phy->dev, "clearing qc1 and qc2 registers.\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Clear qc1 and qc2 registers */
+ writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC1);
+ writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
+ /* to make sure above write goes through */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ }
mutex_unlock(&qphy->phy_lock);
break;
@@ -415,6 +442,22 @@ static int qusb_phy_update_dpdm(struct usb_phy *phy, int value)
dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPR_DMR\n");
mutex_lock(&qphy->phy_lock);
if (qphy->rm_pulldown) {
+ dev_dbg(phy->dev, "clearing qc1 and qc2 registers.\n");
+ if (qphy->dpdm_pulsing_enabled) {
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Clear qc1 and qc2 registers */
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_QC1);
+ writel_relaxed(0x00,
+ qphy->base + QUSB2PHY_PORT_QC2);
+ /* to make sure above write goes through */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ }
+
if (!qphy->cable_connected) {
if (qphy->tcsr_clamp_dig_n)
writel_relaxed(0x0,
@@ -431,15 +474,172 @@ static int qusb_phy_update_dpdm(struct usb_phy *phy, int value)
mutex_unlock(&qphy->phy_lock);
break;
+ case POWER_SUPPLY_DP_DM_DP0P6_DMF:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DP0P6_DMF\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Set DP to 0.6v and DM to High Z state */
+ writel_relaxed(VDP_SRC_EN, qphy->base + QUSB2PHY_PORT_QC1);
+ /* complete above write */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
+
+ case POWER_SUPPLY_DP_DM_DP0P6_DM3P3:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DP0PHVDCP_36_DM3P3\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ /* Set DP to 0.6v */
+ writel_relaxed(VDP_SRC_EN, qphy->base + QUSB2PHY_PORT_QC1);
+ /* Set DM to 3.075v */
+ writel_relaxed(RPUM_LOW_EN | RDM_UP_EN,
+ qphy->base + QUSB2PHY_PORT_QC2);
+ /* complete above write */
+ mb();
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
+
+ case POWER_SUPPLY_DP_DM_DP_PULSE:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DP_PULSE\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ spin_lock_irqsave(&qphy->pulse_lock, flags);
+ /*Set DP to 3.075v, sleep for .25 ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg |= (RDP_UP_EN | RPUP_LOW_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+
+ /* complete above write */
+ mb();
+
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ udelay(PHY_PULSE_TIME_USEC);
+
+ /* Set DP to 0.6v, sleep 2-3ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC1);
+ reg |= VDP_SRC_EN;
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC1);
+
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg &= ~(RDP_UP_EN | RPUP_LOW_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+ /* complete above write */
+ mb();
+ spin_unlock_irqrestore(&qphy->pulse_lock, flags);
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ usleep_range(2000, 3000);
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
+
+ case POWER_SUPPLY_DP_DM_DM_PULSE:
+ if (!qphy->dpdm_pulsing_enabled)
+ break;
+
+ dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DM_PULSE\n");
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret)
+ goto clk_error;
+
+ spin_lock_irqsave(&qphy->pulse_lock, flags);
+ /* Set DM to 0.6v, sleep .25 ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC1);
+ reg |= VDM_SRC_EN;
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC1);
+
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg &= ~(RDM_UP_EN | RPUM_LOW_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+
+ /* complete above write */
+ mb();
+
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ udelay(PHY_PULSE_TIME_USEC);
+
+ /* DM to 3.075v, sleep 2-3ms */
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC2);
+ reg |= (RPUM_LOW_EN | RDM_UP_EN);
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC2);
+
+ reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_QC1);
+ reg &= ~VDM_SRC_EN;
+ writel_relaxed(reg, qphy->base + QUSB2PHY_PORT_QC1);
+
+ /* complete above write */
+ mb();
+ spin_unlock_irqrestore(&qphy->pulse_lock, flags);
+
+ /*
+ * It is recommended to wait here to get voltage change on
+ * DP/DM line.
+ */
+ usleep_range(2000, 3000);
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ break;
default:
ret = -EINVAL;
dev_err(phy->dev, "Invalid power supply property(%d)\n", value);
break;
}
+clk_error:
return ret;
}
+static int qusb_phy_get_property_usb(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ return -EINVAL;
+}
+
+static int qusb_phy_set_property_usb(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct qusb_phy *qphy = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_DP_DM:
+ ret = qusb_phy_update_dpdm(&qphy->phy, val->intval);
+ if (ret) {
+ dev_dbg(qphy->phy.dev, "error in dpdm update: %d\n",
+ ret);
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
{
u8 num_of_bits;
@@ -993,6 +1193,7 @@ static int qusb_phy_probe(struct platform_device *pdev)
int ret = 0, size = 0;
const char *phy_type;
bool hold_phy_reset;
+ struct power_supply_config dpdm_cfg = {};
qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
if (!qphy)
@@ -1232,6 +1433,7 @@ static int qusb_phy_probe(struct platform_device *pdev)
}
mutex_init(&qphy->phy_lock);
+ spin_lock_init(&qphy->pulse_lock);
platform_set_drvdata(pdev, qphy);
qphy->phy.label = "msm-qusb-phy";
@@ -1254,25 +1456,57 @@ static int qusb_phy_probe(struct platform_device *pdev)
dev_err(dev, "%s:phy_reset assert failed\n", __func__);
}
+ qphy->dpdm_pulsing_enabled = of_property_read_bool(dev->of_node,
+ "qcom,enable-dpdm-pulsing");
+
+ if (qphy->dpdm_pulsing_enabled) {
+ qphy->dpdm_psy_desc.name = "dpdm";
+ qphy->dpdm_psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ qphy->dpdm_psy_desc.properties = dpdm_props;
+ qphy->dpdm_psy_desc.num_properties = ARRAY_SIZE(dpdm_props);
+ qphy->dpdm_psy_desc.set_property = qusb_phy_set_property_usb;
+ qphy->dpdm_psy_desc.get_property = qusb_phy_get_property_usb;
+
+ dpdm_cfg.drv_data = qphy;
+ dpdm_cfg.of_node = dev->of_node;
+ qphy->dpdm_psy = power_supply_register(&pdev->dev,
+ &qphy->dpdm_psy_desc, &dpdm_cfg);
+ if (IS_ERR(qphy->dpdm_psy)) {
+ dev_err(&pdev->dev, "%s:dpdm power_supply_register failed\n",
+ __func__);
+ return PTR_ERR(qphy->dpdm_psy);
+ }
+ }
+
ret = usb_add_phy_dev(&qphy->phy);
if (ret)
- return ret;
+ goto unregister_psy;
ret = qusb_phy_regulator_init(qphy);
if (ret)
- usb_remove_phy(&qphy->phy);
+ goto remove_phy;
/* de-assert clamp dig n to reduce leakage on 1p8 upon boot up */
if (qphy->tcsr_clamp_dig_n)
writel_relaxed(0x0, qphy->tcsr_clamp_dig_n);
return ret;
+
+remove_phy:
+ usb_remove_phy(&qphy->phy);
+unregister_psy:
+ if (qphy->dpdm_psy)
+ power_supply_unregister(qphy->dpdm_psy);
+
+ return ret;
}
static int qusb_phy_remove(struct platform_device *pdev)
{
struct qusb_phy *qphy = platform_get_drvdata(pdev);
+ if (qphy->dpdm_psy)
+ power_supply_unregister(qphy->dpdm_psy);
usb_remove_phy(&qphy->phy);
if (qphy->clocks_enabled) {