Detailed explanation of the GPIO subsystem driver framework of the Linux kernel

Table of contents

1 introduction

2 levels of GPIO subsystem

3 gpio subsystem driver process

4 Traditional Chinese medicine data structure of gpio subsystem

5 Detailed details of gpio subsystem function calls

6 sysfs interface of GPIO subsystem

6.1 What gpio controllers are there?

6.2 Details of each gpio controller

6.3 Check gpio usage

6.4 Using GPIO via SYSFS

6.4.1 Determine the GPIO number

6.4.2 Export, set direction, read and write values

7 Feynman Learning Method: So I recorded a learning video explaining the gpio subsystem


1 introduction

When we want to use a certain pin to control the LED light on and off, generally speaking we need to enable the clock, then configure the pin as a GPIO function, then configure the electrical properties, then configure the GPIO as an output, and finally control the GPIO according to the The output level, where the direction and level of the GPIO are configured is done by the GPIO subsystem.

2 levels of GPIO subsystem

The above picture is the hierarchical structure diagram of the gpio subsystem. In other drivers, we can directly use the function gpiod_set_value to set the value of the pin. This function is defined in the gpio library. The gpio library serves as a link between the previous and the following. function, and then the gpiod_set_value function finally calls the chip->set(chip, gpio_chip_hwgpio(desc), value) function. The chip here is the structure registered in the gpio driver. This structure contains some pairs of gpio operation function.

3 gpio subsystem driver process

The picture above is a GPIO driver flow chart I drew based on the Linux kernel source code. The compatible in the gpio controller node in the device tree is fsl, imx35-gpio, and then we search in the kernel source code to find the matching driver. For mxc_gpio_driver, then when the device and driver match, the probe function in the driver will be called, here it is the mxc_gpio_probe function, and then in this mxc_gpio_probe function, the following three tasks are actually done

  • allocation structure
  • set structure
  • Registration structure

The specific work of the mxc_gpio_probe function: First, the mxc_gpio_get_hw function is called. This function obtains the offset address of the gpio register group, and then a platform_get_resource function is used. This platform_get_resource function obtains the base address of the gpio register, and then calls devm_kzalloc to allocate Created a mxc_gpio_port structure,

struct mxc_gpio_port {
    struct list_head node;
    struct clk *clk;
    void __iomem *base;
    int irq;
    int irq_high;
    struct irq_domain *domain;
    struct gpio_chip gc;
    u32 both_edges;
    int saved_reg[6];
    bool gpio_ranges;
};

Then there is an important gpio_chip structure member in this mxc_gpio_port structure.

struct gpio_chip {
    const char      *label;
    struct gpio_device  *gpiodev;
    struct device       *parent;
    struct module       *owner;
    ...省略一些...
    int         (*direction_input)(struct gpio_chip *chip,
                        unsigned offset);
    int         (*direction_output)(struct gpio_chip *chip,
                        unsigned offset, int value);
    int         (*get)(struct gpio_chip *chip,
                        unsigned offset);
    void            (*set)(struct gpio_chip *chip,
                        unsigned offset, int value);
     ...省略一些...
                        enum single_ended_mode mode);
    int         (*to_irq)(struct gpio_chip *chip,
                        unsigned offset);
    void            (*dbg_show)(struct seq_file *s,
                        struct gpio_chip *chip);
     ...省略一些...
};

This gpio_chip contains various operating functions.

Then the following function is called in the probe function

    err = bgpio_init(&port->gc, &pdev->dev, 4,
             port->base + GPIO_PSR,
             port->base + GPIO_DR, NULL,
             port->base + GPIO_GDIR, NULL,
             BGPIOF_READ_OUTPUT_REG_SET);
这里的参数    
port->base + GPIO_PSR,
port->base + GPIO_DR, 
port->base + GPIO_GDIR, 
就是寄存器地址
设置完结构体之后,这个结构体里面有寄存器值也有操作函数。

In this bgpio_init function, the following three functions are mainly called

bgpio_setup_io(gc, dat, set, clr, flags);
bgpio_setup_accessors(dev, gc, flags & BGPIOF_BIG_ENDIAN,
                    flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);
bgpio_setup_direction(gc, dirout, dirin, flags);

Then these three functions are the various operation functions of gpio. Then the err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port); function is called. The gpio_device structure is allocated in this function, and the chip member in the gpio_device structure is the gpio_chip structure allocated and set previously.

4 Traditional Chinese medicine data structure of gpio subsystem

As we said before, we need to allocate settings to register a gpio_chip structure, and then we use the gpiochip_add_data(chip, data); function to register a gpio_device structure, and then this gpio_device structure contains the gpio_chip structure, and then this gpio_device structure In addition to the chip member, there is also a descs member, which is used to represent the pin. Each pin has a descs structure, and then there is a gdev member in the descs structure. We can find the pin based on this gdev member. Which gpio controller the pin belongs to.

5 Detailed details of gpio subsystem function calls

The picture above shows the specific details of the function call of the gpio subsystem. One of our gpio controllers corresponds to a gpio_device structure, and then there are

  • The base member, the base member is the initial label of the pin in the gpio controller,
  • ngpio is the number of pins,
  • The descs member is a structure array, each item in it is a gpio_desc structure, representing a pin.

Then add one in our device tree

myled{
        compatible = "cumtchw"
        led-gpios = <&gpio1  10  GPIO_ACTIVE_LOW>        
};

Then when we call the led_gpio = gpiod_get(&pdev->dev, "led", 0); function, then according to led-gpios = <&gpio1 10 GPIO_ACTIVE_LOW> we can get the 10th item in the gpio1 controller, then led_gpio Just point to the 10th item in the descs array, and then our desc can actually find the controller corresponding to this pin, and then when we use gpiod_set_value(led_gpio, status); this function to set the pin, we will find the control chip->set(chip, gpio_chip_hwgpio(desc), value) in the controller; then the second parameter of this function refers to which pin of the controller is set. The second parameter here is obtained like this

