2010-11-09 02:42:39 +03:00
/*
* WonderMedia WM8505 Frame Buffer device driver
*
* Copyright ( C ) 2010 Ed Spiridonov < edo . rus @ gmail . com >
* Based on vt8500lcdfb . 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>
# include <mach/vt8500fb.h>
# include "wm8505fb_regs.h"
# include "wmt_ge_rops.h"
# define DRIVER_NAME "wm8505-fb"
# define to_wm8505fb_info(__info) container_of(__info, \
struct wm8505fb_info , fb )
struct wm8505fb_info {
struct fb_info fb ;
void __iomem * regbase ;
unsigned int contrast ;
} ;
static int wm8505fb_init_hw ( struct fb_info * info )
{
struct wm8505fb_info * fbi = to_wm8505fb_info ( info ) ;
int i ;
/* I know the purpose only of few registers, so clear unknown */
for ( i = 0 ; i < 0x200 ; i + = 4 )
writel ( 0 , fbi - > regbase + i ) ;
/* Set frame buffer address */
writel ( fbi - > fb . fix . smem_start , fbi - > regbase + WMT_GOVR_FBADDR ) ;
writel ( fbi - > fb . fix . smem_start , fbi - > regbase + WMT_GOVR_FBADDR1 ) ;
/* Set in-memory picture format to RGB 32bpp */
writel ( 0x1c , fbi - > regbase + WMT_GOVR_COLORSPACE ) ;
writel ( 1 , fbi - > regbase + WMT_GOVR_COLORSPACE1 ) ;
/* Virtual buffer size */
writel ( info - > var . xres , fbi - > regbase + WMT_GOVR_XRES ) ;
writel ( info - > var . xres_virtual , fbi - > regbase + WMT_GOVR_XRES_VIRTUAL ) ;
/* black magic ;) */
writel ( 0xf , fbi - > regbase + WMT_GOVR_FHI ) ;
writel ( 4 , fbi - > regbase + WMT_GOVR_DVO_SET ) ;
writel ( 1 , fbi - > regbase + WMT_GOVR_MIF_ENABLE ) ;
writel ( 1 , fbi - > regbase + WMT_GOVR_REG_UPDATE ) ;
return 0 ;
}
static int wm8505fb_set_timing ( struct fb_info * info )
{
struct wm8505fb_info * fbi = to_wm8505fb_info ( info ) ;
int h_start = info - > var . left_margin ;
int h_end = h_start + info - > var . xres ;
int h_all = h_end + info - > var . right_margin ;
int h_sync = info - > var . hsync_len ;
int v_start = info - > var . upper_margin ;
int v_end = v_start + info - > var . yres ;
int v_all = v_end + info - > var . lower_margin ;
2010-12-20 16:09:57 +03:00
int v_sync = info - > var . vsync_len ;
2010-11-09 02:42:39 +03:00
writel ( 0 , fbi - > regbase + WMT_GOVR_TG ) ;
writel ( h_start , fbi - > regbase + WMT_GOVR_TIMING_H_START ) ;
writel ( h_end , fbi - > regbase + WMT_GOVR_TIMING_H_END ) ;
writel ( h_all , fbi - > regbase + WMT_GOVR_TIMING_H_ALL ) ;
writel ( h_sync , fbi - > regbase + WMT_GOVR_TIMING_H_SYNC ) ;
writel ( v_start , fbi - > regbase + WMT_GOVR_TIMING_V_START ) ;
writel ( v_end , fbi - > regbase + WMT_GOVR_TIMING_V_END ) ;
writel ( v_all , fbi - > regbase + WMT_GOVR_TIMING_V_ALL ) ;
writel ( v_sync , fbi - > regbase + WMT_GOVR_TIMING_V_SYNC ) ;
writel ( 1 , fbi - > regbase + WMT_GOVR_TG ) ;
return 0 ;
}
static int wm8505fb_set_par ( struct fb_info * info )
{
struct wm8505fb_info * fbi = to_wm8505fb_info ( info ) ;
if ( ! fbi )
return - EINVAL ;
if ( info - > var . bits_per_pixel = = 32 ) {
info - > var . red . offset = 16 ;
info - > var . red . length = 8 ;
info - > var . red . msb_right = 0 ;
info - > var . green . offset = 8 ;
info - > var . green . length = 8 ;
info - > var . green . msb_right = 0 ;
info - > var . blue . offset = 0 ;
info - > var . blue . length = 8 ;
info - > var . blue . msb_right = 0 ;
info - > fix . visual = FB_VISUAL_TRUECOLOR ;
info - > fix . line_length = info - > var . xres_virtual < < 2 ;
}
wm8505fb_set_timing ( info ) ;
writel ( fbi - > contrast < < 16 | fbi - > contrast < < 8 | fbi - > contrast ,
fbi - > regbase + WMT_GOVR_CONTRAST ) ;
return 0 ;
}
static ssize_t contrast_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct wm8505fb_info * fbi = to_wm8505fb_info ( info ) ;
return sprintf ( buf , " %d \n " , fbi - > contrast ) ;
}
static ssize_t contrast_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct fb_info * info = dev_get_drvdata ( dev ) ;
struct wm8505fb_info * fbi = to_wm8505fb_info ( info ) ;
unsigned long tmp ;
if ( strict_strtoul ( buf , 10 , & tmp ) | | ( tmp > 0xff ) )
return - EINVAL ;
fbi - > contrast = tmp ;
wm8505fb_set_par ( info ) ;
return count ;
}
static DEVICE_ATTR ( contrast , 0644 , contrast_show , contrast_store ) ;
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 wm8505fb_setcolreg ( unsigned regno , unsigned red , unsigned green ,
unsigned blue , unsigned transp ,
struct fb_info * info ) {
struct wm8505fb_info * fbi = to_wm8505fb_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 = info - > 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 ;
}
return ret ;
}
static int wm8505fb_pan_display ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
struct wm8505fb_info * fbi = to_wm8505fb_info ( info ) ;
writel ( var - > xoffset , fbi - > regbase + WMT_GOVR_XPAN ) ;
writel ( var - > yoffset , fbi - > regbase + WMT_GOVR_YPAN ) ;
return 0 ;
}
static int wm8505fb_blank ( int blank , struct fb_info * info )
{
struct wm8505fb_info * fbi = to_wm8505fb_info ( info ) ;
switch ( blank ) {
case FB_BLANK_UNBLANK :
wm8505fb_set_timing ( info ) ;
break ;
default :
writel ( 0 , fbi - > regbase + WMT_GOVR_TIMING_V_SYNC ) ;
break ;
}
return 0 ;
}
static struct fb_ops wm8505fb_ops = {
. owner = THIS_MODULE ,
. fb_set_par = wm8505fb_set_par ,
. fb_setcolreg = wm8505fb_setcolreg ,
. fb_fillrect = wmt_ge_fillrect ,
. fb_copyarea = wmt_ge_copyarea ,
. fb_imageblit = sys_imageblit ,
. fb_sync = wmt_ge_sync ,
. fb_pan_display = wm8505fb_pan_display ,
. fb_blank = wm8505fb_blank ,
} ;
static int __devinit wm8505fb_probe ( struct platform_device * pdev )
{
struct wm8505fb_info * fbi ;
struct resource * res ;
void * addr ;
struct vt8500fb_platform_data * pdata ;
int ret ;
pdata = pdev - > dev . platform_data ;
ret = - ENOMEM ;
fbi = NULL ;
fbi = kzalloc ( sizeof ( struct wm8505fb_info ) + sizeof ( u32 ) * 16 ,
GFP_KERNEL ) ;
if ( ! fbi ) {
dev_err ( & pdev - > dev , " Failed to initialize framebuffer device \n " ) ;
ret = - ENOMEM ;
goto failed ;
}
strcpy ( fbi - > fb . fix . id , DRIVER_NAME ) ;
fbi - > fb . fix . type = FB_TYPE_PACKED_PIXELS ;
fbi - > fb . fix . xpanstep = 1 ;
fbi - > fb . fix . ypanstep = 1 ;
fbi - > fb . fix . ywrapstep = 0 ;
fbi - > fb . fix . accel = FB_ACCEL_NONE ;
fbi - > fb . fbops = & wm8505fb_ops ;
fbi - > fb . flags = FBINFO_DEFAULT
| FBINFO_HWACCEL_COPYAREA
| FBINFO_HWACCEL_FILLRECT
| FBINFO_HWACCEL_XPAN
| FBINFO_HWACCEL_YPAN
| FBINFO_VIRTFB
| FBINFO_PARTIAL_PAN_OK ;
fbi - > fb . node = - 1 ;
addr = fbi ;
addr = addr + sizeof ( struct wm8505fb_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 ;
}
2010-12-20 16:03:14 +03:00
res = request_mem_region ( res - > start , resource_size ( res ) , DRIVER_NAME ) ;
2010-11-09 02:42:39 +03:00
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 ;
}
fb_videomode_to_var ( & fbi - > fb . var , & pdata - > mode ) ;
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 . xres_virtual = pdata - > xres_virtual ;
fbi - > fb . var . yres_virtual = pdata - > yres_virtual ;
fbi - > fb . var . bits_per_pixel = pdata - > bpp ;
fbi - > fb . fix . smem_start = pdata - > video_mem_phys ;
fbi - > fb . fix . smem_len = pdata - > video_mem_len ;
fbi - > fb . screen_base = pdata - > video_mem_virt ;
fbi - > fb . screen_size = pdata - > video_mem_len ;
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_io ;
}
wm8505fb_init_hw ( & fbi - > fb ) ;
fbi - > contrast = 0x80 ;
ret = wm8505fb_set_par ( & fbi - > fb ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to set parameters \n " ) ;
goto failed_free_cmap ;
}
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 ;
}
ret = device_create_file ( & pdev - > dev , & dev_attr_contrast ) ;
if ( ret < 0 ) {
printk ( KERN_WARNING " fb%d: failed to register attributes (%d) \n " ,
fbi - > fb . node , ret ) ;
}
printk ( KERN_INFO " fb%d: %s frame buffer at 0x%lx-0x%lx \n " ,
fbi - > fb . node , fbi - > fb . fix . id , fbi - > fb . fix . smem_start ,
fbi - > fb . fix . smem_start + fbi - > fb . fix . smem_len - 1 ) ;
return 0 ;
failed_free_cmap :
if ( fbi - > fb . cmap . len )
fb_dealloc_cmap ( & fbi - > fb . cmap ) ;
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 wm8505fb_remove ( struct platform_device * pdev )
{
struct wm8505fb_info * fbi = platform_get_drvdata ( pdev ) ;
struct resource * res ;
device_remove_file ( & pdev - > dev , & dev_attr_contrast ) ;
unregister_framebuffer ( & fbi - > fb ) ;
writel ( 0 , fbi - > regbase ) ;
if ( fbi - > fb . cmap . len )
fb_dealloc_cmap ( & fbi - > fb . cmap ) ;
iounmap ( fbi - > regbase ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
release_mem_region ( res - > start , resource_size ( res ) ) ;
kfree ( fbi ) ;
return 0 ;
}
static struct platform_driver wm8505fb_driver = {
. probe = wm8505fb_probe ,
. remove = __devexit_p ( wm8505fb_remove ) ,
. driver = {
. owner = THIS_MODULE ,
. name = DRIVER_NAME ,
} ,
} ;
static int __init wm8505fb_init ( void )
{
return platform_driver_register ( & wm8505fb_driver ) ;
}
static void __exit wm8505fb_exit ( void )
{
platform_driver_unregister ( & wm8505fb_driver ) ;
}
module_init ( wm8505fb_init ) ;
module_exit ( wm8505fb_exit ) ;
MODULE_AUTHOR ( " Ed Spiridonov <edo.rus@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Framebuffer driver for WMT WM8505 " ) ;
MODULE_LICENSE ( " GPL " ) ;