root/drivers/usb/chipidea/otg_fsm.c
// SPDX-License-Identifier: GPL-2.0
/*
 * otg_fsm.c - ChipIdea USB IP core OTG FSM driver
 *
 * Copyright (C) 2014 Freescale Semiconductor, Inc.
 *
 * Author: Jun Li
 */

/*
 * This file mainly handles OTG fsm, it includes OTG fsm operations
 * for HNP and SRP.
 *
 * TODO List
 * - ADP
 * - OTG test device
 */

#include <linux/usb/otg.h>
#include <linux/usb/gadget.h>
#include <linux/usb/hcd.h>
#include <linux/usb/chipidea.h>
#include <linux/regulator/consumer.h>

#include "ci.h"
#include "bits.h"
#include "otg.h"
#include "otg_fsm.h"

/* Add for otg: interact with user space app */
static ssize_t
a_bus_req_show(struct device *dev, struct device_attribute *attr, char *buf)
{
        char            *next;
        unsigned        size, t;
        struct ci_hdrc  *ci = dev_get_drvdata(dev);

        next = buf;
        size = PAGE_SIZE;
        t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_req);
        size -= t;
        next += t;

        return PAGE_SIZE - size;
}

static ssize_t
a_bus_req_store(struct device *dev, struct device_attribute *attr,
                                        const char *buf, size_t count)
{
        struct ci_hdrc *ci = dev_get_drvdata(dev);

        if (count > 2)
                return -1;

        mutex_lock(&ci->fsm.lock);
        if (buf[0] == '0') {
                ci->fsm.a_bus_req = 0;
        } else if (buf[0] == '1') {
                /* If a_bus_drop is TRUE, a_bus_req can't be set */
                if (ci->fsm.a_bus_drop) {
                        mutex_unlock(&ci->fsm.lock);
                        return count;
                }
                ci->fsm.a_bus_req = 1;
                if (ci->fsm.otg->state == OTG_STATE_A_PERIPHERAL) {
                        ci->gadget.host_request_flag = 1;
                        mutex_unlock(&ci->fsm.lock);
                        return count;
                }
        }

        ci_otg_queue_work(ci);
        mutex_unlock(&ci->fsm.lock);

        return count;
}
static DEVICE_ATTR_RW(a_bus_req);

static ssize_t
a_bus_drop_show(struct device *dev, struct device_attribute *attr, char *buf)
{
        char            *next;
        unsigned        size, t;
        struct ci_hdrc  *ci = dev_get_drvdata(dev);

        next = buf;
        size = PAGE_SIZE;
        t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_drop);
        size -= t;
        next += t;

        return PAGE_SIZE - size;
}

static ssize_t
a_bus_drop_store(struct device *dev, struct device_attribute *attr,
                                        const char *buf, size_t count)
{
        struct ci_hdrc  *ci = dev_get_drvdata(dev);

        if (count > 2)
                return -1;

        mutex_lock(&ci->fsm.lock);
        if (buf[0] == '0') {
                ci->fsm.a_bus_drop = 0;
        } else if (buf[0] == '1') {
                ci->fsm.a_bus_drop = 1;
                ci->fsm.a_bus_req = 0;
        }

        ci_otg_queue_work(ci);
        mutex_unlock(&ci->fsm.lock);

        return count;
}
static DEVICE_ATTR_RW(a_bus_drop);

static ssize_t
b_bus_req_show(struct device *dev, struct device_attribute *attr, char *buf)
{
        char            *next;
        unsigned        size, t;
        struct ci_hdrc  *ci = dev_get_drvdata(dev);

        next = buf;
        size = PAGE_SIZE;
        t = scnprintf(next, size, "%d\n", ci->fsm.b_bus_req);
        size -= t;
        next += t;

        return PAGE_SIZE - size;
}

static ssize_t
b_bus_req_store(struct device *dev, struct device_attribute *attr,
                                        const char *buf, size_t count)
{
        struct ci_hdrc  *ci = dev_get_drvdata(dev);

        if (count > 2)
                return -1;

        mutex_lock(&ci->fsm.lock);
        if (buf[0] == '0')
                ci->fsm.b_bus_req = 0;
        else if (buf[0] == '1') {
                ci->fsm.b_bus_req = 1;
                if (ci->fsm.otg->state == OTG_STATE_B_PERIPHERAL) {
                        ci->gadget.host_request_flag = 1;
                        mutex_unlock(&ci->fsm.lock);
                        return count;
                }
        }

        ci_otg_queue_work(ci);
        mutex_unlock(&ci->fsm.lock);

        return count;
}
static DEVICE_ATTR_RW(b_bus_req);

