Linux device driver (3)--LED character driver

Code learning materials come from:

Lecture 4.1 Linux LED Lamp Driving Experiment (Direct Operation Register) - Address Mapping_哔哩哔哩_bilibili

Only for personal study/review, intrusion deleted

1. LED driving principle

The IMX6ULL series chip is a low-power, high-performance and low-cost application processor based on the ARM Cortex A7 core.

Any peripheral driver under linux must finally configure the corresponding hardware registers, so the LED light driver in this chapter is ultimately configured for the IO port of IMX6ULL. Writing a driver under linux must conform to the linux driver framework. IMX6U-ALPHA development board The LED on the IMX6ULL is connected to this pin of GPIO_IO3.

cat /proc/devices displays all devices, but to operate the device, you need to control the device node in the corresponding /dev directory

2. Address mapping

Linux driver development cannot directly read and write register addresses, for example, the physical address of register A is 0x01010101. Bare metal development can directly operate on the physical address, but linux cannot, because linux will enable the MMU, and the full name of the MMU is the memory manager unit, which is the memory management unit. In the old version of linux, the processor required an MMU, but now the linux kernel already supports processors without an MMU. The main functions of the MMU are as follows:

1) Complete the mapping from virtual space to physical space, also known as address mapping

2) Memory protection, set the protection authority of the memory

There is no need to delve into the problem that the virtual address range is larger than the physical address range.

All operations in linux are virtual addresses, so you need to get the virtual address of 0x01010101 first. For example, the address of the multiplexing register IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 of the GPIO_IO03 pin of IMX6ULL is 0X020E0068. If the MMU is not enabled, write data directly to the 0X020E0068 register to configure the multiplexing function of GPIO1_IO03. Now that the MMU is turned on, we need to get the virtual address corresponding to the physical address 0X020E0068 in the Linux system. This involves the conversion between physical memory and virtual memory, and we need to get two functions: ioremap and iounmap .

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

Take these two functions as an example:

#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. LED light character drive

When setting the IO in STM32, the following points need to be done:

1) Enable the clock of the specified GPIO

2) Initialize GPIO, such as output function, pull-up, speed, etc.

3) Some IO ports of STM32 can be used as pins of other peripherals, that is, IO multiplexing. If you want to use IO as other peripheral pins, you need to set the multiplexing function of IO.

4) Finally, set the GPIO to output high or low level

For the meaning and function of each register of GPIO, please refer to:

IMX6ULL bare metal development to light up the LED light - Swiler's Blog - CSDN Blog

There are 8 registers in the GPIO block diagram, which are:

Data register (GPIO_DR): data register (32 bits), a GPIO group has a maximum of 32 IOs, so each bit in the DR register corresponds to a GPIO.

GPIO direction register (GPIO_GDIR): direction register (32 bits), which is to configure input and output. 0: input, 1: output.

Pad sample register (GPIO_PSR): Status register (32 bits), used to obtain the high and low level values ​​of GPIO.

Interrupt control registers (GPIO_ICR1, GPIO_ICR2): Interrupt control registers, ICR1 configures the lower 16 GPIOs, and ICR2 configures the upper 16 GPIOs.

The two bits correspond to four trigger states: 00: low level trigger, 01: high level trigger, 10: rising edge trigger, 11: falling edge trigger.

Edge select register (GPIO_EDGE_SEL): Configure the edge interrupt register (32 bits): Configuring this register will override the settings of ICR1 and ICR2, and setting it to 1 is a double-edge trigger.

Interrupt mask register (GPIO_IMR): Interrupt mask register (32 bits), used to enable or disable interrupts.

Interrupt status register (GPIO_ISR): Interrupt flag bit register (32 bits), as long as a GPIO interrupt occurs, the corresponding bit in the ISR will be set to 1. By reading the value of this register, you can determine whether an interrupt is generated, but remember to manually clear the interrupt flag bit after processing the interrupt, that is, write 1 to the corresponding bit in the ISR.

The registers that need to be controlled for lighting are as follows:

#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. I/O memory access function

After using the ioremap function to map the physical address of the register to the virtual address, we can directly access these addresses through the pointer, but Linux does not recommend this, but recommends using a set of operation functions to read and write the mapped memory .

1) Read operation function

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) Write operation function

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. LED character driver demo

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

Guess you like

Origin blog.csdn.net/qq_58550520/article/details/129162807