[v3,10/11] usb: otg: Add dual-role device (DRD) support
diff mbox

Message ID 1436350777-28056-11-git-send-email-rogerq@ti.com
State New
Headers show

Commit Message

Roger Quadros July 8, 2015, 10:19 a.m. UTC
DRD mode is a reduced functionality OTG mode. In this mode
we don't support SRP, HNP and dynamic role-swap.

In DRD operation, the controller mode (Host or Peripheral)
is decided based on the ID pin status. Once a cable plug (Type-A
or Type-B) is attached the controller selects the state
and doesn't change till the cable in unplugged and a different
cable type is inserted.

As we don't need most of the complex OTG states and OTG timers
we implement a lean DRD state machine in usb-otg.c.
The DRD state machine is only interested in 2 hardware inputs
'id' and 'vbus; that are still passed via the origintal struct otg_fsm.

Most of the usb-otg.c functionality remains the same except
adding a new parameter to usb_otg_register() to indicate that
the OTG controller needs to operate in DRD mode.

Signed-off-by: Roger Quadros <rogerq@ti.com>
---
 drivers/usb/common/usb-otg.c | 179 ++++++++++++++++++++++++++++++++++++++++---
 include/linux/usb/otg-fsm.h  |   8 +-
 include/linux/usb/otg.h      |   5 +-
 3 files changed, 180 insertions(+), 12 deletions(-)

Comments

Li Jun July 17, 2015, 8:14 a.m. UTC | #1
Hi,

