2008-07-24 08:31:24 +04:00
/*
* SuperH Mobile LCDC Framebuffer
*
* Copyright ( c ) 2008 Magnus Damm
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*/
2011-09-07 13:09:26 +04:00
# include <linux/atomic.h>
# include <linux/backlight.h>
2008-07-24 08:31:24 +04:00
# include <linux/clk.h>
2011-09-07 13:09:26 +04:00
# include <linux/console.h>
2011-12-12 21:43:16 +04:00
# include <linux/ctype.h>
2008-07-24 08:31:24 +04:00
# include <linux/dma-mapping.h>
2011-09-07 13:09:26 +04:00
# include <linux/delay.h>
# include <linux/gpio.h>
# include <linux/init.h>
2008-12-19 09:34:41 +03:00
# include <linux/interrupt.h>
2010-02-15 16:57:49 +03:00
# include <linux/ioctl.h>
2011-09-07 13:09:26 +04:00
# include <linux/kernel.h>
# include <linux/mm.h>
2011-07-04 00:17:28 +04:00
# include <linux/module.h>
2011-09-07 13:09:26 +04:00
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/slab.h>
# include <linux/videodev2.h>
# include <linux/vmalloc.h>
2008-10-01 11:24:32 +04:00
# include <video/sh_mobile_lcdc.h>
2011-07-13 14:13:47 +04:00
# include <video/sh_mobile_meram.h>
2008-07-24 08:31:24 +04:00
2010-09-03 11:20:23 +04:00
# include "sh_mobile_lcdcfb.h"
2011-12-12 21:43:16 +04:00
/* ----------------------------------------------------------------------------
* Overlay register definitions
*/
# define LDBCR 0xb00
# define LDBCR_UPC(n) (1 << ((n) + 16))
# define LDBCR_UPF(n) (1 << ((n) + 8))
# define LDBCR_UPD(n) (1 << ((n) + 0))
# define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00)
# define LDBBSIFR_EN (1 << 31)
# define LDBBSIFR_VS (1 << 29)
# define LDBBSIFR_BRSEL (1 << 28)
# define LDBBSIFR_MX (1 << 27)
# define LDBBSIFR_MY (1 << 26)
# define LDBBSIFR_CV3 (3 << 24)
# define LDBBSIFR_CV2 (2 << 24)
# define LDBBSIFR_CV1 (1 << 24)
# define LDBBSIFR_CV0 (0 << 24)
# define LDBBSIFR_CV_MASK (3 << 24)
# define LDBBSIFR_LAY_MASK (0xff << 16)
# define LDBBSIFR_LAY_SHIFT 16
# define LDBBSIFR_ROP3_MASK (0xff << 16)
# define LDBBSIFR_ROP3_SHIFT 16
# define LDBBSIFR_AL_PL8 (3 << 14)
# define LDBBSIFR_AL_PL1 (2 << 14)
# define LDBBSIFR_AL_PK (1 << 14)
# define LDBBSIFR_AL_1 (0 << 14)
# define LDBBSIFR_AL_MASK (3 << 14)
# define LDBBSIFR_SWPL (1 << 10)
# define LDBBSIFR_SWPW (1 << 9)
# define LDBBSIFR_SWPB (1 << 8)
# define LDBBSIFR_RY (1 << 7)
# define LDBBSIFR_CHRR_420 (2 << 0)
# define LDBBSIFR_CHRR_422 (1 << 0)
# define LDBBSIFR_CHRR_444 (0 << 0)
# define LDBBSIFR_RPKF_ARGB32 (0x00 << 0)
# define LDBBSIFR_RPKF_RGB16 (0x03 << 0)
# define LDBBSIFR_RPKF_RGB24 (0x0b << 0)
# define LDBBSIFR_RPKF_MASK (0x1f << 0)
# define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04)
# define LDBBSSZR_BVSS_MASK (0xfff << 16)
# define LDBBSSZR_BVSS_SHIFT 16
# define LDBBSSZR_BHSS_MASK (0xfff << 0)
# define LDBBSSZR_BHSS_SHIFT 0
# define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08)
# define LDBBLOCR_CVLC_MASK (0xfff << 16)
# define LDBBLOCR_CVLC_SHIFT 16
# define LDBBLOCR_CHLC_MASK (0xfff << 0)
# define LDBBLOCR_CHLC_SHIFT 0
# define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c)
# define LDBBSMWR_BSMWA_MASK (0xffff << 16)
# define LDBBSMWR_BSMWA_SHIFT 16
# define LDBBSMWR_BSMW_MASK (0xffff << 0)
# define LDBBSMWR_BSMW_SHIFT 0
# define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10)
# define LDBBSAYR_FG1A_MASK (0xff << 24)
# define LDBBSAYR_FG1A_SHIFT 24
# define LDBBSAYR_FG1R_MASK (0xff << 16)
# define LDBBSAYR_FG1R_SHIFT 16
# define LDBBSAYR_FG1G_MASK (0xff << 8)
# define LDBBSAYR_FG1G_SHIFT 8
# define LDBBSAYR_FG1B_MASK (0xff << 0)
# define LDBBSAYR_FG1B_SHIFT 0
# define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14)
# define LDBBSACR_FG2A_MASK (0xff << 24)
# define LDBBSACR_FG2A_SHIFT 24
# define LDBBSACR_FG2R_MASK (0xff << 16)
# define LDBBSACR_FG2R_SHIFT 16
# define LDBBSACR_FG2G_MASK (0xff << 8)
# define LDBBSACR_FG2G_SHIFT 8
# define LDBBSACR_FG2B_MASK (0xff << 0)
# define LDBBSACR_FG2B_SHIFT 0
# define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18)
# define LDBBSAAR_AP_MASK (0xff << 24)
# define LDBBSAAR_AP_SHIFT 24
# define LDBBSAAR_R_MASK (0xff << 16)
# define LDBBSAAR_R_SHIFT 16
# define LDBBSAAR_GY_MASK (0xff << 8)
# define LDBBSAAR_GY_SHIFT 8
# define LDBBSAAR_B_MASK (0xff << 0)
# define LDBBSAAR_B_SHIFT 0
# define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c)
# define LDBBPPCR_AP_MASK (0xff << 24)
# define LDBBPPCR_AP_SHIFT 24
# define LDBBPPCR_R_MASK (0xff << 16)
# define LDBBPPCR_R_SHIFT 16
# define LDBBPPCR_GY_MASK (0xff << 8)
# define LDBBPPCR_GY_SHIFT 8
# define LDBBPPCR_B_MASK (0xff << 0)
# define LDBBPPCR_B_SHIFT 0
# define LDBnBBGCL(n) (0xb10 + (n) * 0x04)
# define LDBBBGCL_BGA_MASK (0xff << 24)
# define LDBBBGCL_BGA_SHIFT 24
# define LDBBBGCL_BGR_MASK (0xff << 16)
# define LDBBBGCL_BGR_SHIFT 16
# define LDBBBGCL_BGG_MASK (0xff << 8)
# define LDBBBGCL_BGG_SHIFT 8
# define LDBBBGCL_BGB_MASK (0xff << 0)
# define LDBBBGCL_BGB_SHIFT 0
2009-09-15 16:00:30 +04:00
# define SIDE_B_OFFSET 0x1000
# define MIRROR_OFFSET 0x2000
2008-07-24 08:31:24 +04:00
2010-11-04 14:06:06 +03:00
# define MAX_XRES 1920
# define MAX_YRES 1080
2008-07-24 08:31:24 +04:00
2011-12-12 21:43:16 +04:00
enum sh_mobile_lcdc_overlay_mode {
LCDC_OVERLAY_BLEND ,
LCDC_OVERLAY_ROP3 ,
} ;
/*
* struct sh_mobile_lcdc_overlay - LCDC display overlay
*
* @ channel : LCDC channel this overlay belongs to
* @ cfg : Overlay configuration
* @ info : Frame buffer device
* @ index : Overlay index ( 0 - 3 )
* @ base : Overlay registers base address
* @ enabled : True if the overlay is enabled
* @ mode : Overlay blending mode ( alpha blend or ROP3 )
* @ alpha : Global alpha blending value ( 0 - 255 , for alpha blending mode )
* @ rop3 : Raster operation ( for ROP3 mode )
* @ fb_mem : Frame buffer virtual memory address
* @ fb_size : Frame buffer size in bytes
* @ dma_handle : Frame buffer DMA address
* @ base_addr_y : Overlay base address ( RGB or luma component )
* @ base_addr_c : Overlay base address ( chroma component )
2012-03-15 21:15:37 +04:00
* @ pan_y_offset : Panning linear offset in bytes ( luma component )
2011-12-12 21:43:16 +04:00
* @ format : Current pixelf format
* @ xres : Horizontal visible resolution
* @ xres_virtual : Horizontal total resolution
* @ yres : Vertical visible resolution
* @ yres_virtual : Vertical total resolution
* @ pitch : Overlay line pitch
* @ pos_x : Horizontal overlay position
* @ pos_y : Vertical overlay position
*/
struct sh_mobile_lcdc_overlay {
struct sh_mobile_lcdc_chan * channel ;
const struct sh_mobile_lcdc_overlay_cfg * cfg ;
struct fb_info * info ;
unsigned int index ;
unsigned long base ;
bool enabled ;
enum sh_mobile_lcdc_overlay_mode mode ;
unsigned int alpha ;
unsigned int rop3 ;
void * fb_mem ;
unsigned long fb_size ;
dma_addr_t dma_handle ;
unsigned long base_addr_y ;
unsigned long base_addr_c ;
2012-03-15 21:15:37 +04:00
unsigned long pan_y_offset ;
2011-12-12 21:43:16 +04:00
const struct sh_mobile_lcdc_format_info * format ;
unsigned int xres ;
unsigned int xres_virtual ;
unsigned int yres ;
unsigned int yres_virtual ;
unsigned int pitch ;
int pos_x ;
int pos_y ;
} ;
2011-09-07 13:09:26 +04:00
struct sh_mobile_lcdc_priv {
void __iomem * base ;
int irq ;
atomic_t hw_usecnt ;
struct device * dev ;
struct clk * dot_clk ;
unsigned long lddckr ;
2011-12-12 21:43:16 +04:00
2011-09-07 13:09:26 +04:00
struct sh_mobile_lcdc_chan ch [ 2 ] ;
2011-12-12 21:43:16 +04:00
struct sh_mobile_lcdc_overlay overlays [ 4 ] ;
2011-09-07 13:09:26 +04:00
struct notifier_block notifier ;
int started ;
int forced_fourcc ; /* 2 channel LCDC must share fourcc setting */
struct sh_mobile_meram_info * meram_dev ;
} ;
/* -----------------------------------------------------------------------------
* Registers access
*/
2009-08-14 14:49:08 +04:00
static unsigned long lcdc_offs_mainlcd [ NR_CH_REGS ] = {
2008-07-24 08:31:24 +04:00
[ LDDCKPAT1R ] = 0x400 ,
[ LDDCKPAT2R ] = 0x404 ,
[ LDMT1R ] = 0x418 ,
[ LDMT2R ] = 0x41c ,
[ LDMT3R ] = 0x420 ,
[ LDDFR ] = 0x424 ,
[ LDSM1R ] = 0x428 ,
2008-12-19 09:34:41 +03:00
[ LDSM2R ] = 0x42c ,
2008-07-24 08:31:24 +04:00
[ LDSA1R ] = 0x430 ,
2011-02-24 08:47:13 +03:00
[ LDSA2R ] = 0x434 ,
2008-07-24 08:31:24 +04:00
[ LDMLSR ] = 0x438 ,
[ LDHCNR ] = 0x448 ,
[ LDHSYNR ] = 0x44c ,
[ LDVLNR ] = 0x450 ,
[ LDVSYNR ] = 0x454 ,
[ LDPMR ] = 0x460 ,
2010-07-21 14:13:21 +04:00
[ LDHAJR ] = 0x4a0 ,
2008-07-24 08:31:24 +04:00
} ;
2009-08-14 14:49:08 +04:00
static unsigned long lcdc_offs_sublcd [ NR_CH_REGS ] = {
2008-07-24 08:31:24 +04:00
[ LDDCKPAT1R ] = 0x408 ,
[ LDDCKPAT2R ] = 0x40c ,
[ LDMT1R ] = 0x600 ,
[ LDMT2R ] = 0x604 ,
[ LDMT3R ] = 0x608 ,
[ LDDFR ] = 0x60c ,
[ LDSM1R ] = 0x610 ,
2008-12-19 09:34:41 +03:00
[ LDSM2R ] = 0x614 ,
2008-07-24 08:31:24 +04:00
[ LDSA1R ] = 0x618 ,
[ LDMLSR ] = 0x620 ,
[ LDHCNR ] = 0x624 ,
[ LDHSYNR ] = 0x628 ,
[ LDVLNR ] = 0x62c ,
[ LDVSYNR ] = 0x630 ,
[ LDPMR ] = 0x63c ,
} ;
2009-09-15 16:00:30 +04:00
static bool banked ( int reg_nr )
{
switch ( reg_nr ) {
case LDMT1R :
case LDMT2R :
case LDMT3R :
case LDDFR :
case LDSM1R :
case LDSA1R :
2011-02-24 08:47:13 +03:00
case LDSA2R :
2009-09-15 16:00:30 +04:00
case LDMLSR :
case LDHCNR :
case LDHSYNR :
case LDVLNR :
case LDVSYNR :
return true ;
}
return false ;
}
2011-09-07 13:09:26 +04:00
static int lcdc_chan_is_sublcd ( struct sh_mobile_lcdc_chan * chan )
{
2011-11-22 03:56:58 +04:00
return chan - > cfg - > chan = = LCDC_CHAN_SUBLCD ;
2011-09-07 13:09:26 +04:00
}
2008-07-24 08:31:24 +04:00
static void lcdc_write_chan ( struct sh_mobile_lcdc_chan * chan ,
int reg_nr , unsigned long data )
{
iowrite32 ( data , chan - > lcdc - > base + chan - > reg_offs [ reg_nr ] ) ;
2009-09-15 16:00:30 +04:00
if ( banked ( reg_nr ) )
iowrite32 ( data , chan - > lcdc - > base + chan - > reg_offs [ reg_nr ] +
SIDE_B_OFFSET ) ;
}
static void lcdc_write_chan_mirror ( struct sh_mobile_lcdc_chan * chan ,
int reg_nr , unsigned long data )
{
iowrite32 ( data , chan - > lcdc - > base + chan - > reg_offs [ reg_nr ] +
MIRROR_OFFSET ) ;
2008-07-24 08:31:24 +04:00
}
static unsigned long lcdc_read_chan ( struct sh_mobile_lcdc_chan * chan ,
int reg_nr )
{
return ioread32 ( chan - > lcdc - > base + chan - > reg_offs [ reg_nr ] ) ;
}
2011-12-12 21:43:16 +04:00
static void lcdc_write_overlay ( struct sh_mobile_lcdc_overlay * ovl ,
int reg , unsigned long data )
{
iowrite32 ( data , ovl - > channel - > lcdc - > base + reg ) ;
iowrite32 ( data , ovl - > channel - > lcdc - > base + reg + SIDE_B_OFFSET ) ;
}
2008-07-24 08:31:24 +04:00
static void lcdc_write ( struct sh_mobile_lcdc_priv * priv ,
unsigned long reg_offs , unsigned long data )
{
iowrite32 ( data , priv - > base + reg_offs ) ;
}
static unsigned long lcdc_read ( struct sh_mobile_lcdc_priv * priv ,
unsigned long reg_offs )
{
return ioread32 ( priv - > base + reg_offs ) ;
}
static void lcdc_wait_bit ( struct sh_mobile_lcdc_priv * priv ,
unsigned long reg_offs ,
unsigned long mask , unsigned long until )
{
while ( ( lcdc_read ( priv , reg_offs ) & mask ) ! = until )
cpu_relax ( ) ;
}
2011-09-07 13:09:26 +04:00
/* -----------------------------------------------------------------------------
* Clock management
*/
static void sh_mobile_lcdc_clk_on ( struct sh_mobile_lcdc_priv * priv )
2008-07-24 08:31:24 +04:00
{
2011-09-07 13:09:26 +04:00
if ( atomic_inc_and_test ( & priv - > hw_usecnt ) ) {
if ( priv - > dot_clk )
clk_enable ( priv - > dot_clk ) ;
pm_runtime_get_sync ( priv - > dev ) ;
if ( priv - > meram_dev & & priv - > meram_dev - > pdev )
pm_runtime_get_sync ( & priv - > meram_dev - > pdev - > dev ) ;
}
2008-07-24 08:31:24 +04:00
}
2011-09-07 13:09:26 +04:00
static void sh_mobile_lcdc_clk_off ( struct sh_mobile_lcdc_priv * priv )
{
if ( atomic_sub_return ( 1 , & priv - > hw_usecnt ) = = - 1 ) {
if ( priv - > meram_dev & & priv - > meram_dev - > pdev )
pm_runtime_put_sync ( & priv - > meram_dev - > pdev - > dev ) ;
pm_runtime_put ( priv - > dev ) ;
if ( priv - > dot_clk )
clk_disable ( priv - > dot_clk ) ;
}
}
2011-09-07 18:02:31 +04:00
static int sh_mobile_lcdc_setup_clocks ( struct sh_mobile_lcdc_priv * priv ,
int clock_source )
2011-09-07 13:09:26 +04:00
{
2011-09-07 17:47:07 +04:00
struct clk * clk ;
2011-09-07 13:09:26 +04:00
char * str ;
switch ( clock_source ) {
case LCDC_CLK_BUS :
str = " bus_clk " ;
priv - > lddckr = LDDCKR_ICKSEL_BUS ;
break ;
case LCDC_CLK_PERIPHERAL :
str = " peripheral_clk " ;
priv - > lddckr = LDDCKR_ICKSEL_MIPI ;
break ;
case LCDC_CLK_EXTERNAL :
str = NULL ;
priv - > lddckr = LDDCKR_ICKSEL_HDMI ;
break ;
default :
return - EINVAL ;
}
2011-09-07 17:47:07 +04:00
if ( str = = NULL )
return 0 ;
2011-09-07 18:02:31 +04:00
clk = clk_get ( priv - > dev , str ) ;
2011-09-07 17:47:07 +04:00
if ( IS_ERR ( clk ) ) {
2011-09-07 18:02:31 +04:00
dev_err ( priv - > dev , " cannot get dot clock %s \n " , str ) ;
2011-09-07 17:47:07 +04:00
return PTR_ERR ( clk ) ;
2011-09-07 13:09:26 +04:00
}
2011-09-07 17:47:07 +04:00
priv - > dot_clk = clk ;
2011-09-07 13:09:26 +04:00
return 0 ;
}
/* -----------------------------------------------------------------------------
2011-09-09 17:45:43 +04:00
* Display , panel and deferred I / O
2011-09-07 13:09:26 +04:00
*/
2008-07-24 08:31:24 +04:00
static void lcdc_sys_write_index ( void * handle , unsigned long data )
{
struct sh_mobile_lcdc_chan * ch = handle ;
2011-07-13 14:13:47 +04:00
lcdc_write ( ch - > lcdc , _LDDWD0R , data | LDDWDxR_WDACT ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , LDSR_AS , 0 ) ;
lcdc_write ( ch - > lcdc , _LDDWAR , LDDWAR_WA |
( lcdc_chan_is_sublcd ( ch ) ? 2 : 0 ) ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , LDSR_AS , 0 ) ;
2008-07-24 08:31:24 +04:00
}
static void lcdc_sys_write_data ( void * handle , unsigned long data )
{
struct sh_mobile_lcdc_chan * ch = handle ;
2011-07-13 14:13:47 +04:00
lcdc_write ( ch - > lcdc , _LDDWD0R , data | LDDWDxR_WDACT | LDDWDxR_RSW ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , LDSR_AS , 0 ) ;
lcdc_write ( ch - > lcdc , _LDDWAR , LDDWAR_WA |
( lcdc_chan_is_sublcd ( ch ) ? 2 : 0 ) ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , LDSR_AS , 0 ) ;
2008-07-24 08:31:24 +04:00
}
static unsigned long lcdc_sys_read_data ( void * handle )
{
struct sh_mobile_lcdc_chan * ch = handle ;
2011-07-13 14:13:47 +04:00
lcdc_write ( ch - > lcdc , _LDDRDR , LDDRDR_RSR ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , LDSR_AS , 0 ) ;
lcdc_write ( ch - > lcdc , _LDDRAR , LDDRAR_RA |
( lcdc_chan_is_sublcd ( ch ) ? 2 : 0 ) ) ;
2008-07-24 08:31:24 +04:00
udelay ( 1 ) ;
2011-07-13 14:13:47 +04:00
lcdc_wait_bit ( ch - > lcdc , _LDSR , LDSR_AS , 0 ) ;
2008-07-24 08:31:24 +04:00
2011-07-13 14:13:47 +04:00
return lcdc_read ( ch - > lcdc , _LDDRDR ) & LDDRDR_DRD_MASK ;
2008-07-24 08:31:24 +04:00
}
2012-08-13 15:56:46 +04:00
static struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
2008-07-24 08:31:24 +04:00
lcdc_sys_write_index ,
lcdc_sys_write_data ,
lcdc_sys_read_data ,
} ;
2009-07-01 10:50:31 +04:00
static int sh_mobile_lcdc_sginit ( struct fb_info * info ,
struct list_head * pagelist )
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
2011-12-01 02:07:30 +04:00
unsigned int nr_pages_max = ch - > fb_size > > PAGE_SHIFT ;
2009-07-01 10:50:31 +04:00
struct page * page ;
int nr_pages = 0 ;
sg_init_table ( ch - > sglist , nr_pages_max ) ;
list_for_each_entry ( page , pagelist , lru )
sg_set_page ( & ch - > sglist [ nr_pages + + ] , page , PAGE_SIZE , 0 ) ;
return nr_pages ;
}
2008-12-19 09:34:41 +03:00
static void sh_mobile_lcdc_deferred_io ( struct fb_info * info ,
struct list_head * pagelist )
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
2011-11-22 03:56:58 +04:00
const struct sh_mobile_lcdc_panel_cfg * panel = & ch - > cfg - > panel_cfg ;
2008-12-19 09:34:41 +03:00
/* enable clocks before accessing hardware */
sh_mobile_lcdc_clk_on ( ch - > lcdc ) ;
2009-11-04 09:59:04 +03:00
/*
* It ' s possible to get here without anything on the pagelist via
* sh_mobile_lcdc_deferred_io_touch ( ) or via a userspace fsync ( )
* invocation . In the former case , the acceleration routines are
* stepped in to when using the framebuffer console causing the
* workqueue to be scheduled without any dirty pages on the list .
*
* Despite this , a panel update is still needed given that the
* acceleration routines have their own methods for writing in
* that still need to be updated .
*
* The fsync ( ) and empty pagelist case could be optimized for ,
* but we don ' t bother , as any application exhibiting such
* behaviour is fundamentally broken anyways .
*/
if ( ! list_empty ( pagelist ) ) {
unsigned int nr_pages = sh_mobile_lcdc_sginit ( info , pagelist ) ;
/* trigger panel update */
2011-11-29 17:37:35 +04:00
dma_map_sg ( ch - > lcdc - > dev , ch - > sglist , nr_pages , DMA_TO_DEVICE ) ;
2011-09-12 00:59:04 +04:00
if ( panel - > start_transfer )
panel - > start_transfer ( ch , & sh_mobile_lcdc_sys_bus_ops ) ;
2011-07-13 14:13:47 +04:00
lcdc_write_chan ( ch , LDSM2R , LDSM2R_OSTRG ) ;
2011-11-29 17:37:35 +04:00
dma_unmap_sg ( ch - > lcdc - > dev , ch - > sglist , nr_pages ,
DMA_TO_DEVICE ) ;
2009-12-07 17:20:06 +03:00
} else {
2011-09-12 00:59:04 +04:00
if ( panel - > start_transfer )
panel - > start_transfer ( ch , & sh_mobile_lcdc_sys_bus_ops ) ;
2011-07-13 14:13:47 +04:00
lcdc_write_chan ( ch , LDSM2R , LDSM2R_OSTRG ) ;
2009-12-07 17:20:06 +03:00
}
2008-12-19 09:34:41 +03:00
}
static void sh_mobile_lcdc_deferred_io_touch ( struct fb_info * info )
{
struct fb_deferred_io * fbdefio = info - > fbdefio ;
if ( fbdefio )
schedule_delayed_work ( & info - > deferred_work , fbdefio - > delay ) ;
}
2011-09-09 17:45:43 +04:00
static void sh_mobile_lcdc_display_on ( struct sh_mobile_lcdc_chan * ch )
{
2011-11-22 03:56:58 +04:00
const struct sh_mobile_lcdc_panel_cfg * panel = & ch - > cfg - > panel_cfg ;
2011-09-09 17:45:43 +04:00
2011-09-12 00:59:04 +04:00
if ( ch - > tx_dev ) {
2011-11-29 02:19:59 +04:00
int ret ;
ret = ch - > tx_dev - > ops - > display_on ( ch - > tx_dev ) ;
if ( ret < 0 )
2011-09-12 00:59:04 +04:00
return ;
2011-11-29 02:19:59 +04:00
if ( ret = = SH_MOBILE_LCDC_DISPLAY_DISCONNECTED )
ch - > info - > state = FBINFO_STATE_SUSPENDED ;
2011-09-12 00:59:04 +04:00
}
2011-09-09 17:45:43 +04:00
/* HDMI must be enabled before LCDC configuration */
2011-09-12 00:59:04 +04:00
if ( panel - > display_on )
panel - > display_on ( ) ;
2011-09-09 17:45:43 +04:00
}
static void sh_mobile_lcdc_display_off ( struct sh_mobile_lcdc_chan * ch )
{
2011-11-22 03:56:58 +04:00
const struct sh_mobile_lcdc_panel_cfg * panel = & ch - > cfg - > panel_cfg ;
2011-09-09 17:45:43 +04:00
2011-09-12 00:59:04 +04:00
if ( panel - > display_off )
panel - > display_off ( ) ;
2011-09-12 00:59:04 +04:00
if ( ch - > tx_dev )
ch - > tx_dev - > ops - > display_off ( ch - > tx_dev ) ;
2011-09-09 17:45:43 +04:00
}
2011-09-18 16:14:46 +04:00
static bool
sh_mobile_lcdc_must_reconfigure ( struct sh_mobile_lcdc_chan * ch ,
2011-11-29 04:05:47 +04:00
const struct fb_videomode * new_mode )
2011-09-18 16:14:46 +04:00
{
dev_dbg ( ch - > info - > dev , " Old %ux%u, new %ux%u \n " ,
2011-11-29 16:42:48 +04:00
ch - > display . mode . xres , ch - > display . mode . yres ,
new_mode - > xres , new_mode - > yres ) ;
2011-09-18 16:14:46 +04:00
2011-11-29 04:05:47 +04:00
/* It can be a different monitor with an equal video-mode */
2011-11-29 16:42:48 +04:00
if ( fb_mode_is_equal ( & ch - > display . mode , new_mode ) )
2011-09-18 16:14:46 +04:00
return false ;
dev_dbg ( ch - > info - > dev , " Switching %u -> %u lines \n " ,
2011-11-29 16:42:48 +04:00
ch - > display . mode . yres , new_mode - > yres ) ;
ch - > display . mode = * new_mode ;
2011-09-18 16:14:46 +04:00
return true ;
}
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_check_var ( struct fb_var_screeninfo * var ,
struct fb_info * info ) ;
2011-09-18 16:14:46 +04:00
static int sh_mobile_lcdc_display_notify ( struct sh_mobile_lcdc_chan * ch ,
enum sh_mobile_lcdc_entity_event event ,
2011-11-29 04:05:47 +04:00
const struct fb_videomode * mode ,
const struct fb_monspecs * monspec )
2011-09-18 16:14:46 +04:00
{
struct fb_info * info = ch - > info ;
2011-11-29 04:05:47 +04:00
struct fb_var_screeninfo var ;
2011-09-18 16:14:46 +04:00
int ret = 0 ;
switch ( event ) {
case SH_MOBILE_LCDC_EVENT_DISPLAY_CONNECT :
/* HDMI plug in */
if ( lock_fb_info ( info ) ) {
console_lock ( ) ;
2011-11-29 16:42:48 +04:00
ch - > display . width = monspec - > max_x * 10 ;
ch - > display . height = monspec - > max_y * 10 ;
2011-11-29 04:05:47 +04:00
if ( ! sh_mobile_lcdc_must_reconfigure ( ch , mode ) & &
2011-09-18 16:14:46 +04:00
info - > state = = FBINFO_STATE_RUNNING ) {
/* First activation with the default monitor.
* Just turn on , if we run a resume here , the
* logo disappears .
*/
2012-08-13 15:56:46 +04:00
info - > var . width = ch - > display . width ;
info - > var . height = ch - > display . height ;
2011-09-18 16:14:46 +04:00
sh_mobile_lcdc_display_on ( ch ) ;
} else {
/* New monitor or have to wake up */
fb_set_suspend ( info , 0 ) ;
}
console_unlock ( ) ;
unlock_fb_info ( info ) ;
}
break ;
case SH_MOBILE_LCDC_EVENT_DISPLAY_DISCONNECT :
/* HDMI disconnect */
if ( lock_fb_info ( info ) ) {
console_lock ( ) ;
fb_set_suspend ( info , 1 ) ;
console_unlock ( ) ;
unlock_fb_info ( info ) ;
}
break ;
case SH_MOBILE_LCDC_EVENT_DISPLAY_MODE :
/* Validate a proposed new mode */
2011-11-29 04:05:47 +04:00
fb_videomode_to_var ( & var , mode ) ;
var . bits_per_pixel = info - > var . bits_per_pixel ;
var . grayscale = info - > var . grayscale ;
2011-11-22 03:56:58 +04:00
ret = sh_mobile_lcdc_check_var ( & var , info ) ;
2011-09-18 16:14:46 +04:00
break ;
}
return ret ;
}
2011-09-07 13:09:26 +04:00
/* -----------------------------------------------------------------------------
* Format helpers
*/
2011-11-29 18:58:10 +04:00
struct sh_mobile_lcdc_format_info {
u32 fourcc ;
unsigned int bpp ;
bool yuv ;
u32 lddfr ;
} ;
static const struct sh_mobile_lcdc_format_info sh_mobile_format_infos [ ] = {
{
. fourcc = V4L2_PIX_FMT_RGB565 ,
. bpp = 16 ,
. yuv = false ,
. lddfr = LDDFR_PKF_RGB16 ,
} , {
. fourcc = V4L2_PIX_FMT_BGR24 ,
. bpp = 24 ,
. yuv = false ,
. lddfr = LDDFR_PKF_RGB24 ,
} , {
. fourcc = V4L2_PIX_FMT_BGR32 ,
. bpp = 32 ,
. yuv = false ,
. lddfr = LDDFR_PKF_ARGB32 ,
} , {
. fourcc = V4L2_PIX_FMT_NV12 ,
. bpp = 12 ,
. yuv = true ,
. lddfr = LDDFR_CC | LDDFR_YF_420 ,
} , {
. fourcc = V4L2_PIX_FMT_NV21 ,
. bpp = 12 ,
. yuv = true ,
. lddfr = LDDFR_CC | LDDFR_YF_420 ,
} , {
. fourcc = V4L2_PIX_FMT_NV16 ,
. bpp = 16 ,
. yuv = true ,
. lddfr = LDDFR_CC | LDDFR_YF_422 ,
} , {
. fourcc = V4L2_PIX_FMT_NV61 ,
. bpp = 16 ,
. yuv = true ,
. lddfr = LDDFR_CC | LDDFR_YF_422 ,
} , {
. fourcc = V4L2_PIX_FMT_NV24 ,
. bpp = 24 ,
. yuv = true ,
. lddfr = LDDFR_CC | LDDFR_YF_444 ,
} , {
. fourcc = V4L2_PIX_FMT_NV42 ,
. bpp = 24 ,
. yuv = true ,
. lddfr = LDDFR_CC | LDDFR_YF_444 ,
} ,
} ;
static const struct sh_mobile_lcdc_format_info *
sh_mobile_format_info ( u32 fourcc )
{
unsigned int i ;
for ( i = 0 ; i < ARRAY_SIZE ( sh_mobile_format_infos ) ; + + i ) {
if ( sh_mobile_format_infos [ i ] . fourcc = = fourcc )
return & sh_mobile_format_infos [ i ] ;
}
return NULL ;
}
2011-09-07 13:09:26 +04:00
static int sh_mobile_format_fourcc ( const struct fb_var_screeninfo * var )
{
if ( var - > grayscale > 1 )
return var - > grayscale ;
switch ( var - > bits_per_pixel ) {
case 16 :
return V4L2_PIX_FMT_RGB565 ;
case 24 :
return V4L2_PIX_FMT_BGR24 ;
case 32 :
return V4L2_PIX_FMT_BGR32 ;
default :
return 0 ;
}
}
static int sh_mobile_format_is_fourcc ( const struct fb_var_screeninfo * var )
{
return var - > grayscale > 1 ;
}
/* -----------------------------------------------------------------------------
* Start , stop and IRQ
*/
2008-12-19 09:34:41 +03:00
static irqreturn_t sh_mobile_lcdc_irq ( int irq , void * data )
{
struct sh_mobile_lcdc_priv * priv = data ;
2009-03-13 18:36:55 +03:00
struct sh_mobile_lcdc_chan * ch ;
2009-09-15 16:00:18 +04:00
unsigned long ldintr ;
2009-03-13 18:36:55 +03:00
int is_sub ;
int k ;
2008-12-19 09:34:41 +03:00
2011-07-13 14:13:47 +04:00
/* Acknowledge interrupts and disable further VSYNC End IRQs. */
ldintr = lcdc_read ( priv , _LDINTR ) ;
lcdc_write ( priv , _LDINTR , ( ldintr ^ LDINTR_STATUS_MASK ) & ~ LDINTR_VEE ) ;
2008-12-19 09:34:41 +03:00
2009-03-13 18:36:55 +03:00
/* figure out if this interrupt is for main or sub lcd */
2011-07-13 14:13:47 +04:00
is_sub = ( lcdc_read ( priv , _LDSR ) & LDSR_MSS ) ? 1 : 0 ;
2009-03-13 18:36:55 +03:00
2009-09-15 16:00:18 +04:00
/* wake up channel and disable clocks */
2009-03-13 18:36:55 +03:00
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
if ( ! ch - > enabled )
continue ;
2011-07-13 14:13:47 +04:00
/* Frame End */
2009-09-15 16:00:18 +04:00
if ( ldintr & LDINTR_FS ) {
if ( is_sub = = lcdc_chan_is_sublcd ( ch ) ) {
ch - > frame_end = 1 ;
wake_up ( & ch - > frame_end_wait ) ;
2009-03-13 18:36:55 +03:00
2009-09-15 16:00:18 +04:00
sh_mobile_lcdc_clk_off ( priv ) ;
}
}
/* VSYNC End */
2010-02-15 16:57:49 +03:00
if ( ldintr & LDINTR_VES )
complete ( & ch - > vsync_completion ) ;
2009-03-13 18:36:55 +03:00
}
2008-12-19 09:34:41 +03:00
return IRQ_HANDLED ;
}
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_wait_for_vsync ( struct sh_mobile_lcdc_chan * ch )
2011-12-01 02:07:30 +04:00
{
unsigned long ldintr ;
int ret ;
/* Enable VSync End interrupt and be careful not to acknowledge any
* pending interrupt .
*/
ldintr = lcdc_read ( ch - > lcdc , _LDINTR ) ;
ldintr | = LDINTR_VEE | LDINTR_STATUS_MASK ;
lcdc_write ( ch - > lcdc , _LDINTR , ldintr ) ;
ret = wait_for_completion_interruptible_timeout ( & ch - > vsync_completion ,
msecs_to_jiffies ( 100 ) ) ;
if ( ! ret )
return - ETIMEDOUT ;
return 0 ;
}
2008-07-24 08:31:24 +04:00
static void sh_mobile_lcdc_start_stop ( struct sh_mobile_lcdc_priv * priv ,
int start )
{
unsigned long tmp = lcdc_read ( priv , _LDCNT2R ) ;
int k ;
/* start or stop the lcdc */
if ( start )
2011-07-13 14:13:47 +04:00
lcdc_write ( priv , _LDCNT2R , tmp | LDCNT2R_DO ) ;
2008-07-24 08:31:24 +04:00
else
2011-07-13 14:13:47 +04:00
lcdc_write ( priv , _LDCNT2R , tmp & ~ LDCNT2R_DO ) ;
2008-07-24 08:31:24 +04:00
/* wait until power is applied/stopped on all channels */
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + )
if ( lcdc_read ( priv , _LDCNT2R ) & priv - > ch [ k ] . enabled )
while ( 1 ) {
2011-07-13 14:13:47 +04:00
tmp = lcdc_read_chan ( & priv - > ch [ k ] , LDPMR )
& LDPMR_LPS ;
if ( start & & tmp = = LDPMR_LPS )
2008-07-24 08:31:24 +04:00
break ;
if ( ! start & & tmp = = 0 )
break ;
cpu_relax ( ) ;
}
if ( ! start )
lcdc_write ( priv , _LDDCKSTPR , 1 ) ; /* stop dotclock */
}
2010-07-21 14:13:21 +04:00
static void sh_mobile_lcdc_geometry ( struct sh_mobile_lcdc_chan * ch )
{
2011-11-29 16:42:48 +04:00
const struct fb_var_screeninfo * var = & ch - > info - > var ;
const struct fb_videomode * mode = & ch - > display . mode ;
2010-09-03 11:20:27 +04:00
unsigned long h_total , hsync_pos , display_h_total ;
2010-07-21 14:13:21 +04:00
u32 tmp ;
tmp = ch - > ldmt1r_value ;
2011-07-13 14:13:47 +04:00
tmp | = ( var - > sync & FB_SYNC_VERT_HIGH_ACT ) ? 0 : LDMT1R_VPOL ;
tmp | = ( var - > sync & FB_SYNC_HOR_HIGH_ACT ) ? 0 : LDMT1R_HPOL ;
2011-11-22 03:56:58 +04:00
tmp | = ( ch - > cfg - > flags & LCDC_FLAGS_DWPOL ) ? LDMT1R_DWPOL : 0 ;
tmp | = ( ch - > cfg - > flags & LCDC_FLAGS_DIPOL ) ? LDMT1R_DIPOL : 0 ;
tmp | = ( ch - > cfg - > flags & LCDC_FLAGS_DAPOL ) ? LDMT1R_DAPOL : 0 ;
tmp | = ( ch - > cfg - > flags & LCDC_FLAGS_HSCNT ) ? LDMT1R_HSCNT : 0 ;
tmp | = ( ch - > cfg - > flags & LCDC_FLAGS_DWCNT ) ? LDMT1R_DWCNT : 0 ;
2010-07-21 14:13:21 +04:00
lcdc_write_chan ( ch , LDMT1R , tmp ) ;
/* setup SYS bus */
2011-11-22 03:56:58 +04:00
lcdc_write_chan ( ch , LDMT2R , ch - > cfg - > sys_bus_cfg . ldmt2r ) ;
lcdc_write_chan ( ch , LDMT3R , ch - > cfg - > sys_bus_cfg . ldmt3r ) ;
2010-07-21 14:13:21 +04:00
/* horizontal configuration */
2011-11-29 16:42:48 +04:00
h_total = mode - > xres + mode - > hsync_len + mode - > left_margin
+ mode - > right_margin ;
2010-07-21 14:13:21 +04:00
tmp = h_total / 8 ; /* HTCN */
2011-12-01 02:07:30 +04:00
tmp | = ( min ( mode - > xres , ch - > xres ) / 8 ) < < 16 ; /* HDCN */
2010-07-21 14:13:21 +04:00
lcdc_write_chan ( ch , LDHCNR , tmp ) ;
2011-11-29 16:42:48 +04:00
hsync_pos = mode - > xres + mode - > right_margin ;
2010-07-21 14:13:21 +04:00
tmp = hsync_pos / 8 ; /* HSYNP */
2011-11-29 16:42:48 +04:00
tmp | = ( mode - > hsync_len / 8 ) < < 16 ; /* HSYNW */
2010-07-21 14:13:21 +04:00
lcdc_write_chan ( ch , LDHSYNR , tmp ) ;
/* vertical configuration */
2011-11-29 16:42:48 +04:00
tmp = mode - > yres + mode - > vsync_len + mode - > upper_margin
+ mode - > lower_margin ; /* VTLN */
2011-12-01 02:07:30 +04:00
tmp | = min ( mode - > yres , ch - > yres ) < < 16 ; /* VDLN */
2010-07-21 14:13:21 +04:00
lcdc_write_chan ( ch , LDVLNR , tmp ) ;
2011-11-29 16:42:48 +04:00
tmp = mode - > yres + mode - > lower_margin ; /* VSYNP */
tmp | = mode - > vsync_len < < 16 ; /* VSYNW */
2010-07-21 14:13:21 +04:00
lcdc_write_chan ( ch , LDVSYNR , tmp ) ;
/* Adjust horizontal synchronisation for HDMI */
2011-11-29 16:42:48 +04:00
display_h_total = mode - > xres + mode - > hsync_len + mode - > left_margin
+ mode - > right_margin ;
tmp = ( ( mode - > xres & 7 ) < < 24 ) | ( ( display_h_total & 7 ) < < 16 )
| ( ( mode - > hsync_len & 7 ) < < 8 ) | ( hsync_pos & 7 ) ;
2010-07-21 14:13:21 +04:00
lcdc_write_chan ( ch , LDHAJR , tmp ) ;
2013-03-05 08:44:39 +04:00
lcdc_write_chan_mirror ( ch , LDHAJR , tmp ) ;
2010-07-21 14:13:21 +04:00
}
2011-12-12 21:43:16 +04:00
static void sh_mobile_lcdc_overlay_setup ( struct sh_mobile_lcdc_overlay * ovl )
{
u32 format = 0 ;
if ( ! ovl - > enabled ) {
lcdc_write ( ovl - > channel - > lcdc , LDBCR , LDBCR_UPC ( ovl - > index ) ) ;
lcdc_write_overlay ( ovl , LDBnBSIFR ( ovl - > index ) , 0 ) ;
lcdc_write ( ovl - > channel - > lcdc , LDBCR ,
LDBCR_UPF ( ovl - > index ) | LDBCR_UPD ( ovl - > index ) ) ;
return ;
}
ovl - > base_addr_y = ovl - > dma_handle ;
2012-03-15 21:15:37 +04:00
ovl - > base_addr_c = ovl - > dma_handle
+ ovl - > xres_virtual * ovl - > yres_virtual ;
2011-12-12 21:43:16 +04:00
switch ( ovl - > mode ) {
case LCDC_OVERLAY_BLEND :
format = LDBBSIFR_EN | ( ovl - > alpha < < LDBBSIFR_LAY_SHIFT ) ;
break ;
case LCDC_OVERLAY_ROP3 :
format = LDBBSIFR_EN | LDBBSIFR_BRSEL
| ( ovl - > rop3 < < LDBBSIFR_ROP3_SHIFT ) ;
break ;
}
switch ( ovl - > format - > fourcc ) {
case V4L2_PIX_FMT_RGB565 :
case V4L2_PIX_FMT_NV21 :
case V4L2_PIX_FMT_NV61 :
case V4L2_PIX_FMT_NV42 :
format | = LDBBSIFR_SWPL | LDBBSIFR_SWPW ;
break ;
case V4L2_PIX_FMT_BGR24 :
case V4L2_PIX_FMT_NV12 :
case V4L2_PIX_FMT_NV16 :
case V4L2_PIX_FMT_NV24 :
format | = LDBBSIFR_SWPL | LDBBSIFR_SWPW | LDBBSIFR_SWPB ;
break ;
case V4L2_PIX_FMT_BGR32 :
default :
format | = LDBBSIFR_SWPL ;
break ;
}
switch ( ovl - > format - > fourcc ) {
case V4L2_PIX_FMT_RGB565 :
format | = LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16 ;
break ;
case V4L2_PIX_FMT_BGR24 :
format | = LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24 ;
break ;
case V4L2_PIX_FMT_BGR32 :
format | = LDBBSIFR_AL_PK | LDBBSIFR_RY | LDDFR_PKF_ARGB32 ;
break ;
case V4L2_PIX_FMT_NV12 :
case V4L2_PIX_FMT_NV21 :
format | = LDBBSIFR_AL_1 | LDBBSIFR_CHRR_420 ;
break ;
case V4L2_PIX_FMT_NV16 :
case V4L2_PIX_FMT_NV61 :
format | = LDBBSIFR_AL_1 | LDBBSIFR_CHRR_422 ;
break ;
case V4L2_PIX_FMT_NV24 :
case V4L2_PIX_FMT_NV42 :
format | = LDBBSIFR_AL_1 | LDBBSIFR_CHRR_444 ;
break ;
}
lcdc_write ( ovl - > channel - > lcdc , LDBCR , LDBCR_UPC ( ovl - > index ) ) ;
lcdc_write_overlay ( ovl , LDBnBSIFR ( ovl - > index ) , format ) ;
lcdc_write_overlay ( ovl , LDBnBSSZR ( ovl - > index ) ,
( ovl - > yres < < LDBBSSZR_BVSS_SHIFT ) |
( ovl - > xres < < LDBBSSZR_BHSS_SHIFT ) ) ;
lcdc_write_overlay ( ovl , LDBnBLOCR ( ovl - > index ) ,
( ovl - > pos_y < < LDBBLOCR_CVLC_SHIFT ) |
( ovl - > pos_x < < LDBBLOCR_CHLC_SHIFT ) ) ;
lcdc_write_overlay ( ovl , LDBnBSMWR ( ovl - > index ) ,
ovl - > pitch < < LDBBSMWR_BSMW_SHIFT ) ;
lcdc_write_overlay ( ovl , LDBnBSAYR ( ovl - > index ) , ovl - > base_addr_y ) ;
lcdc_write_overlay ( ovl , LDBnBSACR ( ovl - > index ) , ovl - > base_addr_c ) ;
lcdc_write ( ovl - > channel - > lcdc , LDBCR ,
LDBCR_UPF ( ovl - > index ) | LDBCR_UPD ( ovl - > index ) ) ;
}
2011-07-13 14:13:47 +04:00
/*
2011-11-22 03:56:58 +04:00
* __sh_mobile_lcdc_start - Configure and start the LCDC
2011-07-13 14:13:47 +04:00
* @ priv : LCDC device
*
* Configure all enabled channels and start the LCDC device . All external
* devices ( clocks , MERAM , panels , . . . ) are not touched by this function .
*/
static void __sh_mobile_lcdc_start ( struct sh_mobile_lcdc_priv * priv )
2008-07-24 08:31:24 +04:00
{
struct sh_mobile_lcdc_chan * ch ;
unsigned long tmp ;
2011-07-13 14:13:47 +04:00
int k , m ;
2008-12-19 09:34:41 +03:00
2011-07-13 14:13:47 +04:00
/* Enable LCDC channels. Read data from external memory, avoid using the
* BEU for now .
*/
lcdc_write ( priv , _LDCNT2R , priv - > ch [ 0 ] . enabled | priv - > ch [ 1 ] . enabled ) ;
2008-07-24 08:31:24 +04:00
2011-07-13 14:13:47 +04:00
/* Stop the LCDC first and disable all interrupts. */
2008-07-24 08:31:24 +04:00
sh_mobile_lcdc_start_stop ( priv , 0 ) ;
2011-07-13 14:13:47 +04:00
lcdc_write ( priv , _LDINTR , 0 ) ;
2008-07-24 08:31:24 +04:00
2011-07-13 14:13:47 +04:00
/* Configure power supply, dot clocks and start them. */
2008-07-24 08:31:24 +04:00
tmp = priv - > lddckr ;
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
2011-07-13 14:13:47 +04:00
if ( ! ch - > enabled )
2008-07-24 08:31:24 +04:00
continue ;
2011-07-13 14:13:47 +04:00
/* Power supply */
lcdc_write_chan ( ch , LDPMR , 0 ) ;
2011-11-22 03:56:58 +04:00
m = ch - > cfg - > clock_divider ;
2008-07-24 08:31:24 +04:00
if ( ! m )
continue ;
2011-07-13 14:13:47 +04:00
/* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider
* denominator .
*/
lcdc_write_chan ( ch , LDDCKPAT1R , 0 ) ;
lcdc_write_chan ( ch , LDDCKPAT2R , ( 1 < < ( m / 2 ) ) - 1 ) ;
2008-07-24 08:31:24 +04:00
if ( m = = 1 )
2011-07-13 14:13:47 +04:00
m = LDDCKR_MOSEL ;
2008-07-24 08:31:24 +04:00
tmp | = m < < ( lcdc_chan_is_sublcd ( ch ) ? 8 : 0 ) ;
}
lcdc_write ( priv , _LDDCKR , tmp ) ;
lcdc_write ( priv , _LDDCKSTPR , 0 ) ;
lcdc_wait_bit ( priv , _LDDCKSTPR , ~ 0 , 0 ) ;
2011-07-13 14:13:47 +04:00
/* Setup geometry, format, frame buffer memory and operation mode. */
2008-07-24 08:31:24 +04:00
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
if ( ! ch - > enabled )
continue ;
2010-07-21 14:13:21 +04:00
sh_mobile_lcdc_geometry ( ch ) ;
2008-07-24 08:31:24 +04:00
2011-11-29 19:05:36 +04:00
tmp = ch - > format - > lddfr ;
2011-12-13 17:02:28 +04:00
2011-11-29 19:05:36 +04:00
if ( ch - > format - > yuv ) {
2011-12-01 02:07:30 +04:00
switch ( ch - > colorspace ) {
2011-12-13 17:02:28 +04:00
case V4L2_COLORSPACE_REC709 :
tmp | = LDDFR_CF1 ;
2011-02-24 08:47:13 +03:00
break ;
2011-12-13 17:02:28 +04:00
case V4L2_COLORSPACE_JPEG :
tmp | = LDDFR_CF0 ;
2011-02-24 08:47:13 +03:00
break ;
}
2011-01-05 13:21:00 +03:00
}
2011-05-18 15:10:07 +04:00
2011-07-13 14:13:47 +04:00
lcdc_write_chan ( ch , LDDFR , tmp ) ;
2012-05-18 12:58:26 +04:00
lcdc_write_chan ( ch , LDMLSR , ch - > line_size ) ;
2011-07-13 14:13:47 +04:00
lcdc_write_chan ( ch , LDSA1R , ch - > base_addr_y ) ;
2011-11-29 19:05:36 +04:00
if ( ch - > format - > yuv )
2011-07-13 14:13:47 +04:00
lcdc_write_chan ( ch , LDSA2R , ch - > base_addr_c ) ;
2011-05-18 15:10:07 +04:00
2011-07-13 14:13:47 +04:00
/* When using deferred I/O mode, configure the LCDC for one-shot
* operation and enable the frame end interrupt . Otherwise use
* continuous read mode .
*/
if ( ch - > ldmt1r_value & LDMT1R_IFM & &
2011-11-22 03:56:58 +04:00
ch - > cfg - > sys_bus_cfg . deferred_io_msec ) {
2011-07-13 14:13:47 +04:00
lcdc_write_chan ( ch , LDSM1R , LDSM1R_OS ) ;
lcdc_write ( priv , _LDINTR , LDINTR_FE ) ;
} else {
lcdc_write_chan ( ch , LDSM1R , 0 ) ;
}
}
2011-05-18 15:10:07 +04:00
2011-07-13 14:13:47 +04:00
/* Word and long word swap. */
2011-11-29 19:05:36 +04:00
switch ( priv - > ch [ 0 ] . format - > fourcc ) {
2011-12-13 17:02:28 +04:00
case V4L2_PIX_FMT_RGB565 :
case V4L2_PIX_FMT_NV21 :
case V4L2_PIX_FMT_NV61 :
case V4L2_PIX_FMT_NV42 :
tmp = LDDDSR_LS | LDDDSR_WS ;
break ;
case V4L2_PIX_FMT_BGR24 :
case V4L2_PIX_FMT_NV12 :
case V4L2_PIX_FMT_NV16 :
case V4L2_PIX_FMT_NV24 :
2011-07-13 14:13:47 +04:00
tmp = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS ;
2011-12-13 17:02:28 +04:00
break ;
case V4L2_PIX_FMT_BGR32 :
default :
tmp = LDDDSR_LS ;
break ;
2011-07-13 14:13:47 +04:00
}
lcdc_write ( priv , _LDDDSR , tmp ) ;
2011-05-18 15:10:07 +04:00
2011-07-13 14:13:47 +04:00
/* Enable the display output. */
lcdc_write ( priv , _LDCNT1R , LDCNT1R_DE ) ;
sh_mobile_lcdc_start_stop ( priv , 1 ) ;
priv - > started = 1 ;
}
2008-07-24 08:31:24 +04:00
2011-07-13 14:13:47 +04:00
static int sh_mobile_lcdc_start ( struct sh_mobile_lcdc_priv * priv )
{
struct sh_mobile_meram_info * mdev = priv - > meram_dev ;
struct sh_mobile_lcdc_chan * ch ;
unsigned long tmp ;
int ret ;
int k ;
2008-07-24 08:31:24 +04:00
2011-07-13 14:13:47 +04:00
/* enable clocks before accessing the hardware */
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
if ( priv - > ch [ k ] . enabled )
sh_mobile_lcdc_clk_on ( priv ) ;
}
2008-12-19 09:34:41 +03:00
2011-07-13 14:13:47 +04:00
/* reset */
lcdc_write ( priv , _LDCNT2R , lcdc_read ( priv , _LDCNT2R ) | LDCNT2R_BR ) ;
lcdc_wait_bit ( priv , _LDCNT2R , LDCNT2R_BR , 0 ) ;
2008-12-19 09:34:41 +03:00
2011-07-13 14:13:47 +04:00
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
2011-11-22 03:56:58 +04:00
const struct sh_mobile_lcdc_panel_cfg * panel ;
2008-12-19 09:34:41 +03:00
2011-09-09 17:45:43 +04:00
ch = & priv - > ch [ k ] ;
2011-07-13 14:13:47 +04:00
if ( ! ch - > enabled )
continue ;
2011-11-22 03:56:58 +04:00
panel = & ch - > cfg - > panel_cfg ;
2011-09-12 00:59:04 +04:00
if ( panel - > setup_sys ) {
ret = panel - > setup_sys ( ch , & sh_mobile_lcdc_sys_bus_ops ) ;
2011-07-13 14:13:47 +04:00
if ( ret )
return ret ;
2008-12-19 09:34:41 +03:00
}
2008-07-24 08:31:24 +04:00
}
2011-07-13 14:13:47 +04:00
/* Compute frame buffer base address and pitch for each channel. */
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
int pixelformat ;
2012-03-15 15:40:47 +04:00
void * cache ;
2008-07-24 08:31:24 +04:00
2011-07-13 14:13:47 +04:00
ch = & priv - > ch [ k ] ;
if ( ! ch - > enabled )
continue ;
2008-07-24 08:31:24 +04:00
2011-12-01 02:07:30 +04:00
ch - > base_addr_y = ch - > dma_handle ;
2012-03-15 21:15:37 +04:00
ch - > base_addr_c = ch - > dma_handle
+ ch - > xres_virtual * ch - > yres_virtual ;
2012-05-18 12:58:26 +04:00
ch - > line_size = ch - > pitch ;
2011-07-13 14:13:47 +04:00
/* Enable MERAM if possible. */
2012-03-15 15:40:47 +04:00
if ( mdev = = NULL | | ch - > cfg - > meram_cfg = = NULL )
2011-07-13 14:13:47 +04:00
continue ;
2012-03-15 15:40:47 +04:00
/* Free the allocated MERAM cache. */
if ( ch - > cache ) {
2012-03-15 15:40:47 +04:00
sh_mobile_meram_cache_free ( mdev , ch - > cache ) ;
2012-03-15 15:40:47 +04:00
ch - > cache = NULL ;
2011-07-13 14:13:47 +04:00
}
2011-11-29 19:05:36 +04:00
switch ( ch - > format - > fourcc ) {
2011-12-13 17:02:28 +04:00
case V4L2_PIX_FMT_NV12 :
case V4L2_PIX_FMT_NV21 :
case V4L2_PIX_FMT_NV16 :
case V4L2_PIX_FMT_NV61 :
2011-07-13 14:13:47 +04:00
pixelformat = SH_MOBILE_MERAM_PF_NV ;
2011-12-13 17:02:28 +04:00
break ;
case V4L2_PIX_FMT_NV24 :
case V4L2_PIX_FMT_NV42 :
pixelformat = SH_MOBILE_MERAM_PF_NV24 ;
break ;
case V4L2_PIX_FMT_RGB565 :
case V4L2_PIX_FMT_BGR24 :
case V4L2_PIX_FMT_BGR32 :
default :
pixelformat = SH_MOBILE_MERAM_PF_RGB ;
break ;
}
2011-07-13 14:13:47 +04:00
2012-03-15 15:40:47 +04:00
cache = sh_mobile_meram_cache_alloc ( mdev , ch - > cfg - > meram_cfg ,
2011-11-22 03:56:58 +04:00
ch - > pitch , ch - > yres , pixelformat ,
2012-05-18 12:58:26 +04:00
& ch - > line_size ) ;
2012-03-15 15:40:47 +04:00
if ( ! IS_ERR ( cache ) ) {
2012-03-15 15:40:47 +04:00
sh_mobile_meram_cache_update ( mdev , cache ,
2011-11-22 03:56:58 +04:00
ch - > base_addr_y , ch - > base_addr_c ,
& ch - > base_addr_y , & ch - > base_addr_c ) ;
2012-03-15 15:40:47 +04:00
ch - > cache = cache ;
2011-11-22 03:56:58 +04:00
}
2011-07-13 14:13:47 +04:00
}
2011-12-12 21:43:16 +04:00
for ( k = 0 ; k < ARRAY_SIZE ( priv - > overlays ) ; + + k ) {
struct sh_mobile_lcdc_overlay * ovl = & priv - > overlays [ k ] ;
sh_mobile_lcdc_overlay_setup ( ovl ) ;
}
2011-07-13 14:13:47 +04:00
/* Start the LCDC. */
__sh_mobile_lcdc_start ( priv ) ;
/* Setup deferred I/O, tell the board code to enable the panels, and
* turn backlight on .
*/
2008-07-24 08:31:24 +04:00
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
2009-08-15 06:53:16 +04:00
if ( ! ch - > enabled )
continue ;
2011-11-22 03:56:58 +04:00
tmp = ch - > cfg - > sys_bus_cfg . deferred_io_msec ;
2011-07-13 14:13:47 +04:00
if ( ch - > ldmt1r_value & LDMT1R_IFM & & tmp ) {
ch - > defio . deferred_io = sh_mobile_lcdc_deferred_io ;
ch - > defio . delay = msecs_to_jiffies ( tmp ) ;
ch - > info - > fbdefio = & ch - > defio ;
fb_deferred_io_init ( ch - > info ) ;
}
2011-09-09 17:45:43 +04:00
sh_mobile_lcdc_display_on ( ch ) ;
2011-02-16 06:49:01 +03:00
if ( ch - > bl ) {
ch - > bl - > props . power = FB_BLANK_UNBLANK ;
backlight_update_status ( ch - > bl ) ;
}
2008-07-24 08:31:24 +04:00
}
return 0 ;
}
static void sh_mobile_lcdc_stop ( struct sh_mobile_lcdc_priv * priv )
{
struct sh_mobile_lcdc_chan * ch ;
int k ;
2009-03-13 18:36:55 +03:00
/* clean up deferred io and ask board code to disable panel */
2008-07-24 08:31:24 +04:00
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
2009-08-15 06:53:16 +04:00
if ( ! ch - > enabled )
continue ;
2008-12-19 09:34:41 +03:00
2009-03-13 18:36:55 +03:00
/* deferred io mode:
* flush frame , and wait for frame end interrupt
* clean up deferred io and enable clock
*/
2010-09-03 11:19:57 +04:00
if ( ch - > info & & ch - > info - > fbdefio ) {
2009-03-13 18:36:55 +03:00
ch - > frame_end = 0 ;
2009-07-07 06:24:32 +04:00
schedule_delayed_work ( & ch - > info - > deferred_work , 0 ) ;
2009-03-13 18:36:55 +03:00
wait_event ( ch - > frame_end_wait , ch - > frame_end ) ;
2009-07-07 06:24:32 +04:00
fb_deferred_io_cleanup ( ch - > info ) ;
ch - > info - > fbdefio = NULL ;
2009-03-13 18:36:55 +03:00
sh_mobile_lcdc_clk_on ( priv ) ;
2008-12-19 09:34:41 +03:00
}
2009-03-13 18:36:55 +03:00
2011-02-16 06:49:01 +03:00
if ( ch - > bl ) {
ch - > bl - > props . power = FB_BLANK_POWERDOWN ;
backlight_update_status ( ch - > bl ) ;
}
2011-09-09 17:45:43 +04:00
sh_mobile_lcdc_display_off ( ch ) ;
2011-05-18 15:10:07 +04:00
2012-03-15 15:40:47 +04:00
/* Free the MERAM cache. */
if ( ch - > cache ) {
2012-03-15 15:40:47 +04:00
sh_mobile_meram_cache_free ( priv - > meram_dev , ch - > cache ) ;
2012-03-15 15:40:47 +04:00
ch - > cache = 0 ;
2011-05-18 15:10:07 +04:00
}
2008-07-24 08:31:24 +04:00
}
/* stop the lcdc */
2009-05-20 18:34:43 +04:00
if ( priv - > started ) {
sh_mobile_lcdc_start_stop ( priv , 0 ) ;
priv - > started = 0 ;
}
2008-10-31 14:23:26 +03:00
2008-12-19 09:34:41 +03:00
/* stop clocks */
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + )
if ( priv - > ch [ k ] . enabled )
sh_mobile_lcdc_clk_off ( priv ) ;
2008-07-24 08:31:24 +04:00
}
2011-12-12 21:43:16 +04:00
static int __sh_mobile_lcdc_check_var ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
if ( var - > xres > MAX_XRES | | var - > yres > MAX_YRES )
return - EINVAL ;
/* Make sure the virtual resolution is at least as big as the visible
* resolution .
*/
if ( var - > xres_virtual < var - > xres )
var - > xres_virtual = var - > xres ;
if ( var - > yres_virtual < var - > yres )
var - > yres_virtual = var - > yres ;
if ( sh_mobile_format_is_fourcc ( var ) ) {
const struct sh_mobile_lcdc_format_info * format ;
format = sh_mobile_format_info ( var - > grayscale ) ;
if ( format = = NULL )
return - EINVAL ;
var - > bits_per_pixel = format - > bpp ;
/* Default to RGB and JPEG color-spaces for RGB and YUV formats
* respectively .
*/
if ( ! format - > yuv )
var - > colorspace = V4L2_COLORSPACE_SRGB ;
else if ( var - > colorspace ! = V4L2_COLORSPACE_REC709 )
var - > colorspace = V4L2_COLORSPACE_JPEG ;
} else {
if ( var - > bits_per_pixel < = 16 ) { /* RGB 565 */
var - > bits_per_pixel = 16 ;
var - > red . offset = 11 ;
var - > red . length = 5 ;
var - > green . offset = 5 ;
var - > green . length = 6 ;
var - > blue . offset = 0 ;
var - > blue . length = 5 ;
var - > transp . offset = 0 ;
var - > transp . length = 0 ;
} else if ( var - > bits_per_pixel < = 24 ) { /* RGB 888 */
var - > bits_per_pixel = 24 ;
var - > red . offset = 16 ;
var - > red . length = 8 ;
var - > green . offset = 8 ;
var - > green . length = 8 ;
var - > blue . offset = 0 ;
var - > blue . length = 8 ;
var - > transp . offset = 0 ;
var - > transp . length = 0 ;
} else if ( var - > bits_per_pixel < = 32 ) { /* RGBA 888 */
var - > bits_per_pixel = 32 ;
var - > red . offset = 16 ;
var - > red . length = 8 ;
var - > green . offset = 8 ;
var - > green . length = 8 ;
var - > blue . offset = 0 ;
var - > blue . length = 8 ;
var - > transp . offset = 24 ;
var - > transp . length = 8 ;
} else
return - EINVAL ;
var - > red . msb_right = 0 ;
var - > green . msb_right = 0 ;
var - > blue . msb_right = 0 ;
var - > transp . msb_right = 0 ;
}
/* Make sure we don't exceed our allocated memory. */
if ( var - > xres_virtual * var - > yres_virtual * var - > bits_per_pixel / 8 >
info - > fix . smem_len )
return - EINVAL ;
return 0 ;
}
/* -----------------------------------------------------------------------------
* Frame buffer operations - Overlays
*/
static ssize_t
overlay_alpha_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
return scnprintf ( buf , PAGE_SIZE , " %u \n " , ovl - > alpha ) ;
}
static ssize_t
overlay_alpha_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
unsigned int alpha ;
char * endp ;
alpha = simple_strtoul ( buf , & endp , 10 ) ;
if ( isspace ( * endp ) )
endp + + ;
if ( endp - buf ! = count )
return - EINVAL ;
if ( alpha > 255 )
return - EINVAL ;
if ( ovl - > alpha ! = alpha ) {
ovl - > alpha = alpha ;
if ( ovl - > mode = = LCDC_OVERLAY_BLEND & & ovl - > enabled )
sh_mobile_lcdc_overlay_setup ( ovl ) ;
}
return count ;
}
static ssize_t
overlay_mode_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
return scnprintf ( buf , PAGE_SIZE , " %u \n " , ovl - > mode ) ;
}
static ssize_t
overlay_mode_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
unsigned int mode ;
char * endp ;
mode = simple_strtoul ( buf , & endp , 10 ) ;
if ( isspace ( * endp ) )
endp + + ;
if ( endp - buf ! = count )
return - EINVAL ;
if ( mode ! = LCDC_OVERLAY_BLEND & & mode ! = LCDC_OVERLAY_ROP3 )
return - EINVAL ;
if ( ovl - > mode ! = mode ) {
ovl - > mode = mode ;
if ( ovl - > enabled )
sh_mobile_lcdc_overlay_setup ( ovl ) ;
}
return count ;
}
static ssize_t
overlay_position_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
return scnprintf ( buf , PAGE_SIZE , " %d,%d \n " , ovl - > pos_x , ovl - > pos_y ) ;
}
static ssize_t
overlay_position_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
char * endp ;
int pos_x ;
int pos_y ;
pos_x = simple_strtol ( buf , & endp , 10 ) ;
if ( * endp ! = ' , ' )
return - EINVAL ;
pos_y = simple_strtol ( endp + 1 , & endp , 10 ) ;
if ( isspace ( * endp ) )
endp + + ;
if ( endp - buf ! = count )
return - EINVAL ;
if ( ovl - > pos_x ! = pos_x | | ovl - > pos_y ! = pos_y ) {
ovl - > pos_x = pos_x ;
ovl - > pos_y = pos_y ;
if ( ovl - > enabled )
sh_mobile_lcdc_overlay_setup ( ovl ) ;
}
return count ;
}
static ssize_t
overlay_rop3_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
return scnprintf ( buf , PAGE_SIZE , " %u \n " , ovl - > rop3 ) ;
}
static ssize_t
overlay_rop3_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
unsigned int rop3 ;
char * endp ;
rop3 = ! ! simple_strtoul ( buf , & endp , 10 ) ;
if ( isspace ( * endp ) )
endp + + ;
if ( endp - buf ! = count )
return - EINVAL ;
if ( rop3 > 255 )
return - EINVAL ;
if ( ovl - > rop3 ! = rop3 ) {
ovl - > rop3 = rop3 ;
if ( ovl - > mode = = LCDC_OVERLAY_ROP3 & & ovl - > enabled )
sh_mobile_lcdc_overlay_setup ( ovl ) ;
}
return count ;
}
static const struct device_attribute overlay_sysfs_attrs [ ] = {
__ATTR ( ovl_alpha , S_IRUGO | S_IWUSR ,
overlay_alpha_show , overlay_alpha_store ) ,
__ATTR ( ovl_mode , S_IRUGO | S_IWUSR ,
overlay_mode_show , overlay_mode_store ) ,
__ATTR ( ovl_position , S_IRUGO | S_IWUSR ,
overlay_position_show , overlay_position_store ) ,
__ATTR ( ovl_rop3 , S_IRUGO | S_IWUSR ,
overlay_rop3_show , overlay_rop3_store ) ,
} ;
static const struct fb_fix_screeninfo sh_mobile_lcdc_overlay_fix = {
. id = " SH Mobile LCDC " ,
. type = FB_TYPE_PACKED_PIXELS ,
. visual = FB_VISUAL_TRUECOLOR ,
. accel = FB_ACCEL_NONE ,
2012-07-18 19:09:04 +04:00
. xpanstep = 1 ,
2011-12-12 21:43:16 +04:00
. ypanstep = 1 ,
. ywrapstep = 0 ,
. capabilities = FB_CAP_FOURCC ,
} ;
static int sh_mobile_lcdc_overlay_pan ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
unsigned long base_addr_y ;
unsigned long base_addr_c ;
2012-03-15 21:15:37 +04:00
unsigned long y_offset ;
2011-12-12 21:43:16 +04:00
unsigned long c_offset ;
2012-03-15 21:15:37 +04:00
if ( ! ovl - > format - > yuv ) {
y_offset = ( var - > yoffset * ovl - > xres_virtual + var - > xoffset )
* ovl - > format - > bpp / 8 ;
c_offset = 0 ;
} else {
unsigned int xsub = ovl - > format - > bpp < 24 ? 2 : 1 ;
unsigned int ysub = ovl - > format - > bpp < 16 ? 2 : 1 ;
y_offset = var - > yoffset * ovl - > xres_virtual + var - > xoffset ;
c_offset = var - > yoffset / ysub * ovl - > xres_virtual * 2 / xsub
+ var - > xoffset * 2 / xsub ;
}
2011-12-12 21:43:16 +04:00
2012-03-15 21:15:37 +04:00
/* If the Y offset hasn't changed, the C offset hasn't either. There's
* nothing to do in that case .
*/
if ( y_offset = = ovl - > pan_y_offset )
return 0 ;
2011-12-12 21:43:16 +04:00
/* Set the source address for the next refresh */
2012-03-15 21:15:37 +04:00
base_addr_y = ovl - > dma_handle + y_offset ;
base_addr_c = ovl - > dma_handle + ovl - > xres_virtual * ovl - > yres_virtual
+ c_offset ;
2011-12-12 21:43:16 +04:00
ovl - > base_addr_y = base_addr_y ;
2012-03-15 21:15:37 +04:00
ovl - > base_addr_c = base_addr_c ;
ovl - > pan_y_offset = y_offset ;
2011-12-12 21:43:16 +04:00
2012-07-19 04:29:52 +04:00
lcdc_write ( ovl - > channel - > lcdc , LDBCR , LDBCR_UPC ( ovl - > index ) ) ;
2011-12-12 21:43:16 +04:00
lcdc_write_overlay ( ovl , LDBnBSAYR ( ovl - > index ) , ovl - > base_addr_y ) ;
lcdc_write_overlay ( ovl , LDBnBSACR ( ovl - > index ) , ovl - > base_addr_c ) ;
2012-07-19 04:29:52 +04:00
lcdc_write ( ovl - > channel - > lcdc , LDBCR ,
LDBCR_UPF ( ovl - > index ) | LDBCR_UPD ( ovl - > index ) ) ;
2011-12-12 21:43:16 +04:00
return 0 ;
}
static int sh_mobile_lcdc_overlay_ioctl ( struct fb_info * info , unsigned int cmd ,
unsigned long arg )
{
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
switch ( cmd ) {
case FBIO_WAITFORVSYNC :
return sh_mobile_lcdc_wait_for_vsync ( ovl - > channel ) ;
default :
return - ENOIOCTLCMD ;
}
}
static int sh_mobile_lcdc_overlay_check_var ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
return __sh_mobile_lcdc_check_var ( var , info ) ;
}
static int sh_mobile_lcdc_overlay_set_par ( struct fb_info * info )
{
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
ovl - > format =
sh_mobile_format_info ( sh_mobile_format_fourcc ( & info - > var ) ) ;
ovl - > xres = info - > var . xres ;
ovl - > xres_virtual = info - > var . xres_virtual ;
ovl - > yres = info - > var . yres ;
ovl - > yres_virtual = info - > var . yres_virtual ;
if ( ovl - > format - > yuv )
2012-07-18 18:29:20 +04:00
ovl - > pitch = info - > var . xres_virtual ;
2011-12-12 21:43:16 +04:00
else
2012-07-18 18:29:20 +04:00
ovl - > pitch = info - > var . xres_virtual * ovl - > format - > bpp / 8 ;
2011-12-12 21:43:16 +04:00
sh_mobile_lcdc_overlay_setup ( ovl ) ;
info - > fix . line_length = ovl - > pitch ;
if ( sh_mobile_format_is_fourcc ( & info - > var ) ) {
info - > fix . type = FB_TYPE_FOURCC ;
info - > fix . visual = FB_VISUAL_FOURCC ;
} else {
info - > fix . type = FB_TYPE_PACKED_PIXELS ;
info - > fix . visual = FB_VISUAL_TRUECOLOR ;
}
return 0 ;
}
/* Overlay blanking. Disable the overlay when blanked. */
static int sh_mobile_lcdc_overlay_blank ( int blank , struct fb_info * info )
{
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
ovl - > enabled = ! blank ;
sh_mobile_lcdc_overlay_setup ( ovl ) ;
/* Prevent the backlight from receiving a blanking event by returning
* a non - zero value .
*/
return 1 ;
}
2012-08-16 14:13:20 +04:00
static int
sh_mobile_lcdc_overlay_mmap ( struct fb_info * info , struct vm_area_struct * vma )
{
struct sh_mobile_lcdc_overlay * ovl = info - > par ;
return dma_mmap_coherent ( ovl - > channel - > lcdc - > dev , vma , ovl - > fb_mem ,
ovl - > dma_handle , ovl - > fb_size ) ;
}
2011-12-12 21:43:16 +04:00
static struct fb_ops sh_mobile_lcdc_overlay_ops = {
. owner = THIS_MODULE ,
. fb_read = fb_sys_read ,
. fb_write = fb_sys_write ,
. fb_fillrect = sys_fillrect ,
. fb_copyarea = sys_copyarea ,
. fb_imageblit = sys_imageblit ,
. fb_blank = sh_mobile_lcdc_overlay_blank ,
. fb_pan_display = sh_mobile_lcdc_overlay_pan ,
. fb_ioctl = sh_mobile_lcdc_overlay_ioctl ,
. fb_check_var = sh_mobile_lcdc_overlay_check_var ,
. fb_set_par = sh_mobile_lcdc_overlay_set_par ,
2012-08-16 14:13:20 +04:00
. fb_mmap = sh_mobile_lcdc_overlay_mmap ,
2011-12-12 21:43:16 +04:00
} ;
static void
sh_mobile_lcdc_overlay_fb_unregister ( struct sh_mobile_lcdc_overlay * ovl )
{
struct fb_info * info = ovl - > info ;
if ( info = = NULL | | info - > dev = = NULL )
return ;
unregister_framebuffer ( ovl - > info ) ;
}
2012-12-22 01:07:39 +04:00
static int
2011-12-12 21:43:16 +04:00
sh_mobile_lcdc_overlay_fb_register ( struct sh_mobile_lcdc_overlay * ovl )
{
struct sh_mobile_lcdc_priv * lcdc = ovl - > channel - > lcdc ;
struct fb_info * info = ovl - > info ;
unsigned int i ;
int ret ;
if ( info = = NULL )
return 0 ;
ret = register_framebuffer ( info ) ;
if ( ret < 0 )
return ret ;
dev_info ( lcdc - > dev , " registered %s/overlay %u as %dx%d %dbpp. \n " ,
dev_name ( lcdc - > dev ) , ovl - > index , info - > var . xres ,
info - > var . yres , info - > var . bits_per_pixel ) ;
for ( i = 0 ; i < ARRAY_SIZE ( overlay_sysfs_attrs ) ; + + i ) {
ret = device_create_file ( info - > dev , & overlay_sysfs_attrs [ i ] ) ;
if ( ret < 0 )
return ret ;
}
return 0 ;
}
static void
sh_mobile_lcdc_overlay_fb_cleanup ( struct sh_mobile_lcdc_overlay * ovl )
{
struct fb_info * info = ovl - > info ;
if ( info = = NULL | | info - > device = = NULL )
return ;
framebuffer_release ( info ) ;
}
2012-12-22 01:07:39 +04:00
static int
2011-12-12 21:43:16 +04:00
sh_mobile_lcdc_overlay_fb_init ( struct sh_mobile_lcdc_overlay * ovl )
{
struct sh_mobile_lcdc_priv * priv = ovl - > channel - > lcdc ;
struct fb_var_screeninfo * var ;
struct fb_info * info ;
/* Allocate and initialize the frame buffer device. */
info = framebuffer_alloc ( 0 , priv - > dev ) ;
if ( info = = NULL ) {
dev_err ( priv - > dev , " unable to allocate fb_info \n " ) ;
return - ENOMEM ;
}
ovl - > info = info ;
info - > flags = FBINFO_FLAG_DEFAULT ;
info - > fbops = & sh_mobile_lcdc_overlay_ops ;
info - > device = priv - > dev ;
info - > screen_base = ovl - > fb_mem ;
info - > par = ovl ;
/* Initialize fixed screen information. Restrict pan to 2 lines steps
* for NV12 and NV21 .
*/
info - > fix = sh_mobile_lcdc_overlay_fix ;
snprintf ( info - > fix . id , sizeof ( info - > fix . id ) ,
" SH Mobile LCDC Overlay %u " , ovl - > index ) ;
info - > fix . smem_start = ovl - > dma_handle ;
info - > fix . smem_len = ovl - > fb_size ;
info - > fix . line_length = ovl - > pitch ;
if ( ovl - > format - > yuv )
info - > fix . visual = FB_VISUAL_FOURCC ;
else
info - > fix . visual = FB_VISUAL_TRUECOLOR ;
2012-07-18 19:09:04 +04:00
switch ( ovl - > format - > fourcc ) {
case V4L2_PIX_FMT_NV12 :
case V4L2_PIX_FMT_NV21 :
2012-07-26 16:36:55 +04:00
info - > fix . ypanstep = 2 ;
case V4L2_PIX_FMT_NV16 :
case V4L2_PIX_FMT_NV61 :
2012-07-18 19:09:04 +04:00
info - > fix . xpanstep = 2 ;
}
2011-12-12 21:43:16 +04:00
/* Initialize variable screen information. */
var = & info - > var ;
memset ( var , 0 , sizeof ( * var ) ) ;
var - > xres = ovl - > xres ;
var - > yres = ovl - > yres ;
var - > xres_virtual = ovl - > xres_virtual ;
var - > yres_virtual = ovl - > yres_virtual ;
var - > activate = FB_ACTIVATE_NOW ;
/* Use the legacy API by default for RGB formats, and the FOURCC API
* for YUV formats .
*/
if ( ! ovl - > format - > yuv )
var - > bits_per_pixel = ovl - > format - > bpp ;
else
var - > grayscale = ovl - > format - > fourcc ;
return sh_mobile_lcdc_overlay_check_var ( var , info ) ;
}
2011-09-07 13:09:26 +04:00
/* -----------------------------------------------------------------------------
2011-12-12 21:43:16 +04:00
* Frame buffer operations - main frame buffer
2011-09-07 13:09:26 +04:00
*/
2008-07-24 08:31:24 +04:00
static int sh_mobile_lcdc_setcolreg ( u_int regno ,
u_int red , u_int green , u_int blue ,
u_int transp , struct fb_info * info )
{
u32 * palette = info - > pseudo_palette ;
if ( regno > = PALETTE_NR )
return - EINVAL ;
/* only FB_VISUAL_TRUECOLOR supported */
red > > = 16 - info - > var . red . length ;
green > > = 16 - info - > var . green . length ;
blue > > = 16 - info - > var . blue . length ;
transp > > = 16 - info - > var . transp . length ;
palette [ regno ] = ( red < < info - > var . red . offset ) |
( green < < info - > var . green . offset ) |
( blue < < info - > var . blue . offset ) |
( transp < < info - > var . transp . offset ) ;
return 0 ;
}
2011-11-22 03:56:58 +04:00
static const struct fb_fix_screeninfo sh_mobile_lcdc_fix = {
2008-07-24 08:31:24 +04:00
. id = " SH Mobile LCDC " ,
. type = FB_TYPE_PACKED_PIXELS ,
. visual = FB_VISUAL_TRUECOLOR ,
. accel = FB_ACCEL_NONE ,
2012-07-18 19:09:04 +04:00
. xpanstep = 1 ,
2009-09-15 16:00:18 +04:00
. ypanstep = 1 ,
. ywrapstep = 0 ,
2011-12-13 17:02:28 +04:00
. capabilities = FB_CAP_FOURCC ,
2008-07-24 08:31:24 +04:00
} ;
2008-12-19 09:34:41 +03:00
static void sh_mobile_lcdc_fillrect ( struct fb_info * info ,
const struct fb_fillrect * rect )
{
sys_fillrect ( info , rect ) ;
sh_mobile_lcdc_deferred_io_touch ( info ) ;
}
static void sh_mobile_lcdc_copyarea ( struct fb_info * info ,
const struct fb_copyarea * area )
{
sys_copyarea ( info , area ) ;
sh_mobile_lcdc_deferred_io_touch ( info ) ;
}
static void sh_mobile_lcdc_imageblit ( struct fb_info * info ,
const struct fb_image * image )
{
sys_imageblit ( info , image ) ;
sh_mobile_lcdc_deferred_io_touch ( info ) ;
}
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_pan ( struct fb_var_screeninfo * var ,
struct fb_info * info )
2009-09-15 16:00:18 +04:00
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
2010-02-11 13:24:25 +03:00
struct sh_mobile_lcdc_priv * priv = ch - > lcdc ;
unsigned long ldrcntr ;
2012-03-15 21:15:37 +04:00
unsigned long base_addr_y , base_addr_c ;
unsigned long y_offset ;
2011-02-24 08:47:13 +03:00
unsigned long c_offset ;
2010-02-11 13:24:25 +03:00
2012-03-15 21:15:37 +04:00
if ( ! ch - > format - > yuv ) {
y_offset = ( var - > yoffset * ch - > xres_virtual + var - > xoffset )
* ch - > format - > bpp / 8 ;
c_offset = 0 ;
} else {
unsigned int xsub = ch - > format - > bpp < 24 ? 2 : 1 ;
unsigned int ysub = ch - > format - > bpp < 16 ? 2 : 1 ;
2009-09-15 16:00:18 +04:00
2012-03-15 21:15:37 +04:00
y_offset = var - > yoffset * ch - > xres_virtual + var - > xoffset ;
c_offset = var - > yoffset / ysub * ch - > xres_virtual * 2 / xsub
+ var - > xoffset * 2 / xsub ;
}
2009-09-15 16:00:18 +04:00
2012-03-15 21:15:37 +04:00
/* If the Y offset hasn't changed, the C offset hasn't either. There's
* nothing to do in that case .
*/
if ( y_offset = = ch - > pan_y_offset )
return 0 ;
2009-09-15 16:00:18 +04:00
2010-02-11 13:24:25 +03:00
/* Set the source address for the next refresh */
2012-03-15 21:15:37 +04:00
base_addr_y = ch - > dma_handle + y_offset ;
base_addr_c = ch - > dma_handle + ch - > xres_virtual * ch - > yres_virtual
+ c_offset ;
2011-02-24 08:47:13 +03:00
2012-03-15 15:40:47 +04:00
if ( ch - > cache )
sh_mobile_meram_cache_update ( priv - > meram_dev , ch - > cache ,
base_addr_y , base_addr_c ,
& base_addr_y , & base_addr_c ) ;
2011-05-18 15:10:07 +04:00
2011-07-13 14:13:47 +04:00
ch - > base_addr_y = base_addr_y ;
ch - > base_addr_c = base_addr_c ;
2012-03-15 21:15:37 +04:00
ch - > pan_y_offset = y_offset ;
2011-05-18 15:10:07 +04:00
2011-07-13 14:13:47 +04:00
lcdc_write_chan_mirror ( ch , LDSA1R , base_addr_y ) ;
2011-12-01 02:07:30 +04:00
if ( ch - > format - > yuv )
2011-07-13 14:13:47 +04:00
lcdc_write_chan_mirror ( ch , LDSA2R , base_addr_c ) ;
2011-02-24 08:47:13 +03:00
2012-03-15 21:15:37 +04:00
ldrcntr = lcdc_read ( priv , _LDRCNTR ) ;
2010-02-11 13:24:25 +03:00
if ( lcdc_chan_is_sublcd ( ch ) )
lcdc_write ( ch - > lcdc , _LDRCNTR , ldrcntr ^ LDRCNTR_SRS ) ;
else
lcdc_write ( ch - > lcdc , _LDRCNTR , ldrcntr ^ LDRCNTR_MRS ) ;
sh_mobile_lcdc_deferred_io_touch ( info ) ;
2009-09-15 16:00:18 +04:00
return 0 ;
}
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_ioctl ( struct fb_info * info , unsigned int cmd ,
unsigned long arg )
2010-02-15 16:57:49 +03:00
{
2011-11-22 03:56:58 +04:00
struct sh_mobile_lcdc_chan * ch = info - > par ;
2010-02-15 16:57:49 +03:00
int retval ;
switch ( cmd ) {
case FBIO_WAITFORVSYNC :
2011-11-22 03:56:58 +04:00
retval = sh_mobile_lcdc_wait_for_vsync ( ch ) ;
2010-02-15 16:57:49 +03:00
break ;
default :
retval = - ENOIOCTLCMD ;
break ;
}
return retval ;
}
2010-09-14 18:48:54 +04:00
static void sh_mobile_fb_reconfig ( struct fb_info * info )
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
2011-11-29 16:42:48 +04:00
struct fb_var_screeninfo var ;
struct fb_videomode mode ;
2010-09-14 18:48:54 +04:00
struct fb_event event ;
int evnt = FB_EVENT_MODE_CHANGE_ALL ;
if ( ch - > use_count > 1 | | ( ch - > use_count = = 1 & & ! info - > fbcon_par ) )
/* More framebuffer users are active */
return ;
2011-11-29 16:42:48 +04:00
fb_var_to_videomode ( & mode , & info - > var ) ;
2010-09-14 18:48:54 +04:00
2011-11-29 16:42:48 +04:00
if ( fb_mode_is_equal ( & ch - > display . mode , & mode ) )
2010-09-14 18:48:54 +04:00
return ;
/* Display has been re-plugged, framebuffer is free now, reconfigure */
2011-11-29 16:42:48 +04:00
var = info - > var ;
fb_videomode_to_var ( & var , & ch - > display . mode ) ;
var . width = ch - > display . width ;
var . height = ch - > display . height ;
var . activate = FB_ACTIVATE_NOW ;
if ( fb_set_var ( info , & var ) < 0 )
2010-09-14 18:48:54 +04:00
/* Couldn't reconfigure, hopefully, can continue as before */
return ;
/*
* fb_set_var ( ) calls the notifier change internally , only if
* FBINFO_MISC_USEREVENT flag is set . Since we do not want to fake a
* user event , we have to call the chain ourselves .
*/
event . info = info ;
2011-11-29 16:42:48 +04:00
event . data = & ch - > display . mode ;
2010-09-14 18:48:54 +04:00
fb_notifier_call_chain ( evnt , & event ) ;
}
/*
* Locking : both . fb_release ( ) and . fb_open ( ) are called with info - > lock held if
* user = = 1 , or with console sem held , if user = = 0.
*/
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_release ( struct fb_info * info , int user )
2010-09-14 18:48:54 +04:00
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
mutex_lock ( & ch - > open_lock ) ;
dev_dbg ( info - > dev , " %s(): %d users \n " , __func__ , ch - > use_count ) ;
ch - > use_count - - ;
/* Nothing to reconfigure, when called from fbcon */
if ( user ) {
2011-01-26 02:07:35 +03:00
console_lock ( ) ;
2010-09-14 18:48:54 +04:00
sh_mobile_fb_reconfig ( info ) ;
2011-01-26 02:07:35 +03:00
console_unlock ( ) ;
2010-09-14 18:48:54 +04:00
}
mutex_unlock ( & ch - > open_lock ) ;
return 0 ;
}
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_open ( struct fb_info * info , int user )
2010-09-14 18:48:54 +04:00
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
mutex_lock ( & ch - > open_lock ) ;
ch - > use_count + + ;
dev_dbg ( info - > dev , " %s(): %d users \n " , __func__ , ch - > use_count ) ;
mutex_unlock ( & ch - > open_lock ) ;
return 0 ;
}
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_check_var ( struct fb_var_screeninfo * var ,
struct fb_info * info )
2010-09-14 18:48:54 +04:00
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
2011-01-05 13:21:00 +03:00
struct sh_mobile_lcdc_priv * p = ch - > lcdc ;
2011-08-31 15:00:53 +04:00
unsigned int best_dist = ( unsigned int ) - 1 ;
unsigned int best_xres = 0 ;
unsigned int best_yres = 0 ;
unsigned int i ;
2011-12-12 21:43:16 +04:00
int ret ;
2011-08-31 15:00:53 +04:00
/* If board code provides us with a list of available modes, make sure
* we use one of them . Find the mode closest to the requested one . The
* distance between two modes is defined as the size of the
* non - overlapping parts of the two rectangles .
*/
2011-11-22 03:56:58 +04:00
for ( i = 0 ; i < ch - > cfg - > num_modes ; + + i ) {
const struct fb_videomode * mode = & ch - > cfg - > lcd_modes [ i ] ;
2011-08-31 15:00:53 +04:00
unsigned int dist ;
/* We can only round up. */
if ( var - > xres > mode - > xres | | var - > yres > mode - > yres )
continue ;
dist = var - > xres * var - > yres + mode - > xres * mode - > yres
- 2 * min ( var - > xres , mode - > xres )
* min ( var - > yres , mode - > yres ) ;
if ( dist < best_dist ) {
best_xres = mode - > xres ;
best_yres = mode - > yres ;
best_dist = dist ;
}
2010-09-14 18:48:54 +04:00
}
2011-01-05 13:21:00 +03:00
2011-08-31 15:00:53 +04:00
/* If no available mode can be used, return an error. */
2011-11-22 03:56:58 +04:00
if ( ch - > cfg - > num_modes ! = 0 ) {
2011-08-31 15:00:53 +04:00
if ( best_dist = = ( unsigned int ) - 1 )
return - EINVAL ;
var - > xres = best_xres ;
var - > yres = best_yres ;
}
2011-12-12 21:43:16 +04:00
ret = __sh_mobile_lcdc_check_var ( var , info ) ;
if ( ret < 0 )
return ret ;
2011-08-31 15:00:53 +04:00
2011-12-13 17:02:28 +04:00
/* only accept the forced_fourcc for dual channel configurations */
if ( p - > forced_fourcc & &
p - > forced_fourcc ! = sh_mobile_format_fourcc ( var ) )
2011-01-05 13:21:00 +03:00
return - EINVAL ;
2010-09-14 18:48:54 +04:00
return 0 ;
}
2010-02-15 16:57:49 +03:00
2011-11-22 03:56:58 +04:00
static int sh_mobile_lcdc_set_par ( struct fb_info * info )
2011-08-31 15:00:54 +04:00
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
int ret ;
sh_mobile_lcdc_stop ( ch - > lcdc ) ;
2011-08-31 15:00:56 +04:00
2011-11-29 19:05:36 +04:00
ch - > format = sh_mobile_format_info ( sh_mobile_format_fourcc ( & info - > var ) ) ;
2011-12-01 02:07:30 +04:00
ch - > colorspace = info - > var . colorspace ;
ch - > xres = info - > var . xres ;
ch - > xres_virtual = info - > var . xres_virtual ;
ch - > yres = info - > var . yres ;
ch - > yres_virtual = info - > var . yres_virtual ;
if ( ch - > format - > yuv )
2012-07-18 18:29:20 +04:00
ch - > pitch = info - > var . xres_virtual ;
2011-12-01 02:07:30 +04:00
else
2012-07-18 18:29:20 +04:00
ch - > pitch = info - > var . xres_virtual * ch - > format - > bpp / 8 ;
2011-11-29 19:05:36 +04:00
2011-08-31 15:00:54 +04:00
ret = sh_mobile_lcdc_start ( ch - > lcdc ) ;
2011-12-01 02:07:30 +04:00
if ( ret < 0 )
2011-08-31 15:00:54 +04:00
dev_err ( info - > dev , " %s: unable to restart LCDC \n " , __func__ ) ;
2011-12-01 02:07:30 +04:00
info - > fix . line_length = ch - > pitch ;
2011-08-31 15:00:54 +04:00
2011-12-13 17:02:28 +04:00
if ( sh_mobile_format_is_fourcc ( & info - > var ) ) {
info - > fix . type = FB_TYPE_FOURCC ;
info - > fix . visual = FB_VISUAL_FOURCC ;
} else {
info - > fix . type = FB_TYPE_PACKED_PIXELS ;
info - > fix . visual = FB_VISUAL_TRUECOLOR ;
}
2011-08-31 15:00:54 +04:00
return ret ;
}
2011-02-23 11:36:30 +03:00
/*
* Screen blanking . Behavior is as follows :
* FB_BLANK_UNBLANK : screen unblanked , clocks enabled
* FB_BLANK_NORMAL : screen blanked , clocks enabled
* FB_BLANK_VSYNC ,
* FB_BLANK_HSYNC ,
* FB_BLANK_POWEROFF : screen blanked , clocks disabled
*/
static int sh_mobile_lcdc_blank ( int blank , struct fb_info * info )
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
struct sh_mobile_lcdc_priv * p = ch - > lcdc ;
/* blank the screen? */
if ( blank > FB_BLANK_UNBLANK & & ch - > blank_status = = FB_BLANK_UNBLANK ) {
struct fb_fillrect rect = {
2011-12-01 02:07:30 +04:00
. width = ch - > xres ,
. height = ch - > yres ,
2011-02-23 11:36:30 +03:00
} ;
sh_mobile_lcdc_fillrect ( info , & rect ) ;
}
/* turn clocks on? */
if ( blank < = FB_BLANK_NORMAL & & ch - > blank_status > FB_BLANK_NORMAL ) {
sh_mobile_lcdc_clk_on ( p ) ;
}
/* turn clocks off? */
if ( blank > FB_BLANK_NORMAL & & ch - > blank_status < = FB_BLANK_NORMAL ) {
/* make sure the screen is updated with the black fill before
* switching the clocks off . one vsync is not enough since
* blanking may occur in the middle of a refresh . deferred io
* mode will reenable the clocks and update the screen in time ,
* so it does not need this . */
if ( ! info - > fbdefio ) {
2011-11-22 03:56:58 +04:00
sh_mobile_lcdc_wait_for_vsync ( ch ) ;
sh_mobile_lcdc_wait_for_vsync ( ch ) ;
2011-02-23 11:36:30 +03:00
}
sh_mobile_lcdc_clk_off ( p ) ;
}
ch - > blank_status = blank ;
return 0 ;
}
2012-08-16 14:13:20 +04:00
static int
sh_mobile_lcdc_mmap ( struct fb_info * info , struct vm_area_struct * vma )
{
struct sh_mobile_lcdc_chan * ch = info - > par ;
return dma_mmap_coherent ( ch - > lcdc - > dev , vma , ch - > fb_mem ,
ch - > dma_handle , ch - > fb_size ) ;
}
2008-07-24 08:31:24 +04:00
static struct fb_ops sh_mobile_lcdc_ops = {
2009-09-15 16:00:18 +04:00
. owner = THIS_MODULE ,
2008-07-24 08:31:24 +04:00
. fb_setcolreg = sh_mobile_lcdc_setcolreg ,
2008-12-17 11:29:49 +03:00
. fb_read = fb_sys_read ,
. fb_write = fb_sys_write ,
2008-12-19 09:34:41 +03:00
. fb_fillrect = sh_mobile_lcdc_fillrect ,
. fb_copyarea = sh_mobile_lcdc_copyarea ,
. fb_imageblit = sh_mobile_lcdc_imageblit ,
2011-02-23 11:36:30 +03:00
. fb_blank = sh_mobile_lcdc_blank ,
2011-11-22 03:56:58 +04:00
. fb_pan_display = sh_mobile_lcdc_pan ,
. fb_ioctl = sh_mobile_lcdc_ioctl ,
. fb_open = sh_mobile_lcdc_open ,
. fb_release = sh_mobile_lcdc_release ,
. fb_check_var = sh_mobile_lcdc_check_var ,
. fb_set_par = sh_mobile_lcdc_set_par ,
2012-08-16 14:13:20 +04:00
. fb_mmap = sh_mobile_lcdc_mmap ,
2008-07-24 08:31:24 +04:00
} ;
2011-11-29 17:37:35 +04:00
static void
sh_mobile_lcdc_channel_fb_unregister ( struct sh_mobile_lcdc_chan * ch )
{
if ( ch - > info & & ch - > info - > dev )
unregister_framebuffer ( ch - > info ) ;
}
2012-12-22 01:07:39 +04:00
static int
2011-11-29 17:37:35 +04:00
sh_mobile_lcdc_channel_fb_register ( struct sh_mobile_lcdc_chan * ch )
{
struct fb_info * info = ch - > info ;
int ret ;
if ( info - > fbdefio ) {
ch - > sglist = vmalloc ( sizeof ( struct scatterlist ) *
ch - > fb_size > > PAGE_SHIFT ) ;
if ( ! ch - > sglist ) {
dev_err ( ch - > lcdc - > dev , " cannot allocate sglist \n " ) ;
return - ENOMEM ;
}
}
info - > bl_dev = ch - > bl ;
ret = register_framebuffer ( info ) ;
if ( ret < 0 )
return ret ;
dev_info ( ch - > lcdc - > dev , " registered %s/%s as %dx%d %dbpp. \n " ,
2011-11-22 03:56:58 +04:00
dev_name ( ch - > lcdc - > dev ) , ( ch - > cfg - > chan = = LCDC_CHAN_MAINLCD ) ?
2011-11-29 17:37:35 +04:00
" mainlcd " : " sublcd " , info - > var . xres , info - > var . yres ,
info - > var . bits_per_pixel ) ;
/* deferred io mode: disable clock to save power */
if ( info - > fbdefio | | info - > state = = FBINFO_STATE_SUSPENDED )
sh_mobile_lcdc_clk_off ( ch - > lcdc ) ;
return ret ;
}
static void
sh_mobile_lcdc_channel_fb_cleanup ( struct sh_mobile_lcdc_chan * ch )
{
struct fb_info * info = ch - > info ;
if ( ! info | | ! info - > device )
return ;
if ( ch - > sglist )
vfree ( ch - > sglist ) ;
fb_dealloc_cmap ( & info - > cmap ) ;
framebuffer_release ( info ) ;
}
2012-12-22 01:07:39 +04:00
static int
2011-11-29 17:37:35 +04:00
sh_mobile_lcdc_channel_fb_init ( struct sh_mobile_lcdc_chan * ch ,
2012-08-13 15:56:46 +04:00
const struct fb_videomode * modes ,
2011-11-29 17:37:35 +04:00
unsigned int num_modes )
{
struct sh_mobile_lcdc_priv * priv = ch - > lcdc ;
struct fb_var_screeninfo * var ;
struct fb_info * info ;
int ret ;
/* Allocate and initialize the frame buffer device. Create the modes
* list and allocate the color map .
*/
info = framebuffer_alloc ( 0 , priv - > dev ) ;
if ( info = = NULL ) {
dev_err ( priv - > dev , " unable to allocate fb_info \n " ) ;
return - ENOMEM ;
}
ch - > info = info ;
info - > flags = FBINFO_FLAG_DEFAULT ;
info - > fbops = & sh_mobile_lcdc_ops ;
info - > device = priv - > dev ;
info - > screen_base = ch - > fb_mem ;
info - > pseudo_palette = & ch - > pseudo_palette ;
info - > par = ch ;
2012-08-13 15:56:46 +04:00
fb_videomode_to_modelist ( modes , num_modes , & info - > modelist ) ;
2011-11-29 17:37:35 +04:00
ret = fb_alloc_cmap ( & info - > cmap , PALETTE_NR , 0 ) ;
if ( ret < 0 ) {
dev_err ( priv - > dev , " unable to allocate cmap \n " ) ;
return ret ;
}
/* Initialize fixed screen information. Restrict pan to 2 lines steps
* for NV12 and NV21 .
*/
info - > fix = sh_mobile_lcdc_fix ;
info - > fix . smem_start = ch - > dma_handle ;
info - > fix . smem_len = ch - > fb_size ;
2011-12-01 02:07:30 +04:00
info - > fix . line_length = ch - > pitch ;
if ( ch - > format - > yuv )
info - > fix . visual = FB_VISUAL_FOURCC ;
else
info - > fix . visual = FB_VISUAL_TRUECOLOR ;
2012-07-18 19:09:04 +04:00
switch ( ch - > format - > fourcc ) {
case V4L2_PIX_FMT_NV12 :
case V4L2_PIX_FMT_NV21 :
2012-07-26 16:36:55 +04:00
info - > fix . ypanstep = 2 ;
case V4L2_PIX_FMT_NV16 :
case V4L2_PIX_FMT_NV61 :
2012-07-18 19:09:04 +04:00
info - > fix . xpanstep = 2 ;
}
2011-11-29 17:37:35 +04:00
/* Initialize variable screen information using the first mode as
2012-07-18 18:59:16 +04:00
* default .
2011-11-29 17:37:35 +04:00
*/
var = & info - > var ;
2012-08-13 15:56:46 +04:00
fb_videomode_to_var ( var , modes ) ;
2012-08-13 15:56:46 +04:00
var - > width = ch - > display . width ;
var - > height = ch - > display . height ;
2012-07-18 18:59:16 +04:00
var - > xres_virtual = ch - > xres_virtual ;
var - > yres_virtual = ch - > yres_virtual ;
2011-11-29 17:37:35 +04:00
var - > activate = FB_ACTIVATE_NOW ;
/* Use the legacy API by default for RGB formats, and the FOURCC API
* for YUV formats .
*/
if ( ! ch - > format - > yuv )
var - > bits_per_pixel = ch - > format - > bpp ;
else
var - > grayscale = ch - > format - > fourcc ;
2011-11-22 03:56:58 +04:00
ret = sh_mobile_lcdc_check_var ( var , info ) ;
2011-11-29 17:37:35 +04:00
if ( ret )
return ret ;
return 0 ;
}
2011-09-07 13:09:26 +04:00
/* -----------------------------------------------------------------------------
* Backlight
*/
2011-02-16 06:49:01 +03:00
static int sh_mobile_lcdc_update_bl ( struct backlight_device * bdev )
{
struct sh_mobile_lcdc_chan * ch = bl_get_data ( bdev ) ;
int brightness = bdev - > props . brightness ;
if ( bdev - > props . power ! = FB_BLANK_UNBLANK | |
bdev - > props . state & ( BL_CORE_SUSPENDED | BL_CORE_FBBLANK ) )
brightness = 0 ;
2012-08-15 20:10:03 +04:00
ch - > bl_brightness = brightness ;
2011-11-22 03:56:58 +04:00
return ch - > cfg - > bl_info . set_brightness ( brightness ) ;
2011-02-16 06:49:01 +03:00
}
static int sh_mobile_lcdc_get_brightness ( struct backlight_device * bdev )
{
struct sh_mobile_lcdc_chan * ch = bl_get_data ( bdev ) ;
2012-08-15 20:10:03 +04:00
return ch - > bl_brightness ;
2011-02-16 06:49:01 +03:00
}
static int sh_mobile_lcdc_check_fb ( struct backlight_device * bdev ,
struct fb_info * info )
{
return ( info - > bl_dev = = bdev ) ;
}
static struct backlight_ops sh_mobile_lcdc_bl_ops = {
. options = BL_CORE_SUSPENDRESUME ,
. update_status = sh_mobile_lcdc_update_bl ,
. get_brightness = sh_mobile_lcdc_get_brightness ,
. check_fb = sh_mobile_lcdc_check_fb ,
} ;
static struct backlight_device * sh_mobile_lcdc_bl_probe ( struct device * parent ,
struct sh_mobile_lcdc_chan * ch )
{
struct backlight_device * bl ;
2011-11-22 03:56:58 +04:00
bl = backlight_device_register ( ch - > cfg - > bl_info . name , parent , ch ,
2011-02-16 06:49:01 +03:00
& sh_mobile_lcdc_bl_ops , NULL ) ;
2011-03-21 18:03:13 +03:00
if ( IS_ERR ( bl ) ) {
dev_err ( parent , " unable to register backlight device: %ld \n " ,
PTR_ERR ( bl ) ) ;
2011-02-16 06:49:01 +03:00
return NULL ;
}
2011-11-22 03:56:58 +04:00
bl - > props . max_brightness = ch - > cfg - > bl_info . max_brightness ;
2011-02-16 06:49:01 +03:00
bl - > props . brightness = bl - > props . max_brightness ;
backlight_update_status ( bl ) ;
return bl ;
}
static void sh_mobile_lcdc_bl_remove ( struct backlight_device * bdev )
{
backlight_device_unregister ( bdev ) ;
}
2011-09-07 13:09:26 +04:00
/* -----------------------------------------------------------------------------
* Power management
*/
2009-03-13 18:36:55 +03:00
static int sh_mobile_lcdc_suspend ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
sh_mobile_lcdc_stop ( platform_get_drvdata ( pdev ) ) ;
return 0 ;
}
static int sh_mobile_lcdc_resume ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
return sh_mobile_lcdc_start ( platform_get_drvdata ( pdev ) ) ;
}
2009-08-14 14:49:08 +04:00
static int sh_mobile_lcdc_runtime_suspend ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
2011-07-13 14:13:47 +04:00
struct sh_mobile_lcdc_priv * priv = platform_get_drvdata ( pdev ) ;
2009-08-14 14:49:08 +04:00
/* turn off LCDC hardware */
2011-07-13 14:13:47 +04:00
lcdc_write ( priv , _LDCNT1R , 0 ) ;
2009-08-14 14:49:08 +04:00
return 0 ;
}
static int sh_mobile_lcdc_runtime_resume ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
2011-07-13 14:13:47 +04:00
struct sh_mobile_lcdc_priv * priv = platform_get_drvdata ( pdev ) ;
2009-08-14 14:49:08 +04:00
2011-07-13 14:13:47 +04:00
__sh_mobile_lcdc_start ( priv ) ;
2009-08-14 14:49:08 +04:00
return 0 ;
}
2009-12-15 05:00:08 +03:00
static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
2009-03-13 18:36:55 +03:00
. suspend = sh_mobile_lcdc_suspend ,
. resume = sh_mobile_lcdc_resume ,
2009-08-14 14:49:08 +04:00
. runtime_suspend = sh_mobile_lcdc_runtime_suspend ,
. runtime_resume = sh_mobile_lcdc_runtime_resume ,
2009-03-13 18:36:55 +03:00
} ;
2011-09-07 13:09:26 +04:00
/* -----------------------------------------------------------------------------
* Framebuffer notifier
*/
2010-09-03 11:20:23 +04:00
/* locking: called with info->lock held */
2010-07-21 14:13:21 +04:00
static int sh_mobile_lcdc_notify ( struct notifier_block * nb ,
unsigned long action , void * data )
{
struct fb_event * event = data ;
struct fb_info * info = event - > info ;
struct sh_mobile_lcdc_chan * ch = info - > par ;
if ( & ch - > lcdc - > notifier ! = nb )
2010-09-03 11:20:08 +04:00
return NOTIFY_DONE ;
2010-07-21 14:13:21 +04:00
dev_dbg ( info - > dev , " %s(): action = %lu, data = %p \n " ,
__func__ , action , event - > data ) ;
switch ( action ) {
case FB_EVENT_SUSPEND :
2011-09-09 17:45:43 +04:00
sh_mobile_lcdc_display_off ( ch ) ;
2010-09-03 11:20:39 +04:00
sh_mobile_lcdc_stop ( ch - > lcdc ) ;
2010-07-21 14:13:21 +04:00
break ;
case FB_EVENT_RESUME :
2010-09-14 18:48:54 +04:00
mutex_lock ( & ch - > open_lock ) ;
sh_mobile_fb_reconfig ( info ) ;
mutex_unlock ( & ch - > open_lock ) ;
2010-07-21 14:13:21 +04:00
2011-09-09 17:45:43 +04:00
sh_mobile_lcdc_display_on ( ch ) ;
2011-05-05 20:33:40 +04:00
sh_mobile_lcdc_start ( ch - > lcdc ) ;
2010-07-21 14:13:21 +04:00
}
2010-09-03 11:20:08 +04:00
return NOTIFY_OK ;
2010-07-21 14:13:21 +04:00
}
2011-09-07 13:09:26 +04:00
/* -----------------------------------------------------------------------------
* Probe / remove and driver init / exit
*/
2012-12-22 01:07:39 +04:00
static const struct fb_videomode default_720p = {
2011-09-07 13:09:26 +04:00
. name = " HDMI 720p " ,
. xres = 1280 ,
. yres = 720 ,
. left_margin = 220 ,
. right_margin = 110 ,
. hsync_len = 40 ,
. upper_margin = 20 ,
. lower_margin = 5 ,
. vsync_len = 5 ,
. pixclock = 13468 ,
. refresh = 60 ,
. sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT ,
} ;
2011-08-31 15:00:57 +04:00
static int sh_mobile_lcdc_remove ( struct platform_device * pdev )
{
struct sh_mobile_lcdc_priv * priv = platform_get_drvdata ( pdev ) ;
2011-12-12 21:43:16 +04:00
unsigned int i ;
2011-08-31 15:00:57 +04:00
fb_unregister_client ( & priv - > notifier ) ;
2011-12-12 21:43:16 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( priv - > overlays ) ; i + + )
sh_mobile_lcdc_overlay_fb_unregister ( & priv - > overlays [ i ] ) ;
2011-08-31 15:00:57 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( priv - > ch ) ; i + + )
2011-11-29 17:37:35 +04:00
sh_mobile_lcdc_channel_fb_unregister ( & priv - > ch [ i ] ) ;
2011-08-31 15:00:57 +04:00
sh_mobile_lcdc_stop ( priv ) ;
2011-12-12 21:43:16 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( priv - > overlays ) ; i + + ) {
struct sh_mobile_lcdc_overlay * ovl = & priv - > overlays [ i ] ;
sh_mobile_lcdc_overlay_fb_cleanup ( ovl ) ;
if ( ovl - > fb_mem )
dma_free_coherent ( & pdev - > dev , ovl - > fb_size ,
ovl - > fb_mem , ovl - > dma_handle ) ;
}
2011-08-31 15:00:57 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( priv - > ch ) ; i + + ) {
2011-09-12 00:59:04 +04:00
struct sh_mobile_lcdc_chan * ch = & priv - > ch [ i ] ;
2011-08-31 15:00:57 +04:00
2011-09-18 14:21:17 +04:00
if ( ch - > tx_dev ) {
ch - > tx_dev - > lcdc = NULL ;
2011-11-22 03:56:58 +04:00
module_put ( ch - > cfg - > tx_dev - > dev . driver - > owner ) ;
2011-09-18 14:21:17 +04:00
}
2011-09-12 00:59:04 +04:00
2011-11-29 17:37:35 +04:00
sh_mobile_lcdc_channel_fb_cleanup ( ch ) ;
2011-08-31 15:00:57 +04:00
2011-11-29 17:37:35 +04:00
if ( ch - > fb_mem )
dma_free_coherent ( & pdev - > dev , ch - > fb_size ,
ch - > fb_mem , ch - > dma_handle ) ;
2011-08-31 15:00:57 +04:00
}
for ( i = 0 ; i < ARRAY_SIZE ( priv - > ch ) ; i + + ) {
2012-03-15 21:34:05 +04:00
struct sh_mobile_lcdc_chan * ch = & priv - > ch [ i ] ;
if ( ch - > bl )
sh_mobile_lcdc_bl_remove ( ch - > bl ) ;
mutex_destroy ( & ch - > open_lock ) ;
2011-08-31 15:00:57 +04:00
}
2011-09-07 17:47:07 +04:00
if ( priv - > dot_clk ) {
pm_runtime_disable ( & pdev - > dev ) ;
2011-08-31 15:00:57 +04:00
clk_put ( priv - > dot_clk ) ;
2011-09-07 17:47:07 +04:00
}
2011-08-31 15:00:57 +04:00
if ( priv - > base )
iounmap ( priv - > base ) ;
if ( priv - > irq )
free_irq ( priv - > irq , priv ) ;
kfree ( priv ) ;
return 0 ;
}
2008-07-24 08:31:24 +04:00
2012-12-22 01:07:39 +04:00
static int sh_mobile_lcdc_check_interface ( struct sh_mobile_lcdc_chan * ch )
2011-09-07 13:09:26 +04:00
{
2011-11-22 03:56:58 +04:00
int interface_type = ch - > cfg - > interface_type ;
2011-09-07 13:09:26 +04:00
switch ( interface_type ) {
case RGB8 :
case RGB9 :
case RGB12A :
case RGB12B :
case RGB16 :
case RGB18 :
case RGB24 :
case SYS8A :
case SYS8B :
case SYS8C :
case SYS8D :
case SYS9 :
case SYS12 :
case SYS16A :
case SYS16B :
case SYS16C :
case SYS18 :
case SYS24 :
break ;
default :
return - EINVAL ;
}
/* SUBLCD only supports SYS interface */
if ( lcdc_chan_is_sublcd ( ch ) ) {
if ( ! ( interface_type & LDMT1R_IFM ) )
return - EINVAL ;
interface_type & = ~ LDMT1R_IFM ;
}
ch - > ldmt1r_value = interface_type ;
return 0 ;
}
2012-12-22 01:07:39 +04:00
static int
2012-08-13 15:56:46 +04:00
sh_mobile_lcdc_overlay_init ( struct sh_mobile_lcdc_overlay * ovl )
2011-12-12 21:43:16 +04:00
{
const struct sh_mobile_lcdc_format_info * format ;
2012-08-13 15:56:46 +04:00
struct device * dev = ovl - > channel - > lcdc - > dev ;
2011-12-12 21:43:16 +04:00
int ret ;
if ( ovl - > cfg - > fourcc = = 0 )
return 0 ;
/* Validate the format. */
format = sh_mobile_format_info ( ovl - > cfg - > fourcc ) ;
if ( format = = NULL ) {
2012-08-13 15:56:46 +04:00
dev_err ( dev , " Invalid FOURCC %08x \n " , ovl - > cfg - > fourcc ) ;
2011-12-12 21:43:16 +04:00
return - EINVAL ;
}
ovl - > enabled = false ;
ovl - > mode = LCDC_OVERLAY_BLEND ;
ovl - > alpha = 255 ;
ovl - > rop3 = 0 ;
ovl - > pos_x = 0 ;
ovl - > pos_y = 0 ;
/* The default Y virtual resolution is twice the panel size to allow for
* double - buffering .
*/
ovl - > format = format ;
ovl - > xres = ovl - > cfg - > max_xres ;
ovl - > xres_virtual = ovl - > xres ;
ovl - > yres = ovl - > cfg - > max_yres ;
ovl - > yres_virtual = ovl - > yres * 2 ;
if ( ! format - > yuv )
2012-07-18 18:29:20 +04:00
ovl - > pitch = ovl - > xres_virtual * format - > bpp / 8 ;
2011-12-12 21:43:16 +04:00
else
2012-07-18 18:29:20 +04:00
ovl - > pitch = ovl - > xres_virtual ;
2011-12-12 21:43:16 +04:00
/* Allocate frame buffer memory. */
ovl - > fb_size = ovl - > cfg - > max_xres * ovl - > cfg - > max_yres
* format - > bpp / 8 * 2 ;
2012-08-13 15:56:46 +04:00
ovl - > fb_mem = dma_alloc_coherent ( dev , ovl - > fb_size , & ovl - > dma_handle ,
GFP_KERNEL ) ;
2011-12-12 21:43:16 +04:00
if ( ! ovl - > fb_mem ) {
2012-08-13 15:56:46 +04:00
dev_err ( dev , " unable to allocate buffer \n " ) ;
2011-12-12 21:43:16 +04:00
return - ENOMEM ;
}
ret = sh_mobile_lcdc_overlay_fb_init ( ovl ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
2012-12-22 01:07:39 +04:00
static int
2012-08-13 15:56:46 +04:00
sh_mobile_lcdc_channel_init ( struct sh_mobile_lcdc_chan * ch )
2008-07-24 08:31:24 +04:00
{
2011-11-29 18:58:10 +04:00
const struct sh_mobile_lcdc_format_info * format ;
2011-11-22 03:56:58 +04:00
const struct sh_mobile_lcdc_chan_cfg * cfg = ch - > cfg ;
2012-08-13 15:56:46 +04:00
struct device * dev = ch - > lcdc - > dev ;
2011-08-31 15:00:58 +04:00
const struct fb_videomode * max_mode ;
const struct fb_videomode * mode ;
2011-11-29 17:37:35 +04:00
unsigned int num_modes ;
2011-08-31 15:00:58 +04:00
unsigned int max_size ;
2011-11-29 17:37:35 +04:00
unsigned int i ;
2011-08-31 15:00:58 +04:00
2011-08-31 15:00:59 +04:00
mutex_init ( & ch - > open_lock ) ;
2011-09-18 16:14:46 +04:00
ch - > notify = sh_mobile_lcdc_display_notify ;
2011-08-31 15:00:59 +04:00
2011-11-29 18:58:10 +04:00
/* Validate the format. */
format = sh_mobile_format_info ( cfg - > fourcc ) ;
if ( format = = NULL ) {
2012-08-13 15:56:46 +04:00
dev_err ( dev , " Invalid FOURCC %08x. \n " , cfg - > fourcc ) ;
2011-11-29 18:58:10 +04:00
return - EINVAL ;
}
2011-08-31 15:00:58 +04:00
/* Iterate through the modes to validate them and find the highest
* resolution .
*/
max_mode = NULL ;
max_size = 0 ;
2011-11-29 17:33:41 +04:00
for ( i = 0 , mode = cfg - > lcd_modes ; i < cfg - > num_modes ; i + + , mode + + ) {
2011-08-31 15:00:58 +04:00
unsigned int size = mode - > yres * mode - > xres ;
2011-12-13 17:02:28 +04:00
/* NV12/NV21 buffers must have even number of lines */
if ( ( cfg - > fourcc = = V4L2_PIX_FMT_NV12 | |
cfg - > fourcc = = V4L2_PIX_FMT_NV21 ) & & ( mode - > yres & 0x1 ) ) {
2012-08-13 15:56:46 +04:00
dev_err ( dev , " yres must be multiple of 2 for "
2011-09-07 18:02:31 +04:00
" YCbCr420 mode. \n " ) ;
2011-08-31 15:00:58 +04:00
return - EINVAL ;
}
if ( size > max_size ) {
max_mode = mode ;
max_size = size ;
}
}
if ( ! max_size )
max_size = MAX_XRES * MAX_YRES ;
else
2012-08-13 15:56:46 +04:00
dev_dbg ( dev , " Found largest videomode %ux%u \n " ,
2011-08-31 15:00:58 +04:00
max_mode - > xres , max_mode - > yres ) ;
2011-11-29 17:33:41 +04:00
if ( cfg - > lcd_modes = = NULL ) {
2011-08-31 15:00:58 +04:00
mode = & default_720p ;
2011-11-29 17:33:41 +04:00
num_modes = 1 ;
2011-08-31 15:00:58 +04:00
} else {
2011-11-29 17:33:41 +04:00
mode = cfg - > lcd_modes ;
num_modes = cfg - > num_modes ;
2011-08-31 15:00:58 +04:00
}
2012-07-18 18:59:16 +04:00
/* Use the first mode as default. The default Y virtual resolution is
* twice the panel size to allow for double - buffering .
*/
2011-12-01 02:07:30 +04:00
ch - > format = format ;
ch - > xres = mode - > xres ;
ch - > xres_virtual = mode - > xres ;
ch - > yres = mode - > yres ;
ch - > yres_virtual = mode - > yres * 2 ;
if ( ! format - > yuv ) {
ch - > colorspace = V4L2_COLORSPACE_SRGB ;
2012-07-18 18:29:20 +04:00
ch - > pitch = ch - > xres_virtual * format - > bpp / 8 ;
2011-12-01 02:07:30 +04:00
} else {
ch - > colorspace = V4L2_COLORSPACE_REC709 ;
2012-07-18 18:29:20 +04:00
ch - > pitch = ch - > xres_virtual ;
2011-12-01 02:07:30 +04:00
}
2011-11-29 17:37:35 +04:00
ch - > display . width = cfg - > panel_cfg . width ;
ch - > display . height = cfg - > panel_cfg . height ;
ch - > display . mode = * mode ;
/* Allocate frame buffer memory. */
ch - > fb_size = max_size * format - > bpp / 8 * 2 ;
2012-08-13 15:56:46 +04:00
ch - > fb_mem = dma_alloc_coherent ( dev , ch - > fb_size , & ch - > dma_handle ,
2011-11-29 17:37:35 +04:00
GFP_KERNEL ) ;
if ( ch - > fb_mem = = NULL ) {
2012-08-13 15:56:46 +04:00
dev_err ( dev , " unable to allocate buffer \n " ) ;
2011-11-29 17:37:35 +04:00
return - ENOMEM ;
}
2011-08-31 15:00:58 +04:00
2011-11-29 04:46:12 +04:00
/* Initialize the transmitter device if present. */
if ( cfg - > tx_dev ) {
if ( ! cfg - > tx_dev - > dev . driver | |
! try_module_get ( cfg - > tx_dev - > dev . driver - > owner ) ) {
2012-08-13 15:56:46 +04:00
dev_warn ( dev , " unable to get transmitter device \n " ) ;
2011-11-29 04:46:12 +04:00
return - EINVAL ;
}
ch - > tx_dev = platform_get_drvdata ( cfg - > tx_dev ) ;
ch - > tx_dev - > lcdc = ch ;
ch - > tx_dev - > def_mode = * mode ;
}
2011-11-29 17:37:35 +04:00
return sh_mobile_lcdc_channel_fb_init ( ch , mode , num_modes ) ;
2011-08-31 15:00:58 +04:00
}
2012-12-22 01:07:39 +04:00
static int sh_mobile_lcdc_probe ( struct platform_device * pdev )
2011-08-31 15:00:58 +04:00
{
2010-09-03 11:20:00 +04:00
struct sh_mobile_lcdc_info * pdata = pdev - > dev . platform_data ;
2011-08-31 15:00:58 +04:00
struct sh_mobile_lcdc_priv * priv ;
2008-07-24 08:31:24 +04:00
struct resource * res ;
2011-08-31 15:00:58 +04:00
int num_channels ;
2008-07-24 08:31:24 +04:00
int error ;
2011-08-31 15:00:58 +04:00
int i ;
2008-07-24 08:31:24 +04:00
2010-09-03 11:20:00 +04:00
if ( ! pdata ) {
2008-07-24 08:31:24 +04:00
dev_err ( & pdev - > dev , " no platform data defined \n " ) ;
2010-04-30 20:07:00 +04:00
return - EINVAL ;
2008-07-24 08:31:24 +04:00
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2008-12-19 09:34:41 +03:00
i = platform_get_irq ( pdev , 0 ) ;
if ( ! res | | i < 0 ) {
dev_err ( & pdev - > dev , " cannot get platform resources \n " ) ;
2010-04-30 20:07:00 +04:00
return - ENOENT ;
2008-07-24 08:31:24 +04:00
}
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
dev_err ( & pdev - > dev , " cannot allocate device data \n " ) ;
2010-04-30 20:07:00 +04:00
return - ENOMEM ;
2008-07-24 08:31:24 +04:00
}
2011-09-07 17:47:07 +04:00
priv - > dev = & pdev - > dev ;
priv - > meram_dev = pdata - > meram_dev ;
2010-04-30 20:07:00 +04:00
platform_set_drvdata ( pdev , priv ) ;
2011-09-22 12:59:16 +04:00
error = request_irq ( i , sh_mobile_lcdc_irq , 0 ,
2009-03-25 02:38:21 +03:00
dev_name ( & pdev - > dev ) , priv ) ;
2008-12-19 09:34:41 +03:00
if ( error ) {
dev_err ( & pdev - > dev , " unable to request irq \n " ) ;
goto err1 ;
}
priv - > irq = i ;
2010-09-03 11:19:57 +04:00
atomic_set ( & priv - > hw_usecnt , - 1 ) ;
2008-07-24 08:31:24 +04:00
2011-08-31 15:00:58 +04:00
for ( i = 0 , num_channels = 0 ; i < ARRAY_SIZE ( pdata - > ch ) ; i + + ) {
struct sh_mobile_lcdc_chan * ch = priv - > ch + num_channels ;
2008-07-24 08:31:24 +04:00
2010-09-03 11:20:00 +04:00
ch - > lcdc = priv ;
2011-11-22 03:56:58 +04:00
ch - > cfg = & pdata - > ch [ i ] ;
2008-07-24 08:31:24 +04:00
2010-09-03 11:20:00 +04:00
error = sh_mobile_lcdc_check_interface ( ch ) ;
2008-07-24 08:31:24 +04:00
if ( error ) {
dev_err ( & pdev - > dev , " unsupported interface type \n " ) ;
goto err1 ;
}
2010-09-03 11:20:00 +04:00
init_waitqueue_head ( & ch - > frame_end_wait ) ;
init_completion ( & ch - > vsync_completion ) ;
2008-07-24 08:31:24 +04:00
2011-02-16 06:49:01 +03:00
/* probe the backlight is there is one defined */
2011-11-22 03:56:58 +04:00
if ( ch - > cfg - > bl_info . max_brightness )
2011-02-16 06:49:01 +03:00
ch - > bl = sh_mobile_lcdc_bl_probe ( & pdev - > dev , ch ) ;
2008-07-24 08:31:24 +04:00
switch ( pdata - > ch [ i ] . chan ) {
case LCDC_CHAN_MAINLCD :
2011-07-13 14:13:47 +04:00
ch - > enabled = LDCNT2R_ME ;
2010-09-03 11:20:00 +04:00
ch - > reg_offs = lcdc_offs_mainlcd ;
2011-08-31 15:00:58 +04:00
num_channels + + ;
2008-07-24 08:31:24 +04:00
break ;
case LCDC_CHAN_SUBLCD :
2011-07-13 14:13:47 +04:00
ch - > enabled = LDCNT2R_SE ;
2010-09-03 11:20:00 +04:00
ch - > reg_offs = lcdc_offs_sublcd ;
2011-08-31 15:00:58 +04:00
num_channels + + ;
2008-07-24 08:31:24 +04:00
break ;
}
}
2011-08-31 15:00:58 +04:00
if ( ! num_channels ) {
2008-07-24 08:31:24 +04:00
dev_err ( & pdev - > dev , " no channels defined \n " ) ;
error = - EINVAL ;
goto err1 ;
}
2011-12-13 17:02:28 +04:00
/* for dual channel LCDC (MAIN + SUB) force shared format setting */
2011-08-31 15:00:58 +04:00
if ( num_channels = = 2 )
2011-12-13 17:02:28 +04:00
priv - > forced_fourcc = pdata - > ch [ 0 ] . fourcc ;
2011-01-05 13:21:00 +03:00
2010-06-30 13:26:35 +04:00
priv - > base = ioremap_nocache ( res - > start , resource_size ( res ) ) ;
if ( ! priv - > base )
goto err1 ;
2011-09-07 18:02:31 +04:00
error = sh_mobile_lcdc_setup_clocks ( priv , pdata - > clock_source ) ;
2008-07-24 08:31:24 +04:00
if ( error ) {
dev_err ( & pdev - > dev , " unable to setup clocks \n " ) ;
goto err1 ;
}
2011-09-07 17:47:07 +04:00
/* Enable runtime PM. */
pm_runtime_enable ( & pdev - > dev ) ;
2011-05-18 15:10:07 +04:00
2011-08-31 15:00:58 +04:00
for ( i = 0 ; i < num_channels ; i + + ) {
2012-08-13 15:56:46 +04:00
struct sh_mobile_lcdc_chan * ch = & priv - > ch [ i ] ;
2010-10-15 11:53:52 +04:00
2012-08-13 15:56:46 +04:00
error = sh_mobile_lcdc_channel_init ( ch ) ;
2008-07-24 08:31:24 +04:00
if ( error )
2011-08-31 15:00:58 +04:00
goto err1 ;
2008-07-24 08:31:24 +04:00
}
2011-12-12 21:43:16 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( pdata - > overlays ) ; i + + ) {
struct sh_mobile_lcdc_overlay * ovl = & priv - > overlays [ i ] ;
ovl - > cfg = & pdata - > overlays [ i ] ;
ovl - > channel = & priv - > ch [ 0 ] ;
2012-08-13 15:56:46 +04:00
error = sh_mobile_lcdc_overlay_init ( ovl ) ;
2011-12-12 21:43:16 +04:00
if ( error )
goto err1 ;
}
2008-07-24 08:31:24 +04:00
error = sh_mobile_lcdc_start ( priv ) ;
if ( error ) {
dev_err ( & pdev - > dev , " unable to start hardware \n " ) ;
goto err1 ;
}
2011-08-31 15:00:58 +04:00
for ( i = 0 ; i < num_channels ; i + + ) {
2009-07-01 10:50:31 +04:00
struct sh_mobile_lcdc_chan * ch = priv - > ch + i ;
2011-11-29 17:37:35 +04:00
error = sh_mobile_lcdc_channel_fb_register ( ch ) ;
if ( error )
2008-07-24 08:31:24 +04:00
goto err1 ;
}
2011-12-12 21:43:16 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( pdata - > overlays ) ; i + + ) {
struct sh_mobile_lcdc_overlay * ovl = & priv - > overlays [ i ] ;
error = sh_mobile_lcdc_overlay_fb_register ( ovl ) ;
if ( error )
goto err1 ;
}
2010-07-21 14:13:21 +04:00
/* Failure ignored */
priv - > notifier . notifier_call = sh_mobile_lcdc_notify ;
fb_register_client ( & priv - > notifier ) ;
2008-07-24 08:31:24 +04:00
return 0 ;
2010-04-30 20:07:00 +04:00
err1 :
2008-07-24 08:31:24 +04:00
sh_mobile_lcdc_remove ( pdev ) ;
2010-04-30 20:07:00 +04:00
2008-07-24 08:31:24 +04:00
return error ;
}
static struct platform_driver sh_mobile_lcdc_driver = {
. driver = {
. name = " sh_mobile_lcdc_fb " ,
. owner = THIS_MODULE ,
2009-03-13 18:36:55 +03:00
. pm = & sh_mobile_lcdc_dev_pm_ops ,
2008-07-24 08:31:24 +04:00
} ,
. probe = sh_mobile_lcdc_probe ,
. remove = sh_mobile_lcdc_remove ,
} ;
2011-11-26 06:25:54 +04:00
module_platform_driver ( sh_mobile_lcdc_driver ) ;
2008-07-24 08:31:24 +04:00
MODULE_DESCRIPTION ( " SuperH Mobile LCDC Framebuffer driver " ) ;
MODULE_AUTHOR ( " Magnus Damm <damm@opensource.se> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;