Controlador de dispositivo Linux (3): controlador de caracteres LED

Los materiales de aprendizaje de código provienen de:

Conferencia 4.1 Experimento de conducción de lámparas LED de Linux (registro de operación directa) - Mapeo de direcciones_哔哩哔哩_bilibili

Sólo para estudio/revisión personal, intrusión eliminada

1. Principio de conducción del LED

El chip de la serie IMX6ULL es un procesador de aplicaciones de bajo consumo, alto rendimiento y bajo costo basado en el núcleo ARM Cortex A7.

Cualquier controlador periférico en Linux finalmente debe configurar los registros de hardware correspondientes, por lo que el controlador de luz LED en este capítulo finalmente se configura para el puerto IO de IMX6ULL. La escritura de un controlador en Linux debe cumplir con el marco del controlador de Linux. Placa de desarrollo IMX6U-ALPHA El LED del IMX6ULL está conectado a este pin de GPIO_IO3.

cat /proc/devices muestra todos los dispositivos, pero para operar el dispositivo, necesita controlar el nodo del dispositivo en el directorio /dev correspondiente

2. Mapeo de direcciones

El desarrollo de controladores de Linux no puede leer y escribir direcciones de registro directamente; por ejemplo, la dirección física del registro A es 0x01010101. El desarrollo bare metal puede operar directamente en la dirección física, pero Linux no, porque Linux habilitará la MMU, y el nombre completo de la MMU es la unidad de administración de memoria, que es la unidad de administración de memoria. En la versión anterior de Linux, el procesador requería una MMU, pero ahora el kernel de Linux ya admite procesadores sin MMU. Las principales funciones de la MMU son las siguientes:

1) Complete el mapeo del espacio virtual al espacio físico, también conocido como mapeo de direcciones.

2) Protección de la memoria, establece la autoridad de protección de la memoria.

No es necesario profundizar en el problema de que el rango de direcciones virtuales es mayor que el rango de direcciones físicas.

Todas las operaciones en Linux son direcciones virtuales, por lo que primero debe obtener la dirección virtual 0x01010101. Por ejemplo, la dirección del registro de multiplexación IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 del pin GPIO_IO03 de IMX6ULL es 0X020E0068. Si la MMU no está habilitada, escriba datos directamente en el registro 0X020E0068 para configurar la función de multiplexación de GPIO1_IO03. Ahora que la MMU está encendida, necesitamos obtener la dirección virtual correspondiente a la dirección física 0X020E0068 en el sistema Linux, esto implica la conversión entre memoria física y memoria virtual, y necesitamos obtener dos funciones: ioremap e iounmap .

arco/arm64/include/asm/io.h

#define ioremap(addr, size)             __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
static inline void __iomem *__ioremap(unsigned long port, unsigned long size,
                                      unsigned long flags)
{
        return ioremap(port, size);
}
参数说明:
addr:起始地址
size:要映射的内存空间大小
flags:ioremap类型,选择MT_DEVICE

返回值:__iomem类型的指针,指向映射后的虚拟地址首地址

#define iounmap                         __iounmap
extern void __iounmap(volatile void __iomem *addr);

Tome estas dos funciones como ejemplo:

#define SW_MUX_GPIO1_IO03_BASE 0X020E0068
static void __iomem *SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
...
iounmap(SW_MUX_GPIO1_IO03_BASE);

宏SW_MUX_GPIO1_IO03_BASE是寄存器物理地址,SW_MUX_GPIO1_IO03是映射后的虚拟地址。对于IMX6ULL的一个寄存器是4字节的,所以映射内存的长度为4,映射完成以后就可以直接对SW_MUX_GPIO1_IO03进行读写操作即可。

卸载驱动时需要iounmap释放虚拟地址映射

3. Unidad de caracteres con luz LED

Al configurar IO en STM32, se deben realizar los siguientes puntos:

1) Habilite el reloj del GPIO especificado

2) Inicialice GPIO, como función de salida, pull-up, velocidad, etc.

