Implementing GPIO LED Control Using the Linux Platform Bus

Driver Source Code

The following implementation utilizes the platform bus architecture to manage GPIO-based LEDs. It establihses a character device interface and parses device tree information during the probe phase.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include "led_control.h"

static struct gpio_desc *led_pins[3];
static struct timer_list blink_timer;
static struct device_node *tree_node;
static unsigned int dev_major = 0;
static unsigned int dev_minor = 0;
static dev_t device_id;
static struct cdev *led_cdev;
static char kernel_buffer[128];
static struct class *led_class;
static struct device *led_device;
static struct resource *mem_resource;
static unsigned int interrupt_number;

static int led_device_open(struct inode *inode, struct file *filp)
{
    dev_t dev_id = inode->i_rdev;
    filp->private_data = (void *)(MINOR(dev_id));
    printk(KERN_INFO "%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

static ssize_t led_device_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "%s:%s:%d\n", __FILE__, __func__, __LINE__);
    unsigned long missing;
    if (count > sizeof(kernel_buffer))
        count = sizeof(kernel_buffer);
    missing = copy_to_user(buf, kernel_buffer, count);
    if (missing) {
        printk(KERN_ERR "copy_to_user failed\n");
        return -EFAULT;
    }
    return 0;
}

static ssize_t led_device_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long missing;
    if (count > sizeof(kernel_buffer))
        count = sizeof(kernel_buffer);
    missing = copy_from_user(kernel_buffer, buf, count);
    if (missing) {
        printk(KERN_ERR "copy_from_user failed\n");
        return -EFAULT;
    }
    return 0;
}

static long led_device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int led_index;
    int ret = copy_from_user(&led_index, (void __user *)arg, sizeof(int));
    if (ret) return -EFAULT;

    if (led_index < 0 || led_index > 2) return -EINVAL;

    switch (cmd) {
    case CMD_LED_ON:
        gpiod_set_value(led_pins[led_index], 1);
        break;
    case CMD_LED_OFF:
        gpiod_set_value(led_pins[led_index], 0);
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

static int led_device_release(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

static const struct file_operations led_fops = {
    .unlocked_ioctl = led_device_ioctl,
    .open = led_device_open,
    .read = led_device_read,
    .write = led_device_write,
    .release = led_device_release,
};

static int platform_led_probe(struct platform_device *pdev)
{
    printk(KERN_INFO "%s:%s:%d\n", __FILE__, __func__, __LINE__);
    mem_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!mem_resource) {
        printk(KERN_ERR "Failed to get MEM resource\n");
        return -EFAULT;
    }
    printk(KERN_INFO "MEM resource start: %x\n", mem_resource->start);

    interrupt_number = platform_get_irq(pdev, 0);
    if (interrupt_number < 0) {
        printk(KERN_ERR "Failed to get IRQ resource\n");
        return interrupt_number;
    }
    printk(KERN_INFO "IRQ number: %d\n", interrupt_number);

    int ret;
    led_cdev = cdev_alloc();
    if (!led_cdev) return -ENOMEM;
    printk(KERN_INFO "cdev allocated\n");

    cdev_init(led_cdev, &led_fops);

    ret = alloc_chrdev_region(&device_id, 0, 3, "platform_led");
    if (ret) {
        printk(KERN_ERR "Failed to allocate char dev region\n");
        goto fail_alloc;
    }
    dev_major = MAJOR(device_id);
    dev_minor = MINOR(device_id);
    printk(KERN_INFO "Char dev region allocated\n");

    ret = cdev_add(led_cdev, device_id, 3);
    if (ret) {
        printk(KERN_ERR "Failed to add cdev\n");
        goto fail_add;
    }
    printk(KERN_INFO "cdev added\n");

    led_class = class_create(THIS_MODULE, "led_class");
    if (IS_ERR(led_class)) {
        ret = -PTR_ERR(led_class);
        printk(KERN_ERR "Failed to create class\n");
        goto fail_class;
    }
    printk(KERN_INFO "Class created\n");

    for (int i = 0; i < 3; i++) {
        led_device = device_create(led_class, NULL, MKDEV(dev_major, i), NULL, "platform_led%d", i);
        if (IS_ERR(led_device)) {
            ret = -PTR_ERR(led_device);
            printk(KERN_ERR "Failed to create device %d\n", i);
            goto fail_device;
        }
    }
    printk(KERN_INFO "Devices created\n");

    // Initialize GPIOs
    for (int i = 0; i < 3; i++) {
        char prop_name[16];
        snprintf(prop_name, sizeof(prop_name), "led%d-gpio", i);
        led_pins[i] = gpiod_get_from_of_node(pdev->dev.of_node, prop_name, 0, GPIOD_OUT_LOW, NULL);
        if (IS_ERR(led_pins[i])) {
            printk(KERN_ERR "Failed to get GPIO for %s\n", prop_name);
            ret = -ENXIO;
            goto fail_gpio;
        }
    }
    printk(KERN_INFO "GPIOs initialized\n");

    return 0;

fail_gpio:
    while (--i >= 0) gpiod_put(led_pins[i]);
fail_device:
    while (--i >= 0) device_destroy(led_class, MKDEV(dev_major, i));
    class_destroy(led_class);
fail_class:
    cdev_del(led_cdev);
fail_add:
    unregister_chrdev_region(device_id, 3);
fail_alloc:
    kfree(led_cdev);
    return ret;
}

static int platform_led_remove(struct platform_device *pdev)
{
    printk(KERN_INFO "%s:%s:%d\n", __FILE__, __func__, __LINE__);
    for (int i = 0; i < 3; i++) {
        gpiod_put(led_pins[i]);
        device_destroy(led_class, MKDEV(dev_major, i));
    }
    class_destroy(led_class);
    cdev_del(led_cdev);
    unregister_chrdev_region(device_id, 3);
    kfree(led_cdev);
    del_timer(&blink_timer);
    return 0;
}

static const struct of_device_id led_of_match[] = {
    { .compatible = "vendor,platform-led" },
    { }
};

static struct platform_driver platform_led_driver = {
    .probe = platform_led_probe,
    .remove = platform_led_remove,
    .driver = {
        .name = "platform_led_drv",
        .of_match_table = led_of_match,
    },
};

module_platform_driver(platform_led_driver);
MODULE_LICENSE("GPL");

Header File Definitions

#ifndef _LED_CONTROL_H
#define _LED_CONTROL_H

#include <linux/ioctl.h>

#define CMD_LED_ON  _IOW('L', 1, int)
#define CMD_LED_OFF _IOW('L', 0, int)

#endif

User Space Application

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "led_control.h"

int main(int argc, char *argv[])
{
    int fd, choice, led_id;
    fd = open("/dev/platform_led0", O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        exit(EXIT_FAILURE);
    }

    while (1) {
        printf("Select Action: 0 (OFF), 1 (ON)\n> ");
        scanf("%d", &choice);
        printf("Select LED ID: 0, 1, 2\n> ");
        scanf("%d", &led_id);

        if (choice == 1) {
            ioctl(fd, CMD_LED_ON, &led_id);
        } else {
            ioctl(fd, CMD_LED_OFF, &led_id);
        }
    }
    close(fd);
    return 0;
}

Tags: linux-kernel driver-development platform-bus gpio embedded-systems

Posted on Tue, 25 Aug 2026 16:19:27 +0000 by sillyman