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 .
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/mm.h>
# include <linux/fb.h>
# include <linux/clk.h>
# include <linux/platform_device.h>
# include <linux/dma-mapping.h>
2008-10-01 11:24:32 +04:00
# include <video/sh_mobile_lcdc.h>
2008-07-24 08:31:24 +04:00
# define PALETTE_NR 16
struct sh_mobile_lcdc_priv ;
struct sh_mobile_lcdc_chan {
struct sh_mobile_lcdc_priv * lcdc ;
unsigned long * reg_offs ;
unsigned long ldmt1r_value ;
unsigned long enabled ; /* ME and SE in LDCNT2R */
struct sh_mobile_lcdc_chan_cfg cfg ;
u32 pseudo_palette [ PALETTE_NR ] ;
struct fb_info info ;
dma_addr_t dma_handle ;
} ;
struct sh_mobile_lcdc_priv {
void __iomem * base ;
2008-10-01 11:24:32 +04:00
# ifdef CONFIG_HAVE_CLK
2008-07-24 08:31:24 +04:00
struct clk * clk ;
2008-10-01 11:24:32 +04:00
# endif
2008-07-24 08:31:24 +04:00
unsigned long lddckr ;
struct sh_mobile_lcdc_chan ch [ 2 ] ;
} ;
/* shared registers */
# define _LDDCKR 0x410
# define _LDDCKSTPR 0x414
# define _LDINTR 0x468
# define _LDSR 0x46c
# define _LDCNT1R 0x470
# define _LDCNT2R 0x474
# define _LDDDSR 0x47c
# define _LDDWD0R 0x800
# define _LDDRDR 0x840
# define _LDDWAR 0x900
# define _LDDRAR 0x904
/* per-channel registers */
enum { LDDCKPAT1R , LDDCKPAT2R , LDMT1R , LDMT2R , LDMT3R , LDDFR , LDSM1R ,
LDSA1R , LDMLSR , LDHCNR , LDHSYNR , LDVLNR , LDVSYNR , LDPMR } ;
static unsigned long lcdc_offs_mainlcd [ ] = {
[ LDDCKPAT1R ] = 0x400 ,
[ LDDCKPAT2R ] = 0x404 ,
[ LDMT1R ] = 0x418 ,
[ LDMT2R ] = 0x41c ,
[ LDMT3R ] = 0x420 ,
[ LDDFR ] = 0x424 ,
[ LDSM1R ] = 0x428 ,
[ LDSA1R ] = 0x430 ,
[ LDMLSR ] = 0x438 ,
[ LDHCNR ] = 0x448 ,
[ LDHSYNR ] = 0x44c ,
[ LDVLNR ] = 0x450 ,
[ LDVSYNR ] = 0x454 ,
[ LDPMR ] = 0x460 ,
} ;
static unsigned long lcdc_offs_sublcd [ ] = {
[ LDDCKPAT1R ] = 0x408 ,
[ LDDCKPAT2R ] = 0x40c ,
[ LDMT1R ] = 0x600 ,
[ LDMT2R ] = 0x604 ,
[ LDMT3R ] = 0x608 ,
[ LDDFR ] = 0x60c ,
[ LDSM1R ] = 0x610 ,
[ LDSA1R ] = 0x618 ,
[ LDMLSR ] = 0x620 ,
[ LDHCNR ] = 0x624 ,
[ LDHSYNR ] = 0x628 ,
[ LDVLNR ] = 0x62c ,
[ LDVSYNR ] = 0x630 ,
[ LDPMR ] = 0x63c ,
} ;
# define START_LCDC 0x00000001
# define LCDC_RESET 0x00000100
# define DISPLAY_BEU 0x00000008
# define LCDC_ENABLE 0x00000001
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 ] ) ;
}
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 ] ) ;
}
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 ( ) ;
}
static int lcdc_chan_is_sublcd ( struct sh_mobile_lcdc_chan * chan )
{
return chan - > cfg . chan = = LCDC_CHAN_SUBLCD ;
}
static void lcdc_sys_write_index ( void * handle , unsigned long data )
{
struct sh_mobile_lcdc_chan * ch = handle ;
lcdc_write ( ch - > lcdc , _LDDWD0R , data | 0x10000000 ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , 2 , 0 ) ;
lcdc_write ( ch - > lcdc , _LDDWAR , 1 | ( lcdc_chan_is_sublcd ( ch ) ? 2 : 0 ) ) ;
}
static void lcdc_sys_write_data ( void * handle , unsigned long data )
{
struct sh_mobile_lcdc_chan * ch = handle ;
lcdc_write ( ch - > lcdc , _LDDWD0R , data | 0x11000000 ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , 2 , 0 ) ;
lcdc_write ( ch - > lcdc , _LDDWAR , 1 | ( lcdc_chan_is_sublcd ( ch ) ? 2 : 0 ) ) ;
}
static unsigned long lcdc_sys_read_data ( void * handle )
{
struct sh_mobile_lcdc_chan * ch = handle ;
lcdc_write ( ch - > lcdc , _LDDRDR , 0x01000000 ) ;
lcdc_wait_bit ( ch - > lcdc , _LDSR , 2 , 0 ) ;
lcdc_write ( ch - > lcdc , _LDDRAR , 1 | ( lcdc_chan_is_sublcd ( ch ) ? 2 : 0 ) ) ;
udelay ( 1 ) ;
return lcdc_read ( ch - > lcdc , _LDDRDR ) & 0xffff ;
}
struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
lcdc_sys_write_index ,
lcdc_sys_write_data ,
lcdc_sys_read_data ,
} ;
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 )
lcdc_write ( priv , _LDCNT2R , tmp | START_LCDC ) ;
else
lcdc_write ( priv , _LDCNT2R , tmp & ~ START_LCDC ) ;
/* 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 ) {
tmp = lcdc_read_chan ( & priv - > ch [ k ] , LDPMR ) & 3 ;
if ( start & & tmp = = 3 )
break ;
if ( ! start & & tmp = = 0 )
break ;
cpu_relax ( ) ;
}
if ( ! start )
lcdc_write ( priv , _LDDCKSTPR , 1 ) ; /* stop dotclock */
}
static int sh_mobile_lcdc_start ( struct sh_mobile_lcdc_priv * priv )
{
struct sh_mobile_lcdc_chan * ch ;
struct fb_videomode * lcd_cfg ;
struct sh_mobile_lcdc_board_cfg * board_cfg ;
unsigned long tmp ;
int k , m ;
int ret = 0 ;
/* reset */
lcdc_write ( priv , _LDCNT2R , lcdc_read ( priv , _LDCNT2R ) | LCDC_RESET ) ;
lcdc_wait_bit ( priv , _LDCNT2R , LCDC_RESET , 0 ) ;
/* enable LCDC channels */
tmp = lcdc_read ( priv , _LDCNT2R ) ;
tmp | = priv - > ch [ 0 ] . enabled ;
tmp | = priv - > ch [ 1 ] . enabled ;
lcdc_write ( priv , _LDCNT2R , tmp ) ;
/* read data from external memory, avoid using the BEU for now */
lcdc_write ( priv , _LDCNT2R , lcdc_read ( priv , _LDCNT2R ) & ~ DISPLAY_BEU ) ;
/* stop the lcdc first */
sh_mobile_lcdc_start_stop ( priv , 0 ) ;
/* configure clocks */
tmp = priv - > lddckr ;
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
if ( ! priv - > ch [ k ] . enabled )
continue ;
m = ch - > cfg . clock_divider ;
if ( ! m )
continue ;
if ( m = = 1 )
m = 1 < < 6 ;
tmp | = m < < ( lcdc_chan_is_sublcd ( ch ) ? 8 : 0 ) ;
lcdc_write_chan ( ch , LDDCKPAT1R , 0x00000000 ) ;
lcdc_write_chan ( ch , LDDCKPAT2R , ( 1 < < ( m / 2 ) ) - 1 ) ;
}
lcdc_write ( priv , _LDDCKR , tmp ) ;
/* start dotclock again */
lcdc_write ( priv , _LDDCKSTPR , 0 ) ;
lcdc_wait_bit ( priv , _LDDCKSTPR , ~ 0 , 0 ) ;
/* interrupts are disabled */
lcdc_write ( priv , _LDINTR , 0 ) ;
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
lcd_cfg = & ch - > cfg . lcd_cfg ;
if ( ! ch - > enabled )
continue ;
tmp = ch - > ldmt1r_value ;
tmp | = ( lcd_cfg - > sync & FB_SYNC_VERT_HIGH_ACT ) ? 0 : 1 < < 28 ;
tmp | = ( lcd_cfg - > sync & FB_SYNC_HOR_HIGH_ACT ) ? 0 : 1 < < 27 ;
2008-10-09 13:48:16 +04:00
tmp | = ( ch - > cfg . flags & LCDC_FLAGS_DWPOL ) ? 1 < < 26 : 0 ;
tmp | = ( ch - > cfg . flags & LCDC_FLAGS_DIPOL ) ? 1 < < 25 : 0 ;
tmp | = ( ch - > cfg . flags & LCDC_FLAGS_DAPOL ) ? 1 < < 24 : 0 ;
tmp | = ( ch - > cfg . flags & LCDC_FLAGS_HSCNT ) ? 1 < < 17 : 0 ;
tmp | = ( ch - > cfg . flags & LCDC_FLAGS_DWCNT ) ? 1 < < 16 : 0 ;
2008-07-24 08:31:24 +04:00
lcdc_write_chan ( ch , LDMT1R , tmp ) ;
/* setup SYS bus */
lcdc_write_chan ( ch , LDMT2R , ch - > cfg . sys_bus_cfg . ldmt2r ) ;
lcdc_write_chan ( ch , LDMT3R , ch - > cfg . sys_bus_cfg . ldmt3r ) ;
/* horizontal configuration */
tmp = lcd_cfg - > xres + lcd_cfg - > hsync_len ;
tmp + = lcd_cfg - > left_margin ;
tmp + = lcd_cfg - > right_margin ;
tmp / = 8 ; /* HTCN */
tmp | = ( lcd_cfg - > xres / 8 ) < < 16 ; /* HDCN */
lcdc_write_chan ( ch , LDHCNR , tmp ) ;
tmp = lcd_cfg - > xres ;
tmp + = lcd_cfg - > right_margin ;
tmp / = 8 ; /* HSYNP */
tmp | = ( lcd_cfg - > hsync_len / 8 ) < < 16 ; /* HSYNW */
lcdc_write_chan ( ch , LDHSYNR , tmp ) ;
/* power supply */
lcdc_write_chan ( ch , LDPMR , 0 ) ;
/* vertical configuration */
tmp = lcd_cfg - > yres + lcd_cfg - > vsync_len ;
tmp + = lcd_cfg - > upper_margin ;
tmp + = lcd_cfg - > lower_margin ; /* VTLN */
tmp | = lcd_cfg - > yres < < 16 ; /* VDLN */
lcdc_write_chan ( ch , LDVLNR , tmp ) ;
tmp = lcd_cfg - > yres ;
tmp + = lcd_cfg - > lower_margin ; /* VSYNP */
tmp | = lcd_cfg - > vsync_len < < 16 ; /* VSYNW */
lcdc_write_chan ( ch , LDVSYNR , tmp ) ;
board_cfg = & ch - > cfg . board_cfg ;
if ( board_cfg - > setup_sys )
ret = board_cfg - > setup_sys ( board_cfg - > board_data , ch ,
& sh_mobile_lcdc_sys_bus_ops ) ;
if ( ret )
return ret ;
}
/* --- display_lcdc_data() --- */
lcdc_write ( priv , _LDINTR , 0x00000f00 ) ;
/* word and long word swap */
lcdc_write ( priv , _LDDDSR , lcdc_read ( priv , _LDDDSR ) | 6 ) ;
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
if ( ! priv - > ch [ k ] . enabled )
continue ;
/* set bpp format in PKF[4:0] */
tmp = lcdc_read_chan ( ch , LDDFR ) ;
tmp & = ~ ( 0x0001001f ) ;
tmp | = ( priv - > ch [ k ] . info . var . bits_per_pixel = = 16 ) ? 3 : 0 ;
lcdc_write_chan ( ch , LDDFR , tmp ) ;
/* point out our frame buffer */
lcdc_write_chan ( ch , LDSA1R , ch - > info . fix . smem_start ) ;
/* set line size */
lcdc_write_chan ( ch , LDMLSR , ch - > info . fix . line_length ) ;
/* continuous read mode */
lcdc_write_chan ( ch , LDSM1R , 0 ) ;
}
/* display output */
lcdc_write ( priv , _LDCNT1R , LCDC_ENABLE ) ;
/* start the lcdc */
sh_mobile_lcdc_start_stop ( priv , 1 ) ;
/* tell the board code to enable the panel */
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
board_cfg = & ch - > cfg . board_cfg ;
if ( board_cfg - > display_on )
board_cfg - > display_on ( board_cfg - > board_data ) ;
}
return 0 ;
}
static void sh_mobile_lcdc_stop ( struct sh_mobile_lcdc_priv * priv )
{
struct sh_mobile_lcdc_chan * ch ;
struct sh_mobile_lcdc_board_cfg * board_cfg ;
int k ;
/* tell the board code to disable the panel */
for ( k = 0 ; k < ARRAY_SIZE ( priv - > ch ) ; k + + ) {
ch = & priv - > ch [ k ] ;
board_cfg = & ch - > cfg . board_cfg ;
if ( board_cfg - > display_off )
board_cfg - > display_off ( board_cfg - > board_data ) ;
}
/* stop the lcdc */
sh_mobile_lcdc_start_stop ( priv , 0 ) ;
}
static int sh_mobile_lcdc_check_interface ( struct sh_mobile_lcdc_chan * ch )
{
int ifm , miftyp ;
switch ( ch - > cfg . interface_type ) {
case RGB8 : ifm = 0 ; miftyp = 0 ; break ;
case RGB9 : ifm = 0 ; miftyp = 4 ; break ;
case RGB12A : ifm = 0 ; miftyp = 5 ; break ;
case RGB12B : ifm = 0 ; miftyp = 6 ; break ;
case RGB16 : ifm = 0 ; miftyp = 7 ; break ;
case RGB18 : ifm = 0 ; miftyp = 10 ; break ;
case RGB24 : ifm = 0 ; miftyp = 11 ; break ;
case SYS8A : ifm = 1 ; miftyp = 0 ; break ;
case SYS8B : ifm = 1 ; miftyp = 1 ; break ;
case SYS8C : ifm = 1 ; miftyp = 2 ; break ;
case SYS8D : ifm = 1 ; miftyp = 3 ; break ;
case SYS9 : ifm = 1 ; miftyp = 4 ; break ;
case SYS12 : ifm = 1 ; miftyp = 5 ; break ;
case SYS16A : ifm = 1 ; miftyp = 7 ; break ;
case SYS16B : ifm = 1 ; miftyp = 8 ; break ;
case SYS16C : ifm = 1 ; miftyp = 9 ; break ;
case SYS18 : ifm = 1 ; miftyp = 10 ; break ;
case SYS24 : ifm = 1 ; miftyp = 11 ; break ;
default : goto bad ;
}
/* SUBLCD only supports SYS interface */
if ( lcdc_chan_is_sublcd ( ch ) ) {
if ( ifm = = 0 )
goto bad ;
else
ifm = 0 ;
}
ch - > ldmt1r_value = ( ifm < < 12 ) | miftyp ;
return 0 ;
bad :
return - EINVAL ;
}
static int sh_mobile_lcdc_setup_clocks ( struct device * dev , int clock_source ,
struct sh_mobile_lcdc_priv * priv )
{
char * str ;
int icksel ;
switch ( clock_source ) {
case LCDC_CLK_BUS : str = " bus_clk " ; icksel = 0 ; break ;
case LCDC_CLK_PERIPHERAL : str = " peripheral_clk " ; icksel = 1 ; break ;
case LCDC_CLK_EXTERNAL : str = NULL ; icksel = 2 ; break ;
default :
return - EINVAL ;
}
priv - > lddckr = icksel < < 16 ;
2008-10-01 11:24:32 +04:00
# ifdef CONFIG_HAVE_CLK
2008-07-24 08:31:24 +04:00
if ( str ) {
priv - > clk = clk_get ( dev , str ) ;
if ( IS_ERR ( priv - > clk ) ) {
dev_err ( dev , " cannot get clock %s \n " , str ) ;
return PTR_ERR ( priv - > clk ) ;
}
clk_enable ( priv - > clk ) ;
}
2008-10-01 11:24:32 +04:00
# endif
2008-07-24 08:31:24 +04:00
return 0 ;
}
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 ;
}
static struct fb_fix_screeninfo sh_mobile_lcdc_fix = {
. id = " SH Mobile LCDC " ,
. type = FB_TYPE_PACKED_PIXELS ,
. visual = FB_VISUAL_TRUECOLOR ,
. accel = FB_ACCEL_NONE ,
} ;
static struct fb_ops sh_mobile_lcdc_ops = {
. fb_setcolreg = sh_mobile_lcdc_setcolreg ,
. fb_fillrect = cfb_fillrect ,
. fb_copyarea = cfb_copyarea ,
. fb_imageblit = cfb_imageblit ,
} ;
static int sh_mobile_lcdc_set_bpp ( struct fb_var_screeninfo * var , int bpp )
{
switch ( bpp ) {
case 16 : /* PKF[4:0] = 00011 - RGB 565 */
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 ;
break ;
case 32 : /* PKF[4:0] = 00000 - RGB 888
* sh7722 pdf says 00 RRGGBB but reality is GGBB00RR
* this may be because LDDDSR has word swap enabled . .
*/
var - > red . offset = 0 ;
var - > red . length = 8 ;
var - > green . offset = 24 ;
var - > green . length = 8 ;
var - > blue . offset = 16 ;
var - > blue . length = 8 ;
var - > transp . offset = 0 ;
var - > transp . length = 0 ;
break ;
default :
return - EINVAL ;
}
var - > bits_per_pixel = bpp ;
var - > red . msb_right = 0 ;
var - > green . msb_right = 0 ;
var - > blue . msb_right = 0 ;
var - > transp . msb_right = 0 ;
return 0 ;
}
static int sh_mobile_lcdc_remove ( struct platform_device * pdev ) ;
static int __init sh_mobile_lcdc_probe ( struct platform_device * pdev )
{
struct fb_info * info ;
struct sh_mobile_lcdc_priv * priv ;
struct sh_mobile_lcdc_info * pdata ;
struct sh_mobile_lcdc_chan_cfg * cfg ;
struct resource * res ;
int error ;
void * buf ;
int i , j ;
if ( ! pdev - > dev . platform_data ) {
dev_err ( & pdev - > dev , " no platform data defined \n " ) ;
error = - EINVAL ;
goto err0 ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( res = = NULL ) {
dev_err ( & pdev - > dev , " cannot find IO resource \n " ) ;
error = - ENOENT ;
goto err0 ;
}
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
dev_err ( & pdev - > dev , " cannot allocate device data \n " ) ;
error = - ENOMEM ;
goto err0 ;
}
platform_set_drvdata ( pdev , priv ) ;
pdata = pdev - > dev . platform_data ;
j = 0 ;
for ( i = 0 ; i < ARRAY_SIZE ( pdata - > ch ) ; i + + ) {
priv - > ch [ j ] . lcdc = priv ;
memcpy ( & priv - > ch [ j ] . cfg , & pdata - > ch [ i ] , sizeof ( pdata - > ch [ i ] ) ) ;
error = sh_mobile_lcdc_check_interface ( & priv - > ch [ i ] ) ;
if ( error ) {
dev_err ( & pdev - > dev , " unsupported interface type \n " ) ;
goto err1 ;
}
switch ( pdata - > ch [ i ] . chan ) {
case LCDC_CHAN_MAINLCD :
priv - > ch [ j ] . enabled = 1 < < 1 ;
priv - > ch [ j ] . reg_offs = lcdc_offs_mainlcd ;
j + + ;
break ;
case LCDC_CHAN_SUBLCD :
priv - > ch [ j ] . enabled = 1 < < 2 ;
priv - > ch [ j ] . reg_offs = lcdc_offs_sublcd ;
j + + ;
break ;
}
}
if ( ! j ) {
dev_err ( & pdev - > dev , " no channels defined \n " ) ;
error = - EINVAL ;
goto err1 ;
}
error = sh_mobile_lcdc_setup_clocks ( & pdev - > dev ,
pdata - > clock_source , priv ) ;
if ( error ) {
dev_err ( & pdev - > dev , " unable to setup clocks \n " ) ;
goto err1 ;
}
priv - > base = ioremap_nocache ( res - > start , ( res - > end - res - > start ) + 1 ) ;
for ( i = 0 ; i < j ; i + + ) {
info = & priv - > ch [ i ] . info ;
cfg = & priv - > ch [ i ] . cfg ;
info - > fbops = & sh_mobile_lcdc_ops ;
info - > var . xres = info - > var . xres_virtual = cfg - > lcd_cfg . xres ;
info - > var . yres = info - > var . yres_virtual = cfg - > lcd_cfg . yres ;
2008-08-11 10:26:00 +04:00
info - > var . width = cfg - > lcd_size_cfg . width ;
info - > var . height = cfg - > lcd_size_cfg . height ;
2008-07-24 08:31:24 +04:00
info - > var . activate = FB_ACTIVATE_NOW ;
error = sh_mobile_lcdc_set_bpp ( & info - > var , cfg - > bpp ) ;
if ( error )
break ;
info - > fix = sh_mobile_lcdc_fix ;
info - > fix . line_length = cfg - > lcd_cfg . xres * ( cfg - > bpp / 8 ) ;
info - > fix . smem_len = info - > fix . line_length * cfg - > lcd_cfg . yres ;
buf = dma_alloc_coherent ( & pdev - > dev , info - > fix . smem_len ,
& priv - > ch [ i ] . dma_handle , GFP_KERNEL ) ;
if ( ! buf ) {
dev_err ( & pdev - > dev , " unable to allocate buffer \n " ) ;
error = - ENOMEM ;
break ;
}
info - > pseudo_palette = & priv - > ch [ i ] . pseudo_palette ;
info - > flags = FBINFO_FLAG_DEFAULT ;
error = fb_alloc_cmap ( & info - > cmap , PALETTE_NR , 0 ) ;
if ( error < 0 ) {
dev_err ( & pdev - > dev , " unable to allocate cmap \n " ) ;
dma_free_coherent ( & pdev - > dev , info - > fix . smem_len ,
buf , priv - > ch [ i ] . dma_handle ) ;
break ;
}
memset ( buf , 0 , info - > fix . smem_len ) ;
info - > fix . smem_start = priv - > ch [ i ] . dma_handle ;
info - > screen_base = buf ;
info - > device = & pdev - > dev ;
}
if ( error )
goto err1 ;
error = sh_mobile_lcdc_start ( priv ) ;
if ( error ) {
dev_err ( & pdev - > dev , " unable to start hardware \n " ) ;
goto err1 ;
}
for ( i = 0 ; i < j ; i + + ) {
error = register_framebuffer ( & priv - > ch [ i ] . info ) ;
if ( error < 0 )
goto err1 ;
}
for ( i = 0 ; i < j ; i + + ) {
info = & priv - > ch [ i ] . info ;
dev_info ( info - > dev ,
" registered %s/%s as %dx%d %dbpp. \n " ,
pdev - > name ,
( priv - > ch [ i ] . cfg . chan = = LCDC_CHAN_MAINLCD ) ?
" mainlcd " : " sublcd " ,
( int ) priv - > ch [ i ] . cfg . lcd_cfg . xres ,
( int ) priv - > ch [ i ] . cfg . lcd_cfg . yres ,
priv - > ch [ i ] . cfg . bpp ) ;
}
return 0 ;
err1 :
sh_mobile_lcdc_remove ( pdev ) ;
err0 :
return error ;
}
static int sh_mobile_lcdc_remove ( struct platform_device * pdev )
{
struct sh_mobile_lcdc_priv * priv = platform_get_drvdata ( pdev ) ;
struct fb_info * info ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( priv - > ch ) ; i + + )
if ( priv - > ch [ i ] . info . dev )
unregister_framebuffer ( & priv - > ch [ i ] . info ) ;
sh_mobile_lcdc_stop ( priv ) ;
for ( i = 0 ; i < ARRAY_SIZE ( priv - > ch ) ; i + + ) {
info = & priv - > ch [ i ] . info ;
if ( ! info - > device )
continue ;
dma_free_coherent ( & pdev - > dev , info - > fix . smem_len ,
info - > screen_base , priv - > ch [ i ] . dma_handle ) ;
fb_dealloc_cmap ( & info - > cmap ) ;
}
2008-10-01 11:24:32 +04:00
# ifdef CONFIG_HAVE_CLK
2008-07-24 08:31:24 +04:00
if ( priv - > clk ) {
clk_disable ( priv - > clk ) ;
clk_put ( priv - > clk ) ;
}
2008-10-01 11:24:32 +04:00
# endif
2008-07-24 08:31:24 +04:00
if ( priv - > base )
iounmap ( priv - > base ) ;
kfree ( priv ) ;
return 0 ;
}
static struct platform_driver sh_mobile_lcdc_driver = {
. driver = {
. name = " sh_mobile_lcdc_fb " ,
. owner = THIS_MODULE ,
} ,
. probe = sh_mobile_lcdc_probe ,
. remove = sh_mobile_lcdc_remove ,
} ;
static int __init sh_mobile_lcdc_init ( void )
{
return platform_driver_register ( & sh_mobile_lcdc_driver ) ;
}
static void __exit sh_mobile_lcdc_exit ( void )
{
platform_driver_unregister ( & sh_mobile_lcdc_driver ) ;
}
module_init ( sh_mobile_lcdc_init ) ;
module_exit ( sh_mobile_lcdc_exit ) ;
MODULE_DESCRIPTION ( " SuperH Mobile LCDC Framebuffer driver " ) ;
MODULE_AUTHOR ( " Magnus Damm <damm@opensource.se> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;