[punctual atom linux serial] Chapter 54 platform device driver experiment - extracted from [punctual atom] I.MX6U embedded Linux Driver Development Guide V1.0

Keywords: Linux IoT stm32

1) Experimental platform: punctual atom alpha Linux development board
2) Platform purchase address: https://item.taobao.com/item.htm?id=603672744434
2) Full set of experimental source code + manual + video download address: http://www.openedv.com/thread-300792-1-1.html
3) Students interested in punctual atomic Linux can add group discussion: 935446741
4) pay attention to the official account of the dot atom and get updated information.

Chapter 54 platform device driving experiment

The device drivers we wrote in the previous chapters are very simple and correct IO Perform the simplest read and write operations. image I2C,SPI,LCD The drivers of these complex peripherals can't be written like this, Linux The system should consider the reusability of drivers, so it puts forward the software idea of separation and layering of drivers, which gives birth to the software we will most often deal with in the future platform Device driver, also known as platform device driver. Let's study this chapter Linux Drive separation and stratification under, and platform How to write the device driver under the framework.

54.1 separation and layering of linux drivers
54.1.1 separation and separation of drives
For a mature, large and complex operating system such as Linux, the reusability of code is very important. Otherwise, there will be a lot of meaningless duplicate code in the Linux kernel. Especially the driver, because the driver occupies a large part of the Linux kernel code. If the driver is not managed and the repeated code is arbitrarily increased, the number of files in the Linux kernel will be unacceptably large before long.
Suppose there are three platforms A, B and C, and the six axis sensors with the I2C interface MPU6050 are on these three platforms (the platform here refers to SOC). According to the idea when we write the bare metal I2C driver, each platform has A MPU6050 driver. Therefore, the simplest driver framework is shown in figure 54.1.1:

Figure 54.1.1.1 conventional I2C device driver
As can be seen from figure 54.1.1.1, there is a host driver and device driver under each platform. The host driver must be necessary. After all, different platforms have different I2C controllers. However, there is no need to write one device driver for each platform on the right, because MPU6050 is the same for that SOC. Just read and write data through I2C interface, and only one MPU6050 driver is required. If there are several I2C devices, such as AT24C02 and ft5206 (capacitive touch screen), the device driver will be written repeatedly several times according to the writing method in figure 54.1.1. Obviously, this writing method is not recommended in the Linux driver. The best way is to provide a unified interface (also known as host driver) for the I2C controller of each platform, and only one driver (device driver) for each device. Each device is accessed through the unified I2C interface driver, which can greatly simplify the driver file, For example, the MPU6050 driver framework under the three platforms in 54.1.1 can be simplified as shown in figure 54.1.1.2:
Insert picture description here

Figure 54.1.1.2 improved equipment drive
There must be many kinds of I2C driving devices, not just MPU6050. The actual driving architecture is shown in figure 54.1.1.3:

Figure 54.1.1.3 separated drive frame
This is the separation of drivers, that is, the separation of host drivers and device drivers. For example, I2C, SPI and so on will use the separation of drivers to simplify the development of drivers. In the actual driver development, generally, the I2C host controller driver has been written by the semiconductor manufacturer, and the device driver is also written by the manufacturer of the device. We only need to provide the device information, such as which I2C interface the device is connected to, the speed of I2C, etc. It is equivalent to separating the device information from the device driver. The driver uses standard methods to obtain the device information (such as obtaining the device information from the device tree), and then initializes the device according to the obtained device information. In this way, the driver is only responsible for driving and the equipment is only responsible for equipment. Find a way to match the two. This is the bus, driver and device model in Linux, that is, the separation of drivers. The bus is the aging of driver and equipment information, which is responsible for bridging the two, as shown in figure 54.1.1.4:

Figure 54.1.1.4 Linux bus, driver and device modes
When we register a driver with the system, the bus will look in the device on the right to see if there is a matching device, and if so, connect the two. Similarly, when registering a device with the system, the bus will look for a matching device in the driver on the left and connect it if any. A large number of drivers in the Linux kernel adopt bus, driver and device modes. The platform driver we will focus on later is the product of this idea.
54.1.2 driven layering
The previous section talked about the separation and separation of drivers. In this section, let's take a brief look at the layering of drivers. You should have heard of the 7-layer model of the network. Different layers are responsible for different contents. Similarly, drivers under Linux are often layered, and the purpose of layering is to deal with different contents in different layers. Take the input (input subsystem, which is often used in other books or materials, which will be explained in detail in a special chapter) as an example to briefly introduce the driver layering. The input subsystem is responsible for managing all input related drivers, including keyboard, mouse, touch, etc. the lowest layer is the original device driver, which is responsible for obtaining the original value of the input device and reporting the obtained input events to the input core layer. The input core layer handles various IO models and provides file_operations collection of operations. When writing the input device driver, we only need to handle the reporting of input events. As for how to deal with these reported input events, the upper level will consider it, and we don't care. It can be seen that the layered model can greatly simplify our driver writing, which is very friendly for driver writing.
54.2 introduction to platform driven model
Earlier, we talked about the separation of device drivers, and introduced bus, driver and device models, such as I2C, SPI, USB and other buses. However, some peripherals in SOC do not have the concept of bus, but what should we do if we use bus, driver and device model? In order to solve this problem, Linux puts forward the virtual bus platform, and the corresponding platform_driver and platform_device.
54.2.1 platform bus
Linux kernel uses bus_ The type structure represents the bus, which is defined in the file include/linux/device.h, bus_ The type structure is as follows:

