On Mon, Mar 31, 2014 at 05:03:00PM +0800, Li Jun wrote:
> On Mon, Mar 31, 2014 at 03:24:35PM +0800, Peter Chen wrote:
> > On Sun, Mar 30, 2014 at 08:20:09PM +0800, Li Jun wrote:
> > > From: Li Jun <b47...@freescale.com>
> > > 
> > > USB OTG interrupt handling and fsm transitions according to USB OTG
> > > and EH 2.0.
> > > 
> > > Signed-off-by: Li Jun <b47...@freescale.com>
> > > ---
> > >  drivers/usb/chipidea/core.c    |    8 +-
> > >  drivers/usb/chipidea/otg.c     |    9 +-
> > >  drivers/usb/chipidea/otg_fsm.c |  236 
> > > ++++++++++++++++++++++++++++++++++++++++
> > >  drivers/usb/chipidea/otg_fsm.h |   18 +++
> > >  drivers/usb/chipidea/udc.c     |    6 +
> > >  5 files changed, 273 insertions(+), 4 deletions(-)
> > > 
> > > diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
> > > index ff38cf3..6544149 100644
> > > --- a/drivers/usb/chipidea/core.c
> > > +++ b/drivers/usb/chipidea/core.c
> > > @@ -42,7 +42,6 @@
> > >   * - Not Supported: 15 & 16 (ISO)
> > >   *
> > >   * TODO List
> > > - * - OTG
> > >   * - Interrupt Traffic
> > >   * - GET_STATUS(device) - always reports 0
> > >   * - Gadget API (majority of optional features)
> > > @@ -74,6 +73,7 @@
> > >  #include "host.h"
> > >  #include "debug.h"
> > >  #include "otg.h"
> > > +#include "otg_fsm.h"
> > >  
> > >  /* Controller register map */
> > >  static const u8 ci_regs_nolpm[] = {
> > > @@ -412,8 +412,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_otgsc(ci, ~0);
> > > +         ret = ci_otg_fsm_irq(ci);
> > > +         if (ret == IRQ_HANDLED)
> > > +                 return ret;
> > > + }
> > >  
> > >   /*
> > >    * Handle id change interrupt, it indicates device/host function
> > > diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
> > > index b278cd9..5169640 100644
> > > --- a/drivers/usb/chipidea/otg.c
> > > +++ b/drivers/usb/chipidea/otg.c
> > > @@ -11,8 +11,8 @@
> > >   */
> > >  
> > >  /*
> > > - * This file mainly handles otgsc register, it may include OTG operation
> > > - * in the future.
> > > + * This file mainly handles otgsc register, OTG fsm operations for HNP 
> > > and SRP
> > > + * are also included.
> > >   */
> > >  
> > >  #include <linux/usb/otg.h>
> > > @@ -91,6 +91,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 d49bbdd..8e30fc64 100644
> > > --- a/drivers/usb/chipidea/otg_fsm.c
> > > +++ b/drivers/usb/chipidea/otg_fsm.c
> > > @@ -13,6 +13,10 @@
> > >  /*
> > >   * 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>
> > > @@ -92,6 +96,33 @@ static void ci_otg_del_timer(struct ci_hdrc *ci, enum 
> > > ci_otg_fsm_timer_index t)
> > >           hw_write_otgsc(ci, OTGSC_1MSIE, 0);
> > >  }
> > >  
> > > +/*
> > > + * Reduce timer count by 1, and find timeout conditions.
> > > + * Called by otg 1ms timer interrupt
> > > + */
> > > +static inline int ci_otg_tick_timer(struct ci_hdrc *ci)
> > > +{
> > > + struct ci_otg_fsm_timer *tmp_timer, *del_tmp;
> > > + struct list_head *active_timers = &ci->fsm_timer->active_timers;
> > > + int expired = 0;
> > > +
> > > + list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) {
> > > +         tmp_timer->count--;
> > > +         /* check if timer expires */
> > > +         if (!tmp_timer->count) {
> > > +                 list_del(&tmp_timer->list);
> > > +                 tmp_timer->function(ci, tmp_timer->data);
> > > +                 expired = 1;
> > > +         }
> > > + }
> > > +
> > > + /* disable 1ms irq if there is no any timer active */
> > > + if ((expired == 1) && list_empty(active_timers))
> > > +         hw_write_otgsc(ci, OTGSC_1MSIE, 0);
> > > +
> > > + return expired;
> > > +}
> > > +
> > >  /* The timeout callback function to set time out bit */
> > >  static void set_tmout(void *ptr, unsigned long indicator)
> > >  {
> > > @@ -385,6 +416,209 @@ 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)
> > > +         return -ENODEV;
> > > +
> > > + /*
> > > +  * Don't do fsm transition for B device
> > > +  * when there is no gadget class driver
> > > +  */
> > > + if (ci->fsm.id && !(ci->driver) &&
> > > +         ci->transceiver->state < OTG_STATE_A_IDLE)
> > 
> > Why the third condition is needed?
> > 
> 
> To make a_xxxx state can be transfered to next state when micro-A plug is 
> removed
> (id is 1) while gadget driver is not enabled.
> 
> > > +         return 0;
> > > +
> > > + 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;
> > > +}
> > > +
> > > +/*
> > > + * 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->transceiver->state) {
> > > + case OTG_STATE_A_WAIT_BCON:
> > > +         if (port_conn) {
> > > +                 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 (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) {
> > > +                 fsm->b_sess_vld = 1;
> > > +                 if (fsm->power_up)
> > > +                         fsm->power_up = 0;
> > > +                 disable_irq_nosync(ci->irq);
> > > +                 queue_work(ci->wq, &ci->work);
> > > +         }
> > > +         break;
> > > + case OTG_STATE_B_PERIPHERAL:
> > > +         if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) {
> > > +                 fsm->a_bus_suspend = 1;
> > > +                 disable_irq_nosync(ci->irq);
> > > +                 queue_work(ci->wq, &ci->work);
> > > +         } else if (intr_sts & USBi_PCI) {
> > > +                 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;
> > > +                 disable_irq_nosync(ci->irq);
> > > +                 queue_work(ci->wq, &ci->work);
> > > +                 ci_otg_add_timer(ci, B_SESS_VLD);
> > > +         }
> > > +         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 contine, 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;
> > > +                 }
> > > +                 disable_irq_nosync(ci->irq);
> > > +                 queue_work(ci->wq, &ci->work);
> > > +         }
> > > +         break;
> > > + case OTG_STATE_A_HOST:
> > > +         if ((intr_sts & USBi_PCI) && !port_conn) {
> > > +                 fsm->b_conn = 0;
> > > +                 disable_irq_nosync(ci->irq);
> > > +                 queue_work(ci->wq, &ci->work);
> > > +         }
> > > +         break;
> > > + case OTG_STATE_B_WAIT_ACON:
> > > +         if ((intr_sts & USBi_PCI) && port_conn) {
> > > +                 fsm->a_conn = 1;
> > > +                 disable_irq_nosync(ci->irq);
> > > +                 queue_work(ci->wq, &ci->work);
> > > +         }
> > > +         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;
> > > +
> > > + if (!ci_otg_is_fsm_mode(ci))
> > > +         return retval;
> > > +
> > > + 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_1MSIS) {
> > > +                 hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS);
> > > +                 retval = ci_otg_tick_timer(ci);
> > > +                 return IRQ_HANDLED;
> > > +         } else 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_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;
> > > +                 }
> > > +         }
> > > +         disable_irq_nosync(ci->irq);
> > > +         queue_work(ci->wq, &ci->work);
> > > +         return IRQ_HANDLED;
> > > + }
> > > +
> > > + ci_otg_fsm_event(ci);
> > > +
> > > + return retval;
> > > +}
> > > +
> > > +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
> > > +{
> > > + if (ci_otg_is_fsm_mode(ci))
> > > +         ci_otg_fsm_work(ci);
> > > +}
> > > +
> > >  int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
> > >  {
> > >   int retval = 0;
> > > @@ -435,5 +669,7 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
> > >                   hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0;
> > >   }
> > >  
> > > + ci_hdrc_otg_fsm_start(ci);
> > > +
> > 
> > After this change, ci_hdrc_otg_fsm_start will be called before
> > ci_role_start, is it your expectation?
> > 
> 
> It's no problem, here we just need drive the fsm from *null* state to some
> known state, then further state transitions can be driven by irq/events.
> 
> > Besides, in order to avoid calling ci_hdrc_otg_fsm_start two times at
> > peripheral mode, it is better move it to host_start, like you change for
> > udc.c
> > 
> 
> host_start is not fit since it will be called in every gadget-->host
> role switch, ci_hdrc_otg_fsm_start() only need be called once after power up.
> 

Get it.

I also notice you have checked at ci_otg_fsm_work for non gadget
situation, it is great.

-- 

Best Regards,
Peter Chen

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

Reply via email to