lkml.org 
[lkml]   [2015]   [Aug]   [24]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v4 13/13] usb: otg: Add dual-role device (DRD) support
    Date
    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@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


    \
     
     \ /
      Last update: 2015-08-24 15:41    [W:4.435 / U:0.072 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site