2 * Userspace driver for the LED subsystem
4 * Copyright (C) 2016 David Lechner <david@lechnology.com>
6 * Based on uinput.c: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
19 #include <linux/init.h>
20 #include <linux/leds.h>
21 #include <linux/miscdevice.h>
22 #include <linux/module.h>
23 #include <linux/poll.h>
24 #include <linux/sched.h>
25 #include <linux/slab.h>
27 #include <uapi/linux/uleds.h>
29 #define ULEDS_NAME "uleds"
33 ULEDS_STATE_REGISTERED
,
37 struct uleds_user_dev user_dev
;
38 struct led_classdev led_cdev
;
40 enum uleds_state state
;
41 wait_queue_head_t waitq
;
46 static struct miscdevice uleds_misc
;
48 static void uleds_brightness_set(struct led_classdev
*led_cdev
,
49 enum led_brightness brightness
)
51 struct uleds_device
*udev
= container_of(led_cdev
, struct uleds_device
,
54 if (udev
->brightness
!= brightness
) {
55 udev
->brightness
= brightness
;
56 udev
->new_data
= true;
57 wake_up_interruptible(&udev
->waitq
);
61 static int uleds_open(struct inode
*inode
, struct file
*file
)
63 struct uleds_device
*udev
;
65 udev
= kzalloc(sizeof(*udev
), GFP_KERNEL
);
69 udev
->led_cdev
.name
= udev
->user_dev
.name
;
70 udev
->led_cdev
.brightness_set
= uleds_brightness_set
;
72 mutex_init(&udev
->mutex
);
73 init_waitqueue_head(&udev
->waitq
);
74 udev
->state
= ULEDS_STATE_UNKNOWN
;
76 file
->private_data
= udev
;
77 nonseekable_open(inode
, file
);
82 static ssize_t
uleds_write(struct file
*file
, const char __user
*buffer
,
83 size_t count
, loff_t
*ppos
)
85 struct uleds_device
*udev
= file
->private_data
;
92 ret
= mutex_lock_interruptible(&udev
->mutex
);
96 if (udev
->state
== ULEDS_STATE_REGISTERED
) {
101 if (count
!= sizeof(struct uleds_user_dev
)) {
106 if (copy_from_user(&udev
->user_dev
, buffer
,
107 sizeof(struct uleds_user_dev
))) {
112 name
= udev
->user_dev
.name
;
113 if (!name
[0] || !strcmp(name
, ".") || !strcmp(name
, "..") ||
119 if (udev
->user_dev
.max_brightness
<= 0) {
123 udev
->led_cdev
.max_brightness
= udev
->user_dev
.max_brightness
;
125 ret
= devm_led_classdev_register(uleds_misc
.this_device
,
130 udev
->new_data
= true;
131 udev
->state
= ULEDS_STATE_REGISTERED
;
135 mutex_unlock(&udev
->mutex
);
140 static ssize_t
uleds_read(struct file
*file
, char __user
*buffer
, size_t count
,
143 struct uleds_device
*udev
= file
->private_data
;
146 if (count
< sizeof(udev
->brightness
))
150 retval
= mutex_lock_interruptible(&udev
->mutex
);
154 if (udev
->state
!= ULEDS_STATE_REGISTERED
) {
156 } else if (!udev
->new_data
&& (file
->f_flags
& O_NONBLOCK
)) {
158 } else if (udev
->new_data
) {
159 retval
= copy_to_user(buffer
, &udev
->brightness
,
160 sizeof(udev
->brightness
));
161 udev
->new_data
= false;
162 retval
= sizeof(udev
->brightness
);
165 mutex_unlock(&udev
->mutex
);
170 if (!(file
->f_flags
& O_NONBLOCK
))
171 retval
= wait_event_interruptible(udev
->waitq
,
173 udev
->state
!= ULEDS_STATE_REGISTERED
);
174 } while (retval
== 0);
179 static unsigned int uleds_poll(struct file
*file
, poll_table
*wait
)
181 struct uleds_device
*udev
= file
->private_data
;
183 poll_wait(file
, &udev
->waitq
, wait
);
186 return POLLIN
| POLLRDNORM
;
191 static int uleds_release(struct inode
*inode
, struct file
*file
)
193 struct uleds_device
*udev
= file
->private_data
;
195 if (udev
->state
== ULEDS_STATE_REGISTERED
) {
196 udev
->state
= ULEDS_STATE_UNKNOWN
;
197 devm_led_classdev_unregister(uleds_misc
.this_device
,
205 static const struct file_operations uleds_fops
= {
206 .owner
= THIS_MODULE
,
208 .release
= uleds_release
,
210 .write
= uleds_write
,
215 static struct miscdevice uleds_misc
= {
217 .minor
= MISC_DYNAMIC_MINOR
,
221 static int __init
uleds_init(void)
223 return misc_register(&uleds_misc
);
225 module_init(uleds_init
);
227 static void __exit
uleds_exit(void)
229 misc_deregister(&uleds_misc
);
231 module_exit(uleds_exit
);
233 MODULE_AUTHOR("David Lechner <david@lechnology.com>");
234 MODULE_DESCRIPTION("Userspace driver for the LED subsystem");
235 MODULE_LICENSE("GPL");