[PATCH v4 3/8] usb: common: otg-fsm: add HNP polling support

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

 



Adds HNP polling timer when transits to host state, the OTG status request
will be sent to peripheral after timeout, if host request flag is set, it will
switch to peripheral state, otherwise it will repeat HNP polling every 1.5s and
maintain the current session.

Signed-off-by: Li Jun <jun.li@xxxxxxxxxxxxx>

Change for v4:
Add check if the connected device is a HNP capable OTG device before sending
HNP polling request.

---
 drivers/usb/common/usb-otg-fsm.c |   92 ++++++++++++++++++++++++++++++++++++++
 include/linux/usb/otg-fsm.h      |   13 ++++++
 2 files changed, 105 insertions(+)

diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c
index 61d538a..6f0d40b 100644
--- a/drivers/usb/common/usb-otg-fsm.c
+++ b/drivers/usb/common/usb-otg-fsm.c
@@ -78,6 +78,8 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
 		fsm->b_srp_done = 0;
 		break;
 	case OTG_STATE_B_PERIPHERAL:
+		if (fsm->otg->gadget)
+			fsm->otg->gadget->host_request_flag = 0;
 		break;
 	case OTG_STATE_B_WAIT_ACON:
 		otg_del_timer(fsm, B_ASE0_BRST);
@@ -107,6 +109,8 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
 	case OTG_STATE_A_PERIPHERAL:
 		otg_del_timer(fsm, A_BIDL_ADIS);
 		fsm->a_bidl_adis_tmout = 0;
+		if (fsm->otg->gadget)
+			fsm->otg->gadget->host_request_flag = 0;
 		break;
 	case OTG_STATE_A_WAIT_VFALL:
 		otg_del_timer(fsm, A_WAIT_VFALL);
@@ -120,6 +124,92 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
 	}
 }
 
+static void otg_hnp_polling_work(struct work_struct *work)
+{
+	struct otg_fsm *fsm = container_of(to_delayed_work(work),
+				struct otg_fsm, hnp_polling_work);
+	struct usb_device *udev;
+	u8 host_req_flag;
+	enum usb_otg_state state = fsm->otg->state;
+	struct usb_otg_descriptor *desc = NULL;
+	int retval;
+
+	if (state != OTG_STATE_A_HOST && state != OTG_STATE_B_HOST)
+		return;
+
+	udev = usb_hub_find_child(fsm->otg->host->root_hub, 1);
+	if (!udev) {
+		dev_err(fsm->otg->host->controller,
+			"no usb dev connected, can't start HNP polling\n");
+		return;
+	}
+
+	/* Check if it's a HNP capable OTG device */
+	retval = __usb_get_extra_descriptor(udev->rawdescriptors[0],
+			le16_to_cpu(udev->config[0].desc.wTotalLength),
+						USB_DT_OTG, (void **) &desc);
+	if (retval || !(desc->bmAttributes & USB_OTG_HNP))
+		return;
+
+	/* Get host request flag from connected USB device */
+	retval = usb_control_msg(udev,
+				usb_rcvctrlpipe(udev, 0),
+				USB_REQ_GET_STATUS,
+				USB_DIR_IN | USB_RECIP_DEVICE,
+				0,
+				OTG_STS_SELECTOR,
+				&host_req_flag,
+				1,
+				USB_CTRL_GET_TIMEOUT);
+	if (retval != 1) {
+		dev_err(&udev->dev, "Get one byte OTG status failed\n");
+		return;
+	}
+
+	if (host_req_flag == 0) {
+		/* Continue HNP polling */
+		schedule_delayed_work(&fsm->hnp_polling_work,
+					msecs_to_jiffies(T_HOST_REQ_POLL));
+		return;
+	} else if (host_req_flag != HOST_REQUEST_FLAG) {
+		dev_err(&udev->dev, "host request flag %d is invalid\n",
+							host_req_flag);
+		return;
+	}
+
+	/* Host request flag is set */
+	if (state == OTG_STATE_A_HOST) {
+		/* Set b_hnp_enable */
+		if (!fsm->otg->host->b_hnp_enable) {
+			retval = usb_control_msg(udev,
+					usb_sndctrlpipe(udev, 0),
+					USB_REQ_SET_FEATURE, 0,
+					USB_DEVICE_B_HNP_ENABLE,
+					0, NULL, 0,
+					USB_CTRL_SET_TIMEOUT);
+			if (retval < 0) {
+				dev_err(&udev->dev,
+					"can't enable HNP %d\n", retval);
+				return;
+			}
+			fsm->otg->host->b_hnp_enable = 1;
+		}
+
+		fsm->a_bus_req = 0;
+	} else if (state == OTG_STATE_B_HOST) {
+		fsm->b_bus_req = 0;
+	}
+
+	otg_statemachine(fsm);
+}
+
+static void otg_start_hnp_polling(struct otg_fsm *fsm)
+{
+	INIT_DELAYED_WORK(&fsm->hnp_polling_work, otg_hnp_polling_work);
+	schedule_delayed_work(&fsm->hnp_polling_work,
+					msecs_to_jiffies(T_HOST_REQ_POLL));
+}
+
 /* Called when entering a state */
 static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
 {
@@ -169,6 +259,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 +294,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/otg-fsm.h b/include/linux/usb/otg-fsm.h
index f728f18..2bea252 100644
--- a/include/linux/usb/otg-fsm.h
+++ b/include/linux/usb/otg-fsm.h
@@ -40,6 +40,18 @@
 #define PROTO_HOST	(1)
 #define PROTO_GADGET	(2)
 
+#define OTG_STS_SELECTOR        0xF000	/* OTG status selector, according to
+					 * OTG and EH 2.0 Chapter 6.2.3
+					 * Table:6-4
+					 */
+
+#define HOST_REQUEST_FLAG       1	/* Host request flag, according to
+					 * OTG and EH 2.0 Charpter 6.2.3
+					 * Table:6-5
+					 */
+
+#define T_HOST_REQ_POLL         (1500)	/* 1500ms, HNP polling interval */
+
 enum otg_fsm_timer {
 	/* Standard OTG timers */
 	A_WAIT_VRISE,
@@ -119,6 +131,7 @@ struct otg_fsm {
 	/* Current usb protocol used: 0:undefine; 1:host; 2:client */
 	int protocol;
 	struct mutex lock;
+	struct delayed_work hnp_polling_work;
 };
 
 struct otg_fsm_ops {
-- 
1.7.9.5

--
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