summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrashanth K <prashanth.k@oss.qualcomm.com>2026-02-04 11:11:55 +0530
committerSasha Levin <sashal@kernel.org>2026-03-04 07:21:15 -0500
commit76c1123ffccfaba95cf4ecc2a50f95504a522424 (patch)
tree57af48438eff08a1b430b193e12d8c9b6d6d537b
parentaa8d68d97c7f0ef966e51afc17fdbdc372700edf (diff)
usb: dwc3: gadget: Move vbus draw to workqueue context
[ Upstream commit 54aaa3b387c2f580a99dc86a9cc2eb6dfaf599a7 ] Currently dwc3_gadget_vbus_draw() can be called from atomic context, which in turn invokes power-supply-core APIs. And some these PMIC APIs have operations that may sleep, leading to kernel panic. Fix this by moving the vbus_draw into a workqueue context. Fixes: 99288de36020 ("usb: dwc3: add an alternate path in vbus_draw callback") Cc: stable <stable@kernel.org> Tested-by: Samuel Wu <wusamuel@google.com> Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com> Signed-off-by: Prashanth K <prashanth.k@oss.qualcomm.com> Link: https://patch.msgid.link/20260204054155.3063825-1-prashanth.k@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
-rw-r--r--drivers/usb/dwc3/core.c19
-rw-r--r--drivers/usb/dwc3/core.h4
-rw-r--r--drivers/usb/dwc3/gadget.c8
3 files changed, 25 insertions, 6 deletions
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 45c9a399f8a5..11b938fd9de0 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -1892,6 +1892,20 @@ static int dwc3_get_clocks(struct dwc3 *dwc)
return 0;
}
+static void dwc3_vbus_draw_work(struct work_struct *work)
+{
+ struct dwc3 *dwc = container_of(work, struct dwc3, vbus_draw_work);
+ union power_supply_propval val = {0};
+ int ret;
+
+ val.intval = 1000 * (dwc->current_limit);
+ ret = power_supply_set_property(dwc->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
+
+ if (ret < 0)
+ dev_dbg(dwc->dev, "Error (%d) setting vbus draw (%d mA)\n",
+ ret, dwc->current_limit);
+}
+
static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc)
{
struct power_supply *usb_psy;
@@ -1906,6 +1920,7 @@ static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc)
if (!usb_psy)
return ERR_PTR(-EPROBE_DEFER);
+ INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work);
return usb_psy;
}
@@ -2097,8 +2112,10 @@ static void dwc3_remove(struct platform_device *pdev)
dwc3_free_event_buffers(dwc);
- if (dwc->usb_psy)
+ if (dwc->usb_psy) {
+ cancel_work_sync(&dwc->vbus_draw_work);
power_supply_put(dwc->usb_psy);
+ }
}
#ifdef CONFIG_PM
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 9fc91f5c0bdb..45084ca66806 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1034,6 +1034,8 @@ struct dwc3_scratchpad_array {
* @role_switch_default_mode: default operation mode of controller while
* usb role is USB_ROLE_NONE.
* @usb_psy: pointer to power supply interface.
+ * @vbus_draw_work: Work to set the vbus drawing limit
+ * @current_limit: How much current to draw from vbus, in milliAmperes.
* @usb2_phy: pointer to USB2 PHY
* @usb3_phy: pointer to USB3 PHY
* @usb2_generic_phy: pointer to USB2 PHY
@@ -1202,6 +1204,8 @@ struct dwc3 {
enum usb_dr_mode role_switch_default_mode;
struct power_supply *usb_psy;
+ struct work_struct vbus_draw_work;
+ unsigned int current_limit;
u32 fladj;
u32 ref_clk_per;
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 8781ebeaab90..7e6661781aff 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -3139,8 +3139,6 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g,
static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
{
struct dwc3 *dwc = gadget_to_dwc(g);
- union power_supply_propval val = {0};
- int ret;
if (dwc->usb2_phy)
return usb_phy_set_power(dwc->usb2_phy, mA);
@@ -3148,10 +3146,10 @@ static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
if (!dwc->usb_psy)
return -EOPNOTSUPP;
- val.intval = 1000 * mA;
- ret = power_supply_set_property(dwc->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
+ dwc->current_limit = mA;
+ schedule_work(&dwc->vbus_draw_work);
- return ret;
+ return 0;
}
/**