USB OTG interrupt handling and fsm transition according to USB OTG
spec 2.0, update otg timer time out handlers.

Signed-off-by: Li Jun <[email protected]>
---
 drivers/usb/chipidea/bits.h    |    3 +
 drivers/usb/chipidea/core.c    |   10 ++-
 drivers/usb/chipidea/otg.c     |    6 +
 drivers/usb/chipidea/otg_fsm.c |  264 ++++++++++++++++++++++++++++++++++++++--
 drivers/usb/chipidea/otg_fsm.h |   18 +++
 5 files changed, 292 insertions(+), 9 deletions(-)

diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h
index 4347414..9a1c4c0 100644
--- a/drivers/usb/chipidea/bits.h
+++ b/drivers/usb/chipidea/bits.h
@@ -33,6 +33,8 @@
 #define USBCMD_ATDTW          BIT(14)
 
 /* USBSTS & USBINTR */
+#define USBSTS_PCI            BIT(2)
+#define USBSTS_SLI            BIT(8)
 #define USBi_UI               BIT(0)
 #define USBi_UEI              BIT(1)
 #define USBi_PCI              BIT(2)
@@ -81,6 +83,7 @@
 #define OTGSC_VC             BIT(1)
 #define OTGSC_IDPU           BIT(5)
 #define OTGSC_HADP           BIT(6)
+#define OTGSC_HABA            BIT(7)
 #define OTGSC_ID             BIT(8)
 #define OTGSC_AVV            BIT(9)
 #define OTGSC_ASV            BIT(10)
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 9a5ef20..2f29791 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -73,6 +73,7 @@
 #include "host.h"
 #include "debug.h"
 #include "otg.h"