static ssize_t
a_clr_err_store(struct device *dev, struct device_attribute *attr,
                                        const char *buf, size_t count)
{
        struct ci_hdrc  *ci = dev_get_drvdata(dev);

        if (count > 2)
                return -1;

        mutex_lock(&ci->fsm.lock);
        if (buf[0] == '1')
                ci->fsm.a_clr_err = 1;

        ci_otg_queue_work(ci);
        mutex_unlock(&ci->fsm.lock);

        return count;
}
static DEVICE_ATTR_WO(a_clr_err);

static struct attribute *inputs_attrs[] = {
        &dev_attr_a_bus_req.attr,
        &dev_attr_a_bus_drop.attr,
        &dev_attr_b_bus_req.attr,
        &dev_attr_a_clr_err.attr,
        NULL,
};

static const struct attribute_group inputs_attr_group = {
        .name = "inputs",
        .attrs = inputs_attrs,
};

/*
 * Keep this list in the same order as timers indexed
 * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h
 */
static unsigned otg_timer_ms[] = {
        TA_WAIT_VRISE,
        TA_WAIT_VFALL,
        TA_WAIT_BCON,
        TA_AIDL_BDIS,
        TB_ASE0_BRST,
        TA_BIDL_ADIS,
        TB_AIDL_BDIS,
        TB_SE0_SRP,
        TB_SRP_FAIL,
        0,
        TB_DATA_PLS,
        TB_SSEND_SRP,
};

/*
 * Add timer to active timer list
 */
static void ci_otg_add_timer(struct ci_hdrc *ci, enum otg_fsm_timer t)
{
        unsigned long flags, timer_sec, timer_nsec;

        if (t >= NUM_OTG_FSM_TIMERS)
                return;

        spin_lock_irqsave(&ci->lock, flags);
        timer_sec = otg_timer_ms[t] / MSEC_PER_SEC;
        timer_nsec = (otg_timer_ms[t] % MSEC_PER_SEC) * NSEC_PER_MSEC;
        ci->hr_timeouts[t] = ktime_add(ktime_get(),
                                ktime_set(timer_sec, timer_nsec));
        ci->enabled_otg_timer_bits |= (1 << t);
        if ((ci->next_otg_timer == NUM_OTG_FSM_TIMERS) ||
                        ktime_after(ci->hr_timeouts[ci->next_otg_timer],
                                                ci->hr_timeouts[t])) {
                        ci->next_otg_timer = t;
                        hrtimer_start_range_ns(&ci->otg_fsm_hrtimer,
                                        ci->hr_timeouts[t], NSEC_PER_MSEC,
                                                        HRTIMER_MODE_ABS);
        }
        spin_unlock_irqrestore(&ci->lock, flags);
}

/*
 * Remove timer from active timer list
 */
static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t)
{
        unsigned long flags, enabled_timer_bits;
        enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS;

        if ((t >= NUM_OTG_FSM_TIMERS) ||
                        !(ci->enabled_otg_timer_bits & (1 << t)))
                return;

        spin_lock_irqsave(&ci->lock, flags);
        ci->enabled_otg_timer_bits &= ~(1 << t);
        if (ci->next_otg_timer == t) {
                if (ci->enabled_otg_timer_bits == 0) {
                        spin_unlock_irqrestore(&ci->lock, flags);
                        /* No enabled timers after delete it */
                        hrtimer_cancel(&ci->otg_fsm_hrtimer);
                        spin_lock_irqsave(&ci->lock, flags);
                        ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
                } else {
                        /* Find the next timer */
                        enabled_timer_bits = ci->enabled_otg_timer_bits;
                        for_each_set_bit(cur_timer, &enabled_timer_bits,
                                                        NUM_OTG_FSM_TIMERS) {
                                if ((next_timer == NUM_OTG_FSM_TIMERS) ||
                                        ktime_before(ci->hr_timeouts[next_timer],
                                         ci->hr_timeouts[cur_timer]))
                                        next_timer = cur_timer;
                        }
                }
        }
        if (next_timer != NUM_OTG_FSM_TIMERS) {
                ci->next_otg_timer = next_timer;
                hrtimer_start_range_ns(&ci->otg_fsm_hrtimer,
                        ci->hr_timeouts[next_timer], NSEC_PER_MSEC,
                                                        HRTIMER_MODE_ABS);
        }
        spin_unlock_irqrestore(&ci->lock, flags);
}

