1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Chassis LCD/LED driver for HP-PARISC workstations |
4 | * |
5 | * (c) Copyright 2000 Red Hat Software |
6 | * (c) Copyright 2000 Helge Deller <hdeller@redhat.com> |
7 | * (c) Copyright 2001 Randolph Chung <tausq@debian.org> |
8 | * (c) Copyright 2000-2023 Helge Deller <deller@gmx.de> |
9 | * |
10 | * The control of the LEDs and LCDs on PARISC machines has to be done |
11 | * completely in software. |
12 | * |
13 | * The LEDs can be configured at runtime in /sys/class/leds/ |
14 | */ |
15 | |
16 | #include <linux/module.h> |
17 | #include <linux/init.h> |
18 | #include <linux/types.h> |
19 | #include <linux/ioport.h> |
20 | #include <linux/utsname.h> |
21 | #include <linux/capability.h> |
22 | #include <linux/delay.h> |
23 | #include <linux/reboot.h> |
24 | #include <linux/uaccess.h> |
25 | #include <linux/leds.h> |
26 | #include <linux/platform_device.h> |
27 | |
28 | #include <asm/io.h> |
29 | #include <asm/processor.h> |
30 | #include <asm/hardware.h> |
31 | #include <asm/param.h> /* HZ */ |
32 | #include <asm/led.h> |
33 | #include <asm/pdc.h> |
34 | |
35 | #define LED_HAS_LCD 1 |
36 | #define LED_HAS_LED 2 |
37 | |
38 | static unsigned char led_type; /* bitmask of LED_HAS_XXX */ |
39 | static unsigned char lastleds; /* LED state from most recent update */ |
40 | static unsigned char lcd_new_text; |
41 | static unsigned char lcd_text[20]; |
42 | static unsigned char lcd_text_default[20]; |
43 | static unsigned char lcd_no_led_support; /* KittyHawk doesn't support LED on its LCD */ |
44 | |
45 | struct lcd_block { |
46 | unsigned char command; /* stores the command byte */ |
47 | unsigned char on; /* value for turning LED on */ |
48 | unsigned char off; /* value for turning LED off */ |
49 | }; |
50 | |
51 | /* Structure returned by PDC_RETURN_CHASSIS_INFO */ |
52 | /* NOTE: we use unsigned long:16 two times, since the following member |
53 | lcd_cmd_reg_addr needs to be 64bit aligned on 64bit PA2.0-machines */ |
54 | struct pdc_chassis_lcd_info_ret_block { |
55 | unsigned long model:16; /* DISPLAY_MODEL_XXXX */ |
56 | unsigned long lcd_width:16; /* width of the LCD in chars (DISPLAY_MODEL_LCD only) */ |
57 | unsigned long lcd_cmd_reg_addr; /* ptr to LCD cmd-register & data ptr for LED */ |
58 | unsigned long lcd_data_reg_addr; /* ptr to LCD data-register (LCD only) */ |
59 | unsigned int min_cmd_delay; /* delay in uS after cmd-write (LCD only) */ |
60 | unsigned char reset_cmd1; /* command #1 for writing LCD string (LCD only) */ |
61 | unsigned char reset_cmd2; /* command #2 for writing LCD string (LCD only) */ |
62 | unsigned char act_enable; /* 0 = no activity (LCD only) */ |
63 | struct lcd_block heartbeat; |
64 | struct lcd_block disk_io; |
65 | struct lcd_block lan_rcv; |
66 | struct lcd_block lan_tx; |
67 | char _pad; |
68 | }; |
69 | |
70 | |
71 | /* LCD_CMD and LCD_DATA for KittyHawk machines */ |
72 | #define KITTYHAWK_LCD_CMD F_EXTEND(0xf0190000UL) |
73 | #define KITTYHAWK_LCD_DATA (KITTYHAWK_LCD_CMD + 1) |
74 | |
75 | /* lcd_info is pre-initialized to the values needed to program KittyHawk LCD's |
76 | * HP seems to have used Sharp/Hitachi HD44780 LCDs most of the time. */ |
77 | static struct pdc_chassis_lcd_info_ret_block |
78 | lcd_info __attribute__((aligned(8))) = |
79 | { |
80 | .model = DISPLAY_MODEL_NONE, |
81 | .lcd_width = 16, |
82 | .lcd_cmd_reg_addr = KITTYHAWK_LCD_CMD, |
83 | .lcd_data_reg_addr = KITTYHAWK_LCD_DATA, |
84 | .min_cmd_delay = 80, |
85 | .reset_cmd1 = 0x80, |
86 | .reset_cmd2 = 0xc0, |
87 | }; |
88 | |
89 | /* direct access to some of the lcd_info variables */ |
90 | #define LCD_CMD_REG lcd_info.lcd_cmd_reg_addr |
91 | #define LCD_DATA_REG lcd_info.lcd_data_reg_addr |
92 | #define LED_DATA_REG lcd_info.lcd_cmd_reg_addr /* LASI & ASP only */ |
93 | |
94 | /* ptr to LCD/LED-specific function */ |
95 | static void (*led_func_ptr) (unsigned char); |
96 | |
97 | |
98 | static void lcd_print_now(void) |
99 | { |
100 | int i; |
101 | char *str = lcd_text; |
102 | |
103 | if (lcd_info.model != DISPLAY_MODEL_LCD) |
104 | return; |
105 | |
106 | if (!lcd_new_text) |
107 | return; |
108 | lcd_new_text = 0; |
109 | |
110 | /* Set LCD Cursor to 1st character */ |
111 | gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG); |
112 | udelay(lcd_info.min_cmd_delay); |
113 | |
114 | /* Print the string */ |
115 | for (i = 0; i < lcd_info.lcd_width; i++) { |
116 | gsc_writeb(*str ? *str++ : ' ', LCD_DATA_REG); |
117 | udelay(lcd_info.min_cmd_delay); |
118 | } |
119 | } |
120 | |
121 | /** |
122 | * lcd_print() |
123 | * |
124 | * @str: string to show on the LCD. If NULL, print current string again. |
125 | * |
126 | * Displays the given string on the LCD-Display of newer machines. |
127 | */ |
128 | void lcd_print(const char *str) |
129 | { |
130 | /* copy display string to buffer for procfs */ |
131 | if (str) |
132 | strscpy(p: lcd_text, q: str, size: sizeof(lcd_text)); |
133 | lcd_new_text = 1; |
134 | |
135 | /* print now if LCD without any LEDs */ |
136 | if (led_type == LED_HAS_LCD) |
137 | lcd_print_now(); |
138 | } |
139 | |
140 | #define LED_DATA 0x01 /* data to shift (0:on 1:off) */ |
141 | #define LED_STROBE 0x02 /* strobe to clock data */ |
142 | |
143 | /** |
144 | * led_ASP_driver() - LED driver for the ASP controller chip |
145 | * |
146 | * @leds: bitmap representing the LED status |
147 | */ |
148 | static void led_ASP_driver(unsigned char leds) |
149 | { |
150 | int i; |
151 | |
152 | leds = ~leds; |
153 | for (i = 0; i < 8; i++) { |
154 | unsigned char value; |
155 | value = (leds & 0x80) >> 7; |
156 | gsc_writeb( value, LED_DATA_REG ); |
157 | gsc_writeb( value | LED_STROBE, LED_DATA_REG ); |
158 | leds <<= 1; |
159 | } |
160 | } |
161 | |
162 | /** |
163 | * led_LASI_driver() - LED driver for the LASI controller chip |
164 | * |
165 | * @leds: bitmap representing the LED status |
166 | */ |
167 | static void led_LASI_driver(unsigned char leds) |
168 | { |
169 | leds = ~leds; |
170 | gsc_writeb( leds, LED_DATA_REG ); |
171 | } |
172 | |
173 | /** |
174 | * led_LCD_driver() - LED & LCD driver for LCD chips |
175 | * |
176 | * @leds: bitmap representing the LED status |
177 | */ |
178 | static void led_LCD_driver(unsigned char leds) |
179 | { |
180 | static const unsigned char mask[4] = { |
181 | LED_HEARTBEAT, LED_DISK_IO, |
182 | LED_LAN_RCV, LED_LAN_TX }; |
183 | |
184 | static struct lcd_block * const blockp[4] = { |
185 | &lcd_info.heartbeat, |
186 | &lcd_info.disk_io, |
187 | &lcd_info.lan_rcv, |
188 | &lcd_info.lan_tx |
189 | }; |
190 | static unsigned char latest_leds; |
191 | int i; |
192 | |
193 | for (i = 0; i < 4; ++i) { |
194 | if ((leds & mask[i]) == (latest_leds & mask[i])) |
195 | continue; |
196 | |
197 | gsc_writeb( blockp[i]->command, LCD_CMD_REG ); |
198 | udelay(lcd_info.min_cmd_delay); |
199 | |
200 | gsc_writeb( leds & mask[i] ? blockp[i]->on : |
201 | blockp[i]->off, LCD_DATA_REG ); |
202 | udelay(lcd_info.min_cmd_delay); |
203 | } |
204 | latest_leds = leds; |
205 | |
206 | lcd_print_now(); |
207 | } |
208 | |
209 | |
210 | /** |
211 | * lcd_system_halt() |
212 | * |
213 | * @nb: pointer to the notifier_block structure |
214 | * @event: the event (SYS_RESTART, SYS_HALT or SYS_POWER_OFF) |
215 | * @buf: pointer to a buffer (not used) |
216 | * |
217 | * Called by the reboot notifier chain at shutdown. Stops all |
218 | * LED/LCD activities. |
219 | */ |
220 | static int lcd_system_halt(struct notifier_block *nb, unsigned long event, void *buf) |
221 | { |
222 | const char *txt; |
223 | |
224 | switch (event) { |
225 | case SYS_RESTART: txt = "SYSTEM RESTART" ; |
226 | break; |
227 | case SYS_HALT: txt = "SYSTEM HALT" ; |
228 | break; |
229 | case SYS_POWER_OFF: txt = "SYSTEM POWER OFF" ; |
230 | break; |
231 | default: return NOTIFY_DONE; |
232 | } |
233 | |
234 | lcd_print(str: txt); |
235 | |
236 | return NOTIFY_OK; |
237 | } |
238 | |
239 | static struct notifier_block lcd_system_halt_notifier = { |
240 | .notifier_call = lcd_system_halt, |
241 | }; |
242 | |
243 | static void set_led(struct led_classdev *led_cdev, enum led_brightness brightness); |
244 | |
245 | struct hppa_led { |
246 | struct led_classdev led_cdev; |
247 | unsigned char led_bit; |
248 | }; |
249 | #define to_hppa_led(d) container_of(d, struct hppa_led, led_cdev) |
250 | |
251 | typedef void (*set_handler)(struct led_classdev *, enum led_brightness); |
252 | struct led_type { |
253 | const char *name; |
254 | set_handler handler; |
255 | const char *default_trigger; |
256 | }; |
257 | |
258 | #define NUM_LEDS_PER_BOARD 8 |
259 | struct hppa_drvdata { |
260 | struct hppa_led leds[NUM_LEDS_PER_BOARD]; |
261 | }; |
262 | |
263 | static void set_led(struct led_classdev *led_cdev, enum led_brightness brightness) |
264 | { |
265 | struct hppa_led *p = to_hppa_led(led_cdev); |
266 | unsigned char led_bit = p->led_bit; |
267 | |
268 | if (brightness == LED_OFF) |
269 | lastleds &= ~led_bit; |
270 | else |
271 | lastleds |= led_bit; |
272 | |
273 | if (led_func_ptr) |
274 | led_func_ptr(lastleds); |
275 | } |
276 | |
277 | |
278 | static int hppa_led_generic_probe(struct platform_device *pdev, |
279 | struct led_type *types) |
280 | { |
281 | struct hppa_drvdata *p; |
282 | int i, err; |
283 | |
284 | p = devm_kzalloc(dev: &pdev->dev, size: sizeof(*p), GFP_KERNEL); |
285 | if (!p) |
286 | return -ENOMEM; |
287 | |
288 | for (i = 0; i < NUM_LEDS_PER_BOARD; i++) { |
289 | struct led_classdev *lp = &p->leds[i].led_cdev; |
290 | |
291 | p->leds[i].led_bit = BIT(i); |
292 | lp->name = types[i].name; |
293 | lp->brightness = LED_FULL; |
294 | lp->brightness_set = types[i].handler; |
295 | lp->default_trigger = types[i].default_trigger; |
296 | err = led_classdev_register(parent: &pdev->dev, led_cdev: lp); |
297 | if (err) { |
298 | dev_err(&pdev->dev, "Could not register %s LED\n" , |
299 | lp->name); |
300 | for (i--; i >= 0; i--) |
301 | led_classdev_unregister(led_cdev: &p->leds[i].led_cdev); |
302 | return err; |
303 | } |
304 | } |
305 | |
306 | platform_set_drvdata(pdev, data: p); |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | static int platform_led_remove(struct platform_device *pdev) |
312 | { |
313 | struct hppa_drvdata *p = platform_get_drvdata(pdev); |
314 | int i; |
315 | |
316 | for (i = 0; i < NUM_LEDS_PER_BOARD; i++) |
317 | led_classdev_unregister(led_cdev: &p->leds[i].led_cdev); |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | static struct led_type mainboard_led_types[NUM_LEDS_PER_BOARD] = { |
323 | { |
324 | .name = "platform-lan-tx" , |
325 | .handler = set_led, |
326 | .default_trigger = "tx" , |
327 | }, |
328 | { |
329 | .name = "platform-lan-rx" , |
330 | .handler = set_led, |
331 | .default_trigger = "rx" , |
332 | }, |
333 | { |
334 | .name = "platform-disk" , |
335 | .handler = set_led, |
336 | .default_trigger = "disk-activity" , |
337 | }, |
338 | { |
339 | .name = "platform-heartbeat" , |
340 | .handler = set_led, |
341 | .default_trigger = "heartbeat" , |
342 | }, |
343 | { |
344 | .name = "platform-LED4" , |
345 | .handler = set_led, |
346 | .default_trigger = "panic" , |
347 | }, |
348 | { |
349 | .name = "platform-LED5" , |
350 | .handler = set_led, |
351 | .default_trigger = "panic" , |
352 | }, |
353 | { |
354 | .name = "platform-LED6" , |
355 | .handler = set_led, |
356 | .default_trigger = "panic" , |
357 | }, |
358 | { |
359 | .name = "platform-LED7" , |
360 | .handler = set_led, |
361 | .default_trigger = "panic" , |
362 | }, |
363 | }; |
364 | |
365 | static int platform_led_probe(struct platform_device *pdev) |
366 | { |
367 | return hppa_led_generic_probe(pdev, types: mainboard_led_types); |
368 | } |
369 | |
370 | MODULE_ALIAS("platform:platform-leds" ); |
371 | |
372 | static struct platform_driver hppa_mainboard_led_driver = { |
373 | .probe = platform_led_probe, |
374 | .remove = platform_led_remove, |
375 | .driver = { |
376 | .name = "platform-leds" , |
377 | }, |
378 | }; |
379 | |
380 | static struct platform_driver * const drivers[] = { |
381 | &hppa_mainboard_led_driver, |
382 | }; |
383 | |
384 | static struct platform_device platform_leds = { |
385 | .name = "platform-leds" , |
386 | }; |
387 | |
388 | /** |
389 | * register_led_driver() |
390 | * |
391 | * @model: model type, one of the DISPLAY_MODEL_XXXX values |
392 | * @cmd_reg: physical address of cmd register for the LED/LCD |
393 | * @data_reg: physical address of data register for the LED/LCD |
394 | * |
395 | * Registers a chassis LED or LCD which should be driven by this driver. |
396 | * Only PDC-based, LASI- or ASP-style LEDs and LCDs are supported. |
397 | */ |
398 | int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg) |
399 | { |
400 | if (led_func_ptr || !data_reg) |
401 | return 1; |
402 | |
403 | /* No LEDs when running in QEMU */ |
404 | if (running_on_qemu) |
405 | return 1; |
406 | |
407 | lcd_info.model = model; /* store the values */ |
408 | LCD_CMD_REG = (cmd_reg == LED_CMD_REG_NONE) ? 0 : cmd_reg; |
409 | |
410 | switch (lcd_info.model) { |
411 | case DISPLAY_MODEL_LCD: |
412 | LCD_DATA_REG = data_reg; |
413 | pr_info("led: LCD display at %#lx and %#lx\n" , |
414 | LCD_CMD_REG , LCD_DATA_REG); |
415 | led_func_ptr = led_LCD_driver; |
416 | if (lcd_no_led_support) |
417 | led_type = LED_HAS_LCD; |
418 | else |
419 | led_type = LED_HAS_LCD | LED_HAS_LED; |
420 | break; |
421 | |
422 | case DISPLAY_MODEL_LASI: |
423 | LED_DATA_REG = data_reg; |
424 | led_func_ptr = led_LASI_driver; |
425 | pr_info("led: LED display at %#lx\n" , LED_DATA_REG); |
426 | led_type = LED_HAS_LED; |
427 | break; |
428 | |
429 | case DISPLAY_MODEL_OLD_ASP: |
430 | LED_DATA_REG = data_reg; |
431 | led_func_ptr = led_ASP_driver; |
432 | pr_info("led: LED (ASP-style) display at %#lx\n" , |
433 | LED_DATA_REG); |
434 | led_type = LED_HAS_LED; |
435 | break; |
436 | |
437 | default: |
438 | pr_err("led: Unknown LCD/LED model type %d\n" , lcd_info.model); |
439 | return 1; |
440 | } |
441 | |
442 | platform_register_drivers(drivers, ARRAY_SIZE(drivers)); |
443 | |
444 | return register_reboot_notifier(&lcd_system_halt_notifier); |
445 | } |
446 | |
447 | /** |
448 | * early_led_init() |
449 | * |
450 | * early_led_init() is called early in the bootup-process and asks the |
451 | * PDC for an usable chassis LCD or LED. If the PDC doesn't return any |
452 | * info, then a LED might be detected by the LASI or ASP drivers later. |
453 | * KittyHawk machines have often a buggy PDC, so that we explicitly check |
454 | * for those machines here. |
455 | */ |
456 | static int __init early_led_init(void) |
457 | { |
458 | struct pdc_chassis_info chassis_info; |
459 | int ret; |
460 | |
461 | snprintf(buf: lcd_text_default, size: sizeof(lcd_text_default), |
462 | fmt: "Linux %s" , init_utsname()->release); |
463 | strcpy(p: lcd_text, q: lcd_text_default); |
464 | lcd_new_text = 1; |
465 | |
466 | /* Work around the buggy PDC of KittyHawk-machines */ |
467 | switch (CPU_HVERSION) { |
468 | case 0x580: /* KittyHawk DC2-100 (K100) */ |
469 | case 0x581: /* KittyHawk DC3-120 (K210) */ |
470 | case 0x582: /* KittyHawk DC3 100 (K400) */ |
471 | case 0x583: /* KittyHawk DC3 120 (K410) */ |
472 | case 0x58B: /* KittyHawk DC2 100 (K200) */ |
473 | pr_info("LCD on KittyHawk-Machine found.\n" ); |
474 | lcd_info.model = DISPLAY_MODEL_LCD; |
475 | /* KittyHawk has no LED support on its LCD, so skip LED detection */ |
476 | lcd_no_led_support = 1; |
477 | goto found; /* use the preinitialized values of lcd_info */ |
478 | } |
479 | |
480 | /* initialize the struct, so that we can check for valid return values */ |
481 | chassis_info.actcnt = chassis_info.maxcnt = 0; |
482 | |
483 | ret = pdc_chassis_info(&chassis_info, &lcd_info, sizeof(lcd_info)); |
484 | if (ret != PDC_OK) { |
485 | not_found: |
486 | lcd_info.model = DISPLAY_MODEL_NONE; |
487 | return 1; |
488 | } |
489 | |
490 | /* check the results. Some machines have a buggy PDC */ |
491 | if (chassis_info.actcnt <= 0 || chassis_info.actcnt != chassis_info.maxcnt) |
492 | goto not_found; |
493 | |
494 | switch (lcd_info.model) { |
495 | case DISPLAY_MODEL_LCD: /* LCD display */ |
496 | if (chassis_info.actcnt < |
497 | offsetof(struct pdc_chassis_lcd_info_ret_block, _pad)-1) |
498 | goto not_found; |
499 | if (!lcd_info.act_enable) { |
500 | /* PDC tells LCD should not be used. */ |
501 | goto not_found; |
502 | } |
503 | break; |
504 | |
505 | case DISPLAY_MODEL_NONE: /* no LED or LCD available */ |
506 | goto not_found; |
507 | |
508 | case DISPLAY_MODEL_LASI: /* Lasi style 8 bit LED display */ |
509 | if (chassis_info.actcnt != 8 && chassis_info.actcnt != 32) |
510 | goto not_found; |
511 | break; |
512 | |
513 | default: |
514 | pr_warn("PDC reported unknown LCD/LED model %d\n" , |
515 | lcd_info.model); |
516 | goto not_found; |
517 | } |
518 | |
519 | found: |
520 | /* register the LCD/LED driver */ |
521 | return register_led_driver(model: lcd_info.model, LCD_CMD_REG, LCD_DATA_REG); |
522 | } |
523 | arch_initcall(early_led_init); |
524 | |
525 | /** |
526 | * register_led_regions() |
527 | * |
528 | * Register_led_regions() registers the LCD/LED regions for /procfs. |
529 | * At bootup - where the initialisation of the LCD/LED often happens |
530 | * not all internal structures of request_region() are properly set up, |
531 | * so that we delay the led-registration until after busdevices_init() |
532 | * has been executed. |
533 | */ |
534 | static void __init register_led_regions(void) |
535 | { |
536 | switch (lcd_info.model) { |
537 | case DISPLAY_MODEL_LCD: |
538 | request_mem_region((unsigned long)LCD_CMD_REG, 1, "lcd_cmd" ); |
539 | request_mem_region((unsigned long)LCD_DATA_REG, 1, "lcd_data" ); |
540 | break; |
541 | case DISPLAY_MODEL_LASI: |
542 | case DISPLAY_MODEL_OLD_ASP: |
543 | request_mem_region((unsigned long)LED_DATA_REG, 1, "led_data" ); |
544 | break; |
545 | } |
546 | } |
547 | |
548 | static int __init startup_leds(void) |
549 | { |
550 | if (platform_device_register(&platform_leds)) |
551 | printk(KERN_INFO "LED: failed to register LEDs\n" ); |
552 | register_led_regions(); |
553 | return 0; |
554 | } |
555 | device_initcall(startup_leds); |
556 | |