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/usb-otg.h | 5 +- 3 files changed, 179 insertions(+), 13 deletions(-) diff --git a/drivers/usb/common/usb-otg.c b/drivers/usb/common/usb-otg.c index 860e2e7..6d6da86 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) @@ -251,22 +252,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, "drd: 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, "drd: 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: drd: 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: drd: 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, "drd: 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); - if (otg_statemachine(&otgd->fsm)) { - /* state changed. any action ? */ + /* 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 @@ -275,7 +426,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; @@ -309,7 +461,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; @@ -319,9 +479,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; @@ -424,8 +581,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 73136aa..44666aa 100644 --- a/include/linux/usb/otg-fsm.h +++ b/include/linux/usb/otg-fsm.h @@ -45,6 +45,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 @@ -110,7 +115,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/usb-otg.h b/include/linux/usb/usb-otg.h index 8987cd1..cc06a94 100644 --- a/include/linux/usb/usb-otg.h +++ b/include/linux/usb/usb-otg.h @@ -25,7 +25,7 @@ #if IS_ENABLED(CONFIG_USB_OTG_CORE) 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); @@ -40,7 +40,8 @@ struct device *usb_otg_fsm_to_dev(struct otg_fsm *fsm); #else /* CONFIG_USB_OTG_CORE */ 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.0 -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html