static int __maybe_unused gpio_chip_hwgpio(const struct gpio_desc *desc)
{
    return desc - &desc->gdev->descs[0];
}  这里就是10

6 sysfs interface of GPIO subsystem

The driver is `drivers\gpio\gpiolib-sysfs.c`,

6.1 What gpio controllers are there?

All GPIO controllers are listed in the /sys/bus/gpio/devices` directory:

6.2 Details of each gpio controller

 Under /sys/class/gpio/gpiochipXXX`, there is this information:

 base // GPIO number of this GPIO controller
device
label // name
ngpio // number of pins
power
subsystem
uevent

6.3 Check gpio usage

 cat /sys/kernel/debug/gpio

6.4 Using GPIO via SYSFS

If it is just simple pin control (such as output, query input value), you do not need to write a driver.

But if interrupts are involved, a driver needs to be written.

6.4.1 Determine the GPIO number

Check the label in each `/sys/class/gpio/gpiochipXXX` directory to make sure it is the GPIO controller you want to use, also called GPIO Bank.

According to its name gpiochipXXX, you can know that the base value is XXX.

The base value plus the pin offset is the number of the pin.

6.4.2 Export, set direction, read and write values

echo 509 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio509/direction
echo 1 > /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport

echo 509 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio509/direction
cat /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport

7 Feynman Learning Method: So I recorded a learning video explaining the gpio subsystem

8 LED driver based on pinctrl subsystem and gpio subsystem

8.1 Check the schematic diagram to determine the pins

As you can see from the schematic diagram, the LED is connected to GPIO5_3, and when the pin outputs low level, the LED lights up.

8.2 Modify device tree file

8.2.1 Add pinctrl information to the device tree

Here, the Pins_Tool_for_i.MX_Processors_v6_x64.exe tool provided by imx6ull is used to configure pins and generate pinctrl node information. Install Pins_Tool_for_i.MX_Processors_v6_x64.exe and then open the IMX6ULL configuration file "MCIMX6Y2xxx08.mex", then find gpio5_3 in it, and then the tool will automatically generate configuration information for us.

            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;

Put this configuration information in the device tree file,

        pinctrl_leds: ledgrp {
            fsl,pins = <
                  MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;
        };

8.2.2 Add LED node information to the device

    myled {
        compatible = "cumtchw,leddrv";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_leds>;
        led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
    };

Add the above node information under the root node of the device tree, where cumtchw is my name.

8.3 Writing code

8.3.1 Driver leddrv.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>


/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;


/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	//struct inode *inode = file_inode(file);
	//int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	gpiod_set_value(led_gpio, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	//int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	gpiod_direction_output(led_gpio, 0);
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 从platform_device获得GPIO
 *    把file_operations结构体告诉内核:注册驱动程序
 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	//int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 4.1 设备树中定义有: led-gpios=<...>;	*/
    led_gpio = gpiod_get(&pdev->dev, "led", 0);
	if (IS_ERR(led_gpio)) {
		dev_err(&pdev->dev, "Failed to get GPIO for led\n");
		return PTR_ERR(led_gpio);
	}
    
	/* 4.2 注册file_operations 	*/
	major = register_chrdev(0, "cumtchw_led", &led_drv);  /* /dev/led */

	led_class = class_create(THIS_MODULE, "cumtchw_led_class");
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		gpiod_put(led_gpio);
		return PTR_ERR(led_class);
	}

	device_create(led_class, NULL, MKDEV(major, 0), NULL, "cumtchw_led%d", 0); /* /dev/cumtchw_led0 */
        
    return 0;
    
}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "cumtchw_led");
	gpiod_put(led_gpio);
    
    return 0;
}


static const struct of_device_id ask100_leds[] = {
    { .compatible = "cumtchw,leddrv" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "cumtchw_led",
        .of_match_table = ask100_leds,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init led_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&chip_demo_gpio_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&chip_demo_gpio_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");


8.3.2 Test program ledtest.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./ledtest /dev/cumtchw_led0 on
 * ./ledtest /dev/cumtchw_led0 off
 */
int main(int argc, char **argv)
{
	int fd;
	char status;
	
	/* 1. 判断参数 */
	if (argc != 3) 
	{
		printf("Usage: %s <dev> <on | off>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 写文件 */
	if (0 == strcmp(argv[2], "on"))
	{
		status = 1;
		write(fd, &status, 1);
	}
	else
	{
		status = 0;
		write(fd, &status, 1);
	}
	
	close(fd);
	
	return 0;
}


8.3.3 Makefile


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /data/chw/imx6ull_20230512/bsp/100ask_imx6ull-sdk/Linux-4.9.88 # 板子所用内核源码的目录

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o



obj-m += leddrv.o

8.4 Experiment

8.4.1 Compile device tree files

Execute in the kernel directory 

make dtbs

8.4.2 Compile driver and test program

make all

8.4.3 Replace device tree, driver and test program

cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb /data/chw/imx6ull_20230512/nfs_rootfs/
cp leddrv.ko  ledtest /data/chw/imx6ull_20230512/nfs_rootfs/

Then execute on the development board

There are two mistakes in the above screenshot,

1. The device tree file is copied to the boot directory, not the ~/ directory

2. The development board needs to be restarted so that the new device tree file will be used.

After I corrected the above two errors, insmod leddrv.ko reported the following error:

insmod: ERROR: could not insert module leddrv.ko: Invalid parameters

I replaced the device tree file again, then restarted the development board, and found that it was even worse

 

Guess you like

Origin blog.csdn.net/u013171226/article/details/132686757