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 <sallenki@codeaurora.org>
This commit is contained in:
Sriharsha Allenki 2020-09-16 22:01:22 +05:30
commit 119baad772
3 changed files with 126 additions and 3 deletions

View file

@ -11,6 +11,7 @@
#include <linux/dmapool.h>
#include <linux/pm_runtime.h>
#include <linux/ratelimit.h>
#include <linux/iio/consumer.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>
#include <linux/ioport.h>
@ -33,6 +34,7 @@
#include <linux/pm_wakeup.h>
#include <linux/pm_qos.h>
#include <linux/power_supply.h>
#include <linux/qti_power_supply.h>
#include <linux/cdev.h>
#include <linux/completion.h>
#include <linux/interconnect.h>
@ -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.
*

View file

@ -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;

View file

@ -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);