Linux LED driver development experiment

In the last chapter, we explained the development steps of character device driver in detail, and took a virtual chrdevbase device as an example to lead you to complete the development of the first character device driver. In this chapter, we begin to write the first real linux character device driver. There is an LED lamp on the I.MX6U-ALPHA development board. We have written the bare metal driver of this led lamp in the bare metal article. In this chapter, we will learn how to write the LED lamp driver under Linux.

LED lamp driving principle under Linux

Any peripheral driver under Linux will eventually configure the corresponding hardware registers. Therefore, the LED driver in this chapter finally configures the IO port of I.MX6ULL. Different from the bare metal experiment, writing the driver under Linux should comply with the linux driver framework. 1. The LED on the mx6u-alpha development board is connected to gpio1 of I.MX6ULL_ Io03 is on this pin, so the focus of this chapter is to write the I.MX6UL pin control driver under Linux. GPIO details about I.MX6ULL
Please refer to Chapter 8 for explanation.

Address mapping

Before writing drivers, we need to briefly understand the artifact of MMU. The full name of MMU is called memory management unit, that is, memory management unit. In the old version of Linux, the processor must have MMU, but now the Linux kernel supports processors without MMU. The main functions of MMU are as follows:

① Complete the mapping from virtual space to physical space.
② Memory protection, set the access rights of memory and set the buffer characteristics of virtual storage space.
Let's focus on point ①, that is, the mapping from virtual space to physical space, also known as address mapping. First, understand two address concepts: virtual address (VA) and physical address (PA). For a 32-bit processor, the virtual address range is 2^32=4GB. There is 512MB DDR3 on our development board, which is physical memory. It can be mapped to the whole 4GB virtual space through MMU, as shown in figure 41.1.1:

There are only 512MB of physical memory and 4GB of virtual memory, so there must be multiple virtual addresses mapped to the same physical address. The processor will deal with the problem that the virtual address range is larger than the physical address range. We don't go into it here, because MMU is a very complex thing. If there is time later, the punctual atomic Linux team will specially do MMU special tutorials.

When the Linux kernel starts, it initializes the MMU and sets the memory mapping. After setting, the CPU accesses the virtual address. For example, gpio1 of I.MX6ULL_ Multiplexing register of io03 pin
IOMUXC_ SW_ MUX_ CTL_ PAD_ GPIO1_ The address of io03 is 0X020E0068. If the MMU is not enabled, gpio1 can be configured by directly writing data to the register address 0X020E0068_ Reuse function of io03. Now the MMU is turned on and the memory mapping is set, so you can't write data directly to the address 0X020E0068. We must get the virtual address corresponding to the physical address 0X020E0068 in the Linux system. This involves the conversion between physical memory and virtual memory. We need to use two functions: ioremap and iounmap.

1. ioremap function
The ioremap function is used to obtain the virtual address space corresponding to the specified physical address space. It is defined in
In the arch/arm/include/asm/io.h file, the definitions are as follows:

1 #define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
3 void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
4 {
5 return arch_ioremap_caller(phys_addr, size, mtype,
6 }

ioremap is a macro with two parameters: cookie and size. What really works is the function__ arm_ioremap, this function has three parameters and a return value. The meanings of these parameters and return values are as follows:

phys_addr: the physical start address to map to.
Size: the size of memory space to map.
mtype: type of ioremap. MT can be selected_ DEVICE,MT_DEVICE_NONSHARED,
MT_DEVICE_CACHED and MT_DEVICE_WC, select MT for ioremap function_ DEVICE.

Return value:__ Pointer of type iomem, pointing to the first address of the mapped virtual space.

Suppose we want to get the iomuxc of I.MX6ULL_ SW_ MUX_ CTL_ PAD_ GPIO1_ For the virtual address corresponding to io03 register, use the following code:

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

Macro SW_MUX_GPIO1_IO03_BASE is the physical address of the register, SW_MUX_GPIO1_IO03 is after mapping
Virtual address of. For I.MX6ULL, a register is 4 bytes (32 bits), so the mapped memory length is 4.
After the mapping is completed, the SW is directly mapped_ MUX_ GPIO1_ Io03 can read and write.

2. iounmap function
When unloading the driver, you need to use the iounmap function to release the mapping made by the ioremap function. The prototype of the iounmap function is as follows:

void iounmap (volatile void __iomem *addr)

iounmap has only one parameter addr, which is the first address of the virtual address space to be unmapped. If we want to cancel iomuxc now_ SW_ MUX_ CTL_ PAD_ GPIO1_ For the address mapping of io03 register, use the following code:


I/O memory access function

I/O here means input / output, not GPIO pin when we learn MCU. Two concepts are involved here: I/O port and I/O memory. When an external register or memory is mapped to an IO space, it is called an I/O port.
When an external register or memory is mapped to a memory space, it is called I/O memory. However, for ARM, there is no concept of I/O space, so there is only I/O memory (which can be directly understood as memory) under the ARM system. 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. However, the Linux kernel does not recommend this, but recommends using a set of operation functions to read and write the mapped memory.

1. Read operation function
Read operation functions are as follows:

1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)