/* OTG FSM timer handlers */
static int a_wait_vrise_tmout(struct ci_hdrc *ci)
{
        ci->fsm.a_wait_vrise_tmout = 1;
        return 0;
}

static int a_wait_vfall_tmout(struct ci_hdrc *ci)
{
        ci->fsm.a_wait_vfall_tmout = 1;
        return 0;
}

static int a_wait_bcon_tmout(struct ci_hdrc *ci)
{
        ci->fsm.a_wait_bcon_tmout = 1;
        return 0;
}

static int a_aidl_bdis_tmout(struct ci_hdrc *ci)
{
        ci->fsm.a_aidl_bdis_tmout = 1;
        return 0;
}

static int b_ase0_brst_tmout(struct ci_hdrc *ci)
{
        ci->fsm.b_ase0_brst_tmout = 1;
        return 0;
}

static int a_bidl_adis_tmout(struct ci_hdrc *ci)
{
        ci->fsm.a_bidl_adis_tmout = 1;
        return 0;
}

static int b_aidl_bdis_tmout(struct ci_hdrc *ci)
{
        ci->fsm.a_bus_suspend = 1;
        return 0;
}

static int b_se0_srp_tmout(struct ci_hdrc *ci)
{
        ci->fsm.b_se0_srp = 1;
        return 0;
}

static int b_srp_fail_tmout(struct ci_hdrc *ci)
{
        ci->fsm.b_srp_done = 1;
        return 1;
}

static int b_data_pls_tmout(struct ci_hdrc *ci)
{
        ci->fsm.b_srp_done = 1;
        ci->fsm.b_bus_req = 0;
        if (ci->fsm.power_up)
                ci->fsm.power_up = 0;
        hw_write_otgsc(ci, OTGSC_HABA, 0);
        pm_runtime_put(ci->dev);
        return 0;
}

static int b_ssend_srp_tmout(struct ci_hdrc *ci)
{
        ci->fsm.b_ssend_srp = 1;
        /* only vbus fall below B_sess_vld in b_idle state */
        if (ci->fsm.otg->state == OTG_STATE_B_IDLE)
                return 0;
        else
                return 1;
}

/*
 * Keep this list in the same order as timers indexed
 * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h
 */
static int (*otg_timer_handlers[])(struct ci_hdrc *) = {
        a_wait_vrise_tmout,     /* A_WAIT_VRISE */
        a_wait_vfall_tmout,     /* A_WAIT_VFALL */
        a_wait_bcon_tmout,      /* A_WAIT_BCON */
        a_aidl_bdis_tmout,      /* A_AIDL_BDIS */
        b_ase0_brst_tmout,      /* B_ASE0_BRST */
        a_bidl_adis_tmout,      /* A_BIDL_ADIS */
        b_aidl_bdis_tmout,      /* B_AIDL_BDIS */
        b_se0_srp_tmout,        /* B_SE0_SRP */
        b_srp_fail_tmout,       /* B_SRP_FAIL */
        NULL,                   /* A_WAIT_ENUM */
        b_data_pls_tmout,       /* B_DATA_PLS */
        b_ssend_srp_tmout,      /* B_SSEND_SRP */
};

/*
 * Enable the next nearest enabled timer if have
 */
static enum hrtimer_restart ci_otg_hrtimer_func(struct hrtimer *t)
{
        struct ci_hdrc *ci = container_of(t, struct ci_hdrc, otg_fsm_hrtimer);
        ktime_t now, *timeout;
        unsigned long   enabled_timer_bits;
        unsigned long   flags;
        enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS;
        int ret = -EINVAL;

        spin_lock_irqsave(&ci->lock, flags);
        enabled_timer_bits = ci->enabled_otg_timer_bits;
        ci->next_otg_timer = NUM_OTG_FSM_TIMERS;

        now = ktime_get();
        for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) {
                if (ktime_compare(now, ci->hr_timeouts[cur_timer]) >= 0) {
                        ci->enabled_otg_timer_bits &= ~(1 << cur_timer);
                        if (otg_timer_handlers[cur_timer])
                                ret = otg_timer_handlers[cur_timer](ci);
                } else {
                        if ((next_timer == NUM_OTG_FSM_TIMERS) ||
                                ktime_before(ci->hr_timeouts[cur_timer],
                                        ci->hr_timeouts[next_timer]))
                                next_timer = cur_timer;
                }
        }
        /* Enable the next nearest timer */
        if (next_timer < NUM_OTG_FSM_TIMERS) {
                timeout = &ci->hr_timeouts[next_timer];
                hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, *timeout,
                                        NSEC_PER_MSEC, HRTIMER_MODE_ABS);
                ci->next_otg_timer = next_timer;
        }
        spin_unlock_irqrestore(&ci->lock, flags);

        if (!ret)
                ci_otg_queue_work(ci);

        return HRTIMER_NORESTART;
}

