diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 517aa7f1486d..033d5fb3c29e 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work) ret, dwc->current_limit); } -static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) +static int dwc3_psy_notifier(struct notifier_block *nb, + unsigned long event, void *data) { - struct power_supply *usb_psy; - const char *usb_psy_name; + struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); + struct power_supply *psy = data; + unsigned long flags; + + if (dwc->usb_psy) + return NOTIFY_DONE; + + if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) + return NOTIFY_DONE; + + /* Explicitly get the reference for this psy */ + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return NOTIFY_DONE; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * The USB power_supply may already be set. This can happen if notifier + * callbacks for the USB power_supply race, or if a previous notifier + * callback has already successfully fetched and associated the instance. + * In such cases, release the newly acquired reference and ignore + * subsequent notifications until the notifier is unregistered. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return NOTIFY_DONE; + } + + dwc->usb_psy = psy; + if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) + schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); + + return NOTIFY_OK; +} + +static void dwc3_get_usb_power_supply(struct dwc3 *dwc) +{ + struct power_supply *psy; + unsigned long flags; int ret; - ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name); + ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); if (ret < 0) - return NULL; - - usb_psy = power_supply_get_by_name(usb_psy_name); - if (!usb_psy) - return ERR_PTR(-EPROBE_DEFER); + return; INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); - return usb_psy; + + dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; + dwc->psy_nb.notifier_call = dwc3_psy_notifier; + ret = power_supply_reg_notifier(&dwc->psy_nb); + if (ret) { + dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); + dwc->psy_nb.notifier_call = NULL; + return; + } + + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return; + + /* Unregister the notifier now that we have the power supply */ + power_supply_unreg_notifier(&dwc->psy_nb); + dwc->psy_nb.notifier_call = NULL; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * It is possible that the notifier callback ran before we reached here + * and successfully fetched the power supply. In that case we need to + * release the above reference. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return; + } + + dwc->usb_psy = psy; + spin_unlock_irqrestore(&dwc->lock, flags); } int dwc3_core_probe(const struct dwc3_probe_data *data) @@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc3_get_software_properties(dwc, &data->properties); - dwc->usb_psy = dwc3_get_usb_power_supply(dwc); - if (IS_ERR(dwc->usb_psy)) - return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); + spin_lock_init(&dwc->lock); + + dwc3_get_usb_power_supply(dwc); if (!data->ignore_clocks_and_resets) { dwc->reset = devm_reset_control_array_get_optional_shared(dev); @@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc->num_usb3_ports = 1; } - spin_lock_init(&dwc->lock); mutex_init(&dwc->mutex); pm_runtime_get_noresume(dev); @@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) err_assert_reset: reset_control_assert(dwc->reset); err_put_psy: + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); if (dwc->usb_psy) power_supply_put(dwc->usb_psy); @@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc) dwc3_free_event_buffers(dwc); + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) { cancel_work_sync(&dwc->vbus_draw_work); power_supply_put(dwc->usb_psy); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index e0dee9d28740..07e0e1b8e804 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1059,6 +1059,8 @@ struct dwc3_glue_ops { * @role_switch_default_mode: default operation mode of controller while * usb role is USB_ROLE_NONE. * @usb_psy: pointer to power supply interface. + * @usb_psy_name: name of the USB power supply + * @psy_nb: power supply notifier block * @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 @@ -1251,9 +1253,13 @@ struct dwc3 { enum usb_dr_mode role_switch_default_mode; struct power_supply *usb_psy; + const char *usb_psy_name; + struct notifier_block psy_nb; struct work_struct vbus_draw_work; unsigned int current_limit; +#define DWC3_CURRENT_UNSPECIFIED UINT_MAX + u32 fladj; u32 ref_clk_per; u32 irq_gadget; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 3d4ca68e584c..c36d2a949231 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3124,15 +3124,26 @@ 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); + unsigned long flags; if (dwc->usb2_phy) return usb_phy_set_power(dwc->usb2_phy, mA); - if (!dwc->usb_psy) - return -EOPNOTSUPP; + spin_lock_irqsave(&dwc->lock, flags); + if (!dwc->usb_psy) { + if (!dwc->psy_nb.notifier_call) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -EOPNOTSUPP; + } + dwc->current_limit = mA; + spin_unlock_irqrestore(&dwc->lock, flags); + dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); + return 0; + } dwc->current_limit = mA; schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); return 0; }