Search code examples
linux-kernellinux-device-drivergpiodevice-tree

Linux GPIO driver probe fails


I am trying to write a small kernel module that can control some LEDs connected to the GPIO pins on my RaspberryPi 4 Model B. My Pi is running Raspbian GNU/Linux 11 (bullseye) and Linux kernel v6.1.21-v7l+.

I am currently trying to figure out how the DeviceTree works with my kernel module.

This is my DTS file

/dts-v1/;
/plugin/;
/ {
    fragment@0 {
        target-path = "/";
        __overlay__ {
            led {
                compatible = "led";
                status = "okay";
                label = "led";
                led-gpio = <&gpio 17 0>,
                           <&gpio 27 0>;
            };
        };
    };
};

My kernel module code is

#include <linux/module.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/property.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/gpio/consumer.h>
#include <linux/proc_fs.h>

MODULE_AUTHOR("****");
MODULE_LICENSE("GPL v2");

static int dt_probe(struct platform_device *pdev);
static int dt_remove(struct platform_device *pdev);

static struct of_device_id led_ids[] = {
    {
        .compatible = "led",
    }, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_ids);

static struct platform_driver led_driver = {
    .probe = dt_probe,
    .remove = dt_remove,
    .driver = {
        .name = "led",
        .of_match_table = led_ids,
    },
};

static struct gpio_desc *blue = NULL, *red = NULL;

static struct proc_dir_entry *proc_file = NULL;

static ssize_t led_write(struct file *filp, const char *user_buffer, size_t count, loff_t *offset) {
    int value;
    char command[2];

    if (copy_from_user(command, user_buffer, 1)) {
        pr_err("[led] Failed to read command from user\n");
        return -EFAULT;
    }

    command[1] = 0; // null terminate the string so that kstrtoint doesn't fail

    if (kstrtoint(command, 10, &value)) {
        pr_err("[led] Invalid command from user\n");
        return -EINVAL;
    }

    gpiod_set_value(blue, !!value);
    return count;
}

static ssize_t led_read(struct file *filp, char __user *user_buffer, size_t count, loff_t *offset) {
    int value;
    char led_value_str;

    if (*offset)
        return 0;

    value = gpiod_get_raw_value(blue);

    switch (value) {
        case 0:
            led_value_str = '0';
            break;
        case 1:
            led_value_str = '1';
            break;
        default:
            break;
    }
    if (copy_to_user(user_buffer, &led_value_str, 1)) {
        pr_err("[led] Failed to copy led value to user\n");
        return -EFAULT;
    }
    (*offset)++;
    return 1;
};

static struct proc_ops fops = {
    .proc_write = led_write,
    .proc_read = led_read,
};

static int dt_probe(struct platform_device *pdev) {
    struct device *dev = &pdev->dev;
    const char *label;
    int ret;

    /* Check for device properties */
    if (!device_property_present(dev, "label")) {
        pr_err("[led] Device property 'label' not found!\n");
        return -1;
    }

    /* Read device properties */
    ret = device_property_read_string(dev, "label", &label);
    if (ret) {
        pr_err("[led] Failed to read 'label'\n");
        return -1;
    }

    /* Init GPIO */
    blue = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW);
    if (IS_ERR(blue)) {
        pr_err("[led] Failed to setup blue LED\n");
        return PTR_ERR(blue);
    }

    red = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW);
    if (IS_ERR(red)) {
        printk("[led] Failed to setup red LED\n");
        gpiod_put(blue);
        return PTR_ERR(red);
    }

    /* Creating procfs file */
    proc_file = proc_create("led", 0666, NULL, &fops);
    if (proc_file == NULL) {
        pr_err("[led] Failed to create /proc/led\n");
        gpiod_put(blue);
        gpiod_put(red);
        return -ENOMEM;
    }

    pr_info("[led] Successfully setup GPIO driver\n");
    return 0;
}

static int dt_remove(struct platform_device *pdev) {
    pr_info("[led] Removing device from tree\n");
    gpiod_put(blue);
    proc_remove(proc_file);
    return 0;
}

static int __init led_init(void) {
    pr_info("[led] Loading LED driver\n");
    if (platform_driver_register(&led_driver)) {
        pr_err("[led] Failed to register driver\n");
        return -1;
    }
    return 0;
};

static void __exit led_cleanup(void) {
    pr_info("[led] Cleaning up LED driver\n");
    platform_driver_unregister(&led_driver);
};

module_init(led_init);
module_exit(led_cleanup);

My Makefile

obj-m += led.o

KDIR = /lib/modules/$(shell uname -r)/build

all: module dt
    echo Built Device Tree Overlay and kernel module

module:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
dt: ledoverlay.dts
    dtc -@ -I dts -O dtb -o ledoverlay.dtbo ledoverlay.dts
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    rm -rf ledoverlay.dtbo

Loading the DTBO object works fine, but when I try to load my module the following gets printed to dmsg -

[ 9573.984173] [led] Loading LED driver
[ 9573.984509] [led] Failed to setup blue LED
[ 9573.984518] led: probe of led failed with error -2

Sources I looked at -

  1. https://github.com/Johannes4Linux/Linux_Driver_Tutorial/blob/main/21_dt_gpio/dt_gpio.c
  2. https://github.com/PacktPublishing/Linux-Device-Driver-Development-Second-Edition/blob/main/Chapter16/gpio/gpio-descriptor-module.c

Does anyone have any insight on why the call to gpiod_get_index() fails? I feel like I'm misunderstanding something or doing something wrong.

Edit:

If I change the DTS file and split the GPIOs this

...
    led {
        compatible = "led";
                status = "okay";
                label = "led";
                led-blue-gpio = <&gpio 17 0>,;
                led-red-gpio = <&gpio 27 0>;
    };

And the module code to use gpiod_get() instead of gpiod_get_index() like so

...
    blue = gpiod_get(dev, "led-blue", GPIOD_OUT_LOW);
...
    red = gpiod_get(dev, "led-red", GPIOD_OUT_LOW);   

Everything seems to work fine.


Solution

  • So it turns out, as @IanAbbott mentioned, the issue was with the name of the led-gpio property. I renamed it to led-gpios and it worked with gpiod_get_index().