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@xxxxxx> > --- > 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-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html