Hi. I am learning to write kernel drivers. Currently tackling the GPIO subsystem, implementing my own consumer driver.
This is performed on BeagleY-AI board and tested with kernels 6.1.83
and 6.6.58
from official debian images.
The DT overlay:
/dts-v1/;
/plugin/;
#include "k3-pinctrl.h"
#include <dt-bindings/gpio/gpio.h>
/ {
fragment@0 {
target-path = "/";
__overlay__ {
gpiodev: gpiodev {
compatible = "org,beagley-gpio-sysfs";
gpio14 {
label = "GPIO1.14";
beagley-gpios = <&main_gpio1 14 GPIO_ACTIVE_HIGH>;
};
gpio15 {
label = "GPIO1.13";
beagley-gpios = <&main_gpio1 13 GPIO_ACTIVE_HIGH>;
};
};
};
};
};
Here’s the driver code:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/gfp_types.h>
#include <uapi/asm-generic/errno-base.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/device/class.h>
#include <linux/mod_devicetable.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("BeagleY");
MODULE_DESCRIPTION("GPIO sysfs driver");
#undef pr_fmt
#define pr_fmt(fmt) "%s : " fmt, __func__
struct gpio_dev {
char label[32];
struct gpio_desc *desc;
};
static struct gpio_drvdata {
struct class *class;
struct device **dev;
unsigned int devcount;
} gpio_drvdata;
ssize_t direction_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct gpio_dev *gpio_devdata = dev_get_drvdata(dev);
int dir;
char *direction;
dir = gpiod_get_direction(gpio_devdata->desc);
pr_info("gpiod_get_direction result : %d\n", dir);
if (dir < 0) {
return dir;
}
direction = (dir == 0) ? "out" : "in";
return sprintf(buf, "%s\n", direction);
}
ssize_t direction_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct gpio_dev *gpio_devdata = (struct gpio_dev *)dev_get_drvdata(dev);
int ret;
if (sysfs_streq(buf, "out")) {
ret = gpiod_direction_output(gpio_devdata->desc, 0);
} else if (sysfs_streq(buf, "in")) {
ret = gpiod_direction_input(gpio_devdata->desc);
} else {
ret = -EINVAL;
}
return ret ? ret : count;
}
ssize_t value_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct gpio_dev *gpio_devdata = dev_get_drvdata(dev);
int value = gpiod_get_value(gpio_devdata->desc);
return sprintf(buf, "%d\n", value);
}
ssize_t value_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct gpio_dev *gpio_devdata = dev_get_drvdata(dev);
long value;
int ret;
ret = kstrtol(buf, 10, &value);
if (ret) {
return ret;
}
gpiod_set_value(gpio_devdata->desc, value);
return count;
}
ssize_t label_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct gpio_dev *gpio_devdata = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", gpio_devdata->label);
}
static DEVICE_ATTR_RW(direction);
static DEVICE_ATTR_RW(value);
static DEVICE_ATTR_RO(label);
static struct attribute *gpio_attrs[] = { &dev_attr_direction.attr,
&dev_attr_value.attr,
&dev_attr_label.attr, NULL };
static struct attribute_group gpio_attr_group = { .attrs = gpio_attrs };
static const struct attribute_group *gpio_attr_groups[] = { &gpio_attr_group,
NULL };
static int gpiosysfs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *parent = dev->of_node;
struct device_node *child = NULL;
const char *name;
int i = 0, ret;
struct gpio_dev *gpiodev;
gpio_drvdata.devcount = of_get_child_count(parent);
if (!gpio_drvdata.devcount) {
dev_info(dev, "No devices found!\n");
} else {
dev_info(dev, "Number of child nodes = %d\n",
gpio_drvdata.devcount);
}
gpio_drvdata.dev = devm_kzalloc(
dev, sizeof(struct device *) * gpio_drvdata.devcount,
GFP_KERNEL);
if (!gpio_drvdata.dev) {
pr_err("Could not allocate memory for device nodes!\n");
return -ENOMEM;
}
for_each_available_child_of_node(parent, child) {
gpiodev = devm_kzalloc(dev, sizeof(*gpiodev), GFP_KERNEL);
if (!gpiodev) {
dev_err(dev,
"Could not allocate memory for gpiodev!\n");
return -ENOMEM;
}
if (of_property_read_string(child, "label", &name)) {
dev_warn(dev, "Missing label information!\n");
snprintf(gpiodev->label, sizeof(gpiodev->label),
"unknowngpio%d", i);
} else {
strcpy(gpiodev->label, name);
dev_info(dev, "GPIO Label = %s\n", gpiodev->label);
}
gpiodev->desc = devm_fwnode_gpiod_get_index(dev, &child->fwnode,
"beagley", 0,
GPIOD_ASIS,
gpiodev->label);
/*
gpiodev->desc = devm_fwnode_get_gpiod_from_child(
dev, "beagley", &child->fwnode, GPIOD_ASIS,
gpiodev->label);
*/
if (IS_ERR(gpiodev->desc)) {
ret = PTR_ERR(gpiodev->desc);
if (ret == -ENOENT) {
dev_err(dev,
"No GPIO has been assigned to the requested function\n");
return ret;
}
}
/* Set direction to output with initial value 0 (logic low)*/
ret = gpiod_direction_output(gpiodev->desc, 0);
if (ret) {
dev_info(dev, "Failed to set GPIO direction!\n");
return ret;
}
gpio_drvdata.dev[i] = device_create_with_groups(
gpio_drvdata.class, dev, 0, gpiodev, gpio_attr_groups,
gpiodev->label);
if (IS_ERR(gpio_drvdata.dev[i])) {
dev_err(dev, "Failed to create device in sysfs!\n");
return PTR_ERR(gpio_drvdata.dev[i]);
}
i++;
}
return 0;
}
static int gpiosysfs_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "Remove called\n");
for (int i = 0; i < gpio_drvdata.devcount; i++) {
device_unregister(gpio_drvdata.dev[i]);
}
return 0;
}
struct of_device_id gpio_device_ofmatch[] = {
{ .compatible = "org,beagley-gpio-sysfs" },
{ /*NUL termination*/ }
};
static struct platform_driver gpiosysfs_platform_drv = {
.probe = gpiosysfs_probe,
.remove = gpiosysfs_remove,
.driver = { .name = "beagley_gpio_sysfs",
.of_match_table = gpio_device_ofmatch }
};
static int __init gpiosysfs_init(void)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
gpio_drvdata.class = class_create(THIS_MODULE, "beagley_gpio");
#else
gpio_drvdata.class = class_create("beagley_gpio");
#endif
if (IS_ERR(gpio_drvdata.class)) {
pr_err("Could not create class\n");
return PTR_ERR(gpio_drvdata.class);
}
platform_driver_register(&gpiosysfs_platform_drv);
pr_info("Module loaded succesfully!\n");
return 0;
}
/**
* @brief This function is called, when the module is removed from the kernel
*/
static void __exit gpiosysfs_exit(void)
{
platform_driver_unregister(&gpiosysfs_platform_drv);
class_destroy(gpio_drvdata.class);
pr_info("Module unloaded\n");
}
module_init(gpiosysfs_init);
module_exit(gpiosysfs_exit);
The driver works fine for the most part. I can change direction by writing to the direction
attribute and change value in output writing to value
attribute. I can perform read on value
as well. However, when trying to read direction
, I get error -524, from gpiod_get_direction()
which translates to operation not supported.
I would appreciate any help in this matter.