summaryrefslogtreecommitdiff
path: root/drivers/usb/dwc3/dwc3-msm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/dwc3/dwc3-msm.c')
-rw-r--r--drivers/usb/dwc3/dwc3-msm.c158
1 files changed, 128 insertions, 30 deletions
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index c2d788bc4bc5..5ad68df298cd 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -54,6 +54,8 @@
#include "debug.h"
#include "xhci.h"
+#define SDP_CONNETION_CHECK_TIME 10000 /* in ms */
+
/* time out to wait for USB cable status notification (in ms)*/
#define SM_INIT_TIMEOUT 30000
@@ -70,6 +72,8 @@ MODULE_PARM_DESC(cpu_to_affin, "affin usb irq to this cpu");
/* XHCI registers */
#define USB3_HCSPARAMS1 (0x4)
+#define USB3_HCCPARAMS2 (0x1c)
+#define HCC_CTC(p) ((p) & (1 << 3))
#define USB3_PORTSC (0x420)
/**
@@ -211,6 +215,7 @@ struct dwc3_msm {
struct notifier_block dwc3_cpu_notifier;
struct notifier_block usbdev_nb;
bool hc_died;
+ bool xhci_ss_compliance_enable;
struct extcon_dev *extcon_vbus;
struct extcon_dev *extcon_id;
@@ -227,6 +232,7 @@ struct dwc3_msm {
int pm_qos_latency;
struct pm_qos_request pm_qos_req_dma;
struct delayed_work perf_vote_work;
+ struct delayed_work sdp_check;
};
#define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */
@@ -1233,7 +1239,7 @@ static void gsi_set_clear_dbell(struct usb_ep *ep,
*/
static bool gsi_check_ready_to_suspend(struct usb_ep *ep, bool f_suspend)
{
- u32 timeout = 1500;
+ u32 timeout = 500;
u32 reg = 0;
struct dwc3_ep *dep = to_dwc3_ep(ep);
struct dwc3 *dwc = dep->dwc;
@@ -1246,6 +1252,7 @@ static bool gsi_check_ready_to_suspend(struct usb_ep *ep, bool f_suspend)
"Unable to suspend GSI ch. WR_CTRL_STATE != 0\n");
return false;
}
+ usleep_range(20, 22);
}
/* Check for U3 only if we are not handling Function Suspend */
if (!f_suspend) {
@@ -1927,6 +1934,7 @@ static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc)
reg = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG);
if (reg & PWR_EVNT_LPM_IN_L2_MASK)
break;
+ usleep_range(20, 30);
}
if (!(reg & PWR_EVNT_LPM_IN_L2_MASK))
dev_err(mdwc->dev, "could not transition HS PHY to L2\n");
@@ -2625,6 +2633,25 @@ done:
return NOTIFY_DONE;
}
+
+static void check_for_sdp_connection(struct work_struct *w)
+{
+ struct dwc3_msm *mdwc =
+ container_of(w, struct dwc3_msm, sdp_check.work);
+ struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
+
+ if (!mdwc->vbus_active)
+ return;
+
+ /* floating D+/D- lines detected */
+ if (dwc->gadget.state < USB_STATE_DEFAULT &&
+ dwc3_gadget_get_link_state(dwc) != DWC3_LINK_STATE_CMPLY) {
+ mdwc->vbus_active = 0;
+ dbg_event(0xFF, "Q RW SPD CHK", mdwc->vbus_active);
+ queue_work(mdwc->dwc3_wq, &mdwc->resume_work);
+ }
+}
+
static int dwc3_msm_vbus_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
@@ -2796,6 +2823,34 @@ static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR_RW(speed);
static void msm_dwc3_perf_vote_work(struct work_struct *w);
+static ssize_t xhci_link_compliance_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dwc3_msm *mdwc = dev_get_drvdata(dev);
+
+ if (mdwc->xhci_ss_compliance_enable)
+ return snprintf(buf, PAGE_SIZE, "y\n");
+ else
+ return snprintf(buf, PAGE_SIZE, "n\n");
+}
+
+static ssize_t xhci_link_compliance_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct dwc3_msm *mdwc = dev_get_drvdata(dev);
+ bool value;
+ int ret;
+
+ ret = strtobool(buf, &value);
+ if (!ret) {
+ mdwc->xhci_ss_compliance_enable = value;
+ return count;
+ }
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(xhci_link_compliance);
static int dwc3_msm_probe(struct platform_device *pdev)
{
@@ -2833,6 +2888,7 @@ static int dwc3_msm_probe(struct platform_device *pdev)
INIT_WORK(&mdwc->vbus_draw_work, dwc3_msm_vbus_draw_work);
INIT_DELAYED_WORK(&mdwc->sm_work, dwc3_otg_sm_work);
INIT_DELAYED_WORK(&mdwc->perf_vote_work, msm_dwc3_perf_vote_work);
+ INIT_DELAYED_WORK(&mdwc->sdp_check, check_for_sdp_connection);
mdwc->dwc3_wq = alloc_ordered_workqueue("dwc3_wq", 0);
if (!mdwc->dwc3_wq) {
@@ -2844,7 +2900,7 @@ static int dwc3_msm_probe(struct platform_device *pdev)
ret = dwc3_msm_get_clk_gdsc(mdwc);
if (ret) {
dev_err(&pdev->dev, "error getting clock or gdsc.\n");
- return ret;
+ goto err;
}
mdwc->id_state = DWC3_ID_FLOAT;
@@ -2891,8 +2947,9 @@ static int dwc3_msm_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(&pdev->dev, mdwc->ss_phy_irq,
msm_dwc3_pwr_irq,
msm_dwc3_pwr_irq_thread,
- IRQF_TRIGGER_RISING | IRQF_EARLY_RESUME
- | IRQF_ONESHOT, "ss_phy_irq", mdwc);
+ IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH
+ | IRQF_EARLY_RESUME | IRQF_ONESHOT,
+ "ss_phy_irq", mdwc);
if (ret) {
dev_err(&pdev->dev, "irqreq ss_phy_irq failed: %d\n",
ret);
@@ -3139,6 +3196,7 @@ static int dwc3_msm_probe(struct platform_device *pdev)
device_create_file(&pdev->dev, &dev_attr_mode);
device_create_file(&pdev->dev, &dev_attr_speed);
+ device_create_file(&pdev->dev, &dev_attr_xhci_link_compliance);
host_mode = usb_get_dr_mode(&mdwc->dwc3->dev) == USB_DR_MODE_HOST;
if (host_mode ||
@@ -3151,25 +3209,21 @@ static int dwc3_msm_probe(struct platform_device *pdev)
return 0;
put_dwc3:
- platform_device_put(mdwc->dwc3);
if (mdwc->bus_perf_client)
msm_bus_scale_unregister_client(mdwc->bus_perf_client);
+ of_platform_depopulate(&pdev->dev);
err:
+ destroy_workqueue(mdwc->dwc3_wq);
return ret;
}
-static int dwc3_msm_remove_children(struct device *dev, void *data)
-{
- device_unregister(dev);
- return 0;
-}
-
static int dwc3_msm_remove(struct platform_device *pdev)
{
struct dwc3_msm *mdwc = platform_get_drvdata(pdev);
int ret_pm;
device_remove_file(&pdev->dev, &dev_attr_mode);
+ device_remove_file(&pdev->dev, &dev_attr_xhci_link_compliance);
if (cpu_to_affin)
unregister_cpu_notifier(&mdwc->dwc3_cpu_notifier);
@@ -3199,8 +3253,7 @@ static int dwc3_msm_remove(struct platform_device *pdev)
if (mdwc->hs_phy)
mdwc->hs_phy->flags &= ~PHY_HOST_MODE;
- platform_device_put(mdwc->dwc3);
- device_for_each_child(&pdev->dev, NULL, dwc3_msm_remove_children);
+ of_platform_depopulate(&pdev->dev);
dbg_event(0xFF, "Remov put", 0);
pm_runtime_disable(mdwc->dev);
@@ -3383,13 +3436,16 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on)
dev_dbg(mdwc->dev, "%s: turn on host\n", __func__);
mdwc->hs_phy->flags |= PHY_HOST_MODE;
- if (dwc->maximum_speed == USB_SPEED_SUPER)
+ if (dwc->maximum_speed == USB_SPEED_SUPER) {
mdwc->ss_phy->flags |= PHY_HOST_MODE;
+ usb_phy_notify_connect(mdwc->ss_phy,
+ USB_SPEED_SUPER);
+ }
+ usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH);
pm_runtime_get_sync(mdwc->dev);
dbg_event(0xFF, "StrtHost gync",
atomic_read(&mdwc->dev->power.usage_count));
- usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH);
if (!IS_ERR(mdwc->vbus_reg))
ret = regulator_enable(mdwc->vbus_reg);
if (ret) {
@@ -3434,6 +3490,25 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on)
}
/*
+ * If the Compliance Transition Capability(CTC) flag of
+ * HCCPARAMS2 register is set and xhci_link_compliance sysfs
+ * param has been enabled by the user for the SuperSpeed host
+ * controller, then write 10 (Link in Compliance Mode State)
+ * onto the Port Link State(PLS) field of the PORTSC register
+ * for 3.0 host controller which is at an offset of USB3_PORTSC
+ * + 0x10 from the DWC3 base address. Also, disable the runtime
+ * PM of 3.0 root hub (root hub of shared_hcd of xhci device)
+ */
+ if (HCC_CTC(dwc3_msm_read_reg(mdwc->base, USB3_HCCPARAMS2))
+ && mdwc->xhci_ss_compliance_enable
+ && dwc->maximum_speed == USB_SPEED_SUPER) {
+ dwc3_msm_write_reg(mdwc->base, USB3_PORTSC + 0x10,
+ 0x10340);
+ pm_runtime_disable(&hcd_to_xhci(platform_get_drvdata(
+ dwc->xhci))->shared_hcd->self.root_hub->dev);
+ }
+
+ /*
* In some cases it is observed that USB PHY is not going into
* suspend with host mode suspend functionality. Hence disable
* XHCI's runtime PM here if disable_host_mode_pm is set.
@@ -3478,8 +3553,13 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on)
dbg_event(0xFF, "StopHost gsync",
atomic_read(&mdwc->dev->power.usage_count));
usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH);
+ if (mdwc->ss_phy->flags & PHY_HOST_MODE) {
+ usb_phy_notify_disconnect(mdwc->ss_phy,
+ USB_SPEED_SUPER);
+ mdwc->ss_phy->flags &= ~PHY_HOST_MODE;
+ }
+
mdwc->hs_phy->flags &= ~PHY_HOST_MODE;
- mdwc->ss_phy->flags &= ~PHY_HOST_MODE;
platform_device_del(dwc->xhci);
usb_unregister_notify(&mdwc->host_nb);
@@ -3586,36 +3666,48 @@ static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on)
return 0;
}
-static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned mA)
+int get_psy_type(struct dwc3_msm *mdwc)
{
union power_supply_propval pval = {0};
- int ret;
if (mdwc->charging_disabled)
- return 0;
-
- if (mdwc->max_power == mA)
- return 0;
+ return -EINVAL;
if (!mdwc->usb_psy) {
mdwc->usb_psy = power_supply_get_by_name("usb");
if (!mdwc->usb_psy) {
- dev_warn(mdwc->dev, "Could not get usb power_supply\n");
+ dev_err(mdwc->dev, "Could not get usb psy\n");
return -ENODEV;
}
}
- power_supply_get_property(mdwc->usb_psy,
- POWER_SUPPLY_PROP_REAL_TYPE, &pval);
- if (pval.intval != POWER_SUPPLY_TYPE_USB)
+ power_supply_get_property(mdwc->usb_psy, POWER_SUPPLY_PROP_REAL_TYPE,
+ &pval);
+
+ return pval.intval;
+}
+
+static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned mA)
+{
+ union power_supply_propval pval = {0};
+ int ret, psy_type;
+
+ if (mdwc->max_power == mA)
return 0;
- dev_info(mdwc->dev, "Avail curr from USB = %u\n", mA);
+ psy_type = get_psy_type(mdwc);
+ if (psy_type == POWER_SUPPLY_TYPE_USB) {
+ dev_info(mdwc->dev, "Avail curr from USB = %u\n", mA);
+ /* Set max current limit in uA */
+ pval.intval = 1000 * mA;
+ } else if (psy_type == POWER_SUPPLY_TYPE_USB_FLOAT) {
+ pval.intval = -ETIMEDOUT;
+ } else {
+ return 0;
+ }
- /* Set max current limit in uA */
- pval.intval = 1000 * mA;
ret = power_supply_set_property(mdwc->usb_psy,
- POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ POWER_SUPPLY_PROP_SDP_CURRENT_MAX, &pval);
if (ret) {
dev_dbg(mdwc->dev, "power supply error when setting property\n");
return ret;
@@ -3684,6 +3776,10 @@ static void dwc3_otg_sm_work(struct work_struct *w)
work = 1;
} else if (test_bit(B_SESS_VLD, &mdwc->inputs)) {
dev_dbg(mdwc->dev, "b_sess_vld\n");
+ if (get_psy_type(mdwc) == POWER_SUPPLY_TYPE_USB_FLOAT)
+ queue_delayed_work(mdwc->dwc3_wq,
+ &mdwc->sdp_check,
+ msecs_to_jiffies(SDP_CONNETION_CHECK_TIME));
/*
* Increment pm usage count upon cable connect. Count
* is decremented in OTG_STATE_B_PERIPHERAL state on
@@ -3707,6 +3803,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
!test_bit(ID, &mdwc->inputs)) {
dev_dbg(mdwc->dev, "!id || !bsv\n");
mdwc->otg_state = OTG_STATE_B_IDLE;
+ cancel_delayed_work_sync(&mdwc->sdp_check);
dwc3_otg_start_peripheral(mdwc, 0);
/*
* Decrement pm usage count upon cable disconnect
@@ -3739,6 +3836,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
if (!test_bit(B_SESS_VLD, &mdwc->inputs)) {
dev_dbg(mdwc->dev, "BSUSP: !bsv\n");
mdwc->otg_state = OTG_STATE_B_IDLE;
+ cancel_delayed_work_sync(&mdwc->sdp_check);
dwc3_otg_start_peripheral(mdwc, 0);
} else if (!test_bit(B_SUSPEND, &mdwc->inputs)) {
dev_dbg(mdwc->dev, "BSUSP !susp\n");