forked from Minki/linux
usb: chipidea: OTG HNP and SRP fsm implementation
USB OTG interrupt handling and fsm transitions according to USB OTG and EH 2.0. Signed-off-by: Peter Chen <peter.chen@freescale.com> Signed-off-by: Li Jun <b47624@freescale.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
e287b67b00
commit
4dcf720c5d
@ -42,7 +42,6 @@
|
|||||||
* - Not Supported: 15 & 16 (ISO)
|
* - Not Supported: 15 & 16 (ISO)
|
||||||
*
|
*
|
||||||
* TODO List
|
* TODO List
|
||||||
* - OTG
|
|
||||||
* - Interrupt Traffic
|
* - Interrupt Traffic
|
||||||
* - GET_STATUS(device) - always reports 0
|
* - GET_STATUS(device) - always reports 0
|
||||||
* - Gadget API (majority of optional features)
|
* - Gadget API (majority of optional features)
|
||||||
@ -74,6 +73,7 @@
|
|||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "otg.h"
|
#include "otg.h"
|
||||||
|
#include "otg_fsm.h"
|
||||||
|
|
||||||
/* Controller register map */
|
/* Controller register map */
|
||||||
static const u8 ci_regs_nolpm[] = {
|
static const u8 ci_regs_nolpm[] = {
|
||||||
@ -411,8 +411,14 @@ static irqreturn_t ci_irq(int irq, void *data)
|
|||||||
irqreturn_t ret = IRQ_NONE;
|
irqreturn_t ret = IRQ_NONE;
|
||||||
u32 otgsc = 0;
|
u32 otgsc = 0;
|
||||||
|
|
||||||
if (ci->is_otg)
|
if (ci->is_otg) {
|
||||||
otgsc = hw_read_otgsc(ci, ~0);
|
otgsc = hw_read_otgsc(ci, ~0);
|
||||||
|
if (ci_otg_is_fsm_mode(ci)) {
|
||||||
|
ret = ci_otg_fsm_irq(ci);
|
||||||
|
if (ret == IRQ_HANDLED)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle id change interrupt, it indicates device/host function
|
* Handle id change interrupt, it indicates device/host function
|
||||||
@ -691,10 +697,13 @@ static int ci_hdrc_probe(struct platform_device *pdev)
|
|||||||
if (ci->role == CI_ROLE_GADGET)
|
if (ci->role == CI_ROLE_GADGET)
|
||||||
ci_handle_vbus_change(ci);
|
ci_handle_vbus_change(ci);
|
||||||
|
|
||||||
ret = ci_role_start(ci, ci->role);
|
if (!ci_otg_is_fsm_mode(ci)) {
|
||||||
if (ret) {
|
ret = ci_role_start(ci, ci->role);
|
||||||
dev_err(dev, "can't start %s role\n", ci_role(ci)->name);
|
if (ret) {
|
||||||
goto stop;
|
dev_err(dev, "can't start %s role\n",
|
||||||
|
ci_role(ci)->name);
|
||||||
|
goto stop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, ci);
|
platform_set_drvdata(pdev, ci);
|
||||||
@ -703,6 +712,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto stop;
|
goto stop;
|
||||||
|
|
||||||
|
if (ci_otg_is_fsm_mode(ci))
|
||||||
|
ci_hdrc_otg_fsm_start(ci);
|
||||||
|
|
||||||
ret = dbg_create_files(ci);
|
ret = dbg_create_files(ci);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file mainly handles otgsc register, it may include OTG operation
|
* This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
|
||||||
* in the future.
|
* are also included.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/usb/otg.h>
|
#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);
|
struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
|
||||||
|
|
||||||
|
if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
|
||||||
|
enable_irq(ci->irq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ci->id_event) {
|
if (ci->id_event) {
|
||||||
ci->id_event = false;
|
ci->id_event = false;
|
||||||
ci_handle_id_switch(ci);
|
ci_handle_id_switch(ci);
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
/*
|
/*
|
||||||
* This file mainly handles OTG fsm, it includes OTG fsm operations
|
* This file mainly handles OTG fsm, it includes OTG fsm operations
|
||||||
* for HNP and SRP.
|
* for HNP and SRP.
|
||||||
|
*
|
||||||
|
* TODO List
|
||||||
|
* - ADP
|
||||||
|
* - OTG test device
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/usb/otg.h>
|
#include <linux/usb/otg.h>
|
||||||
@ -93,6 +97,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);
|
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 */
|
/* The timeout callback function to set time out bit */
|
||||||
static void set_tmout(void *ptr, unsigned long indicator)
|
static void set_tmout(void *ptr, unsigned long indicator)
|
||||||
{
|
{
|
||||||
@ -394,6 +425,218 @@ static struct otg_fsm_ops ci_otg_ops = {
|
|||||||
.start_gadget = ci_otg_start_gadget,
|
.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->transceiver->state < OTG_STATE_A_IDLE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (otg_statemachine(&ci->fsm)) {
|
||||||
|
if (ci->transceiver->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)) {
|
||||||
|
disable_irq_nosync(ci->irq);
|
||||||
|
queue_work(ci->wq, &ci->work);
|
||||||
|
}
|
||||||
|
if (ci->id_event)
|
||||||
|
ci->id_event = false;
|
||||||
|
} else if (ci->transceiver->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
|
||||||
|
*/
|
||||||
|
disable_irq_nosync(ci->irq);
|
||||||
|
queue_work(ci->wq, &ci->work);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
disable_irq_nosync(ci->irq);
|
||||||
|
queue_work(ci->wq, &ci->work);
|
||||||
|
}
|
||||||
|
|
||||||
int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
|
int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
|
||||||
{
|
{
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
@ -92,6 +92,9 @@ struct ci_otg_fsm_timer_list {
|
|||||||
#ifdef CONFIG_USB_OTG_FSM
|
#ifdef CONFIG_USB_OTG_FSM
|
||||||
|
|
||||||
int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci);
|
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
|
#else
|
||||||
|
|
||||||
@ -100,6 +103,21 @@ static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
|
|||||||
return 0;
|
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
|
||||||
|
|
||||||
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */
|
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "bits.h"
|
#include "bits.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "otg.h"
|
#include "otg.h"
|
||||||
|
#include "otg_fsm.h"
|
||||||
|
|
||||||
/* control endpoint description */
|
/* control endpoint description */
|
||||||
static const struct usb_endpoint_descriptor
|
static const struct usb_endpoint_descriptor
|
||||||
@ -1644,6 +1645,13 @@ static int ci_udc_start(struct usb_gadget *gadget,
|
|||||||
return retval;
|
return retval;
|
||||||
|
|
||||||
ci->driver = driver;
|
ci->driver = driver;
|
||||||
|
|
||||||
|
/* Start otg fsm for B-device */
|
||||||
|
if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) {
|
||||||
|
ci_hdrc_otg_fsm_start(ci);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
pm_runtime_get_sync(&ci->gadget.dev);
|
pm_runtime_get_sync(&ci->gadget.dev);
|
||||||
if (ci->vbus_active) {
|
if (ci->vbus_active) {
|
||||||
spin_lock_irqsave(&ci->lock, flags);
|
spin_lock_irqsave(&ci->lock, flags);
|
||||||
|
Loading…
Reference in New Issue
Block a user