/** * exynos_drd_switch_set_peripheral - bind/unbind the peripheral controller driver. * * @otg: Pointer to the usb_otg structure. * @gadget: pointer to the usb_gadget structure. * * Returns 0 on success otherwise negative errno. */ static int exynos_drd_switch_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) { struct exynos_drd_switch *drd_switch = container_of(otg, struct exynos_drd_switch, otg); struct exynos_drd *drd = container_of(drd_switch->core, struct exynos_drd, core); bool activate = false; unsigned long flags; spin_lock_irqsave(&drd_switch->lock, flags); if (gadget) { dev_dbg(otg->phy->dev, "Binding gadget %s\n", gadget->name); otg->gadget = gadget; /* * Prevents unnecessary activation of the work function. * If both peripheral and host are set or if we want to force * peripheral to run then we ensure that work function will * enter to valid state. */ if (otg->host || (drd->pdata->quirks & FORCE_RUN_PERIPHERAL && drd_switch->id_state == B_DEV)) activate = true; } else { dev_dbg(otg->phy->dev, "Unbinding gadget\n"); if (otg->phy->state == OTG_STATE_B_PERIPHERAL) { exynos_drd_switch_start_peripheral(otg, 0); otg->gadget = NULL; otg->phy->state = OTG_STATE_UNDEFINED; activate = true; } else { otg->gadget = NULL; } } spin_unlock_irqrestore(&drd_switch->lock, flags); if (activate) exynos_drd_switch_schedule_work(&drd_switch->work); return 0; }
/** * exynos_drd_switch_work - work function. * * @w: Pointer to the exynos otg work structure. * * NOTE: After any change in phy->state, * we must reschdule the state machine. */ static void exynos_drd_switch_work(struct work_struct *w) { struct exynos_drd_switch *drd_switch = container_of(w, struct exynos_drd_switch, work.work); struct usb_phy *phy = drd_switch->otg.phy; struct delayed_work *work = &drd_switch->work; enum usb_otg_state state, new_state; enum id_pin_state id_state; bool vbus_active; unsigned long flags; int ret = 0; state = phy->state; new_state = state; if (atomic_read(&drd_switch->sm_reset)) { if (state == OTG_STATE_A_HOST) exynos_drd_switch_start_host(&drd_switch->otg, 0); else if (state == OTG_STATE_B_PERIPHERAL) exynos_drd_switch_start_peripheral(&drd_switch->otg, 0); new_state = OTG_STATE_UNDEFINED; goto exit; } spin_lock_irqsave(&drd_switch->lock, flags); id_state = drd_switch->id_state; vbus_active = drd_switch->vbus_active; spin_unlock_irqrestore(&drd_switch->lock, flags); /* Check OTG state */ switch (state) { case OTG_STATE_UNDEFINED: /* Switch to A or B-Device according to ID state */ if (id_state == B_DEV) new_state = OTG_STATE_B_IDLE; else if (id_state == A_DEV) new_state = OTG_STATE_A_IDLE; exynos_drd_switch_schedule_work(work); break; case OTG_STATE_B_IDLE: if (id_state == A_DEV) { new_state = OTG_STATE_A_IDLE; exynos_drd_switch_schedule_work(work); } else if (vbus_active) { /* Start peripheral only if B-Session is valid */ ret = exynos_drd_switch_start_peripheral( &drd_switch->otg, 1); if (!ret) { new_state = OTG_STATE_B_PERIPHERAL; exynos_drd_switch_schedule_work(work); } else if (ret == -EAGAIN) { exynos_drd_switch_schedule_dwork(work, EAGAIN_DELAY); } else { /* Fatal error */ dev_err(phy->dev, "unable to start B-device\n"); } } else { dev_dbg(phy->dev, "VBus is not active\n"); } break; case OTG_STATE_B_PERIPHERAL: dev_dbg(phy->dev, "OTG_STATE_B_PERIPHERAL\n"); if ((id_state == A_DEV) || !vbus_active) { exynos_drd_switch_start_peripheral(&drd_switch->otg, 0); new_state = OTG_STATE_B_IDLE; exynos_drd_switch_schedule_work(work); } break; case OTG_STATE_A_IDLE: if (id_state == B_DEV) { new_state = OTG_STATE_B_IDLE; exynos_drd_switch_schedule_work(work); } else { /* Switch to A-Device */ ret = exynos_drd_switch_start_host(&drd_switch->otg, 1); if (!ret || ret == -EINPROGRESS) { new_state = OTG_STATE_A_HOST; exynos_drd_switch_schedule_work(work); } else if (ret == -EAGAIN || ret == -EBUSY) { dev_vdbg(phy->dev, "host turn on retry\n"); exynos_drd_switch_schedule_dwork(work, EAGAIN_DELAY); } else { /* Fatal error */ dev_err(phy->dev, "unable to start A-device\n"); } } break; case OTG_STATE_A_HOST: dev_dbg(phy->dev, "OTG_STATE_A_HOST\n"); if (id_state == B_DEV) { ret = exynos_drd_switch_start_host(&drd_switch->otg, 0); /* Currently we ignore most of the errors */ if (ret == -EAGAIN) { dev_vdbg(phy->dev, "host turn off retry\n"); exynos_drd_switch_schedule_dwork(work, EAGAIN_DELAY); } else { if (ret == -EINVAL || ret == -EACCES) /* Fatal error */ dev_err(phy->dev, "unable to stop A-device\n"); new_state = OTG_STATE_A_IDLE; exynos_drd_switch_schedule_work(work); } } break; default: dev_err(phy->dev, "invalid otg-state\n"); } exit: WARN((phy->state != state), "PHY state has been changed outside of SM\n"); phy->state = new_state; }