diff options
Diffstat (limited to 'drivers/usb/common/usb-otg-fsm.c')
-rw-r--r-- | drivers/usb/common/usb-otg-fsm.c | 142 |
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"); + |