162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Chassis LCD/LED driver for HP-PARISC workstations 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (c) Copyright 2000 Red Hat Software 662306a36Sopenharmony_ci * (c) Copyright 2000 Helge Deller <hdeller@redhat.com> 762306a36Sopenharmony_ci * (c) Copyright 2001 Randolph Chung <tausq@debian.org> 862306a36Sopenharmony_ci * (c) Copyright 2000-2023 Helge Deller <deller@gmx.de> 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * The control of the LEDs and LCDs on PARISC machines has to be done 1162306a36Sopenharmony_ci * completely in software. 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * The LEDs can be configured at runtime in /sys/class/leds/ 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/init.h> 1862306a36Sopenharmony_ci#include <linux/types.h> 1962306a36Sopenharmony_ci#include <linux/ioport.h> 2062306a36Sopenharmony_ci#include <linux/utsname.h> 2162306a36Sopenharmony_ci#include <linux/capability.h> 2262306a36Sopenharmony_ci#include <linux/delay.h> 2362306a36Sopenharmony_ci#include <linux/reboot.h> 2462306a36Sopenharmony_ci#include <linux/uaccess.h> 2562306a36Sopenharmony_ci#include <linux/leds.h> 2662306a36Sopenharmony_ci#include <linux/platform_device.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include <asm/io.h> 2962306a36Sopenharmony_ci#include <asm/processor.h> 3062306a36Sopenharmony_ci#include <asm/hardware.h> 3162306a36Sopenharmony_ci#include <asm/param.h> /* HZ */ 3262306a36Sopenharmony_ci#include <asm/led.h> 3362306a36Sopenharmony_ci#include <asm/pdc.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define LED_HAS_LCD 1 3662306a36Sopenharmony_ci#define LED_HAS_LED 2 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic unsigned char led_type; /* bitmask of LED_HAS_XXX */ 3962306a36Sopenharmony_cistatic unsigned char lastleds; /* LED state from most recent update */ 4062306a36Sopenharmony_cistatic unsigned char lcd_new_text; 4162306a36Sopenharmony_cistatic unsigned char lcd_text[20]; 4262306a36Sopenharmony_cistatic unsigned char lcd_text_default[20]; 4362306a36Sopenharmony_cistatic unsigned char lcd_no_led_support; /* KittyHawk doesn't support LED on its LCD */ 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistruct lcd_block { 4662306a36Sopenharmony_ci unsigned char command; /* stores the command byte */ 4762306a36Sopenharmony_ci unsigned char on; /* value for turning LED on */ 4862306a36Sopenharmony_ci unsigned char off; /* value for turning LED off */ 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci/* Structure returned by PDC_RETURN_CHASSIS_INFO */ 5262306a36Sopenharmony_ci/* NOTE: we use unsigned long:16 two times, since the following member 5362306a36Sopenharmony_ci lcd_cmd_reg_addr needs to be 64bit aligned on 64bit PA2.0-machines */ 5462306a36Sopenharmony_cistruct pdc_chassis_lcd_info_ret_block { 5562306a36Sopenharmony_ci unsigned long model:16; /* DISPLAY_MODEL_XXXX */ 5662306a36Sopenharmony_ci unsigned long lcd_width:16; /* width of the LCD in chars (DISPLAY_MODEL_LCD only) */ 5762306a36Sopenharmony_ci unsigned long lcd_cmd_reg_addr; /* ptr to LCD cmd-register & data ptr for LED */ 5862306a36Sopenharmony_ci unsigned long lcd_data_reg_addr; /* ptr to LCD data-register (LCD only) */ 5962306a36Sopenharmony_ci unsigned int min_cmd_delay; /* delay in uS after cmd-write (LCD only) */ 6062306a36Sopenharmony_ci unsigned char reset_cmd1; /* command #1 for writing LCD string (LCD only) */ 6162306a36Sopenharmony_ci unsigned char reset_cmd2; /* command #2 for writing LCD string (LCD only) */ 6262306a36Sopenharmony_ci unsigned char act_enable; /* 0 = no activity (LCD only) */ 6362306a36Sopenharmony_ci struct lcd_block heartbeat; 6462306a36Sopenharmony_ci struct lcd_block disk_io; 6562306a36Sopenharmony_ci struct lcd_block lan_rcv; 6662306a36Sopenharmony_ci struct lcd_block lan_tx; 6762306a36Sopenharmony_ci char _pad; 6862306a36Sopenharmony_ci}; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/* LCD_CMD and LCD_DATA for KittyHawk machines */ 7262306a36Sopenharmony_ci#define KITTYHAWK_LCD_CMD F_EXTEND(0xf0190000UL) 7362306a36Sopenharmony_ci#define KITTYHAWK_LCD_DATA (KITTYHAWK_LCD_CMD + 1) 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* lcd_info is pre-initialized to the values needed to program KittyHawk LCD's 7662306a36Sopenharmony_ci * HP seems to have used Sharp/Hitachi HD44780 LCDs most of the time. */ 7762306a36Sopenharmony_cistatic struct pdc_chassis_lcd_info_ret_block 7862306a36Sopenharmony_cilcd_info __attribute__((aligned(8))) = 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci .model = DISPLAY_MODEL_NONE, 8162306a36Sopenharmony_ci .lcd_width = 16, 8262306a36Sopenharmony_ci .lcd_cmd_reg_addr = KITTYHAWK_LCD_CMD, 8362306a36Sopenharmony_ci .lcd_data_reg_addr = KITTYHAWK_LCD_DATA, 8462306a36Sopenharmony_ci .min_cmd_delay = 80, 8562306a36Sopenharmony_ci .reset_cmd1 = 0x80, 8662306a36Sopenharmony_ci .reset_cmd2 = 0xc0, 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci/* direct access to some of the lcd_info variables */ 9062306a36Sopenharmony_ci#define LCD_CMD_REG lcd_info.lcd_cmd_reg_addr 9162306a36Sopenharmony_ci#define LCD_DATA_REG lcd_info.lcd_data_reg_addr 9262306a36Sopenharmony_ci#define LED_DATA_REG lcd_info.lcd_cmd_reg_addr /* LASI & ASP only */ 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/* ptr to LCD/LED-specific function */ 9562306a36Sopenharmony_cistatic void (*led_func_ptr) (unsigned char); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic void lcd_print_now(void) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci int i; 10162306a36Sopenharmony_ci char *str = lcd_text; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (lcd_info.model != DISPLAY_MODEL_LCD) 10462306a36Sopenharmony_ci return; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (!lcd_new_text) 10762306a36Sopenharmony_ci return; 10862306a36Sopenharmony_ci lcd_new_text = 0; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* Set LCD Cursor to 1st character */ 11162306a36Sopenharmony_ci gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG); 11262306a36Sopenharmony_ci udelay(lcd_info.min_cmd_delay); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* Print the string */ 11562306a36Sopenharmony_ci for (i = 0; i < lcd_info.lcd_width; i++) { 11662306a36Sopenharmony_ci gsc_writeb(*str ? *str++ : ' ', LCD_DATA_REG); 11762306a36Sopenharmony_ci udelay(lcd_info.min_cmd_delay); 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/** 12262306a36Sopenharmony_ci * lcd_print() 12362306a36Sopenharmony_ci * 12462306a36Sopenharmony_ci * @str: string to show on the LCD. If NULL, print current string again. 12562306a36Sopenharmony_ci * 12662306a36Sopenharmony_ci * Displays the given string on the LCD-Display of newer machines. 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_civoid lcd_print(const char *str) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci /* copy display string to buffer for procfs */ 13162306a36Sopenharmony_ci if (str) 13262306a36Sopenharmony_ci strscpy(lcd_text, str, sizeof(lcd_text)); 13362306a36Sopenharmony_ci lcd_new_text = 1; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci /* print now if LCD without any LEDs */ 13662306a36Sopenharmony_ci if (led_type == LED_HAS_LCD) 13762306a36Sopenharmony_ci lcd_print_now(); 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci#define LED_DATA 0x01 /* data to shift (0:on 1:off) */ 14162306a36Sopenharmony_ci#define LED_STROBE 0x02 /* strobe to clock data */ 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci/** 14462306a36Sopenharmony_ci * led_ASP_driver() - LED driver for the ASP controller chip 14562306a36Sopenharmony_ci * 14662306a36Sopenharmony_ci * @leds: bitmap representing the LED status 14762306a36Sopenharmony_ci */ 14862306a36Sopenharmony_cistatic void led_ASP_driver(unsigned char leds) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci int i; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci leds = ~leds; 15362306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 15462306a36Sopenharmony_ci unsigned char value; 15562306a36Sopenharmony_ci value = (leds & 0x80) >> 7; 15662306a36Sopenharmony_ci gsc_writeb( value, LED_DATA_REG ); 15762306a36Sopenharmony_ci gsc_writeb( value | LED_STROBE, LED_DATA_REG ); 15862306a36Sopenharmony_ci leds <<= 1; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/** 16362306a36Sopenharmony_ci * led_LASI_driver() - LED driver for the LASI controller chip 16462306a36Sopenharmony_ci * 16562306a36Sopenharmony_ci * @leds: bitmap representing the LED status 16662306a36Sopenharmony_ci */ 16762306a36Sopenharmony_cistatic void led_LASI_driver(unsigned char leds) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci leds = ~leds; 17062306a36Sopenharmony_ci gsc_writeb( leds, LED_DATA_REG ); 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci/** 17462306a36Sopenharmony_ci * led_LCD_driver() - LED & LCD driver for LCD chips 17562306a36Sopenharmony_ci * 17662306a36Sopenharmony_ci * @leds: bitmap representing the LED status 17762306a36Sopenharmony_ci */ 17862306a36Sopenharmony_cistatic void led_LCD_driver(unsigned char leds) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci static const unsigned char mask[4] = { 18162306a36Sopenharmony_ci LED_HEARTBEAT, LED_DISK_IO, 18262306a36Sopenharmony_ci LED_LAN_RCV, LED_LAN_TX }; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci static struct lcd_block * const blockp[4] = { 18562306a36Sopenharmony_ci &lcd_info.heartbeat, 18662306a36Sopenharmony_ci &lcd_info.disk_io, 18762306a36Sopenharmony_ci &lcd_info.lan_rcv, 18862306a36Sopenharmony_ci &lcd_info.lan_tx 18962306a36Sopenharmony_ci }; 19062306a36Sopenharmony_ci static unsigned char latest_leds; 19162306a36Sopenharmony_ci int i; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci for (i = 0; i < 4; ++i) { 19462306a36Sopenharmony_ci if ((leds & mask[i]) == (latest_leds & mask[i])) 19562306a36Sopenharmony_ci continue; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci gsc_writeb( blockp[i]->command, LCD_CMD_REG ); 19862306a36Sopenharmony_ci udelay(lcd_info.min_cmd_delay); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci gsc_writeb( leds & mask[i] ? blockp[i]->on : 20162306a36Sopenharmony_ci blockp[i]->off, LCD_DATA_REG ); 20262306a36Sopenharmony_ci udelay(lcd_info.min_cmd_delay); 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci latest_leds = leds; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci lcd_print_now(); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci/** 21162306a36Sopenharmony_ci * lcd_system_halt() 21262306a36Sopenharmony_ci * 21362306a36Sopenharmony_ci * @nb: pointer to the notifier_block structure 21462306a36Sopenharmony_ci * @event: the event (SYS_RESTART, SYS_HALT or SYS_POWER_OFF) 21562306a36Sopenharmony_ci * @buf: pointer to a buffer (not used) 21662306a36Sopenharmony_ci * 21762306a36Sopenharmony_ci * Called by the reboot notifier chain at shutdown. Stops all 21862306a36Sopenharmony_ci * LED/LCD activities. 21962306a36Sopenharmony_ci */ 22062306a36Sopenharmony_cistatic int lcd_system_halt(struct notifier_block *nb, unsigned long event, void *buf) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci const char *txt; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci switch (event) { 22562306a36Sopenharmony_ci case SYS_RESTART: txt = "SYSTEM RESTART"; 22662306a36Sopenharmony_ci break; 22762306a36Sopenharmony_ci case SYS_HALT: txt = "SYSTEM HALT"; 22862306a36Sopenharmony_ci break; 22962306a36Sopenharmony_ci case SYS_POWER_OFF: txt = "SYSTEM POWER OFF"; 23062306a36Sopenharmony_ci break; 23162306a36Sopenharmony_ci default: return NOTIFY_DONE; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci lcd_print(txt); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci return NOTIFY_OK; 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic struct notifier_block lcd_system_halt_notifier = { 24062306a36Sopenharmony_ci .notifier_call = lcd_system_halt, 24162306a36Sopenharmony_ci}; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic void set_led(struct led_classdev *led_cdev, enum led_brightness brightness); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistruct hppa_led { 24662306a36Sopenharmony_ci struct led_classdev led_cdev; 24762306a36Sopenharmony_ci unsigned char led_bit; 24862306a36Sopenharmony_ci}; 24962306a36Sopenharmony_ci#define to_hppa_led(d) container_of(d, struct hppa_led, led_cdev) 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_citypedef void (*set_handler)(struct led_classdev *, enum led_brightness); 25262306a36Sopenharmony_cistruct led_type { 25362306a36Sopenharmony_ci const char *name; 25462306a36Sopenharmony_ci set_handler handler; 25562306a36Sopenharmony_ci const char *default_trigger; 25662306a36Sopenharmony_ci}; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci#define NUM_LEDS_PER_BOARD 8 25962306a36Sopenharmony_cistruct hppa_drvdata { 26062306a36Sopenharmony_ci struct hppa_led leds[NUM_LEDS_PER_BOARD]; 26162306a36Sopenharmony_ci}; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic void set_led(struct led_classdev *led_cdev, enum led_brightness brightness) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci struct hppa_led *p = to_hppa_led(led_cdev); 26662306a36Sopenharmony_ci unsigned char led_bit = p->led_bit; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (brightness == LED_OFF) 26962306a36Sopenharmony_ci lastleds &= ~led_bit; 27062306a36Sopenharmony_ci else 27162306a36Sopenharmony_ci lastleds |= led_bit; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci if (led_func_ptr) 27462306a36Sopenharmony_ci led_func_ptr(lastleds); 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_cistatic int hppa_led_generic_probe(struct platform_device *pdev, 27962306a36Sopenharmony_ci struct led_type *types) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci struct hppa_drvdata *p; 28262306a36Sopenharmony_ci int i, err; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); 28562306a36Sopenharmony_ci if (!p) 28662306a36Sopenharmony_ci return -ENOMEM; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci for (i = 0; i < NUM_LEDS_PER_BOARD; i++) { 28962306a36Sopenharmony_ci struct led_classdev *lp = &p->leds[i].led_cdev; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci p->leds[i].led_bit = BIT(i); 29262306a36Sopenharmony_ci lp->name = types[i].name; 29362306a36Sopenharmony_ci lp->brightness = LED_FULL; 29462306a36Sopenharmony_ci lp->brightness_set = types[i].handler; 29562306a36Sopenharmony_ci lp->default_trigger = types[i].default_trigger; 29662306a36Sopenharmony_ci err = led_classdev_register(&pdev->dev, lp); 29762306a36Sopenharmony_ci if (err) { 29862306a36Sopenharmony_ci dev_err(&pdev->dev, "Could not register %s LED\n", 29962306a36Sopenharmony_ci lp->name); 30062306a36Sopenharmony_ci for (i--; i >= 0; i--) 30162306a36Sopenharmony_ci led_classdev_unregister(&p->leds[i].led_cdev); 30262306a36Sopenharmony_ci return err; 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci platform_set_drvdata(pdev, p); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci return 0; 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic int platform_led_remove(struct platform_device *pdev) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci struct hppa_drvdata *p = platform_get_drvdata(pdev); 31462306a36Sopenharmony_ci int i; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci for (i = 0; i < NUM_LEDS_PER_BOARD; i++) 31762306a36Sopenharmony_ci led_classdev_unregister(&p->leds[i].led_cdev); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci return 0; 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cistatic struct led_type mainboard_led_types[NUM_LEDS_PER_BOARD] = { 32362306a36Sopenharmony_ci { 32462306a36Sopenharmony_ci .name = "platform-lan-tx", 32562306a36Sopenharmony_ci .handler = set_led, 32662306a36Sopenharmony_ci .default_trigger = "tx", 32762306a36Sopenharmony_ci }, 32862306a36Sopenharmony_ci { 32962306a36Sopenharmony_ci .name = "platform-lan-rx", 33062306a36Sopenharmony_ci .handler = set_led, 33162306a36Sopenharmony_ci .default_trigger = "rx", 33262306a36Sopenharmony_ci }, 33362306a36Sopenharmony_ci { 33462306a36Sopenharmony_ci .name = "platform-disk", 33562306a36Sopenharmony_ci .handler = set_led, 33662306a36Sopenharmony_ci .default_trigger = "disk-activity", 33762306a36Sopenharmony_ci }, 33862306a36Sopenharmony_ci { 33962306a36Sopenharmony_ci .name = "platform-heartbeat", 34062306a36Sopenharmony_ci .handler = set_led, 34162306a36Sopenharmony_ci .default_trigger = "heartbeat", 34262306a36Sopenharmony_ci }, 34362306a36Sopenharmony_ci { 34462306a36Sopenharmony_ci .name = "platform-LED4", 34562306a36Sopenharmony_ci .handler = set_led, 34662306a36Sopenharmony_ci .default_trigger = "panic", 34762306a36Sopenharmony_ci }, 34862306a36Sopenharmony_ci { 34962306a36Sopenharmony_ci .name = "platform-LED5", 35062306a36Sopenharmony_ci .handler = set_led, 35162306a36Sopenharmony_ci .default_trigger = "panic", 35262306a36Sopenharmony_ci }, 35362306a36Sopenharmony_ci { 35462306a36Sopenharmony_ci .name = "platform-LED6", 35562306a36Sopenharmony_ci .handler = set_led, 35662306a36Sopenharmony_ci .default_trigger = "panic", 35762306a36Sopenharmony_ci }, 35862306a36Sopenharmony_ci { 35962306a36Sopenharmony_ci .name = "platform-LED7", 36062306a36Sopenharmony_ci .handler = set_led, 36162306a36Sopenharmony_ci .default_trigger = "panic", 36262306a36Sopenharmony_ci }, 36362306a36Sopenharmony_ci}; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cistatic int platform_led_probe(struct platform_device *pdev) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci return hppa_led_generic_probe(pdev, mainboard_led_types); 36862306a36Sopenharmony_ci} 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ciMODULE_ALIAS("platform:platform-leds"); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_cistatic struct platform_driver hppa_mainboard_led_driver = { 37362306a36Sopenharmony_ci .probe = platform_led_probe, 37462306a36Sopenharmony_ci .remove = platform_led_remove, 37562306a36Sopenharmony_ci .driver = { 37662306a36Sopenharmony_ci .name = "platform-leds", 37762306a36Sopenharmony_ci }, 37862306a36Sopenharmony_ci}; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic struct platform_driver * const drivers[] = { 38162306a36Sopenharmony_ci &hppa_mainboard_led_driver, 38262306a36Sopenharmony_ci}; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_cistatic struct platform_device platform_leds = { 38562306a36Sopenharmony_ci .name = "platform-leds", 38662306a36Sopenharmony_ci}; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci/** 38962306a36Sopenharmony_ci * register_led_driver() 39062306a36Sopenharmony_ci * 39162306a36Sopenharmony_ci * @model: model type, one of the DISPLAY_MODEL_XXXX values 39262306a36Sopenharmony_ci * @cmd_reg: physical address of cmd register for the LED/LCD 39362306a36Sopenharmony_ci * @data_reg: physical address of data register for the LED/LCD 39462306a36Sopenharmony_ci * 39562306a36Sopenharmony_ci * Registers a chassis LED or LCD which should be driven by this driver. 39662306a36Sopenharmony_ci * Only PDC-based, LASI- or ASP-style LEDs and LCDs are supported. 39762306a36Sopenharmony_ci */ 39862306a36Sopenharmony_ciint __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg) 39962306a36Sopenharmony_ci{ 40062306a36Sopenharmony_ci if (led_func_ptr || !data_reg) 40162306a36Sopenharmony_ci return 1; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci /* No LEDs when running in QEMU */ 40462306a36Sopenharmony_ci if (running_on_qemu) 40562306a36Sopenharmony_ci return 1; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci lcd_info.model = model; /* store the values */ 40862306a36Sopenharmony_ci LCD_CMD_REG = (cmd_reg == LED_CMD_REG_NONE) ? 0 : cmd_reg; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci switch (lcd_info.model) { 41162306a36Sopenharmony_ci case DISPLAY_MODEL_LCD: 41262306a36Sopenharmony_ci LCD_DATA_REG = data_reg; 41362306a36Sopenharmony_ci pr_info("led: LCD display at %#lx and %#lx\n", 41462306a36Sopenharmony_ci LCD_CMD_REG , LCD_DATA_REG); 41562306a36Sopenharmony_ci led_func_ptr = led_LCD_driver; 41662306a36Sopenharmony_ci if (lcd_no_led_support) 41762306a36Sopenharmony_ci led_type = LED_HAS_LCD; 41862306a36Sopenharmony_ci else 41962306a36Sopenharmony_ci led_type = LED_HAS_LCD | LED_HAS_LED; 42062306a36Sopenharmony_ci break; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci case DISPLAY_MODEL_LASI: 42362306a36Sopenharmony_ci LED_DATA_REG = data_reg; 42462306a36Sopenharmony_ci led_func_ptr = led_LASI_driver; 42562306a36Sopenharmony_ci pr_info("led: LED display at %#lx\n", LED_DATA_REG); 42662306a36Sopenharmony_ci led_type = LED_HAS_LED; 42762306a36Sopenharmony_ci break; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci case DISPLAY_MODEL_OLD_ASP: 43062306a36Sopenharmony_ci LED_DATA_REG = data_reg; 43162306a36Sopenharmony_ci led_func_ptr = led_ASP_driver; 43262306a36Sopenharmony_ci pr_info("led: LED (ASP-style) display at %#lx\n", 43362306a36Sopenharmony_ci LED_DATA_REG); 43462306a36Sopenharmony_ci led_type = LED_HAS_LED; 43562306a36Sopenharmony_ci break; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci default: 43862306a36Sopenharmony_ci pr_err("led: Unknown LCD/LED model type %d\n", lcd_info.model); 43962306a36Sopenharmony_ci return 1; 44062306a36Sopenharmony_ci } 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci platform_register_drivers(drivers, ARRAY_SIZE(drivers)); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci return register_reboot_notifier(&lcd_system_halt_notifier); 44562306a36Sopenharmony_ci} 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci/** 44862306a36Sopenharmony_ci * early_led_init() 44962306a36Sopenharmony_ci * 45062306a36Sopenharmony_ci * early_led_init() is called early in the bootup-process and asks the 45162306a36Sopenharmony_ci * PDC for an usable chassis LCD or LED. If the PDC doesn't return any 45262306a36Sopenharmony_ci * info, then a LED might be detected by the LASI or ASP drivers later. 45362306a36Sopenharmony_ci * KittyHawk machines have often a buggy PDC, so that we explicitly check 45462306a36Sopenharmony_ci * for those machines here. 45562306a36Sopenharmony_ci */ 45662306a36Sopenharmony_cistatic int __init early_led_init(void) 45762306a36Sopenharmony_ci{ 45862306a36Sopenharmony_ci struct pdc_chassis_info chassis_info; 45962306a36Sopenharmony_ci int ret; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci snprintf(lcd_text_default, sizeof(lcd_text_default), 46262306a36Sopenharmony_ci "Linux %s", init_utsname()->release); 46362306a36Sopenharmony_ci strcpy(lcd_text, lcd_text_default); 46462306a36Sopenharmony_ci lcd_new_text = 1; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci /* Work around the buggy PDC of KittyHawk-machines */ 46762306a36Sopenharmony_ci switch (CPU_HVERSION) { 46862306a36Sopenharmony_ci case 0x580: /* KittyHawk DC2-100 (K100) */ 46962306a36Sopenharmony_ci case 0x581: /* KittyHawk DC3-120 (K210) */ 47062306a36Sopenharmony_ci case 0x582: /* KittyHawk DC3 100 (K400) */ 47162306a36Sopenharmony_ci case 0x583: /* KittyHawk DC3 120 (K410) */ 47262306a36Sopenharmony_ci case 0x58B: /* KittyHawk DC2 100 (K200) */ 47362306a36Sopenharmony_ci pr_info("LCD on KittyHawk-Machine found.\n"); 47462306a36Sopenharmony_ci lcd_info.model = DISPLAY_MODEL_LCD; 47562306a36Sopenharmony_ci /* KittyHawk has no LED support on its LCD, so skip LED detection */ 47662306a36Sopenharmony_ci lcd_no_led_support = 1; 47762306a36Sopenharmony_ci goto found; /* use the preinitialized values of lcd_info */ 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci /* initialize the struct, so that we can check for valid return values */ 48162306a36Sopenharmony_ci chassis_info.actcnt = chassis_info.maxcnt = 0; 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci ret = pdc_chassis_info(&chassis_info, &lcd_info, sizeof(lcd_info)); 48462306a36Sopenharmony_ci if (ret != PDC_OK) { 48562306a36Sopenharmony_cinot_found: 48662306a36Sopenharmony_ci lcd_info.model = DISPLAY_MODEL_NONE; 48762306a36Sopenharmony_ci return 1; 48862306a36Sopenharmony_ci } 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci /* check the results. Some machines have a buggy PDC */ 49162306a36Sopenharmony_ci if (chassis_info.actcnt <= 0 || chassis_info.actcnt != chassis_info.maxcnt) 49262306a36Sopenharmony_ci goto not_found; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci switch (lcd_info.model) { 49562306a36Sopenharmony_ci case DISPLAY_MODEL_LCD: /* LCD display */ 49662306a36Sopenharmony_ci if (chassis_info.actcnt < 49762306a36Sopenharmony_ci offsetof(struct pdc_chassis_lcd_info_ret_block, _pad)-1) 49862306a36Sopenharmony_ci goto not_found; 49962306a36Sopenharmony_ci if (!lcd_info.act_enable) { 50062306a36Sopenharmony_ci /* PDC tells LCD should not be used. */ 50162306a36Sopenharmony_ci goto not_found; 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci break; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci case DISPLAY_MODEL_NONE: /* no LED or LCD available */ 50662306a36Sopenharmony_ci goto not_found; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci case DISPLAY_MODEL_LASI: /* Lasi style 8 bit LED display */ 50962306a36Sopenharmony_ci if (chassis_info.actcnt != 8 && chassis_info.actcnt != 32) 51062306a36Sopenharmony_ci goto not_found; 51162306a36Sopenharmony_ci break; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci default: 51462306a36Sopenharmony_ci pr_warn("PDC reported unknown LCD/LED model %d\n", 51562306a36Sopenharmony_ci lcd_info.model); 51662306a36Sopenharmony_ci goto not_found; 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_cifound: 52062306a36Sopenharmony_ci /* register the LCD/LED driver */ 52162306a36Sopenharmony_ci return register_led_driver(lcd_info.model, LCD_CMD_REG, LCD_DATA_REG); 52262306a36Sopenharmony_ci} 52362306a36Sopenharmony_ciarch_initcall(early_led_init); 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci/** 52662306a36Sopenharmony_ci * register_led_regions() 52762306a36Sopenharmony_ci * 52862306a36Sopenharmony_ci * Register_led_regions() registers the LCD/LED regions for /procfs. 52962306a36Sopenharmony_ci * At bootup - where the initialisation of the LCD/LED often happens 53062306a36Sopenharmony_ci * not all internal structures of request_region() are properly set up, 53162306a36Sopenharmony_ci * so that we delay the led-registration until after busdevices_init() 53262306a36Sopenharmony_ci * has been executed. 53362306a36Sopenharmony_ci */ 53462306a36Sopenharmony_cistatic void __init register_led_regions(void) 53562306a36Sopenharmony_ci{ 53662306a36Sopenharmony_ci switch (lcd_info.model) { 53762306a36Sopenharmony_ci case DISPLAY_MODEL_LCD: 53862306a36Sopenharmony_ci request_mem_region((unsigned long)LCD_CMD_REG, 1, "lcd_cmd"); 53962306a36Sopenharmony_ci request_mem_region((unsigned long)LCD_DATA_REG, 1, "lcd_data"); 54062306a36Sopenharmony_ci break; 54162306a36Sopenharmony_ci case DISPLAY_MODEL_LASI: 54262306a36Sopenharmony_ci case DISPLAY_MODEL_OLD_ASP: 54362306a36Sopenharmony_ci request_mem_region((unsigned long)LED_DATA_REG, 1, "led_data"); 54462306a36Sopenharmony_ci break; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci} 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cistatic int __init startup_leds(void) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci if (platform_device_register(&platform_leds)) 55162306a36Sopenharmony_ci printk(KERN_INFO "LED: failed to register LEDs\n"); 55262306a36Sopenharmony_ci register_led_regions(); 55362306a36Sopenharmony_ci return 0; 55462306a36Sopenharmony_ci} 55562306a36Sopenharmony_cidevice_initcall(startup_leds); 556