1 // SPDX-License-Identifier: GPL-2.0-only 2 #include <linux/kernel.h> 3 #include <linux/module.h> 4 #include <linux/init.h> 5 #include <linux/proc_fs.h> 6 #include <linux/seq_file.h> 7 #include <linux/slab.h> 8 #include <linux/string.h> 9 #include <linux/jiffies.h> 10 #include <linux/timer.h> 11 #include <linux/uaccess.h> 12 #include <linux/sched/loadavg.h> 13 14 #include <asm/auxio.h> 15 16 #define LED_MAX_LENGTH 8 /* maximum chars written to proc file */ 17 18 static inline void led_toggle(void) 19 { 20 unsigned char val = get_auxio(); 21 unsigned char on, off; 22 23 if (val & AUXIO_LED) { 24 on = 0; 25 off = AUXIO_LED; 26 } else { 27 on = AUXIO_LED; 28 off = 0; 29 } 30 31 set_auxio(on, off); 32 } 33 34 static struct timer_list led_blink_timer; 35 static unsigned long led_blink_timer_timeout; 36 37 static void led_blink(struct timer_list *unused) 38 { 39 unsigned long timeout = led_blink_timer_timeout; 40 41 led_toggle(); 42 43 /* reschedule */ 44 if (!timeout) { /* blink according to load */ 45 led_blink_timer.expires = jiffies + 46 ((1 + (avenrun[0] >> FSHIFT)) * HZ); 47 } else { /* blink at user specified interval */ 48 led_blink_timer.expires = jiffies + (timeout * HZ); 49 } 50 add_timer(&led_blink_timer); 51 } 52 53 #ifdef CONFIG_PROC_FS 54 static int led_proc_show(struct seq_file *m, void *v) 55 { 56 if (get_auxio() & AUXIO_LED) 57 seq_puts(m, "on\n"); 58 else 59 seq_puts(m, "off\n"); 60 return 0; 61 } 62 63 static int led_proc_open(struct inode *inode, struct file *file) 64 { 65 return single_open(file, led_proc_show, NULL); 66 } 67 68 static ssize_t led_proc_write(struct file *file, const char __user *buffer, 69 size_t count, loff_t *ppos) 70 { 71 char *buf = NULL; 72 73 if (count > LED_MAX_LENGTH) 74 count = LED_MAX_LENGTH; 75 76 buf = memdup_user_nul(buffer, count); 77 if (IS_ERR(buf)) 78 return PTR_ERR(buf); 79 80 /* work around \n when echo'ing into proc */ 81 if (buf[count - 1] == '\n') 82 buf[count - 1] = '\0'; 83 84 /* before we change anything we want to stop any running timers, 85 * otherwise calls such as on will have no persistent effect 86 */ 87 del_timer_sync(&led_blink_timer); 88 89 if (!strcmp(buf, "on")) { 90 auxio_set_led(AUXIO_LED_ON); 91 } else if (!strcmp(buf, "toggle")) { 92 led_toggle(); 93 } else if ((*buf > '0') && (*buf <= '9')) { 94 led_blink_timer_timeout = simple_strtoul(buf, NULL, 10); 95 led_blink(&led_blink_timer); 96 } else if (!strcmp(buf, "load")) { 97 led_blink_timer_timeout = 0; 98 led_blink(&led_blink_timer); 99 } else { 100 auxio_set_led(AUXIO_LED_OFF); 101 } 102 103 kfree(buf); 104 105 return count; 106 } 107 108 static const struct proc_ops led_proc_ops = { 109 .proc_open = led_proc_open, 110 .proc_read = seq_read, 111 .proc_lseek = seq_lseek, 112 .proc_release = single_release, 113 .proc_write = led_proc_write, 114 }; 115 #endif 116 117 static struct proc_dir_entry *led; 118 119 #define LED_VERSION "0.1" 120 121 static int __init led_init(void) 122 { 123 timer_setup(&led_blink_timer, led_blink, 0); 124 125 led = proc_create("led", 0, NULL, &led_proc_ops); 126 if (!led) 127 return -ENOMEM; 128 129 printk(KERN_INFO 130 "led: version %s, Lars Kotthoff <metalhead@metalhead.ws>\n", 131 LED_VERSION); 132 133 return 0; 134 } 135 136 static void __exit led_exit(void) 137 { 138 remove_proc_entry("led", NULL); 139 del_timer_sync(&led_blink_timer); 140 } 141 142 module_init(led_init); 143 module_exit(led_exit); 144 145 MODULE_AUTHOR("Lars Kotthoff <metalhead@metalhead.ws>"); 146 MODULE_DESCRIPTION("Provides control of the front LED on SPARC systems."); 147 MODULE_LICENSE("GPL"); 148 MODULE_VERSION(LED_VERSION); 149