Example code 54.2.1.1 bus_type Structure code snippet
1  struct bus_type {
2   	const char      *name;                      		/* Bus name 	*/
3   	const char      *dev_name;                  
4   	struct device       *dev_root;
5   	struct device_attribute *dev_attrs; 
6  	 	const struct attribute_group **bus_groups;  	/* Bus properties	*/
7   	const struct attribute_group **dev_groups;  	/* Device properties 	*/
8   	const struct attribute_group **drv_groups;  	/* Drive properties 	*/
9   
10  	int (*match)(struct device *dev, struct device_driver *drv);
11  	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
12  	int (*probe)(struct device *dev);
13  	int (*remove)(struct device *dev);
14  	void (*shutdown)(struct device *dev);
15  
16  	int (*online)(struct device *dev);
17  	int (*offline)(struct device *dev);
18  	int (*suspend)(struct device *dev, pm_message_t state);
19  	int (*resume)(struct device *dev);
20  	const struct dev_pm_ops *pm;
21  	const struct iommu_ops *iommu_ops;
22  	struct subsys_private *p;
23  	struct lock_class_key lock_key;
24 };
Line 10, match Function, this function is very important, word match It means "match, match", so this function is to complete the matching between the device and the driver, and the bus is used match Function to find the corresponding driver according to the registered device, or to find the corresponding device according to the registered driver. Therefore, each bus must implement this function. match The function has two arguments: dev and drv,These two parameters are device and device_driver Type, i.e. device and driver.

Platform bus is bus_ A specific instance of type is defined in the file drivers/base/platform.c, and the platform bus is defined as follows:

Example code 54.2.1.2 platform Bus instance
1 struct bus_type platform_bus_type = {
2 		.name       	= "platform",
3   	.dev_groups 	= platform_dev_groups,
4   	.match      	= platform_match,
5   	.uevent     	= platform_uevent,
6   	.pm     		= &platform_dev_pm_ops,
7 };

platform_bus_type is the platform platform bus, where platform_match is the matching function. Let's take a look at how drivers and devices match, platform_ The match function is defined in the file drivers/base/platform.c. The contents of the function are as follows:

Example code 54.2.1.3 platform Bus instance
1  static int platform_match(struct device *dev, 
struct device_driver *drv)
2  {
3   	struct platform_device *pdev = to_platform_device(dev);
4   	struct platform_driver *pdrv = to_platform_driver(drv);
5  
6   	/*When driver_override is set,only bind to the matching driver*/
7   	if (pdev->driver_override)
8       return !strcmp(pdev->driver_override, drv->name);
9  
10  	/* Attempt an OF style match first */
11  	if (of_driver_match_device(dev, drv))
12      	return 1;
13 
14  	/* Then try ACPI style match */
15  	if (acpi_driver_match_device(dev, drv))
16      	return 1;
17 
18  	/* Then try to match against the id table */
19  	if (pdrv->id_table)
20      	return platform_match_id(pdrv->id_table, pdev) != NULL;
21 
22  	/* fall-back to driver name match */
23  	return (strcmp(pdev->name, drv->name) == 0);
24 }

There are four methods for matching driver and equipment. Let's take a look at them in turn:
Lines 11 to 12, the first matching method, OF type matching, that is, the matching method adopted by the device tree, OF_ driver_ match_ The device function is defined in the file include/linux/of_device.h. device_ There is a named OF in the driver structure (representing the device driver)_ match_ Table. This member variable holds the compatible matching table OF the driver. The compatible attribute OF each device node in the device tree will be associated with OF_ match_ Compare all members in the table table to see if there are the same entries. If there are, it means that the device matches the driver. After the device and driver match successfully, the probe function will be executed.
Lines 15 ~ 16, the second matching method, ACPI matching method.
Lines 19 ~ 20, the third matching method, id_table matching, each platform_ The driver structure has an id_ The table member variable, as its name suggests, holds a lot of id information. This id information stores the driver types supported by the platform D driver.
Line 23, the fourth matching method, if the ID of the third matching method_ If the table does not exist, directly compare the name fields of the driver and the device to see if they are equal. If they are equal, the matching is successful.
For the Linux version number that supports the device tree, the general device driver supports two matching methods: device tree and no device tree for compatibility. That is, the first matching method generally exists, and only one of the third and fourth matching methods exists. Generally, the fourth matching method is used most, that is, directly comparing the name field of the driver and the device. After all, this method is the simplest.
54.2.2 platform drive
platform_ The driver structure represents the platform driver, which is defined in the file include / Linux / platform_ In device. H, the contents are as follows:

Example code 54.2.2.1 platform_driver structural morphology
1  struct platform_driver {
2   	int (*probe)(struct platform_device *);
3   	int (*remove)(struct platform_device *);
4   	void (*shutdown)(struct platform_device *);
5   	int (*suspend)(struct platform_device *, pm_message_t state);
6  	 	int (*resume)(struct platform_device *);
7   	struct device_driver driver;
8   	const struct platform_device_id *id_table;
9   	bool prevent_deferred_probe;
10 };

Line 2: probe function. After the driver and device are matched successfully, the probe function will be executed. It is a very important function!! Generally, the driver provider will write it. If he wants to write a new driver, the probe needs to implement it by himself.
Line 7, driver member, device_ The driver structure variable is widely used in the Linux kernel, such as object-oriented thinking and device_driver is equivalent to the base class and provides the most basic driving framework. plaform_driver inherits this base class, and then adds some unique member variables on this basis.
Line 8, id_table, which is the third method we used when explaining the platform bus matching driver and device in the previous section, id_table is a table (that is, an array), and the type of each element is platform_device_id,platform_ device_ The ID structure is as follows:

