This patch implements OTG host request polling and does role switch
when peripheral sets the host request flag.

Signed-off-by: Li Jun <[email protected]>
---
 drivers/usb/chipidea/ci.h      |    1 +
 drivers/usb/chipidea/otg_fsm.c |   81 ++++++++++++++++++++++++++++++++++++++++
 drivers/usb/chipidea/otg_fsm.h |    4 ++
 3 files changed, 86 insertions(+), 0 deletions(-)

diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 8152020..8d6032e 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -205,6 +205,7 @@ struct ci_hdrc {
        struct dentry                   *debugfs;
        bool                            id_event;
        bool                            b_sess_valid_event;
+       bool                            hnp_polling_event;
        bool                            imx28_write_fix;
 };
 
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
index 613eb86..2586cdb 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -463,6 +463,14 @@ static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum 
otg_fsm_timer t)
        return;
 }
 
+static void ci_otg_start_hnp_polling(struct otg_fsm *fsm)
+{
+       mod_timer(&fsm->hnp_polling_timer,
+               jiffies + msecs_to_jiffies(T_HOST_REQ_POLL));
+
+       return;
+}
+
 static void ci_otg_chrg_vbus(struct otg_fsm *fsm, int on)
 {
        struct ci_hdrc  *ci = container_of(fsm->otg->gadget,
@@ -613,6 +621,7 @@ static struct otg_fsm_ops ci_otg_ops = {
        .loc_sof = ci_otg_loc_sof,
        .start_pulse = ci_otg_start_pulse,
        .start_adp_prb = ci_otg_start_adp_prb,
+       .start_hnp_polling = ci_otg_start_hnp_polling,
 
        .add_timer = ci_otg_fsm_add_timer,
        .del_timer = ci_otg_fsm_del_timer,
@@ -621,11 +630,80 @@ static struct otg_fsm_ops ci_otg_ops = {
        .start_gadget = ci_otg_start_gadget,
 };
 
+static void hnp_polling_timer_work(unsigned long arg)
+{
+       struct ci_hdrc *ci = (struct ci_hdrc *)arg;
+
+       ci->hnp_polling_event = true;
+       disable_irq_nosync(ci->irq);
+       queue_work(ci->wq, &ci->work);
+}
+
+static int ci_hnp_polling(struct ci_hdrc *ci)
+{
+       struct usb_device       *udev;
+       int                     err = 0;
+       u16                     data;
+
+       if (ci->transceiver->state != OTG_STATE_A_HOST
+                       && ci->transceiver->state != OTG_STATE_B_HOST)
+               return -EINVAL;
+
+       if (ci->transceiver->otg->host &&
+                       ci->transceiver->otg->host->root_hub) {
+               udev = usb_hub_find_child(
+                       ci->transceiver->otg->host->root_hub, 1);
+               if (!udev) {
+                       dev_dbg(ci->dev,
+                       "no usb dev connected, stop HNP polling\n");
+                       return -ENODEV;
+               } else if (udev->state < USB_STATE_DEFAULT) {
+                       ci_otg_start_hnp_polling(ci->fsm);
+                       return -EAGAIN;
+               }
+       } else {
+               dev_dbg(ci->dev, "no host or root_hub registered\n");
+               return -ENODEV;
+       }
+
+       /* get host request flag from connected USB device */
+       err = usb_get_status(udev, USB_RECIP_DEVICE, OTG_STS_SELECTOR, &data);
+       if (err) {
+               dev_warn(ci->dev,
+                       "ERR in HNP polling = %d, stop HNP polling\n", err);
+               return -EINVAL;
+       }
+
+       if ((data & 0xff) == HOST_REQUEST_FLAG) {
+               /* Start role switch */
+               dev_dbg(ci->dev, "host request flag = 1\n");
+               if (ci->transceiver->state == OTG_STATE_A_HOST)
+                       ci->fsm->a_bus_req = 0;
+               else if (ci->transceiver->state == OTG_STATE_B_HOST)
+                       ci->fsm->b_bus_req = 0;
+               return 0;
+       } else if ((data & 0xff) == 0) {
+               /* Continue polling */
+               ci_otg_start_hnp_polling(ci->fsm);
+               return -EAGAIN;
+       } else
+               dev_err(ci->dev, "host request flag is invalid value\n");
+
+       return err;
+}
+
 int ci_otg_fsm_work(struct ci_hdrc *ci)
 {
        if (!ci->transceiver->otg || !ci->fsm)
                return -ENODEV;
 
+       if (ci->hnp_polling_event) {
+               ci->hnp_polling_event = false;
+               if (ci_hnp_polling(ci)) {
+                       return 0;
+               }
+       }
+
        if (otg_statemachine(ci->fsm)) {
                if (ci->transceiver->state == OTG_STATE_A_IDLE) {
                        if (ci->fsm->id)
@@ -869,6 +947,9 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
                return retval;
        }
 
+       setup_timer(&ci->fsm->hnp_polling_timer, hnp_polling_timer_work,
+                                                       (unsigned long)ci);
+
        /* enable ID and A vbus valid irq */
        hw_write(ci, OP_OTGSC, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
                                                OTGSC_IDIE | OTGSC_AVVIE);
diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h
index 6cdd55c2..53f75b2 100644
--- a/drivers/usb/chipidea/otg_fsm.h
+++ b/drivers/usb/chipidea/otg_fsm.h
@@ -78,6 +78,10 @@
 #define TB_SSEND_SRP    (1500)  /* Table 5-1, Session end to SRP init */
 #define TB_SESS_VLD     (1000)
 
+#define T_HOST_REQ_POLL (1500) /* Section 6.3.1 HNP polling process
+                                * OTG and EH Revison 2.0 version 1.1a
+                                * July 27, 2012 */
+
 enum ci_otg_fsm_timer_index {
        /* CI specific timers, start from the end
         * of standard and auxiliary OTG timers */
-- 
1.7.8


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to