The readb, readw and readl functions correspond to 8bit, 16bit and 32bit read operations respectively. The parameter addr is to read the write memory address, and the return value is the read data.

2. Write operation function

Write operation functions are as follows:

1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)

The writeb, writew and writel functions correspond to 8bit, 16bit and 32bit write operations respectively. The parameter value is the value to be written and addr is the address to be written.

Hardware schematic analysis

Refer to section 8.3 for the schematic diagram of experimental hardware in this chapter.

Experimental programming

The routine path corresponding to this experiment is: development board CD - > 2, Linux driver routine - > 2_ led.
In this chapter, the LED lamp driver under Linux can be used to switch the LED lamp on the I.MX6U-ALPHA development board through the application.

LED driver programming

Create a new folder named "2_led", and then in 2_ VSCode project is created in the LED folder, and the workspace is named "led".
After the project is created, create a new led.c file, which is the led driver file. Enter the following contents in led.c:

1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <asm/mach/map.h>
10 #include <asm/uaccess.h>
11 #include <asm/io.h>
12 /***************************************************************
13 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
14 File name: led.c
15 Author: Zuo Zhongkai
16 Version: V1.0
17 Description: LED driver file.
18 Others: None
19 Forum:
20 Log: first version v1.0 created by Zuo Zhongkai on January 30, 2019
21 ***************************************************************/
22 #define LED_MAJOR 200 / * main equipment No*/
23 #define LED_NAME "led" / * device name*/
25 #define LEDOFF 0 / * turn off the light*/
26 #define LEDON 1 / * turn on the light*/
28 /* Register physical address*/
29 #define CCM_CCGR1_BASE (0X020C406C)
30 #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
31 #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
32 #define GPIO1_DR_BASE (0X0209C000)
33 #define GPIO1_GDIR_BASE (0X0209C004)
35 /* Mapped register virtual address pointer*/
36 static void __iomem *IMX6U_CCM_CCGR1;
37 static void __iomem *SW_MUX_GPIO1_IO03;
38 static void __iomem *SW_PAD_GPIO1_IO03;
39 static void __iomem *GPIO1_DR;
40 static void __iomem *GPIO1_GDIR;
42 /*
43 * @description : LED On / off
44 * @param - sta : LEDON(0) Turn on the LED and LEDOFF(1) turns off the LED
45 * @return : nothing
46 */
47 void led_switch(u8 sta)
48 {
49 u32 val = 0;
50 if(sta == LEDON) {
51 val = readl(GPIO1_DR);
52 val &= ~(1 << 3);
53 writel(val, GPIO1_DR);
54 }else if(sta == LEDOFF) {
55 val = readl(GPIO1_DR);
56 val|= (1 << 3);
57 writel(val, GPIO1_DR);
58 }
59 }
61 /*
62 * @description : open device
63 * @param – inode : inode passed to driver
64 * @param - filp : The device file has a file structure called private_ Member variable of data
65 * Generally, private is used when open ing_ Data points to the device structure.
66 * @return : 0 success; Other failures
67 */
68 static int led_open(struct inode *inode, struct file *filp)
69 {
70 return 0;
71 }
73 /*
74 * @description : Read data from device
75 * @param - filp : Device file to open (file descriptor)
76 * @param - buf : Data buffer returned to user space
77 * @param - cnt : Length of data to read
78 * @param - offt : Offset relative to the first address of the file
79 * @return : The number of bytes read. If it is negative, it indicates that the read failed
80 */
81 static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
82 {
83 return 0;
84 }
86 /*
87 * @description : Write data to device
88 * @param - filp : A device file that represents an open file descriptor
89 * @param - buf : Data to write to device
90 * @param - cnt : Length of data to write
91 * @param - offt : Offset relative to the first address of the file
92 * @return : The number of bytes written. If it is negative, it indicates that the write failed
93 */
94 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
95 {
96 int retvalue;
97 unsigned char databuf[1];
98 unsigned char ledstat;
100 retvalue = copy_from_user(databuf, buf, cnt);
101 if(retvalue < 0) {
102 printk("kernel write failed!\r\n");
103 return -EFAULT;
104 }
106 ledstat = databuf[0]; /* Get status value*/
108 if(ledstat == LEDON) {
109 led_switch(LEDON); /* Turn on the LED*/
110 } else if(ledstat == LEDOFF) {
111 led_switch(LEDOFF); /* Turn off the LED*/
112 }
113 return 0;
114 }
116 /*
117 * @description : Turn off / release the device
118 * @param – filp : Device file to close (file descriptor)
119 * @return : 0 success; Other failures
120 */
121 static int led_release(struct inode *inode, struct file *filp)
122 {
123 return 0;
124 }
126 /* Device operation function*/
127 static struct file_operations led_fops = {
128 .owner = THIS_MODULE,
129 .open = led_open,
130 .read = led_read,
131 .write = led_write,
132 .release = led_release,
133 };
135 /*
136 * @description : Drive entry function
137 * @param : nothing
138 * @return : nothing
139 */
140 static int __init led_init(void)
141 {
142 int retvalue = 0;
143 u32 val = 0;
145 /* Initialize LED */
146 /* 1,Register address mapping*/
147 IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
148 SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
149 SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
150 GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
151 GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
153 /* 2,Enable GPIO1 clock*/
154 val = readl(IMX6U_CCM_CCGR1);
155 val &= ~(3 << 26); /* Clear previous settings*/
156 val |= (3 << 26); /* Set new value*/
157 writel(val, IMX6U_CCM_CCGR1);
159 /* 3,Set gpio1_ Reuse function of io03 and reuse it as
160 * GPIO1_IO03,Finally, set the IO attribute.
161 */
162 writel(5, SW_MUX_GPIO1_IO03);
164 /* Register SW_PAD_GPIO1_IO03 setting IO properties*/
165 writel(0x10B0, SW_PAD_GPIO1_IO03);
167 /* 4,Set GPIO1_IO03 is an output function*/
168 val = readl(GPIO1_GDIR);
169 val &= ~(1 << 3); /* Clear previous settings*/
170 val |= (1 << 3); /* Set as output*/
171 writel(val, GPIO1_GDIR);
173 /* 5,Default off LED */
174 val = readl(GPIO1_DR);
175 val |= (1 << 3);
176 writel(val, GPIO1_DR);
178 /* 6,Register character device driver*/
179 retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
180 if(retvalue < 0){
181 printk("register chrdev failed!\r\n");
182 return -EIO;
183 }
184 return 0;
185 }
187 /*
188 * @description : Drive exit function
189 * @param : nothing
190 * @return : nothing
191 */
192 static void __exit led_exit(void)
193 {
194 /* Unmap*/
195 iounmap(IMX6U_CCM_CCGR1);
196 iounmap(SW_MUX_GPIO1_IO03);
197 iounmap(SW_PAD_GPIO1_IO03);
198 iounmap(GPIO1_DR);
199 iounmap(GPIO1_GDIR);
201 /* Unregister character device driver*/
202 unregister_chrdev(LED_MAJOR, LED_NAME);
203 }
205 module_init(led_init);
206 module_exit(led_exit);
208 MODULE_AUTHOR("zuozhongkai");

