Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * include/asm-v850/rte_cb_leds.c -- Midas lab RTE-CB board LED device support | |
3 | * | |
4 | * Copyright (C) 2002,03 NEC Electronics Corporation | |
5 | * Copyright (C) 2002,03 Miles Bader <miles@gnu.org> | |
6 | * | |
7 | * This file is subject to the terms and conditions of the GNU General | |
8 | * Public License. See the file COPYING in the main directory of this | |
9 | * archive for more details. | |
10 | * | |
11 | * Written by Miles Bader <miles@gnu.org> | |
12 | */ | |
13 | ||
1da177e4 LT |
14 | #include <linux/init.h> |
15 | #include <linux/spinlock.h> | |
16 | #include <linux/fs.h> | |
17 | #include <linux/miscdevice.h> | |
18 | ||
19 | #include <asm/uaccess.h> | |
20 | ||
21 | #define LEDS_MINOR 169 /* Minor device number, using misc major. */ | |
22 | ||
23 | /* The actual LED hardware is write-only, so we hold the contents here too. */ | |
24 | static unsigned char leds_image[LED_NUM_DIGITS] = { 0 }; | |
25 | ||
26 | /* Spinlock protecting the above leds. */ | |
27 | static DEFINE_SPINLOCK(leds_lock); | |
28 | ||
29 | /* Common body of LED read/write functions, checks POS and LEN for | |
30 | correctness, declares a variable using IMG_DECL, initialized pointing at | |
31 | the POS position in the LED image buffer, and and iterates COPY_EXPR | |
32 | until BUF is equal to the last buffer position; finally, sets LEN to be | |
33 | the amount actually copied. IMG should be a variable declaration | |
34 | (without an initializer or a terminating semicolon); POS, BUF, and LEN | |
35 | should all be simple variables. */ | |
36 | #define DO_LED_COPY(img_decl, pos, buf, len, copy_expr) \ | |
37 | do { \ | |
38 | if (pos > LED_NUM_DIGITS) \ | |
39 | len = 0; \ | |
40 | else { \ | |
41 | if (pos + len > LED_NUM_DIGITS) \ | |
42 | len = LED_NUM_DIGITS - pos; \ | |
43 | \ | |
44 | if (len > 0) { \ | |
c53421b1 | 45 | unsigned long _flags; \ |
1da177e4 LT |
46 | const char *_end = buf + len; \ |
47 | img_decl = &leds_image[pos]; \ | |
48 | \ | |
49 | spin_lock_irqsave (leds_lock, _flags); \ | |
50 | do \ | |
51 | (copy_expr); \ | |
52 | while (buf != _end); \ | |
53 | spin_unlock_irqrestore (leds_lock, _flags); \ | |
54 | } \ | |
55 | } \ | |
56 | } while (0) | |
57 | ||
58 | /* Read LEN bytes from LEDs at position POS, into BUF. | |
59 | Returns actual amount read. */ | |
60 | unsigned read_leds (unsigned pos, char *buf, unsigned len) | |
61 | { | |
62 | DO_LED_COPY (const char *img, pos, buf, len, *buf++ = *img++); | |
63 | return len; | |
64 | } | |
65 | ||
66 | /* Write LEN bytes to LEDs at position POS, from BUF. | |
67 | Returns actual amount written. */ | |
68 | unsigned write_leds (unsigned pos, const char *buf, unsigned len) | |
69 | { | |
70 | /* We write the actual LED values backwards, because | |
71 | increasing memory addresses reflect LEDs right-to-left. */ | |
72 | volatile char *led = &LED (LED_NUM_DIGITS - pos - 1); | |
73 | /* We invert the value written to the hardware, because 1 = off, | |
74 | and 0 = on. */ | |
75 | DO_LED_COPY (char *img, pos, buf, len, | |
76 | *led-- = 0xFF ^ (*img++ = *buf++)); | |
77 | return len; | |
78 | } | |
79 | ||
80 | \f | |
81 | /* Device functions. */ | |
82 | ||
83 | static ssize_t leds_dev_read (struct file *file, char *buf, size_t len, | |
84 | loff_t *pos) | |
85 | { | |
86 | char temp_buf[LED_NUM_DIGITS]; | |
87 | len = read_leds (*pos, temp_buf, len); | |
88 | if (copy_to_user (buf, temp_buf, len)) | |
89 | return -EFAULT; | |
90 | *pos += len; | |
91 | return len; | |
92 | } | |
93 | ||
94 | static ssize_t leds_dev_write (struct file *file, const char *buf, size_t len, | |
95 | loff_t *pos) | |
96 | { | |
97 | char temp_buf[LED_NUM_DIGITS]; | |
98 | if (copy_from_user (temp_buf, buf, min_t(size_t, len, LED_NUM_DIGITS))) | |
99 | return -EFAULT; | |
100 | len = write_leds (*pos, temp_buf, len); | |
101 | *pos += len; | |
102 | return len; | |
103 | } | |
104 | ||
105 | static loff_t leds_dev_lseek (struct file *file, loff_t offs, int whence) | |
106 | { | |
107 | if (whence == 1) | |
108 | offs += file->f_pos; /* relative */ | |
109 | else if (whence == 2) | |
110 | offs += LED_NUM_DIGITS; /* end-relative */ | |
111 | ||
112 | if (offs < 0 || offs > LED_NUM_DIGITS) | |
113 | return -EINVAL; | |
114 | ||
115 | file->f_pos = offs; | |
116 | ||
117 | return 0; | |
118 | } | |
119 | ||
5dfe4c96 | 120 | static const struct file_operations leds_fops = { |
1da177e4 LT |
121 | .read = leds_dev_read, |
122 | .write = leds_dev_write, | |
123 | .llseek = leds_dev_lseek | |
124 | }; | |
125 | ||
126 | static struct miscdevice leds_miscdev = { | |
127 | .name = "leds", | |
128 | .minor = LEDS_MINOR, | |
129 | .fops = &leds_fops | |
130 | }; | |
131 | ||
132 | int __init leds_dev_init (void) | |
133 | { | |
134 | return misc_register (&leds_miscdev); | |
135 | } | |
136 | ||
137 | __initcall (leds_dev_init); |