Explicación detallada del marco del controlador del subsistema GPIO del kernel de Linux

Tabla de contenido

1. Introducción

2 niveles del subsistema GPIO

Proceso del controlador del subsistema 3 gpio

4 Estructura de datos de medicina china del subsistema gpio

5 Detalles de las llamadas a funciones del subsistema gpio

6 interfaz sysfs del subsistema GPIO

6.1 ¿Qué controladores gpio existen?

6.2 Detalles de cada controlador gpio

6.3 Ver el uso de gpio

6.4 Usando GPIO a través de SYSFS

6.4.1 Determinar el número GPIO

6.4.2 Exportar, establecer dirección, leer y escribir valores

7 Método de aprendizaje de Feynman: grabé un video de aprendizaje que explica el subsistema gpio


1. Introducción

Cuando queremos usar un determinado pin para controlar el encendido y apagado de la luz LED, en términos generales necesitamos habilitar el reloj, luego configurar el pin como una función GPIO, luego configurar las propiedades eléctricas, luego configurar el GPIO como una salida, y finalmente controle el GPIO de acuerdo con el nivel de salida, donde la dirección y el nivel del GPIO son configurados por el subsistema GPIO.

2 niveles del subsistema GPIO

La imagen de arriba es el diagrama de estructura jerárquica del subsistema gpio. En otros controladores, podemos usar directamente la función gpiod_set_value para establecer el valor del pin. Esta función está definida en la biblioteca gpio. La biblioteca gpio sirve como enlace entre los anterior y siguiente, y luego la función gpiod_set_value finalmente llama a la función chip->set(chip, gpio_chip_hwgpio(desc), value). El chip aquí es la estructura registrada en el controlador gpio. Esta estructura contiene algunos pares de gpio función de operación.

Proceso del controlador del subsistema 3 gpio

La imagen de arriba es un diagrama de flujo del controlador GPIO que dibujé basado en el código fuente del kernel de Linux. El compatible en el nodo del controlador gpio en el árbol de dispositivos es fsl, imx35-gpio, y luego buscamos en el código fuente del kernel para encontrar la coincidencia. controlador. Para mxc_gpio_driver, cuando el dispositivo y el controlador coincidan, se llamará a la función de sonda en el controlador, aquí está la función mxc_gpio_probe, y luego en esta función mxc_gpio_probe, las siguientes tres tareas realmente se realizan

  • estructura de asignación
  • Configurar la estructura
  • Estructura de registro

El trabajo específico de la función mxc_gpio_probe: Primero, se llama a la función mxc_gpio_get_hw. Esta función obtiene la dirección de desplazamiento del grupo de registros gpio, y luego se usa una función platform_get_resource. Esta función platform_get_resource obtiene la dirección base del registro gpio y luego llama a devm_kzalloc para asignar. Obtuve una estructura mxc_gpio_port,

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;
};

Luego hay un miembro importante de la estructura gpio_chip en esta estructura mxc_gpio_port.

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);
     ...省略一些...
};

Este gpio_chip contiene varias funciones operativas.

Luego se llama a la siguiente función en la función de sonda

    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, 
就是寄存器地址
设置完结构体之后,这个结构体里面有寄存器值也有操作函数。

En esta función bgpio_init, se llaman principalmente las siguientes tres funciones:

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);

Entonces estas tres funciones incluyen varias funciones operativas de gpio. Luego se llama a la función err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);. La estructura gpio_device se asigna en esta función, y el miembro del chip en la estructura gpio_device es la estructura gpio_chip asignada y configurada previamente.

4 Estructura de datos de medicina china del subsistema gpio

Como dijimos antes, necesitamos asignar configuraciones para registrar una estructura gpio_chip, y luego usamos la función gpiochip_add_data(chip, data); para registrar una estructura gpio_device, y luego esta estructura gpio_device contiene la estructura gpio_chip, y luego esta estructura gpio_device Además del miembro del chip, también hay un miembro descs, que se utiliza para representar el pin. Cada pin tiene una estructura descs, y luego hay un miembro gdev en la estructura descs. Podemos encontrar el pin según este gdev. miembro.A qué controlador gpio pertenece el pin.

5 Detalles de las llamadas a funciones del subsistema gpio

La imagen de arriba muestra los detalles específicos de la llamada de función del subsistema gpio. Uno de nuestros controladores gpio corresponde a una estructura gpio_device, y luego están

  • El miembro base es el número inicial del pin en este controlador gpio.
  • ngpio es el número de pines,
  • El miembro descs es una matriz de estructura, cada elemento que contiene es una estructura gpio_desc, que representa un pin.

