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.  */
24static unsigned char leds_image[LED_NUM_DIGITS] = { 0 };
25
26/* Spinlock protecting the above leds.  */
27static 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)			\
37do {									\
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.  */
60unsigned 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.  */
68unsigned 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
81/* Device functions.  */
82
83static 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
94static 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
105static 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
120static const struct file_operations leds_fops = {
121	.read		= leds_dev_read,
122	.write		= leds_dev_write,
123	.llseek		= leds_dev_lseek
124};
125
126static struct miscdevice leds_miscdev = {
127	.name		= "leds",
128	.minor		= LEDS_MINOR,
129	.fops		= &leds_fops
130};
131
132int __init leds_dev_init (void)
133{
134	return misc_register (&leds_miscdev);
135}
136
137__initcall (leds_dev_init);
138