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 <rog...@ti.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to