diff options
Diffstat (limited to 'drivers/usb/chipidea/udc.c')
-rw-r--r-- | drivers/usb/chipidea/udc.c | 262 |
1 files changed, 204 insertions, 58 deletions
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 2fbc67ca47d4..6f1839a260c2 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -423,7 +423,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) hwreq->req.status = -EALREADY; - ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir); + ret = usb_gadget_map_request_by_dev(ci->dev->parent, + &hwreq->req, hwep->dir); if (ret) return ret; @@ -603,7 +604,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) list_del_init(&node->td); } - usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir); + usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent, + &hwreq->req, hwep->dir); hwreq->req.actual += actual; @@ -750,6 +752,11 @@ __acquires(ci->lock) { int retval; + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_srp_reqd = 0; + ci->fsm.otg_hnp_reqd = 0; + } + spin_unlock(&ci->lock); if (ci->gadget.speed != USB_SPEED_UNKNOWN) usb_gadget_udc_reset(&ci->gadget, ci->driver); @@ -873,7 +880,10 @@ __acquires(hwep->lock) return -ENOMEM; req->complete = isr_get_status_complete; - req->length = 2; + if (setup->wIndex == OTG_STS_SELECTOR) + req->length = 1; + else + req->length = 2; req->buf = kzalloc(req->length, gfp_flags); if (req->buf == NULL) { retval = -ENOMEM; @@ -881,8 +891,16 @@ __acquires(hwep->lock) } if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - *(u16 *)req->buf = (ci->remote_wakeup << 1) | - ci->gadget.is_selfpowered; + if ((setup->wIndex == OTG_STS_SELECTOR) && + ci_otg_is_fsm_mode(ci)) { + if (ci->gadget.host_request_flag) + *(u8 *)req->buf = HOST_REQUEST_FLAG; + else + *(u8 *)req->buf = 0; + } else { + *(u16 *)req->buf = (ci->remote_wakeup << 1) | + ci->gadget.is_selfpowered; + } } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -1007,6 +1025,28 @@ static int otg_a_alt_hnp_support(struct ci_hdrc *ci) return isr_setup_status_phase(ci); } +static int otg_srp_reqd(struct ci_hdrc *ci) +{ + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_srp_reqd = 1; + return isr_setup_status_phase(ci); + } else { + return -ENOTSUPP; + } +} + +static int otg_hnp_reqd(struct ci_hdrc *ci) +{ + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_hnp_reqd = 1; + ci->fsm.b_bus_req = 1; + ci->gadget.host_request_flag = 1; + return isr_setup_status_phase(ci); + } else { + return -ENOTSUPP; + } +} + /** * isr_setup_packet_handler: setup packet handler * @ci: UDC descriptor @@ -1077,8 +1117,9 @@ __acquires(ci->lock) type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && type != (USB_DIR_IN|USB_RECIP_INTERFACE)) goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) + if ((le16_to_cpu(req.wLength) != 2 && + le16_to_cpu(req.wLength) != 1) || + le16_to_cpu(req.wValue) != 0) break; err = isr_get_status_response(ci, &req); break; @@ -1129,6 +1170,12 @@ __acquires(ci->lock) err = isr_setup_status_phase( ci); break; + case TEST_OTG_SRP_REQD: + err = otg_srp_reqd(ci); + break; + case TEST_OTG_HNP_REQD: + err = otg_hnp_reqd(ci); + break; default: break; } @@ -1539,27 +1586,19 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) gadget_ready = 1; spin_unlock_irqrestore(&ci->lock, flags); - if (gadget_ready) { - if (is_active) { - pm_runtime_get_sync(&_gadget->dev); - hw_device_reset(ci); - hw_device_state(ci, ci->ep0out->qh.dma); - usb_gadget_set_state(_gadget, USB_STATE_POWERED); - usb_udc_vbus_handler(_gadget, true); - } else { - usb_udc_vbus_handler(_gadget, false); - if (ci->driver) - ci->driver->disconnect(&ci->gadget); - hw_device_state(ci, 0); - if (ci->platdata->notify_event) - ci->platdata->notify_event(ci, - CI_HDRC_CONTROLLER_STOPPED_EVENT); - _gadget_stop_activity(&ci->gadget); - pm_runtime_put_sync(&_gadget->dev); - usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED); - } + /* Charger Detection */ + ci_usb_charger_connect(ci, is_active); + + if (ci->usb_phy) { + if (is_active) + usb_phy_set_event(ci->usb_phy, USB_EVENT_VBUS); + else + usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE); } + if (gadget_ready) + ci_hdrc_gadget_connect(_gadget, is_active); + return 0; } @@ -1761,7 +1800,6 @@ static int ci_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) { struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); - unsigned long flags; int retval = -ENOMEM; if (driver->disconnect == NULL) @@ -1781,25 +1819,14 @@ static int ci_udc_start(struct usb_gadget *gadget, ci->driver = driver; /* Start otg fsm for B-device */ - if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { - ci_hdrc_otg_fsm_start(ci); + if (ci_otg_is_fsm_mode(ci)) { + if (ci->fsm.id) + ci_hdrc_otg_fsm_start(ci); return retval; } - pm_runtime_get_sync(&ci->gadget.dev); - if (ci->vbus_active) { - spin_lock_irqsave(&ci->lock, flags); - hw_device_reset(ci); - } else { - usb_udc_vbus_handler(&ci->gadget, false); - pm_runtime_put_sync(&ci->gadget.dev); - return retval; - } - - retval = hw_device_state(ci, ci->ep0out->qh.dma); - spin_unlock_irqrestore(&ci->lock, flags); - if (retval) - pm_runtime_put_sync(&ci->gadget.dev); + if (ci->vbus_active) + ci_hdrc_gadget_connect(&ci->gadget, 1); return retval; } @@ -1884,27 +1911,35 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci) if (USBi_PCI & intr) { ci->gadget.speed = hw_port_is_high_speed(ci) ? USB_SPEED_HIGH : USB_SPEED_FULL; - if (ci->suspended && ci->driver->resume) { - spin_unlock(&ci->lock); - ci->driver->resume(&ci->gadget); - spin_lock(&ci->lock); + if (ci->usb_phy) + usb_phy_set_event(ci->usb_phy, + USB_EVENT_ENUMERATED); + if (ci->suspended) { + if (ci->driver->resume) { + spin_unlock(&ci->lock); + ci->driver->resume(&ci->gadget); + spin_lock(&ci->lock); + } ci->suspended = 0; + usb_gadget_set_state(&ci->gadget, + ci->resume_state); } } if (USBi_UI & intr) isr_tr_complete_handler(ci); - if (USBi_SLI & intr) { + if ((USBi_SLI & intr) && !(ci->suspended)) { + ci->suspended = 1; + ci->resume_state = ci->gadget.state; if (ci->gadget.speed != USB_SPEED_UNKNOWN && ci->driver->suspend) { - ci->suspended = 1; spin_unlock(&ci->lock); ci->driver->suspend(&ci->gadget); - usb_gadget_set_state(&ci->gadget, - USB_STATE_SUSPENDED); spin_lock(&ci->lock); } + usb_gadget_set_state(&ci->gadget, + USB_STATE_SUSPENDED); } retval = IRQ_HANDLED; } else { @@ -1938,13 +1973,13 @@ static int udc_start(struct ci_hdrc *ci) INIT_LIST_HEAD(&ci->gadget.ep_list); /* alloc resources */ - ci->qh_pool = dma_pool_create("ci_hw_qh", dev, + ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent, sizeof(struct ci_hw_qh), 64, CI_HDRC_PAGE_SIZE); if (ci->qh_pool == NULL) return -ENOMEM; - ci->td_pool = dma_pool_create("ci_hw_td", dev, + ci->td_pool = dma_pool_create("ci_hw_td", dev->parent, sizeof(struct ci_hw_td), 64, CI_HDRC_PAGE_SIZE); if (ci->td_pool == NULL) { @@ -1994,10 +2029,74 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) dma_pool_destroy(ci->qh_pool); } +int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active) +{ + int ret = 0; + + if (is_active) + pm_runtime_get_sync(ci->dev); + + if (ci->platdata->notify_event) { + if (is_active) + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + ret = ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_VBUS_EVENT); + if (ret == CI_HDRC_NOTIFY_RET_DEFER_EVENT) { + hw_device_reset(ci); + /* Pull up dp */ + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_CHARGER_POST_EVENT); + /* Pull down dp */ + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + } + } + + if (!is_active) + pm_runtime_put_sync(ci->dev); + + return ret; +} + +/** + * ci_hdrc_gadget_connect: caller make sure gadget driver is binded + */ +void ci_hdrc_gadget_connect(struct usb_gadget *gadget, int is_active) +{ + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); + + if (is_active) { + pm_runtime_get_sync(&gadget->dev); + hw_device_reset(ci); + hw_device_state(ci, ci->ep0out->qh.dma); + usb_gadget_set_state(gadget, USB_STATE_POWERED); + usb_udc_vbus_handler(gadget, true); + } else { + usb_udc_vbus_handler(gadget, false); + if (ci->driver) + ci->driver->disconnect(gadget); + hw_device_state(ci, 0); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(gadget); + pm_runtime_put_sync(&gadget->dev); + usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); + } +} + static int udc_id_switch_for_device(struct ci_hdrc *ci) { - if (ci->is_otg) - /* Clear and enable BSV irq */ + if (!ci->is_otg) + return 0; + + /* + * Clear and enable BSV irq for A-device switch to B-device + * (in otg fsm mode, means A_IDLE->B_DILE) due to ID change. + */ + if (!ci_otg_is_fsm_mode(ci) || + ci->fsm.otg->state == OTG_STATE_A_IDLE) hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, OTGSC_BSVIS | OTGSC_BSVIE); @@ -2006,12 +2105,57 @@ static int udc_id_switch_for_device(struct ci_hdrc *ci) static void udc_id_switch_for_host(struct ci_hdrc *ci) { + if (!ci->is_otg) + return; + /* - * host doesn't care B_SESSION_VALID event - * so clear and disbale BSV irq + * Clear and disbale BSV irq for B-device switch to A-device + * (in otg fsm mode, means B_IDLE->A_IDLE) due to ID change. */ - if (ci->is_otg) + if (!ci_otg_is_fsm_mode(ci) || + ci->fsm.otg->state == OTG_STATE_B_IDLE || + ci->fsm.otg->state == OTG_STATE_UNDEFINED) hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); + + ci->vbus_active = 0; +} + +static void udc_suspend_for_power_lost(struct ci_hdrc *ci) +{ + /* + * Set OP_ENDPTLISTADDR to be non-zero for + * checking if controller resume from power lost + * in non-host mode. + */ + if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0) + hw_write(ci, OP_ENDPTLISTADDR, ~0, ~0); +} + +/* Power lost with device mode */ +static void udc_resume_from_power_lost(struct ci_hdrc *ci) +{ + if (ci->is_otg) + hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, + OTGSC_BSVIS | OTGSC_BSVIE); +} + +static void udc_suspend(struct ci_hdrc *ci) +{ + udc_suspend_for_power_lost(ci); + + if (ci->driver && ci->vbus_active && + (ci->gadget.state != USB_STATE_SUSPENDED)) + usb_gadget_disconnect(&ci->gadget); +} + +static void udc_resume(struct ci_hdrc *ci, bool power_lost) +{ + if (power_lost) { + udc_resume_from_power_lost(ci); + } else { + if (ci->driver && ci->vbus_active) + usb_gadget_connect(&ci->gadget); + } } /** @@ -2035,6 +2179,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci) rdrv->start = udc_id_switch_for_device; rdrv->stop = udc_id_switch_for_host; rdrv->irq = udc_irq; + rdrv->suspend = udc_suspend; + rdrv->resume = udc_resume; rdrv->name = "gadget"; ret = udc_start(ci); |