Example code 54.2.2.2 platform_device_id structural morphology
1 struct platform_device_id {
2   	char name[PLATFORM_NAME_SIZE];
3   	kernel_ulong_t driver_data;
4 };
device_driver Structure defined in include/linux/device.h,device_driver The structure is as follows:
Example code 54.2.2.3 device_driver structural morphology
1  struct device_driver {
2   const char      		*name;
3   struct bus_type     	*bus;
4  
5   struct module       	*owner;
6   const char      		*mod_name;  /* used for built-in modules */
7  
8   bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */
9  
10  const struct of_device_id   		*of_match_table;
11  const struct acpi_device_id 	*acpi_match_table;
12 
13  int (*probe) (struct device *dev);
14  int (*remove) (struct device *dev);
15  void (*shutdown) (struct device *dev);
16  int (*suspend) (struct device *dev, pm_message_t state);
17  int (*resume) (struct device *dev);
18  const struct attribute_group **groups;
19 
20  const struct dev_pm_ops *pm;
21 
22  struct driver_private *p;
23 };

Line 10, of_match_table is the matching table used by the driver when using the device tree. It is also an array, and each matching item is of_device_id structure type, which is defined in the file include / Linux / mod_ In devicetable. H, the contents are as follows:

Example code 54.2.2.4 of_device_id structural morphology
1 struct of_device_id {
2   char    		name[32];
3   char    		type[32];
4   char    		compatible[128];
5   const void 	*data;
6 };

The compatible in line 4 is very important because for the device tree, it is through the compatible attribute value and of of of the device node_ match_ Compare the compatible member variables of each item in the table. If they are equal, it means that the device and this driver are successfully matched.
When writing a platform driver, first define a platform_driver structure variable, and then implement each member variable in the structure, focusing on the implementation of matching method and probe function. When the driver and device match successfully, the probe function will be executed. The specific driver is written in the probe function, such as character device driver, etc.
When we define and initialize the platform_ After the driver structure variable, you need to call platform in the driver entry function_ driver_ The register function registers a platform driver with the Linux kernel_ driver_ The prototype of register function is as follows:
int platform_driver_register (struct platform_driver *driver)
Function parameters and return values have the following meanings:
Driver: the platform driver to register.
Return value: negative number, failed; 0, successful.
You also need to use platform in the driver unloading function_ driver_ Unregister function unloads the platform driver_ driver_ The prototype of unregister function is as follows:
void platform_driver_unregister(struct platform_driver *drv)
Function parameters and return values have the following meanings:
drv: platform driver to uninstall.
Return value: none.
The platform driver framework is as follows:

Example code 54.2.2.5 platform Drive frame
 	/* Equipment structure */
1  	struct xxx_dev{
2   	struct cdev cdev;
3   	/* Other specific contents of equipment structure */
4  	};
5  
6  	struct xxx_dev xxxdev;   /* Define a device structure variable */
7  
8  	static int xxx_open(struct inode *inode, struct file *filp)
9  	{    
10  	/* Function details */
11  	return 0;
12 	}
13 
14 static ssize_t xxx_write(struct file *filp, const char __user *buf, 
size_t cnt, loff_t *offt)
15 	{
16  	/* Function details */
17  	return 0;
18 	}
19 
20 /*
21  * Character device driver operation set
22  */
23 	static struct file_operations xxx_fops = {
24  	.owner = THIS_MODULE,
25  	.open = xxx_open,
26  	.write = xxx_write,
27 	};
28 
29 /*
30  * platform Driven probe function
31  * This function will be executed after the driver is successfully matched with the device
32  */
33 	static int xxx_probe(struct platform_device *dev)
34 	{    
35  	......
36  	cdev_init(&xxxdev.cdev, &xxx_fops); /* Register character device driver */
37  	/* Function details */
38  	return 0;
39 	}
40 
41 	static int xxx_remove(struct platform_device *dev)
42 	{
43  	......
44  	cdev_del(&xxxdev.cdev);/*  Delete cdev */
45  	/* Function details */
46  	return 0;
47 	}
48
49 /* Match list */
50 static const struct of_device_id xxx_of_match[] = {
51  	{ .compatible = "xxx-gpio" },
52 	 	{ /* Sentinel */ }
53 };
54
55 /* 
56  * platform Platform driven structure
57  */
58 	static struct platform_driver xxx_driver = {
59  	.driver = {
60      	.name       = "xxx",
61      	.of_match_table = xxx_of_match,
62  	},
63  	.probe      = xxx_probe,
64  	.remove     = xxx_remove,
65 	};
66   
67 	/* Driver module loading */
68 	static int __init xxxdriver_init(void)
69 	{
70  	return platform_driver_register(&xxx_driver);
71 }
72 
73 	/* Driver module unloading */
74 	static void __exit xxxdriver_exit(void)
75 	{ 
76      	platform_driver_unregister(&xxx_driver);
77 	}
78 
79 	module_init(xxxdriver_init);
80 	module_exit(xxxdriver_exit);
81 	MODULE_LICENSE("GPL");
82 	MODULE_AUTHOR("zuozhongkai");
first~27 Line, traditional character device driver, so-called platform The driver is not independent of character device driver, block device driver and network device driver. platform It is only a framework proposed for the separation and layering of drivers. The specific implementation of drivers still needs character device driver, block device driver or network device driver.

