2014-04-23 15:56:44 +08:00
/*
* otg_fsm . c - ChipIdea USB IP core OTG FSM driver
*
* Copyright ( C ) 2014 Freescale Semiconductor , Inc .
*
* Author : Jun Li
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
/*
* This file mainly handles OTG fsm , it includes OTG fsm operations
* for HNP and SRP .
2014-04-23 15:56:50 +08:00
*
* TODO List
* - ADP
* - OTG test device
2014-04-23 15:56:44 +08:00
*/
# include <linux/usb/otg.h>
# include <linux/usb/gadget.h>
# include <linux/usb/hcd.h>
# include <linux/usb/chipidea.h>
2014-04-23 15:56:48 +08:00
# include <linux/regulator/consumer.h>
2014-04-23 15:56:44 +08:00
# include "ci.h"
# include "bits.h"
# include "otg.h"
# include "otg_fsm.h"
2014-04-23 15:56:49 +08:00
static struct ci_otg_fsm_timer * otg_timer_initializer
( struct ci_hdrc * ci , void ( * function ) ( void * , unsigned long ) ,
unsigned long expires , unsigned long data )
{
struct ci_otg_fsm_timer * timer ;
timer = devm_kzalloc ( ci - > dev , sizeof ( struct ci_otg_fsm_timer ) ,
GFP_KERNEL ) ;
if ( ! timer )
return NULL ;
timer - > function = function ;
timer - > expires = expires ;
timer - > data = data ;
return timer ;
}
2014-04-23 15:56:51 +08:00
/* Add for otg: interact with user space app */
static ssize_t
get_a_bus_req ( 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
set_a_bus_req ( 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 ;
}
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:51 +08:00
mutex_unlock ( & ci - > fsm . lock ) ;
return count ;
}
static DEVICE_ATTR ( a_bus_req , S_IRUGO | S_IWUSR , get_a_bus_req , set_a_bus_req ) ;
static ssize_t
get_a_bus_drop ( 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
set_a_bus_drop ( 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 ;
}
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:51 +08:00
mutex_unlock ( & ci - > fsm . lock ) ;
return count ;
}
static DEVICE_ATTR ( a_bus_drop , S_IRUGO | S_IWUSR , get_a_bus_drop ,
set_a_bus_drop ) ;
static ssize_t
get_b_bus_req ( 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
set_b_bus_req ( 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 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:51 +08:00
mutex_unlock ( & ci - > fsm . lock ) ;
return count ;
}
static DEVICE_ATTR ( b_bus_req , S_IRUGO | S_IWUSR , get_b_bus_req , set_b_bus_req ) ;
static ssize_t
set_a_clr_err ( 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 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:51 +08:00
mutex_unlock ( & ci - > fsm . lock ) ;
return count ;
}
static DEVICE_ATTR ( a_clr_err , S_IWUSR , NULL , set_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 struct attribute_group inputs_attr_group = {
. name = " inputs " ,
. attrs = inputs_attrs ,
} ;
2014-04-23 15:56:48 +08:00
/*
* Add timer to active timer list
*/
static void ci_otg_add_timer ( struct ci_hdrc * ci , enum ci_otg_fsm_timer_index t )
{
struct ci_otg_fsm_timer * tmp_timer ;
struct ci_otg_fsm_timer * timer = ci - > fsm_timer - > timer_list [ t ] ;
struct list_head * active_timers = & ci - > fsm_timer - > active_timers ;
if ( t > = NUM_CI_OTG_FSM_TIMERS )
return ;
/*
* Check if the timer is already in the active list ,
* if so update timer count
*/
list_for_each_entry ( tmp_timer , active_timers , list )
if ( tmp_timer = = timer ) {
timer - > count = timer - > expires ;
return ;
}
timer - > count = timer - > expires ;
list_add_tail ( & timer - > list , active_timers ) ;
/* Enable 1ms irq */
if ( ! ( hw_read_otgsc ( ci , OTGSC_1MSIE ) ) )
hw_write_otgsc ( ci , OTGSC_1MSIE , OTGSC_1MSIE ) ;
}
/*
* Remove timer from active timer list
*/
static void ci_otg_del_timer ( struct ci_hdrc * ci , enum ci_otg_fsm_timer_index t )
{
struct ci_otg_fsm_timer * tmp_timer , * del_tmp ;
struct ci_otg_fsm_timer * timer = ci - > fsm_timer - > timer_list [ t ] ;
struct list_head * active_timers = & ci - > fsm_timer - > active_timers ;
if ( t > = NUM_CI_OTG_FSM_TIMERS )
return ;
list_for_each_entry_safe ( tmp_timer , del_tmp , active_timers , list )
if ( tmp_timer = = timer )
list_del ( & timer - > list ) ;
/* Disable 1ms irq if there is no any active timer */
if ( list_empty ( active_timers ) )
hw_write_otgsc ( ci , OTGSC_1MSIE , 0 ) ;
}
2014-04-23 15:56:50 +08:00
/*
* Reduce timer count by 1 , and find timeout conditions .
* Called by otg 1 ms 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 ;
}
2014-04-23 15:56:49 +08:00
/* The timeout callback function to set time out bit */
static void set_tmout ( void * ptr , unsigned long indicator )
{
* ( int * ) indicator = 1 ;
}
static void set_tmout_and_fsm ( void * ptr , unsigned long indicator )
{
struct ci_hdrc * ci = ( struct ci_hdrc * ) ptr ;
set_tmout ( ci , indicator ) ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:49 +08:00
}
static void a_wait_vfall_tmout_func ( void * ptr , unsigned long indicator )
{
struct ci_hdrc * ci = ( struct ci_hdrc * ) ptr ;
set_tmout ( ci , indicator ) ;
/* Disable port power */
hw_write ( ci , OP_PORTSC , PORTSC_W1C_BITS | PORTSC_PP , 0 ) ;
/* Clear exsiting DP irq */
hw_write_otgsc ( ci , OTGSC_DPIS , OTGSC_DPIS ) ;
/* Enable data pulse irq */
hw_write_otgsc ( ci , OTGSC_DPIE , OTGSC_DPIE ) ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:49 +08:00
}
static void b_ase0_brst_tmout_func ( void * ptr , unsigned long indicator )
{
struct ci_hdrc * ci = ( struct ci_hdrc * ) ptr ;
set_tmout ( ci , indicator ) ;
if ( ! hw_read_otgsc ( ci , OTGSC_BSV ) )
ci - > fsm . b_sess_vld = 0 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:49 +08:00
}
static void b_ssend_srp_tmout_func ( 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 */
2014-05-23 08:12:49 +08:00
if ( ci - > transceiver - > state = = OTG_STATE_B_IDLE )
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:49 +08:00
}
static void b_sess_vld_tmout_func ( void * ptr , unsigned long indicator )
{
struct ci_hdrc * ci = ( struct ci_hdrc * ) ptr ;
/* Check if A detached */
if ( ! ( hw_read_otgsc ( ci , OTGSC_BSV ) ) ) {
ci - > fsm . b_sess_vld = 0 ;
ci_otg_add_timer ( ci , B_SSEND_SRP ) ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:49 +08:00
}
}
static 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 ;
hw_write_otgsc ( ci , OTGSC_HABA , 0 ) ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:49 +08:00
}
/* Initialize timers */
static int ci_otg_init_timers ( struct ci_hdrc * ci )
{
struct otg_fsm * fsm = & ci - > fsm ;
/* FSM used timers */
ci - > fsm_timer - > timer_list [ A_WAIT_VRISE ] =
otg_timer_initializer ( ci , & set_tmout_and_fsm , TA_WAIT_VRISE ,
( unsigned long ) & fsm - > a_wait_vrise_tmout ) ;
if ( ci - > fsm_timer - > timer_list [ A_WAIT_VRISE ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ A_WAIT_VFALL ] =
otg_timer_initializer ( ci , & a_wait_vfall_tmout_func ,
TA_WAIT_VFALL , ( unsigned long ) & fsm - > a_wait_vfall_tmout ) ;
if ( ci - > fsm_timer - > timer_list [ A_WAIT_VFALL ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ A_WAIT_BCON ] =
otg_timer_initializer ( ci , & set_tmout_and_fsm , TA_WAIT_BCON ,
( unsigned long ) & fsm - > a_wait_bcon_tmout ) ;
if ( ci - > fsm_timer - > timer_list [ A_WAIT_BCON ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ A_AIDL_BDIS ] =
otg_timer_initializer ( ci , & set_tmout_and_fsm , TA_AIDL_BDIS ,
( unsigned long ) & fsm - > a_aidl_bdis_tmout ) ;
if ( ci - > fsm_timer - > timer_list [ A_AIDL_BDIS ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ A_BIDL_ADIS ] =
otg_timer_initializer ( ci , & set_tmout_and_fsm , TA_BIDL_ADIS ,
( unsigned long ) & fsm - > a_bidl_adis_tmout ) ;
if ( ci - > fsm_timer - > timer_list [ A_BIDL_ADIS ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ B_ASE0_BRST ] =
otg_timer_initializer ( ci , & b_ase0_brst_tmout_func , TB_ASE0_BRST ,
( unsigned long ) & fsm - > b_ase0_brst_tmout ) ;
if ( ci - > fsm_timer - > timer_list [ B_ASE0_BRST ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ B_SE0_SRP ] =
otg_timer_initializer ( ci , & set_tmout_and_fsm , TB_SE0_SRP ,
( unsigned long ) & fsm - > b_se0_srp ) ;
if ( ci - > fsm_timer - > timer_list [ B_SE0_SRP ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ B_SSEND_SRP ] =
otg_timer_initializer ( ci , & b_ssend_srp_tmout_func , TB_SSEND_SRP ,
( unsigned long ) & fsm - > b_ssend_srp ) ;
if ( ci - > fsm_timer - > timer_list [ B_SSEND_SRP ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ B_SRP_FAIL ] =
otg_timer_initializer ( ci , & set_tmout , TB_SRP_FAIL ,
( unsigned long ) & fsm - > b_srp_done ) ;
if ( ci - > fsm_timer - > timer_list [ B_SRP_FAIL ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ B_DATA_PLS ] =
otg_timer_initializer ( ci , & b_data_pulse_end , TB_DATA_PLS , 0 ) ;
if ( ci - > fsm_timer - > timer_list [ B_DATA_PLS ] = = NULL )
return - ENOMEM ;
ci - > fsm_timer - > timer_list [ B_SESS_VLD ] = otg_timer_initializer ( ci ,
& b_sess_vld_tmout_func , TB_SESS_VLD , 0 ) ;
if ( ci - > fsm_timer - > timer_list [ B_SESS_VLD ] = = NULL )
return - ENOMEM ;
return 0 ;
}
2014-04-23 15:56:48 +08:00
/* -------------------------------------------------------------*/
/* 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 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 ;
}
}
/* 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 ) ;
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 .
* This is controlled through suspend / resume the port .
* In host mode , controller will automatically send SOF .
* Suspend will block the data on the port .
*/
static void ci_otg_loc_sof ( struct otg_fsm * fsm , int on )
{
struct ci_hdrc * ci = container_of ( fsm , struct ci_hdrc , fsm ) ;
if ( on )
hw_write ( ci , OP_PORTSC , PORTSC_W1C_BITS | PORTSC_FPR ,
PORTSC_FPR ) ;
else
hw_write ( ci , OP_PORTSC , PORTSC_W1C_BITS | PORTSC_SUSP ,
PORTSC_SUSP ) ;
}
/*
* 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 ) ;
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 ) ;
mutex_unlock ( & fsm - > lock ) ;
if ( on ) {
ci_role_stop ( ci ) ;
ci_role_start ( ci , CI_ROLE_HOST ) ;
} else {
ci_role_stop ( ci ) ;
hw_device_reset ( ci , USBMODE_CM_DC ) ;
ci_role_start ( ci , CI_ROLE_GADGET ) ;
}
mutex_lock ( & fsm - > lock ) ;
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 ) ;
mutex_unlock ( & fsm - > lock ) ;
if ( on )
usb_gadget_vbus_connect ( & ci - > gadget ) ;
else
usb_gadget_vbus_disconnect ( & ci - > gadget ) ;
mutex_lock ( & fsm - > lock ) ;
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 ,
} ;
2014-04-23 15:56:50 +08:00
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 ) | |
2014-05-23 08:12:49 +08:00
( ci - > fsm . power_up ) )
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
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
*/
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
}
}
}
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 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
}
break ;
case OTG_STATE_B_IDLE :
if ( otg_bsess_vld & & ( intr_sts & USBi_PCI ) & & port_conn ) {
fsm - > b_sess_vld = 1 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
}
break ;
case OTG_STATE_B_PERIPHERAL :
if ( ( intr_sts & USBi_SLI ) & & port_conn & & otg_bsess_vld ) {
fsm - > a_bus_suspend = 1 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
} 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 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
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 ;
}
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
}
break ;
case OTG_STATE_A_HOST :
if ( ( intr_sts & USBi_PCI ) & & ! port_conn ) {
fsm - > b_conn = 0 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
}
break ;
case OTG_STATE_B_WAIT_ACON :
if ( ( intr_sts & USBi_PCI ) & & port_conn ) {
fsm - > a_conn = 1 ;
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
}
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 ;
}
}
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
return IRQ_HANDLED ;
}
ci_otg_fsm_event ( ci ) ;
return retval ;
}
void ci_hdrc_otg_fsm_start ( struct ci_hdrc * ci )
{
2014-05-23 08:12:49 +08:00
ci_otg_queue_work ( ci ) ;
2014-04-23 15:56:50 +08:00
}
2014-04-23 15:56:44 +08:00
int ci_hdrc_otg_fsm_init ( struct ci_hdrc * ci )
{
2014-04-23 15:56:49 +08:00
int retval = 0 ;
2014-04-23 15:56:44 +08:00
struct usb_otg * otg ;
otg = devm_kzalloc ( ci - > dev ,
sizeof ( struct usb_otg ) , GFP_KERNEL ) ;
if ( ! otg ) {
dev_err ( ci - > dev ,
" Failed to allocate usb_otg structure for ci hdrc otg! \n " ) ;
return - ENOMEM ;
}
otg - > phy = ci - > transceiver ;
otg - > gadget = & ci - > gadget ;
ci - > fsm . otg = otg ;
ci - > transceiver - > otg = ci - > fsm . otg ;
ci - > fsm . power_up = 1 ;
ci - > fsm . id = hw_read_otgsc ( ci , OTGSC_ID ) ? 1 : 0 ;
ci - > transceiver - > state = OTG_STATE_UNDEFINED ;
2014-04-23 15:56:48 +08:00
ci - > fsm . ops = & ci_otg_ops ;
2014-04-23 15:56:44 +08:00
mutex_init ( & ci - > fsm . lock ) ;
2014-04-23 15:56:49 +08:00
ci - > fsm_timer = devm_kzalloc ( ci - > dev ,
sizeof ( struct ci_otg_fsm_timer_list ) , GFP_KERNEL ) ;
if ( ! ci - > fsm_timer ) {
dev_err ( ci - > dev ,
" Failed to allocate timer structure for ci hdrc otg! \n " ) ;
return - ENOMEM ;
}
INIT_LIST_HEAD ( & ci - > fsm_timer - > active_timers ) ;
retval = ci_otg_init_timers ( ci ) ;
if ( retval ) {
dev_err ( ci - > dev , " Couldn't init OTG timers \n " ) ;
return retval ;
}
2014-04-23 15:56:51 +08:00
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 ;
}
2014-04-23 15:56:44 +08:00
/* 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 ;
}
2014-04-23 15:56:51 +08:00
void ci_hdrc_otg_fsm_remove ( struct ci_hdrc * ci )
{
sysfs_remove_group ( & ci - > dev - > kobj , & inputs_attr_group ) ;
}