/**
 * 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;
}