Lines 33-39, xxx_probe function. This function will be executed after the driver and device are matched successfully. All the character device drivers previously written in the init function of the driver entry will be placed in this probe function. For example, register character device drivers, add cdev, create classes, and so on.
Lines 41-47, xxx_remove function, platform_ The remove member variable in the driver structure. This function will be executed when the platform standby driver is closed. The previous work to be done in the driver uninstall exit function will be put into this function. For example, use iounmap to free memory, delete cdev, log off device number, and so on.
Lines 50-53, xxx_of_match matching table. If the device tree is used, the driver and device will be matched through this matching table. Line 51 sets a matching item. The compatible value of this matching item is "XXX GPIO". Therefore, when the compatible attribute value of the device node in the device tree is "XXX GPIO", this device will match this driver. Line 52 is a tag, of_ device_ The last match in the ID table must be empty.
Line 5865, define a platform_driver structure variable xxx_driver, which means platform driven, and the paltform is set in line 5962_ Device in driver_ Name and of of driver member variables_ match_ Table these two properties. The name attribute is used to match the traditional driver and device, that is, to check whether the name fields of the driver and device are the same. of_ match_ The table attribute is used for driver and device checking under the device tree. For a complete driver, two matching methods must be provided: device tree and no device tree. The last two lines 63 and 64 set the two member variables probe and remove.
Lines 68 to 71, drive the entry function and call platform_ driver_ The register function registers a platform driver with the Linux kernel, that is, XXX defined above_ Driver structure variable.
Lines 74 to 77, drive the exit function and call platform_ driver_ The unregister function unloads the previously registered platform driver.
Generally speaking, the platform driver is still a traditional character device driver, block device driver or network device driver, but it is covered with a "platform" skin. The purpose is to use the driving model of bus, driver and device to realize the separation and layering of drivers.
54.2.3 platform equipment
The platform driver is ready. We also need platform devices. Otherwise, a single driver can't do anything. platform_ The device structure represents the platform device. Here, we should note that if the kernel supports the device tree, we will not use the platform again_ Device is used to describe the device, because the device tree is used to describe it. Of course, if you have to use platform_device can also be used to describe device information. platform_ The device structure is defined in the file include / Linux / platform_ In device. H, the structure is as follows:

Example code 54.2.3.1 platform_device Structure code snippet
22 struct platform_device {
23  	const char  *name;  
24  	int     id;             
25  	bool        id_auto;
26  	struct device   dev;
27  	u32     num_resources;  
28  	struct resource *resource;
29 
30 		const struct platform_device_id *id_entry;
31  	char *driver_override; /* Driver name to force a match */
32 
33  	/* MFD cell pointer */
34  	struct mfd_cell *mfd_cell;
35 
36  	/* arch specific additions */
37  	struct pdev_archdata    archdata;
38 };
Line 23, name Indicates the name of the device, and the device to be used platform Driven name The fields are the same, otherwise the device cannot match the corresponding driver. For example, the corresponding platform Driven name Field is“ xxx-gpio",So this name The field should also be set to“ xxx-gpio". 
Line 27, num_resources Indicates the number of resources, generally line 28 resource The size of the resource.
Line 28, resource Represents resources, that is, device information, such as peripheral registers. Linux Kernel usage resource A structure represents a resource, resource The structure is as follows:
Example code 54.2.3.2 resource Structure code snippet
18 struct resource {
19  	resource_size_t 	start;
20  	resource_size_t 	end;
21  	const char 		*name;
22  	unsigned long 		flags;
23  	struct resource 	*parent, *sibling, *child;
24 };
start and end It represents the start and end information of resources respectively. For memory resources, it represents the start and end addresses of memory, name Represents the resource name, flags Represents the resource type. The optional resource types are defined in the file include/linux/ioport.h Inside, as follows:
Example code 54.2.3.3 Resource type
29  #define IORESOURCE_BITS     		0x000000ff  /* Bus-specific bits */
30  
31  #define IORESOURCE_TYPE_BITS	0x00001f00  /* Resource type 	*/
32  #define IORESOURCE_IO       		0x00000100  /* PCI/ISA I/O ports */
33  #define IORESOURCE_MEM      		0x00000200
34  #define IORESOURCE_REG      		0x00000300  /* Register offsets */
35  #define IORESOURCE_IRQ      		0x00000400
36  #define IORESOURCE_DMA      		0x00000800
37  #define IORESOURCE_BUS     		0x00001000
......
104 /* PCI control bits.  Shares IORESOURCE_BITS with above PCI ROM.  */
105 #define IORESOURCE_PCI_FIXED   	(1<<4)  /* Do not move resource */

In the Linux version that does not support the device tree before, the user needs to write the platform_device variable to describe the device information, and then use the platform_device_register function to register the device information into the Linux kernel. The prototype of this function is as follows:
int platform_device_register(struct platform_device *pdev)
Function parameters and return values have the following meanings:
pdev: the platform device to register.
Return value: negative number, failure; 0, success.
If the platform is no longer used, you can log off the corresponding platform device through the platform_device_unregister function. The prototype of the platform_device_unregister function is as follows:
void platform_device_unregister(struct platform_device *pdev)
Function parameters and return values have the following meanings:
pdev: platform device to log off.
Return value: none.
The platform device information framework is as follows:

