From 119baad772527f26d527dd4d84f72c95beef2e0f Mon Sep 17 00:00:00 2001 From: Sriharsha Allenki Date: Wed, 16 Sep 2020 22:01:22 +0530 Subject: [PATCH] usb: dwc3: Drive a pulse on DP on CDP detection Some USB host PCs switch the downstream port mode from CDP to SDP if the device does not pull DP up within certain duration (~2s). This limits the current drawn by the device to 500mA(or 900mA) as opposed to the possible 1.5A resulting in slow charging of the battery. Fix this by driving a DP pulse when there is a connect notification from PMIC and detected the charger type as CDP. The ser current call also leads to the same failure, hence limit the set current call only to the case where the device is connected to a Standard Downstream Port. Change-Id: I2c737c68de57bc4126fb3e6e1c291b83d681fb13 Signed-off-by: Sriharsha Allenki --- drivers/usb/dwc3/dwc3-msm.c | 49 +++++++++++++++++++-- drivers/usb/phy/phy-msm-qusb-v2.c | 72 +++++++++++++++++++++++++++++++ include/linux/usb/dwc3-msm.h | 8 ++++ 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c index 9cb26af410c4..2d46bd305846 100644 --- a/drivers/usb/dwc3/dwc3-msm.c +++ b/drivers/usb/dwc3/dwc3-msm.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -441,6 +443,7 @@ struct dwc3_msm { enum bus_vote override_bus_vote; struct icc_path *icc_paths[3]; struct power_supply *usb_psy; + struct iio_channel *chg_type; struct work_struct vbus_draw_work; bool in_host_mode; bool in_device_mode; @@ -3416,6 +3419,7 @@ static irqreturn_t msm_dwc3_pwr_irq(int irq, void *data) } static void dwc3_otg_sm_work(struct work_struct *w); +static int get_chg_type(struct dwc3_msm *mdwc); static int dwc3_msm_get_clk_gdsc(struct dwc3_msm *mdwc) { @@ -3552,6 +3556,7 @@ static int dwc3_msm_id_notifier(struct notifier_block *nb, return NOTIFY_DONE; } +#define DP_PULSE_WIDTH_MSEC 200 static int dwc3_msm_vbus_notifier(struct notifier_block *nb, unsigned long event, void *ptr) @@ -3586,6 +3591,16 @@ static int dwc3_msm_vbus_notifier(struct notifier_block *nb, mdwc->vbus_active = event; } + /* + * Drive a pulse on DP to ensure proper CDP detection + * and only when the vbus connect event is a valid one. + */ + if (get_chg_type(mdwc) == POWER_SUPPLY_TYPE_USB_CDP && + mdwc->vbus_active && !mdwc->check_eud_state) { + dev_dbg(mdwc->dev, "Connected to CDP, pull DP up\n"); + usb_phy_drive_dp_pulse(mdwc->hs_phy, DP_PULSE_WIDTH_MSEC); + } + mdwc->ext_idx = enb->idx; if (dwc->dr_mode == USB_DR_MODE_OTG && !mdwc->in_restart) queue_work(mdwc->dwc3_wq, &mdwc->resume_work); @@ -4859,10 +4874,32 @@ static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) return 0; } +static int get_chg_type(struct dwc3_msm *mdwc) +{ + int ret, value; + + if (!mdwc->chg_type) { + mdwc->chg_type = devm_iio_channel_get(mdwc->dev, "chg_type"); + if (IS_ERR_OR_NULL(mdwc->chg_type)) { + dev_dbg(mdwc->dev, "unable to get iio channel\n"); + mdwc->chg_type = NULL; + return -ENODEV; + } + } + + ret = iio_read_channel_processed(mdwc->chg_type, &value); + if (ret < 0) { + dev_err(mdwc->dev, "failed to get charger type\n"); + return ret; + } + + return value; +} + static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned int mA) { union power_supply_propval pval = {0}; - int ret; + int ret, chg_type; if (!mdwc->usb_psy && of_property_read_bool(mdwc->dev->of_node, "qcom,usb-charger")) { @@ -4876,13 +4913,20 @@ static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned int mA) if (!mdwc->usb_psy) return 0; - if (mdwc->max_power == mA) + /* + * Set the valid current only when the device + * is connected to a Standard Downstream Port. + */ + chg_type = get_chg_type(mdwc); + if (mdwc->max_power == mA || (chg_type != -ENODEV + && chg_type != POWER_SUPPLY_TYPE_USB)) return 0; dev_info(mdwc->dev, "Avail curr from USB = %u\n", mA); /* Set max current limit in uA */ pval.intval = 1000 * mA; + ret = power_supply_set_property(mdwc->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &pval); if (ret) { @@ -4894,7 +4938,6 @@ static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned int mA) return 0; } - /** * dwc3_otg_sm_work - workqueue function. * diff --git a/drivers/usb/phy/phy-msm-qusb-v2.c b/drivers/usb/phy/phy-msm-qusb-v2.c index 473576bc1ed0..5af027034061 100644 --- a/drivers/usb/phy/phy-msm-qusb-v2.c +++ b/drivers/usb/phy/phy-msm-qusb-v2.c @@ -461,6 +461,25 @@ static void qusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt, } } +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 qusb_phy_reset(struct qusb_phy *qphy) { int ret; @@ -815,6 +834,59 @@ static int qusb_phy_notify_disconnect(struct usb_phy *phy, return 0; } +void usb_phy_drive_dp_pulse(void *phy, unsigned int interval_ms) +{ + struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); + int ret; + + ret = qusb_phy_enable_power(qphy); + if (ret < 0) { + dev_dbg(qphy->phy.dev, + "dpdm regulator enable failed:%d\n", ret); + return; + } + qusb_phy_enable_clocks(qphy, true); + msm_usb_write_readback(qphy->base, qphy->phy_reg[PWR_CTRL1], + PWR_CTRL1_POWR_DOWN, 0x00); + msm_usb_write_readback(qphy->base, qphy->phy_reg[DEBUG_CTRL4], + FORCED_UTMI_DPPULLDOWN, 0x00); + msm_usb_write_readback(qphy->base, qphy->phy_reg[DEBUG_CTRL4], + FORCED_UTMI_DMPULLDOWN, + FORCED_UTMI_DMPULLDOWN); + msm_usb_write_readback(qphy->base, qphy->phy_reg[DEBUG_CTRL3], + 0xd1, 0xd1); + msm_usb_write_readback(qphy->base, qphy->phy_reg[PWR_CTRL1], + CLAMP_N_EN, CLAMP_N_EN); + msm_usb_write_readback(qphy->base, qphy->phy_reg[INTR_CTRL], + DPSE_INTR_HIGH_SEL, 0x00); + msm_usb_write_readback(qphy->base, qphy->phy_reg[INTR_CTRL], + DPSE_INTR_EN, DPSE_INTR_EN); + + msleep(interval_ms); + + msm_usb_write_readback(qphy->base, qphy->phy_reg[INTR_CTRL], + DPSE_INTR_HIGH_SEL | + DPSE_INTR_EN, 0x00); + msm_usb_write_readback(qphy->base, qphy->phy_reg[DEBUG_CTRL3], + 0xd1, 0x00); + msm_usb_write_readback(qphy->base, qphy->phy_reg[DEBUG_CTRL4], + FORCED_UTMI_DPPULLDOWN | + FORCED_UTMI_DMPULLDOWN, 0x00); + msm_usb_write_readback(qphy->base, qphy->phy_reg[PWR_CTRL1], + PWR_CTRL1_POWR_DOWN | + CLAMP_N_EN, 0x00); + + msleep(20); + + qusb_phy_enable_clocks(qphy, false); + ret = qusb_phy_disable_power(qphy); + if (ret < 0) { + dev_dbg(qphy->phy.dev, + "dpdm regulator disable failed:%d\n", ret); + } +} +EXPORT_SYMBOL(usb_phy_drive_dp_pulse); + static int qusb_phy_dpdm_regulator_enable(struct regulator_dev *rdev) { int ret = 0; diff --git a/include/linux/usb/dwc3-msm.h b/include/linux/usb/dwc3-msm.h index 37726701b1b0..ca75d27903c8 100644 --- a/include/linux/usb/dwc3-msm.h +++ b/include/linux/usb/dwc3-msm.h @@ -111,6 +111,14 @@ struct gsi_channel_info { struct usb_gsi_request *ch_req; }; +#if IS_ENABLED(CONFIG_MSM_QUSB_PHY) +extern void usb_phy_drive_dp_pulse(void *phy, + unsigned int interval_ms); +#else +static inline void usb_phy_drive_dp_pulse(void *phy, unsigned int interval_ms) +{ } +#endif + #if IS_ENABLED(CONFIG_USB_DWC3_MSM) struct usb_ep *usb_ep_autoconfig_by_name(struct usb_gadget *gadget, struct usb_endpoint_descriptor *desc, const char *ep_name);