2010-11-09 02:42:39 +03:00
/*
* linux / drivers / video / vt8500lcdfb . c
*
* Copyright ( C ) 2010 Alexey Charkov < alchark @ gmail . com >
*
* Based on skeletonfb . c and pxafb . c
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/mm.h>
# include <linux/slab.h>
# include <linux/delay.h>
# include <linux/fb.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/dma-mapping.h>
# include <linux/platform_device.h>
# include <linux/wait.h>
2012-08-24 17:18:25 +04:00
# include <linux/platform_data/video-vt8500lcdfb.h>
2010-11-09 02:42:39 +03:00
# include "vt8500lcdfb.h"
# include "wmt_ge_rops.h"
2012-08-03 12:58:22 +04:00
# ifdef CONFIG_OF
# include <linux/of.h>
# include <linux/of_fdt.h>
# include <linux/memblock.h>
# endif
2010-11-09 02:42:39 +03:00
# define to_vt8500lcd_info(__info) container_of(__info, \
struct vt8500lcd_info , fb )
static int vt8500lcd_set_par ( struct fb_info * info )
{
struct vt8500lcd_info * fbi = to_vt8500lcd_info ( info ) ;
int reg_bpp = 5 ; /* 16bpp */
int i ;
unsigned long control0 ;
if ( ! fbi )
return - EINVAL ;
if ( info - > var . bits_per_pixel < = 8 ) {
/* palettized */
info - > var . red . offset = 0 ;
info - > var . red . length = info - > var . bits_per_pixel ;
info - > var . red . msb_right = 0 ;
info - > var . green . offset = 0 ;
info - > var . green . length = info - > var . bits_per_pixel ;
info - > var . green . msb_right = 0 ;
info - > var . blue . offset = 0 ;
info - > var . blue . length = info - > var . bits_per_pixel ;
info - > var . blue . msb_right = 0 ;
info - > var . transp . offset = 0 ;
info - > var . transp . length = 0 ;
info - > var . transp . msb_right = 0 ;
info - > fix . visual = FB_VISUAL_PSEUDOCOLOR ;
info - > fix . line_length = info - > var . xres_virtual /
( 8 / info - > var . bits_per_pixel ) ;
} else {
/* non-palettized */
info - > var . transp . offset = 0 ;
info - > var . transp . length = 0 ;
info - > var . transp . msb_right = 0 ;
if ( info - > var . bits_per_pixel = = 16 ) {
/* RGB565 */
info - > var . red . offset = 11 ;
info - > var . red . length = 5 ;
info - > var . red . msb_right = 0 ;
info - > var . green . offset = 5 ;
info - > var . green . length = 6 ;
info - > var . green . msb_right = 0 ;
info - > var . blue . offset = 0 ;
info - > var . blue . length = 5 ;
info - > var . blue . msb_right = 0 ;
} else {
/* Equal depths per channel */
info - > var . red . offset = info - > var . bits_per_pixel
* 2 / 3 ;
info - > var . red . length = info - > var . bits_per_pixel / 3 ;
info - > var . red . msb_right = 0 ;
info - > var . green . offset = info - > var . bits_per_pixel / 3 ;
info - > var . green . length = info - > var . bits_per_pixel / 3 ;
info - > var . green . msb_right = 0 ;
info - > var . blue . offset = 0 ;
info - > var . blue . length = info - > var . bits_per_pixel / 3 ;
info - > var . blue . msb_right = 0 ;
}
info - > fix . visual = FB_VISUAL_TRUECOLOR ;
info - > fix . line_length = info - > var . bits_per_pixel > 16 ?
info - > var . xres_virtual < < 2 :
info - > var . xres_virtual < < 1 ;
}
for ( i = 0 ; i < 8 ; i + + ) {
if ( bpp_values [ i ] = = info - > var . bits_per_pixel ) {
reg_bpp = i ;
continue ;
}
}
control0 = readl ( fbi - > regbase ) & ~ 0xf ;
writel ( 0 , fbi - > regbase ) ;
while ( readl ( fbi - > regbase + 0x38 ) & 0x10 )
/* wait */ ;
writel ( ( ( ( info - > var . hsync_len - 1 ) & 0x3f ) < < 26 )
| ( ( info - > var . left_margin & 0xff ) < < 18 )
| ( ( ( info - > var . xres - 1 ) & 0x3ff ) < < 8 )
| ( info - > var . right_margin & 0xff ) , fbi - > regbase + 0x4 ) ;
writel ( ( ( ( info - > var . vsync_len - 1 ) & 0x3f ) < < 26 )
| ( ( info - > var . upper_margin & 0xff ) < < 18 )
| ( ( ( info - > var . yres - 1 ) & 0x3ff ) < < 8 )
| ( info - > var . lower_margin & 0xff ) , fbi - > regbase + 0x8 ) ;
writel ( ( ( ( info - > var . yres - 1 ) & 0x400 ) < < 2 )
| ( ( info - > var . xres - 1 ) & 0x400 ) , fbi - > regbase + 0x10 ) ;
writel ( 0x80000000 , fbi - > regbase + 0x20 ) ;
writel ( control0 | ( reg_bpp < < 1 ) | 0x100 , fbi - > regbase ) ;
return 0 ;
}
static inline u_int chan_to_field ( u_int chan , struct fb_bitfield * bf )
{
chan & = 0xffff ;
chan > > = 16 - bf - > length ;
return chan < < bf - > offset ;
}
static int vt8500lcd_setcolreg ( unsigned regno , unsigned red , unsigned green ,
unsigned blue , unsigned transp ,
struct fb_info * info ) {
struct vt8500lcd_info * fbi = to_vt8500lcd_info ( info ) ;
int ret = 1 ;
unsigned int val ;
if ( regno > = 256 )
return - EINVAL ;
if ( info - > var . grayscale )
red = green = blue =
( 19595 * red + 38470 * green + 7471 * blue ) > > 16 ;
switch ( fbi - > fb . fix . visual ) {
case FB_VISUAL_TRUECOLOR :
if ( regno < 16 ) {
u32 * pal = fbi - > fb . pseudo_palette ;
val = chan_to_field ( red , & fbi - > fb . var . red ) ;
val | = chan_to_field ( green , & fbi - > fb . var . green ) ;
val | = chan_to_field ( blue , & fbi - > fb . var . blue ) ;
pal [ regno ] = val ;
ret = 0 ;
}
break ;
case FB_VISUAL_STATIC_PSEUDOCOLOR :
case FB_VISUAL_PSEUDOCOLOR :
writew ( ( red & 0xf800 )
| ( ( green > > 5 ) & 0x7e0 )
| ( ( blue > > 11 ) & 0x1f ) ,
fbi - > palette_cpu + sizeof ( u16 ) * regno ) ;
break ;
}
return ret ;
}
static int vt8500lcd_ioctl ( struct fb_info * info , unsigned int cmd ,
unsigned long arg )
{
int ret = 0 ;
struct vt8500lcd_info * fbi = to_vt8500lcd_info ( info ) ;
if ( cmd = = FBIO_WAITFORVSYNC ) {
/* Unmask End of Frame interrupt */
writel ( 0xffffffff ^ ( 1 < < 3 ) , fbi - > regbase + 0x3c ) ;
ret = wait_event_interruptible_timeout ( fbi - > wait ,
readl ( fbi - > regbase + 0x38 ) & ( 1 < < 3 ) , HZ / 10 ) ;
/* Mask back to reduce unwanted interrupt traffic */
writel ( 0xffffffff , fbi - > regbase + 0x3c ) ;
if ( ret < 0 )
return ret ;
if ( ret = = 0 )
return - ETIMEDOUT ;
}
return ret ;
}
static int vt8500lcd_pan_display ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
unsigned pixlen = info - > fix . line_length / info - > var . xres_virtual ;
unsigned off = pixlen * var - > xoffset
+ info - > fix . line_length * var - > yoffset ;
struct vt8500lcd_info * fbi = to_vt8500lcd_info ( info ) ;
writel ( ( 1 < < 31 )
2011-05-25 13:34:52 +04:00
| ( ( ( info - > var . xres_virtual - info - > var . xres ) * pixlen / 4 ) < < 20 )
| ( off > > 2 ) , fbi - > regbase + 0x20 ) ;
2010-11-09 02:42:39 +03:00
return 0 ;
}
2011-01-13 01:14:44 +03:00
/*
* vt8500lcd_blank ( ) :
* Blank the display by setting all palette values to zero . Note ,
* True Color modes do not really use the palette , so this will not
* blank the display in all modes .
*/
static int vt8500lcd_blank ( int blank , struct fb_info * info )
{
int i ;
switch ( blank ) {
case FB_BLANK_POWERDOWN :
case FB_BLANK_VSYNC_SUSPEND :
case FB_BLANK_HSYNC_SUSPEND :
case FB_BLANK_NORMAL :
if ( info - > fix . visual = = FB_VISUAL_PSEUDOCOLOR | |
info - > fix . visual = = FB_VISUAL_STATIC_PSEUDOCOLOR )
for ( i = 0 ; i < 256 ; i + + )
vt8500lcd_setcolreg ( i , 0 , 0 , 0 , 0 , info ) ;
case FB_BLANK_UNBLANK :
if ( info - > fix . visual = = FB_VISUAL_PSEUDOCOLOR | |
info - > fix . visual = = FB_VISUAL_STATIC_PSEUDOCOLOR )
fb_set_cmap ( & info - > cmap , info ) ;
}
return 0 ;
}
2010-11-09 02:42:39 +03:00
static struct fb_ops vt8500lcd_ops = {
. owner = THIS_MODULE ,
. fb_set_par = vt8500lcd_set_par ,
. fb_setcolreg = vt8500lcd_setcolreg ,
. fb_fillrect = wmt_ge_fillrect ,
. fb_copyarea = wmt_ge_copyarea ,
. fb_imageblit = sys_imageblit ,
. fb_sync = wmt_ge_sync ,
. fb_ioctl = vt8500lcd_ioctl ,
. fb_pan_display = vt8500lcd_pan_display ,
2011-01-13 01:14:44 +03:00
. fb_blank = vt8500lcd_blank ,
2010-11-09 02:42:39 +03:00
} ;
static irqreturn_t vt8500lcd_handle_irq ( int irq , void * dev_id )
{
struct vt8500lcd_info * fbi = dev_id ;
if ( readl ( fbi - > regbase + 0x38 ) & ( 1 < < 3 ) )
wake_up_interruptible ( & fbi - > wait ) ;
writel ( 0xffffffff , fbi - > regbase + 0x38 ) ;
return IRQ_HANDLED ;
}
static int __devinit vt8500lcd_probe ( struct platform_device * pdev )
{
struct vt8500lcd_info * fbi ;
struct resource * res ;
void * addr ;
int irq , ret ;
2012-08-03 12:58:22 +04:00
struct fb_videomode of_mode ;
struct device_node * np ;
u32 bpp ;
dma_addr_t fb_mem_phys ;
unsigned long fb_mem_len ;
void * fb_mem_virt ;
2010-11-09 02:42:39 +03:00
ret = - ENOMEM ;
fbi = NULL ;
2012-08-03 12:58:22 +04:00
fbi = devm_kzalloc ( & pdev - > dev , sizeof ( struct vt8500lcd_info )
+ sizeof ( u32 ) * 16 , GFP_KERNEL ) ;
2010-11-09 02:42:39 +03:00
if ( ! fbi ) {
dev_err ( & pdev - > dev , " Failed to initialize framebuffer device \n " ) ;
ret = - ENOMEM ;
goto failed ;
}
strcpy ( fbi - > fb . fix . id , " VT8500 LCD " ) ;
fbi - > fb . fix . type = FB_TYPE_PACKED_PIXELS ;
fbi - > fb . fix . xpanstep = 0 ;
fbi - > fb . fix . ypanstep = 1 ;
fbi - > fb . fix . ywrapstep = 0 ;
fbi - > fb . fix . accel = FB_ACCEL_NONE ;
fbi - > fb . var . nonstd = 0 ;
fbi - > fb . var . activate = FB_ACTIVATE_NOW ;
fbi - > fb . var . height = - 1 ;
fbi - > fb . var . width = - 1 ;
fbi - > fb . var . vmode = FB_VMODE_NONINTERLACED ;
fbi - > fb . fbops = & vt8500lcd_ops ;
fbi - > fb . flags = FBINFO_DEFAULT
| FBINFO_HWACCEL_COPYAREA
| FBINFO_HWACCEL_FILLRECT
| FBINFO_HWACCEL_YPAN
| FBINFO_VIRTFB
| FBINFO_PARTIAL_PAN_OK ;
fbi - > fb . node = - 1 ;
addr = fbi ;
addr = addr + sizeof ( struct vt8500lcd_info ) ;
fbi - > fb . pseudo_palette = addr ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( res = = NULL ) {
dev_err ( & pdev - > dev , " no I/O memory resource defined \n " ) ;
ret = - ENODEV ;
goto failed_fbi ;
}
res = request_mem_region ( res - > start , resource_size ( res ) , " vt8500lcd " ) ;
if ( res = = NULL ) {
dev_err ( & pdev - > dev , " failed to request I/O memory \n " ) ;
ret = - EBUSY ;
goto failed_fbi ;
}
fbi - > regbase = ioremap ( res - > start , resource_size ( res ) ) ;
if ( fbi - > regbase = = NULL ) {
dev_err ( & pdev - > dev , " failed to map I/O memory \n " ) ;
ret = - EBUSY ;
goto failed_free_res ;
}
2012-08-03 12:58:22 +04:00
np = of_parse_phandle ( pdev - > dev . of_node , " default-mode " , 0 ) ;
if ( ! np ) {
pr_err ( " %s: No display description in Device Tree \n " , __func__ ) ;
ret = - EINVAL ;
goto failed_free_res ;
}
/*
* This code is copied from Sascha Hauer ' s of_videomode helper
* and can be replaced with a call to the helper once mainlined
*/
ret = 0 ;
ret | = of_property_read_u32 ( np , " hactive " , & of_mode . xres ) ;
ret | = of_property_read_u32 ( np , " vactive " , & of_mode . yres ) ;
ret | = of_property_read_u32 ( np , " hback-porch " , & of_mode . left_margin ) ;
ret | = of_property_read_u32 ( np , " hfront-porch " , & of_mode . right_margin ) ;
ret | = of_property_read_u32 ( np , " hsync-len " , & of_mode . hsync_len ) ;
ret | = of_property_read_u32 ( np , " vback-porch " , & of_mode . upper_margin ) ;
ret | = of_property_read_u32 ( np , " vfront-porch " , & of_mode . lower_margin ) ;
ret | = of_property_read_u32 ( np , " vsync-len " , & of_mode . vsync_len ) ;
ret | = of_property_read_u32 ( np , " bpp " , & bpp ) ;
if ( ret ) {
pr_err ( " %s: Unable to read display properties \n " , __func__ ) ;
goto failed_free_res ;
}
of_mode . vmode = FB_VMODE_NONINTERLACED ;
/* try allocating the framebuffer */
fb_mem_len = of_mode . xres * of_mode . yres * 2 * ( bpp / 8 ) ;
fb_mem_virt = dma_alloc_coherent ( & pdev - > dev , fb_mem_len , & fb_mem_phys ,
GFP_KERNEL ) ;
if ( ! fb_mem_virt ) {
pr_err ( " %s: Failed to allocate framebuffer \n " , __func__ ) ;
return - ENOMEM ;
} ;
fbi - > fb . fix . smem_start = fb_mem_phys ;
fbi - > fb . fix . smem_len = fb_mem_len ;
fbi - > fb . screen_base = fb_mem_virt ;
2010-11-09 02:42:39 +03:00
fbi - > palette_size = PAGE_ALIGN ( 512 ) ;
fbi - > palette_cpu = dma_alloc_coherent ( & pdev - > dev ,
fbi - > palette_size ,
& fbi - > palette_phys ,
GFP_KERNEL ) ;
if ( fbi - > palette_cpu = = NULL ) {
dev_err ( & pdev - > dev , " Failed to allocate palette buffer \n " ) ;
ret = - ENOMEM ;
goto failed_free_io ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_err ( & pdev - > dev , " no IRQ defined \n " ) ;
ret = - ENODEV ;
goto failed_free_palette ;
}
2011-09-22 12:59:16 +04:00
ret = request_irq ( irq , vt8500lcd_handle_irq , 0 , " LCD " , fbi ) ;
2010-11-09 02:42:39 +03:00
if ( ret ) {
dev_err ( & pdev - > dev , " request_irq failed: %d \n " , ret ) ;
ret = - EBUSY ;
goto failed_free_palette ;
}
init_waitqueue_head ( & fbi - > wait ) ;
if ( fb_alloc_cmap ( & fbi - > fb . cmap , 256 , 0 ) < 0 ) {
dev_err ( & pdev - > dev , " Failed to allocate color map \n " ) ;
ret = - ENOMEM ;
goto failed_free_irq ;
}
2012-08-03 12:58:22 +04:00
fb_videomode_to_var ( & fbi - > fb . var , & of_mode ) ;
fbi - > fb . var . xres_virtual = of_mode . xres ;
fbi - > fb . var . yres_virtual = of_mode . yres * 2 ;
fbi - > fb . var . bits_per_pixel = bpp ;
2010-11-09 02:42:39 +03:00
ret = vt8500lcd_set_par ( & fbi - > fb ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to set parameters \n " ) ;
goto failed_free_cmap ;
}
writel ( fbi - > fb . fix . smem_start > > 22 , fbi - > regbase + 0x1c ) ;
writel ( ( fbi - > palette_phys & 0xfffffe00 ) | 1 , fbi - > regbase + 0x18 ) ;
platform_set_drvdata ( pdev , fbi ) ;
ret = register_framebuffer ( & fbi - > fb ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev ,
" Failed to register framebuffer device: %d \n " , ret ) ;
goto failed_free_cmap ;
}
/*
* Ok , now enable the LCD controller
*/
writel ( readl ( fbi - > regbase ) | 1 , fbi - > regbase ) ;
return 0 ;
failed_free_cmap :
if ( fbi - > fb . cmap . len )
fb_dealloc_cmap ( & fbi - > fb . cmap ) ;
failed_free_irq :
free_irq ( irq , fbi ) ;
failed_free_palette :
dma_free_coherent ( & pdev - > dev , fbi - > palette_size ,
fbi - > palette_cpu , fbi - > palette_phys ) ;
failed_free_io :
iounmap ( fbi - > regbase ) ;
failed_free_res :
release_mem_region ( res - > start , resource_size ( res ) ) ;
failed_fbi :
platform_set_drvdata ( pdev , NULL ) ;
kfree ( fbi ) ;
failed :
return ret ;
}
static int __devexit vt8500lcd_remove ( struct platform_device * pdev )
{
struct vt8500lcd_info * fbi = platform_get_drvdata ( pdev ) ;
struct resource * res ;
int irq ;
unregister_framebuffer ( & fbi - > fb ) ;
writel ( 0 , fbi - > regbase ) ;
if ( fbi - > fb . cmap . len )
fb_dealloc_cmap ( & fbi - > fb . cmap ) ;
irq = platform_get_irq ( pdev , 0 ) ;
free_irq ( irq , fbi ) ;
dma_free_coherent ( & pdev - > dev , fbi - > palette_size ,
fbi - > palette_cpu , fbi - > palette_phys ) ;
iounmap ( fbi - > regbase ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
release_mem_region ( res - > start , resource_size ( res ) ) ;
kfree ( fbi ) ;
return 0 ;
}
2012-08-03 12:58:22 +04:00
static const struct of_device_id via_dt_ids [ ] = {
{ . compatible = " via,vt8500-fb " , } ,
{ }
} ;
2010-11-09 02:42:39 +03:00
static struct platform_driver vt8500lcd_driver = {
. probe = vt8500lcd_probe ,
. remove = __devexit_p ( vt8500lcd_remove ) ,
. driver = {
. owner = THIS_MODULE ,
. name = " vt8500-lcd " ,
2012-08-03 12:58:22 +04:00
. of_match_table = of_match_ptr ( via_dt_ids ) ,
2010-11-09 02:42:39 +03:00
} ,
} ;
2011-11-26 06:25:54 +04:00
module_platform_driver ( vt8500lcd_driver ) ;
2010-11-09 02:42:39 +03:00
MODULE_AUTHOR ( " Alexey Charkov <alchark@gmail.com> " ) ;
MODULE_DESCRIPTION ( " LCD controller driver for VIA VT8500 " ) ;
2012-08-03 12:58:22 +04:00
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DEVICE_TABLE ( of , via_dt_ids ) ;