/** * 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); struct device *dev = otg->phy->dev; bool activate = false; unsigned long flags; if (gadget) { dev_dbg(dev, "Binding gadget %s\n", gadget->name); spin_lock_irqsave(&drd_switch->lock, flags); 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; spin_unlock_irqrestore(&drd_switch->lock, flags); } else { dev_dbg(dev, "Unbinding gadget\n"); /* put state machine into reset state */ atomic_inc(&drd_switch->sm_reset); exynos_drd_switch_schedule_work(&drd_switch->work); flush_delayed_work_sync(&drd_switch->work); if (otg->phy->state != OTG_STATE_UNDEFINED) dev_err(dev, "%s: SM reset failed\n", __func__); spin_lock_irqsave(&drd_switch->lock, flags); otg->gadget = NULL; activate = true; spin_unlock_irqrestore(&drd_switch->lock, flags); atomic_dec(&drd_switch->sm_reset); } if (activate) exynos_drd_switch_schedule_work(&drd_switch->work); return 0; }
/** * exynos_drd_switch_reset - reset DRD role switch. * * @drd: Pointer to DRD controller structure. * @run: Start sm if 1. */ void exynos_drd_switch_reset(struct exynos_drd *drd, int run) { struct usb_otg *otg = drd->core.otg; struct exynos_drd_switch *drd_switch; unsigned long flags; if (otg) { drd_switch = container_of(otg, struct exynos_drd_switch, otg); spin_lock_irqsave(&drd_switch->lock, flags); if (drd->pdata->quirks & FORCE_INIT_PERIPHERAL) drd_switch->id_state = B_DEV; else drd_switch->id_state = exynos_drd_switch_get_id_state(drd_switch); drd_switch->vbus_active = exynos_drd_switch_get_bses_vld(drd_switch); otg->phy->state = OTG_STATE_UNDEFINED; spin_unlock_irqrestore(&drd_switch->lock, flags); if (run) exynos_drd_switch_schedule_work(&drd_switch->work); dev_dbg(drd->dev, "%s: id = %d, vbus = %d\n", __func__, drd_switch->id_state, drd_switch->vbus_active); } }
/** * exynos_drd_switch_set_host - bind/unbind the host controller driver. * * @otg: Pointer to the usb_otg structure. * @host: Pointer to the usb_bus structure. * * Returns 0 on success otherwise negative errno. */ static int exynos_drd_switch_set_host(struct usb_otg *otg, struct usb_bus *host) { struct exynos_drd_switch *drd_switch = container_of(otg, struct exynos_drd_switch, otg); struct device *dev = otg->phy->dev; bool activate = false; unsigned long flags; if (host) { dev_dbg(dev, "Binding host %s\n", host->bus_name); spin_lock_irqsave(&drd_switch->lock, flags); otg->host = host; /* * Prevents unnecessary activation of the work function. * If both peripheral and host are set or if ID pin is low * then we ensure that work function will enter to valid state. */ if (otg->gadget || drd_switch->id_state == A_DEV) activate = true; spin_unlock_irqrestore(&drd_switch->lock, flags); } else { dev_dbg(dev, "Unbinding host\n"); /* put state machine into reset state */ atomic_inc(&drd_switch->sm_reset); exynos_drd_switch_schedule_work(&drd_switch->work); flush_delayed_work_sync(&drd_switch->work); if (otg->phy->state != OTG_STATE_UNDEFINED) dev_err(dev, "%s: SM reset failed\n", __func__); spin_lock_irqsave(&drd_switch->lock, flags); otg->host = NULL; activate = true; spin_unlock_irqrestore(&drd_switch->lock, flags); atomic_dec(&drd_switch->sm_reset); } if (activate) exynos_drd_switch_schedule_work(&drd_switch->work); return 0; }
/** * exynos_drd_switch_debounce - GPIO debounce timer handler. * * @data: Pointer to DRD switch structure represented as unsigned long. */ static void exynos_drd_switch_debounce(unsigned long data) { struct exynos_drd_switch *drd_switch = (struct exynos_drd_switch *) data; struct usb_phy *phy = drd_switch->otg.phy; exynos_drd_switch_schedule_work(&drd_switch->work); dev_dbg(phy->dev, "new state id: %d, vbus: %d\n", drd_switch->id_state, drd_switch->vbus_active ? 1 : 0); }
/** * exynos_drd_switch_set_host - bind/unbind the host controller driver. * * @otg: Pointer to the usb_otg structure. * @host: Pointer to the usb_bus structure. * * Returns 0 on success otherwise negative errno. */ static int exynos_drd_switch_set_host(struct usb_otg *otg, struct usb_bus *host) { struct exynos_drd_switch *drd_switch = container_of(otg, struct exynos_drd_switch, otg); struct device *dev = otg->phy->dev; bool activate = false; unsigned long flags; if (host) { dev_dbg(dev, "Binding host %s\n", host->bus_name); spin_lock_irqsave(&drd_switch->lock, flags); otg->host = host; spin_unlock_irqrestore(&drd_switch->lock, flags); } else { dev_dbg(dev, "Unbinding host\n"); /* put state machine into reset state */ atomic_inc(&drd_switch->sm_reset); exynos_drd_switch_schedule_work(&drd_switch->work); flush_delayed_work_sync(&drd_switch->work); if (otg->phy->state != OTG_STATE_UNDEFINED) dev_err(dev, "%s: SM reset failed\n", __func__); spin_lock_irqsave(&drd_switch->lock, flags); otg->host = NULL; activate = true; spin_unlock_irqrestore(&drd_switch->lock, flags); atomic_dec(&drd_switch->sm_reset); } if (activate) exynos_drd_switch_schedule_work(&drd_switch->work); return 0; }
/** * 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 device *dev = otg->phy->dev; unsigned long flags; if (gadget) { dev_dbg(dev, "Binding gadget %s\n", gadget->name); spin_lock_irqsave(&drd_switch->lock, flags); otg->gadget = gadget; spin_unlock_irqrestore(&drd_switch->lock, flags); } else { dev_dbg(dev, "Unbinding gadget\n"); /* put state machine into reset state */ atomic_inc(&drd_switch->sm_reset); exynos_drd_switch_schedule_work(&drd_switch->work); flush_delayed_work_sync(&drd_switch->work); if (otg->phy->state != OTG_STATE_UNDEFINED) dev_err(dev, "%s: SM reset failed\n", __func__); spin_lock_irqsave(&drd_switch->lock, flags); otg->gadget = NULL; spin_unlock_irqrestore(&drd_switch->lock, flags); atomic_dec(&drd_switch->sm_reset); } exynos_drd_switch_schedule_work(&drd_switch->work); return 0; }
/** * 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_set_host - bind/unbind the host controller driver. * * @otg: Pointer to the usb_otg structure. * @host: Pointer to the usb_bus structure. * * Returns 0 on success otherwise negative errno. */ static int exynos_drd_switch_set_host(struct usb_otg *otg, struct usb_bus *host) { struct exynos_drd_switch *drd_switch = container_of(otg, struct exynos_drd_switch, otg); bool activate = false; unsigned long flags; spin_lock_irqsave(&drd_switch->lock, flags); if (host) { dev_dbg(otg->phy->dev, "Binding host %s\n", host->bus_name); otg->host = host; /* * Prevents unnecessary activation of the work function. * If both peripheral and host are set or if ID pin is low * then we ensure that work function will enter to valid state. */ if (otg->gadget || drd_switch->id_state == A_DEV) activate = true; } else { dev_dbg(otg->phy->dev, "Unbinding host\n"); if (otg->phy->state == OTG_STATE_A_HOST) { exynos_drd_switch_start_host(otg, 0); otg->host = NULL; otg->phy->state = OTG_STATE_UNDEFINED; activate = true; } else { otg->host = NULL; } } spin_unlock_irqrestore(&drd_switch->lock, flags); if (activate) exynos_drd_switch_schedule_work(&drd_switch->work); return 0; }
/** * exynos_drd_switch_reset - reset DRD role switch. * * @drd: Pointer to DRD controller structure. * @run: Start sm if 1. */ void exynos_drd_switch_reset(struct exynos_drd *drd, int run) { struct usb_otg *otg = drd->core.otg; struct exynos_drd_switch *drd_switch; unsigned long flags; if (otg) { drd_switch = container_of(otg, struct exynos_drd_switch, otg); /* reset state machine; we assume no works scheduled or running at this moment */ atomic_inc(&drd_switch->sm_reset); exynos_drd_switch_work(&drd_switch->work.work); spin_lock_irqsave(&drd_switch->lock, flags); if (drd->pdata->quirks & FORCE_INIT_PERIPHERAL) drd_switch->id_state = B_DEV; else drd_switch->id_state = exynos_drd_switch_get_id_state(drd_switch); drd_switch->vbus_active = exynos_drd_switch_get_bses_vld(drd_switch); spin_unlock_irqrestore(&drd_switch->lock, flags); atomic_dec(&drd_switch->sm_reset); if (run) exynos_drd_switch_schedule_work(&drd_switch->work); dev_dbg(drd->dev, "%s: id = %d, vbus = %d\n", __func__, drd_switch->id_state, drd_switch->vbus_active); } }
/** * 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; }