/* Initialize timers */
static int ci_otg_init_timers(struct ci_hdrc *ci)
{
        hrtimer_setup(&ci->otg_fsm_hrtimer, ci_otg_hrtimer_func, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);

        return 0;
}

/* -------------------------------------------------------------*/
/* Operations that will be called from OTG Finite State Machine */
/* -------------------------------------------------------------*/
static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
{
        struct ci_hdrc  *ci = container_of(fsm, struct ci_hdrc, fsm);

        if (t < NUM_OTG_FSM_TIMERS)
                ci_otg_add_timer(ci, t);
        return;
}

static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
{
        struct ci_hdrc  *ci = container_of(fsm, struct ci_hdrc, fsm);

        if (t < NUM_OTG_FSM_TIMERS)
                ci_otg_del_timer(ci, t);
        return;
}

/*
 * A-device drive vbus: turn on vbus regulator and enable port power
 * Data pulse irq should be disabled while vbus is on.
 */
static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
{
        int ret;
        struct ci_hdrc  *ci = container_of(fsm, struct ci_hdrc, fsm);

        if (on) {
                /* Enable power */
                hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP,
                                                        PORTSC_PP);
                if (ci->platdata->reg_vbus) {
                        ret = regulator_enable(ci->platdata->reg_vbus);
                        if (ret) {
                                dev_err(ci->dev,
                                "Failed to enable vbus regulator, ret=%d\n",
                                ret);
                                return;
                        }
                }

                if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL)
                        usb_phy_vbus_on(ci->usb_phy);

                /* Disable data pulse irq */
                hw_write_otgsc(ci, OTGSC_DPIE, 0);

                fsm->a_srp_det = 0;
                fsm->power_up = 0;
        } else {
                if (ci->platdata->reg_vbus)
                        regulator_disable(ci->platdata->reg_vbus);

                if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL)
                        usb_phy_vbus_off(ci->usb_phy);

                fsm->a_bus_drop = 1;
                fsm->a_bus_req = 0;
        }
}

/*
 * Control data line by Run Stop bit.
 */
static void ci_otg_loc_conn(struct otg_fsm *fsm, int on)
{
        struct ci_hdrc  *ci = container_of(fsm, struct ci_hdrc, fsm);

        if (on)
                hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
        else
                hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
}

/*
 * Generate SOF by host.
 * In host mode, controller will automatically send SOF.
 * Suspend will block the data on the port.
 *
 * This is controlled through usbcore by usb autosuspend,
 * so the usb device class driver need support autosuspend,
 * otherwise the bus suspend will not happen.
 */
static void ci_otg_loc_sof(struct otg_fsm *fsm, int on)
{
        struct usb_device *udev;

        if (!fsm->otg->host)
                return;

        udev = usb_hub_find_child(fsm->otg->host->root_hub, 1);
        if (!udev)
                return;

        if (on) {
                usb_disable_autosuspend(udev);
        } else {
                pm_runtime_set_autosuspend_delay(&udev->dev, 0);
                usb_enable_autosuspend(udev);
        }
}

/*
 * Start SRP pulsing by data-line pulsing,
 * no v-bus pulsing followed
 */
static void ci_otg_start_pulse(struct otg_fsm *fsm)
{
        struct ci_hdrc  *ci = container_of(fsm, struct ci_hdrc, fsm);

        /* Hardware Assistant Data pulse */
        hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP);

        pm_runtime_get(ci->dev);
        ci_otg_add_timer(ci, B_DATA_PLS);
}

static int ci_otg_start_host(struct otg_fsm *fsm, int on)
{
        struct ci_hdrc  *ci = container_of(fsm, struct ci_hdrc, fsm);

        if (on) {
                ci_role_stop(ci);
                ci_role_start(ci, CI_ROLE_HOST);
        } else {
                ci_role_stop(ci);
                ci_role_start(ci, CI_ROLE_GADGET);
        }
        return 0;
}

