[PATCH 08/10] usb: chipidea: add OTG HNP polling support.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux