diff options
Diffstat (limited to 'drivers/parisc/led.c')
| -rw-r--r-- | drivers/parisc/led.c | 225 |
1 files changed, 112 insertions, 113 deletions
diff --git a/drivers/parisc/led.c b/drivers/parisc/led.c index 286902298e33..95bd07b8b61b 100644 --- a/drivers/parisc/led.c +++ b/drivers/parisc/led.c | |||
| @@ -18,6 +18,9 @@ | |||
| 18 | * Changes: | 18 | * Changes: |
| 19 | * - Audit copy_from_user in led_proc_write. | 19 | * - Audit copy_from_user in led_proc_write. |
| 20 | * Daniele Bellucci <bellucda@tiscali.it> | 20 | * Daniele Bellucci <bellucda@tiscali.it> |
| 21 | * - Switch from using a tasklet to a work queue, so the led_LCD_driver | ||
| 22 | * can sleep. | ||
| 23 | * David Pye <dmp@davidmpye.dyndns.org> | ||
| 21 | */ | 24 | */ |
| 22 | 25 | ||
| 23 | #include <linux/config.h> | 26 | #include <linux/config.h> |
| @@ -37,6 +40,7 @@ | |||
| 37 | #include <linux/proc_fs.h> | 40 | #include <linux/proc_fs.h> |
| 38 | #include <linux/ctype.h> | 41 | #include <linux/ctype.h> |
| 39 | #include <linux/blkdev.h> | 42 | #include <linux/blkdev.h> |
| 43 | #include <linux/workqueue.h> | ||
| 40 | #include <linux/rcupdate.h> | 44 | #include <linux/rcupdate.h> |
| 41 | #include <asm/io.h> | 45 | #include <asm/io.h> |
| 42 | #include <asm/processor.h> | 46 | #include <asm/processor.h> |
| @@ -47,25 +51,30 @@ | |||
| 47 | #include <asm/uaccess.h> | 51 | #include <asm/uaccess.h> |
| 48 | 52 | ||
| 49 | /* The control of the LEDs and LCDs on PARISC-machines have to be done | 53 | /* The control of the LEDs and LCDs on PARISC-machines have to be done |
| 50 | completely in software. The necessary calculations are done in a tasklet | 54 | completely in software. The necessary calculations are done in a work queue |
| 51 | which is scheduled at every timer interrupt and since the calculations | 55 | task which is scheduled regularly, and since the calculations may consume a |
| 52 | may consume relatively much CPU-time some of the calculations can be | 56 | relatively large amount of CPU time, some of the calculations can be |
| 53 | turned off with the following variables (controlled via procfs) */ | 57 | turned off with the following variables (controlled via procfs) */ |
| 54 | 58 | ||
| 55 | static int led_type = -1; | 59 | static int led_type = -1; |
| 56 | static int led_heartbeat = 1; | 60 | static unsigned char lastleds; /* LED state from most recent update */ |
| 57 | static int led_diskio = 1; | 61 | static unsigned int led_heartbeat = 1; |
| 58 | static int led_lanrxtx = 1; | 62 | static unsigned int led_diskio = 1; |
| 63 | static unsigned int led_lanrxtx = 1; | ||
| 59 | static char lcd_text[32]; | 64 | static char lcd_text[32]; |
| 60 | static char lcd_text_default[32]; | 65 | static char lcd_text_default[32]; |
| 61 | 66 | ||
| 67 | |||
| 68 | static struct workqueue_struct *led_wq; | ||
| 69 | static void led_work_func(void *); | ||
| 70 | static DECLARE_WORK(led_task, led_work_func, NULL); | ||
| 71 | |||
| 62 | #if 0 | 72 | #if 0 |
| 63 | #define DPRINTK(x) printk x | 73 | #define DPRINTK(x) printk x |
| 64 | #else | 74 | #else |
| 65 | #define DPRINTK(x) | 75 | #define DPRINTK(x) |
| 66 | #endif | 76 | #endif |
| 67 | 77 | ||
| 68 | |||
| 69 | struct lcd_block { | 78 | struct lcd_block { |
| 70 | unsigned char command; /* stores the command byte */ | 79 | unsigned char command; /* stores the command byte */ |
| 71 | unsigned char on; /* value for turning LED on */ | 80 | unsigned char on; /* value for turning LED on */ |
| @@ -116,12 +125,27 @@ lcd_info __attribute__((aligned(8))) = | |||
| 116 | #define LCD_DATA_REG lcd_info.lcd_data_reg_addr | 125 | #define LCD_DATA_REG lcd_info.lcd_data_reg_addr |
| 117 | #define LED_DATA_REG lcd_info.lcd_cmd_reg_addr /* LASI & ASP only */ | 126 | #define LED_DATA_REG lcd_info.lcd_cmd_reg_addr /* LASI & ASP only */ |
| 118 | 127 | ||
| 128 | #define LED_HASLCD 1 | ||
| 129 | #define LED_NOLCD 0 | ||
| 130 | |||
| 131 | /* The workqueue must be created at init-time */ | ||
| 132 | static int start_task(void) | ||
| 133 | { | ||
| 134 | /* Display the default text now */ | ||
| 135 | if (led_type == LED_HASLCD) lcd_print( lcd_text_default ); | ||
| 136 | |||
| 137 | /* Create the work queue and queue the LED task */ | ||
| 138 | led_wq = create_singlethread_workqueue("led_wq"); | ||
| 139 | queue_work(led_wq, &led_task); | ||
| 140 | |||
| 141 | return 0; | ||
| 142 | } | ||
| 143 | |||
| 144 | device_initcall(start_task); | ||
| 119 | 145 | ||
| 120 | /* ptr to LCD/LED-specific function */ | 146 | /* ptr to LCD/LED-specific function */ |
| 121 | static void (*led_func_ptr) (unsigned char); | 147 | static void (*led_func_ptr) (unsigned char); |
| 122 | 148 | ||
| 123 | #define LED_HASLCD 1 | ||
| 124 | #define LED_NOLCD 0 | ||
| 125 | #ifdef CONFIG_PROC_FS | 149 | #ifdef CONFIG_PROC_FS |
| 126 | static int led_proc_read(char *page, char **start, off_t off, int count, | 150 | static int led_proc_read(char *page, char **start, off_t off, int count, |
| 127 | int *eof, void *data) | 151 | int *eof, void *data) |
| @@ -286,52 +310,35 @@ static void led_LASI_driver(unsigned char leds) | |||
| 286 | /* | 310 | /* |
| 287 | ** | 311 | ** |
| 288 | ** led_LCD_driver() | 312 | ** led_LCD_driver() |
| 289 | ** | ||
| 290 | ** The logic of the LCD driver is, that we write at every scheduled call | ||
| 291 | ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers. | ||
| 292 | ** That way we don't need to let this tasklet busywait for min_cmd_delay | ||
| 293 | ** milliseconds. | ||
| 294 | ** | ||
| 295 | ** TODO: check the value of "min_cmd_delay" against the value of HZ. | ||
| 296 | ** | 313 | ** |
| 297 | */ | 314 | */ |
| 298 | static void led_LCD_driver(unsigned char leds) | 315 | static void led_LCD_driver(unsigned char leds) |
| 299 | { | 316 | { |
| 300 | static int last_index; /* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */ | 317 | static int i; |
| 301 | static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */ | 318 | static unsigned char mask[4] = { LED_HEARTBEAT, LED_DISK_IO, |
| 302 | struct lcd_block *block_ptr; | 319 | LED_LAN_RCV, LED_LAN_TX }; |
| 303 | int value; | ||
| 304 | |||
| 305 | switch (last_index) { | ||
| 306 | case 0: block_ptr = &lcd_info.heartbeat; | ||
| 307 | value = leds & LED_HEARTBEAT; | ||
| 308 | break; | ||
| 309 | case 1: block_ptr = &lcd_info.disk_io; | ||
| 310 | value = leds & LED_DISK_IO; | ||
| 311 | break; | ||
| 312 | case 2: block_ptr = &lcd_info.lan_rcv; | ||
| 313 | value = leds & LED_LAN_RCV; | ||
| 314 | break; | ||
| 315 | case 3: block_ptr = &lcd_info.lan_tx; | ||
| 316 | value = leds & LED_LAN_TX; | ||
| 317 | break; | ||
| 318 | default: /* should never happen: */ | ||
| 319 | return; | ||
| 320 | } | ||
| 321 | |||
| 322 | if (last_was_cmd) { | ||
| 323 | /* write the value to the LCD data port */ | ||
| 324 | gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG ); | ||
| 325 | } else { | ||
| 326 | /* write the command-byte to the LCD command register */ | ||
| 327 | gsc_writeb( block_ptr->command, LCD_CMD_REG ); | ||
| 328 | } | ||
| 329 | 320 | ||
| 330 | /* now update the vars for the next interrupt iteration */ | 321 | static struct lcd_block * blockp[4] = { |
| 331 | if (++last_was_cmd == 2) { /* switch between cmd & data */ | 322 | &lcd_info.heartbeat, |
| 332 | last_was_cmd = 0; | 323 | &lcd_info.disk_io, |
| 333 | if (++last_index == 4) | 324 | &lcd_info.lan_rcv, |
| 334 | last_index = 0; /* switch back to heartbeat index */ | 325 | &lcd_info.lan_tx |
| 326 | }; | ||
| 327 | |||
| 328 | /* Convert min_cmd_delay to milliseconds */ | ||
| 329 | unsigned int msec_cmd_delay = 1 + (lcd_info.min_cmd_delay / 1000); | ||
| 330 | |||
| 331 | for (i=0; i<4; ++i) | ||
| 332 | { | ||
| 333 | if ((leds & mask[i]) != (lastleds & mask[i])) | ||
| 334 | { | ||
| 335 | gsc_writeb( blockp[i]->command, LCD_CMD_REG ); | ||
| 336 | msleep(msec_cmd_delay); | ||
| 337 | |||
| 338 | gsc_writeb( leds & mask[i] ? blockp[i]->on : | ||
| 339 | blockp[i]->off, LCD_DATA_REG ); | ||
| 340 | msleep(msec_cmd_delay); | ||
| 341 | } | ||
| 335 | } | 342 | } |
| 336 | } | 343 | } |
| 337 | 344 | ||
| @@ -356,7 +363,7 @@ static __inline__ int led_get_net_activity(void) | |||
| 356 | 363 | ||
| 357 | rx_total = tx_total = 0; | 364 | rx_total = tx_total = 0; |
| 358 | 365 | ||
| 359 | /* we are running as tasklet, so locking dev_base | 366 | /* we are running as a workqueue task, so locking dev_base |
| 360 | * for reading should be OK */ | 367 | * for reading should be OK */ |
| 361 | read_lock(&dev_base_lock); | 368 | read_lock(&dev_base_lock); |
| 362 | rcu_read_lock(); | 369 | rcu_read_lock(); |
| @@ -405,7 +412,7 @@ static __inline__ int led_get_diskio_activity(void) | |||
| 405 | static unsigned long last_pgpgin, last_pgpgout; | 412 | static unsigned long last_pgpgin, last_pgpgout; |
| 406 | struct page_state pgstat; | 413 | struct page_state pgstat; |
| 407 | int changed; | 414 | int changed; |
| 408 | 415 | ||
| 409 | get_full_page_state(&pgstat); /* get no of sectors in & out */ | 416 | get_full_page_state(&pgstat); /* get no of sectors in & out */ |
| 410 | 417 | ||
| 411 | /* Just use a very simple calculation here. Do not care about overflow, | 418 | /* Just use a very simple calculation here. Do not care about overflow, |
| @@ -413,86 +420,70 @@ static __inline__ int led_get_diskio_activity(void) | |||
| 413 | changed = (pgstat.pgpgin != last_pgpgin) || (pgstat.pgpgout != last_pgpgout); | 420 | changed = (pgstat.pgpgin != last_pgpgin) || (pgstat.pgpgout != last_pgpgout); |
| 414 | last_pgpgin = pgstat.pgpgin; | 421 | last_pgpgin = pgstat.pgpgin; |
| 415 | last_pgpgout = pgstat.pgpgout; | 422 | last_pgpgout = pgstat.pgpgout; |
| 416 | 423 | ||
| 417 | return (changed ? LED_DISK_IO : 0); | 424 | return (changed ? LED_DISK_IO : 0); |
| 418 | } | 425 | } |
| 419 | 426 | ||
| 420 | 427 | ||
| 421 | 428 | ||
| 422 | /* | 429 | /* |
| 423 | ** led_tasklet_func() | 430 | ** led_work_func() |
| 424 | ** | 431 | ** |
| 425 | ** is scheduled at every timer interrupt from time.c and | 432 | ** manages when and which chassis LCD/LED gets updated |
| 426 | ** updates the chassis LCD/LED | ||
| 427 | 433 | ||
| 428 | TODO: | 434 | TODO: |
| 429 | - display load average (older machines like 715/64 have 4 "free" LED's for that) | 435 | - display load average (older machines like 715/64 have 4 "free" LED's for that) |
| 430 | - optimizations | 436 | - optimizations |
| 431 | */ | 437 | */ |
| 432 | 438 | ||
| 433 | #define HEARTBEAT_LEN (HZ*6/100) | 439 | #define HEARTBEAT_LEN (HZ*10/100) |
| 434 | #define HEARTBEAT_2ND_RANGE_START (HZ*22/100) | 440 | #define HEARTBEAT_2ND_RANGE_START (HZ*28/100) |
| 435 | #define HEARTBEAT_2ND_RANGE_END (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN) | 441 | #define HEARTBEAT_2ND_RANGE_END (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN) |
| 436 | 442 | ||
| 437 | #define NORMALIZED_COUNT(count) (count/(HZ/100)) | 443 | #define LED_UPDATE_INTERVAL (1 + (HZ*19/1000)) |
| 438 | 444 | ||
| 439 | static void led_tasklet_func(unsigned long unused) | 445 | static void led_work_func (void *unused) |
| 440 | { | 446 | { |
| 441 | static unsigned char lastleds; | 447 | static unsigned long last_jiffies; |
| 442 | unsigned char currentleds; /* stores current value of the LEDs */ | ||
| 443 | static unsigned long count; /* static incremented value, not wrapped */ | ||
| 444 | static unsigned long count_HZ; /* counter in range 0..HZ */ | 448 | static unsigned long count_HZ; /* counter in range 0..HZ */ |
| 449 | unsigned char currentleds = 0; /* stores current value of the LEDs */ | ||
| 445 | 450 | ||
| 446 | /* exit if not initialized */ | 451 | /* exit if not initialized */ |
| 447 | if (!led_func_ptr) | 452 | if (!led_func_ptr) |
| 448 | return; | 453 | return; |
| 449 | 454 | ||
| 450 | /* increment the local counters */ | 455 | /* increment the heartbeat timekeeper */ |
| 451 | ++count; | 456 | count_HZ += jiffies - last_jiffies; |
| 452 | if (++count_HZ == HZ) | 457 | last_jiffies = jiffies; |
| 458 | if (count_HZ >= HZ) | ||
| 453 | count_HZ = 0; | 459 | count_HZ = 0; |
| 454 | 460 | ||
| 455 | currentleds = lastleds; | 461 | if (likely(led_heartbeat)) |
| 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 | /* look for network activity and flash LEDs respectively */ | ||
| 468 | if (led_lanrxtx && ((NORMALIZED_COUNT(count)+(8/2)) & 7) == 0) | ||
| 469 | { | 462 | { |
| 470 | currentleds &= ~(LED_LAN_RCV | LED_LAN_TX); | 463 | /* flash heartbeat-LED like a real heart |
| 471 | currentleds |= led_get_net_activity(); | 464 | * (2 x short then a long delay) |
| 465 | */ | ||
| 466 | if (count_HZ < HEARTBEAT_LEN || | ||
| 467 | (count_HZ >= HEARTBEAT_2ND_RANGE_START && | ||
| 468 | count_HZ < HEARTBEAT_2ND_RANGE_END)) | ||
| 469 | currentleds |= LED_HEARTBEAT; | ||
| 472 | } | 470 | } |
| 473 | 471 | ||
| 474 | /* avoid to calculate diskio-stats at same irq as netio-stats */ | 472 | if (likely(led_lanrxtx)) currentleds |= led_get_net_activity(); |
| 475 | if (led_diskio && (NORMALIZED_COUNT(count) & 7) == 0) | 473 | if (likely(led_diskio)) currentleds |= led_get_diskio_activity(); |
| 476 | { | ||
| 477 | currentleds &= ~LED_DISK_IO; | ||
| 478 | currentleds |= led_get_diskio_activity(); | ||
| 479 | } | ||
| 480 | 474 | ||
| 481 | /* blink all LEDs twice a second if we got an Oops (HPMC) */ | 475 | /* blink all LEDs twice a second if we got an Oops (HPMC) */ |
| 482 | if (oops_in_progress) { | 476 | if (unlikely(oops_in_progress)) |
| 483 | currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff; | 477 | currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff; |
| 484 | } | ||
| 485 | |||
| 486 | /* update the LCD/LEDs */ | ||
| 487 | if (currentleds != lastleds) { | ||
| 488 | led_func_ptr(currentleds); | ||
| 489 | lastleds = currentleds; | ||
| 490 | } | ||
| 491 | } | ||
| 492 | 478 | ||
| 493 | /* main led tasklet struct (scheduled from time.c) */ | 479 | if (currentleds != lastleds) |
| 494 | DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0); | 480 | { |
| 481 | led_func_ptr(currentleds); /* Update the LCD/LEDs */ | ||
| 482 | lastleds = currentleds; | ||
| 483 | } | ||
| 495 | 484 | ||
| 485 | queue_delayed_work(led_wq, &led_task, LED_UPDATE_INTERVAL); | ||
| 486 | } | ||
| 496 | 487 | ||
| 497 | /* | 488 | /* |
| 498 | ** led_halt() | 489 | ** led_halt() |
| @@ -522,9 +513,13 @@ static int led_halt(struct notifier_block *nb, unsigned long event, void *buf) | |||
| 522 | default: return NOTIFY_DONE; | 513 | default: return NOTIFY_DONE; |
| 523 | } | 514 | } |
| 524 | 515 | ||
| 525 | /* completely stop the LED/LCD tasklet */ | 516 | /* Cancel the work item and delete the queue */ |
| 526 | tasklet_disable(&led_tasklet); | 517 | if (led_wq) { |
| 527 | 518 | cancel_rearming_delayed_workqueue(led_wq, &led_task); | |
| 519 | destroy_workqueue(led_wq); | ||
| 520 | led_wq = NULL; | ||
| 521 | } | ||
| 522 | |||
| 528 | if (lcd_info.model == DISPLAY_MODEL_LCD) | 523 | if (lcd_info.model == DISPLAY_MODEL_LCD) |
| 529 | lcd_print(txt); | 524 | lcd_print(txt); |
| 530 | else | 525 | else |
| @@ -559,7 +554,6 @@ int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long d | |||
| 559 | printk(KERN_INFO "LCD display at %lx,%lx registered\n", | 554 | printk(KERN_INFO "LCD display at %lx,%lx registered\n", |
| 560 | LCD_CMD_REG , LCD_DATA_REG); | 555 | LCD_CMD_REG , LCD_DATA_REG); |
| 561 | led_func_ptr = led_LCD_driver; | 556 | led_func_ptr = led_LCD_driver; |
| 562 | lcd_print( lcd_text_default ); | ||
| 563 | led_type = LED_HASLCD; | 557 | led_type = LED_HASLCD; |
| 564 | break; | 558 | break; |
| 565 | 559 | ||
| @@ -589,9 +583,11 @@ int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long d | |||
| 589 | initialized++; | 583 | initialized++; |
| 590 | register_reboot_notifier(&led_notifier); | 584 | register_reboot_notifier(&led_notifier); |
| 591 | 585 | ||
| 592 | /* start the led tasklet for the first time */ | 586 | /* Ensure the work is queued */ |
| 593 | tasklet_enable(&led_tasklet); | 587 | if (led_wq) { |
| 594 | 588 | queue_work(led_wq, &led_task); | |
| 589 | } | ||
| 590 | |||
| 595 | return 0; | 591 | return 0; |
| 596 | } | 592 | } |
| 597 | 593 | ||
| @@ -626,8 +622,8 @@ void __init register_led_regions(void) | |||
| 626 | ** lcd_print() | 622 | ** lcd_print() |
| 627 | ** | 623 | ** |
| 628 | ** Displays the given string on the LCD-Display of newer machines. | 624 | ** Displays the given string on the LCD-Display of newer machines. |
| 629 | ** lcd_print() disables the timer-based led tasklet during its | 625 | ** lcd_print() disables/enables the timer-based led work queue to |
| 630 | ** execution and enables it afterwards again. | 626 | ** avoid a race condition while writing the CMD/DATA register pair. |
| 631 | ** | 627 | ** |
| 632 | */ | 628 | */ |
| 633 | int lcd_print( char *str ) | 629 | int lcd_print( char *str ) |
| @@ -637,12 +633,13 @@ int lcd_print( char *str ) | |||
| 637 | if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD) | 633 | if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD) |
| 638 | return 0; | 634 | return 0; |
| 639 | 635 | ||
| 640 | /* temporarily disable the led tasklet */ | 636 | /* temporarily disable the led work task */ |
| 641 | tasklet_disable(&led_tasklet); | 637 | if (led_wq) |
| 638 | cancel_rearming_delayed_workqueue(led_wq, &led_task); | ||
| 642 | 639 | ||
| 643 | /* copy display string to buffer for procfs */ | 640 | /* copy display string to buffer for procfs */ |
| 644 | strlcpy(lcd_text, str, sizeof(lcd_text)); | 641 | strlcpy(lcd_text, str, sizeof(lcd_text)); |
| 645 | 642 | ||
| 646 | /* Set LCD Cursor to 1st character */ | 643 | /* Set LCD Cursor to 1st character */ |
| 647 | gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG); | 644 | gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG); |
| 648 | udelay(lcd_info.min_cmd_delay); | 645 | udelay(lcd_info.min_cmd_delay); |
| @@ -656,8 +653,10 @@ int lcd_print( char *str ) | |||
| 656 | udelay(lcd_info.min_cmd_delay); | 653 | udelay(lcd_info.min_cmd_delay); |
| 657 | } | 654 | } |
| 658 | 655 | ||
| 659 | /* re-enable the led tasklet */ | 656 | /* re-queue the work */ |
| 660 | tasklet_enable(&led_tasklet); | 657 | if (led_wq) { |
| 658 | queue_work(led_wq, &led_task); | ||
| 659 | } | ||
| 661 | 660 | ||
| 662 | return lcd_info.lcd_width; | 661 | return lcd_info.lcd_width; |
| 663 | } | 662 | } |
