2005-04-17 02:20:36 +04:00
/*
* Chassis LCD / LED driver for HP - PARISC workstations
*
* ( c ) Copyright 2000 Red Hat Software
* ( c ) Copyright 2000 Helge Deller < hdeller @ redhat . com >
* ( c ) Copyright 2001 - 2004 Helge Deller < deller @ gmx . de >
* ( c ) Copyright 2001 Randolph Chung < tausq @ debian . org >
*
* 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 .
*
* TODO :
* - speed - up calculations with inlined assembler
* - interface to write to second row of LCD from / proc ( if technically possible )
*
* Changes :
* - Audit copy_from_user in led_proc_write .
* Daniele Bellucci < bellucda @ tiscali . it >
2005-10-22 06:46:18 +04:00
* - Switch from using a tasklet to a work queue , so the led_LCD_driver
* can sleep .
* David Pye < dmp @ davidmpye . dyndns . org >
2005-04-17 02:20:36 +04:00
*/
# include <linux/config.h>
# include <linux/module.h>
# include <linux/stddef.h> /* for offsetof() */
# include <linux/init.h>
# include <linux/types.h>
# include <linux/ioport.h>
# include <linux/utsname.h>
# include <linux/delay.h>
# include <linux/netdevice.h>
# include <linux/inetdevice.h>
# include <linux/in.h>
# include <linux/interrupt.h>
# include <linux/kernel_stat.h>
# include <linux/reboot.h>
# include <linux/proc_fs.h>
# include <linux/ctype.h>
# include <linux/blkdev.h>
2005-10-22 06:46:18 +04:00
# include <linux/workqueue.h>
2005-10-04 01:35:55 +04:00
# include <linux/rcupdate.h>
2005-04-17 02:20:36 +04:00
# include <asm/io.h>
# include <asm/processor.h>
# include <asm/hardware.h>
# include <asm/param.h> /* HZ */
# include <asm/led.h>
# include <asm/pdc.h>
# include <asm/uaccess.h>
/* The control of the LEDs and LCDs on PARISC-machines have to be done
2005-10-22 06:46:18 +04:00
completely in software . The necessary calculations are done in a work queue
task which is scheduled regularly , and since the calculations may consume a
relatively large amount of CPU time , some of the calculations can be
2005-04-17 02:20:36 +04:00
turned off with the following variables ( controlled via procfs ) */
static int led_type = - 1 ;
2005-10-22 06:46:18 +04:00
static unsigned char lastleds ; /* LED state from most recent update */
static unsigned int led_heartbeat = 1 ;
static unsigned int led_diskio = 1 ;
static unsigned int led_lanrxtx = 1 ;
2005-04-17 02:20:36 +04:00
static char lcd_text [ 32 ] ;
static char lcd_text_default [ 32 ] ;
2005-10-22 06:46:18 +04:00
static struct workqueue_struct * led_wq ;
static void led_work_func ( void * ) ;
static DECLARE_WORK ( led_task , led_work_func , NULL ) ;
2005-04-17 02:20:36 +04:00
#if 0
# define DPRINTK(x) printk x
# else
# define DPRINTK(x)
# endif
struct lcd_block {
unsigned char command ; /* stores the command byte */
unsigned char on ; /* value for turning LED on */
unsigned char off ; /* value for turning LED off */
} ;
/* Structure returned by PDC_RETURN_CHASSIS_INFO */
/* NOTE: we use unsigned long:16 two times, since the following member
lcd_cmd_reg_addr needs to be 64 bit aligned on 64 bit PA2 .0 - machines */
struct pdc_chassis_lcd_info_ret_block {
unsigned long model : 16 ; /* DISPLAY_MODEL_XXXX */
unsigned long lcd_width : 16 ; /* width of the LCD in chars (DISPLAY_MODEL_LCD only) */
unsigned long lcd_cmd_reg_addr ; /* ptr to LCD cmd-register & data ptr for LED */
unsigned long lcd_data_reg_addr ; /* ptr to LCD data-register (LCD only) */
unsigned int min_cmd_delay ; /* delay in uS after cmd-write (LCD only) */
unsigned char reset_cmd1 ; /* command #1 for writing LCD string (LCD only) */
unsigned char reset_cmd2 ; /* command #2 for writing LCD string (LCD only) */
unsigned char act_enable ; /* 0 = no activity (LCD only) */
struct lcd_block heartbeat ;
struct lcd_block disk_io ;
struct lcd_block lan_rcv ;
struct lcd_block lan_tx ;
char _pad ;
} ;
/* LCD_CMD and LCD_DATA for KittyHawk machines */
# define KITTYHAWK_LCD_CMD F_EXTEND(0xf0190000UL) /* 64bit-ready */
# define KITTYHAWK_LCD_DATA (KITTYHAWK_LCD_CMD+1)
/* lcd_info is pre-initialized to the values needed to program KittyHawk LCD's
* HP seems to have used Sharp / Hitachi HD44780 LCDs most of the time . */
static struct pdc_chassis_lcd_info_ret_block
lcd_info __attribute__ ( ( aligned ( 8 ) ) ) =
{
. model = DISPLAY_MODEL_LCD ,
. lcd_width = 16 ,
. lcd_cmd_reg_addr = KITTYHAWK_LCD_CMD ,
. lcd_data_reg_addr = KITTYHAWK_LCD_DATA ,
. min_cmd_delay = 40 ,
. reset_cmd1 = 0x80 ,
. reset_cmd2 = 0xc0 ,
} ;
/* direct access to some of the lcd_info variables */
# define LCD_CMD_REG lcd_info.lcd_cmd_reg_addr
# define LCD_DATA_REG lcd_info.lcd_data_reg_addr
# define LED_DATA_REG lcd_info.lcd_cmd_reg_addr /* LASI & ASP only */
2005-10-22 06:46:18 +04:00
# define LED_HASLCD 1
# define LED_NOLCD 0
/* The workqueue must be created at init-time */
static int start_task ( void )
{
/* Display the default text now */
if ( led_type = = LED_HASLCD ) lcd_print ( lcd_text_default ) ;
/* Create the work queue and queue the LED task */
led_wq = create_singlethread_workqueue ( " led_wq " ) ;
queue_work ( led_wq , & led_task ) ;
return 0 ;
}
device_initcall ( start_task ) ;
2005-04-17 02:20:36 +04:00
/* ptr to LCD/LED-specific function */
static void ( * led_func_ptr ) ( unsigned char ) ;
# ifdef CONFIG_PROC_FS
static int led_proc_read ( char * page , char * * start , off_t off , int count ,
int * eof , void * data )
{
char * out = page ;
int len ;
switch ( ( long ) data )
{
case LED_NOLCD :
out + = sprintf ( out , " Heartbeat: %d \n " , led_heartbeat ) ;
out + = sprintf ( out , " Disk IO: %d \n " , led_diskio ) ;
out + = sprintf ( out , " LAN Rx/Tx: %d \n " , led_lanrxtx ) ;
break ;
case LED_HASLCD :
out + = sprintf ( out , " %s \n " , lcd_text ) ;
break ;
default :
* eof = 1 ;
return 0 ;
}
len = out - page - off ;
if ( len < count ) {
* eof = 1 ;
if ( len < = 0 ) return 0 ;
} else {
len = count ;
}
* start = page + off ;
return len ;
}
static int led_proc_write ( struct file * file , const char * buf ,
unsigned long count , void * data )
{
char * cur , lbuf [ count + 1 ] ;
int d ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EACCES ;
memset ( lbuf , 0 , count + 1 ) ;
if ( copy_from_user ( lbuf , buf , count ) )
return - EFAULT ;
cur = lbuf ;
/* skip initial spaces */
while ( * cur & & isspace ( * cur ) )
{
cur + + ;
}
switch ( ( long ) data )
{
case LED_NOLCD :
d = * cur + + - ' 0 ' ;
if ( d ! = 0 & & d ! = 1 ) goto parse_error ;
led_heartbeat = d ;
if ( * cur + + ! = ' ' ) goto parse_error ;
d = * cur + + - ' 0 ' ;
if ( d ! = 0 & & d ! = 1 ) goto parse_error ;
led_diskio = d ;
if ( * cur + + ! = ' ' ) goto parse_error ;
d = * cur + + - ' 0 ' ;
if ( d ! = 0 & & d ! = 1 ) goto parse_error ;
led_lanrxtx = d ;
break ;
case LED_HASLCD :
if ( * cur & & cur [ strlen ( cur ) - 1 ] = = ' \n ' )
cur [ strlen ( cur ) - 1 ] = 0 ;
if ( * cur = = 0 )
cur = lcd_text_default ;
lcd_print ( cur ) ;
break ;
default :
return 0 ;
}
return count ;
parse_error :
if ( ( long ) data = = LED_NOLCD )
printk ( KERN_CRIT " Parse error: expect \" n n n \" (n == 0 or 1) for heartbeat, \n disk io and lan tx/rx indicators \n " ) ;
return - EINVAL ;
}
static int __init led_create_procfs ( void )
{
struct proc_dir_entry * proc_pdc_root = NULL ;
struct proc_dir_entry * ent ;
if ( led_type = = - 1 ) return - 1 ;
proc_pdc_root = proc_mkdir ( " pdc " , 0 ) ;
if ( ! proc_pdc_root ) return - 1 ;
proc_pdc_root - > owner = THIS_MODULE ;
ent = create_proc_entry ( " led " , S_IFREG | S_IRUGO | S_IWUSR , proc_pdc_root ) ;
if ( ! ent ) return - 1 ;
ent - > nlink = 1 ;
ent - > data = ( void * ) LED_NOLCD ; /* LED */
ent - > read_proc = led_proc_read ;
ent - > write_proc = led_proc_write ;
ent - > owner = THIS_MODULE ;
if ( led_type = = LED_HASLCD )
{
ent = create_proc_entry ( " lcd " , S_IFREG | S_IRUGO | S_IWUSR , proc_pdc_root ) ;
if ( ! ent ) return - 1 ;
ent - > nlink = 1 ;
ent - > data = ( void * ) LED_HASLCD ; /* LCD */
ent - > read_proc = led_proc_read ;
ent - > write_proc = led_proc_write ;
ent - > owner = THIS_MODULE ;
}
return 0 ;
}
# endif
/*
* *
* * led_ASP_driver ( )
* *
*/
# define LED_DATA 0x01 /* data to shift (0:on 1:off) */
# define LED_STROBE 0x02 /* strobe to clock data */
static void led_ASP_driver ( unsigned char leds )
{
int i ;
leds = ~ leds ;
for ( i = 0 ; i < 8 ; i + + ) {
unsigned char value ;
value = ( leds & 0x80 ) > > 7 ;
gsc_writeb ( value , LED_DATA_REG ) ;
gsc_writeb ( value | LED_STROBE , LED_DATA_REG ) ;
leds < < = 1 ;
}
}
/*
* *
* * led_LASI_driver ( )
* *
*/
static void led_LASI_driver ( unsigned char leds )
{
leds = ~ leds ;
gsc_writeb ( leds , LED_DATA_REG ) ;
}
/*
* *
* * led_LCD_driver ( )
* *
*/
static void led_LCD_driver ( unsigned char leds )
{
2005-10-22 06:46:18 +04:00
static int i ;
static unsigned char mask [ 4 ] = { LED_HEARTBEAT , LED_DISK_IO ,
LED_LAN_RCV , LED_LAN_TX } ;
static struct lcd_block * blockp [ 4 ] = {
& lcd_info . heartbeat ,
& lcd_info . disk_io ,
& lcd_info . lan_rcv ,
& lcd_info . lan_tx
} ;
/* Convert min_cmd_delay to milliseconds */
unsigned int msec_cmd_delay = 1 + ( lcd_info . min_cmd_delay / 1000 ) ;
2005-04-17 02:20:36 +04:00
2005-10-22 06:46:18 +04:00
for ( i = 0 ; i < 4 ; + + i )
{
if ( ( leds & mask [ i ] ) ! = ( lastleds & mask [ i ] ) )
{
gsc_writeb ( blockp [ i ] - > command , LCD_CMD_REG ) ;
msleep ( msec_cmd_delay ) ;
gsc_writeb ( leds & mask [ i ] ? blockp [ i ] - > on :
blockp [ i ] - > off , LCD_DATA_REG ) ;
msleep ( msec_cmd_delay ) ;
}
2005-04-17 02:20:36 +04:00
}
}
/*
* *
* * led_get_net_activity ( )
* *
* * calculate if there was TX - or RX - troughput on the network interfaces
* * ( analog to dev_get_info ( ) from net / core / dev . c )
* *
*/
static __inline__ int led_get_net_activity ( void )
{
# ifndef CONFIG_NET
return 0 ;
# else
static unsigned long rx_total_last , tx_total_last ;
unsigned long rx_total , tx_total ;
struct net_device * dev ;
int retval ;
rx_total = tx_total = 0 ;
2005-10-22 06:46:18 +04:00
/* we are running as a workqueue task, so locking dev_base
2005-04-17 02:20:36 +04:00
* for reading should be OK */
read_lock ( & dev_base_lock ) ;
2005-10-04 01:35:55 +04:00
rcu_read_lock ( ) ;
2005-04-17 02:20:36 +04:00
for ( dev = dev_base ; dev ; dev = dev - > next ) {
struct net_device_stats * stats ;
2005-10-04 01:35:55 +04:00
struct in_device * in_dev = __in_dev_get_rcu ( dev ) ;
2005-04-17 02:20:36 +04:00
if ( ! in_dev | | ! in_dev - > ifa_list )
continue ;
if ( LOOPBACK ( in_dev - > ifa_list - > ifa_local ) )
continue ;
if ( ! dev - > get_stats )
continue ;
stats = dev - > get_stats ( dev ) ;
rx_total + = stats - > rx_packets ;
tx_total + = stats - > tx_packets ;
}
2005-10-04 01:35:55 +04:00
rcu_read_unlock ( ) ;
2005-04-17 02:20:36 +04:00
read_unlock ( & dev_base_lock ) ;
retval = 0 ;
if ( rx_total ! = rx_total_last ) {
rx_total_last = rx_total ;
retval | = LED_LAN_RCV ;
}
if ( tx_total ! = tx_total_last ) {
tx_total_last = tx_total ;
retval | = LED_LAN_TX ;
}
return retval ;
# endif
}
/*
* *
* * led_get_diskio_activity ( )
* *
* * calculate if there was disk - io in the system
* *
*/
static __inline__ int led_get_diskio_activity ( void )
{
static unsigned long last_pgpgin , last_pgpgout ;
struct page_state pgstat ;
int changed ;
2005-10-22 06:46:18 +04:00
2005-04-17 02:20:36 +04:00
get_full_page_state ( & pgstat ) ; /* get no of sectors in & out */
/* Just use a very simple calculation here. Do not care about overflow,
since we only want to know if there was activity or not . */
changed = ( pgstat . pgpgin ! = last_pgpgin ) | | ( pgstat . pgpgout ! = last_pgpgout ) ;
last_pgpgin = pgstat . pgpgin ;
last_pgpgout = pgstat . pgpgout ;
2005-10-22 06:46:18 +04:00
2005-04-17 02:20:36 +04:00
return ( changed ? LED_DISK_IO : 0 ) ;
}
/*
2005-10-22 06:46:18 +04:00
* * led_work_func ( )
2005-04-17 02:20:36 +04:00
* *
2005-10-22 06:46:18 +04:00
* * manages when and which chassis LCD / LED gets updated
2005-04-17 02:20:36 +04:00
TODO :
- display load average ( older machines like 715 / 64 have 4 " free " LED ' s for that )
- optimizations
*/
2005-10-22 06:46:18 +04:00
# define HEARTBEAT_LEN (HZ*10 / 100)
# define HEARTBEAT_2ND_RANGE_START (HZ*28 / 100)
2005-04-17 02:20:36 +04:00
# define HEARTBEAT_2ND_RANGE_END (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
2005-10-22 06:46:18 +04:00
# define LED_UPDATE_INTERVAL (1 + (HZ*19 / 1000))
2005-04-17 02:20:36 +04:00
2005-10-22 06:46:18 +04:00
static void led_work_func ( void * unused )
2005-04-17 02:20:36 +04:00
{
2005-10-22 06:46:18 +04:00
static unsigned long last_jiffies ;
2005-04-17 02:20:36 +04:00
static unsigned long count_HZ ; /* counter in range 0..HZ */
2005-10-22 06:46:18 +04:00
unsigned char currentleds = 0 ; /* stores current value of the LEDs */
2005-04-17 02:20:36 +04:00
/* exit if not initialized */
if ( ! led_func_ptr )
return ;
2005-10-22 06:46:18 +04:00
/* increment the heartbeat timekeeper */
count_HZ + = jiffies - last_jiffies ;
last_jiffies = jiffies ;
if ( count_HZ > = HZ )
2005-04-17 02:20:36 +04:00
count_HZ = 0 ;
2005-10-22 06:46:18 +04:00
if ( likely ( led_heartbeat ) )
2005-04-17 02:20:36 +04:00
{
2005-10-22 06:46:18 +04:00
/* flash heartbeat-LED like a real heart
* ( 2 x short then a long delay )
*/
if ( count_HZ < HEARTBEAT_LEN | |
( count_HZ > = HEARTBEAT_2ND_RANGE_START & &
count_HZ < HEARTBEAT_2ND_RANGE_END ) )
currentleds | = LED_HEARTBEAT ;
2005-04-17 02:20:36 +04:00
}
2005-10-22 06:46:18 +04:00
if ( likely ( led_lanrxtx ) ) currentleds | = led_get_net_activity ( ) ;
if ( likely ( led_diskio ) ) currentleds | = led_get_diskio_activity ( ) ;
2005-04-17 02:20:36 +04:00
/* blink all LEDs twice a second if we got an Oops (HPMC) */
2005-10-22 06:46:18 +04:00
if ( unlikely ( oops_in_progress ) )
2005-04-17 02:20:36 +04:00
currentleds = ( count_HZ < = ( HZ / 2 ) ) ? 0 : 0xff ;
2005-10-22 06:46:18 +04:00
if ( currentleds ! = lastleds )
{
led_func_ptr ( currentleds ) ; /* Update the LCD/LEDs */
lastleds = currentleds ;
}
2005-04-17 02:20:36 +04:00
2005-10-22 06:46:18 +04:00
queue_delayed_work ( led_wq , & led_task , LED_UPDATE_INTERVAL ) ;
}
2005-04-17 02:20:36 +04:00
/*
* * led_halt ( )
* *
* * called by the reboot notifier chain at shutdown and stops all
* * LED / LCD activities .
* *
*/
static int led_halt ( struct notifier_block * , unsigned long , void * ) ;
static struct notifier_block led_notifier = {
. notifier_call = led_halt ,
} ;
static int led_halt ( struct notifier_block * nb , unsigned long event , void * buf )
{
char * txt ;
switch ( event ) {
case SYS_RESTART : txt = " SYSTEM RESTART " ;
break ;
case SYS_HALT : txt = " SYSTEM HALT " ;
break ;
case SYS_POWER_OFF : txt = " SYSTEM POWER OFF " ;
break ;
default : return NOTIFY_DONE ;
}
2005-10-22 06:46:18 +04:00
/* Cancel the work item and delete the queue */
if ( led_wq ) {
cancel_rearming_delayed_workqueue ( led_wq , & led_task ) ;
destroy_workqueue ( led_wq ) ;
led_wq = NULL ;
}
2005-04-17 02:20:36 +04:00
if ( lcd_info . model = = DISPLAY_MODEL_LCD )
lcd_print ( txt ) ;
else
if ( led_func_ptr )
led_func_ptr ( 0xff ) ; /* turn all LEDs ON */
unregister_reboot_notifier ( & led_notifier ) ;
return NOTIFY_OK ;
}
/*
* * register_led_driver ( )
* *
* * registers an external LED or LCD for usage by this driver .
* * currently only LCD - , LASI - and ASP - style LCD / LED ' s are supported .
* *
*/
int __init register_led_driver ( int model , unsigned long cmd_reg , unsigned long data_reg )
{
static int initialized ;
if ( initialized | | ! data_reg )
return 1 ;
lcd_info . model = model ; /* store the values */
LCD_CMD_REG = ( cmd_reg = = LED_CMD_REG_NONE ) ? 0 : cmd_reg ;
switch ( lcd_info . model ) {
case DISPLAY_MODEL_LCD :
LCD_DATA_REG = data_reg ;
printk ( KERN_INFO " LCD display at %lx,%lx registered \n " ,
LCD_CMD_REG , LCD_DATA_REG ) ;
led_func_ptr = led_LCD_driver ;
led_type = LED_HASLCD ;
break ;
case DISPLAY_MODEL_LASI :
LED_DATA_REG = data_reg ;
led_func_ptr = led_LASI_driver ;
printk ( KERN_INFO " LED display at %lx registered \n " , LED_DATA_REG ) ;
led_type = LED_NOLCD ;
break ;
case DISPLAY_MODEL_OLD_ASP :
LED_DATA_REG = data_reg ;
led_func_ptr = led_ASP_driver ;
printk ( KERN_INFO " LED (ASP-style) display at %lx registered \n " ,
LED_DATA_REG ) ;
led_type = LED_NOLCD ;
break ;
default :
printk ( KERN_ERR " %s: Wrong LCD/LED model %d ! \n " ,
__FUNCTION__ , lcd_info . model ) ;
return 1 ;
}
/* mark the LCD/LED driver now as initialized and
* register to the reboot notifier chain */
initialized + + ;
register_reboot_notifier ( & led_notifier ) ;
2005-10-22 06:46:18 +04:00
/* Ensure the work is queued */
if ( led_wq ) {
queue_work ( led_wq , & led_task ) ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
/*
* * register_led_regions ( )
* *
* * register_led_regions ( ) registers the LCD / LED regions for / procfs .
* * At bootup - where the initialisation of the LCD / LED normally happens -
* * not all internal structures of request_region ( ) are properly set up ,
* * so that we delay the led - registration until after busdevices_init ( )
* * has been executed .
* *
*/
void __init register_led_regions ( void )
{
switch ( lcd_info . model ) {
case DISPLAY_MODEL_LCD :
request_mem_region ( ( unsigned long ) LCD_CMD_REG , 1 , " lcd_cmd " ) ;
request_mem_region ( ( unsigned long ) LCD_DATA_REG , 1 , " lcd_data " ) ;
break ;
case DISPLAY_MODEL_LASI :
case DISPLAY_MODEL_OLD_ASP :
request_mem_region ( ( unsigned long ) LED_DATA_REG , 1 , " led_data " ) ;
break ;
}
}
/*
* *
* * lcd_print ( )
* *
* * Displays the given string on the LCD - Display of newer machines .
2005-10-22 06:46:18 +04:00
* * lcd_print ( ) disables / enables the timer - based led work queue to
* * avoid a race condition while writing the CMD / DATA register pair .
2005-04-17 02:20:36 +04:00
* *
*/
int lcd_print ( char * str )
{
int i ;
if ( ! led_func_ptr | | lcd_info . model ! = DISPLAY_MODEL_LCD )
return 0 ;
2005-10-22 06:46:18 +04:00
/* temporarily disable the led work task */
if ( led_wq )
cancel_rearming_delayed_workqueue ( led_wq , & led_task ) ;
2005-04-17 02:20:36 +04:00
/* copy display string to buffer for procfs */
strlcpy ( lcd_text , str , sizeof ( lcd_text ) ) ;
2005-10-22 06:46:18 +04:00
2005-04-17 02:20:36 +04:00
/* Set LCD Cursor to 1st character */
gsc_writeb ( lcd_info . reset_cmd1 , LCD_CMD_REG ) ;
udelay ( lcd_info . min_cmd_delay ) ;
/* Print the string */
for ( i = 0 ; i < lcd_info . lcd_width ; i + + ) {
if ( str & & * str )
gsc_writeb ( * str + + , LCD_DATA_REG ) ;
else
gsc_writeb ( ' ' , LCD_DATA_REG ) ;
udelay ( lcd_info . min_cmd_delay ) ;
}
2005-10-22 06:46:18 +04:00
/* re-queue the work */
if ( led_wq ) {
queue_work ( led_wq , & led_task ) ;
}
2005-04-17 02:20:36 +04:00
return lcd_info . lcd_width ;
}
/*
* * led_init ( )
* *
* * led_init ( ) is called very early in the bootup - process from setup . c
* * and asks the PDC for an usable chassis LCD or LED .
* * If the PDC doesn ' t return any info , then the LED
* * is detected by lasi . c or asp . c and registered with the
* * above functions lasi_led_init ( ) or asp_led_init ( ) .
* * KittyHawk machines have often a buggy PDC , so that
* * we explicitly check for those machines here .
*/
int __init led_init ( void )
{
struct pdc_chassis_info chassis_info ;
int ret ;
snprintf ( lcd_text_default , sizeof ( lcd_text_default ) ,
" Linux %s " , system_utsname . release ) ;
/* Work around the buggy PDC of KittyHawk-machines */
switch ( CPU_HVERSION ) {
case 0x580 : /* KittyHawk DC2-100 (K100) */
case 0x581 : /* KittyHawk DC3-120 (K210) */
case 0x582 : /* KittyHawk DC3 100 (K400) */
case 0x583 : /* KittyHawk DC3 120 (K410) */
case 0x58B : /* KittyHawk DC2 100 (K200) */
printk ( KERN_INFO " %s: KittyHawk-Machine (hversion 0x%x) found, "
" LED detection skipped. \n " , __FILE__ , CPU_HVERSION ) ;
goto found ; /* use the preinitialized values of lcd_info */
}
/* initialize the struct, so that we can check for valid return values */
lcd_info . model = DISPLAY_MODEL_NONE ;
chassis_info . actcnt = chassis_info . maxcnt = 0 ;
ret = pdc_chassis_info ( & chassis_info , & lcd_info , sizeof ( lcd_info ) ) ;
if ( ret = = PDC_OK ) {
DPRINTK ( ( KERN_INFO " %s: chassis info: model=%d (%s), "
" lcd_width=%d, cmd_delay=%u, \n "
" %s: sizecnt=%d, actcnt=%ld, maxcnt=%ld \n " ,
__FILE__ , lcd_info . model ,
( lcd_info . model = = DISPLAY_MODEL_LCD ) ? " LCD " :
( lcd_info . model = = DISPLAY_MODEL_LASI ) ? " LED " : " unknown " ,
lcd_info . lcd_width , lcd_info . min_cmd_delay ,
__FILE__ , sizeof ( lcd_info ) ,
chassis_info . actcnt , chassis_info . maxcnt ) ) ;
DPRINTK ( ( KERN_INFO " %s: cmd=%p, data=%p, reset1=%x, reset2=%x, act_enable=%d \n " ,
__FILE__ , lcd_info . lcd_cmd_reg_addr ,
lcd_info . lcd_data_reg_addr , lcd_info . reset_cmd1 ,
lcd_info . reset_cmd2 , lcd_info . act_enable ) ) ;
/* check the results. Some machines have a buggy PDC */
if ( chassis_info . actcnt < = 0 | | chassis_info . actcnt ! = chassis_info . maxcnt )
goto not_found ;
switch ( lcd_info . model ) {
case DISPLAY_MODEL_LCD : /* LCD display */
if ( chassis_info . actcnt <
offsetof ( struct pdc_chassis_lcd_info_ret_block , _pad ) - 1 )
goto not_found ;
if ( ! lcd_info . act_enable ) {
DPRINTK ( ( KERN_INFO " PDC prohibited usage of the LCD. \n " ) ) ;
goto not_found ;
}
break ;
case DISPLAY_MODEL_NONE : /* no LED or LCD available */
printk ( KERN_INFO " PDC reported no LCD or LED. \n " ) ;
goto not_found ;
case DISPLAY_MODEL_LASI : /* Lasi style 8 bit LED display */
if ( chassis_info . actcnt ! = 8 & & chassis_info . actcnt ! = 32 )
goto not_found ;
break ;
default :
printk ( KERN_WARNING " PDC reported unknown LCD/LED model %d \n " ,
lcd_info . model ) ;
goto not_found ;
} /* switch() */
found :
/* register the LCD/LED driver */
register_led_driver ( lcd_info . model , LCD_CMD_REG , LCD_DATA_REG ) ;
return 0 ;
} else { /* if() */
DPRINTK ( ( KERN_INFO " pdc_chassis_info call failed with retval = %d \n " , ret ) ) ;
}
not_found :
lcd_info . model = DISPLAY_MODEL_NONE ;
return 1 ;
}
# ifdef CONFIG_PROC_FS
module_init ( led_create_procfs )
# endif