Merge branch 'cris' of git://www.jni.nu/cris
[linux-2.6] / arch / v850 / kernel / rte_cb_leds.c
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
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) {                                          \
45                         unsigned long _flags;                           \
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
120 static const struct file_operations leds_fops = {
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);