This patch add OTG HNP polling support for both A and B device.
After A/B in host state, host request polling message will be sent
from host to peripheral every 1.5s, if host found the host request
flag is set to be 1 by peripheral, a role switch will be started.

Signed-off-by: Li Jun <[email protected]>
---
 drivers/usb/chipidea/ci.h      |    2 +
 drivers/usb/chipidea/otg_fsm.c |   80 ++++++++++++++++++++++++++++++++++++++++
 drivers/usb/chipidea/otg_fsm.h |    3 +
 drivers/usb/chipidea/udc.c     |   11 ++++-
 drivers/usb/phy/phy-fsm-usb.c  |    6 +++
 include/linux/usb/gadget.h     |    1 +
 include/linux/usb/otg-fsm.h    |   13 ++++++
 7 files changed, 114 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 0f1abc1..1780945 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -135,6 +135,7 @@ struct hw_bank {
  * @id_event: indicates there is an id event, and handled at ci_otg_work
  * @b_sess_valid_event: indicates there is a vbus event, and handled
  * at ci_otg_work
+ * @hnp_polling_event: indicate HNP polling request should be sent out
  */
 struct ci_hdrc {
        struct device                   *dev;
@@ -174,6 +175,7 @@ struct ci_hdrc {
        struct dentry                   *debugfs;
        bool                            id_event;
        bool                            b_sess_valid_event;
+       bool                            hnp_polling_event;
 };
 
 static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
index cfdfebd..60465ab 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -475,6 +475,76 @@ int ci_otg_start_gadget(struct otg_fsm *fsm, int on)
        return 0;
 }
 
+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 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;
+}
+
 static struct otg_fsm_ops ci_otg_ops = {
        .chrg_vbus = ci_otg_chrg_vbus,
        .drv_vbus = ci_otg_drv_vbus,
@@ -482,6 +552,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,
@@ -495,6 +566,13 @@ 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)
@@ -723,6 +801,8 @@ 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 994e4c9..812efaa 100644
--- a/drivers/usb/chipidea/otg_fsm.h
+++ b/drivers/usb/chipidea/otg_fsm.h
@@ -76,6 +76,9 @@
 #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 */
+
 struct ci_otg_fsm_timer {
        unsigned long expires;  /* Number of count increase to timeout */
        unsigned long count;    /* Tick counter */
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index ccdc277..69c290a 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -844,8 +844,15 @@ __acquires(hwep->lock)
        }
 
        if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) {
-               /* Assume that device is bus powered for now. */
-               *(u16 *)req->buf = ci->remote_wakeup << 1;
+               if ((setup->wIndex == OTG_STS_SELECTOR) &&
+                               (gadget_is_otg(&ci->gadget))) {
+                       if (ci->gadget.host_request_flag)
+                               *(u16 *)req->buf = 1;
+                       else
+                               *(u16 *)req->buf = 0;
+               } else
+                       /* Assume that device is bus powered for now. */
+                       *(u16 *)req->buf = ci->remote_wakeup << 1;
                retval = 0;
        } else if ((setup->bRequestType & USB_RECIP_MASK) \
                   == USB_RECIP_ENDPOINT) {
diff --git a/drivers/usb/phy/phy-fsm-usb.c b/drivers/usb/phy/phy-fsm-usb.c
index c47e5a6..293f35f 100644
--- a/drivers/usb/phy/phy-fsm-usb.c
+++ b/drivers/usb/phy/phy-fsm-usb.c
@@ -84,6 +84,8 @@ static void otg_leave_state(struct otg_fsm *fsm, enum 
usb_otg_state old_state)
                fsm->b_ase0_brst_tmout = 0;
                break;
        case OTG_STATE_B_HOST:
+               if (fsm->otg->gadget)
+                       fsm->otg->gadget->host_request_flag = 0;
                break;
        case OTG_STATE_A_IDLE:
                fsm->adp_prb = 0;
@@ -98,6 +100,8 @@ static void otg_leave_state(struct otg_fsm *fsm, enum 
usb_otg_state old_state)
                break;
        case OTG_STATE_A_HOST:
                otg_del_timer(fsm, A_WAIT_ENUM);
+               if (fsm->otg->gadget)
+                       fsm->otg->gadget->host_request_flag = 0;
                break;
        case OTG_STATE_A_SUSPEND:
                otg_del_timer(fsm, A_AIDL_BDIS);
@@ -169,6 +173,7 @@ static int otg_set_state(struct otg_fsm *fsm, enum 
usb_otg_state new_state)
                otg_set_protocol(fsm, PROTO_HOST);
                usb_bus_start_enum(fsm->otg->host,
                                fsm->otg->host->otg_port);
+               otg_start_hnp_polling(fsm);
                break;
        case OTG_STATE_A_IDLE:
                otg_drv_vbus(fsm, 0);
@@ -203,6 +208,7 @@ static int otg_set_state(struct otg_fsm *fsm, enum 
usb_otg_state new_state)
                 */
                if (!fsm->a_bus_req || fsm->a_suspend_req_inf)
                        otg_add_timer(fsm, A_WAIT_ENUM);
+               otg_start_hnp_polling(fsm);
                break;
        case OTG_STATE_A_SUSPEND:
                otg_drv_vbus(fsm, 1);
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
index c3a6185..93aae2f 100644
--- a/include/linux/usb/gadget.h
+++ b/include/linux/usb/gadget.h
@@ -563,6 +563,7 @@ struct usb_gadget {
        unsigned                        a_hnp_support:1;
        unsigned                        a_alt_hnp_support:1;
        unsigned                        quirk_ep_out_aligned_size:1;
+       unsigned                        host_request_flag:1;
 };
 #define work_to_gadget(w)      (container_of((w), struct usb_gadget, work))
 
diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h
index b6ba1bf..571d2f2 100644
--- a/include/linux/usb/otg-fsm.h
+++ b/include/linux/usb/otg-fsm.h
@@ -40,6 +40,9 @@
 #define PROTO_HOST     (1)
 #define PROTO_GADGET   (2)
 
+#define OTG_STS_SELECTOR               0xF000
+#define HOST_REQUEST_FLAG              1
+
 enum otg_fsm_timer {
        /* Standard OTG timers */
        A_WAIT_VRISE,
@@ -113,6 +116,7 @@ struct otg_fsm {
 
        struct otg_fsm_ops *ops;
        struct usb_otg *otg;
+       struct timer_list hnp_polling_timer;
 
        /* Current usb protocol used: 0:undefine; 1:host; 2:client */
        int protocol;
@@ -127,6 +131,7 @@ struct otg_fsm_ops {
        void    (*start_pulse)(struct otg_fsm *fsm);
        void    (*start_adp_prb)(struct otg_fsm *fsm);
        void    (*start_adp_sns)(struct otg_fsm *fsm);
+       void    (*start_hnp_polling)(struct otg_fsm *fsm);
        void    (*add_timer)(struct otg_fsm *fsm, enum otg_fsm_timer timer);
        void    (*del_timer)(struct otg_fsm *fsm, enum otg_fsm_timer timer);
        int     (*start_host)(struct otg_fsm *fsm, int on);
@@ -209,6 +214,14 @@ static inline int otg_start_adp_sns(struct otg_fsm *fsm)
        return 0;
 }
 
+static inline int otg_start_hnp_polling(struct otg_fsm *fsm)
+{
+       if (!fsm->ops->start_hnp_polling)
+               return -EOPNOTSUPP;
+       fsm->ops->start_hnp_polling(fsm);
+       return 0;
+}
+
 static inline int otg_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer timer)
 {
        if (!fsm->ops->add_timer)
-- 
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