On Wed, Jul 08, 2015 at 01:19:36PM +0300, Roger Quadros wrote:
> DRD mode is a reduced functionality OTG mode. In this mode
> we don't support SRP, HNP and dynamic role-swap.
> 
> In DRD operation, the controller mode (Host or Peripheral)
> is decided based on the ID pin status. Once a cable plug (Type-A
> or Type-B) is attached the controller selects the state
> and doesn't change till the cable in unplugged and a different
> cable type is inserted.
> 
> As we don't need most of the complex OTG states and OTG timers
> we implement a lean DRD state machine in usb-otg.c.
> The DRD state machine is only interested in 2 hardware inputs
> 'id' and 'vbus; that are still passed via the origintal struct otg_fsm.
> 
> Most of the usb-otg.c functionality remains the same except
> adding a new parameter to usb_otg_register() to indicate that
> the OTG controller needs to operate in DRD mode.
> 
> Signed-off-by: Roger Quadros <rogerq@ti.com>
> ---
>  drivers/usb/common/usb-otg.c | 179 ++++++++++++++++++++++++++++++++++++++++---
>  include/linux/usb/otg-fsm.h  |   8 +-
>  include/linux/usb/otg.h      |   5 +-
>  3 files changed, 180 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/usb/common/usb-otg.c b/drivers/usb/common/usb-otg.c
> index 1f19001..9b89f4b 100644
> --- a/drivers/usb/common/usb-otg.c
> +++ b/drivers/usb/common/usb-otg.c
> @@ -44,6 +44,7 @@ struct otg_hcd {
>  struct otg_data {
>  	struct device *dev;	/* HCD & GCD's parent device */
>  
> +	bool drd_only;		/* Dual-role only, no OTG features */

After we introduce otg caps, we can use hnp_support to judge it it's
drd or OTG.

>  	struct otg_fsm fsm;
>  	/* HCD, GCD and usb_otg_state are present in otg_fsm->otg
>  	 * HCD is bus_to_hcd(fsm->otg->host)
> @@ -272,20 +273,172 @@ static int usb_otg_start_gadget(struct otg_fsm *fsm, int on)
>  	return 0;
>  }
>  
> +/* Change USB protocol when there is a protocol change */
> +static int drd_set_protocol(struct otg_fsm *fsm, int protocol)
> +{
> +	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
> +	int ret = 0;
> +
> +	if (fsm->protocol != protocol) {
> +		dev_dbg(otgd->dev, "otg: changing role fsm->protocol= %d; new protocol= %d\n",
> +			fsm->protocol, protocol);
> +		/* stop old protocol */
> +		if (fsm->protocol == PROTO_HOST)
> +			ret = otg_start_host(fsm, 0);
> +		else if (fsm->protocol == PROTO_GADGET)
> +			ret = otg_start_gadget(fsm, 0);
> +		if (ret)
> +			return ret;
> +
> +		/* start new protocol */
> +		if (protocol == PROTO_HOST)
> +			ret = otg_start_host(fsm, 1);
> +		else if (protocol == PROTO_GADGET)
> +			ret = otg_start_gadget(fsm, 1);
> +		if (ret)
> +			return ret;
> +
> +		fsm->protocol = protocol;
> +		return 0;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Called when entering a DRD state */
> +static void drd_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
> +{
> +	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
> +
> +	if (fsm->otg->state == new_state)
> +		return;
> +
> +	fsm->state_changed = 1;
> +	dev_dbg(otgd->dev, "otg: set state: %s\n",
> +		usb_otg_state_string(new_state));
> +	switch (new_state) {
> +	case OTG_STATE_B_IDLE:

otg_drv_vbus(fsm, 0);

> +		drd_set_protocol(fsm, PROTO_UNDEF);
> +		break;
> +	case OTG_STATE_B_PERIPHERAL:

otg_drv_vbus(fsm, 0);

> +		drd_set_protocol(fsm, PROTO_GADGET);
> +		break;
> +	case OTG_STATE_A_HOST:

otg_drv_vbus(fsm, 1);

> +		drd_set_protocol(fsm, PROTO_HOST);
> +		break;
> +	case OTG_STATE_UNDEFINED:
> +	case OTG_STATE_B_SRP_INIT:
> +	case OTG_STATE_B_WAIT_ACON:
> +	case OTG_STATE_B_HOST:
> +	case OTG_STATE_A_IDLE:
> +	case OTG_STATE_A_WAIT_VRISE:
> +	case OTG_STATE_A_WAIT_BCON:
> +	case OTG_STATE_A_SUSPEND:
> +	case OTG_STATE_A_PERIPHERAL:
> +	case OTG_STATE_A_WAIT_VFALL:
> +	case OTG_STATE_A_VBUS_ERR:
> +	default:
> +		dev_warn(otgd->dev, "%s: otg: invalid state: %s\n",
> +			 __func__, usb_otg_state_string(new_state));
> +		break;
> +	}
> +
> +	fsm->otg->state = new_state;
> +}
> +
>  /**
> - * OTG FSM work function
> + * DRD state change judgement
> + *
> + * For DRD we're only interested in some of the OTG states
> + * i.e. OTG_STATE_B_IDLE: both peripheral and host are stopped
> + *	OTG_STATE_B_PERIPHERAL: peripheral active
> + *	OTG_STATE_A_HOST: host active
> + * we're only interested in the following inputs
> + *	fsm->id, fsm->vbus
> + */
> +static int drd_statemachine(struct otg_fsm *fsm)
> +{
> +	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
> +	enum usb_otg_state state;
> +
> +	mutex_lock(&fsm->lock);
> +
> +	state = fsm->otg->state;
> +
> +	switch (state) {
> +	case OTG_STATE_UNDEFINED:
> +		if (!fsm->id)
> +			drd_set_state(fsm, OTG_STATE_A_HOST);
> +		else if (fsm->id && fsm->vbus)
> +			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
> +		else
> +			drd_set_state(fsm, OTG_STATE_B_IDLE);
> +		break;
> +	case OTG_STATE_B_IDLE:
> +		if (!fsm->id)
> +			drd_set_state(fsm, OTG_STATE_A_HOST);
> +		else if (fsm->vbus)
> +			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
> +		break;
> +	case OTG_STATE_B_PERIPHERAL:
> +		if (!fsm->id)
> +			drd_set_state(fsm, OTG_STATE_A_HOST);
> +		else if (!fsm->vbus)
> +			drd_set_state(fsm, OTG_STATE_B_IDLE);
> +		break;
> +	case OTG_STATE_A_HOST:
> +		if (fsm->id && fsm->vbus)
> +			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
> +		else if (fsm->id && !fsm->vbus)
> +			drd_set_state(fsm, OTG_STATE_B_IDLE);
> +		break;
> +
> +	/* invalid states for DRD */
> +	case OTG_STATE_B_SRP_INIT:
> +	case OTG_STATE_B_WAIT_ACON:
> +	case OTG_STATE_B_HOST:
> +	case OTG_STATE_A_IDLE:
> +	case OTG_STATE_A_WAIT_VRISE:
> +	case OTG_STATE_A_WAIT_BCON:
> +	case OTG_STATE_A_SUSPEND:
> +	case OTG_STATE_A_PERIPHERAL:
> +	case OTG_STATE_A_WAIT_VFALL:
> +	case OTG_STATE_A_VBUS_ERR:
> +		dev_err(otgd->dev, "%s: otg: invalid usb-drd state: %s\n",
> +			__func__, usb_otg_state_string(state));
> +		drd_set_state(fsm, OTG_STATE_UNDEFINED);
> +	break;
> +	}
> +
> +	mutex_unlock(&fsm->lock);
> +	dev_dbg(otgd->dev, "otg: quit statemachine, changed %d\n",
> +		fsm->state_changed);
> +
> +	return fsm->state_changed;
> +}
> +
> +/**
> + * OTG FSM/DRD work function
>   */
>  static void usb_otg_work(struct work_struct *work)
>  {
>  	struct otg_data *otgd = container_of(work, struct otg_data, work);
>  
> -	otg_statemachine(&otgd->fsm);
> +	/* OTG state machine */
> +	if (!otgd->drd_only) {
> +		otg_statemachine(&otgd->fsm);
> +		return;
> +	}
> +
> +	/* DRD state machine */
> +	drd_statemachine(&otgd->fsm);
>  }
>  
>  /**
>   * usb_otg_register() - Register the OTG device to OTG core
>   * @parent_device:	parent device of Host & Gadget controllers.
>   * @otg_fsm_ops:	otg state machine ops.
> + * @drd_only:		dual-role only. no OTG features.
>   *
>   * Register parent device that contains both HCD and GCD into
>   * the USB OTG core. HCD and GCD will be prevented from starting
> @@ -294,7 +447,8 @@ static void usb_otg_work(struct work_struct *work)
>   * Return: struct otg_fsm * if success, NULL if error.
>   */
>  struct otg_fsm *usb_otg_register(struct device *parent_dev,
> -				 struct otg_fsm_ops *fsm_ops)
> +				 struct otg_fsm_ops *fsm_ops,
> +				 bool drd_only)
>  {
>  	struct otg_data *otgd;
>  	int ret = 0;
> @@ -328,7 +482,15 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev,
>  		goto err_wq;
>  	}
>  
> -	usb_otg_init_timers(otgd);
> +	otgd->drd_only = drd_only;
> +	/* For DRD mode we don't need OTG timers */
> +	if (!drd_only) {
> +		usb_otg_init_timers(otgd);
> +
> +		/* FIXME: we ignore caller's timer ops */
> +		otgd->fsm_ops.add_timer = usb_otg_add_timer;
> +		otgd->fsm_ops.del_timer = usb_otg_del_timer;
> +	}
>  
>  	/* save original start host/gadget ops */
>  	otgd->start_host = fsm_ops->start_host;
> @@ -338,9 +500,6 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev,
>  	/* override ops */
>  	otgd->fsm_ops.start_host = usb_otg_start_host;
>  	otgd->fsm_ops.start_gadget = usb_otg_start_gadget;
> -	/* FIXME: we ignore caller's timer ops */
> -	otgd->fsm_ops.add_timer = usb_otg_add_timer;
> -	otgd->fsm_ops.del_timer = usb_otg_del_timer;
>  	/* set otg ops */
>  	otgd->fsm.ops = &otgd->fsm_ops;
>  	otgd->fsm.otg = &otgd->otg;
> @@ -443,8 +602,10 @@ static void usb_otg_stop_fsm(struct otg_fsm *fsm)
>  	otgd->fsm_running = false;
>  
>  	/* Stop state machine / timers */
> -	for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
> -		hrtimer_cancel(&otgd->timers[i].timer);
> +	if (!otgd->drd_only) {
> +		for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
> +			hrtimer_cancel(&otgd->timers[i].timer);
> +	}
>  
>  	flush_workqueue(otgd->wq);
>  	fsm->otg->state = OTG_STATE_UNDEFINED;
> diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h
> index 22d8baa..ae9c30a 100644
> --- a/include/linux/usb/otg-fsm.h
> +++ b/include/linux/usb/otg-fsm.h
> @@ -48,6 +48,11 @@ enum otg_fsm_timer {
>  /**
>   * struct otg_fsm - OTG state machine according to the OTG spec
>   *
> + * DRD mode hardware Inputs
> + *
> + * @id:		TRUE for B-device, FALSE for A-device.
> + * @vbus:	VBUS voltage in regulation.
> + *
>   * OTG hardware Inputs
>   *
>   *	Common inputs for A and B device
> @@ -122,7 +127,8 @@ enum otg_fsm_timer {
>   */
>  struct otg_fsm {
>  	/* Input */
> -	int id;
> +	int id;			/* DRD + OTG */
> +	int vbus;		/* DRD only */
>  	int adp_change;
>  	int power_up;
>  	int a_srp_det;
> diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
> index ce6f8d8..1086a0b 100644
> --- a/include/linux/usb/otg.h
> +++ b/include/linux/usb/otg.h
> @@ -58,7 +58,7 @@ enum usb_dr_mode {
>  
>  #if IS_ENABLED(CONFIG_USB_OTG)
>  struct otg_fsm *usb_otg_register(struct device *parent_dev,
> -				 struct otg_fsm_ops *fsm_ops);
> +				 struct otg_fsm_ops *fsm_ops, bool drd_only);
>  int usb_otg_unregister(struct device *parent_dev);
>  int usb_otg_register_hcd(struct usb_hcd *hcd, unsigned int irqnum,
>  			 unsigned long irqflags, struct otg_hcd_ops *ops);
> @@ -73,7 +73,8 @@ struct device *usb_otg_fsm_to_dev(struct otg_fsm *fsm);
>  #else /* CONFIG_USB_OTG */
>  
>  static inline struct otg_fsm *usb_otg_register(struct device *parent_dev,
> -					       struct otg_fsm_ops *fsm_ops)
> +					       struct otg_fsm_ops *fsm_ops,
> +					       bool drd_only)
>  {
>  	return ERR_PTR(-ENOSYS);
>  }
> -- 
> 2.1.4
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Li Jun July 17, 2015, 9:02 a.m. UTC | #2
On Wed, Jul 08, 2015 at 01:19:36PM +0300, Roger Quadros wrote:

[...]

>  struct otg_fsm *usb_otg_register(struct device *parent_dev,
> -				 struct otg_fsm_ops *fsm_ops)
> +				 struct otg_fsm_ops *fsm_ops,
> +				 bool drd_only)
>  {
>  	struct otg_data *otgd;
>  	int ret = 0;
> @@ -328,7 +482,15 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev,
>  		goto err_wq;
>  	}
>  
> -	usb_otg_init_timers(otgd);
> +	otgd->drd_only = drd_only;
> +	/* For DRD mode we don't need OTG timers */
> +	if (!drd_only) {
> +		usb_otg_init_timers(otgd);
> +
> +		/* FIXME: we ignore caller's timer ops */
> +		otgd->fsm_ops.add_timer = usb_otg_add_timer;
> +		otgd->fsm_ops.del_timer = usb_otg_del_timer;
> +	}
>  
>  	/* save original start host/gadget ops */
>  	otgd->start_host = fsm_ops->start_host;
> @@ -338,9 +500,6 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev,

Your above override will be override back to be fsm_ops's by below copy:

	/* create copy of original ops */
	otgd->fsm_ops = *fsm_ops;

So add/del_timer must be override after the copy.

>  	/* override ops */
>  	otgd->fsm_ops.start_host = usb_otg_start_host;
>  	otgd->fsm_ops.start_gadget = usb_otg_start_gadget;
> -	/* FIXME: we ignore caller's timer ops */
> -	otgd->fsm_ops.add_timer = usb_otg_add_timer;
> -	otgd->fsm_ops.del_timer = usb_otg_del_timer;
>  	/* set otg ops */
>  	otgd->fsm.ops = &otgd->fsm_ops;
>  	otgd->fsm.otg = &otgd->otg;
> @@ -443,8 +602,10 @@ static void usb_otg_stop_fsm(struct otg_fsm *fsm)
>  	otgd->fsm_running = false;
>  
>  	/* Stop state machine / timers */
> -	for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
> -		hrtimer_cancel(&otgd->timers[i].timer);
> +	if (!otgd->drd_only) {
> +		for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
> +			hrtimer_cancel(&otgd->timers[i].timer);
> +	}
>  
>  	flush_workqueue(otgd->wq);
>  	fsm->otg->state = OTG_STATE_UNDEFINED;
> diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h
> index 22d8baa..ae9c30a 100644
> --- a/include/linux/usb/otg-fsm.h
> +++ b/include/linux/usb/otg-fsm.h
> @@ -48,6 +48,11 @@ enum otg_fsm_timer {
>  /**
>   * struct otg_fsm - OTG state machine according to the OTG spec
>   *
> + * DRD mode hardware Inputs
> + *
> + * @id:		TRUE for B-device, FALSE for A-device.
> + * @vbus:	VBUS voltage in regulation.
> + *
>   * OTG hardware Inputs
>   *
>   *	Common inputs for A and B device
> @@ -122,7 +127,8 @@ enum otg_fsm_timer {
>   */
>  struct otg_fsm {
>  	/* Input */
> -	int id;
> +	int id;			/* DRD + OTG */
> +	int vbus;		/* DRD only */

Existing b_sess_vld can be also used for drd only case, no need create
a new flag.

>  	int adp_change;
>  	int power_up;
>  	int a_srp_det;
> diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
> index ce6f8d8..1086a0b 100644
> --- a/include/linux/usb/otg.h
> +++ b/include/linux/usb/otg.h
> @@ -58,7 +58,7 @@ enum usb_dr_mode {
>  
>  #if IS_ENABLED(CONFIG_USB_OTG)
>  struct otg_fsm *usb_otg_register(struct device *parent_dev,
> -				 struct otg_fsm_ops *fsm_ops);
> +				 struct otg_fsm_ops *fsm_ops, bool drd_only);
>  int usb_otg_unregister(struct device *parent_dev);
>  int usb_otg_register_hcd(struct usb_hcd *hcd, unsigned int irqnum,
>  			 unsigned long irqflags, struct otg_hcd_ops *ops);
> @@ -73,7 +73,8 @@ struct device *usb_otg_fsm_to_dev(struct otg_fsm *fsm);
>  #else /* CONFIG_USB_OTG */
>  
>  static inline struct otg_fsm *usb_otg_register(struct device *parent_dev,
> -					       struct otg_fsm_ops *fsm_ops)
> +					       struct otg_fsm_ops *fsm_ops,
> +					       bool drd_only)
>  {
>  	return ERR_PTR(-ENOSYS);
>  }
> -- 
> 2.1.4
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Roger Quadros July 17, 2015, 10:41 a.m. UTC | #3
On 17/07/15 11:14, Li Jun wrote:
> Hi,
> 
> On Wed, Jul 08, 2015 at 01:19:36PM +0300, Roger Quadros wrote:
>> DRD mode is a reduced functionality OTG mode. In this mode
>> we don't support SRP, HNP and dynamic role-swap.
>>
>> In DRD operation, the controller mode (Host or Peripheral)
>> is decided based on the ID pin status. Once a cable plug (Type-A
>> or Type-B) is attached the controller selects the state
>> and doesn't change till the cable in unplugged and a different
>> cable type is inserted.
>>
>> As we don't need most of the complex OTG states and OTG timers
>> we implement a lean DRD state machine in usb-otg.c.
>> The DRD state machine is only interested in 2 hardware inputs
>> 'id' and 'vbus; that are still passed via the origintal struct otg_fsm.
>>
>> Most of the usb-otg.c functionality remains the same except
>> adding a new parameter to usb_otg_register() to indicate that
>> the OTG controller needs to operate in DRD mode.
>>
>> Signed-off-by: Roger Quadros <rogerq@ti.com>
>> ---
>>  drivers/usb/common/usb-otg.c | 179 ++++++++++++++++++++++++++++++++++++++++---
>>  include/linux/usb/otg-fsm.h  |   8 +-
>>  include/linux/usb/otg.h      |   5 +-
>>  3 files changed, 180 insertions(+), 12 deletions(-)
>>
>> diff --git a/drivers/usb/common/usb-otg.c b/drivers/usb/common/usb-otg.c
>> index 1f19001..9b89f4b 100644
>> --- a/drivers/usb/common/usb-otg.c
>> +++ b/drivers/usb/common/usb-otg.c
>> @@ -44,6 +44,7 @@ struct otg_hcd {
>>  struct otg_data {
>>  	struct device *dev;	/* HCD & GCD's parent device */
>>  
>> +	bool drd_only;		/* Dual-role only, no OTG features */
> 
> After we introduce otg caps, we can use hnp_support to judge it it's
> drd or OTG.

Yes. I will rebase this series on top of your otg_caps patches
and incorporate it.

> 
>>  	struct otg_fsm fsm;
>>  	/* HCD, GCD and usb_otg_state are present in otg_fsm->otg
>>  	 * HCD is bus_to_hcd(fsm->otg->host)
>> @@ -272,20 +273,172 @@ static int usb_otg_start_gadget(struct otg_fsm *fsm, int on)
>>  	return 0;
>>  }
>>  
>> +/* Change USB protocol when there is a protocol change */
>> +static int drd_set_protocol(struct otg_fsm *fsm, int protocol)
>> +{
>> +	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
>> +	int ret = 0;
>> +
>> +	if (fsm->protocol != protocol) {
>> +		dev_dbg(otgd->dev, "otg: changing role fsm->protocol= %d; new protocol= %d\n",
>> +			fsm->protocol, protocol);
>> +		/* stop old protocol */
>> +		if (fsm->protocol == PROTO_HOST)
>> +			ret = otg_start_host(fsm, 0);
>> +		else if (fsm->protocol == PROTO_GADGET)
>> +			ret = otg_start_gadget(fsm, 0);
>> +		if (ret)
>> +			return ret;
>> +
>> +		/* start new protocol */
>> +		if (protocol == PROTO_HOST)
>> +			ret = otg_start_host(fsm, 1);
>> +		else if (protocol == PROTO_GADGET)
>> +			ret = otg_start_gadget(fsm, 1);
>> +		if (ret)
>> +			return ret;
>> +
>> +		fsm->protocol = protocol;
>> +		return 0;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +/* Called when entering a DRD state */
>> +static void drd_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
>> +{
>> +	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
>> +
>> +	if (fsm->otg->state == new_state)
>> +		return;
>> +
>> +	fsm->state_changed = 1;
>> +	dev_dbg(otgd->dev, "otg: set state: %s\n",
>> +		usb_otg_state_string(new_state));
>> +	switch (new_state) {
>> +	case OTG_STATE_B_IDLE:
> 
> otg_drv_vbus(fsm, 0);
> 
>> +		drd_set_protocol(fsm, PROTO_UNDEF);
>> +		break;
>> +	case OTG_STATE_B_PERIPHERAL:
> 
> otg_drv_vbus(fsm, 0);
> 
>> +		drd_set_protocol(fsm, PROTO_GADGET);
>> +		break;
>> +	case OTG_STATE_A_HOST:
> 
> otg_drv_vbus(fsm, 1);

OK.

> 
>> +		drd_set_protocol(fsm, PROTO_HOST);
>> +		break;
>> +	case OTG_STATE_UNDEFINED:
>> +	case OTG_STATE_B_SRP_INIT:
>> +	case OTG_STATE_B_WAIT_ACON:
>> +	case OTG_STATE_B_HOST:
>> +	case OTG_STATE_A_IDLE:
>> +	case OTG_STATE_A_WAIT_VRISE:
>> +	case OTG_STATE_A_WAIT_BCON:
>> +	case OTG_STATE_A_SUSPEND:
>> +	case OTG_STATE_A_PERIPHERAL:
>> +	case OTG_STATE_A_WAIT_VFALL:
>> +	case OTG_STATE_A_VBUS_ERR:
>> +	default:
>> +		dev_warn(otgd->dev, "%s: otg: invalid state: %s\n",
>> +			 __func__, usb_otg_state_string(new_state));
>> +		break;
>> +	}
>> +
>> +	fsm->otg->state = new_state;
>> +}
>> +

cheers,
-roger
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Roger Quadros July 17, 2015, 10:47 a.m. UTC | #4
On 17/07/15 12:02, Li Jun wrote:
> On Wed, Jul 08, 2015 at 01:19:36PM +0300, Roger Quadros wrote:
> 
> [...]
> 
>>  struct otg_fsm *usb_otg_register(struct device *parent_dev,
>> -				 struct otg_fsm_ops *fsm_ops)
>> +				 struct otg_fsm_ops *fsm_ops,
>> +				 bool drd_only)
>>  {
>>  	struct otg_data *otgd;
>>  	int ret = 0;
>> @@ -328,7 +482,15 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev,
>>  		goto err_wq;
>>  	}
>>  
>> -	usb_otg_init_timers(otgd);
>> +	otgd->drd_only = drd_only;
>> +	/* For DRD mode we don't need OTG timers */
>> +	if (!drd_only) {
>> +		usb_otg_init_timers(otgd);
>> +
>> +		/* FIXME: we ignore caller's timer ops */
>> +		otgd->fsm_ops.add_timer = usb_otg_add_timer;
>> +		otgd->fsm_ops.del_timer = usb_otg_del_timer;
>> +	}
>>  
>>  	/* save original start host/gadget ops */
>>  	otgd->start_host = fsm_ops->start_host;
>> @@ -338,9 +500,6 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev,
> 
> Your above override will be override back to be fsm_ops's by below copy:
> 
> 	/* create copy of original ops */
> 	otgd->fsm_ops = *fsm_ops;
> 
> So add/del_timer must be override after the copy.

Good catch :).

> 
>>  	/* override ops */
>>  	otgd->fsm_ops.start_host = usb_otg_start_host;
>>  	otgd->fsm_ops.start_gadget = usb_otg_start_gadget;
>> -	/* FIXME: we ignore caller's timer ops */
>> -	otgd->fsm_ops.add_timer = usb_otg_add_timer;
>> -	otgd->fsm_ops.del_timer = usb_otg_del_timer;
>>  	/* set otg ops */
>>  	otgd->fsm.ops = &otgd->fsm_ops;
>>  	otgd->fsm.otg = &otgd->otg;
>> @@ -443,8 +602,10 @@ static void usb_otg_stop_fsm(struct otg_fsm *fsm)
>>  	otgd->fsm_running = false;
>>  
>>  	/* Stop state machine / timers */
>> -	for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
>> -		hrtimer_cancel(&otgd->timers[i].timer);
>> +	if (!otgd->drd_only) {
>> +		for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
>> +			hrtimer_cancel(&otgd->timers[i].timer);
>> +	}
>>  
>>  	flush_workqueue(otgd->wq);
>>  	fsm->otg->state = OTG_STATE_UNDEFINED;
>> diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h
>> index 22d8baa..ae9c30a 100644
>> --- a/include/linux/usb/otg-fsm.h
>> +++ b/include/linux/usb/otg-fsm.h
>> @@ -48,6 +48,11 @@ enum otg_fsm_timer {
>>  /**
>>   * struct otg_fsm - OTG state machine according to the OTG spec
>>   *
>> + * DRD mode hardware Inputs
>> + *
>> + * @id:		TRUE for B-device, FALSE for A-device.
>> + * @vbus:	VBUS voltage in regulation.
>> + *
>>   * OTG hardware Inputs
>>   *
>>   *	Common inputs for A and B device
>> @@ -122,7 +127,8 @@ enum otg_fsm_timer {
>>   */
>>  struct otg_fsm {
>>  	/* Input */
>> -	int id;
>> +	int id;			/* DRD + OTG */
>> +	int vbus;		/* DRD only */
> 
> Existing b_sess_vld can be also used for drd only case, no need create
> a new flag.

b_sess_vld is a bit confusing to people not familiar with OTG.
My suggestion is to use dedicated 'vbus' flag for DRD case
for simplicity.

> 
>>  	int adp_change;
>>  	int power_up;
>>  	int a_srp_det;

cheers,
-roger
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Peter Chen July 20, 2015, 1:23 a.m. UTC | #5
On Fri, Jul 17, 2015 at 01:47:12PM +0300, Roger Quadros wrote:
> >> + * DRD mode hardware Inputs
> >> + *
> >> + * @id:		TRUE for B-device, FALSE for A-device.
> >> + * @vbus:	VBUS voltage in regulation.
> >> + *
> >>   * OTG hardware Inputs
> >>   *
> >>   *	Common inputs for A and B device
> >> @@ -122,7 +127,8 @@ enum otg_fsm_timer {
> >>   */
> >>  struct otg_fsm {
> >>  	/* Input */
> >> -	int id;
> >> +	int id;			/* DRD + OTG */
> >> +	int vbus;		/* DRD only */
> > 
> > Existing b_sess_vld can be also used for drd only case, no need create
> > a new flag.
> 
> b_sess_vld is a bit confusing to people not familiar with OTG.
> My suggestion is to use dedicated 'vbus' flag for DRD case
> for simplicity.
> 

Since OTG DRD is the subset in OTG FSM (FSM, data structure, APIs, etc),
I agree with Jun to reuse existing variables, and we can add some comments
for b_sess_vld if needed.


> > 
> >>  	int adp_change;
> >>  	int power_up;
> >>  	int a_srp_det;
> 
> cheers,
> -roger
Roger Quadros July 27, 2015, 9:31 a.m. UTC | #6
On 20/07/15 04:23, Peter Chen wrote:
> On Fri, Jul 17, 2015 at 01:47:12PM +0300, Roger Quadros wrote:
>>>> + * DRD mode hardware Inputs
>>>> + *
>>>> + * @id:		TRUE for B-device, FALSE for A-device.
>>>> + * @vbus:	VBUS voltage in regulation.
>>>> + *
>>>>   * OTG hardware Inputs
>>>>   *
>>>>   *	Common inputs for A and B device
>>>> @@ -122,7 +127,8 @@ enum otg_fsm_timer {
>>>>   */
>>>>  struct otg_fsm {
>>>>  	/* Input */
>>>> -	int id;
>>>> +	int id;			/* DRD + OTG */
>>>> +	int vbus;		/* DRD only */
>>>
>>> Existing b_sess_vld can be also used for drd only case, no need create
>>> a new flag.
>>
>> b_sess_vld is a bit confusing to people not familiar with OTG.
>> My suggestion is to use dedicated 'vbus' flag for DRD case
>> for simplicity.
>>
> 
> Since OTG DRD is the subset in OTG FSM (FSM, data structure, APIs, etc),
> I agree with Jun to reuse existing variables, and we can add some comments
> for b_sess_vld if needed.

OK then. I'll get rid of vbus and use b_sess_vld.

cheers,
-roger

> 
> 
>>>
>>>>  	int adp_change;
>>>>  	int power_up;
>>>>  	int a_srp_det;
>>
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch
diff mbox

diff --git a/drivers/usb/common/usb-otg.c b/drivers/usb/common/usb-otg.c
index 1f19001..9b89f4b 100644
--- a/drivers/usb/common/usb-otg.c
+++ b/drivers/usb/common/usb-otg.c
@@ -44,6 +44,7 @@  struct otg_hcd {
 struct otg_data {
 	struct device *dev;	/* HCD & GCD's parent device */
 
+	bool drd_only;		/* Dual-role only, no OTG features */
 	struct otg_fsm fsm;
 	/* HCD, GCD and usb_otg_state are present in otg_fsm->otg
 	 * HCD is bus_to_hcd(fsm->otg->host)
@@ -272,20 +273,172 @@  static int usb_otg_start_gadget(struct otg_fsm *fsm, int on)
 	return 0;
 }
 
+/* Change USB protocol when there is a protocol change */
+static int drd_set_protocol(struct otg_fsm *fsm, int protocol)
+{
+	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
+	int ret = 0;
+
+	if (fsm->protocol != protocol) {
+		dev_dbg(otgd->dev, "otg: changing role fsm->protocol= %d; new protocol= %d\n",
+			fsm->protocol, protocol);
+		/* stop old protocol */
+		if (fsm->protocol == PROTO_HOST)
+			ret = otg_start_host(fsm, 0);
+		else if (fsm->protocol == PROTO_GADGET)
+			ret = otg_start_gadget(fsm, 0);
+		if (ret)
+			return ret;
+
+		/* start new protocol */
+		if (protocol == PROTO_HOST)
+			ret = otg_start_host(fsm, 1);
+		else if (protocol == PROTO_GADGET)
+			ret = otg_start_gadget(fsm, 1);
+		if (ret)
+			return ret;
+
+		fsm->protocol = protocol;
+		return 0;
+	}
+
+	return 0;
+}
+
+/* Called when entering a DRD state */
+static void drd_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
+{
+	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
+
+	if (fsm->otg->state == new_state)
+		return;
+
+	fsm->state_changed = 1;
+	dev_dbg(otgd->dev, "otg: set state: %s\n",
+		usb_otg_state_string(new_state));
+	switch (new_state) {
+	case OTG_STATE_B_IDLE:
+		drd_set_protocol(fsm, PROTO_UNDEF);
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		drd_set_protocol(fsm, PROTO_GADGET);
+		break;
+	case OTG_STATE_A_HOST:
+		drd_set_protocol(fsm, PROTO_HOST);
+		break;
+	case OTG_STATE_UNDEFINED:
+	case OTG_STATE_B_SRP_INIT:
+	case OTG_STATE_B_WAIT_ACON:
+	case OTG_STATE_B_HOST:
+	case OTG_STATE_A_IDLE:
+	case OTG_STATE_A_WAIT_VRISE:
+	case OTG_STATE_A_WAIT_BCON:
+	case OTG_STATE_A_SUSPEND:
+	case OTG_STATE_A_PERIPHERAL:
+	case OTG_STATE_A_WAIT_VFALL:
+	case OTG_STATE_A_VBUS_ERR:
+	default:
+		dev_warn(otgd->dev, "%s: otg: invalid state: %s\n",
+			 __func__, usb_otg_state_string(new_state));
+		break;
+	}
+
+	fsm->otg->state = new_state;
+}
+
 /**
- * OTG FSM work function
+ * DRD state change judgement
+ *
+ * For DRD we're only interested in some of the OTG states
+ * i.e. OTG_STATE_B_IDLE: both peripheral and host are stopped
+ *	OTG_STATE_B_PERIPHERAL: peripheral active
+ *	OTG_STATE_A_HOST: host active
+ * we're only interested in the following inputs
+ *	fsm->id, fsm->vbus
+ */
+static int drd_statemachine(struct otg_fsm *fsm)
+{
+	struct otg_data *otgd = container_of(fsm, struct otg_data, fsm);
+	enum usb_otg_state state;
+
+	mutex_lock(&fsm->lock);
+
+	state = fsm->otg->state;
+
+	switch (state) {
+	case OTG_STATE_UNDEFINED:
+		if (!fsm->id)
+			drd_set_state(fsm, OTG_STATE_A_HOST);
+		else if (fsm->id && fsm->vbus)
+			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+		else
+			drd_set_state(fsm, OTG_STATE_B_IDLE);
+		break;
+	case OTG_STATE_B_IDLE:
+		if (!fsm->id)
+			drd_set_state(fsm, OTG_STATE_A_HOST);
+		else if (fsm->vbus)
+			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		if (!fsm->id)
+			drd_set_state(fsm, OTG_STATE_A_HOST);
+		else if (!fsm->vbus)
+			drd_set_state(fsm, OTG_STATE_B_IDLE);
+		break;
+	case OTG_STATE_A_HOST:
+		if (fsm->id && fsm->vbus)
+			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+		else if (fsm->id && !fsm->vbus)
+			drd_set_state(fsm, OTG_STATE_B_IDLE);
+		break;
+
+	/* invalid states for DRD */
+	case OTG_STATE_B_SRP_INIT:
+	case OTG_STATE_B_WAIT_ACON:
+	case OTG_STATE_B_HOST:
+	case OTG_STATE_A_IDLE:
+	case OTG_STATE_A_WAIT_VRISE:
+	case OTG_STATE_A_WAIT_BCON:
+	case OTG_STATE_A_SUSPEND:
+	case OTG_STATE_A_PERIPHERAL:
+	case OTG_STATE_A_WAIT_VFALL:
+	case OTG_STATE_A_VBUS_ERR:
+		dev_err(otgd->dev, "%s: otg: invalid usb-drd state: %s\n",
+			__func__, usb_otg_state_string(state));
+		drd_set_state(fsm, OTG_STATE_UNDEFINED);
+	break;
+	}
+
+	mutex_unlock(&fsm->lock);
+	dev_dbg(otgd->dev, "otg: quit statemachine, changed %d\n",
+		fsm->state_changed);
+
+	return fsm->state_changed;
+}
+
+/**
+ * OTG FSM/DRD work function
  */
 static void usb_otg_work(struct work_struct *work)
 {
 	struct otg_data *otgd = container_of(work, struct otg_data, work);
 
-	otg_statemachine(&otgd->fsm);
+	/* OTG state machine */
+	if (!otgd->drd_only) {
+		otg_statemachine(&otgd->fsm);
+		return;
+	}
+
+	/* DRD state machine */
+	drd_statemachine(&otgd->fsm);
 }
 
 /**
  * usb_otg_register() - Register the OTG device to OTG core
  * @parent_device:	parent device of Host & Gadget controllers.
  * @otg_fsm_ops:	otg state machine ops.
+ * @drd_only:		dual-role only. no OTG features.
  *
  * Register parent device that contains both HCD and GCD into
  * the USB OTG core. HCD and GCD will be prevented from starting
@@ -294,7 +447,8 @@  static void usb_otg_work(struct work_struct *work)
  * Return: struct otg_fsm * if success, NULL if error.
  */
 struct otg_fsm *usb_otg_register(struct device *parent_dev,
-				 struct otg_fsm_ops *fsm_ops)
+				 struct otg_fsm_ops *fsm_ops,
+				 bool drd_only)
 {
 	struct otg_data *otgd;
 	int ret = 0;
@@ -328,7 +482,15 @@  struct otg_fsm *usb_otg_register(struct device *parent_dev,
 		goto err_wq;
 	}
 
-	usb_otg_init_timers(otgd);
+	otgd->drd_only = drd_only;
+	/* For DRD mode we don't need OTG timers */
+	if (!drd_only) {
+		usb_otg_init_timers(otgd);
+
+		/* FIXME: we ignore caller's timer ops */
+		otgd->fsm_ops.add_timer = usb_otg_add_timer;
+		otgd->fsm_ops.del_timer = usb_otg_del_timer;
+	}
 
 	/* save original start host/gadget ops */
 	otgd->start_host = fsm_ops->start_host;
@@ -338,9 +500,6 @@  struct otg_fsm *usb_otg_register(struct device *parent_dev,
 	/* override ops */
 	otgd->fsm_ops.start_host = usb_otg_start_host;
 	otgd->fsm_ops.start_gadget = usb_otg_start_gadget;
-	/* FIXME: we ignore caller's timer ops */
-	otgd->fsm_ops.add_timer = usb_otg_add_timer;
-	otgd->fsm_ops.del_timer = usb_otg_del_timer;
 	/* set otg ops */
 	otgd->fsm.ops = &otgd->fsm_ops;
 	otgd->fsm.otg = &otgd->otg;
@@ -443,8 +602,10 @@  static void usb_otg_stop_fsm(struct otg_fsm *fsm)
 	otgd->fsm_running = false;
 
 	/* Stop state machine / timers */
-	for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
-		hrtimer_cancel(&otgd->timers[i].timer);
+	if (!otgd->drd_only) {
+		for (i = 0; i < ARRAY_SIZE(otgd->timers); i++)
+			hrtimer_cancel(&otgd->timers[i].timer);
+	}
 
 	flush_workqueue(otgd->wq);
 	fsm->otg->state = OTG_STATE_UNDEFINED;
diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h
index 22d8baa..ae9c30a 100644
--- a/include/linux/usb/otg-fsm.h
+++ b/include/linux/usb/otg-fsm.h
@@ -48,6 +48,11 @@  enum otg_fsm_timer {
 /**
  * struct otg_fsm - OTG state machine according to the OTG spec
  *
+ * DRD mode hardware Inputs
+ *
+ * @id:		TRUE for B-device, FALSE for A-device.
+ * @vbus:	VBUS voltage in regulation.
+ *
  * OTG hardware Inputs
  *
  *	Common inputs for A and B device
@@ -122,7 +127,8 @@  enum otg_fsm_timer {
  */
 struct otg_fsm {
 	/* Input */
-	int id;
+	int id;			/* DRD + OTG */
+	int vbus;		/* DRD only */
 	int adp_change;
 	int power_up;
 	int a_srp_det;
diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
index ce6f8d8..1086a0b 100644
--- a/include/linux/usb/otg.h
+++ b/include/linux/usb/otg.h
@@ -58,7 +58,7 @@  enum usb_dr_mode {
 
 #if IS_ENABLED(CONFIG_USB_OTG)
 struct otg_fsm *usb_otg_register(struct device *parent_dev,
-				 struct otg_fsm_ops *fsm_ops);
+				 struct otg_fsm_ops *fsm_ops, bool drd_only);
 int usb_otg_unregister(struct device *parent_dev);
 int usb_otg_register_hcd(struct usb_hcd *hcd, unsigned int irqnum,
 			 unsigned long irqflags, struct otg_hcd_ops *ops);
@@ -73,7 +73,8 @@  struct device *usb_otg_fsm_to_dev(struct otg_fsm *fsm);
 #else /* CONFIG_USB_OTG */
 
 static inline struct otg_fsm *usb_otg_register(struct device *parent_dev,
-					       struct otg_fsm_ops *fsm_ops)
+					       struct otg_fsm_ops *fsm_ops,
+					       bool drd_only)
 {
 	return ERR_PTR(-ENOSYS);
 }