Lines 22 ~ 26 define some macros, including main equipment number, equipment name and LED on / off macro.
Lines 29 ~ 33 are the register macro definitions to be used in this experiment.
Lines 36-40, register address pointer after memory mapping.
Lines 47-59, LED_ The switch function is used to control the LED lights on and off on the development board. When the parameter sta is LEDON(1), turn on the LED lights, and when sta is LEDOFF(0), turn off the LED lights.

Lines 68-71, LED_ The open function is an empty function. You can add relevant contents to this function by yourself. Generally, the device structure is used as the private data of the parameter filp (filp - > private_data).
Lines 81-84, LED_ The read function is empty. If you want to read the LED status in the application, you can add corresponding code to this function, such as reading gpio1_ The value of the Dr register is then returned to the application.
Lines 94-114, LED_ The write function realizes the switching operation of the LED lamp. When the application calls the write function to write data to the LED device, this function will be executed. First, use the function copy_from_user obtains the operation information sent by the application (turn on or off the LED), and finally turns on or off the LED according to the operation information of the application.
Lines 121-124, led_release function is an empty function. You can add relevant contents to this function. Generally, the LED will be released when the device is turned off_ Private data added in the open function.
Lines 127 to 133, device file operation structure_ FOPS definition and initialization.
Lines 140-185, drive the entry function led_init, this function realizes the initialization of LED. Lines 147 ~ 151 obtain the virtual address mapped by the physical register address through the ioremap function. After obtaining the virtual address corresponding to the register, the relevant initialization can be completed. For example, enable GPIO1 clock and set GPIO1_IO03 multiplexing function and GPIO1 configuration_ Properties of io03, etc. Finally, the most important step! Using register_ The chrdev function registers the character device led.
Lines 192 to 202, drive the exit function led_exit, first use the function iounmap to unmap the memory, and finally use the function unregister_chrdev unregisters the LED character device.
Lines 205 to 206, use module_init and module_exit these two functions specify the led device driver load and unload functions.
Lines 207-208, add LICENSE and author information.

