| /* |
| * include/asm-v850/rte_cb_leds.c -- Midas lab RTE-CB board LED device support |
| * |
| * Copyright (C) 2002,03 NEC Electronics Corporation |
| * Copyright (C) 2002,03 Miles Bader <miles@gnu.org> |
| * |
| * This file is subject to the terms and conditions of the GNU General |
| * Public License. See the file COPYING in the main directory of this |
| * archive for more details. |
| * |
| * Written by Miles Bader <miles@gnu.org> |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/spinlock.h> |
| #include <linux/fs.h> |
| #include <linux/miscdevice.h> |
| |
| #include <asm/uaccess.h> |
| |
| #define LEDS_MINOR 169 /* Minor device number, using misc major. */ |
| |
| /* The actual LED hardware is write-only, so we hold the contents here too. */ |
| static unsigned char leds_image[LED_NUM_DIGITS] = { 0 }; |
| |
| /* Spinlock protecting the above leds. */ |
| static DEFINE_SPINLOCK(leds_lock); |
| |
| /* Common body of LED read/write functions, checks POS and LEN for |
| correctness, declares a variable using IMG_DECL, initialized pointing at |
| the POS position in the LED image buffer, and and iterates COPY_EXPR |
| until BUF is equal to the last buffer position; finally, sets LEN to be |
| the amount actually copied. IMG should be a variable declaration |
| (without an initializer or a terminating semicolon); POS, BUF, and LEN |
| should all be simple variables. */ |
| #define DO_LED_COPY(img_decl, pos, buf, len, copy_expr) \ |
| do { \ |
| if (pos > LED_NUM_DIGITS) \ |
| len = 0; \ |
| else { \ |
| if (pos + len > LED_NUM_DIGITS) \ |
| len = LED_NUM_DIGITS - pos; \ |
| \ |
| if (len > 0) { \ |
| unsigned long _flags; \ |
| const char *_end = buf + len; \ |
| img_decl = &leds_image[pos]; \ |
| \ |
| spin_lock_irqsave (leds_lock, _flags); \ |
| do \ |
| (copy_expr); \ |
| while (buf != _end); \ |
| spin_unlock_irqrestore (leds_lock, _flags); \ |
| } \ |
| } \ |
| } while (0) |
| |
| /* Read LEN bytes from LEDs at position POS, into BUF. |
| Returns actual amount read. */ |
| unsigned read_leds (unsigned pos, char *buf, unsigned len) |
| { |
| DO_LED_COPY (const char *img, pos, buf, len, *buf++ = *img++); |
| return len; |
| } |
| |
| /* Write LEN bytes to LEDs at position POS, from BUF. |
| Returns actual amount written. */ |
| unsigned write_leds (unsigned pos, const char *buf, unsigned len) |
| { |
| /* We write the actual LED values backwards, because |
| increasing memory addresses reflect LEDs right-to-left. */ |
| volatile char *led = &LED (LED_NUM_DIGITS - pos - 1); |
| /* We invert the value written to the hardware, because 1 = off, |
| and 0 = on. */ |
| DO_LED_COPY (char *img, pos, buf, len, |
| *led-- = 0xFF ^ (*img++ = *buf++)); |
| return len; |
| } |
| |
| |
| /* Device functions. */ |
| |
| static ssize_t leds_dev_read (struct file *file, char *buf, size_t len, |
| loff_t *pos) |
| { |
| char temp_buf[LED_NUM_DIGITS]; |
| len = read_leds (*pos, temp_buf, len); |
| if (copy_to_user (buf, temp_buf, len)) |
| return -EFAULT; |
| *pos += len; |
| return len; |
| } |
| |
| static ssize_t leds_dev_write (struct file *file, const char *buf, size_t len, |
| loff_t *pos) |
| { |
| char temp_buf[LED_NUM_DIGITS]; |
| if (copy_from_user (temp_buf, buf, min_t(size_t, len, LED_NUM_DIGITS))) |
| return -EFAULT; |
| len = write_leds (*pos, temp_buf, len); |
| *pos += len; |
| return len; |
| } |
| |
| static loff_t leds_dev_lseek (struct file *file, loff_t offs, int whence) |
| { |
| if (whence == 1) |
| offs += file->f_pos; /* relative */ |
| else if (whence == 2) |
| offs += LED_NUM_DIGITS; /* end-relative */ |
| |
| if (offs < 0 || offs > LED_NUM_DIGITS) |
| return -EINVAL; |
| |
| file->f_pos = offs; |
| |
| return 0; |
| } |
| |
| static struct file_operations leds_fops = { |
| .read = leds_dev_read, |
| .write = leds_dev_write, |
| .llseek = leds_dev_lseek |
| }; |
| |
| static struct miscdevice leds_miscdev = { |
| .name = "leds", |
| .minor = LEDS_MINOR, |
| .fops = &leds_fops |
| }; |
| |
| int __init leds_dev_init (void) |
| { |
| return misc_register (&leds_miscdev); |
| } |
| |
| __initcall (leds_dev_init); |