Luego únete a nuestro árbol de dispositivos que tiene un

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

Luego, cuando llamamos a la función led_gpio = gpiod_get(&pdev->dev, "led", 0);, de acuerdo con led-gpios = <&gpio1 10 GPIO_ACTIVE_LOW> podemos obtener el décimo elemento en el controlador gpio1, luego led_gpio Solo apunte al décimo elemento en la matriz descs, y luego nuestro desc puede encontrar el controlador correspondiente a este pin, y luego, cuando usamos gpiod_set_value(led_gpio, status); esta función para configurar el pin, encontraremos el chip de control. >set(chip, gpio_chip_hwgpio(desc), value) en el controlador; entonces el segundo parámetro de esta función se refiere a qué pin del controlador está configurado. El segundo parámetro aquí se obtiene así

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

6 interfaz sysfs del subsistema GPIO

El controlador es `drivers\gpio\gpiolib-sysfs.c`,

6.1 ¿Qué controladores gpio existen?

Todos los controladores GPIO se enumeran en el directorio /sys/bus/gpio/devices`:

6.2 Detalles de cada controlador gpio

 En /sys/class/gpio/gpiochipXXX`, se encuentra esta información:

base // número GPIO de esta etiqueta
del dispositivo  controlador GPIO // nombre ngpio // número de pines subsistema de alimentación uevent




6.3 Ver el uso de gpio

 cat /sys/kernel/debug/gpio

6.4 Usando GPIO a través de SYSFS

Si se trata simplemente de un control de pin simple (como salida, valor de entrada de consulta), no es necesario escribir un controlador.

Pero si hay interrupciones involucradas, es necesario escribir un controlador.

6.4.1 Determinar el número GPIO

Verifique la etiqueta en cada directorio `/sys/class/gpio/gpiochipXXX` para asegurarse de que sea el controlador GPIO que desea usar, también llamado GPIO Bank.

Según su nombre gpiochipXXX, puedes saber que el valor base es XXX.

El valor base más el desplazamiento del pin es el número del pin.

6.4.2 Exportar, establecer dirección, leer y escribir valores

echo 509 > /sys/class/gpio/export
salida de eco > /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 Método de aprendizaje de Feynman: grabé un video de aprendizaje que explica el subsistema gpio

Controlador de 8 LED basado en el subsistema pinctrl y el subsistema gpio

8.1 Verifique el diagrama esquemático para determinar el pin.

Como se puede ver en el diagrama esquemático, el LED está conectado a GPIO5_3 y cuando el pin genera un nivel bajo, el LED se enciende.

8.2 Modificar el archivo del árbol de dispositivos

8.2.1 Agregar información pinctrl en el árbol de dispositivos

Aquí, la herramienta Pins_Tool_for_i.MX_Processors_v6_x64.exe proporcionada por imx6ull se utiliza para configurar pines y generar información del nodo pinctrl. Instale Pins_Tool_for_i.MX_Processors_v6_x64.exe y luego abra el archivo de configuración IMX6ULL "MCIMX6Y2xxx08.mex", luego busque gpio5_3 en él y luego la herramienta generará automáticamente información de configuración para nosotros.

            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;

Coloque esta información de configuración en el archivo del árbol de dispositivos,

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

8.2.2 Agregar información del nodo LED al dispositivo

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

Agregue la información del nodo anterior debajo del nodo raíz del árbol de dispositivos, donde cumtchw es mi nombre.

8.3 Escribir código

8.3.1 Controlador 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 Programa de prueba 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 Archivo MAKE


# 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 Experimentos

8.4.1 Compilar archivos de árbol de dispositivos

Vaya al directorio del kernel y ejecute 

make dtbs

8.4.2 Compilación del controlador y programa de prueba

make all

8.4.3 Reemplazar árbol de dispositivos, controlador y programa de prueba

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/

Luego ejecutar en la placa de desarrollo.

Hay dos errores en la captura de pantalla anterior,

1. El archivo del árbol de dispositivos se copia en el directorio de inicio, no en el directorio ~/.

2. Debe reiniciar la placa de desarrollo para que se utilice el nuevo archivo de árbol de dispositivos.

Después de corregir los dos errores anteriores, insmod leddrv.ko informó el siguiente error:

insmod: ERROR: no se pudo insertar el módulo leddrv.ko: parámetros no válidos

Reemplacé el archivo del árbol de dispositivos nuevamente, luego reinicié la placa de desarrollo y descubrí que estaba aún peor.

 

Supongo que te gusta

Origin blog.csdn.net/u013171226/article/details/132686757
Recomendado
Clasificación