Write test APP

Write the test APP. After the led driver is loaded successfully, manually create the / dev/led node, and the application APP operates the / dev/led
File to complete the control of LED equipment. Write 0 to the / dev/led file to turn off the LED, and write 1 to turn on the LED.
Create a new ledApp.c file and enter the following contents:

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8 /***************************************************************
9 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
10 File name: ledApp.c
11 Author: Zuo Zhongkai
12 Version: V1.0
13 Description: LED driver test APP.
14 Others: None
15 How to use:. / ledtest /dev/led 0 turn off the LED
16 ./ledtest /dev/led 1 Turn on the LED
17 Forum:
18 Log: first version v1.0 created by Zuo Zhongkai on January 30, 2019
19 ***************************************************************/
21 #define LEDOFF 0
22 #define LEDON 1
24 /*
25 * @description : main main program
26 * @param - argc : argv Number of array elements
27 * @param - argv : Specific parameters
28 * @return : 0 success; Other failures
29 */
30 int main(int argc, char *argv[])
31 {
32 int fd, retvalue;
33 char *filename;
34 unsigned char databuf[1];
36 if(argc != 3){
37 printf("Error Usage!\r\n");
38 return -1;
39 }
41 filename = argv[1];
43 /* Turn on the led driver*/
44 fd = open(filename, O_RDWR);
45 if(fd < 0){
46 printf("file %s open failed!\r\n", argv[1]);
47 return -1;
48 }
50 databuf[0] = atoi(argv[2]); /* What to do: turn on or off*/
52 /* Write data to / dev/led file*/
53 retvalue = write(fd, databuf, sizeof(databuf));
54 if(retvalue < 0){
55 printf("LED Control Failed!\r\n");
56 close(fd);
57 return -1;
58 }
60 retvalue = close(fd); /* Close file*/
61 if(retvalue < 0){
62 printf("file %s close failed!\r\n", argv[1]);
63 return -1;
64 }
65 return 0;
66 }

The content of ledApp.c is still very simple, that is, to open, close and write the led driver file.

Run test

Compile driver and test APP

1. Compile driver
Write the Makefile file. The Makefile file of the experiment in this chapter is basically the same as the experiment in Chapter 40, except that the value of obj-m variable is changed to led.o. the contents of the Makefile are as follows:

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
4 obj-m := led.o
11 clean:

In line 4, set the value of obj-m variable to led.o.
Input the following command to compile the driver module file:

make -j32

After successful compilation, a driver module file named "led.ko" will be generated.
2. Compile the test APP. Enter the following command to compile the test program ledApp.c:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

After the compilation is successful, the ledApp application will be generated.

Run test

be careful! If you use the punctual atomic delivery system to do this experiment, you will find that the LED light will always flash. This is because the on-time atomic factory system takes the LED light as the heartbeat light by default. Therefore, after the system is started, the LED light will flash automatically, which will affect everyone's experiment. If you are self porting the kernel and root file system in full accordance with this tutorial, you will not encounter this problem. If the factory system is directly used for the experiment, we need to turn off the heartbeat function of the LED lamp. For the turning off method, refer to subsection 3.1 of [punctual atom] I.MX6U user quick experience, or enter the following command:

echo none > /sys/class/leds/sys-led/trigger // Change the trigger mode of the LED

In addition, you can also refer to the post:

Copy the led.ko and ledApp files compiled in the previous section to the rootfs/lib/modules/4.1.15 directory, restart the development board, enter the directory lib/modules/4.1.15, and enter the following command to load the led.ko driver module:

depmod //This command needs to be run when the driver is loaded for the first time
modprobe led.ko //Load driver

After the driver is loaded successfully, create the "/ dev/led" device node. The command is as follows:

mknod /dev/led c 200 0

After the driver node is successfully created, you can use the ledApp software to test whether the driver works normally. Enter the following command to turn on the LED:

./ledApp /dev/led 1 //Turn on the LED

After entering the above command, observe whether the red LED on the I.MX6U-ALPHA development board is on. If it is on, it indicates that the drive works normally. Turn off the LED after entering the following command:

./ledApp /dev/led 0 //Turn off the LED

After entering the above command, observe whether the red LED light on the I.MX6U-ALPHA development board goes out. If it goes out, it means that the LED driver we write works completely normally! So far, we have successfully written the first real Linux device driver.
If you want to unload the driver, you can enter the following command:

rmmod led.ko

Tags: Linux Operation & Maintenance

Posted on Sat, 06 Nov 2021 07:53:30 -0400 by ucbones