Example code 54.2.3.4 platform Equipment frame
1  /* Register address definition*/
2  #define PERIPH1_REGISTER_BASE   	 (0X20000000) / * first address of peripheral 1 register*/    
3  #define PERIPH2_REGISTER_BASE   	 (0X020E0068) / * first address of peripheral 2 register*/
4  #define REGISTER_LENGTH          	4
5  
6  /* resources */
7  static struct resource xxx_resources[] = {
8   	[0] = {
9       		.start  = PERIPH1_REGISTER_BASE,
10      	.end    = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
11      	.flags  = IORESOURCE_MEM,
12  	},  
13  	[1] = {
14      	.start  = PERIPH2_REGISTER_BASE,
15      	.end    = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
16      	.flags  = IORESOURCE_MEM,
17  	},
18 };
19 
20 /* platform Equipment structure */
21 static struct platform_device xxxdevice = {
22  	.name = "xxx-gpio",
23  	.id = -1,
24  	.num_resources = ARRAY_SIZE(xxx_resources),
25  	.resource = xxx_resources,
26 };
27      
28 /* Device module loading */
29 static int __init xxxdevice_init(void)
30 {
31  	return platform_device_register(&xxxdevice);
32 }
33 
34 /* Device module logout */
35 static void __exit xxx_resourcesdevice_exit(void)
36 {
37  	platform_device_unregister(&xxxdevice);
38 }
39 
40 module_init(xxxdevice_init);
41 module_exit(xxxdevice_exit);
42 MODULE_LICENSE("GPL");
43 MODULE_AUTHOR("zuozhongkai");
seventh~18 Row, array xxx_resources Represents the device resource. There are two resources, which are the register information of device peripheral 1 and peripheral 2. Therefore flags All for IORESOURCE_MEM,Indicates that the resource is of memory type.
twenty-first~26 that 's ok, platform Equipment structure variables, note name The fields are to be the same as those in the driver used name The fields are consistent, otherwise the driver and device cannot be matched successfully. num_resources Indicates the resource size, which is actually an array xxx_resources The number of elements, used here ARRAY_SIZE To measure the number of elements in an array.
twenty-ninth~32 OK, the device module loading function is called in this function. platform_device_register towards Linux Kernel registration platform Equipment.
thirty-fifth~38 OK, the device module unloading function is called in this function. platform_device_unregister from Linux Uninstall in kernel platform Equipment.
Example code 54.2.3.4 This is mainly when the device tree is not supported Linux Used in version, when Linux After the kernel supports the device tree, users do not need to register manually platform The device information is described in the device tree, Linux When the kernel starts, it will read the device information from the device tree and organize it into platform_device Form, as for the device tree to platform_device There is no need to investigate the specific process in detail. Those who are interested can have a look. There are many blogs on the Internet that explain the whole process in detail.
about platform That's all for the bus, driver and device under the. We'll use it next platform Driver framework to write a LED In this chapter, we do not use the device tree to describe the device information. We use custom platform_device In this "ancient" way LED In the next chapter, we will write the device information under the device tree platform Driver, so that we can master the two types of no device tree and with device tree platform Driven development.

54.3 hardware schematic diagram analysis
In this chapter, we only use the LED lights on the IMX6U-ALPHA development board. Therefore, refer to section 8.3 for the schematic diagram of the experimental hardware.
54.4 preparation of test procedure
The routine path corresponding to this experiment is: development board CD - > 2. Linux driver routine - > 17_platform.
In this chapter, we need to write a driver module and a device module, in which the driver module is the platform driver and the device module is the device information of the platform. When the two modules are loaded successfully, they will match successfully, and then the probe function in the platform driver module will execute. The probe function is the traditional character device driver.
54.4.1 platform device and driver programming
Create a new folder named "17_platform", and then_ Create a vscode project in the platform folder, and the workspace is named "platform". Create two files named leddevice.c and leddriver.c, which are respectively the platform device file of LED lamp and the platform driver file of LED lamp. Enter the following in leddevice.c:

