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