3) Algunos puertos IO de STM32 se pueden usar como pines de otros periféricos, es decir, multiplexación IO. Si desea usar IO como otros pines periféricos, debe configurar la función de multiplexación de IO.

4) Finalmente, configure el GPIO para que emita un nivel alto o bajo

Para conocer el significado y la función de cada registro de GPIO, consulte:

Desarrollo de metal desnudo IMX6ULL para iluminar la luz LED - Blog de Swiler - Blog de CSDN

Hay 8 registros en el diagrama de bloques GPIO, que son:

Registro de datos (GPIO_DR): registro de datos (32 bits), un grupo GPIO tiene un máximo de 32 IO, por lo que cada bit en el registro DR corresponde a un GPIO.

Registro de dirección GPIO (GPIO_GDIR): registro de dirección (32 bits), que sirve para configurar la entrada y la salida. 0: entrada, 1: salida.

Registro de muestra de pad (GPIO_PSR): Registro de estado (32 bits), utilizado para obtener los valores de nivel alto y bajo de GPIO.

Registros de control de interrupciones (GPIO_ICR1, GPIO_ICR2): registros de control de interrupciones, ICR1 configura los 16 GPIO inferiores y ICR2 configura los 16 GPIO superiores.

Los dos bits corresponden a cuatro estados de activación: 00: activación por nivel bajo, 01: activación por nivel alto, 10: activación por flanco ascendente, 11: activación por flanco descendente.

Registro de selección de borde (GPIO_EDGE_SEL): configure el registro de interrupción de borde (32 bits): la configuración de este registro anulará la configuración de ICR1 e ICR2, y establecerlo en 1 es un disparador de doble flanco.

Registro de máscara de interrupción (GPIO_IMR): Registro de máscara de interrupción (32 bits), utilizado para habilitar o deshabilitar interrupciones.

Registro de estado de interrupción (GPIO_ISR): registro de bits de indicador de interrupción (32 bits). Siempre que se produzca una interrupción GPIO, el bit correspondiente en el ISR se establecerá en 1. Al leer el valor de este registro, puede determinar si se genera una interrupción, pero recuerde borrar manualmente el bit del indicador de interrupción después de procesar la interrupción, es decir, escribir 1 en el bit correspondiente en el ISR.

Los registros que es necesario controlar para la iluminación son los siguientes:

#define CCM_CCGR1_BASE (0X020C406C)     // 时钟寄存器
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)     // 设置 IO 的复用功能,使其复用为 GPIO 功能
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)     // 设置 IO 的上下拉、速度等属性
#define GPIO1_DR_BASE  (0X0209C000)     // 数据寄存器(32位),一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO
#define GPIO1_GDIR_BASE  (0X0209C004)   // 配置输入输出。0:输入,1:输出

4. Función de acceso a la memoria de E/S

Después de usar la función ioremap para asignar la dirección física del registro a la dirección virtual, podemos acceder directamente a estas direcciones a través del puntero, pero Linux no recomienda esto, pero recomienda usar un conjunto de funciones operativas para leer y escribir la memoria asignada . .

1) Función de operación de lectura

extern inline u8 readb(const volatile void __iomem *addr)
extern inline u16 readw(const volatile void __iomem *addr)
extern inline u32 readl(const volatile void __iomem *addr)

三个函数分别对应8bit,16bit,32bit读操作,参数addr就是要读取内存地址,返回值为读到的数据

2) Función de operación de escritura

extern inline void writeb(u8 b, volatile void __iomem *addr)
extern inline void writew(u16 b, volatile void __iomem *addr)
extern inline void writel(u32 b, volatile void __iomem *addr)

三个函数分别对应8bit,16bit,32bit写操作,参数value表示要写入的数值,addr是写入的地址

5. Demostración de unidad de caracteres LED

led.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/compat.h>

#define LED_MAJOR 200
#define LED_NAME "led"

