Reading gpio direction returns -ENOTSUPP

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.

1 Like

Hello…I have noticed sometimes within the specific kernels and images listed and available through beagleboard.org people, there are specifics:

  1. Sometimes the GPIO is In unless otherwise stated (Out).
  2. Also…

Maybe the driver does not need sysfs interface since you are using gpiod_get_direction().

#include <gpiod.h>

The above preprocessor may work also…

I have not written a kernel driver like yours to this day. So, mixing sysfs with a character device file may not or cannot be interwoven in the same kernel driver. I cannot be positive for now. It may be something to look into…

Seth

P.S. As this morning proceeds, I will look into it slowly or later in the day.

I really found some good data in the archives:

  1. Character device drivers — The Linux Kernel documentation
  2. The Linux driver implementer’s API guide — The Linux Kernel documentation

Although number 2 is an older kernel, it may help you on your way. For now, this is all I have now. I will try later to get more up-to-date and current. Then, I can reply with a better answer.

  • update
#include <linux/gpio/consumer.h> // Include for gpiod functions

Enjoy! I think using that include preprocessor directive may be more valuable.

Here is something to read that is way more up-to-date.

https://www.kernel.org/doc/html/v6.1/driver-api/gpio/index.html

One last thing: GPIO Descriptor Consumer Interface — The Linux Kernel documentation

I am not sure if using GPIOs and setting up the driver in one go is how it is done. I am speculating for now.

Ok best guess is the gpio driver for the BeagleAI-Y does not support getting the direction.
Had a quick look at the driver for the BBAI-64 and it doesn’t appear to support getting the direction. Not sure if it is the same drvier though.

Check the main DTSI file for the BeagleAI-Y and look for the compatible=“” for the gpio driver and do a search to find the source.

For the BBAI-64 this is gpio-davinci,c - compatible = "ti,j721e-gpio", "ti,keystone-gpio";

Check the probe function for the driver and see what is being registered.

for the BBAI-64 there is this snippet

	chips->chip.direction_input = davinci_direction_in;
	chips->chip.get = davinci_gpio_get;
	chips->chip.direction_output = davinci_direction_out;
	chips->chip.set = davinci_gpio_set;

The driver is not setting

chips->chip.get_direction

Assuming the AI-Y is using the same driver this is the issue. Even if it is a different driver it is most likely the issue for that driver also.

3 Likes

Thanks for the response. I think you might be correct in your assessment.

I looked through the BeagleY-AI (aka am67a) dts file(s). It looks like it borrows a lot from am62, which seems to be using the same davinci controller. I checked out the Keystone GPIO manual, and it shouldn’t be a problem reading the direction register.

Well this is just a consumer driver that relies on existing controller driver, so maybe in future I could look into trying to improve davinci gpio driver.