Example code 54.4.1.1 leddevice.c File code snippet
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 <linux/cdev.h>
10  #include <linux/device.h>
11  #include <linux/of_gpio.h>
12  #include <linux/semaphore.h>
13  #include <linux/timer.h>
14  #include <linux/irq.h>
15  #include <linux/wait.h>
16  #include <linux/poll.h>
17  #include <linux/fs.h>
18  #include <linux/fcntl.h>
19  #include <linux/platform_device.h>
20  #include <asm/mach/map.h>
21  #include <asm/uaccess.h>
22  #include <asm/io.h>
23  /***************************************************************
24  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
25  File name 	:  leddevice.c
26  Author 	:  Zuo Zhongkai
27  edition 	:  V1.0
28  Description 	:  platform device
29  other 	:  nothing
30  Forum 	:  www.openedv.com
31  journal 	:  First version v1.0 created by Zuo Zhongkai on August 13, 2019
32  ***************************************************************/
33  
34  /* 
35   * Register address definition
36   */
37  #define CCM_CCGR1_BASE            	(0X020C406C)    
38  #define SW_MUX_GPIO1_IO03_BASE      (0X020E0068)
39  #define SW_PAD_GPIO1_IO03_BASE      (0X020E02F4)
40  #define GPIO1_DR_BASE              	(0X0209C000)
41  #define GPIO1_GDIR_BASE             	(0X0209C004)
42  #define REGISTER_LENGTH             	4
43  
44  /* @description 	: This function will be executed when releasing the flatform device module 
45   * @param - dev 	: Device to release 
46   * @return        	: nothing
47   */
48  static void led_release(struct device *dev)
49  {
50      printk("led device released!\r\n"); 
51  }
52  
53  /*  
54   * Device resource information, that is, all registers used by LED0
55   */
56  static struct resource led_resources[] = {
57      [0] = {
58          .start  = CCM_CCGR1_BASE,
59          .end    = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
60          .flags  = IORESOURCE_MEM,
61      },  
62      [1] = {
63          .start  = SW_MUX_GPIO1_IO03_BASE,
64          .end    = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
65          .flags  = IORESOURCE_MEM,
66      },
67      [2] = {
68          .start  = SW_PAD_GPIO1_IO03_BASE,
69          .end    = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
70          .flags  = IORESOURCE_MEM,
71      },
72      [3] = {
73          .start  = GPIO1_DR_BASE,
74          .end    = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
75          .flags  = IORESOURCE_MEM,
76      },
77      [4] = {
78          .start  = GPIO1_GDIR_BASE,
79          .end    = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
80          .flags  = IORESOURCE_MEM,
81      },
82  };
83  
84  
85  /*
86   * platform Equipment structure 
87   */
88  static struct platform_device leddevice = {
89      .name = "imx6ul-led",
90      .id = -1,
91      .dev = {
92          .release = &led_release,
93      },
94      .num_resources = ARRAY_SIZE(led_resources),
95      .resource = led_resources,
96  };
97          
98  /*
99   * @description 	: Device module loading 
100  * @param       	: nothing
101  * @return      	: nothing
102  */
103 static int __init leddevice_init(void)
104 {
105     return platform_device_register(&leddevice);
106 }
107 
108 /*
109  * @description 	: Device module logout
110  * @param       	: nothing
111  * @return      	: nothing
112  */
113 static void __exit leddevice_exit(void)
114 {
115     platform_device_unregister(&leddevice);
116 }
117 
118 module_init(leddevice_init);
119 module_exit(leddevice_exit);
120 MODULE_LICENSE("GPL");
121 MODULE_AUTHOR("zuozhongkai");
leddevice.c The content of the file is in accordance with example code 54.2.3.4 of platform Prepared by equipment template.
fifty-sixth~82 that 's ok, led_resources Array, that is, device resources, describes LED Register information to be used, i.e IORESOURCE_MEM resources.
eighty-eighth~96,platform Device structure variable leddevice,Pay attention here name Field is“ imx6ul-led",So write it later platform In drive name The field should also be“ imx6ul-led",Otherwise, device and driver matching fails.
103rd~106 Line, the device module loads the function through platform_device_register towards Linux Kernel registration leddevice this platform Equipment.
113rd~116 Line, equipment module unloading function, in which platform_device_unregister from Linux Remove from kernel leddevice this platform Equipment.
leddevice.c The document will be prepared after it is prepared leddriver.c this platform Driver files, in leddriver.c Enter the following contents:
Example code 54.4.1.2 leddriver.c File code snippet
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 <linux/cdev.h>
10  #include <linux/device.h>
11  #include <linux/of_gpio.h>
12  #include <linux/semaphore.h>
13  #include <linux/timer.h>
14  #include <linux/irq.h>
15  #include <linux/wait.h>
16  #include <linux/poll.h>
17  #include <linux/fs.h>
18  #include <linux/fcntl.h>
19  #include <linux/platform_device.h>
20  #include <asm/mach/map.h>
21  #include <asm/uaccess.h>
22  #include <asm/io.h>
23  /***************************************************************
24  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
25  File name 		:  leddriver.c
26  Author 	:  Zuo Zhongkai
27  edition 	:  V1.0
28  Description 	:  platform driver
29  other 	:  nothing
30  Forum 	:  www.openedv.com
31  journal 	:  First version v1.0 created by Zuo Zhongkai on August 13, 2019
32  ***************************************************************/
33  
34  #define LEDDEV_CNT    		 one 		/*  Equipment number length 	*/
35  #define LEDDEV_NAME   		 "platled"    	/*  Device name*/
36  #define LEDOFF          	0
37  #define LEDON           	1
38  
39  /* Register name */
40  static void __iomem *IMX6U_CCM_CCGR1;
41  static void __iomem *SW_MUX_GPIO1_IO03;
42  static void __iomem *SW_PAD_GPIO1_IO03;
43  static void __iomem *GPIO1_DR;
44  static void __iomem *GPIO1_GDIR;
45  
46  /* leddev Equipment structure */
47  struct leddev_dev{
48      dev_t devid;            	/* Equipment number    	*/
49      struct cdev cdev;       	/* cdev     	*/
50      struct class *class;    	/* class      		*/
51      struct device *device;  	/* equipment       	*/
52      int major;              	/* Main equipment No 		*/      
53  };
54  
55  struct leddev_dev leddev;   	/* led equipment 	*/
56  
57  /*
58   * @description 	: LED On / off
59   * @param - sta  	: LEDON(0) Turn on the LED and LEDOFF(1) turns off the LED
60   * @return       	: nothing
61   */
62  void led0_switch(u8 sta)
63  {
64      u32 val = 0;
65      if(sta == LEDON){
66          val = readl(GPIO1_DR);
67          val &= ~(1 << 3);   
68          writel(val, GPIO1_DR);
69      }else if(sta == LEDOFF){
70          val = readl(GPIO1_DR);
71          val|= (1 << 3); 
72          writel(val, GPIO1_DR);
73      }   
74  }
75  
76  /*
77   * @description  	: open device
78   * @param – inode	: inode passed to driver
79   * @param - filp 	: The file structure of the device file has a member variable called private_data
80   *                    Generally, private_data is pointed to the device structure when open ing.
81   * @return        	: 0 Success; other failures
82   */
83  static int led_open(struct inode *inode, struct file *filp)
84  {
85      filp->private_data = &leddev; /* Set private data  */
86      return 0;
87  }
88  
89  /*
90   * @description 	: Write data to device 
91   * @param – filp	: A device file that represents an open file descriptor
92   * @param - buf  	: Data to write to device
93   * @param - cnt  	: Length of data to write
94   * @param - offt 	: Offset relative to the first address of the file
95   * @return       	: The number of bytes written. If it is negative, it indicates that the write failed
96   */
97  static ssize_t led_write(struct file *filp, const char __user *buf, 
size_t cnt, loff_t *offt)
98  {
99      int retvalue;
100     unsigned char databuf[1];
101     unsigned char ledstat;
102 
103     retvalue = copy_from_user(databuf, buf, cnt);
104     if(retvalue < 0) {
105         return -EFAULT;
106     }
107 
108     ledstat = databuf[0];       	/* Get status value 	*/
109     if(ledstat == LEDON) {
110         led0_switch(LEDON);     	/* Turn on the LED 	*/
111     }else if(ledstat == LEDOFF) {
112         led0_switch(LEDOFF);    	/* Turn off the LED 	*/
113     }
114     return 0;
115 }
116 
117 /* Device operation function */
118 static struct file_operations led_fops = {
119     .owner = THIS_MODULE,
120     .open = led_open,
121     .write = led_write,
122 };
123 
124 /*
125  * @description	: flatform The probe function of the driver, when the driver and the device
126  *                    After matching, this function will execute
127  * @param - dev  	: platform equipment
128  * @return        	: 0,Success; other negative values, failure
129  */
130 static int led_probe(struct platform_device *dev)
131 {   
132     int i = 0;
133     int ressize[5];
134     u32 val = 0;
135     struct resource *ledsource[5];
136 
137     printk("led driver and device has matched!\r\n");
138     /* 1,Get resources */
139     for (i = 0; i < 5; i++) {
140         ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); 
141         if (!ledsource[i]) {
142             dev_err(&dev->dev, "No MEM resource for always on\n");
143             return -ENXIO;
144         }
145         ressize[i] = resource_size(ledsource[i]);   
146     }   
147 
148     /* 2,Initialize LED */
149     /* Register address mapping */
150     IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
151     SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
152     SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
153     GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
154     GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
155     
156     val = readl(IMX6U_CCM_CCGR1);
157     val &= ~(3 << 26);              	/* Clear previous settings	*/
158     val |= (3 << 26);               	/* Set new value 			*/
159     writel(val, IMX6U_CCM_CCGR1);
160 
161     /* Set the GPIO1_IO03 multiplexing function and multiplex it to GPIO1_IO03 */
162     writel(5, SW_MUX_GPIO1_IO03);
163     writel(0x10B0, SW_PAD_GPIO1_IO03);
164 
165     /* Set GPIO1_IO03 as output function */
166     val = readl(GPIO1_GDIR);
167     val &= ~(1 << 3);          	 	/* Clear previous settings 	*/
168     val |= (1 << 3);           	 	/* Set as output 		*/
169     writel(val, GPIO1_GDIR);
170 
171     /* LED1 is off by default */
172     val = readl(GPIO1_DR);
173     val |= (1 << 3) ;   
174     writel(val, GPIO1_DR);
175     
176     /* Register character device driver */
177     /*1,Create device number */
178     if (leddev.major) {     	/*  Defines the equipment number 	*/
179         leddev.devid = MKDEV(leddev.major, 0);
180         register_chrdev_region(leddev.devid, LEDDEV_CNT, 
LEDDEV_NAME);
181     } else {               		/* No device number defined 	*/
182         alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, 
LEDDEV_NAME); 	
183         leddev.major = MAJOR(leddev.devid); 
184     }
185     
186     /* 2,Initialize cdev */
187     leddev.cdev.owner = THIS_MODULE;
188     cdev_init(&leddev.cdev, &led_fops);
189     
190     /* 3,Add a cdev */
191     cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
192 
193     /* 4,Create class */
194     leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
195     if (IS_ERR(leddev.class)) {
196         return PTR_ERR(leddev.class);
197     }
198 
199     /* 5,Create device */
200     leddev.device = device_create(leddev.class, NULL, leddev.devid, 
NULL, LEDDEV_NAME);
201     if (IS_ERR(leddev.device)) {
202         return PTR_ERR(leddev.device);
203     }
204 
205     return 0;
206 }
207 
208 /*
209  * @description 	:This function will be executed when the platform driver is removed
210  * @param - dev  	: platform equipment
211  * @return       	: 0,Success; other negative values, failure
212  */
213 static int led_remove(struct platform_device *dev)
214 {
215     iounmap(IMX6U_CCM_CCGR1);
216     iounmap(SW_MUX_GPIO1_IO03);
217     iounmap(SW_PAD_GPIO1_IO03);
218     iounmap(GPIO1_DR);
219     iounmap(GPIO1_GDIR);
220 
221     cdev_del(&leddev.cdev);		/*  Delete cdev */
222     unregister_chrdev_region(leddev.devid, LEDDEV_CNT); 
223     device_destroy(leddev.class, leddev.devid);
224     class_destroy(leddev.class);
225     return 0;
226 }
227 
228 /* platform Drive structure */
229 static struct platform_driver led_driver = {
230     .driver     = {
231         .name   = "imx6ul-led",         /* Driver name, used to match the device */
232     },
233     .probe      = led_probe,
234     .remove     = led_remove,
235 };
236         
237 /*
238  * @description 	: Driver module loading function
239  * @param       	: nothing
240  * @return      	: nothing
241  */
242 static int __init leddriver_init(void)
243 {
244     return platform_driver_register(&led_driver);
245 }
246 
247 /*
248  * @description 	: Driver module unloading function
249  * @param    		: nothing
250  * @return      	: nothing
251  */
252 static void __exit leddriver_exit(void)
253 {
254     platform_driver_unregister(&led_driver);
255 }
256 
257 module_init(leddriver_init);
258 module_exit(leddriver_exit);
259 MODULE_LICENSE("GPL");
260 MODULE_AUTHOR("zuozhongkai");
leddriver.c The content of the file is in accordance with example code 54.2.2.5 of platform Written by the driver template.
thirty-fourth~122 Line, traditional character device driver.
130th~206 that 's ok, probe Function. This function will be executed after the device and driver are matched. When the matching is successful, it will be output on the terminal“ led driver and device has matched!"This statement probe Initialization in function LED,Register the character device driver. That is, put all the work done in the driver loading function into probe Function.
213rd~226 that 's ok, remobe Function when uninstalling platform This function will be executed when driving. In this function, free memory, log off character devices, etc. that is, put all the work in the original driver unloading function into remove Function.
229th~235 that 's ok, platform_driver Drive structure, attention name Field is"imx6ul-led",With us leddevice.c Devices set in the file name The fields are consistent.
242nd~245 Line, the driver module loads the function through platform_driver_register towards Linux Kernel registration led_driver Drive.
252nd~255 Line, the driver module unloading function is passed in this function platform_driver_unregister from Linux Kernel uninstall led_driver Drive.

