* mydevice.c - LED Pattern Character Device Driver
* Drives three LEDs (GPIO17, GPIO27, GPIO22) in a configurable
* blink pattern. Controlled via /dev/mydevice, with sysfs and
* Target: Raspberry Pi Zero 2 W (BCM2710A1)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/jiffies.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#define DEVICE_NAME "mydevice"
#define CLASS_NAME "mydevice"
#define PROC_NAME "mydevice_stats"
/* GPIO pin assignments */
static int gpio_leds[NUM_LEDS] = { GPIO_LED1, GPIO_LED2, GPIO_LED3 };
static const char *gpio_labels[NUM_LEDS] = { "led1", "led2", "led3" };
static int blink_speed_ms = 500;
module_param(blink_speed_ms, int, 0644);
MODULE_PARM_DESC(blink_speed_ms, "LED blink interval in ms (default: 500)");
static struct cdev mydevice_cdev;
static struct class *mydevice_class;
static struct device *mydevice_device;
/* Pattern state (protected by pattern_mutex) */
static DEFINE_MUTEX(pattern_mutex);
static char pattern_str[MAX_PATTERN] = "101010";
static int pattern_len = 6;
static unsigned long cycle_count;
static unsigned long load_time_jiffies;
/* Kernel timer for LED blinking */
static struct timer_list blink_timer;
/* ----------------------------------------------------------------
* ---------------------------------------------------------------- */
static int setup_gpios(void)
for (i = 0; i < NUM_LEDS; i++) {
ret = gpio_request(gpio_leds[i], gpio_labels[i]);
pr_err("mydevice: GPIO %d request failed\n", gpio_leds[i]);
ret = gpio_direction_output(gpio_leds[i], 0);
pr_err("mydevice: GPIO %d direction set failed\n", gpio_leds[i]);
static void cleanup_gpios(void)
for (i = 0; i < NUM_LEDS; i++) {
gpio_set_value(gpio_leds[i], 0);
static void set_leds_from_pattern(int pos)
* Pattern string is read three bits at a time.
* Each character is '0' or '1'. If the pattern is shorter
* than 3 * position, it wraps around.
for (i = 0; i < NUM_LEDS; i++) {
int idx = (pos * NUM_LEDS + i) % pattern_len;
int val = (pattern_str[idx] == '1') ? 1 : 0;
gpio_set_value(gpio_leds[i], val);
/* ----------------------------------------------------------------
* ---------------------------------------------------------------- */
static void blink_timer_callback(struct timer_list *t)
mutex_lock(&pattern_mutex);
set_leds_from_pattern(pattern_pos);
if (pattern_pos * NUM_LEDS >= pattern_len) {
mutex_unlock(&pattern_mutex);
mod_timer(&blink_timer, jiffies + msecs_to_jiffies(blink_speed_ms));
/* ----------------------------------------------------------------
* ---------------------------------------------------------------- */
static int mydevice_open(struct inode *inode, struct file *filp)
pr_info("mydevice: device opened by pid %d\n", current->pid);
static int mydevice_release(struct inode *inode, struct file *filp)
pr_info("mydevice: device closed\n");
static ssize_t mydevice_read(struct file *filp, char __user *buf,
size_t count, loff_t *offset)
char tmp[MAX_PATTERN + 64];
mutex_lock(&pattern_mutex);
len = snprintf(tmp, sizeof(tmp), "pattern: %s\ncycles: %lu\n",
pattern_str, cycle_count);
mutex_unlock(&pattern_mutex);
if (count > len - *offset)
if (copy_to_user(buf, tmp + *offset, count))
static ssize_t mydevice_write(struct file *filp, const char __user *buf,
size_t count, loff_t *offset)
if (count == 0 || count >= MAX_PATTERN)
if (copy_from_user(tmp, buf, count))
/* Strip trailing newline */
if (tmp[count - 1] == '\n')
/* Validate: only '0' and '1' allowed */
for (i = 0; i < count; i++) {
if (tmp[i] != '0' && tmp[i] != '1')
mutex_lock(&pattern_mutex);
strncpy(pattern_str, tmp, MAX_PATTERN);
mutex_unlock(&pattern_mutex);
pr_info("mydevice: new pattern set: %s\n", pattern_str);
/* ioctl command definitions */
#define MYDEVICE_MAGIC 'M'
#define MYDEVICE_RESET_CYCLES _IO(MYDEVICE_MAGIC, 0)
#define MYDEVICE_GET_CYCLES _IOR(MYDEVICE_MAGIC, 1, unsigned long)
#define MYDEVICE_SET_SPEED _IOW(MYDEVICE_MAGIC, 2, int)
static long mydevice_ioctl(struct file *filp, unsigned int cmd,
case MYDEVICE_RESET_CYCLES:
mutex_lock(&pattern_mutex);
mutex_unlock(&pattern_mutex);
pr_info("mydevice: cycle counter reset\n");
case MYDEVICE_GET_CYCLES:
mutex_lock(&pattern_mutex);
if (copy_to_user((unsigned long __user *)arg,
&cycle_count, sizeof(cycle_count))) {
mutex_unlock(&pattern_mutex);
mutex_unlock(&pattern_mutex);
if ((int)arg < 10 || (int)arg > 10000)
blink_speed_ms = (int)arg;
pr_info("mydevice: blink speed set to %d ms\n", blink_speed_ms);
static const struct file_operations mydevice_fops = {
.release = mydevice_release,
.unlocked_ioctl = mydevice_ioctl,
/* ----------------------------------------------------------------
* sysfs attribute: blink_speed_ms
* ---------------------------------------------------------------- */
static ssize_t blink_speed_show(struct device *dev,
struct device_attribute *attr, char *buf)
return sprintf(buf, "%d\n", blink_speed_ms);
static ssize_t blink_speed_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
if (kstrtoint(buf, 10, &val))
if (val < 10 || val > 10000)
pr_info("mydevice: blink_speed_ms set to %d via sysfs\n", val);
static DEVICE_ATTR_RW(blink_speed);
/* ----------------------------------------------------------------
* procfs: /proc/mydevice_stats
* ---------------------------------------------------------------- */
static int mydevice_proc_show(struct seq_file *m, void *v)
unsigned long uptime_sec;
mutex_lock(&pattern_mutex);
uptime_sec = (jiffies - load_time_jiffies) / HZ;
seq_printf(m, "driver: mydevice\n");
seq_printf(m, "pattern: %s\n", pattern_str);
seq_printf(m, "pattern_len: %d\n", pattern_len);
seq_printf(m, "cycle_count: %lu\n", cycle_count);
seq_printf(m, "blink_speed: %d ms\n", blink_speed_ms);
seq_printf(m, "uptime: %lu seconds\n", uptime_sec);
seq_printf(m, "gpio_pins: %d, %d, %d\n",
GPIO_LED1, GPIO_LED2, GPIO_LED3);
mutex_unlock(&pattern_mutex);
static int mydevice_proc_open(struct inode *inode, struct file *file)
return single_open(file, mydevice_proc_show, NULL);
static const struct proc_ops mydevice_proc_ops = {
.proc_open = mydevice_proc_open,
.proc_release = single_release,
/* ----------------------------------------------------------------
* ---------------------------------------------------------------- */
static int __init mydevice_init(void)
load_time_jiffies = jiffies;
/* 2. Allocate character device region */
ret = alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME);
pr_err("mydevice: alloc_chrdev_region failed\n");
/* 3. Initialize and add cdev */
cdev_init(&mydevice_cdev, &mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
ret = cdev_add(&mydevice_cdev, dev_number, 1);
pr_err("mydevice: cdev_add failed\n");
/* 4. Create device class */
mydevice_class = class_create(CLASS_NAME);
if (IS_ERR(mydevice_class)) {
pr_err("mydevice: class_create failed\n");
ret = PTR_ERR(mydevice_class);
/* 5. Create device node */
mydevice_device = device_create(mydevice_class, NULL, dev_number,
if (IS_ERR(mydevice_device)) {
pr_err("mydevice: device_create failed\n");
ret = PTR_ERR(mydevice_device);
/* 6. Create sysfs attribute */
ret = device_create_file(mydevice_device, &dev_attr_blink_speed);
pr_err("mydevice: sysfs attribute creation failed\n");
/* 7. Create procfs entry */
if (!proc_create(PROC_NAME, 0444, NULL, &mydevice_proc_ops)) {
pr_err("mydevice: proc_create failed\n");
/* 8. Start the blink timer */
timer_setup(&blink_timer, blink_timer_callback, 0);
mod_timer(&blink_timer, jiffies + msecs_to_jiffies(blink_speed_ms));
pr_info("mydevice: loaded (major=%d, blink_speed=%d ms)\n",
MAJOR(dev_number), blink_speed_ms);
device_remove_file(mydevice_device, &dev_attr_blink_speed);
device_destroy(mydevice_class, dev_number);
class_destroy(mydevice_class);
cdev_del(&mydevice_cdev);
unregister_chrdev_region(dev_number, 1);
static void __exit mydevice_exit(void)
del_timer_sync(&blink_timer);
remove_proc_entry(PROC_NAME, NULL);
device_remove_file(mydevice_device, &dev_attr_blink_speed);
device_destroy(mydevice_class, dev_number);
class_destroy(mydevice_class);
cdev_del(&mydevice_cdev);
unregister_chrdev_region(dev_number, 1);
pr_info("mydevice: unloaded\n");
module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("LED pattern character device driver");
Comments