static int ci_otg_start_gadget(struct otg_fsm *fsm, int on)
{
        struct ci_hdrc  *ci = container_of(fsm, struct ci_hdrc, fsm);

        if (on)
                usb_gadget_vbus_connect(&ci->gadget);
        else
                usb_gadget_vbus_disconnect(&ci->gadget);

        return 0;
}

static struct otg_fsm_ops ci_otg_ops = {
        .drv_vbus = ci_otg_drv_vbus,
        .loc_conn = ci_otg_loc_conn,
        .loc_sof = ci_otg_loc_sof,
        .start_pulse = ci_otg_start_pulse,
        .add_timer = ci_otg_fsm_add_timer,
        .del_timer = ci_otg_fsm_del_timer,
        .start_host = ci_otg_start_host,
        .start_gadget = ci_otg_start_gadget,
};

int ci_otg_fsm_work(struct ci_hdrc *ci)
{
        /*
         * Don't do fsm transition for B device
         * when there is no gadget class driver
         */
        if (ci->fsm.id && !(ci->driver) &&
                ci->fsm.otg->state < OTG_STATE_A_IDLE)
                return 0;

        pm_runtime_get_sync(ci->dev);
        if (otg_statemachine(&ci->fsm)) {
                if (ci->fsm.otg->state == OTG_STATE_A_IDLE) {
                        /*
                         * Further state change for cases:
                         * a_idle to b_idle; or
                         * a_idle to a_wait_vrise due to ID change(1->0), so
                         * B-dev becomes A-dev can try to start new session
                         * consequently; or
                         * a_idle to a_wait_vrise when power up
                         */
                        if ((ci->fsm.id) || (ci->id_event) ||
                                                (ci->fsm.power_up)) {
                                ci_otg_queue_work(ci);
                        } else {
                                /* Enable data pulse irq */
                                hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS |
                                                                PORTSC_PP, 0);
                                hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
                                hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE);
                        }
                        if (ci->id_event)
                                ci->id_event = false;
                } else if (ci->fsm.otg->state == OTG_STATE_B_IDLE) {
                        if (ci->fsm.b_sess_vld) {
                                ci->fsm.power_up = 0;
                                /*
                                 * Further transite to b_periphearl state
                                 * when register gadget driver with vbus on
                                 */
                                ci_otg_queue_work(ci);
                        }
                } else if (ci->fsm.otg->state == OTG_STATE_A_HOST) {
                        pm_runtime_put_autosuspend(ci->dev);
                        return 0;
                }
        }
        pm_runtime_put_sync(ci->dev);
        return 0;
}

/*
 * Update fsm variables in each state if catching expected interrupts,
 * called by otg fsm isr.
 */
static void ci_otg_fsm_event(struct ci_hdrc *ci)
{
        u32 intr_sts, otg_bsess_vld, port_conn;
        struct otg_fsm *fsm = &ci->fsm;

        intr_sts = hw_read_intr_status(ci);
        otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV);
        port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS);

        switch (ci->fsm.otg->state) {
        case OTG_STATE_A_WAIT_BCON:
                if (port_conn) {
                        fsm->b_conn = 1;
                        fsm->a_bus_req = 1;
                        ci_otg_queue_work(ci);
                }
                break;
        case OTG_STATE_B_IDLE:
                if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) {
                        fsm->b_sess_vld = 1;
                        ci_otg_queue_work(ci);
                }
                break;
        case OTG_STATE_B_PERIPHERAL:
                if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) {
                        ci_otg_add_timer(ci, B_AIDL_BDIS);
                } else if (intr_sts & USBi_PCI) {
                        ci_otg_del_timer(ci, B_AIDL_BDIS);
                        if (fsm->a_bus_suspend == 1)
                                fsm->a_bus_suspend = 0;
                }
                break;
        case OTG_STATE_B_HOST:
                if ((intr_sts & USBi_PCI) && !port_conn) {
                        fsm->a_conn = 0;
                        fsm->b_bus_req = 0;
                        ci_otg_queue_work(ci);
                }
                break;
        case OTG_STATE_A_PERIPHERAL:
                if (intr_sts & USBi_SLI) {
                         fsm->b_bus_suspend = 1;
                        /*
                         * Init a timer to know how long this suspend
                         * will continue, if time out, indicates B no longer
                         * wants to be host role
                         */
                         ci_otg_add_timer(ci, A_BIDL_ADIS);
                }

                if (intr_sts & USBi_URI)
                        ci_otg_del_timer(ci, A_BIDL_ADIS);

                if (intr_sts & USBi_PCI) {
                        if (fsm->b_bus_suspend == 1) {
                                ci_otg_del_timer(ci, A_BIDL_ADIS);
                                fsm->b_bus_suspend = 0;
                        }
                }
                break;
        case OTG_STATE_A_SUSPEND:
                if ((intr_sts & USBi_PCI) && !port_conn) {
                        fsm->b_conn = 0;

                        /* if gadget driver is binded */
                        if (ci->driver) {
                                /* A device to be peripheral mode */
                                ci->gadget.is_a_peripheral = 1;
                        }
                        ci_otg_queue_work(ci);
                }
                break;
        case OTG_STATE_A_HOST:
                if ((intr_sts & USBi_PCI) && !port_conn) {
                        fsm->b_conn = 0;
                        ci_otg_queue_work(ci);
                }
                break;
        case OTG_STATE_B_WAIT_ACON:
                if ((intr_sts & USBi_PCI) && port_conn) {
                        fsm->a_conn = 1;
                        ci_otg_queue_work(ci);
                }
                break;
        default:
                break;
        }
}

