[Linux driver learning] character driver GPIO

This article aims to summarize the experience of learning character-driven GPIO based on Wei Dongshan's IMX6ULL PRO development board.
[1] In the case of no hardware operation, what parts should a simple character driver include?
The following default device name: device
Two basic variables:
major → major device number
struct class *device_class → device classification
These two variables need to be used when registering and generating the device.
(1) Write operation functions around the file_operations device file structure.
Its structure includes five attributes: among them, open, read, write, and relase are actually function pointers.
owner【THIS_MODULE】→Slave
open→Function read when the file is opened
→Function write called when the file is read
→Function release called when the file is written
→Function called when the file is closed
(2) Declaring the file_operations device structure
is actually It is to let the function pointer in file_operations point to the operation function written by the user.

static struct file_operations device_drv = {
    
    
   .owner = THIS_MODULE,
   .open  = device_drv_open,
   .read  = device_drv_read,
   .write = device_drv_write,
   .release = device_drv_close,
}

(3) Write the __init device_init(void) function.
This function is the function that the character device enters first when it is loaded, but why it must be it, please press it first.
For example:
insmod device.ko
when loading the device.ko module, it will first enter the device_init function to register and generate the device.
The first step is to register:
The so-called registration is the process of applying for a device number from the kernel.

major = register_chrdev(0,"device",&device_drv);
第二步生成类:
device_class = class_create(THIS_MODULE,"device_class");

So far, two basic variables have been used.
The third step is to generate the device file:

device_create(device_class,NULL,MKDEV(major,0),NULL,"device");

This step is crucial because it generates an operable device file device in the /dev/ directory.
If you need to generate a series of devices of the same type, you need to change the minor device number and the corresponding device file name. For example:

for(i = 0;i<num;i++)
device_create(device_class,NULL,MKDEV(major,i),NULL,"device%d",i);

In this way, a series of device files will be generated according to the number of num, and the file names are: device0, device1····deviceN. It is worth noting that their major device numbers are all determined during registration, and the minor device numbers are filled in according to the order of the cycle and determined in MKDEV (primary, minor).
(4) Write the __exit device_exit(void) function.
This function is the function that the character device enters when it is unloaded, but why it must be it, please press it first.
For example:

rmmod device.ko

When uninstalling the device.ko module, it will first enter the device_exit function to log out the device, clear the class, and destroy the device file.
Step 1: Destruction of device files

 device_destroy(device_class,MKDEV(major,0));

Step Two: Class Cleanup

 class_destroy(device_class);

Step Three: Device Logout

 unregister_chrdev(major,"device");

(5) Improve device information
This step is mainly to specify the function pointers that need to be loaded when the module is loaded and unloaded, and to supplement the passport information of the module.

module_init(device_init);//指定加载时使用device_init
module_exit(device_exit);//指定卸载时使用device_exit
MODULE_LICENSE("GPL");

Summary: A basic character driver mainly includes these five steps, which revolve around three basic elements: major, struct class *device_class, and file_operations.
When the device module (.ko) is loaded, it first enters the specified init function to register the device, generate classes, and device files. Then, according to the attributes in file_operations, the corresponding operation function will be called when operating the device file. Finally, when the device module (.ko) is uninstalled, it will enter the specified exit function to destroy the device file of the device, clear the class, and cancel the device number.
The two variables major and struct class *device_class run through the loading and unloading of device modules. When loading, major obtains the major device number provided by the kernel to the device, and device_class obtains the device class. Then generate corresponding operable device files in the /dev/ directory according to the major device number, minor device number and device class.
file_operations runs through the opening, reading/writing, and closing of operable device files, and the members in its attributes point to the operation functions under different operations.
[2] How to drive GPIO
In the microcontroller, there are three main steps to drive GPIO to light up the LED:
(1) Enable the GPIOx clock
(2) Declare the GPIO setting structure, and set the GPIO in the structure properties, and finally set the The structure is filled in the GPIO_Init function for initialization.
(3) Setbit/Resetbit the GPIO.
But in fact, whether it is enabling the clock or setting the GPIO, all of this is implemented on the register. It is similar on IMX6ULL, its GPIO is mainly divided into three parts for management:
(1) CCM_CCGRx → clock register
(2) IOMUXC → input and output multiplexer controller (tube pin multiplexing, pull high pull low, loopback, etc. set)
(3) GPIO → GPIO register (DR\GDIR\PSR\ICRx\IMR\ISR\EDGE_SEL)
The first part is similar to the clock control of the microcontroller.
The second part is similar to the setting of the single-chip microcomputer for the GPIO structure, but this time the registers are no longer placed together with the GPIO, but are managed exclusively by IOMUXC.
The third part is similar to the GPIO operation of the single-chip microcomputer, reading/writing high and low levels or reading terminals, etc.
GPIO internal module diagram
So how to operate these registers? Use ioremap to map the IO address space to the virtual address space of the kernel for easy access. Where there is mapping, there is unmapping: iounmap. So in what order should we arrange these three steps in the driver?
In the single-chip microcomputer, these three steps are often integrated in the system initialization stage of the first two steps, and the writing of high and low levels is placed in the user program stage.
The driver program is divided into: load module→init→use APP to operate the generated hardware file→unload module→exit
Generally speaking, the simplest writing method is to place the memory map in init, and configure CCM and IOMUXC in the file open stage . In the read and write phase, read and write GPIO registers.
But this has a disadvantage, that is, it is not quite portable, because the memory mapping is already involved in the init stage, and the mapping address is inseparable from the hardware.
Therefore, the safest way is to abstract the device into a structure again, and then write the driver program corresponding to the board according to different board/IO conditions, and the character driver is more similar to the interface for calling this driver. The initialization of memory mapping and CCM and IOMUXC are placed in the sub-driver open function.
For example: abstract a device like this:

struct Device_operations{
    
    
   int(*init)(int which);//init函数指针
   int(*write)(int which,int value);//写函数指针
   int(*read)(int which);//读函数指针
   int(*exit)(int which);//exit函数指针
};

In the main program it can be used like this:

struct Device_operations *p_dev_opr;
p_dev_opr->init(iminor);
p_dev_opr->write(iminor,value);
value = p_dev_opr->read(iminor);
p_dev_opr->exit(iminor);

How to solve the pointing problem of function pointer? The structure initialization function of the subroutine can be called in the init function of the main program.

p_dev_opr = get_board_device_opr();
static struct Device_operations board_demo_device_opr={
    
    
   .init = board_demo_device_init;
   .exit = board_demo_device_exit;
   .write = board_demo_device_write;
   .read = board_demo_device_read;
};
struct Device_operations *get_board_device_opr(void)
{
    
    
  return &board_demo_device_opr;
}

In this way, we can map memory, configure, etc. in the subroutine board_demo_device_init. If the board is changed and the function is the same, we only need to replace the subroutine when compiling, and the main program can not be changed.

Guess you like

Origin blog.csdn.net/qq_32006213/article/details/128964268