Message ID | 20190826102512.14829-1-jun.li@nxp.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v4] usb: chipidea: add role switch class support | expand |
On 19-08-26 18:25:12, jun.li@nxp.com wrote: > From: Li Jun <jun.li@nxp.com> > > USB role is fully controlled by usb role switch consumer(e.g. typec), > usb port can be at host mode(USB_ROLE_HOST), device mode connected to > host(USB_ROLE_DEVICE), or not connecting any partner(USB_ROLE_NONE). > > Signed-off-by: Li Jun <jun.li@nxp.com> > --- > changes for v4: > - Add spinlock for role set(). > - Select USB_ROLE_SWITCH to fix build break. > - Add runtime PM for role set. > - Do flash_workqueue only in case both stop and start required > for role set, means role change directly from device to host > or vice versa. > > Changes for v3: > - Remove the patch usb: chipidea: replace ci_role with usb_role > as the existing ci->role usage can't map to usb_role. > - Use the suggested ci_hdrc_cable for reuse current role change > handling. > - Fix build robot warning by add usb_role head file. > > Change for v2: > - Support USB_ROLE_NONE, which for usb port not connecting any > device or host, and will be in low power mode. > > drivers/usb/chipidea/Kconfig | 1 + > drivers/usb/chipidea/ci.h | 12 +++++++ > drivers/usb/chipidea/core.c | 83 ++++++++++++++++++++++++++++++++++++++++++++ > drivers/usb/chipidea/otg.c | 8 ++--- > 4 files changed, 100 insertions(+), 4 deletions(-) > > diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig > index eb37ebf..ae850b3 100644 > --- a/drivers/usb/chipidea/Kconfig > +++ b/drivers/usb/chipidea/Kconfig > @@ -6,6 +6,7 @@ config USB_CHIPIDEA > select EXTCON > select RESET_CONTROLLER > select USB_ULPI_BUS > + select USB_ROLE_SWITCH > help > Say Y here if your system has a dual role high speed USB > controller based on ChipIdea silicon IP. It supports: > diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h > index 6a2cc5c..6911aef 100644 > --- a/drivers/usb/chipidea/ci.h > +++ b/drivers/usb/chipidea/ci.h > @@ -16,6 +16,7 @@ > #include <linux/usb/gadget.h> > #include <linux/usb/otg-fsm.h> > #include <linux/usb/otg.h> > +#include <linux/usb/role.h> > #include <linux/ulpi/interface.h> > > /****************************************************************************** > @@ -217,6 +218,7 @@ struct ci_hdrc { > ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS]; > unsigned enabled_otg_timer_bits; > enum otg_fsm_timer next_otg_timer; > + struct usb_role_switch *role_switch; > struct work_struct work; > struct workqueue_struct *wq; > > @@ -290,6 +292,16 @@ static inline void ci_role_stop(struct ci_hdrc *ci) > ci->roles[role]->stop(ci); > } > > +static inline enum usb_role ci_role_to_usb_role(struct ci_hdrc *ci) > +{ > + if (ci->role == CI_ROLE_HOST) > + return USB_ROLE_HOST; > + else if (ci->role == CI_ROLE_GADGET && ci->vbus_active) > + return USB_ROLE_DEVICE; > + else > + return USB_ROLE_NONE; > +} > + > /** > * hw_read_id_reg: reads from a identification register > * @ci: the controller > diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c > index 26062d6..bfc6a18 100644 > --- a/drivers/usb/chipidea/core.c > +++ b/drivers/usb/chipidea/core.c > @@ -600,6 +600,71 @@ static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, > return NOTIFY_DONE; > } > > +static enum usb_role ci_usb_role_switch_get(struct device *dev) > +{ > + struct ci_hdrc *ci = dev_get_drvdata(dev); > + enum usb_role role; > + unsigned long flags; > + > + spin_lock_irqsave(&ci->lock, flags); > + role = ci_role_to_usb_role(ci); > + spin_unlock_irqrestore(&ci->lock, flags); > + > + return role; > +} > + > +static int ci_usb_role_switch_set(struct device *dev, enum usb_role role) > +{ > + struct ci_hdrc *ci = dev_get_drvdata(dev); > + struct ci_hdrc_cable *cable = NULL; > + enum usb_role current_role = ci_role_to_usb_role(ci); > + unsigned long flags; > + > + if (current_role == role) > + return 0; > + > + pm_runtime_get_sync(ci->dev); > + /* Stop current role */ > + spin_lock_irqsave(&ci->lock, flags); > + if (current_role == USB_ROLE_DEVICE) > + cable = &ci->platdata->vbus_extcon; > + else if (current_role == USB_ROLE_HOST) > + cable = &ci->platdata->id_extcon; > + > + if (cable) { > + cable->changed = true; > + cable->connected = false; > + ci_irq(ci->irq, ci); > + spin_unlock_irqrestore(&ci->lock, flags); > + if (ci->wq && role != USB_ROLE_NONE) > + flush_workqueue(ci->wq); > + spin_lock_irqsave(&ci->lock, flags); > + } > + > + cable = NULL; > + > + /* Start target role */ > + if (role == USB_ROLE_DEVICE) > + cable = &ci->platdata->vbus_extcon; > + else if (role == USB_ROLE_HOST) > + cable = &ci->platdata->id_extcon; > + > + if (cable) { > + cable->changed = true; > + cable->connected = true; > + ci_irq(ci->irq, ci); > + } > + spin_unlock_irqrestore(&ci->lock, flags); > + pm_runtime_put_sync(ci->dev); > + > + return 0; > +} > + > +static struct usb_role_switch_desc ci_role_switch = { > + .set = ci_usb_role_switch_set, > + .get = ci_usb_role_switch_get, > +}; > + > static int ci_get_platdata(struct device *dev, > struct ci_hdrc_platform_data *platdata) > { > @@ -726,6 +791,9 @@ static int ci_get_platdata(struct device *dev, > cable->connected = false; > } > > + if (device_property_read_bool(dev, "usb-role-switch")) > + ci_role_switch.fwnode = dev->fwnode; > + > platdata->pctl = devm_pinctrl_get(dev); > if (!IS_ERR(platdata->pctl)) { > struct pinctrl_state *p; > @@ -1051,6 +1119,15 @@ static int ci_hdrc_probe(struct platform_device *pdev) > } > } > > + if (ci_role_switch.fwnode) { > + ci->role_switch = usb_role_switch_register(dev, > + &ci_role_switch); > + if (IS_ERR(ci->role_switch)) { > + ret = PTR_ERR(ci->role_switch); > + goto deinit_otg; > + } > + } > + > if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { > if (ci->is_otg) { > ci->role = ci_otg_role(ci); > @@ -1115,6 +1192,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) > remove_debug: > dbg_remove_files(ci); > stop: > + if (ci->role_switch) > + usb_role_switch_unregister(ci->role_switch); > +deinit_otg: > if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) > ci_hdrc_otg_destroy(ci); > deinit_gadget: > @@ -1133,6 +1213,9 @@ static int ci_hdrc_remove(struct platform_device *pdev) > { > struct ci_hdrc *ci = platform_get_drvdata(pdev); > > + if (ci->role_switch) > + usb_role_switch_unregister(ci->role_switch); > + > if (ci->supports_runtime_pm) { > pm_runtime_get_sync(&pdev->dev); > pm_runtime_disable(&pdev->dev); > diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c > index f25d482..fbfb02e 100644 > --- a/drivers/usb/chipidea/otg.c > +++ b/drivers/usb/chipidea/otg.c > @@ -35,7 +35,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) > * detection overwrite OTGSC register value > */ > cable = &ci->platdata->vbus_extcon; > - if (!IS_ERR(cable->edev)) { > + if (!IS_ERR(cable->edev) || ci->role_switch) { > if (cable->changed) > val |= OTGSC_BSVIS; > else > @@ -53,7 +53,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) > } > > cable = &ci->platdata->id_extcon; > - if (!IS_ERR(cable->edev)) { > + if (!IS_ERR(cable->edev) || ci->role_switch) { > if (cable->changed) > val |= OTGSC_IDIS; > else > @@ -83,7 +83,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) > struct ci_hdrc_cable *cable; > > cable = &ci->platdata->vbus_extcon; > - if (!IS_ERR(cable->edev)) { > + if (!IS_ERR(cable->edev) || ci->role_switch) { > if (data & mask & OTGSC_BSVIS) > cable->changed = false; > > @@ -97,7 +97,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) > } > > cable = &ci->platdata->id_extcon; > - if (!IS_ERR(cable->edev)) { > + if (!IS_ERR(cable->edev) || ci->role_switch) { > if (data & mask & OTGSC_IDIS) > cable->changed = false; > > -- > 2.7.4 > Applied, thanks. Peter
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index eb37ebf..ae850b3 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -6,6 +6,7 @@ config USB_CHIPIDEA select EXTCON select RESET_CONTROLLER select USB_ULPI_BUS + select USB_ROLE_SWITCH help Say Y here if your system has a dual role high speed USB controller based on ChipIdea silicon IP. It supports: diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 6a2cc5c..6911aef 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -16,6 +16,7 @@ #include <linux/usb/gadget.h> #include <linux/usb/otg-fsm.h> #include <linux/usb/otg.h> +#include <linux/usb/role.h> #include <linux/ulpi/interface.h> /****************************************************************************** @@ -217,6 +218,7 @@ struct ci_hdrc { ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS]; unsigned enabled_otg_timer_bits; enum otg_fsm_timer next_otg_timer; + struct usb_role_switch *role_switch; struct work_struct work; struct workqueue_struct *wq; @@ -290,6 +292,16 @@ static inline void ci_role_stop(struct ci_hdrc *ci) ci->roles[role]->stop(ci); } +static inline enum usb_role ci_role_to_usb_role(struct ci_hdrc *ci) +{ + if (ci->role == CI_ROLE_HOST) + return USB_ROLE_HOST; + else if (ci->role == CI_ROLE_GADGET && ci->vbus_active) + return USB_ROLE_DEVICE; + else + return USB_ROLE_NONE; +} + /** * hw_read_id_reg: reads from a identification register * @ci: the controller diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 26062d6..bfc6a18 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -600,6 +600,71 @@ static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, return NOTIFY_DONE; } +static enum usb_role ci_usb_role_switch_get(struct device *dev) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + enum usb_role role; + unsigned long flags; + + spin_lock_irqsave(&ci->lock, flags); + role = ci_role_to_usb_role(ci); + spin_unlock_irqrestore(&ci->lock, flags); + + return role; +} + +static int ci_usb_role_switch_set(struct device *dev, enum usb_role role) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + struct ci_hdrc_cable *cable = NULL; + enum usb_role current_role = ci_role_to_usb_role(ci); + unsigned long flags; + + if (current_role == role) + return 0; + + pm_runtime_get_sync(ci->dev); + /* Stop current role */ + spin_lock_irqsave(&ci->lock, flags); + if (current_role == USB_ROLE_DEVICE) + cable = &ci->platdata->vbus_extcon; + else if (current_role == USB_ROLE_HOST) + cable = &ci->platdata->id_extcon; + + if (cable) { + cable->changed = true; + cable->connected = false; + ci_irq(ci->irq, ci); + spin_unlock_irqrestore(&ci->lock, flags); + if (ci->wq && role != USB_ROLE_NONE) + flush_workqueue(ci->wq); + spin_lock_irqsave(&ci->lock, flags); + } + + cable = NULL; + + /* Start target role */ + if (role == USB_ROLE_DEVICE) + cable = &ci->platdata->vbus_extcon; + else if (role == USB_ROLE_HOST) + cable = &ci->platdata->id_extcon; + + if (cable) { + cable->changed = true; + cable->connected = true; + ci_irq(ci->irq, ci); + } + spin_unlock_irqrestore(&ci->lock, flags); + pm_runtime_put_sync(ci->dev); + + return 0; +} + +static struct usb_role_switch_desc ci_role_switch = { + .set = ci_usb_role_switch_set, + .get = ci_usb_role_switch_get, +}; + static int ci_get_platdata(struct device *dev, struct ci_hdrc_platform_data *platdata) { @@ -726,6 +791,9 @@ static int ci_get_platdata(struct device *dev, cable->connected = false; } + if (device_property_read_bool(dev, "usb-role-switch")) + ci_role_switch.fwnode = dev->fwnode; + platdata->pctl = devm_pinctrl_get(dev); if (!IS_ERR(platdata->pctl)) { struct pinctrl_state *p; @@ -1051,6 +1119,15 @@ static int ci_hdrc_probe(struct platform_device *pdev) } } + if (ci_role_switch.fwnode) { + ci->role_switch = usb_role_switch_register(dev, + &ci_role_switch); + if (IS_ERR(ci->role_switch)) { + ret = PTR_ERR(ci->role_switch); + goto deinit_otg; + } + } + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { if (ci->is_otg) { ci->role = ci_otg_role(ci); @@ -1115,6 +1192,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) remove_debug: dbg_remove_files(ci); stop: + if (ci->role_switch) + usb_role_switch_unregister(ci->role_switch); +deinit_otg: if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) ci_hdrc_otg_destroy(ci); deinit_gadget: @@ -1133,6 +1213,9 @@ static int ci_hdrc_remove(struct platform_device *pdev) { struct ci_hdrc *ci = platform_get_drvdata(pdev); + if (ci->role_switch) + usb_role_switch_unregister(ci->role_switch); + if (ci->supports_runtime_pm) { pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index f25d482..fbfb02e 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -35,7 +35,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) * detection overwrite OTGSC register value */ cable = &ci->platdata->vbus_extcon; - if (!IS_ERR(cable->edev)) { + if (!IS_ERR(cable->edev) || ci->role_switch) { if (cable->changed) val |= OTGSC_BSVIS; else @@ -53,7 +53,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) } cable = &ci->platdata->id_extcon; - if (!IS_ERR(cable->edev)) { + if (!IS_ERR(cable->edev) || ci->role_switch) { if (cable->changed) val |= OTGSC_IDIS; else @@ -83,7 +83,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) struct ci_hdrc_cable *cable; cable = &ci->platdata->vbus_extcon; - if (!IS_ERR(cable->edev)) { + if (!IS_ERR(cable->edev) || ci->role_switch) { if (data & mask & OTGSC_BSVIS) cable->changed = false; @@ -97,7 +97,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) } cable = &ci->platdata->id_extcon; - if (!IS_ERR(cable->edev)) { + if (!IS_ERR(cable->edev) || ci->role_switch) { if (data & mask & OTGSC_IDIS) cable->changed = false;