linux-old/arch/parisc/kernel/led.c
<<
>>
Prefs
   1/*
   2 *    Chassis LCD/LED driver for HP-PARISC workstations
   3 *
   4 *      (c) Copyright 2000 Red Hat Software
   5 *      (c) Copyright 2000 Helge Deller <hdeller@redhat.com>
   6 *      (c) Copyright 2001-2002 Helge Deller <deller@gmx.de>
   7 *      (c) Copyright 2001 Randolph Chung <tausq@debian.org>
   8 *
   9 *      This program is free software; you can redistribute it and/or modify
  10 *      it under the terms of the GNU General Public License as published by
  11 *      the Free Software Foundation; either version 2 of the License, or
  12 *      (at your option) any later version.
  13 *
  14 * TODO:
  15 *      - speed-up calculations with inlined assembler
  16 *      - interface to write to second row of LCD from /proc
  17 */
  18
  19#include <linux/config.h>
  20#include <linux/module.h>
  21#include <linux/stddef.h>       /* for offsetof() */
  22#include <linux/init.h>
  23#include <linux/types.h>
  24#include <linux/ioport.h>
  25#include <linux/bitops.h>
  26#include <linux/version.h>
  27#include <linux/delay.h>
  28#include <linux/netdevice.h>
  29#include <linux/interrupt.h>
  30#include <linux/kernel_stat.h>
  31#include <linux/reboot.h>
  32#include <linux/proc_fs.h>
  33#include <linux/ctype.h>
  34#include <asm/io.h>
  35#include <asm/gsc.h>
  36#include <asm/processor.h>
  37#include <asm/hardware.h>
  38#include <asm/param.h>          /* HZ */
  39#include <asm/led.h>
  40#include <asm/pdc.h>
  41#include <asm/uaccess.h>
  42
  43/* The control of the LEDs and LCDs on PARISC-machines have to be done 
  44   completely in software. The necessary calculations are done in a tasklet
  45   which is scheduled at every timer interrupt and since the calculations 
  46   may consume relatively much CPU-time some of the calculations can be 
  47   turned off with the following variables (controlled via procfs) */
  48
  49static int led_type = -1;
  50static int led_heartbeat = 1;
  51static int led_diskio = 1;
  52static int led_lanrxtx = 1;
  53static char lcd_text[32];
  54
  55#if 0
  56#define DPRINTK(x)      printk x
  57#else
  58#define DPRINTK(x)
  59#endif
  60
  61
  62#define CALC_ADD(val, comp, add) \
  63 (val<=(comp/8) ? add/16 : val<=(comp/4) ? add/8 : val<=(comp/2) ? add/4 : add)
  64
  65
  66struct lcd_block {
  67        unsigned char command;  /* stores the command byte      */
  68        unsigned char on;       /* value for turning LED on     */
  69        unsigned char off;      /* value for turning LED off    */
  70};
  71
  72/* Structure returned by PDC_RETURN_CHASSIS_INFO */
  73/* NOTE: we use unsigned long:16 two times, since the following member 
  74   lcd_cmd_reg_addr needs to be 64bit aligned on 64bit PA2.0-machines */
  75struct pdc_chassis_lcd_info_ret_block {
  76        unsigned long model:16;         /* DISPLAY_MODEL_XXXX */
  77        unsigned long lcd_width:16;     /* width of the LCD in chars (DISPLAY_MODEL_LCD only) */
  78        char *lcd_cmd_reg_addr;         /* ptr to LCD cmd-register & data ptr for LED */
  79        char *lcd_data_reg_addr;        /* ptr to LCD data-register (LCD only) */
  80        unsigned int min_cmd_delay;     /* delay in uS after cmd-write (LCD only) */
  81        unsigned char reset_cmd1;       /* command #1 for writing LCD string (LCD only) */
  82        unsigned char reset_cmd2;       /* command #2 for writing LCD string (LCD only) */
  83        unsigned char act_enable;       /* 0 = no activity (LCD only) */
  84        struct lcd_block heartbeat;
  85        struct lcd_block disk_io;
  86        struct lcd_block lan_rcv;
  87        struct lcd_block lan_tx;
  88        char _pad;
  89};
  90
  91
  92/* LCD_CMD and LCD_DATA for KittyHawk machines */
  93#define KITTYHAWK_LCD_CMD  (0xfffffffff0190000UL) /* 64bit-ready */
  94#define KITTYHAWK_LCD_DATA (KITTYHAWK_LCD_CMD+1)
  95
  96/* lcd_info is pre-initialized to the values needed to program KittyHawk LCD's 
  97 * HP seems to have used Sharp/Hitachi HD44780 LCDs most of the time. */
  98static struct pdc_chassis_lcd_info_ret_block
  99lcd_info __attribute__((aligned(8))) =
 100{
 101      model:            DISPLAY_MODEL_LCD,
 102      lcd_width:        16,
 103      lcd_cmd_reg_addr: (char *) KITTYHAWK_LCD_CMD,
 104      lcd_data_reg_addr:(char *) KITTYHAWK_LCD_DATA,
 105      min_cmd_delay:    40,
 106      reset_cmd1:       0x80,
 107      reset_cmd2:       0xc0,
 108};
 109
 110
 111/* direct access to some of the lcd_info variables */
 112#define LCD_CMD_REG     lcd_info.lcd_cmd_reg_addr        
 113#define LCD_DATA_REG    lcd_info.lcd_data_reg_addr       
 114#define LED_DATA_REG    lcd_info.lcd_cmd_reg_addr       /* LASI & ASP only */
 115
 116
 117/* ptr to LCD/LED-specific function */
 118static void (*led_func_ptr) (unsigned char);
 119
 120#define LED_HASLCD 1
 121#define LED_NOLCD  0
 122#ifdef CONFIG_PROC_FS
 123static int led_proc_read(char *page, char **start, off_t off, int count, 
 124        int *eof, void *data)
 125{
 126        char *out = page;
 127        int len;
 128
 129        switch ((long)data)
 130        {
 131        case LED_NOLCD:
 132                out += sprintf(out, "Heartbeat: %d\n", led_heartbeat);
 133                out += sprintf(out, "Disk IO: %d\n", led_diskio);
 134                out += sprintf(out, "LAN Rx/Tx: %d\n", led_lanrxtx);
 135                break;
 136        case LED_HASLCD:
 137                out += sprintf(out, "%s\n", lcd_text);
 138                break;
 139        default:
 140                *eof = 1;
 141                return 0;
 142        }
 143
 144        len = out - page - off;
 145        if (len < count) {
 146                *eof = 1;
 147                if (len <= 0) return 0;
 148        } else {
 149                len = count;
 150        }
 151        *start = page + off;
 152        return len;
 153}
 154
 155static int led_proc_write(struct file *file, const char *buf, 
 156        unsigned long count, void *data)
 157{
 158        char *cur, lbuf[count];
 159        int d;
 160
 161        if (!capable(CAP_SYS_ADMIN))
 162                return -EACCES;
 163
 164        memset(lbuf, 0, count);
 165
 166        copy_from_user(lbuf, buf, count);
 167        cur = lbuf;
 168
 169        /* skip initial spaces */
 170        while (*cur && isspace(*cur))
 171        {
 172                cur++;
 173        }
 174
 175        switch ((long)data)
 176        {
 177        case LED_NOLCD:
 178                d = *cur++ - '0';
 179                if (d != 0 && d != 1) goto parse_error;
 180                led_heartbeat = d;
 181
 182                if (*cur++ != ' ') goto parse_error;
 183
 184                d = *cur++ - '0';
 185                if (d != 0 && d != 1) goto parse_error;
 186                led_diskio = d;
 187
 188                if (*cur++ != ' ') goto parse_error;
 189
 190                d = *cur++ - '0';
 191                if (d != 0 && d != 1) goto parse_error;
 192                led_lanrxtx = d;
 193
 194                break;
 195        case LED_HASLCD:
 196                if (*cur == 0) 
 197                {
 198                        /* reset to default */
 199                        lcd_print("Linux " UTS_RELEASE);
 200                }
 201                else
 202                {
 203                        /* chop off trailing \n.. if the user gives multiple
 204                         * \n then it's all their fault.. */
 205                        if (*cur && cur[strlen(cur)-1] == '\n')
 206                                cur[strlen(cur)-1] = 0;
 207                        lcd_print(cur);
 208                }
 209                break;
 210        default:
 211                return 0;
 212        }
 213        
 214        return count;
 215
 216parse_error:
 217        if ((long)data == LED_NOLCD)
 218                printk(KERN_CRIT "Parse error: expect \"n n n\" (n == 0 or 1) for heartbeat,\ndisk io and lan tx/rx indicators\n");
 219        return -EINVAL;
 220}
 221
 222static int __init led_create_procfs(void)
 223{
 224        struct proc_dir_entry *proc_pdc_root = NULL;
 225        struct proc_dir_entry *ent;
 226
 227        if (led_type == -1) return -1;
 228
 229        proc_pdc_root = proc_mkdir("pdc", 0);
 230        if (!proc_pdc_root) return -1;
 231        proc_pdc_root->owner = THIS_MODULE;
 232        ent = create_proc_entry("led", S_IFREG|S_IRUGO|S_IWUSR, proc_pdc_root);
 233        if (!ent) return -1;
 234        ent->nlink = 1;
 235        ent->data = (void *)LED_NOLCD; /* LED */
 236        ent->read_proc = led_proc_read;
 237        ent->write_proc = led_proc_write;
 238        ent->owner = THIS_MODULE;
 239
 240        if (led_type == LED_HASLCD)
 241        {
 242                ent = create_proc_entry("lcd", S_IFREG|S_IRUGO|S_IWUSR, proc_pdc_root);
 243                if (!ent) return -1;
 244                ent->nlink = 1;
 245                ent->data = (void *)LED_HASLCD; /* LCD */
 246                ent->read_proc = led_proc_read;
 247                ent->write_proc = led_proc_write;
 248                ent->owner = THIS_MODULE;
 249        }
 250
 251        return 0;
 252}
 253#endif
 254
 255/*
 256   ** 
 257   ** led_ASP_driver()
 258   ** 
 259 */
 260#define LED_DATA        0x01    /* data to shift (0:on 1:off) */
 261#define LED_STROBE      0x02    /* strobe to clock data */
 262static void led_ASP_driver(unsigned char leds)
 263{
 264        int i;
 265
 266        leds = ~leds;
 267        for (i = 0; i < 8; i++) {
 268                unsigned char value;
 269                value = (leds & 0x80) >> 7;
 270                gsc_writeb( value,               LED_DATA_REG );
 271                gsc_writeb( value | LED_STROBE,  LED_DATA_REG );
 272                leds <<= 1;
 273        }
 274}
 275
 276
 277/*
 278   ** 
 279   ** led_LASI_driver()
 280   ** 
 281 */
 282static void led_LASI_driver(unsigned char leds)
 283{
 284        leds = ~leds;
 285        gsc_writeb( leds, LED_DATA_REG );
 286}
 287
 288
 289/*
 290   ** 
 291   ** led_LCD_driver()
 292   ** 
 293   ** The logic of the LCD driver is, that we write at every scheduled call
 294   ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers.
 295   ** That way we don't need to let this tasklet busywait for min_cmd_delay
 296   ** milliseconds.
 297   **
 298   ** TODO: check the value of "min_cmd_delay" against the value of HZ.
 299   **   
 300 */
 301static void led_LCD_driver(unsigned char leds)
 302{
 303        static int last_index;  /* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */
 304        static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */
 305        struct lcd_block *block_ptr;
 306        int value;
 307
 308        switch (last_index) {
 309            case 0:     block_ptr = &lcd_info.heartbeat;
 310                        value = leds & LED_HEARTBEAT;
 311                        break;
 312            case 1:     block_ptr = &lcd_info.disk_io;
 313                        value = leds & LED_DISK_IO;
 314                        break;                                  
 315            case 2:     block_ptr = &lcd_info.lan_rcv;
 316                        value = leds & LED_LAN_RCV;
 317                        break;                                  
 318            case 3:     block_ptr = &lcd_info.lan_tx;
 319                        value = leds & LED_LAN_TX;
 320                        break;
 321            default:    /* should never happen: */
 322                        return;
 323        }
 324
 325        if (last_was_cmd) {
 326            /* write the value to the LCD data port */
 327            gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG );
 328        } else {
 329            /* write the command-byte to the LCD command register */
 330            gsc_writeb( block_ptr->command, LCD_CMD_REG );
 331        }    
 332        
 333        /* now update the vars for the next interrupt iteration */ 
 334        if (++last_was_cmd == 2) { /* switch between cmd & data */
 335            last_was_cmd = 0;
 336            if (++last_index == 4) 
 337                last_index = 0;  /* switch back to heartbeat index */
 338        }
 339}
 340
 341
 342/*
 343   ** 
 344   ** led_get_net_stats()
 345   ** 
 346   ** calculate the TX- & RX-troughput on the network interfaces in
 347   ** the system for usage in the LED code
 348   **
 349   ** (analog to dev_get_info() from net/core/dev.c)
 350   **   
 351 */
 352static unsigned long led_net_rx_counter, led_net_tx_counter;
 353
 354static void led_get_net_stats(int addvalue)
 355{ 
 356#ifdef CONFIG_NET
 357        static unsigned long rx_total_last, tx_total_last;
 358        unsigned long rx_total, tx_total;
 359        struct net_device *dev;
 360        struct net_device_stats *stats;
 361
 362        rx_total = tx_total = 0;
 363        
 364        /* we are running as a tasklet, so locking dev_base 
 365         * for reading should be OK */
 366        read_lock(&dev_base_lock);
 367        for (dev = dev_base; dev != NULL; dev = dev->next) {
 368            if (dev->get_stats) { 
 369                stats = dev->get_stats(dev);
 370                rx_total += stats->rx_packets;
 371                tx_total += stats->tx_packets;
 372            }
 373        }
 374        read_unlock(&dev_base_lock);
 375
 376        rx_total -= rx_total_last;
 377        tx_total -= tx_total_last;
 378        
 379        if (rx_total)
 380            led_net_rx_counter += CALC_ADD(rx_total, tx_total, addvalue);
 381
 382        if (tx_total)
 383            led_net_tx_counter += CALC_ADD(tx_total, rx_total, addvalue);
 384        
 385        rx_total_last += rx_total;
 386        tx_total_last += tx_total;
 387#endif
 388}
 389
 390
 391/*
 392   ** 
 393   ** led_get_diskio_stats()
 394   ** 
 395   ** calculate the disk-io througput in the system
 396   ** (analog to linux/fs/proc/proc_misc.c)
 397   **   
 398 */
 399static unsigned long led_diskio_counter;
 400
 401static void led_get_diskio_stats(int addvalue)
 402{       
 403        static unsigned int diskio_total_last, diskio_max;
 404        int major, disk, total;
 405        
 406        total = 0;
 407        for (major = 0; major < DK_MAX_MAJOR; major++) {
 408            for (disk = 0; disk < DK_MAX_DISK; disk++)
 409                total += kstat.dk_drive[major][disk];
 410        }
 411        total -= diskio_total_last;
 412        
 413        if (total) {
 414            if (total >= diskio_max) {
 415                led_diskio_counter += addvalue;
 416                diskio_max = total; /* new maximum value found */ 
 417            } else
 418                led_diskio_counter += CALC_ADD(total, diskio_max, addvalue);
 419        }
 420        
 421        diskio_total_last += total; 
 422}
 423
 424
 425
 426/*
 427   ** led_tasklet_func()
 428   ** 
 429   ** is scheduled at every timer interrupt from time.c and
 430   ** updates the chassis LCD/LED 
 431
 432    TODO:
 433    - display load average (older machines like 715/64 have 4 "free" LED's for that)
 434    - optimizations
 435 */
 436
 437static unsigned char currentleds;       /* stores current value of the LEDs */
 438
 439#define HEARTBEAT_LEN (HZ*6/100)
 440#define HEARTBEAT_2ND_RANGE_START (HZ*22/100)
 441#define HEARTBEAT_2ND_RANGE_END   (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
 442
 443static void led_tasklet_func(unsigned long unused)
 444{
 445        static unsigned int count, count_HZ;
 446        static unsigned char lastleds;
 447
 448        /* exit if not initialized */
 449        if (!led_func_ptr)
 450            return;
 451
 452        /* increment the local counters */
 453        ++count;
 454        if (++count_HZ == HZ)
 455            count_HZ = 0;
 456
 457        if (led_heartbeat)
 458        {
 459                /* flash heartbeat-LED like a real heart (2 x short then a long delay) */
 460                if (count_HZ<HEARTBEAT_LEN || 
 461                    (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) 
 462                    currentleds |= LED_HEARTBEAT;
 463                else
 464                    currentleds &= ~LED_HEARTBEAT;
 465        }
 466
 467        /* gather network and diskio statistics and flash LEDs respectively */
 468
 469        if (led_lanrxtx)
 470        {
 471                if ((count & 31) == 0)
 472                        led_get_net_stats(30);
 473
 474                if (led_net_rx_counter) {
 475                        led_net_rx_counter--;
 476                        currentleds |= LED_LAN_RCV;
 477                }
 478                else    
 479                        currentleds &= ~LED_LAN_RCV;
 480
 481                if (led_net_tx_counter) {
 482                        led_net_tx_counter--;
 483                        currentleds |= LED_LAN_TX;
 484                }
 485                else    
 486                        currentleds &= ~LED_LAN_TX;
 487        }
 488
 489        if (led_diskio)
 490        {
 491                /* avoid to calculate diskio-stats at same irq as netio-stats ! */
 492                if ((count & 31) == 15) 
 493                        led_get_diskio_stats(30);
 494
 495                if (led_diskio_counter) {
 496                        led_diskio_counter--;
 497                        currentleds |= LED_DISK_IO;
 498                }
 499                else    
 500                        currentleds &= ~LED_DISK_IO;
 501        }
 502
 503        /* update the LCD/LEDs */
 504        if (currentleds != lastleds) {
 505            led_func_ptr(currentleds);
 506            lastleds = currentleds;
 507        }
 508}
 509
 510/* main led tasklet struct (scheduled from time.c) */
 511DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0);
 512
 513
 514/*
 515   ** led_halt()
 516   ** 
 517   ** called by the reboot notifier chain at shutdown and stops all
 518   ** LED/LCD activities.
 519   ** 
 520 */
 521
 522static int led_halt(struct notifier_block *, unsigned long, void *);
 523
 524static struct notifier_block led_notifier = {
 525        notifier_call: led_halt,
 526};
 527
 528static int led_halt(struct notifier_block *nb, unsigned long event, void *buf) 
 529{
 530        char *txt;
 531        
 532        switch (event) {
 533        case SYS_RESTART:       txt = "SYSTEM RESTART";
 534                                break;
 535        case SYS_HALT:          txt = "SYSTEM HALT";
 536                                break;
 537        case SYS_POWER_OFF:     txt = "SYSTEM POWER OFF";
 538                                break;
 539        default:                return NOTIFY_DONE;
 540        }
 541        
 542        /* completely stop the LED/LCD tasklet */
 543        tasklet_disable(&led_tasklet);
 544
 545        if (lcd_info.model == DISPLAY_MODEL_LCD)
 546                lcd_print(txt);
 547        else
 548                if (led_func_ptr)
 549                        led_func_ptr(0xff); /* turn all LEDs ON */
 550        
 551        unregister_reboot_notifier(&led_notifier);
 552        return NOTIFY_OK;
 553}
 554
 555/*
 556   ** register_led_driver()
 557   ** 
 558   ** registers an external LED or LCD for usage by this driver.
 559   ** currently only LCD-, LASI- and ASP-style LCD/LED's are supported.
 560   ** 
 561 */
 562
 563int __init register_led_driver(int model, char *cmd_reg, char *data_reg)
 564{
 565        static int initialized;
 566        
 567        if (initialized || !data_reg)
 568            return 1;
 569        
 570        lcd_info.model = model;         /* store the values */
 571        LCD_CMD_REG = (cmd_reg == LED_CMD_REG_NONE) ? NULL : cmd_reg;
 572
 573        switch (lcd_info.model) {
 574        case DISPLAY_MODEL_LCD:
 575                LCD_DATA_REG = data_reg;
 576                printk(KERN_INFO "LCD display at %p,%p registered\n", 
 577                        LCD_CMD_REG , LCD_DATA_REG);
 578                led_func_ptr = led_LCD_driver;
 579                lcd_print( "Linux " UTS_RELEASE );
 580                led_type = LED_HASLCD;
 581                break;
 582
 583        case DISPLAY_MODEL_LASI:
 584                LED_DATA_REG = data_reg;
 585                led_func_ptr = led_LASI_driver;
 586                printk(KERN_INFO "LED display at %p registered\n", LED_DATA_REG);
 587                led_type = LED_NOLCD;
 588                break;
 589
 590        case DISPLAY_MODEL_OLD_ASP:
 591                LED_DATA_REG = data_reg;
 592                led_func_ptr = led_ASP_driver;
 593                printk(KERN_INFO "LED (ASP-style) display at %p registered\n", 
 594                    LED_DATA_REG);
 595                led_type = LED_NOLCD;
 596                break;
 597
 598        default:
 599                printk(KERN_ERR "%s: Wrong LCD/LED model %d !\n",
 600                       __FUNCTION__, lcd_info.model);
 601                return 1;
 602        }
 603        
 604        /* mark the LCD/LED driver now as initialized and 
 605         * register to the reboot notifier chain */
 606        initialized++;
 607        register_reboot_notifier(&led_notifier);
 608
 609        /* start the led tasklet for the first time */
 610        tasklet_enable(&led_tasklet);
 611        
 612        return 0;
 613}
 614
 615/*
 616   ** register_led_regions()
 617   ** 
 618   ** register_led_regions() registers the LCD/LED regions for /procfs.
 619   ** At bootup - where the initialisation of the LCD/LED normally happens - 
 620   ** not all internal structures of request_region() are properly set up,
 621   ** so that we delay the led-registration until after busdevices_init() 
 622   ** has been executed.
 623   **
 624 */
 625
 626void __init register_led_regions(void)
 627{
 628        switch (lcd_info.model) {
 629        case DISPLAY_MODEL_LCD:
 630                request_mem_region((unsigned long)LCD_CMD_REG,  1, "lcd_cmd");
 631                request_mem_region((unsigned long)LCD_DATA_REG, 1, "lcd_data");
 632                break;
 633        case DISPLAY_MODEL_LASI:
 634        case DISPLAY_MODEL_OLD_ASP:
 635                request_mem_region((unsigned long)LED_DATA_REG, 1, "led_data");
 636                break;
 637        }
 638}
 639
 640
 641/*
 642   ** 
 643   ** lcd_print()
 644   ** 
 645   ** Displays the given string on the LCD-Display of newer machines.
 646   ** lcd_print() disables the timer-based led tasklet during its 
 647   ** execution and enables it afterwards again.
 648   **
 649 */
 650int lcd_print( char *str )
 651{
 652        int i;
 653
 654        if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
 655            return 0;
 656        
 657        /* temporarily disable the led tasklet */
 658        tasklet_disable(&led_tasklet);
 659
 660        /* copy display string to buffer for procfs */
 661        strncpy(lcd_text, str, sizeof(lcd_text)-1);
 662        
 663        /* Set LCD Cursor to 1st character */
 664        gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG);
 665        udelay(lcd_info.min_cmd_delay);
 666
 667        /* Print the string */
 668        for (i=0; i < lcd_info.lcd_width; i++) {
 669            if (str && *str)
 670                gsc_writeb(*str++, LCD_DATA_REG);
 671            else
 672                gsc_writeb(' ', LCD_DATA_REG);
 673            udelay(lcd_info.min_cmd_delay);
 674        }
 675        
 676        /* re-enable the led tasklet */
 677        tasklet_enable(&led_tasklet);
 678
 679        return lcd_info.lcd_width;
 680}
 681
 682/*
 683   ** led_init()
 684   ** 
 685   ** led_init() is called very early in the bootup-process from setup.c 
 686   ** and asks the PDC for an usable chassis LCD or LED.
 687   ** If the PDC doesn't return any info, then the LED
 688   ** is detected by lasi.c or asp.c and registered with the
 689   ** above functions lasi_led_init() or asp_led_init().
 690   ** KittyHawk machines have often a buggy PDC, so that
 691   ** we explicitly check for those machines here.
 692 */
 693
 694int __init led_init(void)
 695{
 696        struct pdc_chassis_info chassis_info;
 697        int ret;
 698
 699        /* Work around the buggy PDC of KittyHawk-machines */
 700        switch (CPU_HVERSION) {
 701        case 0x580:             /* KittyHawk DC2-100 (K100) */
 702        case 0x581:             /* KittyHawk DC3-120 (K210) */
 703        case 0x582:             /* KittyHawk DC3 100 (K400) */
 704        case 0x583:             /* KittyHawk DC3 120 (K410) */
 705        case 0x58B:             /* KittyHawk DC2 100 (K200) */
 706                printk(KERN_INFO "%s: KittyHawk-Machine (hversion 0x%x) found, "
 707                                "LED detection skipped.\n", __FILE__, CPU_HVERSION);
 708                goto found;     /* use the preinitialized values of lcd_info */
 709        }
 710
 711        /* initialize the struct, so that we can check for valid return values */
 712        lcd_info.model = DISPLAY_MODEL_NONE;
 713        chassis_info.actcnt = chassis_info.maxcnt = 0;
 714
 715        if ((ret = pdc_chassis_info(&chassis_info, &lcd_info, sizeof(lcd_info))) == PDC_OK) {
 716                DPRINTK((KERN_INFO "%s: chassis info: model=%d (%s), "
 717                         "lcd_width=%d, cmd_delay=%u,\n"
 718                         "%s: sizecnt=%d, actcnt=%ld, maxcnt=%ld\n",
 719                         __FILE__, lcd_info.model,
 720                         (lcd_info.model==DISPLAY_MODEL_LCD) ? "LCD" :
 721                          (lcd_info.model==DISPLAY_MODEL_LASI) ? "LED" : "unknown",
 722                         lcd_info.lcd_width, lcd_info.min_cmd_delay,
 723                         __FILE__, sizeof(lcd_info), 
 724                         chassis_info.actcnt, chassis_info.maxcnt));
 725                DPRINTK((KERN_INFO "%s: cmd=%p, data=%p, reset1=%x, reset2=%x, act_enable=%d\n",
 726                        __FILE__, lcd_info.lcd_cmd_reg_addr, 
 727                        lcd_info.lcd_data_reg_addr, lcd_info.reset_cmd1,  
 728                        lcd_info.reset_cmd2, lcd_info.act_enable ));
 729        
 730                /* check the results. Some machines have a buggy PDC */
 731                if (chassis_info.actcnt <= 0 || chassis_info.actcnt != chassis_info.maxcnt)
 732                        goto not_found;
 733
 734                switch (lcd_info.model) {
 735                case DISPLAY_MODEL_LCD:         /* LCD display */
 736                        if (chassis_info.actcnt < 
 737                                offsetof(struct pdc_chassis_lcd_info_ret_block, _pad)-1)
 738                                goto not_found;
 739                        if (!lcd_info.act_enable) {
 740                                DPRINTK((KERN_INFO "PDC prohibited usage of the LCD.\n"));
 741                                goto not_found;
 742                        }
 743                        break;
 744
 745                case DISPLAY_MODEL_NONE:        /* no LED or LCD available */
 746                        printk(KERN_INFO "PDC reported no LCD or LED.\n");
 747                        goto not_found;
 748
 749                case DISPLAY_MODEL_LASI:        /* Lasi style 8 bit LED display */
 750                        if (chassis_info.actcnt != 8 && chassis_info.actcnt != 32)
 751                                goto not_found;
 752                        break;
 753
 754                default:
 755                        printk(KERN_WARNING "PDC reported unknown LCD/LED model %d\n",
 756                               lcd_info.model);
 757                        goto not_found;
 758                } /* switch() */
 759
 760found:
 761                /* register the LCD/LED driver */
 762                register_led_driver(lcd_info.model, LCD_CMD_REG, LCD_DATA_REG);
 763                return 0;
 764
 765        } else { /* if() */
 766                DPRINTK((KERN_INFO "pdc_chassis_info call failed with retval = %d\n", ret));
 767        }
 768
 769not_found:
 770        lcd_info.model = DISPLAY_MODEL_NONE;
 771        return 1;
 772}
 773
 774#ifdef CONFIG_PROC_FS
 775module_init(led_create_procfs)
 776#endif
 777
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.