diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c index af64f2ec7c7e..488f081c54ea 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; @@ -3396,6 +3399,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) { @@ -3532,6 +3536,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) @@ -3566,6 +3571,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); @@ -4839,10 +4854,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")) { @@ -4856,13 +4893,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) { @@ -4874,7 +4918,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);