/*
 * ci_otg_irq - otg fsm related irq handling
 * and also update otg fsm variable by monitoring usb host and udc
 * state change interrupts.
 * @ci: ci_hdrc
 */
irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
{
        irqreturn_t retval =  IRQ_NONE;
        u32 otgsc, otg_int_src = 0;
        struct otg_fsm *fsm = &ci->fsm;

        otgsc = hw_read_otgsc(ci, ~0);
        otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8);
        fsm->id = (otgsc & OTGSC_ID) ? 1 : 0;

        if (otg_int_src) {
                if (otg_int_src & OTGSC_DPIS) {
                        hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
                        fsm->a_srp_det = 1;
                        fsm->a_bus_drop = 0;
                } else if (otg_int_src & OTGSC_IDIS) {
                        hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS);
                        if (fsm->id == 0) {
                                fsm->a_bus_drop = 0;
                                fsm->a_bus_req = 1;
                                ci->id_event = true;
                        }
                } else if (otg_int_src & OTGSC_BSVIS) {
                        hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
                        if (otgsc & OTGSC_BSV) {
                                fsm->b_sess_vld = 1;
                                ci_otg_del_timer(ci, B_SSEND_SRP);
                                ci_otg_del_timer(ci, B_SRP_FAIL);
                                fsm->b_ssend_srp = 0;
                        } else {
                                fsm->b_sess_vld = 0;
                                if (fsm->id)
                                        ci_otg_add_timer(ci, B_SSEND_SRP);
                        }
                } else if (otg_int_src & OTGSC_AVVIS) {
                        hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS);
                        if (otgsc & OTGSC_AVV) {
                                fsm->a_vbus_vld = 1;
                        } else {
                                fsm->a_vbus_vld = 0;
                                fsm->b_conn = 0;
                        }
                }
                ci_otg_queue_work(ci);
                return IRQ_HANDLED;
        }

        ci_otg_fsm_event(ci);

        return retval;
}

void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
{
        ci_otg_queue_work(ci);
}

int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
{
        int retval = 0;

        if (ci->phy)
                ci->otg.phy = ci->phy;
        else
                ci->otg.usb_phy = ci->usb_phy;

        ci->otg.gadget = &ci->gadget;
        ci->fsm.otg = &ci->otg;
        ci->fsm.power_up = 1;
        ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0;
        ci->fsm.otg->state = OTG_STATE_UNDEFINED;
        ci->fsm.ops = &ci_otg_ops;
        ci->gadget.hnp_polling_support = 1;
        ci->fsm.host_req_flag = devm_kzalloc(ci->dev, 1, GFP_KERNEL);
        if (!ci->fsm.host_req_flag)
                return -ENOMEM;

        mutex_init(&ci->fsm.lock);

        retval = ci_otg_init_timers(ci);
        if (retval) {
                dev_err(ci->dev, "Couldn't init OTG timers\n");
                return retval;
        }
        ci->enabled_otg_timer_bits = 0;
        ci->next_otg_timer = NUM_OTG_FSM_TIMERS;

        retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group);
        if (retval < 0) {
                dev_dbg(ci->dev,
                        "Can't register sysfs attr group: %d\n", retval);
                return retval;
        }

        /* Enable A vbus valid irq */
        hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE);

        if (ci->fsm.id) {
                ci->fsm.b_ssend_srp =
                        hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1;
                ci->fsm.b_sess_vld =
                        hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0;
                /* Enable BSV irq */
                hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE);
        }

        return 0;
}

void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
{
        sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group);
}