54.4.2 preparation of test APP
The content of the test APP is very simple, that is, turn on and off the LED light, create a new ledApp.c file, and then enter the following contents in it:

Example code 54.4.2.1 ledApp.c File code snippet
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 edition 	:  V1.0
13 Description 	:  platform driver test APP.
14 other 	:  nothing
15 usage method 	 	: ./ Ledapp / dev / Plated 0 turns off the LED
16           	  ./ledApp /dev/platled  1 Turn on the LED     
17 Forum 	:  www.openedv.com
18 journal 	:  First version v1.0 created by Zuo Zhongkai on August 16, 2019
19 ***************************************************************/
20 #define LEDOFF   0
21 #define LEDON    1
22 
23 /*
24  * @description 	: main main program
25  * @param - argc 	: argv Number of array elements
26  * @param - argv 	: Specific parameters
27  * @return        	: 0 Success; other failures
28  */
29 int main(int argc, char *argv[])
30 {
31  	int fd, retvalue;
32  	char *filename;
33  	unsigned char databuf[2];
34  
35  	if(argc != 3){
36     	 	printf("Error Usage!\r\n");
37      	return -1;
38  	}
39 
40  	filename = argv[1];
41  	/* Turn on the led driver */
42  	fd = open(filename, O_RDWR);
43  	if(fd < 0){
44      	printf("file %s open failed!\r\n", argv[1]);
45      	return -1;
46  	}
47  
48  	databuf[0] = atoi(argv[2]); /* What to do: turn on or off */
49  	retvalue = write(fd, databuf, sizeof(databuf));
50  	if(retvalue < 0){
51      	printf("LED Control Failed!\r\n");
52      	close(fd);
53      	return -1;
54  	}
55 
56  	retvalue = close(fd); /* Close file */
57  	if(retvalue < 0){
58      	printf("file %s close failed!\r\n", argv[1]);
59     	 	return -1;
60  	}
61  	return 0;
62 }
ledApp.c The content of the file is very simple, that is, control LED Light on and off, and the test in Chapter 41 APP Basically the same. I won't repeat the explanation here.

