summaryrefslogtreecommitdiff
path: root/drivers/usb/common/usb-otg-fsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/common/usb-otg-fsm.c')
-rw-r--r--drivers/usb/common/usb-otg-fsm.c142
1 files changed, 135 insertions, 7 deletions
diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c
index 4f4f06a5889f..015a7cd3db91 100644
--- a/drivers/usb/common/usb-otg-fsm.c
+++ b/drivers/usb/common/usb-otg-fsm.c
@@ -1,7 +1,7 @@
/*
* OTG Finite State Machine from OTG spec
*
- * Copyright (C) 2007,2008 Freescale Semiconductor, Inc.
+ * Copyright (C) 2007-2015 Freescale Semiconductor, Inc.
*
* Author: Li Yang <LeoLi@freescale.com>
* Jerry Huang <Chang-Ming.Huang@freescale.com>
@@ -70,6 +70,7 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
switch (old_state) {
case OTG_STATE_B_IDLE:
otg_del_timer(fsm, B_SE0_SRP);
+ otg_del_timer(fsm, B_SRP_FAIL);
fsm->b_se0_srp = 0;
fsm->adp_sns = 0;
fsm->adp_prb = 0;
@@ -79,12 +80,19 @@ 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);
fsm->b_ase0_brst_tmout = 0;
break;
case OTG_STATE_B_HOST:
+ if (fsm->otg_hnp_reqd) {
+ fsm->otg_hnp_reqd = 0;
+ fsm->b_bus_req = 0;
+ }
+ fsm->a_conn = 0;
break;
case OTG_STATE_A_IDLE:
fsm->adp_prb = 0;
@@ -108,6 +116,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);
@@ -142,6 +152,10 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
otg_start_adp_sns(fsm);
otg_set_protocol(fsm, PROTO_UNDEF);
otg_add_timer(fsm, B_SE0_SRP);
+ if (fsm->otg_hnp_reqd) {
+ fsm->otg_hnp_reqd = 0;
+ fsm->b_bus_req = 0;
+ }
break;
case OTG_STATE_B_SRP_INIT:
otg_start_pulse(fsm);
@@ -154,6 +168,7 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
otg_loc_sof(fsm, 0);
otg_set_protocol(fsm, PROTO_GADGET);
otg_loc_conn(fsm, 1);
+ fsm->b_bus_req = 0;
break;
case OTG_STATE_B_WAIT_ACON:
otg_chrg_vbus(fsm, 0);
@@ -168,8 +183,7 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
otg_loc_conn(fsm, 0);
otg_loc_sof(fsm, 1);
otg_set_protocol(fsm, PROTO_HOST);
- usb_bus_start_enum(fsm->otg->host,
- fsm->otg->host->otg_port);
+ otg_add_timer(fsm, HNP_POLLING);
break;
case OTG_STATE_A_IDLE:
otg_drv_vbus(fsm, 0);
@@ -204,6 +218,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_add_timer(fsm, HNP_POLLING);
break;
case OTG_STATE_A_SUSPEND:
otg_drv_vbus(fsm, 1);
@@ -321,8 +336,7 @@ int otg_statemachine(struct otg_fsm *fsm)
case OTG_STATE_A_HOST:
if (fsm->id || fsm->a_bus_drop)
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
- else if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) &&
- fsm->otg->host->b_hnp_enable)
+ else if (!fsm->a_bus_req || fsm->a_suspend_req_inf)
otg_set_state(fsm, OTG_STATE_A_SUSPEND);
else if (!fsm->b_conn)
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
@@ -330,9 +344,9 @@ int otg_statemachine(struct otg_fsm *fsm)
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
break;
case OTG_STATE_A_SUSPEND:
- if (!fsm->b_conn && fsm->otg->host->b_hnp_enable)
+ if (!fsm->b_conn && fsm->a_set_b_hnp_en)
otg_set_state(fsm, OTG_STATE_A_PERIPHERAL);
- else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable)
+ else if (!fsm->b_conn && !fsm->a_set_b_hnp_en)
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
else if (fsm->a_bus_req || fsm->b_bus_resume)
otg_set_state(fsm, OTG_STATE_A_HOST);
@@ -366,4 +380,118 @@ int otg_statemachine(struct otg_fsm *fsm)
return state_changed;
}
EXPORT_SYMBOL_GPL(otg_statemachine);
+
+static int otg_handle_role_switch(struct otg_fsm *fsm, struct usb_device *udev)
+{
+ int err;
+ enum usb_otg_state state = fsm->otg->state;
+
+ if (state == OTG_STATE_A_HOST) {
+ /* Set b_hnp_enable */
+ if (!fsm->a_set_b_hnp_en) {
+ err = 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 (err >= 0)
+ fsm->a_set_b_hnp_en = 1;
+ }
+ fsm->a_bus_req = 0;
+ if (fsm->tst_maint) {
+ fsm->tst_maint = 0;
+ fsm->otg_vbus_off = 0;
+ otg_del_timer(fsm, A_TST_MAINT);
+ }
+ return HOST_REQUEST_FLAG;
+ } else if (state == OTG_STATE_B_HOST) {
+ fsm->b_bus_req = 0;
+ return HOST_REQUEST_FLAG;
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Called by host to poll peripheral if it wants to be host
+ * Return value:
+ * - host request flag(1) if the device wants to be host,
+ * - host request flag(0) if the device keeps peripheral role,
+ * - otherwise, error code.
+ */
+int otg_hnp_polling(struct otg_fsm *fsm)
+{
+ struct usb_device *udev;
+ int retval;
+ enum usb_otg_state state = fsm->otg->state;
+ struct usb_otg_descriptor *desc = NULL;
+ u8 host_request_flag;
+
+ if ((state != OTG_STATE_A_HOST || !fsm->b_hnp_enable) &&
+ state != OTG_STATE_B_HOST)
+ return -EINVAL;
+
+ 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 -ENODEV;
+ }
+
+ if (udev->state != USB_STATE_CONFIGURED) {
+ dev_dbg(&udev->dev, "the B dev is not resumed!\n");
+ otg_add_timer(fsm, HNP_POLLING);
+ return -EPERM;
+ }
+
+ /*
+ * Legacy otg test device does not support HNP polling,
+ * start HNP directly for legacy otg test device.
+ */
+ if (fsm->tst_maint &&
+ (__usb_get_extra_descriptor(udev->rawdescriptors[0],
+ le16_to_cpu(udev->config[0].desc.wTotalLength),
+ USB_DT_OTG, (void **) &desc) == 0)) {
+ /* shorter bLength of OTG 1.3 or earlier */
+ if (desc->bLength < 5) {
+ fsm->a_bus_req = 0;
+ fsm->tst_maint = 0;
+ otg_del_timer(fsm, A_TST_MAINT);
+ return HOST_REQUEST_FLAG;
+ }
+ }
+
+ *fsm->host_req_flag = 0;
+ /* 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,
+ fsm->host_req_flag,
+ 1,
+ USB_CTRL_GET_TIMEOUT);
+ if (retval == 1) {
+ host_request_flag = *fsm->host_req_flag;
+ if (host_request_flag == HOST_REQUEST_FLAG) {
+ retval = otg_handle_role_switch(fsm, udev);
+ } else if (host_request_flag == 0) {
+ /* Continue polling */
+ otg_add_timer(fsm, HNP_POLLING);
+ retval = 0;
+ } else {
+ dev_err(&udev->dev, "host request flag is invalid\n");
+ retval = -EINVAL;
+ }
+ } else {
+ dev_warn(&udev->dev, "Get one byte OTG status failed\n");
+ retval = -EIO;
+ }
+ return retval;
+}
+EXPORT_SYMBOL_GPL(otg_hnp_polling);
+
MODULE_LICENSE("GPL");
+