aboutsummaryrefslogtreecommitdiffstats
path: root/arch/v850/kernel/rte_cb_leds.c
blob: aa47ab1dcd87a866cf8ecb54000aef614bf8e70d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
 * 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 const 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);