54.5 operation test
54.5.1 compiling driver and testing 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 "leddevice.o leddriver.o". The contents of the Makefile are as follows:
Example code 54.5.1.1 Makefile file

1  KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
...... 
4  obj-m := leddevice.o leddriver.o
......
11 clean:
12  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
Line 4, setting obj-m The value of the variable is“ leddevice.o leddriver.o". 
Input the following command to compile the driver module file:

make -j32
After the compilation is successful, a driver module file named "leddevice.ko leddriver.ko" will be generated.
2. Compile 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.
54.4.2 operation test
Copy the leddevice.ko, leddriver.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 commands to load the leddevice.ko device module and leddriver.ko drive module.
depmod / / this command needs to be run when the driver is loaded for the first time
modprobe leddevice.ko / / load the device module
modprobe leddriver.ko / / load the driver module
The devices and drivers under the current board platform bus are saved in the / sys/bus/platform / directory of the root file system, where the devices subdirectory is the platform device and the drivers subdirectory is the platofm driver. Check the / sys/bus/platform/devices / directory to see if our device exists. In leddevice.c, we set the name field of leddevice(platform_device type) to "imx6ul led", that is, the device name is imx6ul led. Therefore, there must be a file named "imx6ul led" in the / sys/bus/platform/devices / directory, Otherwise, it means that our device module failed to load, and the result is shown in figure 54.4.2.1:

Figure 54.4.2.1 imx6ul LED equipment
Similarly, check the / sys/bus/platform/drivers / directory to see if the driver exists. We set the LED in leddriver.c_ The name field of driver (platform_driver type) is "imx6ul led", so there will be a file named "imx6ul led" in / sys/bus/platform/drivers / directory. The result is shown in figure 54.4.2.2:

Figure 54.4.2.2 imx6ul LED drive
After the driver module and device module are loaded successfully, the platform bus will match. When the driver and device are matched successfully, a line of statements as shown in figure 54.4.2.3 will be output:

Figure 54.4.2.3 successful matching of drive and equipment
After the driver and equipment are matched successfully, you can test the LED lamp drive. Enter the following command to turn on the LED lamp:
. / ledapp / dev / Plated 1 / / turn on the LED
Turn off the LED after entering the following command:
. / ledapp / dev / Plated 0 / / turn off the LED
Observe whether the LED light can be turned on and off. If it can, it means that the driver works normally. If you want to unload the driver, enter the following command:
rmmod leddevice.ko
rmmod leddriver.ko

Posted by kidintraffic on Tue, 21 Sep 2021 23:55:12 -0700