diff mbox series

[v2,2/2] usb: chipidea: add role switch class support

Message ID 20190807072342.9700-2-jun.li@nxp.com (mailing list archive)
State New, archived
Headers show
Series [v2,1/2] usb: chipidea: replace ci_role with usb_role | expand

Commit Message

Jun Li Aug. 7, 2019, 7:23 a.m. UTC
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 parter(USB_ROLE_NONE).

Signed-off-by: Li Jun <jun.li@nxp.com>
---

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(-)

Comments

Peter Chen Aug. 8, 2019, 9:30 a.m. UTC | #1
> 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@nxp.com>
> ---
> 
> 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
Jun Li Aug. 9, 2019, 7:29 a.m. UTC | #2
> -----Original Message-----
> From: Peter Chen
> Sent: 2019年8月8日 17:31
> To: Jun Li <jun.li@nxp.com>
> Cc: gregkh@linuxfoundation.org; Jun Li <jun.li@nxp.com>; dl-linux-imx
> <linux-imx@nxp.com>; linux-usb@vger.kernel.org
> Subject: RE: [PATCH v2 2/2] usb: chipidea: add role switch class support
> 
> 
> > 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 ?

Yes, I will update.

> 
> 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.

The new role switch class has nothing to do with extcon, it's using graph bindings
to link the connection, so there is no extcon_dev, change for ci_hdrc_probe() is
required as property usb-role-switch has to be specified, there may be some code
reuse if still touch the external cable even no extcon dev, but that will make existing
external cable complicated and not clean.

On the other hand, a new GPIO based role switch driver is on review:
https://patchwork.kernel.org/patch/11056379/
Seems this is the direction to move out from extcon.

Li Jun
> 
> Peter
> 
> > Signed-off-by: Li Jun <jun.li@nxp.com>
> > ---
> >
> > 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
Peter Chen Aug. 9, 2019, 10:11 a.m. UTC | #3
> > > 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 ?
> 
> Yes, I will update.
> 
> >
> > 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.
> 
> The new role switch class has nothing to do with extcon, it's using graph bindings to
> link the connection, so there is no extcon_dev, change for ci_hdrc_probe() is
> required as property usb-role-switch has to be specified, there may be some code
> reuse if still touch the external cable even no extcon dev, but that will make existing
> external cable complicated and not clean.
> 

I don't mean using extcon directly.

At ci_usb_role_switch_set, you could know current "id" and "vbus" status.
as well as if "id" and "vbus" has changed. So, you could update ci->id_event
or ci->vbus_event. And at otgsc_read, you could return correct vbus and id value.

So, you may not need to change main function for ci_otg_work, for ci_hdrc_probe,
you could only need to register usb-switch class.

Surely, you may need to introduce some structures or variables for usb switch, but
it could keep main structure not changing, and just differentiate the way to get
id and vbus.


> On the other hand, a new GPIO based role switch driver is on review:
> https://patchwork.kernel.org/patch/11056379/
> Seems this is the direction to move out from extcon.
> 

If external connector is given up, we need to use that.

When you update the v3, please fix the build error reported by kbuild test robot.

Peter	

> Li Jun
> >
> > Peter
> >
> > > Signed-off-by: Li Jun <jun.li@nxp.com>
> > > ---
> > >
> > > 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
diff mbox series

Patch

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);