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 'b_sess_vld'. Signed-off-by: Roger Quadros <rogerq@xxxxxx> --- drivers/usb/common/usb-otg.c | 178 +++++++++++++++++++++++++++++++++++++++++-- include/linux/usb/otg-fsm.h | 5 ++ include/linux/usb/otg.h | 2 + 3 files changed, 177 insertions(+), 8 deletions(-) diff --git a/drivers/usb/common/usb-otg.c b/drivers/usb/common/usb-otg.c index 930c2fe..44b5577 100644 --- a/drivers/usb/common/usb-otg.c +++ b/drivers/usb/common/usb-otg.c @@ -519,14 +519,165 @@ int usb_otg_start_gadget(struct otg_fsm *fsm, int on) } EXPORT_SYMBOL_GPL(usb_otg_start_gadget); +/* Change USB protocol when there is a protocol change */ +static int drd_set_protocol(struct otg_fsm *fsm, int protocol) +{ + struct usb_otg *otgd = container_of(fsm, struct usb_otg, 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 usb_otg *otgd = container_of(fsm, struct usb_otg, 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->b_sess_vld + */ +static int drd_statemachine(struct otg_fsm *fsm) +{ + struct usb_otg *otgd = container_of(fsm, struct usb_otg, 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->b_sess_vld) + 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->b_sess_vld) + 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->b_sess_vld) + drd_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_A_HOST: + if (fsm->id && fsm->b_sess_vld) + drd_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else if (fsm->id && !fsm->b_sess_vld) + 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 usb_otg *otgd = container_of(work, struct usb_otg, work); - otg_statemachine(&otgd->fsm); + /* OTG state machine */ + if (!otgd->drd_only) { + otg_statemachine(&otgd->fsm); + return; + } + + /* DRD state machine */ + drd_statemachine(&otgd->fsm); } /** @@ -584,13 +735,22 @@ struct otg_fsm *usb_otg_register(struct device *dev, goto err_wq; } - usb_otg_init_timers(otgd, config->otg_timeouts); + if (!(otgd->caps->hnp_support || otgd->caps->srp_support || + otgd->caps->adp_support)) + otgd->drd_only = true; /* create copy of original ops */ otgd->fsm_ops = *config->fsm_ops; - /* 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; + + /* For DRD mode we don't need OTG timers */ + if (!otgd->drd_only) { + usb_otg_init_timers(otgd, config->otg_timeouts); + + /* 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; @@ -703,8 +863,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 75e82cc..48a6aea 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. + * @b_sess_vld: VBUS voltage in regulation. + * * OTG hardware Inputs * * Common inputs for A and B device diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h index 38cabe0..18de812 100644 --- a/include/linux/usb/otg.h +++ b/include/linux/usb/otg.h @@ -74,6 +74,7 @@ struct otg_timer { * @work: otg state machine work * @wq: otg state machine work queue * @fsm_running: state machine running/stopped indicator + * @drd_only: dual-role mode. no otg features. */ struct usb_otg { u8 default_a; @@ -102,6 +103,7 @@ struct usb_otg { struct workqueue_struct *wq; bool fsm_running; /* use otg->fsm.lock for serializing access */ + bool drd_only; /*------------- deprecated interface -----------------------------*/ /* bind/unbind the host controller */ -- 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