> 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 parter(USB_ROLE_NONE). > %s/parter/partner ? Are there any ways you could get external cable status from usb-switch or type-c like external connector? If there are, you could update otgsc value for otgsc_read, or change cable status, and avoid changing common handling, like ci_hdrc_probe and ci_otg_work. And it could benefit for other use cases, like booting with cable connected and switch role through /sys. Peter > Signed-off-by: Li Jun <jun.li@xxxxxxx> > --- > > 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/ci.h | 3 ++ > drivers/usb/chipidea/core.c | 120 +++++++++++++++++++++++++++++++++------- > ---- > drivers/usb/chipidea/otg.c | 20 ++++++++ > 3 files changed, 114 insertions(+), 29 deletions(-) > > diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index > 82b86cd..f0aec1d 100644 > --- a/drivers/usb/chipidea/ci.h > +++ b/drivers/usb/chipidea/ci.h > @@ -205,6 +205,7 @@ struct ci_hdrc { > int irq; > struct ci_role_driver *roles[USB_ROLE_DEVICE + 1]; > enum usb_role role; > + enum usb_role target_role; > bool is_otg; > struct usb_otg otg; > struct otg_fsm fsm; > @@ -212,6 +213,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; > > @@ -244,6 +246,7 @@ struct ci_hdrc { > struct dentry *debugfs; > bool id_event; > bool b_sess_valid_event; > + bool role_switch_event; > bool imx28_write_fix; > bool supports_runtime_pm; > bool in_lpm; > diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index > bc24c5b..965ce17 100644 > --- a/drivers/usb/chipidea/core.c > +++ b/drivers/usb/chipidea/core.c > @@ -587,6 +587,42 @@ static irqreturn_t ci_irq(int irq, void *data) > return ret; > } > > +static int ci_usb_role_switch_set(struct device *dev, enum usb_role > +role) { > + struct ci_hdrc *ci = dev_get_drvdata(dev); > + unsigned long flags; > + > + if (ci->role == role) > + return 0; > + > + spin_lock_irqsave(&ci->lock, flags); > + ci->role_switch_event = true; > + ci->target_role = role; > + spin_unlock_irqrestore(&ci->lock, flags); > + > + ci_otg_queue_work(ci); > + > + return 0; > +} > + > +static enum usb_role ci_usb_role_switch_get(struct device *dev) { > + struct ci_hdrc *ci = dev_get_drvdata(dev); > + unsigned long flags; > + enum usb_role role; > + > + spin_lock_irqsave(&ci->lock, flags); > + role = ci->role; > + spin_unlock_irqrestore(&ci->lock, flags); > + > + return role; > +} > + > +static struct usb_role_switch_desc ci_role_switch = { > + .set = ci_usb_role_switch_set, > + .get = ci_usb_role_switch_get, > +}; > + > static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, > void *ptr) > { > @@ -689,6 +725,9 @@ static int ci_get_platdata(struct device *dev, > if (of_find_property(dev->of_node, "non-zero-ttctrl-ttha", NULL)) > platdata->flags |= CI_HDRC_SET_NON_ZERO_TTHA; > > + if (device_property_read_bool(dev, "usb-role-switch")) > + ci_role_switch.fwnode = dev->fwnode; > + > ext_id = ERR_PTR(-ENODEV); > ext_vbus = ERR_PTR(-ENODEV); > if (of_property_read_bool(dev->of_node, "extcon")) { @@ -908,6 +947,43 > @@ static const struct attribute_group ci_attr_group = { > .attrs = ci_attrs, > }; > > +static int ci_start_initial_role(struct ci_hdrc *ci) { > + int ret = 0; > + > + if (ci->roles[USB_ROLE_HOST] && ci->roles[USB_ROLE_DEVICE]) { > + if (ci->is_otg) { > + ci->role = ci_otg_role(ci); > + /* Enable ID change irq */ > + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); > + } else { > + /* > + * If the controller is not OTG capable, but support > + * role switch, the defalt role is gadget, and the > + * user can switch it through debugfs. > + */ > + ci->role = USB_ROLE_DEVICE; > + } > + } else { > + ci->role = ci->roles[USB_ROLE_HOST] > + ? USB_ROLE_HOST > + : USB_ROLE_DEVICE; > + } > + > + if (!ci_otg_is_fsm_mode(ci)) { > + /* only update vbus status for peripheral */ > + if (ci->role == USB_ROLE_DEVICE) > + ci_handle_vbus_change(ci); > + > + ret = ci_role_start(ci, ci->role); > + if (ret) > + dev_err(ci->dev, "can't start %s role\n", > + ci_role(ci)->name); > + } > + > + return ret; > +} > + > static int ci_hdrc_probe(struct platform_device *pdev) { > struct device *dev = &pdev->dev; > @@ -1051,36 +1127,10 @@ static int ci_hdrc_probe(struct platform_device *pdev) > } > } > > - if (ci->roles[USB_ROLE_HOST] && ci->roles[USB_ROLE_DEVICE]) { > - if (ci->is_otg) { > - ci->role = ci_otg_role(ci); > - /* Enable ID change irq */ > - hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); > - } else { > - /* > - * If the controller is not OTG capable, but support > - * role switch, the defalt role is gadget, and the > - * user can switch it through debugfs. > - */ > - ci->role = USB_ROLE_DEVICE; > - } > - } else { > - ci->role = ci->roles[USB_ROLE_HOST] > - ? USB_ROLE_HOST > - : USB_ROLE_DEVICE; > - } > - > - if (!ci_otg_is_fsm_mode(ci)) { > - /* only update vbus status for peripheral */ > - if (ci->role == USB_ROLE_DEVICE) > - ci_handle_vbus_change(ci); > - > - ret = ci_role_start(ci, ci->role); > - if (ret) { > - dev_err(dev, "can't start %s role\n", > - ci_role(ci)->name); > + if (!ci_role_switch.fwnode) { > + ret = ci_start_initial_role(ci); > + if (ret) > goto stop; > - } > } > > ret = devm_request_irq(dev, ci->irq, ci_irq, IRQF_SHARED, @@ -1092,6 > +1142,15 @@ static int ci_hdrc_probe(struct platform_device *pdev) > if (ret) > goto stop; > > + 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 stop; > + } > + } > + > if (ci->supports_runtime_pm) { > pm_runtime_set_active(&pdev->dev); > pm_runtime_enable(&pdev->dev); > @@ -1133,6 +1192,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 > 5bde0b5..03675b6 100644 > --- a/drivers/usb/chipidea/otg.c > +++ b/drivers/usb/chipidea/otg.c > @@ -214,6 +214,26 @@ static void ci_otg_work(struct work_struct *work) > ci_handle_vbus_change(ci); > } > > + if (ci->role_switch_event) { > + ci->role_switch_event = false; > + > + /* Stop current role */ > + if (ci->role == USB_ROLE_DEVICE) { > + usb_gadget_vbus_disconnect(&ci->gadget); > + ci->role = USB_ROLE_NONE; > + } else if (ci->role == USB_ROLE_HOST) { > + ci_role_stop(ci); > + } > + > + /* Start target role */ > + if (ci->target_role == USB_ROLE_DEVICE) { > + usb_gadget_vbus_connect(&ci->gadget); > + ci->role = USB_ROLE_DEVICE; > + } else if (ci->target_role == USB_ROLE_HOST) { > + ci_role_start(ci, USB_ROLE_HOST); > + } > + } > + > pm_runtime_put_sync(ci->dev); > > enable_irq(ci->irq); > -- > 2.7.4