2012-10-24 12:39:53 +03:00
/*
* Copyright ( C ) 2012 Texas Instruments
* Author : Tomi Valkeinen < tomi . valkeinen @ ti . com >
*
* 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 program is distributed in the hope that it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License along with
* this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# define DSS_SUBSYS_NAME "APPLY"
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/jiffies.h>
# include <linux/delay.h>
2012-10-10 15:55:19 +03:00
# include <linux/interrupt.h>
# include <linux/seq_file.h>
2012-10-24 12:39:53 +03:00
# include <video/omapdss.h>
# include "dss.h"
# include "dss_features.h"
# include "dispc-compat.h"
2012-10-10 15:55:19 +03:00
# define DISPC_IRQ_MASK_ERROR (DISPC_IRQ_GFX_FIFO_UNDERFLOW | \
DISPC_IRQ_OCP_ERR | \
DISPC_IRQ_VID1_FIFO_UNDERFLOW | \
DISPC_IRQ_VID2_FIFO_UNDERFLOW | \
DISPC_IRQ_SYNC_LOST | \
DISPC_IRQ_SYNC_LOST_DIGIT )
# define DISPC_MAX_NR_ISRS 8
struct omap_dispc_isr_data {
omap_dispc_isr_t isr ;
void * arg ;
u32 mask ;
} ;
struct dispc_irq_stats {
unsigned long last_reset ;
unsigned irq_count ;
unsigned irqs [ 32 ] ;
} ;
static struct {
spinlock_t irq_lock ;
u32 irq_error_mask ;
struct omap_dispc_isr_data registered_isr [ DISPC_MAX_NR_ISRS ] ;
u32 error_irqs ;
struct work_struct error_work ;
# ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
spinlock_t irq_stats_lock ;
struct dispc_irq_stats irq_stats ;
# endif
} dispc_compat ;
# ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
static void dispc_dump_irqs ( struct seq_file * s )
{
unsigned long flags ;
struct dispc_irq_stats stats ;
spin_lock_irqsave ( & dispc_compat . irq_stats_lock , flags ) ;
stats = dispc_compat . irq_stats ;
memset ( & dispc_compat . irq_stats , 0 , sizeof ( dispc_compat . irq_stats ) ) ;
dispc_compat . irq_stats . last_reset = jiffies ;
spin_unlock_irqrestore ( & dispc_compat . irq_stats_lock , flags ) ;
seq_printf ( s , " period %u ms \n " ,
jiffies_to_msecs ( jiffies - stats . last_reset ) ) ;
seq_printf ( s , " irqs %d \n " , stats . irq_count ) ;
# define PIS(x) \
seq_printf ( s , " %-20s %10d \n " , # x , stats . irqs [ ffs ( DISPC_IRQ_ # # x ) - 1 ] ) ;
PIS ( FRAMEDONE ) ;
PIS ( VSYNC ) ;
PIS ( EVSYNC_EVEN ) ;
PIS ( EVSYNC_ODD ) ;
PIS ( ACBIAS_COUNT_STAT ) ;
PIS ( PROG_LINE_NUM ) ;
PIS ( GFX_FIFO_UNDERFLOW ) ;
PIS ( GFX_END_WIN ) ;
PIS ( PAL_GAMMA_MASK ) ;
PIS ( OCP_ERR ) ;
PIS ( VID1_FIFO_UNDERFLOW ) ;
PIS ( VID1_END_WIN ) ;
PIS ( VID2_FIFO_UNDERFLOW ) ;
PIS ( VID2_END_WIN ) ;
if ( dss_feat_get_num_ovls ( ) > 3 ) {
PIS ( VID3_FIFO_UNDERFLOW ) ;
PIS ( VID3_END_WIN ) ;
}
PIS ( SYNC_LOST ) ;
PIS ( SYNC_LOST_DIGIT ) ;
PIS ( WAKEUP ) ;
if ( dss_has_feature ( FEAT_MGR_LCD2 ) ) {
PIS ( FRAMEDONE2 ) ;
PIS ( VSYNC2 ) ;
PIS ( ACBIAS_COUNT_STAT2 ) ;
PIS ( SYNC_LOST2 ) ;
}
if ( dss_has_feature ( FEAT_MGR_LCD3 ) ) {
PIS ( FRAMEDONE3 ) ;
PIS ( VSYNC3 ) ;
PIS ( ACBIAS_COUNT_STAT3 ) ;
PIS ( SYNC_LOST3 ) ;
}
# undef PIS
}
# endif
/* dispc.irq_lock has to be locked by the caller */
static void _omap_dispc_set_irqs ( void )
{
u32 mask ;
int i ;
struct omap_dispc_isr_data * isr_data ;
mask = dispc_compat . irq_error_mask ;
for ( i = 0 ; i < DISPC_MAX_NR_ISRS ; i + + ) {
isr_data = & dispc_compat . registered_isr [ i ] ;
if ( isr_data - > isr = = NULL )
continue ;
mask | = isr_data - > mask ;
}
dispc_write_irqenable ( mask ) ;
}
int omap_dispc_register_isr ( omap_dispc_isr_t isr , void * arg , u32 mask )
{
int i ;
int ret ;
unsigned long flags ;
struct omap_dispc_isr_data * isr_data ;
if ( isr = = NULL )
return - EINVAL ;
spin_lock_irqsave ( & dispc_compat . irq_lock , flags ) ;
/* check for duplicate entry */
for ( i = 0 ; i < DISPC_MAX_NR_ISRS ; i + + ) {
isr_data = & dispc_compat . registered_isr [ i ] ;
if ( isr_data - > isr = = isr & & isr_data - > arg = = arg & &
isr_data - > mask = = mask ) {
ret = - EINVAL ;
goto err ;
}
}
isr_data = NULL ;
ret = - EBUSY ;
for ( i = 0 ; i < DISPC_MAX_NR_ISRS ; i + + ) {
isr_data = & dispc_compat . registered_isr [ i ] ;
if ( isr_data - > isr ! = NULL )
continue ;
isr_data - > isr = isr ;
isr_data - > arg = arg ;
isr_data - > mask = mask ;
ret = 0 ;
break ;
}
if ( ret )
goto err ;
_omap_dispc_set_irqs ( ) ;
spin_unlock_irqrestore ( & dispc_compat . irq_lock , flags ) ;
return 0 ;
err :
spin_unlock_irqrestore ( & dispc_compat . irq_lock , flags ) ;
return ret ;
}
EXPORT_SYMBOL ( omap_dispc_register_isr ) ;
int omap_dispc_unregister_isr ( omap_dispc_isr_t isr , void * arg , u32 mask )
{
int i ;
unsigned long flags ;
int ret = - EINVAL ;
struct omap_dispc_isr_data * isr_data ;
spin_lock_irqsave ( & dispc_compat . irq_lock , flags ) ;
for ( i = 0 ; i < DISPC_MAX_NR_ISRS ; i + + ) {
isr_data = & dispc_compat . registered_isr [ i ] ;
if ( isr_data - > isr ! = isr | | isr_data - > arg ! = arg | |
isr_data - > mask ! = mask )
continue ;
/* found the correct isr */
isr_data - > isr = NULL ;
isr_data - > arg = NULL ;
isr_data - > mask = 0 ;
ret = 0 ;
break ;
}
if ( ret = = 0 )
_omap_dispc_set_irqs ( ) ;
spin_unlock_irqrestore ( & dispc_compat . irq_lock , flags ) ;
return ret ;
}
EXPORT_SYMBOL ( omap_dispc_unregister_isr ) ;
static void print_irq_status ( u32 status )
{
if ( ( status & dispc_compat . irq_error_mask ) = = 0 )
return ;
# define PIS(x) (status & DISPC_IRQ_##x) ? (#x " ") : ""
pr_debug ( " DISPC IRQ: 0x%x: %s%s%s%s%s%s%s%s%s \n " ,
status ,
PIS ( OCP_ERR ) ,
PIS ( GFX_FIFO_UNDERFLOW ) ,
PIS ( VID1_FIFO_UNDERFLOW ) ,
PIS ( VID2_FIFO_UNDERFLOW ) ,
dss_feat_get_num_ovls ( ) > 3 ? PIS ( VID3_FIFO_UNDERFLOW ) : " " ,
PIS ( SYNC_LOST ) ,
PIS ( SYNC_LOST_DIGIT ) ,
dss_has_feature ( FEAT_MGR_LCD2 ) ? PIS ( SYNC_LOST2 ) : " " ,
dss_has_feature ( FEAT_MGR_LCD3 ) ? PIS ( SYNC_LOST3 ) : " " ) ;
# undef PIS
}
/* Called from dss.c. Note that we don't touch clocks here,
* but we presume they are on because we got an IRQ . However ,
* an irq handler may turn the clocks off , so we may not have
* clock later in the function . */
static irqreturn_t omap_dispc_irq_handler ( int irq , void * arg )
{
int i ;
u32 irqstatus , irqenable ;
u32 handledirqs = 0 ;
u32 unhandled_errors ;
struct omap_dispc_isr_data * isr_data ;
struct omap_dispc_isr_data registered_isr [ DISPC_MAX_NR_ISRS ] ;
spin_lock ( & dispc_compat . irq_lock ) ;
irqstatus = dispc_read_irqstatus ( ) ;
irqenable = dispc_read_irqenable ( ) ;
/* IRQ is not for us */
if ( ! ( irqstatus & irqenable ) ) {
spin_unlock ( & dispc_compat . irq_lock ) ;
return IRQ_NONE ;
}
# ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
spin_lock ( & dispc_compat . irq_stats_lock ) ;
dispc_compat . irq_stats . irq_count + + ;
dss_collect_irq_stats ( irqstatus , dispc_compat . irq_stats . irqs ) ;
spin_unlock ( & dispc_compat . irq_stats_lock ) ;
# endif
print_irq_status ( irqstatus ) ;
/* Ack the interrupt. Do it here before clocks are possibly turned
* off */
dispc_clear_irqstatus ( irqstatus ) ;
/* flush posted write */
dispc_read_irqstatus ( ) ;
/* make a copy and unlock, so that isrs can unregister
* themselves */
memcpy ( registered_isr , dispc_compat . registered_isr ,
sizeof ( registered_isr ) ) ;
spin_unlock ( & dispc_compat . irq_lock ) ;
for ( i = 0 ; i < DISPC_MAX_NR_ISRS ; i + + ) {
isr_data = & registered_isr [ i ] ;
if ( ! isr_data - > isr )
continue ;
if ( isr_data - > mask & irqstatus ) {
isr_data - > isr ( isr_data - > arg , irqstatus ) ;
handledirqs | = isr_data - > mask ;
}
}
spin_lock ( & dispc_compat . irq_lock ) ;
unhandled_errors = irqstatus & ~ handledirqs & dispc_compat . irq_error_mask ;
if ( unhandled_errors ) {
dispc_compat . error_irqs | = unhandled_errors ;
dispc_compat . irq_error_mask & = ~ unhandled_errors ;
_omap_dispc_set_irqs ( ) ;
schedule_work ( & dispc_compat . error_work ) ;
}
spin_unlock ( & dispc_compat . irq_lock ) ;
return IRQ_HANDLED ;
}
static void dispc_error_worker ( struct work_struct * work )
{
int i ;
u32 errors ;
unsigned long flags ;
static const unsigned fifo_underflow_bits [ ] = {
DISPC_IRQ_GFX_FIFO_UNDERFLOW ,
DISPC_IRQ_VID1_FIFO_UNDERFLOW ,
DISPC_IRQ_VID2_FIFO_UNDERFLOW ,
DISPC_IRQ_VID3_FIFO_UNDERFLOW ,
} ;
spin_lock_irqsave ( & dispc_compat . irq_lock , flags ) ;
errors = dispc_compat . error_irqs ;
dispc_compat . error_irqs = 0 ;
spin_unlock_irqrestore ( & dispc_compat . irq_lock , flags ) ;
dispc_runtime_get ( ) ;
for ( i = 0 ; i < omap_dss_get_num_overlays ( ) ; + + i ) {
struct omap_overlay * ovl ;
unsigned bit ;
ovl = omap_dss_get_overlay ( i ) ;
bit = fifo_underflow_bits [ i ] ;
if ( bit & errors ) {
DSSERR ( " FIFO UNDERFLOW on %s, disabling the overlay \n " ,
ovl - > name ) ;
2013-04-25 11:28:15 +03:00
ovl - > disable ( ovl ) ;
2012-10-10 15:55:19 +03:00
msleep ( 50 ) ;
}
}
for ( i = 0 ; i < omap_dss_get_num_overlay_managers ( ) ; + + i ) {
struct omap_overlay_manager * mgr ;
unsigned bit ;
mgr = omap_dss_get_overlay_manager ( i ) ;
bit = dispc_mgr_get_sync_lost_irq ( i ) ;
if ( bit & errors ) {
int j ;
DSSERR ( " SYNC_LOST on channel %s, restarting the output "
" with video overlays disabled \n " ,
mgr - > name ) ;
dss_mgr_disable ( mgr ) ;
for ( j = 0 ; j < omap_dss_get_num_overlays ( ) ; + + j ) {
struct omap_overlay * ovl ;
ovl = omap_dss_get_overlay ( j ) ;
if ( ovl - > id ! = OMAP_DSS_GFX & &
ovl - > manager = = mgr )
ovl - > disable ( ovl ) ;
}
dss_mgr_enable ( mgr ) ;
}
}
if ( errors & DISPC_IRQ_OCP_ERR ) {
DSSERR ( " OCP_ERR \n " ) ;
for ( i = 0 ; i < omap_dss_get_num_overlay_managers ( ) ; + + i ) {
struct omap_overlay_manager * mgr ;
mgr = omap_dss_get_overlay_manager ( i ) ;
dss_mgr_disable ( mgr ) ;
}
}
spin_lock_irqsave ( & dispc_compat . irq_lock , flags ) ;
dispc_compat . irq_error_mask | = errors ;
_omap_dispc_set_irqs ( ) ;
spin_unlock_irqrestore ( & dispc_compat . irq_lock , flags ) ;
dispc_runtime_put ( ) ;
}
int dss_dispc_initialize_irq ( void )
{
int r ;
# ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
spin_lock_init ( & dispc_compat . irq_stats_lock ) ;
dispc_compat . irq_stats . last_reset = jiffies ;
dss_debugfs_create_file ( " dispc_irq " , dispc_dump_irqs ) ;
# endif
spin_lock_init ( & dispc_compat . irq_lock ) ;
memset ( dispc_compat . registered_isr , 0 ,
sizeof ( dispc_compat . registered_isr ) ) ;
dispc_compat . irq_error_mask = DISPC_IRQ_MASK_ERROR ;
if ( dss_has_feature ( FEAT_MGR_LCD2 ) )
dispc_compat . irq_error_mask | = DISPC_IRQ_SYNC_LOST2 ;
if ( dss_has_feature ( FEAT_MGR_LCD3 ) )
dispc_compat . irq_error_mask | = DISPC_IRQ_SYNC_LOST3 ;
if ( dss_feat_get_num_ovls ( ) > 3 )
dispc_compat . irq_error_mask | = DISPC_IRQ_VID3_FIFO_UNDERFLOW ;
/*
* there ' s SYNC_LOST_DIGIT waiting after enabling the DSS ,
* so clear it
*/
dispc_clear_irqstatus ( dispc_read_irqstatus ( ) ) ;
INIT_WORK ( & dispc_compat . error_work , dispc_error_worker ) ;
_omap_dispc_set_irqs ( ) ;
r = dispc_request_irq ( omap_dispc_irq_handler , & dispc_compat ) ;
if ( r ) {
DSSERR ( " dispc_request_irq failed \n " ) ;
return r ;
}
return 0 ;
}
void dss_dispc_uninitialize_irq ( void )
{
dispc_free_irq ( & dispc_compat ) ;
}
2012-10-24 12:39:53 +03:00
static void dispc_mgr_disable_isr ( void * data , u32 mask )
{
struct completion * compl = data ;
complete ( compl ) ;
}
static void dispc_mgr_enable_lcd_out ( enum omap_channel channel )
{
dispc_mgr_enable ( channel , true ) ;
}
static void dispc_mgr_disable_lcd_out ( enum omap_channel channel )
{
DECLARE_COMPLETION_ONSTACK ( framedone_compl ) ;
int r ;
u32 irq ;
if ( dispc_mgr_is_enabled ( channel ) = = false )
return ;
/*
* When we disable LCD output , we need to wait for FRAMEDONE to know
* that DISPC has finished with the LCD output .
*/
irq = dispc_mgr_get_framedone_irq ( channel ) ;
r = omap_dispc_register_isr ( dispc_mgr_disable_isr , & framedone_compl ,
irq ) ;
if ( r )
DSSERR ( " failed to register FRAMEDONE isr \n " ) ;
dispc_mgr_enable ( channel , false ) ;
/* if we couldn't register for framedone, just sleep and exit */
if ( r ) {
msleep ( 100 ) ;
return ;
}
if ( ! wait_for_completion_timeout ( & framedone_compl ,
msecs_to_jiffies ( 100 ) ) )
DSSERR ( " timeout waiting for FRAME DONE \n " ) ;
r = omap_dispc_unregister_isr ( dispc_mgr_disable_isr , & framedone_compl ,
irq ) ;
if ( r )
DSSERR ( " failed to unregister FRAMEDONE isr \n " ) ;
}
static void dispc_digit_out_enable_isr ( void * data , u32 mask )
{
struct completion * compl = data ;
/* ignore any sync lost interrupts */
if ( mask & ( DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD ) )
complete ( compl ) ;
}
static void dispc_mgr_enable_digit_out ( void )
{
DECLARE_COMPLETION_ONSTACK ( vsync_compl ) ;
int r ;
u32 irq_mask ;
if ( dispc_mgr_is_enabled ( OMAP_DSS_CHANNEL_DIGIT ) = = true )
return ;
/*
* Digit output produces some sync lost interrupts during the first
* frame when enabling . Those need to be ignored , so we register for the
* sync lost irq to prevent the error handler from triggering .
*/
irq_mask = dispc_mgr_get_vsync_irq ( OMAP_DSS_CHANNEL_DIGIT ) |
dispc_mgr_get_sync_lost_irq ( OMAP_DSS_CHANNEL_DIGIT ) ;
r = omap_dispc_register_isr ( dispc_digit_out_enable_isr , & vsync_compl ,
irq_mask ) ;
if ( r ) {
DSSERR ( " failed to register %x isr \n " , irq_mask ) ;
return ;
}
dispc_mgr_enable ( OMAP_DSS_CHANNEL_DIGIT , true ) ;
/* wait for the first evsync */
if ( ! wait_for_completion_timeout ( & vsync_compl , msecs_to_jiffies ( 100 ) ) )
DSSERR ( " timeout waiting for digit out to start \n " ) ;
r = omap_dispc_unregister_isr ( dispc_digit_out_enable_isr , & vsync_compl ,
irq_mask ) ;
if ( r )
DSSERR ( " failed to unregister %x isr \n " , irq_mask ) ;
}
static void dispc_mgr_disable_digit_out ( void )
{
DECLARE_COMPLETION_ONSTACK ( framedone_compl ) ;
int r , i ;
u32 irq_mask ;
int num_irqs ;
if ( dispc_mgr_is_enabled ( OMAP_DSS_CHANNEL_DIGIT ) = = false )
return ;
/*
* When we disable the digit output , we need to wait for FRAMEDONE to
* know that DISPC has finished with the output .
*/
irq_mask = dispc_mgr_get_framedone_irq ( OMAP_DSS_CHANNEL_DIGIT ) ;
num_irqs = 1 ;
if ( ! irq_mask ) {
/*
* omap 2 / 3 don ' t have framedone irq for TV , so we need to use
* vsyncs for this .
*/
irq_mask = dispc_mgr_get_vsync_irq ( OMAP_DSS_CHANNEL_DIGIT ) ;
/*
* We need to wait for both even and odd vsyncs . Note that this
* is not totally reliable , as we could get a vsync interrupt
* before we disable the output , which leads to timeout in the
* wait_for_completion .
*/
num_irqs = 2 ;
}
r = omap_dispc_register_isr ( dispc_mgr_disable_isr , & framedone_compl ,
irq_mask ) ;
if ( r )
DSSERR ( " failed to register %x isr \n " , irq_mask ) ;
dispc_mgr_enable ( OMAP_DSS_CHANNEL_DIGIT , false ) ;
/* if we couldn't register the irq, just sleep and exit */
if ( r ) {
msleep ( 100 ) ;
return ;
}
for ( i = 0 ; i < num_irqs ; + + i ) {
if ( ! wait_for_completion_timeout ( & framedone_compl ,
msecs_to_jiffies ( 100 ) ) )
DSSERR ( " timeout waiting for digit out to stop \n " ) ;
}
r = omap_dispc_unregister_isr ( dispc_mgr_disable_isr , & framedone_compl ,
irq_mask ) ;
if ( r )
DSSERR ( " failed to unregister %x isr \n " , irq_mask ) ;
}
void dispc_mgr_enable_sync ( enum omap_channel channel )
{
if ( dss_mgr_is_lcd ( channel ) )
dispc_mgr_enable_lcd_out ( channel ) ;
else if ( channel = = OMAP_DSS_CHANNEL_DIGIT )
dispc_mgr_enable_digit_out ( ) ;
else
WARN_ON ( 1 ) ;
}
void dispc_mgr_disable_sync ( enum omap_channel channel )
{
if ( dss_mgr_is_lcd ( channel ) )
dispc_mgr_disable_lcd_out ( channel ) ;
else if ( channel = = OMAP_DSS_CHANNEL_DIGIT )
dispc_mgr_disable_digit_out ( ) ;
else
WARN_ON ( 1 ) ;
}
2012-10-10 14:03:11 +03:00
int omap_dispc_wait_for_irq_interruptible_timeout ( u32 irqmask ,
unsigned long timeout )
{
void dispc_irq_wait_handler ( void * data , u32 mask )
{
complete ( ( struct completion * ) data ) ;
}
int r ;
DECLARE_COMPLETION_ONSTACK ( completion ) ;
r = omap_dispc_register_isr ( dispc_irq_wait_handler , & completion ,
irqmask ) ;
if ( r )
return r ;
timeout = wait_for_completion_interruptible_timeout ( & completion ,
timeout ) ;
omap_dispc_unregister_isr ( dispc_irq_wait_handler , & completion , irqmask ) ;
if ( timeout = = 0 )
return - ETIMEDOUT ;
if ( timeout = = - ERESTARTSYS )
return - ERESTARTSYS ;
return 0 ;
}