/* register physical address */
#define CCM_CCGR1_BASE (0X020C406C)        // 时钟寄存器
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)        // 设置 IO 的复用功能,使其复用为 GPIO 功能
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)        // 设置 IO 的上下拉、速度等属性
#define GPIO1_DR_BASE  (0X0209C000)        // 数据寄存器(32位),一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO,低电平0 led亮
#define GPIO1_GDIR_BASE  (0X0209C004)        // 方向寄存器,设置为输入还输出。0:输入,1:输出

static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF 0;        // 关闭
#define LEDON 1;        // 打开

static ssize_t led_write(struct file *file,
                                char __user *buf,
                                     size_t count, loff_t *offset)
{
        int retval;
        unsigned char databuf[1];
        retval = copy_from_user(databuf, buf, count);
        if (retval < 0) {
                printk("kernel write failed! \n");
                return -EFAULT;
        }

        if (databuf[1] == LEDOFF) {
                val = readl(GPIO1_DR);
                val |= (1 << 3);        // bit3置1,关闭led
                writel(val, GPIO1_DR);
        }

        if (databuf[1] == LEDON) {
                val = readl(GPIO1_DR);
                val &= ~(1 << 3);        // bit3置0,打开led
                writel(val, GPIO1_DR);
        }

        return 0;
}

static int led_open(struct inode *inode, struct file *file)
{
        printk("led_open success \n");
        return 0;
}

static int led_release(struct inode *inode, struct file *file)
{
        printk("led close success \n");
        return 0;
}

static const struct file_operations led_fops = {
        .owner = THIS_MODULE,
        .write = led_write,
        .open = led_open,
        .release = led_release,
};

static int __init led_init(void)
{
        int ret = 0;
        int val = 0;

        /*init led, address remap*/
        IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
        SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
        SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
        GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
        GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

        val = readl(IMX6U_CCM_CCGR1);
        val &= ~(3 << 26);        // 先清除以前的配置bit26和bit27
        val |= 3 << 26;                // bit26,bit27置1
        writel(val, IMX6U_CCM_CCGR1);

        writel(0x5, SW_MUX_GPIO1_IO03);                // 设置复用
        writel(0x1080, SW_PAD_GPIO1_IO03);        // 设置电气属性

        val = readl(GPIO1_GDIR);
        val |= 1 << 3;                // bit3置1,设置为输出
        writel(val, GPIO1_GDIR);

        val = readl(GPIO1_DR);
        val &= ~(1 << 3);        // bit3清零,打开led
        writel(val, GPIO1_DR);

        ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
        if (ret < 0) {
                printk("led register failed \n");
                return -EIO;
        }

        printk("led_init \n");
        return 0;
}

static void __exit led_exit(void)
{
        int val = 0;

        iounmap(IMX6U_CCM_CCGR1);
        iounmap(SW_MUX_GPIO1_IO03);
        iounmap(SW_PAD_GPIO1_IO03);
        iounmap(GPIO1_DR);
        iounmap(GPIO1_GDIR);

        val = readl(GPIO1_DR);
        val |= (1 << 3);        // bit3置1,关闭led
        writel(val, GPIO1_DR);

        unregister_chrdev(LED_MAJOR, LED_NAME);

        printk("led_exit \n");
}

module_init(led_init);
module_exit(led_exit);

ledAPP.c

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

/*
 * ./ledAPP <filename> <1/2>
 * 1 读数据
 * 2 写数据
 * */ 

#define LEDOFF 0
#define LEDON 1

int main(int argc, int *argv[])
{
        char *filename;
        int fd;
        int ret;
        unsigned char databuf[1];
        
        filename = argv[1];

        if (argc < 3) {
                printf("usage error \n");
        }

        fd = open(filename, O_RDWR);
        if (fd < 0) {
                printf("open %s failed\n", filename);
                return -1;
        }

        databuf[0] = atoi(argv[2]);
        ret = write(fd, databuf, 1);            // 打开led灯
        if (ret < 0) {
                printf("LED control failed \n");
        }   

        ret = close(fd);
        if (ret < 0) {
                printf("close %s failed\n", filename);
                return -1; 
        }   

        return 0;
}

Supongo que te gusta

Origin blog.csdn.net/qq_58550520/article/details/129162807
Recomendado
Clasificación