| 1 | /* |
| 2 | * LED Morse Trigger |
| 3 | * |
| 4 | * Copyright (C) 2007 Gabor Juhos <juhosg at openwrt.org> |
| 5 | * |
| 6 | * This file was based on: drivers/led/ledtrig-timer.c |
| 7 | * Copyright 2005-2006 Openedhand Ltd. |
| 8 | * Author: Richard Purdie <rpurdie@openedhand.com> |
| 9 | * |
| 10 | * also based on the patch '[PATCH] 2.5.59 morse code panics' posted |
| 11 | * in the LKML by Tomas Szepe at Thu, 30 Jan 2003 |
| 12 | * Copyright (C) 2002 Andrew Rodland <arodland@noln.com> |
| 13 | * Copyright (C) 2003 Tomas Szepe <szepe@pinerecords.com> |
| 14 | * |
| 15 | * This program is free software; you can redistribute it and/or modify it |
| 16 | * under the terms of the GNU General Public License version 2 as published |
| 17 | * by the Free Software Foundation. |
| 18 | * |
| 19 | */ |
| 20 | |
| 21 | #include <linux/kernel.h> |
| 22 | #include <linux/version.h> |
| 23 | #include <linux/module.h> |
| 24 | #include <linux/jiffies.h> |
| 25 | #include <linux/init.h> |
| 26 | #include <linux/list.h> |
| 27 | #include <linux/spinlock.h> |
| 28 | #include <linux/device.h> |
| 29 | #include <linux/sysdev.h> |
| 30 | #include <linux/timer.h> |
| 31 | #include <linux/ctype.h> |
| 32 | #include <linux/leds.h> |
| 33 | #include <linux/slab.h> |
| 34 | |
| 35 | #include "leds.h" |
| 36 | |
| 37 | #define MORSE_DELAY_BASE (HZ/2) |
| 38 | |
| 39 | #define MORSE_STATE_BLINK_START 0 |
| 40 | #define MORSE_STATE_BLINK_STOP 1 |
| 41 | |
| 42 | #define MORSE_DIT_LEN 1 |
| 43 | #define MORSE_DAH_LEN 3 |
| 44 | #define MORSE_SPACE_LEN 7 |
| 45 | |
| 46 | struct morse_trig_data { |
| 47 | unsigned long delay; |
| 48 | char *msg; |
| 49 | |
| 50 | unsigned char morse; |
| 51 | unsigned char state; |
| 52 | char *msgpos; |
| 53 | struct timer_list timer; |
| 54 | }; |
| 55 | |
| 56 | const unsigned char morsetable[] = { |
| 57 | 0122, 0, 0310, 0, 0, 0163, /* "#$%&' */ |
| 58 | 055, 0155, 0, 0, 0163, 0141, 0152, 0051, /* ()*+,-./ */ |
| 59 | 077, 076, 074, 070, 060, 040, 041, 043, 047, 057, /* 0-9 */ |
| 60 | 0107, 0125, 0, 0061, 0, 0114, 0, /* :;<=>?@ */ |
| 61 | 006, 021, 025, 011, 002, 024, 013, 020, 004, /* A-I */ |
| 62 | 036, 015, 022, 007, 005, 017, 026, 033, 012, /* J-R */ |
| 63 | 010, 003, 014, 030, 016, 031, 035, 023, /* S-Z */ |
| 64 | 0, 0, 0, 0, 0154 /* [\]^_ */ |
| 65 | }; |
| 66 | |
| 67 | static inline unsigned char tomorse(char c) { |
| 68 | if (c >= 'a' && c <= 'z') |
| 69 | c = c - 'a' + 'A'; |
| 70 | if (c >= '"' && c <= '_') { |
| 71 | return morsetable[c - '"']; |
| 72 | } else |
| 73 | return 0; |
| 74 | } |
| 75 | |
| 76 | static inline unsigned long dit_len(struct morse_trig_data *morse_data) |
| 77 | { |
| 78 | return MORSE_DIT_LEN*morse_data->delay; |
| 79 | } |
| 80 | |
| 81 | static inline unsigned long dah_len(struct morse_trig_data *morse_data) |
| 82 | { |
| 83 | return MORSE_DAH_LEN*morse_data->delay; |
| 84 | } |
| 85 | |
| 86 | static inline unsigned long space_len(struct morse_trig_data *morse_data) |
| 87 | { |
| 88 | return MORSE_SPACE_LEN*morse_data->delay; |
| 89 | } |
| 90 | |
| 91 | static void morse_timer_function(unsigned long data) |
| 92 | { |
| 93 | struct led_classdev *led_cdev = (struct led_classdev *)data; |
| 94 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 95 | unsigned long brightness = LED_OFF; |
| 96 | unsigned long delay = 0; |
| 97 | |
| 98 | if (!morse_data->msg) |
| 99 | goto set_led; |
| 100 | |
| 101 | switch (morse_data->state) { |
| 102 | case MORSE_STATE_BLINK_START: |
| 103 | /* Starting a new blink. We have a valid code in morse. */ |
| 104 | delay = (morse_data->morse & 001) ? dah_len(morse_data): |
| 105 | dit_len(morse_data); |
| 106 | brightness = LED_FULL; |
| 107 | morse_data->state = MORSE_STATE_BLINK_STOP; |
| 108 | morse_data->morse >>= 1; |
| 109 | break; |
| 110 | case MORSE_STATE_BLINK_STOP: |
| 111 | /* Coming off of a blink. */ |
| 112 | morse_data->state = MORSE_STATE_BLINK_START; |
| 113 | |
| 114 | if (morse_data->morse > 1) { |
| 115 | /* Not done yet, just a one-dit pause. */ |
| 116 | delay = dit_len(morse_data); |
| 117 | break; |
| 118 | } |
| 119 | |
| 120 | /* Get a new char, figure out how much space. */ |
| 121 | /* First time through */ |
| 122 | if (!morse_data->msgpos) |
| 123 | morse_data->msgpos = (char *)morse_data->msg; |
| 124 | |
| 125 | if (!*morse_data->msgpos) { |
| 126 | /* Repeating */ |
| 127 | morse_data->msgpos = (char *)morse_data->msg; |
| 128 | delay = space_len(morse_data); |
| 129 | } else { |
| 130 | /* Inter-letter space */ |
| 131 | delay = dah_len(morse_data); |
| 132 | } |
| 133 | |
| 134 | if (!(morse_data->morse = tomorse(*morse_data->msgpos))) { |
| 135 | delay = space_len(morse_data); |
| 136 | /* And get us back here */ |
| 137 | morse_data->state = MORSE_STATE_BLINK_STOP; |
| 138 | } |
| 139 | morse_data->msgpos++; |
| 140 | break; |
| 141 | } |
| 142 | |
| 143 | mod_timer(&morse_data->timer, jiffies + msecs_to_jiffies(delay)); |
| 144 | |
| 145 | set_led: |
| 146 | led_set_brightness(led_cdev, brightness); |
| 147 | } |
| 148 | |
| 149 | static ssize_t _morse_delay_show(struct led_classdev *led_cdev, char *buf) |
| 150 | { |
| 151 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 152 | |
| 153 | sprintf(buf, "%lu\n", morse_data->delay); |
| 154 | |
| 155 | return strlen(buf) + 1; |
| 156 | } |
| 157 | |
| 158 | static ssize_t _morse_delay_store(struct led_classdev *led_cdev, |
| 159 | const char *buf, size_t size) |
| 160 | { |
| 161 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 162 | char *after; |
| 163 | unsigned long state = simple_strtoul(buf, &after, 10); |
| 164 | size_t count = after - buf; |
| 165 | int ret = -EINVAL; |
| 166 | |
| 167 | if (*after && isspace(*after)) |
| 168 | count++; |
| 169 | |
| 170 | if (count == size) { |
| 171 | morse_data->delay = state; |
| 172 | mod_timer(&morse_data->timer, jiffies + 1); |
| 173 | ret = count; |
| 174 | } |
| 175 | |
| 176 | return ret; |
| 177 | } |
| 178 | |
| 179 | static ssize_t _morse_msg_show(struct led_classdev *led_cdev, char *buf) |
| 180 | { |
| 181 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 182 | |
| 183 | if (!morse_data->msg) |
| 184 | sprintf(buf, "<none>\n"); |
| 185 | else |
| 186 | sprintf(buf, "%s\n", morse_data->msg); |
| 187 | |
| 188 | return strlen(buf) + 1; |
| 189 | } |
| 190 | |
| 191 | static ssize_t _morse_msg_store(struct led_classdev *led_cdev, |
| 192 | const char *buf, size_t size) |
| 193 | { |
| 194 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 195 | char *m; |
| 196 | |
| 197 | m = kmalloc(size, GFP_KERNEL); |
| 198 | if (!m) |
| 199 | return -ENOMEM; |
| 200 | |
| 201 | memcpy(m,buf,size); |
| 202 | m[size]='\0'; |
| 203 | |
| 204 | if (morse_data->msg) |
| 205 | kfree(morse_data->msg); |
| 206 | |
| 207 | morse_data->msg = m; |
| 208 | morse_data->msgpos = NULL; |
| 209 | morse_data->state = MORSE_STATE_BLINK_STOP; |
| 210 | |
| 211 | mod_timer(&morse_data->timer, jiffies + 1); |
| 212 | |
| 213 | return size; |
| 214 | } |
| 215 | |
| 216 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23) |
| 217 | static ssize_t morse_delay_show(struct device *dev, |
| 218 | struct device_attribute *attr, char *buf) |
| 219 | { |
| 220 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 221 | |
| 222 | return _morse_delay_show(led_cdev, buf); |
| 223 | } |
| 224 | |
| 225 | static ssize_t morse_delay_store(struct device *dev, |
| 226 | struct device_attribute *attr, const char *buf, size_t size) |
| 227 | { |
| 228 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 229 | |
| 230 | return _morse_delay_store(led_cdev, buf, size); |
| 231 | } |
| 232 | |
| 233 | static ssize_t morse_msg_show(struct device *dev, |
| 234 | struct device_attribute *attr, char *buf) |
| 235 | { |
| 236 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 237 | |
| 238 | return _morse_msg_show(led_cdev, buf); |
| 239 | } |
| 240 | |
| 241 | static ssize_t morse_msg_store(struct device *dev, |
| 242 | struct device_attribute *attr, const char *buf, size_t size) |
| 243 | { |
| 244 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 245 | |
| 246 | return _morse_msg_store(led_cdev, buf, size); |
| 247 | } |
| 248 | |
| 249 | static DEVICE_ATTR(delay, 0644, morse_delay_show, morse_delay_store); |
| 250 | static DEVICE_ATTR(message, 0644, morse_msg_show, morse_msg_store); |
| 251 | |
| 252 | #define led_device_create_file(leddev, attr) \ |
| 253 | device_create_file(leddev->dev, &dev_attr_ ## attr) |
| 254 | #define led_device_remove_file(leddev, attr) \ |
| 255 | device_remove_file(leddev->dev, &dev_attr_ ## attr) |
| 256 | |
| 257 | #else |
| 258 | static ssize_t morse_delay_show(struct class_device *dev, char *buf) |
| 259 | { |
| 260 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 261 | |
| 262 | return _morse_delay_show(led_cdev, buf); |
| 263 | } |
| 264 | |
| 265 | static ssize_t morse_delay_store(struct class_device *dev, const char *buf, |
| 266 | size_t size) |
| 267 | { |
| 268 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 269 | |
| 270 | return _morse_delay_store(led_cdev, buf, size); |
| 271 | } |
| 272 | |
| 273 | static ssize_t morse_msg_show(struct class_device *dev, char *buf) |
| 274 | { |
| 275 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 276 | |
| 277 | return _morse_msg_show(led_cdev, buf); |
| 278 | } |
| 279 | |
| 280 | static ssize_t morse_msg_store(struct class_device *dev, const char *buf, |
| 281 | size_t size) |
| 282 | { |
| 283 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 284 | |
| 285 | return _morse_msg_store(led_cdev, buf, size); |
| 286 | } |
| 287 | |
| 288 | static CLASS_DEVICE_ATTR(delay, 0644, morse_delay_show, morse_delay_store); |
| 289 | static CLASS_DEVICE_ATTR(message, 0644, morse_msg_show, morse_msg_store); |
| 290 | |
| 291 | #define led_device_create_file(leddev, attr) \ |
| 292 | class_device_create_file(leddev->class_dev, &class_device_attr_ ## attr) |
| 293 | #define led_device_remove_file(leddev, attr) \ |
| 294 | class_device_remove_file(leddev->class_dev, &class_device_attr_ ## attr) |
| 295 | |
| 296 | #endif |
| 297 | |
| 298 | static void morse_trig_activate(struct led_classdev *led_cdev) |
| 299 | { |
| 300 | struct morse_trig_data *morse_data; |
| 301 | int rc; |
| 302 | |
| 303 | morse_data = kzalloc(sizeof(*morse_data), GFP_KERNEL); |
| 304 | if (!morse_data) |
| 305 | return; |
| 306 | |
| 307 | morse_data->delay = MORSE_DELAY_BASE; |
| 308 | init_timer(&morse_data->timer); |
| 309 | morse_data->timer.function = morse_timer_function; |
| 310 | morse_data->timer.data = (unsigned long)led_cdev; |
| 311 | |
| 312 | rc = led_device_create_file(led_cdev, delay); |
| 313 | if (rc) goto err; |
| 314 | |
| 315 | rc = led_device_create_file(led_cdev, message); |
| 316 | if (rc) goto err_delay; |
| 317 | |
| 318 | led_cdev->trigger_data = morse_data; |
| 319 | |
| 320 | return; |
| 321 | |
| 322 | err_delay: |
| 323 | led_device_remove_file(led_cdev, delay); |
| 324 | err: |
| 325 | kfree(morse_data); |
| 326 | } |
| 327 | |
| 328 | static void morse_trig_deactivate(struct led_classdev *led_cdev) |
| 329 | { |
| 330 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 331 | |
| 332 | if (!morse_data) |
| 333 | return; |
| 334 | |
| 335 | led_device_remove_file(led_cdev, message); |
| 336 | led_device_remove_file(led_cdev, delay); |
| 337 | |
| 338 | del_timer_sync(&morse_data->timer); |
| 339 | if (morse_data->msg) |
| 340 | kfree(morse_data->msg); |
| 341 | |
| 342 | kfree(morse_data); |
| 343 | } |
| 344 | |
| 345 | static struct led_trigger morse_led_trigger = { |
| 346 | .name = "morse", |
| 347 | .activate = morse_trig_activate, |
| 348 | .deactivate = morse_trig_deactivate, |
| 349 | }; |
| 350 | |
| 351 | static int __init morse_trig_init(void) |
| 352 | { |
| 353 | return led_trigger_register(&morse_led_trigger); |
| 354 | } |
| 355 | |
| 356 | static void __exit morse_trig_exit(void) |
| 357 | { |
| 358 | led_trigger_unregister(&morse_led_trigger); |
| 359 | } |
| 360 | |
| 361 | module_init(morse_trig_init); |
| 362 | module_exit(morse_trig_exit); |
| 363 | |
| 364 | MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); |
| 365 | MODULE_DESCRIPTION("Morse LED trigger"); |
| 366 | MODULE_LICENSE("GPL"); |
| 367 | |