2005-06-22 04:17:04 +04:00
/*
* linux / drivers / video / arcfb . c - - FB driver for Arc monochrome LCD board
*
* Copyright ( C ) 2005 , Jaya Kumar < jayalk @ intworks . biz >
* http : //www.intworks.biz/arclcd
*
* 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 .
*
* Layout is based on skeletonfb . c by James Simmons and Geert Uytterhoeven .
*
* This driver was written to be used with the Arc LCD board . Arc uses a
* set of KS108 chips that control individual 64 x64 LCD matrices . The board
* can be paneled in a variety of setups such as 2 x1 = 128 x64 , 4 x4 = 256 x256 and
* so on . The interface between the board and the host is TTL based GPIO . The
* GPIO requirements are 8 writable data lines and 4 + n lines for control . On a
* GPIO - less system , the board can be tested by connecting the respective sigs
* up to a parallel port connector . The driver requires the IO addresses for
* data and control GPIO at load time . It is unable to probe for the
* existence of the LCD so it must be told at load time whether it should
* be enabled or not .
*
* Todo :
* - testing with 4 x4
* - testing with interrupt hw
*
* General notes :
* - User must set tuhold . It ' s in microseconds . According to the 108 spec ,
* the hold time is supposed to be at least 1 microsecond .
* - User must set num_cols = x num_rows = y , eg : x = 2 means 128
* - User must set arcfb_enable = 1 to enable it
* - User must set dio_addr = 0 xIOADDR cio_addr = 0 xIOADDR
*
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/mm.h>
# include <linux/tty.h>
# include <linux/slab.h>
# include <linux/vmalloc.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/fb.h>
# include <linux/init.h>
# include <linux/arcfb.h>
2005-10-29 22:07:23 +04:00
# include <linux/platform_device.h>
2005-06-22 04:17:04 +04:00
# include <asm/uaccess.h>
# define floor8(a) (a&(~0x07))
# define floorXres(a,xres) (a&(~(xres - 1)))
# define iceil8(a) (((int)((a+7) / 8))*8)
# define ceil64(a) (a|0x3F)
# define ceilXres(a,xres) (a|(xres - 1))
/* ks108 chipset specific defines and code */
# define KS_SET_DPY_START_LINE 0xC0
# define KS_SET_PAGE_NUM 0xB8
# define KS_SET_X 0x40
# define KS_CEHI 0x01
# define KS_CELO 0x00
# define KS_SEL_CMD 0x08
# define KS_SEL_DATA 0x00
# define KS_DPY_ON 0x3F
# define KS_DPY_OFF 0x3E
# define KS_INTACK 0x40
# define KS_CLRINT 0x02
struct arcfb_par {
unsigned long dio_addr ;
unsigned long cio_addr ;
unsigned long c2io_addr ;
atomic_t ref_count ;
unsigned char cslut [ 9 ] ;
struct fb_info * info ;
unsigned int irq ;
spinlock_t lock ;
} ;
static struct fb_fix_screeninfo arcfb_fix __initdata = {
. id = " arcfb " ,
. type = FB_TYPE_PACKED_PIXELS ,
. visual = FB_VISUAL_MONO01 ,
. xpanstep = 0 ,
. ypanstep = 1 ,
. ywrapstep = 0 ,
. accel = FB_ACCEL_NONE ,
} ;
static struct fb_var_screeninfo arcfb_var __initdata = {
. xres = 128 ,
. yres = 64 ,
. xres_virtual = 128 ,
. yres_virtual = 64 ,
. bits_per_pixel = 1 ,
. nonstd = 1 ,
} ;
static unsigned long num_cols ;
static unsigned long num_rows ;
static unsigned long dio_addr ;
static unsigned long cio_addr ;
static unsigned long c2io_addr ;
static unsigned long splashval ;
static unsigned long tuhold ;
static unsigned int nosplash ;
static unsigned int arcfb_enable ;
static unsigned int irq ;
static DECLARE_WAIT_QUEUE_HEAD ( arcfb_waitq ) ;
static void ks108_writeb_ctl ( struct arcfb_par * par ,
unsigned int chipindex , unsigned char value )
{
unsigned char chipselval = par - > cslut [ chipindex ] ;
outb ( chipselval | KS_CEHI | KS_SEL_CMD , par - > cio_addr ) ;
outb ( value , par - > dio_addr ) ;
udelay ( tuhold ) ;
outb ( chipselval | KS_CELO | KS_SEL_CMD , par - > cio_addr ) ;
}
static void ks108_writeb_mainctl ( struct arcfb_par * par , unsigned char value )
{
outb ( value , par - > cio_addr ) ;
udelay ( tuhold ) ;
}
static unsigned char ks108_readb_ctl2 ( struct arcfb_par * par )
{
return inb ( par - > c2io_addr ) ;
}
static void ks108_writeb_data ( struct arcfb_par * par ,
unsigned int chipindex , unsigned char value )
{
unsigned char chipselval = par - > cslut [ chipindex ] ;
outb ( chipselval | KS_CEHI | KS_SEL_DATA , par - > cio_addr ) ;
outb ( value , par - > dio_addr ) ;
udelay ( tuhold ) ;
outb ( chipselval | KS_CELO | KS_SEL_DATA , par - > cio_addr ) ;
}
static void ks108_set_start_line ( struct arcfb_par * par ,
unsigned int chipindex , unsigned char y )
{
ks108_writeb_ctl ( par , chipindex , KS_SET_DPY_START_LINE | y ) ;
}
static void ks108_set_yaddr ( struct arcfb_par * par ,
unsigned int chipindex , unsigned char y )
{
ks108_writeb_ctl ( par , chipindex , KS_SET_PAGE_NUM | y ) ;
}
static void ks108_set_xaddr ( struct arcfb_par * par ,
unsigned int chipindex , unsigned char x )
{
ks108_writeb_ctl ( par , chipindex , KS_SET_X | x ) ;
}
static void ks108_clear_lcd ( struct arcfb_par * par , unsigned int chipindex )
{
int i , j ;
for ( i = 0 ; i < = 8 ; i + + ) {
ks108_set_yaddr ( par , chipindex , i ) ;
ks108_set_xaddr ( par , chipindex , 0 ) ;
for ( j = 0 ; j < 64 ; j + + ) {
ks108_writeb_data ( par , chipindex ,
( unsigned char ) splashval ) ;
}
}
}
/* main arcfb functions */
static int arcfb_open ( struct fb_info * info , int user )
{
struct arcfb_par * par = info - > par ;
atomic_inc ( & par - > ref_count ) ;
return 0 ;
}
static int arcfb_release ( struct fb_info * info , int user )
{
struct arcfb_par * par = info - > par ;
int count = atomic_read ( & par - > ref_count ) ;
if ( ! count )
return - EINVAL ;
atomic_dec ( & par - > ref_count ) ;
return 0 ;
}
static int arcfb_pan_display ( struct fb_var_screeninfo * var ,
struct fb_info * info )
{
int i ;
struct arcfb_par * par = info - > par ;
if ( ( var - > vmode & FB_VMODE_YWRAP ) & & ( var - > yoffset < 64 )
& & ( info - > var . yres < = 64 ) ) {
for ( i = 0 ; i < num_cols ; i + + ) {
ks108_set_start_line ( par , i , var - > yoffset ) ;
}
info - > var . yoffset = var - > yoffset ;
return 0 ;
}
return - EINVAL ;
}
static irqreturn_t arcfb_interrupt ( int vec , void * dev_instance ,
struct pt_regs * regs )
{
struct fb_info * info = dev_instance ;
unsigned char ctl2status ;
struct arcfb_par * par = info - > par ;
ctl2status = ks108_readb_ctl2 ( par ) ;
if ( ! ( ctl2status & KS_INTACK ) ) /* not arc generated interrupt */
return IRQ_NONE ;
ks108_writeb_mainctl ( par , KS_CLRINT ) ;
spin_lock ( & par - > lock ) ;
if ( waitqueue_active ( & arcfb_waitq ) ) {
wake_up ( & arcfb_waitq ) ;
}
spin_unlock ( & par - > lock ) ;
return IRQ_HANDLED ;
}
/*
* here we handle a specific page on the lcd . the complexity comes from
* the fact that the fb is laidout in 8 xX vertical columns . we extract
* each write of 8 vertical pixels . then we shift out as we move along
* X . That ' s what rightshift does . bitmask selects the desired input bit .
*/
static void arcfb_lcd_update_page ( struct arcfb_par * par , unsigned int upper ,
unsigned int left , unsigned int right , unsigned int distance )
{
unsigned char * src ;
unsigned int xindex , yindex , chipindex , linesize ;
2006-03-11 14:27:21 +03:00
int i ;
2005-06-22 04:17:04 +04:00
unsigned char val ;
unsigned char bitmask , rightshift ;
xindex = left > > 6 ;
yindex = upper > > 6 ;
chipindex = ( xindex + ( yindex * num_cols ) ) ;
ks108_set_yaddr ( par , chipindex , upper / 8 ) ;
linesize = par - > info - > var . xres / 8 ;
src = par - > info - > screen_base + ( left / 8 ) + ( upper * linesize ) ;
ks108_set_xaddr ( par , chipindex , left ) ;
bitmask = 1 ;
rightshift = 0 ;
while ( left < = right ) {
val = 0 ;
for ( i = 0 ; i < 8 ; i + + ) {
if ( i > rightshift ) {
val | = ( * ( src + ( i * linesize ) ) & bitmask )
< < ( i - rightshift ) ;
} else {
val | = ( * ( src + ( i * linesize ) ) & bitmask )
> > ( rightshift - i ) ;
}
}
ks108_writeb_data ( par , chipindex , val ) ;
left + + ;
if ( bitmask = = 0x80 ) {
bitmask = 1 ;
src + + ;
rightshift = 0 ;
} else {
bitmask < < = 1 ;
rightshift + + ;
}
}
}
/*
* here we handle the entire vertical page of the update . we write across
* lcd chips . update_page uses the upper / left values to decide which
* chip to select for the right . upper is needed for setting the page
* desired for the write .
*/
static void arcfb_lcd_update_vert ( struct arcfb_par * par , unsigned int top ,
unsigned int bottom , unsigned int left , unsigned int right )
{
unsigned int distance , upper , lower ;
distance = ( bottom - top ) + 1 ;
upper = top ;
lower = top + 7 ;
while ( distance > 0 ) {
distance - = 8 ;
arcfb_lcd_update_page ( par , upper , left , right , 8 ) ;
upper = lower + 1 ;
lower = upper + 7 ;
}
}
/*
* here we handle horizontal blocks for the update . update_vert will
* handle spaning multiple pages . we break out each horizontal
* block in to individual blocks no taller than 64 pixels .
*/
static void arcfb_lcd_update_horiz ( struct arcfb_par * par , unsigned int left ,
unsigned int right , unsigned int top , unsigned int h )
{
unsigned int distance , upper , lower ;
distance = h ;
upper = floor8 ( top ) ;
lower = min ( upper + distance - 1 , ceil64 ( upper ) ) ;
while ( distance > 0 ) {
distance - = ( ( lower - upper ) + 1 ) ;
arcfb_lcd_update_vert ( par , upper , lower , left , right ) ;
upper = lower + 1 ;
lower = min ( upper + distance - 1 , ceil64 ( upper ) ) ;
}
}
/*
* here we start the process of spliting out the fb update into
* individual blocks of pixels . we end up spliting into 64 x64 blocks
* and finally down to 64 x8 pages .
*/
static void arcfb_lcd_update ( struct arcfb_par * par , unsigned int dx ,
unsigned int dy , unsigned int w , unsigned int h )
{
unsigned int left , right , distance , y ;
/* align the request first */
y = floor8 ( dy ) ;
h + = dy - y ;
h = iceil8 ( h ) ;
distance = w ;
left = dx ;
right = min ( left + w - 1 , ceil64 ( left ) ) ;
while ( distance > 0 ) {
arcfb_lcd_update_horiz ( par , left , right , y , h ) ;
distance - = ( ( right - left ) + 1 ) ;
left = right + 1 ;
right = min ( left + distance - 1 , ceil64 ( left ) ) ;
}
}
2006-01-10 07:54:04 +03:00
static void arcfb_fillrect ( struct fb_info * info ,
const struct fb_fillrect * rect )
2005-06-22 04:17:04 +04:00
{
struct arcfb_par * par = info - > par ;
cfb_fillrect ( info , rect ) ;
/* update the physical lcd */
arcfb_lcd_update ( par , rect - > dx , rect - > dy , rect - > width , rect - > height ) ;
}
2006-01-10 07:54:04 +03:00
static void arcfb_copyarea ( struct fb_info * info ,
const struct fb_copyarea * area )
2005-06-22 04:17:04 +04:00
{
struct arcfb_par * par = info - > par ;
cfb_copyarea ( info , area ) ;
/* update the physical lcd */
arcfb_lcd_update ( par , area - > dx , area - > dy , area - > width , area - > height ) ;
}
2006-01-10 07:54:04 +03:00
static void arcfb_imageblit ( struct fb_info * info , const struct fb_image * image )
2005-06-22 04:17:04 +04:00
{
struct arcfb_par * par = info - > par ;
cfb_imageblit ( info , image ) ;
/* update the physical lcd */
arcfb_lcd_update ( par , image - > dx , image - > dy , image - > width ,
image - > height ) ;
}
2006-01-15 00:21:25 +03:00
static int arcfb_ioctl ( struct fb_info * info ,
unsigned int cmd , unsigned long arg )
2005-06-22 04:17:04 +04:00
{
void __user * argp = ( void __user * ) arg ;
struct arcfb_par * par = info - > par ;
unsigned long flags ;
switch ( cmd ) {
case FBIO_WAITEVENT :
{
DEFINE_WAIT ( wait ) ;
/* illegal to wait on arc if no irq will occur */
if ( ! par - > irq )
return - EINVAL ;
/* wait until the Arc has generated an interrupt
* which will wake us up */
spin_lock_irqsave ( & par - > lock , flags ) ;
prepare_to_wait ( & arcfb_waitq , & wait ,
TASK_INTERRUPTIBLE ) ;
spin_unlock_irqrestore ( & par - > lock , flags ) ;
schedule ( ) ;
finish_wait ( & arcfb_waitq , & wait ) ;
}
case FBIO_GETCONTROL2 :
{
unsigned char ctl2 ;
ctl2 = ks108_readb_ctl2 ( info - > par ) ;
if ( copy_to_user ( argp , & ctl2 , sizeof ( ctl2 ) ) )
return - EFAULT ;
return 0 ;
}
default :
return - EINVAL ;
}
}
/*
* this is the access path from userspace . they can seek and write to
* the fb . it ' s inefficient for them to do anything less than 64 * 8
* writes since we update the lcd in each write ( ) anyway .
*/
2005-12-15 12:18:15 +03:00
static ssize_t arcfb_write ( struct file * file , const char __user * buf , size_t count ,
2005-06-22 04:17:04 +04:00
loff_t * ppos )
{
/* modded from epson 1355 */
struct inode * inode ;
int fbidx ;
struct fb_info * info ;
unsigned long p ;
int err = - EINVAL ;
unsigned int fbmemlength , x , y , w , h , bitppos , startpos , endpos , bitcount ;
struct arcfb_par * par ;
unsigned int xres ;
p = * ppos ;
inode = file - > f_dentry - > d_inode ;
fbidx = iminor ( inode ) ;
info = registered_fb [ fbidx ] ;
par = info - > par ;
if ( ! info | | ! info - > screen_base )
return - ENODEV ;
xres = info - > var . xres ;
fbmemlength = ( xres * info - > var . yres ) / 8 ;
if ( p > fbmemlength )
return - ENOSPC ;
err = 0 ;
if ( ( count + p ) > fbmemlength ) {
count = fbmemlength - p ;
err = - ENOSPC ;
}
if ( count ) {
char * base_addr ;
base_addr = info - > screen_base ;
count - = copy_from_user ( base_addr + p , buf , count ) ;
* ppos + = count ;
err = - EFAULT ;
}
bitppos = p * 8 ;
startpos = floorXres ( bitppos , xres ) ;
endpos = ceilXres ( ( bitppos + ( count * 8 ) ) , xres ) ;
bitcount = endpos - startpos ;
x = startpos % xres ;
y = startpos / xres ;
w = xres ;
h = bitcount / xres ;
arcfb_lcd_update ( par , x , y , w , h ) ;
if ( count )
return count ;
return err ;
}
static struct fb_ops arcfb_ops = {
. owner = THIS_MODULE ,
. fb_open = arcfb_open ,
. fb_write = arcfb_write ,
. fb_release = arcfb_release ,
. fb_pan_display = arcfb_pan_display ,
. fb_fillrect = arcfb_fillrect ,
. fb_copyarea = arcfb_copyarea ,
. fb_imageblit = arcfb_imageblit ,
. fb_ioctl = arcfb_ioctl ,
} ;
2005-11-10 01:32:44 +03:00
static int __init arcfb_probe ( struct platform_device * dev )
2005-06-22 04:17:04 +04:00
{
struct fb_info * info ;
int retval = - ENOMEM ;
int videomemorysize ;
unsigned char * videomemory ;
struct arcfb_par * par ;
int i ;
videomemorysize = ( ( ( 64 * 64 ) * num_cols ) * num_rows ) / 8 ;
/* We need a flat backing store for the Arc's
less - flat actual paged framebuffer */
if ( ! ( videomemory = vmalloc ( videomemorysize ) ) )
return retval ;
memset ( videomemory , 0 , videomemorysize ) ;
info = framebuffer_alloc ( sizeof ( struct arcfb_par ) , & dev - > dev ) ;
if ( ! info )
goto err ;
info - > screen_base = ( char __iomem * ) videomemory ;
info - > fbops = & arcfb_ops ;
info - > var = arcfb_var ;
info - > fix = arcfb_fix ;
par = info - > par ;
par - > info = info ;
if ( ! dio_addr | | ! cio_addr | | ! c2io_addr ) {
printk ( KERN_WARNING " no IO addresses supplied \n " ) ;
goto err1 ;
}
par - > dio_addr = dio_addr ;
par - > cio_addr = cio_addr ;
par - > c2io_addr = c2io_addr ;
par - > cslut [ 0 ] = 0x00 ;
par - > cslut [ 1 ] = 0x06 ;
info - > flags = FBINFO_FLAG_DEFAULT ;
spin_lock_init ( & par - > lock ) ;
retval = register_framebuffer ( info ) ;
if ( retval < 0 )
goto err1 ;
2005-11-10 01:32:44 +03:00
platform_set_drvdata ( dev , info ) ;
2005-06-22 04:17:04 +04:00
if ( irq ) {
par - > irq = irq ;
if ( request_irq ( par - > irq , & arcfb_interrupt , SA_SHIRQ ,
" arcfb " , info ) ) {
printk ( KERN_INFO
" arcfb: Failed req IRQ %d \n " , par - > irq ) ;
goto err1 ;
}
}
printk ( KERN_INFO
" fb%d: Arc frame buffer device, using %dK of video memory \n " ,
info - > node , videomemorysize > > 10 ) ;
/* this inits the lcd but doesn't clear dirty pixels */
for ( i = 0 ; i < num_cols * num_rows ; i + + ) {
ks108_writeb_ctl ( par , i , KS_DPY_OFF ) ;
ks108_set_start_line ( par , i , 0 ) ;
ks108_set_yaddr ( par , i , 0 ) ;
ks108_set_xaddr ( par , i , 0 ) ;
ks108_writeb_ctl ( par , i , KS_DPY_ON ) ;
}
/* if we were told to splash the screen, we just clear it */
if ( ! nosplash ) {
for ( i = 0 ; i < num_cols * num_rows ; i + + ) {
printk ( KERN_INFO " fb%d: splashing lcd %d \n " ,
info - > node , i ) ;
ks108_set_start_line ( par , i , 0 ) ;
ks108_clear_lcd ( par , i ) ;
}
}
return 0 ;
err1 :
framebuffer_release ( info ) ;
err :
vfree ( videomemory ) ;
return retval ;
}
2005-11-10 01:32:44 +03:00
static int arcfb_remove ( struct platform_device * dev )
2005-06-22 04:17:04 +04:00
{
2005-11-10 01:32:44 +03:00
struct fb_info * info = platform_get_drvdata ( dev ) ;
2005-06-22 04:17:04 +04:00
if ( info ) {
unregister_framebuffer ( info ) ;
vfree ( info - > screen_base ) ;
framebuffer_release ( info ) ;
}
return 0 ;
}
2005-11-10 01:32:44 +03:00
static struct platform_driver arcfb_driver = {
2005-06-22 04:17:04 +04:00
. probe = arcfb_probe ,
. remove = arcfb_remove ,
2005-11-10 01:32:44 +03:00
. driver = {
. name = " arcfb " ,
} ,
2005-06-22 04:17:04 +04:00
} ;
2005-11-06 00:21:38 +03:00
static struct platform_device * arcfb_device ;
2005-06-22 04:17:04 +04:00
static int __init arcfb_init ( void )
{
int ret ;
if ( ! arcfb_enable )
return - ENXIO ;
2005-11-10 01:32:44 +03:00
ret = platform_driver_register ( & arcfb_driver ) ;
2005-06-22 04:17:04 +04:00
if ( ! ret ) {
2005-11-06 00:21:38 +03:00
arcfb_device = platform_device_alloc ( " arcfb " , 0 ) ;
if ( arcfb_device ) {
ret = platform_device_add ( arcfb_device ) ;
} else {
ret = - ENOMEM ;
}
if ( ret ) {
platform_device_put ( arcfb_device ) ;
2005-11-10 01:32:44 +03:00
platform_driver_unregister ( & arcfb_driver ) ;
2005-11-06 00:21:38 +03:00
}
2005-06-22 04:17:04 +04:00
}
return ret ;
}
static void __exit arcfb_exit ( void )
{
2005-11-06 00:21:38 +03:00
platform_device_unregister ( arcfb_device ) ;
2005-11-10 01:32:44 +03:00
platform_driver_unregister ( & arcfb_driver ) ;
2005-06-22 04:17:04 +04:00
}
module_param ( num_cols , ulong , 0 ) ;
MODULE_PARM_DESC ( num_cols , " Num horiz panels, eg: 2 = 128 bit wide " ) ;
module_param ( num_rows , ulong , 0 ) ;
MODULE_PARM_DESC ( num_rows , " Num vert panels, eg: 1 = 64 bit high " ) ;
module_param ( nosplash , uint , 0 ) ;
MODULE_PARM_DESC ( nosplash , " Disable doing the splash screen " ) ;
module_param ( arcfb_enable , uint , 0 ) ;
MODULE_PARM_DESC ( arcfb_enable , " Enable communication with Arc board " ) ;
module_param ( dio_addr , ulong , 0 ) ;
MODULE_PARM_DESC ( dio_addr , " IO address for data, eg: 0x480 " ) ;
module_param ( cio_addr , ulong , 0 ) ;
MODULE_PARM_DESC ( cio_addr , " IO address for control, eg: 0x400 " ) ;
module_param ( c2io_addr , ulong , 0 ) ;
MODULE_PARM_DESC ( c2io_addr , " IO address for secondary control, eg: 0x408 " ) ;
module_param ( splashval , ulong , 0 ) ;
MODULE_PARM_DESC ( splashval , " Splash pattern: 0xFF is black, 0x00 is green " ) ;
module_param ( tuhold , ulong , 0 ) ;
MODULE_PARM_DESC ( tuhold , " Time to hold between strobing data to Arc board " ) ;
module_param ( irq , uint , 0 ) ;
MODULE_PARM_DESC ( irq , " IRQ for the Arc board " ) ;
module_init ( arcfb_init ) ;
module_exit ( arcfb_exit ) ;
MODULE_DESCRIPTION ( " fbdev driver for Arc monochrome LCD board " ) ;
MODULE_AUTHOR ( " Jaya Kumar " ) ;
MODULE_LICENSE ( " GPL " ) ;