2005-04-17 02:20:36 +04:00
/*
* BRIEF MODULE DESCRIPTION
* Au1100 LCD Driver .
*
2005-04-04 05:06:19 +04:00
* Rewritten for 2.6 by Embedded Alley Solutions
* < source @ embeddedalley . com > , based on submissions by
* Karl Lessard < klessard @ sunrisetelecom . com >
* < c . pellegrin @ exadron . com >
*
2006-06-26 11:26:49 +04:00
* PM support added by Rodolfo Giometti < giometti @ linux . it >
*
2005-04-17 02:20:36 +04:00
* Copyright 2002 MontaVista Software
* Author : MontaVista Software , Inc .
* ppopov @ mvista . com or source @ mvista . com
*
* Copyright 2002 Alchemy Semiconductor
* Author : Alchemy Semiconductor
*
* Based on :
* linux / drivers / video / skeletonfb . c - - Skeleton for a frame buffer device
* Created 28 Dec 1997 by Geert Uytterhoeven
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*
* THIS SOFTWARE IS PROVIDED ` ` AS IS ' ' AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF
* USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/mm.h>
# include <linux/fb.h>
# include <linux/init.h>
2005-04-04 05:06:19 +04:00
# include <linux/interrupt.h>
# include <linux/ctype.h>
# include <linux/dma-mapping.h>
2006-02-25 00:04:17 +03:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
# include <asm/mach-au1x00/au1000.h>
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
# define DEBUG 0
# include "au1100fb.h"
2005-04-17 02:20:36 +04:00
/*
* Sanity check . If this is a new Au1100 based board , search for
* the PB1100 ifdefs to make sure you modify the code accordingly .
*/
2005-04-04 05:06:19 +04:00
# if defined(CONFIG_MIPS_PB1100)
# include <asm/mach-pb1x00/pb1100.h>
# elif defined(CONFIG_MIPS_DB1100)
# include <asm/mach-db1x00/db1x00.h>
2005-04-17 02:20:36 +04:00
# else
2005-04-04 05:06:19 +04:00
# error "Unknown Au1100 board, Au1100 FB driver not supported"
2005-04-17 02:20:36 +04:00
# endif
2005-04-04 05:06:19 +04:00
# define DRIVER_NAME "au1100fb"
# define DRIVER_DESC "LCD controller driver for AU1100 processors"
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
# define to_au1100fb_device(_info) \
( _info ? container_of ( _info , struct au1100fb_device , info ) : NULL ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/* Bitfields format supported by the controller. Note that the order of formats
* SHOULD be the same as in the LCD_CONTROL_SBPPF field , so we can retrieve the
* right pixel format by doing rgb_bitfields [ LCD_CONTROL_SBPPF_XXX > > LCD_CONTROL_SBPPF ]
*/
struct fb_bitfield rgb_bitfields [ ] [ 4 ] =
{
/* Red, Green, Blue, Transp */
{ { 10 , 6 , 0 } , { 5 , 5 , 0 } , { 0 , 5 , 0 } , { 0 , 0 , 0 } } ,
{ { 11 , 5 , 0 } , { 5 , 6 , 0 } , { 0 , 5 , 0 } , { 0 , 0 , 0 } } ,
{ { 11 , 5 , 0 } , { 6 , 5 , 0 } , { 0 , 6 , 0 } , { 0 , 0 , 0 } } ,
{ { 10 , 5 , 0 } , { 5 , 5 , 0 } , { 0 , 5 , 0 } , { 15 , 1 , 0 } } ,
{ { 11 , 5 , 0 } , { 6 , 5 , 0 } , { 1 , 5 , 0 } , { 0 , 1 , 0 } } ,
/* The last is used to describe 12bpp format */
{ { 8 , 4 , 0 } , { 4 , 4 , 0 } , { 0 , 4 , 0 } , { 0 , 0 , 0 } } ,
2005-04-17 02:20:36 +04:00
} ;
2005-04-04 05:06:19 +04:00
static struct fb_fix_screeninfo au1100fb_fix __initdata = {
. id = " AU1100 FB " ,
. xpanstep = 1 ,
. ypanstep = 1 ,
. type = FB_TYPE_PACKED_PIXELS ,
. accel = FB_ACCEL_NONE ,
2005-04-17 02:20:36 +04:00
} ;
2005-04-04 05:06:19 +04:00
static struct fb_var_screeninfo au1100fb_var __initdata = {
. activate = FB_ACTIVATE_NOW ,
. height = - 1 ,
. width = - 1 ,
. vmode = FB_VMODE_NONINTERLACED ,
2005-04-17 02:20:36 +04:00
} ;
2005-04-04 05:06:19 +04:00
static struct au1100fb_drv_info drv_info ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/*
* Set hardware with var settings . This will enable the controller with a specific
* mode , normally validated with the fb_check_var method
2005-04-17 02:20:36 +04:00
*/
2005-04-04 05:06:19 +04:00
int au1100fb_setmode ( struct au1100fb_device * fbdev )
2005-04-17 02:20:36 +04:00
{
2005-04-04 05:06:19 +04:00
struct fb_info * info = & fbdev - > info ;
u32 words ;
int index ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
if ( ! fbdev )
return - EINVAL ;
/* Update var-dependent FB info */
if ( panel_is_active ( fbdev - > panel ) | | panel_is_color ( fbdev - > panel ) ) {
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 */
index = ( fbdev - > panel - > control_base & LCD_CONTROL_SBPPF_MASK ) > > LCD_CONTROL_SBPPF_BIT ;
info - > var . red = rgb_bitfields [ index ] [ 0 ] ;
info - > var . green = rgb_bitfields [ index ] [ 1 ] ;
info - > var . blue = rgb_bitfields [ index ] [ 2 ] ;
info - > var . transp = rgb_bitfields [ index ] [ 3 ] ;
info - > fix . visual = FB_VISUAL_TRUECOLOR ;
info - > fix . line_length = info - > var . xres_virtual < < 1 ; /* depth=16 */
2006-08-05 23:14:22 +04:00
}
2005-04-04 05:06:19 +04:00
} else {
/* mono */
info - > fix . visual = FB_VISUAL_MONO10 ;
info - > fix . line_length = info - > var . xres_virtual / 8 ;
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
info - > screen_size = info - > fix . line_length * info - > var . yres_virtual ;
2006-08-05 23:14:19 +04:00
info - > var . rotate = ( ( fbdev - > panel - > control_base & LCD_CONTROL_SM_MASK ) \
> > LCD_CONTROL_SM_BIT ) * 90 ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/* Determine BPP mode and format */
2006-08-05 23:14:19 +04:00
fbdev - > regs - > lcd_control = fbdev - > panel - > control_base ;
2005-04-04 05:06:19 +04:00
fbdev - > regs - > lcd_horztiming = fbdev - > panel - > horztiming ;
fbdev - > regs - > lcd_verttiming = fbdev - > panel - > verttiming ;
fbdev - > regs - > lcd_clkcontrol = fbdev - > panel - > clkcontrol_base ;
2006-08-05 23:14:22 +04:00
fbdev - > regs - > lcd_intenable = 0 ;
fbdev - > regs - > lcd_intstatus = 0 ;
2005-04-04 05:06:19 +04:00
fbdev - > regs - > lcd_dmaaddr0 = LCD_DMA_SA_N ( fbdev - > fb_phys ) ;
if ( panel_is_dual ( fbdev - > panel ) ) {
/* Second panel display seconf half of screen if possible,
* otherwise display the same as the first panel */
if ( info - > var . yres_virtual > = ( info - > var . yres < < 1 ) ) {
fbdev - > regs - > lcd_dmaaddr1 = LCD_DMA_SA_N ( fbdev - > fb_phys +
( info - > fix . line_length *
( info - > var . yres_virtual > > 1 ) ) ) ;
} else {
fbdev - > regs - > lcd_dmaaddr1 = LCD_DMA_SA_N ( fbdev - > fb_phys ) ;
}
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
words = info - > fix . line_length / sizeof ( u32 ) ;
if ( ! info - > var . rotate | | ( info - > var . rotate = = 180 ) ) {
words * = info - > var . yres_virtual ;
if ( info - > var . rotate /* 180 */ ) {
words - = ( words % 8 ) ; /* should be divisable by 8 */
}
}
fbdev - > regs - > lcd_words = LCD_WRD_WRDS_N ( words ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
fbdev - > regs - > lcd_pwmdiv = 0 ;
fbdev - > regs - > lcd_pwmhi = 0 ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/* Resume controller */
fbdev - > regs - > lcd_control | = LCD_CONTROL_GO ;
2006-08-05 23:14:22 +04:00
mdelay ( 10 ) ;
au1100fb_fb_blank ( VESA_NO_BLANKING , info ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
/* fb_setcolreg
* Set color in LCD palette .
*/
int au1100fb_fb_setcolreg ( unsigned regno , unsigned red , unsigned green , unsigned blue , unsigned transp , struct fb_info * fbi )
2005-04-17 02:20:36 +04:00
{
2006-05-31 08:26:57 +04:00
struct au1100fb_device * fbdev ;
u32 * palette ;
2005-04-04 05:06:19 +04:00
u32 value ;
2005-04-17 02:20:36 +04:00
2006-05-31 08:26:57 +04:00
fbdev = to_au1100fb_device ( fbi ) ;
palette = fbdev - > regs - > lcd_pallettebase ;
2005-04-04 05:06:19 +04:00
if ( regno > ( AU1100_LCD_NBR_PALETTE_ENTRIES - 1 ) )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
if ( fbi - > var . grayscale ) {
/* Convert color to grayscale */
red = green = blue =
( 19595 * red + 38470 * green + 7471 * blue ) > > 16 ;
}
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
if ( fbi - > fix . visual = = FB_VISUAL_TRUECOLOR ) {
/* Place color in the pseudopalette */
if ( regno > 16 )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
palette = ( u32 * ) fbi - > pseudo_palette ;
red > > = ( 16 - fbi - > var . red . length ) ;
green > > = ( 16 - fbi - > var . green . length ) ;
blue > > = ( 16 - fbi - > var . blue . length ) ;
value = ( red < < fbi - > var . red . offset ) |
( green < < fbi - > var . green . offset ) |
( blue < < fbi - > var . blue . offset ) ;
value & = 0xFFFF ;
} else if ( panel_is_active ( fbdev - > panel ) ) {
/* COLOR TFT PALLETTIZED (use RGB 565) */
value = ( red & 0xF800 ) | ( ( green > > 5 ) & 0x07E0 ) | ( ( blue > > 11 ) & 0x001F ) ;
value & = 0xFFFF ;
} else if ( panel_is_color ( fbdev - > panel ) ) {
/* COLOR STN MODE */
value = ( ( ( panel_swap_rgb ( fbdev - > panel ) ? blue : red ) > > 12 ) & 0x000F ) |
( ( green > > 8 ) & 0x00F0 ) |
( ( ( panel_swap_rgb ( fbdev - > panel ) ? red : blue ) > > 4 ) & 0x0F00 ) ;
value & = 0xFFF ;
} else {
/* MONOCHROME MODE */
value = ( green > > 12 ) & 0x000F ;
value & = 0xF ;
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
palette [ regno ] = value ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-04-04 05:06:19 +04:00
/* fb_blank
* Blank the screen . Depending on the mode , the screen will be
* activated with the backlight color , or desactivated
*/
int au1100fb_fb_blank ( int blank_mode , struct fb_info * fbi )
2005-04-17 02:20:36 +04:00
{
2005-04-04 05:06:19 +04:00
struct au1100fb_device * fbdev = to_au1100fb_device ( fbi ) ;
print_dbg ( " fb_blank %d %p " , blank_mode , fbi ) ;
2005-04-17 02:20:36 +04:00
switch ( blank_mode ) {
2005-04-04 05:06:19 +04:00
2005-04-17 02:20:36 +04:00
case VESA_NO_BLANKING :
2005-04-04 05:06:19 +04:00
/* Turn on panel */
fbdev - > regs - > lcd_control | = LCD_CONTROL_GO ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_MIPS_PB1100
2005-04-04 05:06:19 +04:00
if ( drv_info . panel_idx = = 1 ) {
au_writew ( au_readw ( PB1100_G_CONTROL )
| ( PB1100_G_CONTROL_BL | PB1100_G_CONTROL_VDD ) ,
2005-04-17 02:20:36 +04:00
PB1100_G_CONTROL ) ;
2005-04-04 05:06:19 +04:00
}
2005-04-17 02:20:36 +04:00
# endif
au_sync ( ) ;
break ;
case VESA_VSYNC_SUSPEND :
case VESA_HSYNC_SUSPEND :
case VESA_POWERDOWN :
2005-04-04 05:06:19 +04:00
/* Turn off panel */
fbdev - > regs - > lcd_control & = ~ LCD_CONTROL_GO ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_MIPS_PB1100
2005-04-04 05:06:19 +04:00
if ( drv_info . panel_idx = = 1 ) {
au_writew ( au_readw ( PB1100_G_CONTROL )
& ~ ( PB1100_G_CONTROL_BL | PB1100_G_CONTROL_VDD ) ,
2005-04-17 02:20:36 +04:00
PB1100_G_CONTROL ) ;
2005-04-04 05:06:19 +04:00
}
2005-04-17 02:20:36 +04:00
# endif
au_sync ( ) ;
break ;
default :
break ;
}
return 0 ;
}
2005-04-04 05:06:19 +04:00
/* fb_pan_display
* Pan display in x and / or y as specified
*/
int au1100fb_fb_pan_display ( struct fb_var_screeninfo * var , struct fb_info * fbi )
2005-04-17 02:20:36 +04:00
{
2006-05-31 08:26:57 +04:00
struct au1100fb_device * fbdev ;
2005-04-04 05:06:19 +04:00
int dy ;
2006-05-31 08:26:57 +04:00
fbdev = to_au1100fb_device ( fbi ) ;
2005-04-04 05:06:19 +04:00
print_dbg ( " fb_pan_display %p %p " , var , fbi ) ;
if ( ! var | | ! fbdev ) {
return - EINVAL ;
}
if ( var - > xoffset - fbi - > var . xoffset ) {
/* No support for X panning for now! */
return - EINVAL ;
}
print_dbg ( " fb_pan_display 2 %p %p " , var , fbi ) ;
dy = var - > yoffset - fbi - > var . yoffset ;
if ( dy ) {
u32 dmaaddr ;
print_dbg ( " Panning screen of %d lines " , dy ) ;
dmaaddr = fbdev - > regs - > lcd_dmaaddr0 ;
dmaaddr + = ( fbi - > fix . line_length * dy ) ;
/* TODO: Wait for current frame to finished */
fbdev - > regs - > lcd_dmaaddr0 = LCD_DMA_SA_N ( dmaaddr ) ;
if ( panel_is_dual ( fbdev - > panel ) ) {
dmaaddr = fbdev - > regs - > lcd_dmaaddr1 ;
dmaaddr + = ( fbi - > fix . line_length * dy ) ;
fbdev - > regs - > lcd_dmaaddr0 = LCD_DMA_SA_N ( dmaaddr ) ;
}
}
print_dbg ( " fb_pan_display 3 %p %p " , var , fbi ) ;
return 0 ;
}
/* fb_rotate
* Rotate the display of this angle . This doesn ' t seems to be used by the core ,
* but as our hardware supports it , so why not implementing it . . .
*/
void au1100fb_fb_rotate ( struct fb_info * fbi , int angle )
{
struct au1100fb_device * fbdev = to_au1100fb_device ( fbi ) ;
print_dbg ( " fb_rotate %p %d " , fbi , angle ) ;
if ( fbdev & & ( angle > 0 ) & & ! ( angle % 90 ) ) {
fbdev - > regs - > lcd_control & = ~ LCD_CONTROL_GO ;
fbdev - > regs - > lcd_control & = ~ ( LCD_CONTROL_SM_MASK ) ;
fbdev - > regs - > lcd_control | = ( ( angle / 90 ) < < LCD_CONTROL_SM_BIT ) ;
fbdev - > regs - > lcd_control | = LCD_CONTROL_GO ;
2005-04-17 02:20:36 +04:00
}
}
2005-04-04 05:06:19 +04:00
/* fb_mmap
* Map video memory in user space . We don ' t use the generic fb_mmap method mainly
* to allow the use of the TLB streaming flag ( CCA = 6 )
*/
2006-01-15 00:21:25 +03:00
int au1100fb_fb_mmap ( struct fb_info * fbi , struct vm_area_struct * vma )
2005-04-17 02:20:36 +04:00
{
2006-05-31 08:26:57 +04:00
struct au1100fb_device * fbdev ;
2005-04-17 02:20:36 +04:00
unsigned int len ;
unsigned long start = 0 , off ;
2006-05-31 08:26:57 +04:00
fbdev = to_au1100fb_device ( fbi ) ;
2005-04-17 02:20:36 +04:00
if ( vma - > vm_pgoff > ( ~ 0UL > > PAGE_SHIFT ) ) {
return - EINVAL ;
}
2005-04-04 05:06:19 +04:00
start = fbdev - > fb_phys & PAGE_MASK ;
len = PAGE_ALIGN ( ( start & ~ PAGE_MASK ) + fbdev - > fb_len ) ;
2005-04-17 02:20:36 +04:00
off = vma - > vm_pgoff < < PAGE_SHIFT ;
if ( ( vma - > vm_end - vma - > vm_start + off ) > len ) {
return - EINVAL ;
}
off + = start ;
vma - > vm_pgoff = off > > PAGE_SHIFT ;
2005-04-04 05:06:19 +04:00
vma - > vm_page_prot = pgprot_noncached ( vma - > vm_page_prot ) ;
2005-04-17 02:20:36 +04:00
pgprot_val ( vma - > vm_page_prot ) | = ( 6 < < 9 ) ; //CCA=6
vma - > vm_flags | = VM_IO ;
2006-02-25 00:04:17 +03:00
if ( io_remap_pfn_range ( vma , vma - > vm_start , off > > PAGE_SHIFT ,
2005-04-17 02:20:36 +04:00
vma - > vm_end - vma - > vm_start ,
vma - > vm_page_prot ) ) {
return - EAGAIN ;
}
return 0 ;
}
2005-04-04 05:06:19 +04:00
static struct fb_ops au1100fb_ops =
2005-04-17 02:20:36 +04:00
{
2005-04-04 05:06:19 +04:00
. owner = THIS_MODULE ,
. fb_setcolreg = au1100fb_fb_setcolreg ,
. fb_blank = au1100fb_fb_blank ,
. fb_pan_display = au1100fb_fb_pan_display ,
. fb_fillrect = cfb_fillrect ,
. fb_copyarea = cfb_copyarea ,
. fb_imageblit = cfb_imageblit ,
. fb_rotate = au1100fb_fb_rotate ,
. fb_mmap = au1100fb_fb_mmap ,
} ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/*-------------------------------------------------------------------------*/
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/* AU1100 LCD controller device driver */
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
int au1100fb_drv_probe ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2005-04-04 05:06:19 +04:00
struct au1100fb_device * fbdev = NULL ;
struct resource * regs_res ;
unsigned long page ;
u32 sys_clksrc ;
if ( ! dev )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2005-04-04 05:06:19 +04:00
/* Allocate new device private */
if ( ! ( fbdev = kmalloc ( sizeof ( struct au1100fb_device ) , GFP_KERNEL ) ) ) {
print_err ( " fail to allocate device private record " ) ;
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
memset ( ( void * ) fbdev , 0 , sizeof ( struct au1100fb_device ) ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
fbdev - > panel = & known_lcd_panels [ drv_info . panel_idx ] ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
dev_set_drvdata ( dev , ( void * ) fbdev ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/* Allocate region for our registers and map them */
if ( ! ( regs_res = platform_get_resource ( to_platform_device ( dev ) ,
IORESOURCE_MEM , 0 ) ) ) {
print_err ( " fail to retrieve registers resource " ) ;
return - EFAULT ;
}
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
au1100fb_fix . mmio_start = regs_res - > start ;
au1100fb_fix . mmio_len = regs_res - > end - regs_res - > start + 1 ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
if ( ! request_mem_region ( au1100fb_fix . mmio_start , au1100fb_fix . mmio_len ,
DRIVER_NAME ) ) {
2006-05-31 08:26:57 +04:00
print_err ( " fail to lock memory region at 0x%08lx " ,
2005-04-04 05:06:19 +04:00
au1100fb_fix . mmio_start ) ;
return - EBUSY ;
}
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
fbdev - > regs = ( struct au1100fb_regs * ) KSEG1ADDR ( au1100fb_fix . mmio_start ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
print_dbg ( " Register memory map at %p " , fbdev - > regs ) ;
print_dbg ( " phys=0x%08x, size=%d " , fbdev - > regs_phys , fbdev - > regs_len ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/* Allocate the framebuffer to the maximum screen size * nbr of video buffers */
fbdev - > fb_len = fbdev - > panel - > xres * fbdev - > panel - > yres *
( fbdev - > panel - > bpp > > 3 ) * AU1100FB_NBR_VIDEO_BUFFERS ;
fbdev - > fb_mem = dma_alloc_coherent ( dev , PAGE_ALIGN ( fbdev - > fb_len ) ,
& fbdev - > fb_phys , GFP_KERNEL ) ;
if ( ! fbdev - > fb_mem ) {
print_err ( " fail to allocate frambuffer (size: %dK)) " ,
fbdev - > fb_len / 1024 ) ;
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
}
2005-04-04 05:06:19 +04:00
au1100fb_fix . smem_start = fbdev - > fb_phys ;
au1100fb_fix . smem_len = fbdev - > fb_len ;
2005-04-17 02:20:36 +04:00
/*
* Set page reserved so that mmap will work . This is necessary
* since we ' ll be remapping normal memory .
*/
2005-04-04 05:06:19 +04:00
for ( page = ( unsigned long ) fbdev - > fb_mem ;
page < PAGE_ALIGN ( ( unsigned long ) fbdev - > fb_mem + fbdev - > fb_len ) ;
2005-04-17 02:20:36 +04:00
page + = PAGE_SIZE ) {
2005-04-04 05:06:19 +04:00
# if CONFIG_DMA_NONCOHERENT
SetPageReserved ( virt_to_page ( CAC_ADDR ( page ) ) ) ;
# else
2005-04-17 02:20:36 +04:00
SetPageReserved ( virt_to_page ( page ) ) ;
2005-04-04 05:06:19 +04:00
# endif
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
print_dbg ( " Framebuffer memory map at %p " , fbdev - > fb_mem ) ;
print_dbg ( " phys=0x%08x, size=%dK " , fbdev - > fb_phys , fbdev - > fb_len / 1024 ) ;
/* Setup LCD clock to AUX (48 MHz) */
sys_clksrc = au_readl ( SYS_CLKSRC ) & ~ ( SYS_CS_ML_MASK | SYS_CS_DL | SYS_CS_CL ) ;
au_writel ( ( sys_clksrc | ( 1 < < SYS_CS_ML_BIT ) ) , SYS_CLKSRC ) ;
/* load the panel info into the var struct */
au1100fb_var . bits_per_pixel = fbdev - > panel - > bpp ;
au1100fb_var . xres = fbdev - > panel - > xres ;
au1100fb_var . xres_virtual = au1100fb_var . xres ;
au1100fb_var . yres = fbdev - > panel - > yres ;
au1100fb_var . yres_virtual = au1100fb_var . yres ;
fbdev - > info . screen_base = fbdev - > fb_mem ;
fbdev - > info . fbops = & au1100fb_ops ;
fbdev - > info . fix = au1100fb_fix ;
if ( ! ( fbdev - > info . pseudo_palette = kmalloc ( sizeof ( u32 ) * 16 , GFP_KERNEL ) ) ) {
return - ENOMEM ;
}
memset ( fbdev - > info . pseudo_palette , 0 , sizeof ( u32 ) * 16 ) ;
if ( fb_alloc_cmap ( & fbdev - > info . cmap , AU1100_LCD_NBR_PALETTE_ENTRIES , 0 ) < 0 ) {
print_err ( " Fail to allocate colormap (%d entries) " ,
AU1100_LCD_NBR_PALETTE_ENTRIES ) ;
kfree ( fbdev - > info . pseudo_palette ) ;
return - EFAULT ;
}
fbdev - > info . var = au1100fb_var ;
/* Set h/w registers */
au1100fb_setmode ( fbdev ) ;
/* Register new framebuffer */
if ( register_framebuffer ( & fbdev - > info ) < 0 ) {
print_err ( " cannot register new framebuffer " ) ;
goto failed ;
}
return 0 ;
failed :
if ( fbdev - > regs ) {
release_mem_region ( fbdev - > regs_phys , fbdev - > regs_len ) ;
}
if ( fbdev - > fb_mem ) {
dma_free_noncoherent ( dev , fbdev - > fb_len , fbdev - > fb_mem , fbdev - > fb_phys ) ;
}
if ( fbdev - > info . cmap . len ! = 0 ) {
fb_dealloc_cmap ( & fbdev - > info . cmap ) ;
}
kfree ( fbdev ) ;
dev_set_drvdata ( dev , NULL ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-04-04 05:06:19 +04:00
int au1100fb_drv_remove ( struct device * dev )
{
struct au1100fb_device * fbdev = NULL ;
if ( ! dev )
return - ENODEV ;
fbdev = ( struct au1100fb_device * ) dev_get_drvdata ( dev ) ;
# if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
au1100fb_fb_blank ( VESA_POWERDOWN , & fbdev - > info ) ;
# endif
fbdev - > regs - > lcd_control & = ~ LCD_CONTROL_GO ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
/* Clean up all probe data */
unregister_framebuffer ( & fbdev - > info ) ;
release_mem_region ( fbdev - > regs_phys , fbdev - > regs_len ) ;
dma_free_coherent ( dev , PAGE_ALIGN ( fbdev - > fb_len ) , fbdev - > fb_mem , fbdev - > fb_phys ) ;
fb_dealloc_cmap ( & fbdev - > info . cmap ) ;
kfree ( fbdev - > info . pseudo_palette ) ;
kfree ( ( void * ) fbdev ) ;
return 0 ;
}
2006-06-26 11:26:49 +04:00
# ifdef CONFIG_PM
static u32 sys_clksrc ;
static struct au1100fb_regs fbregs ;
2006-05-31 08:26:57 +04:00
int au1100fb_drv_suspend ( struct device * dev , pm_message_t state )
2005-04-04 05:06:19 +04:00
{
2006-06-26 11:26:49 +04:00
struct au1100fb_device * fbdev = dev_get_drvdata ( dev ) ;
if ( ! fbdev )
return 0 ;
/* Save the clock source state */
sys_clksrc = au_readl ( SYS_CLKSRC ) ;
/* Blank the LCD */
au1100fb_fb_blank ( VESA_POWERDOWN , & fbdev - > info ) ;
/* Stop LCD clocking */
au_writel ( sys_clksrc & ~ SYS_CS_ML_MASK , SYS_CLKSRC ) ;
memcpy ( & fbregs , fbdev - > regs , sizeof ( struct au1100fb_regs ) ) ;
2005-04-04 05:06:19 +04:00
return 0 ;
}
2006-05-31 08:26:57 +04:00
int au1100fb_drv_resume ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2006-06-26 11:26:49 +04:00
struct au1100fb_device * fbdev = dev_get_drvdata ( dev ) ;
if ( ! fbdev )
return 0 ;
memcpy ( fbdev - > regs , & fbregs , sizeof ( struct au1100fb_regs ) ) ;
/* Restart LCD clocking */
au_writel ( sys_clksrc , SYS_CLKSRC ) ;
/* Unblank the LCD */
au1100fb_fb_blank ( VESA_NO_BLANKING , & fbdev - > info ) ;
2005-04-04 05:06:19 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2006-06-26 11:26:49 +04:00
# else
# define au1100fb_drv_suspend NULL
# define au1100fb_drv_resume NULL
# endif
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
static struct device_driver au1100fb_driver = {
. name = " au1100-lcd " ,
. bus = & platform_bus_type ,
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
. probe = au1100fb_drv_probe ,
. remove = au1100fb_drv_remove ,
. suspend = au1100fb_drv_suspend ,
. resume = au1100fb_drv_resume ,
} ;
/*-------------------------------------------------------------------------*/
/* Kernel driver */
int au1100fb_setup ( char * options )
2005-04-17 02:20:36 +04:00
{
char * this_opt ;
2005-04-04 05:06:19 +04:00
int num_panels = ARRAY_SIZE ( known_lcd_panels ) ;
char * mode = NULL ;
int panel_idx = 0 ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
if ( num_panels < = 0 ) {
print_err ( " No LCD panels supported by driver! " ) ;
return - EFAULT ;
}
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
if ( options ) {
while ( ( this_opt = strsep ( & options , " , " ) ) ! = NULL ) {
/* Panel option */
2005-04-17 02:20:36 +04:00
if ( ! strncmp ( this_opt , " panel: " , 6 ) ) {
2005-04-04 05:06:19 +04:00
int i ;
this_opt + = 6 ;
for ( i = 0 ; i < num_panels ; i + + ) {
if ( ! strncmp ( this_opt ,
known_lcd_panels [ i ] . name ,
2005-04-17 02:20:36 +04:00
strlen ( this_opt ) ) ) {
2005-04-04 05:06:19 +04:00
panel_idx = i ;
2005-04-17 02:20:36 +04:00
break ;
}
}
2005-04-04 05:06:19 +04:00
if ( i > = num_panels ) {
print_warn ( " Panel %s not supported! " , this_opt ) ;
}
}
/* Mode option (only option that start with digit) */
else if ( isdigit ( this_opt [ 0 ] ) ) {
mode = kmalloc ( strlen ( this_opt ) + 1 , GFP_KERNEL ) ;
strncpy ( mode , this_opt , strlen ( this_opt ) + 1 ) ;
}
/* Unsupported option */
else {
print_warn ( " Unsupported option \" %s \" " , this_opt ) ;
2005-04-17 02:20:36 +04:00
}
}
}
2005-04-04 05:06:19 +04:00
drv_info . panel_idx = panel_idx ;
drv_info . opt_mode = mode ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
print_info ( " Panel=%s Mode=%s " ,
known_lcd_panels [ drv_info . panel_idx ] . name ,
drv_info . opt_mode ? drv_info . opt_mode : " default " ) ;
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
return 0 ;
}
2005-04-17 02:20:36 +04:00
2005-04-04 05:06:19 +04:00
int __init au1100fb_init ( void )
2005-04-17 02:20:36 +04:00
{
2005-04-04 05:06:19 +04:00
char * options ;
int ret ;
print_info ( " " DRIVER_DESC " " ) ;
memset ( & drv_info , 0 , sizeof ( drv_info ) ) ;
if ( fb_get_options ( DRIVER_NAME , & options ) )
return - ENODEV ;
/* Setup driver with options */
ret = au1100fb_setup ( options ) ;
if ( ret < 0 ) {
print_err ( " Fail to setup driver " ) ;
return ret ;
}
return driver_register ( & au1100fb_driver ) ;
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
void __exit au1100fb_cleanup ( void )
2005-04-17 02:20:36 +04:00
{
2005-04-04 05:06:19 +04:00
driver_unregister ( & au1100fb_driver ) ;
2006-06-27 13:55:06 +04:00
kfree ( drv_info . opt_mode ) ;
2005-04-17 02:20:36 +04:00
}
2005-04-04 05:06:19 +04:00
module_init ( au1100fb_init ) ;
module_exit ( au1100fb_cleanup ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;