Escribir un módulo de kernel de Linux: GPIO con soporte IRQ

Habr, hola!

Este artículo trata sobre el desarrollo del módulo GPIO (Entrada / Salida de propósito general) del kernel de Linux. Como en el artículo anterior , implementamos la estructura básica del controlador GPIO con soporte de interrupción (IRQ: Solicitud de interrupción).



Los datos de entrada son similares al artículo anterior: el bloque GPIO desarrollado para el nuevo procesador "conectado" a la FPGA y que ejecuta la versión 3.18.19 de Linux.

Para desarrollar un controlador GPIO, debemos realizar los siguientes pasos:

  1. Comprender el principio de interacción entre el controlador GPIO y la interfaz de espacio de usuario;
  2. Agregue el módulo del núcleo al ensamblaje y describa el hardware en el árbol de dispositivos;
  3. Implemente el esqueleto básico del conductor, así como sus puntos de entrada y recuperación;
  4. Implemente la parte funcional del controlador GPIO;
  5. Agregue compatibilidad con IRQ a la implementación del controlador.

Ejemplos de controladores GPIO se pueden encontrar aquí .

Primer paso


Primero, conozcamos el principio de interacción entre el controlador GPIO a través de la consola del usuario.

Usando un pequeño script bash, cree los controles para cada GPIO en / sysfs. Para hacer esto, escriba el siguiente script en la línea de comando:

for i in {248..255}; do echo $i > /sys/class/gpio/export; done 

A continuación, veamos qué características / sysfs proporciona para configurar cada GPIO:

 root@zed-slave:/sys/class/gpio# ls -l gpio248/ total 0 -rw-r--r-- 1 root root 4096 Jan 7 20:50 active_low -rw-r--r-- 1 root root 4096 Jan 7 20:50 direction -rw-r--r-- 1 root root 4096 Jan 7 20:50 edge drwxr-xr-x 2 root root 0 Jan 7 20:50 power lrwxrwxrwx 1 root root 0 Jan 7 20:50 subsystem -> ../../../../class/gpio -rw-r--r-- 1 root root 4096 Jan 7 20:10 uevent -rw-r--r-- 1 root root 4096 Jan 7 20:50 value 

Actualmente estamos interesados ​​en los siguientes campos:

  • direction : establece la dirección de la línea. Puede tomar los valores "dentro" o "fuera";
  • valor : le permite establecer una señal alta o baja en la línea (si la dirección se establece en "out"), de lo contrario (la dirección se establece en "in") le permite leer el estado de la línea;
  • edge : le permite configurar el evento por el cual ocurre la interrupción. Puede tomar los siguientes valores: "ninguno", "ascendente", "descendente" o "ambos".

Después de un rápido conocimiento de la interfaz del controlador a través de sysfs, puede considerar cómo el controlador procesa los comandos del usuario. El kernel de Linux tiene una estructura gpio_chip que describe la funcionalidad del controlador gpio. Los siguientes campos están presentes en él:

  • direction_input : establece la línea en la entrada. Llamado en la siguiente entrada: echo "in"> / sys / class / gpio / gpio248 / direction;
  • direction_output : establece la línea para salir. Llamado en la siguiente entrada: echo "out"> / sys / class / gpio / gpio248 / direction;
  • get : lee el valor establecido en la línea. Llamado en la siguiente entrada: cat / sys / class / gpio / gpio248 / value;
  • set : establece el valor en la línea. Llamado en la siguiente entrada: echo 1/0> / sys / class / gpio / gpio248 / value;

Para describir la configuración de IRQ en Linux, hay una estructura irq_chip que contiene los siguientes campos:

  • irq_set_type : establece el tipo de evento por el cual ocurrirá la interrupción. Llamado en la siguiente entrada: echo> "ascendente" / "descendente" / "ambos"> / sys / class / gpio / gpio248 / edge;
  • irq_mask : prohíbe las interrupciones. Llamado en la siguiente entrada: echo "none"> / sys / class / gpio / gpio248 / edge;
  • irq_unmask : habilita la interrupción en un evento que se configuró en irq_set_type. Se llama inmediatamente después de que se ejecute irq_set_type.

Segundo paso


Ahora puede agregar el controlador al ensamblaje y describir el hardware, siguiendo los pasos estándar. Primero, cree el archivo fuente:

 cd drivers/gpio/ vim gpio-skel.c :wq 

Después de agregar la configuración del controlador a drivers / gpio / Kconfig :

 config GPIO_SKEL tristate "SKEL GPIO" help Say yes here to support SKEL GPIO. 

Agregue el controlador al ensamblado en drivers / gpio / Makefile :

 obj-$(CONFIG_GPIO_SKEL) += gpio-skel.o 

Y finalmente, agregue una descripción del bloque GPIO a devicetree (* .dts):

 gpio: gpio@f8f01d00 { compatible = "skel-gpio"; rcm,ngpio = <8>; rcm,interrupt-type = <IRQ_TYPE_EDGE_RISING>; clocks = <&clkc 42>; gpio-controller ; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 29 4>; reg = <0x43c00000 0x100>; } ; 

