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 <b47624@xxxxxxxxxxxxx> --- 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html