+#include "otg_fsm.h"
 
 /* Controller register map */
 static uintptr_t ci_regs_nolpm[] = {
@@ -356,8 +357,12 @@ static irqreturn_t ci_irq(int irq, void *data)
        irqreturn_t ret = IRQ_NONE;
        u32 otgsc = 0;
 
-       if (ci->is_otg)
+       if (ci->is_otg) {
                otgsc = hw_read(ci, OP_OTGSC, ~0);
+               ret = ci_otg_fsm_irq(ci);
+               if (ret == IRQ_HANDLED)
+                       return ret;
+       }
 
        /*
         * Handle id change interrupt, it indicates device/host function
@@ -659,6 +664,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
        if (ret)
                goto stop;
 
+       if (ci->is_otg)
+               ci_hdrc_otg_fsm_start(ci);
+
        ret = dbg_create_files(ci);
        if (!ret)
                return 0;
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 2e13f2f..5914c92 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -22,6 +22,7 @@
 #include "ci.h"
 #include "bits.h"
 #include "otg.h"
+#include "otg_fsm.h"
 
 /**
  * ci_otg_role - pick role based on ID pin state
@@ -76,6 +77,11 @@ static void ci_otg_work(struct work_struct *work)
 {
        struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
 
+       if (!ci_otg_fsm_work(ci)) {
+               enable_irq(ci->irq);
+               return;
+       }
+
        if (ci->id_event) {
                ci->id_event = false;
                ci_handle_id_switch(ci);
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
index 86bed68..cfdfebd 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -192,6 +192,60 @@ void set_tmout(void *ptr, unsigned long indicator)
        *(int *)indicator = 1;
 }
 
+void set_tmout_and_fsm(void *ptr, unsigned long indicator)
+{
+       struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+       set_tmout(ci, indicator);
+
+       /* trans from a_wait_bcon to a_wait_vfall */
+       disable_irq_nosync(ci->irq);
+       queue_work(ci->wq, &ci->work);
+}
+
+void b_ssend_srp_tmout_handler(void *ptr, unsigned long indicator)
+{
+       struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+       set_tmout(ci, indicator);
+
+       /* only vbus fall below B_sess_vld in b_idle state */
+       if (ci->transceiver->state == OTG_STATE_B_IDLE) {
+               disable_irq_nosync(ci->irq);
+               queue_work(ci->wq, &ci->work);
+       }
+}
+
+void b_sess_vld_tmout_handler(void *ptr, unsigned long indicator)
+{
+       struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+       /* Check if A detached */
+       if (!(hw_read(ci, OP_OTGSC, OTGSC_BSV))) {
+               ci->fsm->b_sess_vld = 0;
+               ci_otg_add_timer(ci, b_ssend_srp_tmr);
+               disable_irq_nosync(ci->irq);
+               queue_work(ci->wq, &ci->work);
+       }
+}
+
+void b_data_pulse_end(void *ptr, unsigned long indicator)
+{
+       struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+       ci->fsm->b_srp_done = 1;
+       ci->fsm->b_bus_req = 0;
+       if (ci->fsm->power_up)
+               ci->fsm->power_up = 0;
+#ifdef HA_DATA_PULSE
+       hw_write(ci, OP_OTGSC, OTGSC_INT_STATUS_BITS | OTGSC_HABA, 0);
+
+       disable_irq_nosync(ci->irq);
+       queue_work(ci->wq, &ci->work);
+#else
+       ci_otg_loc_conn(ci->fsm, 0);
+#endif
+}
+
 /* Initialize timers */
 int ci_otg_init_timers(struct otg_fsm *fsm)
 {
@@ -201,22 +255,22 @@ int ci_otg_init_timers(struct otg_fsm *fsm)
        if (a_wait_vrise_tmr == NULL)
                return -ENOMEM;
 
-       a_wait_vfall_tmr = otg_timer_initializer(&set_tmout,
+       a_wait_vfall_tmr = otg_timer_initializer(&set_tmout_and_fsm,
                        TA_WAIT_VFALL, (unsigned long)&fsm->a_wait_vfall_tmout);
        if (a_wait_vfall_tmr == NULL)
                return -ENOMEM;
 
-       a_wait_bcon_tmr = otg_timer_initializer(&set_tmout,
+       a_wait_bcon_tmr = otg_timer_initializer(&set_tmout_and_fsm,
                TA_WAIT_BCON, (unsigned long)&fsm->a_wait_bcon_tmout);
        if (a_wait_bcon_tmr == NULL)
                return -ENOMEM;
 
-       a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout,
+       a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout_and_fsm,
                TA_AIDL_BDIS, (unsigned long)&fsm->a_aidl_bdis_tmout);
        if (a_aidl_bdis_tmr == NULL)
                return -ENOMEM;
 
-       a_bidl_adis_tmr = otg_timer_initializer(&set_tmout,
+       a_bidl_adis_tmr = otg_timer_initializer(&set_tmout_and_fsm,
                TA_BIDL_ADIS, (unsigned long)&fsm->a_bidl_adis_tmout);
        if (a_bidl_adis_tmr == NULL)
                return -ENOMEM;
@@ -226,12 +280,12 @@ int ci_otg_init_timers(struct otg_fsm *fsm)
        if (b_ase0_brst_tmr == NULL)
                return -ENOMEM;
 
-       b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP,
+       b_se0_srp_tmr = otg_timer_initializer(&set_tmout_and_fsm, TB_SE0_SRP,
                                (unsigned long)&fsm->b_se0_srp);
        if (b_se0_srp_tmr == NULL)
                return -ENOMEM;
 
-       b_ssend_srp_tmr = otg_timer_initializer(&set_tmout,
+       b_ssend_srp_tmr = otg_timer_initializer(&b_ssend_srp_tmout_handler,
                        TB_SSEND_SRP, (unsigned long)&fsm->b_ssend_srp);
        if (b_ssend_srp_tmr == NULL)
                return -ENOMEM;
@@ -241,11 +295,13 @@ int ci_otg_init_timers(struct otg_fsm *fsm)
        if (b_srp_fail_tmr == NULL)
                return -ENOMEM;
 
-       b_data_pulse_tmr = otg_timer_initializer(&set_tmout, TB_DATA_PLS, 0);
+       b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end,
+                                                       TB_DATA_PLS, 0);
        if (b_data_pulse_tmr == NULL)
                return -ENOMEM;
 
-       b_sess_vld_tmr = otg_timer_initializer(&set_tmout, TB_SESS_VLD, 0);
+       b_sess_vld_tmr = otg_timer_initializer(&b_sess_vld_tmout_handler,
+                                                       TB_SESS_VLD, 0);
        if (b_sess_vld_tmr == NULL)
                return -ENOMEM;
 
@@ -434,6 +490,198 @@ static struct otg_fsm_ops ci_otg_ops = {
        .start_gadget = ci_otg_start_gadget,
 };
 
+int ci_otg_fsm_work(struct ci_hdrc *ci)
+{
+       if (!ci->transceiver->otg || !ci->fsm)
+               return -ENODEV;
+
+       if (otg_statemachine(ci->fsm)) {
+               if (ci->transceiver->state == OTG_STATE_A_IDLE) {
+                       if (ci->fsm->id)
+                               /* A idle to B idle */
+                               otg_statemachine(ci->fsm);
+                       else if ((ci->id_event) || (ci->fsm->power_up)) {
+                               ci->id_event = false;
+                               /* A idle to A wait vrise */
+                               otg_statemachine(ci->fsm);
+                               ci->fsm->power_up = false;
+                       }
+               }
+       }
+       return 0;
+}
+
+static void ci_otg_fsm_event(struct ci_hdrc *ci, struct otg_fsm *fsm)
+{
+       if ((ci == NULL) || (fsm == NULL))
+               return;
+
+       switch (ci->transceiver->state) {
+       case OTG_STATE_A_WAIT_BCON:
+               if (hw_read(ci, OP_PORTSC, PORTSC_CCS)) {
+                       fsm->b_conn = 1;
+                       fsm->a_bus_req = 1;
+                       disable_irq_nosync(ci->irq);
+                       queue_work(ci->wq, &ci->work);
+               }
+               break;
+       case OTG_STATE_B_IDLE:
+               if (hw_read(ci, OP_OTGSC, OTGSC_BSV) &&
+                       hw_read(ci, OP_USBSTS, USBSTS_PCI) &&
+                       hw_read(ci, OP_PORTSC, PORTSC_CCS)) {
+                       fsm->b_sess_vld = 1;
+                       disable_irq_nosync(ci->irq);
+                       queue_work(ci->wq, &ci->work);
+               }
+               break;
+       case OTG_STATE_B_PERIPHERAL:
+               if (hw_read(ci, OP_USBSTS, USBSTS_SLI) &&
+                       hw_read(ci, OP_PORTSC, PORTSC_CCS) &&
+                       hw_read(ci, OP_OTGSC, OTGSC_BSV)) {
+                       fsm->a_bus_suspend = 1;
+                       disable_irq_nosync(ci->irq);
+                       queue_work(ci->wq, &ci->work);
+               } else if (hw_read(ci, OP_USBSTS, USBSTS_PCI)) {
+                       if (fsm->a_bus_suspend == 1)
+                               fsm->a_bus_suspend = 0;
+               }
+               break;
+       case OTG_STATE_B_HOST:
+               if (hw_read(ci, OP_USBSTS, USBSTS_PCI) &&
+                       !(hw_read(ci, OP_PORTSC, PORTSC_CCS))) {
+                       fsm->a_conn = 0;
+                       fsm->b_bus_req = 0;
+                       disable_irq_nosync(ci->irq);
+                       queue_work(ci->wq, &ci->work);
+                       ci_otg_add_timer(ci, b_sess_vld_tmr);
+               }
+               break;
+       case OTG_STATE_A_PERIPHERAL:
+               if (hw_read(ci, OP_USBSTS, USBSTS_SLI)) {
+                        fsm->b_bus_suspend = 1;
+                       /* Init a timer to know how long this suspend
+                        * will contine, if time out, indicates B no longer
+                        * wants to be host role */
+                        ci_otg_add_timer(ci, a_bidl_adis_tmr);
+               }
+
+               if (hw_read(ci, OP_USBSTS, USBi_URI))
+                       ci_otg_del_timer(ci, a_bidl_adis_tmr);
+
+               if (hw_read(ci, OP_USBSTS, USBSTS_PCI)) {
+                       if (fsm->b_bus_suspend == 1) {
+                               ci_otg_del_timer(ci, a_bidl_adis_tmr);
+                               fsm->b_bus_suspend = 0;
+                       }
+               }
+               break;
+       case OTG_STATE_A_SUSPEND:
+               if (hw_read(ci, OP_USBSTS, USBSTS_PCI) &&
+                       !(hw_read(ci, OP_PORTSC, PORTSC_CCS))) {
+                       fsm->b_conn = 0;
+
+                       /* if gadget driver is binded */
+                       if (ci->driver) {
+                               /* A device to be peripheral mode */
+                               ci->gadget.is_a_peripheral = 1;
+                       }
+                       disable_irq_nosync(ci->irq);
+                       queue_work(ci->wq, &ci->work);
+               }
+               break;
+       case OTG_STATE_A_HOST:
+               if (hw_read(ci, OP_USBSTS, USBSTS_PCI) &&
+                       !(hw_read(ci, OP_PORTSC, PORTSC_CCS))) {
+                       fsm->b_conn = 0;
+                       disable_irq_nosync(ci->irq);
+                       queue_work(ci->wq, &ci->work);
+               }
+               break;
+       case OTG_STATE_B_WAIT_ACON:
+               if (hw_read(ci, OP_USBSTS, USBSTS_PCI) &&
+                       hw_read(ci, OP_PORTSC, PORTSC_CCS)) {
+                       fsm->a_conn = 1;
+                       disable_irq_nosync(ci->irq);
+                       queue_work(ci->wq, &ci->work);
+               }
+               break;
+       default:
+               break;
+       }
+}
+/**
+ * ci_otg_irq - perform otg fsm related irq handling
+ * @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;
+
+       if ((ci == NULL) || !(ci->is_otg) ||
+               (ci->platdata->dr_mode != USB_DR_MODE_OTG))
+               return retval;
+
+       otgsc = hw_read(ci, OP_OTGSC, ~0);
+       otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8);
+       fsm->id = (otgsc & OTGSC_ID) ? 1 : 0;
+
+       /* process OTG interrupts: ID,A vbus vld,B sess Vld,1ms */
+       if (otg_int_src) {
+               if (otg_int_src & OTGSC_1MSIS) {
+                       ci_clear_otg_interrupt(ci, OTGSC_1MSIS);
+                       retval = ci_otg_tick_timer(ci);
+                       return IRQ_HANDLED;
+               } else if (otg_int_src & OTGSC_DPIS) {
+                       ci_clear_otg_interrupt(ci, OTGSC_DPIS);
+                       fsm->a_srp_det = 1;
+                       fsm->a_bus_drop = 0;
+               } else if (otg_int_src & OTGSC_IDIS) {
+                       ci_clear_otg_interrupt(ci, OTGSC_IDIS);
+                       if (fsm->id == 0) {
+                               fsm->a_bus_req = 1;
+                               ci->id_event = true;
+                       }
+               } else if (otg_int_src & OTGSC_BSVIS) {
+                       ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
+                       if (otgsc & OTGSC_BSV) {
+                               fsm->b_sess_vld = 1;
+                               ci_otg_del_timer(ci, b_ssend_srp_tmr);
+                               ci_otg_del_timer(ci, b_srp_fail_tmr);
+                               fsm->b_ssend_srp = 0;
+                       } else {
+                               fsm->b_sess_vld = 0;
+                               if (fsm->id)
+                                       ci_otg_add_timer(ci, b_ssend_srp_tmr);
+                       }
+               } else if (otg_int_src & OTGSC_AVVIS) {
+                       ci_clear_otg_interrupt(ci, OTGSC_AVVIS);
+                       if (otgsc & OTGSC_AVV) {
+                               fsm->a_vbus_vld = 1;
+                       } else {
+                               fsm->a_vbus_vld = 0;
+                               fsm->b_conn = 0;
+                       }
+               }
+               disable_irq_nosync(ci->irq);
+               queue_work(ci->wq, &ci->work);
+               return IRQ_HANDLED;
+       }
+
+       ci_otg_fsm_event(ci, fsm);
+
+       return retval;
+}
+
+void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
+{
+       if (ci->platdata->dr_mode == USB_DR_MODE_OTG) {
+               disable_irq_nosync(ci->irq);
+               queue_work(ci->wq, &ci->work);
+       }
+}
+
 int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
 {
        int retval = 0;
diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h
index 6e8226a..994e4c9 100644
--- a/drivers/usb/chipidea/otg_fsm.h
+++ b/drivers/usb/chipidea/otg_fsm.h
@@ -87,6 +87,9 @@ struct ci_otg_fsm_timer {
 #ifdef CONFIG_USB_OTG_FSM
 
 int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci);
+int ci_otg_fsm_work(struct ci_hdrc *ci);
+irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci);
+void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci);
 
 #else
 
@@ -95,6 +98,21 @@ static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
        return 0;
 }
 
+static inline int ci_otg_fsm_work(struct ci_hdrc *ci)
+{
+       return -ENXIO;
+}
+
+static inline irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
+{
+       return IRQ_NONE;
+}
+
+static inline void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
+{
+
+}
+
 #endif
 
 #endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */
-- 
1.7.8


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to