Puede leer más sobre devicetree aquí .

Paso tres


¡Pasemos a la parte más interesante para nosotros!

Comenzamos el desarrollo del controlador conectando los archivos de encabezado necesarios y describiendo el esqueleto completo del controlador sin compatibilidad con IRQ. A continuación, llenaremos secuencialmente cada función con un código y acompañaremos las explicaciones necesarias.

Esqueleto del controlador GPIO
 /* gpio-skel.c: GPIO driver * * Name Surname <email> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { } /* * echo "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { } /* * echo "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_probe(struct platform_device *pdev) { } static int skel_gpio_remove(struct platform_device *pdev) { } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL"); 


Como puede ver en la implementación, el esqueleto del controlador parece bastante simple y no contiene muchas funciones y estructuras necesarias.

Para describir el futuro controlador, necesitamos los siguientes elementos:

  • plataforma_controlador skel_gpio_driver: describe el punto de entrada skel_gpio_probe al cargar el controlador y skel_gpio_remove cuando se extrae del núcleo;
  • struct of_device_id skel_gpio_of_match: contiene una tabla que describe el hardware del bloque GPIO;
  • struct skel_gpio_chip: contiene los campos necesarios para controlar el controlador de bloque GPIO.

Además, para cargar / extraer el controlador a / desde Linux, es necesario implementar los métodos .probe y .remove especificados en la estructura skel_gpio_driver .

Implementación de Skel_gpio_probe
 static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) goto free; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; } 


Implementación Skel_gpio_remove
 static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; } 


La función skel_gpio_remove simplemente elimina el controlador GPIO registrado del kernel, así que considere los puntos principales en skel_gpio_probe:

  • devm_kzalloc - asigna memoria para la estructura skel_gpio_chip;
  • platform_get_resource : lee la dirección del comienzo de la tarjeta de registro GPIO desde devicetree;
  • devm_ioremap_resource : realiza la asignación de la dirección física a virtual;
  • of_property_read_u32 : lee el número de GPIO disponibles desde devicetree;
  • skel_gc-> gchip. * - completa los campos de estructura requeridos;
  • gpiochip_add : agrega un controlador GPIO al controlador;

Hasta ahora, no se ha descrito por qué se usan números mágicos como 248 ... 255. La entrada skel_gc-> gchip.base = -1; pide al núcleo que asigne dinámicamente los números utilizados por el GPIO. Para averiguar estos números al final del controlador, se agrega la salida:

 dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); 

Por supuesto, Linux proporciona la capacidad de establecer números manualmente, pero veamos el comentario en el código fuente :

 @base: identifies the first GPIO number handled by this chip; * or, if negative during registration, requests dynamic ID allocation. * DEPRECATION: providing anything non-negative and nailing the base * offset of GPIO chips is deprecated. Please pass -1 as base to * let gpiolib select the chip base in all possible cases. We want to * get rid of the static GPIO number space in the long run. 


Cuarto paso


Considere la parte funcional del controlador, es decir, implementamos los siguientes métodos:
.direction_output , .direction_input , .get y .set . A continuación, se mostrará un código dependiente del hardware, que en la mayoría de los casos será diferente.

Implementación Skel_gpio_direction_input
 /* * echo "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } 


El método skel_gpio_direction_input realiza las siguientes acciones:

  • Llamado con el siguiente comando echo "in"> / sys / class / gpio / gpioN / direction;
  • Lee el registro SKEL_GPIO_PAD_DIR, que es responsable de establecer la dirección del pin GPIO;
  • Establece la máscara requerida;
  • Escribe el valor recibido de nuevo en SKEL_GPIO_PAD_DIR.

Implementación Skel_gpio_direction_output
 /* * echo "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } 


El método skel_gpio_direction_output realiza acciones similares a skel_gpio_direction_inut, excepto que se llama con los siguientes comandos:

  • echo "out"> / sys / class / gpio / gpioN / direction;
  • echo "high"> / sys / class / gpio / gpioN / direction, estableciendo el valor en 1;
  • echo "low"> / sys / class / gpio / gpioN / direction, estableciendo el valor en 0.

valor: un valor que determina el nivel de señal en la línea de salida.

Implementación de Skel_gpio_set
 static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); } 


El método skel_gpio_set realiza las siguientes acciones:

  • Llamado con el siguiente comando echo 1/0> / sys / class / gpio / gpioN / value;
  • Lee el registro SKEL_GPIO_WR_DATA, que muestra el valor de la señal actual en la línea;
  • Establece o restablece el bit requerido por desplazamiento;
  • Escribe el valor recibido de nuevo en SKEL_GPIO_WR_DATA.

Implementación de Skel_gpio_get
 static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); } 


El método skel_gpio_get lee el valor de la señal en la línea leyendo el registro SKEL_GPIO_RD_DATA.

Después de que hayamos descrito todos los métodos y estructuras necesarios, puede armar todo y echar un vistazo a la versión final.

Implementación del controlador GPIO
 /* gpio-skel.c: GPIO driver * * Name Surname <email> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { return container_of(chip, struct skel_gpio_chip, gchip); } /* * echo > "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } /* * echo > "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); } static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; } static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) return -ENXIO; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL"); 


El controlador implementado contiene la funcionalidad necesaria para controlar el GPIO, pero en este momento el controlador no admite el manejo de interrupciones, por lo que puede pasar al siguiente paso.

Quinto paso


Agregar IRQ al controlador GPIO se puede dividir en tres pasos:

  • Descripción de los métodos admitidos en las estructuras de datos del núcleo;
  • Habilitar la compatibilidad con IRQ en el momento en que el controlador se carga en el sistema
  • Implementación de métodos soportados.

Inicialmente, describimos el conjunto de operaciones necesarias:

 static struct irq_chip skel_irq_chip = { .name = "skel-gpio", .irq_mask = skel_gpio_irq_mask, .irq_unmask = skel_gpio_irq_unmask, .irq_set_type = skel_gpio_irq_set_type, }; 

Por lo tanto, el controlador puede habilitar (skel_gpio_irq_unmask) / deshabilitar (skel_gpio_irq_mask) interrupciones e indicar el tipo de evento por el cual se generará (skel_gpio_irq_set_type).
A continuación, describimos el único método responsable de asignar un número de irq virtual a uno de hardware.

 static struct irq_domain_ops skel_gpio_irq_domain_ops = { .map = skel_gpio_irq_domain_map, }; 

Luego le diremos al kernel que el controlador cargado admite trabajar con IRQ. Para hacer esto, agregue el siguiente código a la función de sonda:

Agregar soporte IRQ
 skel_gc->gchip.to_irq = skel_gpio_to_irq; skel_gc->domain = irq_domain_add_linear(pdev->dev.of_node, rcm_gc->gchip.ngpio, &skel_gpio_irq_domain_ops, skel_gc); if (!skel_gc->domain) return -ENODEV; skel_gc->irq = platform_get_irq(pdev, 0); if (skel_gc->irq < 0) goto free; for (i = 0; i < skel_gc->gchip.ngpio; i++) { int irq = rcm_gpio_to_irq(&skel_gc->gchip, i); irq_set_chip_and_handler(irq, &skel_irq_chip, handle_simple_irq); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else irq_set_noprobe(irq); #endif } irq_set_chained_handler(skel_gc->irq, skel_irq_handler); irq_set_handler_data(skel_gc->irq, skel_gc); 


En el código anterior sucede:

  • Asignación e inicialización del área bajo irq_domain;
  • Leer el número de interrupción con devicetree;
  • mapeo entre interrupción virtual y de hardware;
  • Registrar un controlador de interrupciones y configurar datos para su transmisión al controlador;

Procedemos a implementar algunos de los métodos descritos anteriormente.
skel_gpio_to_irq - Crea una asignación entre el hardware y las interrupciones virtuales. Si esta asignación ya se ha creado, devuelve el número de la interrupción virtual creada.

Implementación de Skel_gpio_to_irq
 static int skel_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) { struct skel_gpio_chip *rcm = to_skel_gpio(chip); return irq_create_mapping(rcm->domain, gpio); } 


skel_irq_handler es un controlador de interrupciones que:

  • Lee un registro de estado de interrupción;
  • Lee el registro de la máscara de interrupción;
  • Lanza interrupciones en espera de procesamiento;
  • Para cada GPIO en el que se produce una interrupción, llama a generic_handle_irq.

Implementación del controlador de interrupciones
 static void skel_irq_handler(unsigned int irq, struct irq_desc *desc) { struct skel_gpio_chip *skel_gc = irq_get_handler_data(irq); struct irq_chip *chip = irq_desc_get_chip(desc); void __iomem *base; u32 status, mask, gpio, pending; chained_irq_enter(chip, desc); base = skel_gc->regs; status = gpio_read(base, SKEL_GPIO_STATUS); mask = gpio_read(base, SKEL_GPIO_IRQ_MASK); pending = status & mask; while (pending) { gpio = __ffs(pending); pending &= ~BIT(gpio); generic_handle_irq( irq_find_mapping(skel_gc->domain, gpio)); } chained_irq_exit(chip, desc); } 


Eso es todo, en este artículo aprendimos cómo interactúa el controlador GPIO con el sistema de archivos virtual sysfs, implementamos la estructura básica del controlador GPIO y también examinamos los métodos necesarios para admitir IRQ.

El artículo no describe la implementación de los métodos skel_gpio_irq_unmask, skel_gpio_irq_mask y skel_gpio_irq_set_type por dos razones. En primer lugar, estos métodos son fáciles de implementar. En segundo lugar, depende del hardware. Son responsables de permitir o deshabilitar las interrupciones para ciertos eventos que admite el controlador GPIO.

Por favor, si encuentra errores / inexactitudes, o si tiene algo que agregar, escriba en el PM o en los comentarios.

Gracias por su atencion!

Source: https://habr.com/ru/post/es419273/


All Articles