platform device driving experiment

Posted by Sillysoft on Mon, 03 Jan 2022 01:21:16 +0100

1, Separation and layering of linux Driver

The device drivers we wrote in the previous chapters are very simple. They are the simplest read and write operations for IO.

The drivers of complex peripherals such as I2C, SPI and LCD cannot be written in this way. Linux system should consider the reusability of drivers, so it puts forward the software idea of driver separation and layering. Under this idea, the platform device driver we most often deal with in the future, also known as platform device driver, was born.

In this chapter, we will learn about driver separation and layering under Linux and how to write device drivers under platform framework.

1. Separation and separation of drive

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 three platforms (the platform here refers to SOC) all have six axis sensors with MPU6050, which is an I2C interface. According to the idea when we write bare metal I2C driver, each platform has A MPU6050 driver. Therefore, the simplest driver framework written is shown in the figure

Each platform has a host driver and device driver. 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.

The best way is that the I2C controller of each platform provides a unified interface (also known as host driver), and only one driver (device driver) is provided for each device. Each device is accessed through the unified I2C interface driver, which can greatly simplify the driver file,

There must be many kinds of I2C driving devices, not just MPU6050. The actual driving architecture is shown in the figure

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.

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.

2. Driven 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.

2, Introduction to platform driven model

In SOC, some peripherals 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.

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:

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 is the match function. This function is very important. The word match means "match", so this function is used to complete the matching between the device and the driver. The bus uses the match function to find the corresponding driver according to the registered device or the corresponding device according to the registered driver. Therefore, each bus must implement this function.
The match function has two parameters: dev and drv,
The two parameters are device and device respectively_ Driver type, that is, device and driver.

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

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 In C, the function contents are as follows:

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 medium.
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.

2.platform driver

platform_ The driver structure represents the platform driver, which is defined in the file include/linux/platform_device.h, as follows:

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:

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:
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,

This structure is defined in the file include/linux/mod_devicetable.h, as follows:

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 shown below

/* 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");

Lines 1 to 27, the traditional character device driver, the so-called platform driver, is not independent of other types of drivers other than character device driver, block device driver and network device driver. Platform is only a framework for the separation and layering of drivers. The specific implementation of its 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.
Lines 58 to 65 define a platform_driver structure variable xxx_driver, which means platform driven, and the paltform is set in lines 59 to 62_ 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.

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_device.h, the structure is as follows:

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

In line 23, name indicates the device name, which should be the same as the name field of the platform driver used. Otherwise, the device cannot match the corresponding driver. For example, if the name field of the corresponding platform driver is "XXX GPIO", the name field should also be set to "XXX GPIO".
Line 27, num_resources indicates the number of resources, which is generally the size of the resource in line 28.
In line 28, resource represents resources, that is, device information, such as peripheral registers.

The resource structure represents resources. The content of the resource structure is as follows:

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 represent the start and end information of resources respectively. For memory resources, they represent the start and end addresses of memory,
Name indicates the resource name,
flags indicates the resource type,

The optional resource types are defined in the file include / Linux / ioport H inside, as follows:

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 previous Linux versions that did not support device trees, users need to write a platform_ The device variable is used to describe the device information, and then the platform is used_ device_ The register function registers the device information in 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, failed; 0, successful.

If you no longer use platform, you can use platform_device_unregister function unregisters the corresponding platform device_ device_ The prototype of 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:

1 /* Register address definition*/
2 #define PERIPH1_REGISTER_BASE (0X20000000) / * first register address of peripheral 1*/
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");

Lines 7-18, array xxx_resources refers to device resources. There are two resources in total, which are the register information of device peripheral 1 and peripheral 2. Therefore, all flags are IORESOURCE_MEM, indicating that the resource is of memory type.
Lines 21-26: platform device structure variable. Note that the name field should be consistent with the name field in the driver used, otherwise the driver and device cannot be matched successfully. num_resources indicates the resource size, which is actually the array XXX_ The number of elements of resources. Here, array is used_ Size to measure the number of elements of an array.
Line 29~32, the device module loading function, calling platform_ in this function. device_ Register registers the platform device with the Linux kernel.
Line 35~38, the device module unloads function, and calls platform_ in this function. device_ Unregister unloads the platform device from the Linux kernel.

The sample code is mainly used in the Linux version that does not support the device tree. When the Linux kernel supports the device tree, the user does not need to register the platform device manually. Because the device information is described in the device tree, when the Linux kernel starts, it will read the device information from the device tree and organize it into a platform_device form, as for the device tree to platform_ The specific process of device will not be investigated in detail. Those interested can have a look. There are many blogs on the Internet that explain the whole process in detail.

That's all about the bus, driver and device under the platform. Next, we use the platform driver framework to write an LED lamp driver. In this chapter, we don't use the device tree to describe the device information. We use the user-defined platform_device this "ancient" way to write LED device information. In the next chapter, we will write the platform driver under the device tree, so that we can master the development methods of platform driver without device tree and with device tree.

3, Hardware schematic analysis

4, Experimental programming

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.

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 a new one named leddevice C and ledriver C these two files are respectively the LED lamp platform device file and the LED lamp platform driver file. In leddevice Enter the following in C:

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/gpio.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/of_gpio.h>

#include <linux/semaphore.h>

#include <linux/timer.h>

#include <linux/irq.h>

#include <linux/wait.h>

#include <linux/poll.h>

#include <linux/fs.h>

#include <linux/fcntl.h>

#include <linux/platform_device.h>

#include <asm/mach/map.h>

#include <asm/uaccess.h>

#include <asm/io.h>

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

file name 		:  leddevice.c

author 	  	:  Zuo Zhongkai

edition 	   	:  V1.0

describe 	   	:  platform device

other 	   	:  nothing

Forum 	   	:  www.openedv.com

journal 	   	:  First edition v1 0 created by Zuo Zhongkai on August 13, 2019

***************************************************************/



/* 

 * Register address definition

 */

#define CCM_CCGR1_BASE				(0X020C406C)	

#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)

#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)

#define GPIO1_DR_BASE				(0X0209C000)

#define GPIO1_GDIR_BASE				(0X0209C004)

#define REGISTER_LENGTH				4



/* @description		: This function will be executed when releasing the flatform device module	

 * @param - dev 	: Device to release 

 * @return 			: nothing

 */

static void	led_release(struct device *dev)

{

	printk("led device released!\r\n");	

}



/*  

 * Device resource information, that is, all registers used by LED0

 */

static struct resource led_resources[] = {

	[0] = {

		.start 	= CCM_CCGR1_BASE,

		.end 	= (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),

		.flags 	= IORESOURCE_MEM,

	},	

	[1] = {

		.start	= SW_MUX_GPIO1_IO03_BASE,

		.end	= (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),

		.flags	= IORESOURCE_MEM,

	},

	[2] = {

		.start	= SW_PAD_GPIO1_IO03_BASE,

		.end	= (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),

		.flags	= IORESOURCE_MEM,

	},

	[3] = {

		.start	= GPIO1_DR_BASE,

		.end	= (GPIO1_DR_BASE + REGISTER_LENGTH - 1),

		.flags	= IORESOURCE_MEM,

	},

	[4] = {

		.start	= GPIO1_GDIR_BASE,

		.end	= (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),

		.flags	= IORESOURCE_MEM,

	},

};





/*

 * platform Equipment structure 

 */

static struct platform_device leddevice = {

	.name = "imx6ul-led",

	.id = -1,

	.dev = {

		.release = &led_release,

	},

	.num_resources = ARRAY_SIZE(led_resources),

	.resource = led_resources,

};

		

/*

 * @description	: Device module loading 

 * @param 		: nothing

 * @return 		: nothing

 */

static int __init leddevice_init(void)

{

	return platform_device_register(&leddevice);

}



/*

 * @description	: Device module logout

 * @param 		: nothing

 * @return 		: nothing

 */

static void __exit leddevice_exit(void)

{

	platform_device_unregister(&leddevice);

}



module_init(leddevice_init);

module_exit(leddevice_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("zuozhongkai");




Article 56~82 that 's ok, led_resources Array, that is, device resources, describes LED Register information to be used, i.e IORESOURCE_MEM resources.
Article 88~96,platform Device structure variable leddevice,Pay attention here name Field is“ imx6ul-led",place
 To be written later platform In drive name The field should also be“ imx6ul-led",Otherwise, device and driver matching fails.
Article 103~106 Line, the device module loads the function through platform_device_register towards Linux within
 Nuclear registration leddevice this platform Equipment.
Article 113~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 in
 Face input the following:
#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/gpio.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/of_gpio.h>

#include <linux/semaphore.h>

#include <linux/timer.h>

#include <linux/irq.h>

#include <linux/wait.h>

#include <linux/poll.h>

#include <linux/fs.h>

#include <linux/fcntl.h>

#include <linux/platform_device.h>

#include <asm/mach/map.h>

#include <asm/uaccess.h>

#include <asm/io.h>

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

file name 		:  leddriver.c

author 	  	:  Zuo Zhongkai

edition 	   	:  V1.0

describe 	   	:  platform driver

other 	   	:  nothing

Forum 	   	:  www.openedv.com

journal 	   	:  First edition v1 0 created by Zuo Zhongkai on August 13, 2019

***************************************************************/



#define LEDDEV_CNT 		 one 			/*  Equipment number length 	*/

#define LEDDEV_NAME 		 "platled" 	/*  Device name 	*/

#define LEDOFF 			0

#define LEDON 			1



/* Register name */

static void __iomem *IMX6U_CCM_CCGR1;

static void __iomem *SW_MUX_GPIO1_IO03;

static void __iomem *SW_PAD_GPIO1_IO03;

static void __iomem *GPIO1_DR;

static void __iomem *GPIO1_GDIR;



/* leddev Equipment structure */

struct leddev_dev{

	dev_t devid;			/* Equipment number	*/

	struct cdev cdev;		/* cdev		*/

	struct class *class;	/* class 		*/

	struct device *device;	/* equipment		*/

	int major;				/* Main equipment No	*/		

};



struct leddev_dev leddev; 	/* led equipment */



/*

 * @description		: LED On / off

 * @param - sta 	: LEDON(0) Turn on the LED and LEDOFF(1) turns off the LED

 * @return 			: nothing

 */

void led0_switch(u8 sta)

{

	u32 val = 0;

	if(sta == LEDON){

		val = readl(GPIO1_DR);

		val &= ~(1 << 3);	

		writel(val, GPIO1_DR);

	}else if(sta == LEDOFF){

		val = readl(GPIO1_DR);

		val|= (1 << 3);	

		writel(val, GPIO1_DR);

	}	

}



/*

 * @description		: open device

 * @param - inode 	: inode passed to driver

 * @param - filp 	: The device file has a file structure called private_ Member variable of data

 * 					  Generally, private is used when open ing_ Data points to the device structure.

 * @return 			: 0 success; Other failures

 */

static int led_open(struct inode *inode, struct file *filp)

{

	filp->private_data = &leddev; /* Set private data  */

	return 0;

}



/*

 * @description		: Write data to device 

 * @param - filp 	: A device file that represents an open file descriptor

 * @param - buf 	: Data to write to device

 * @param - cnt 	: Length of data to write

 * @param - offt 	: Offset relative to the first address of the file

 * @return 			: The number of bytes written. If it is negative, it indicates that the write failed

 */

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

{

	int retvalue;

	unsigned char databuf[1];

	unsigned char ledstat;



	retvalue = copy_from_user(databuf, buf, cnt);

	if(retvalue < 0) {

		return -EFAULT;

	}



	ledstat = databuf[0];		/* Get status value */

	if(ledstat == LEDON) {

		led0_switch(LEDON);		/* Turn on the LED */

	}else if(ledstat == LEDOFF) {

		led0_switch(LEDOFF);	/* Turn off the LED */

	}

	return 0;

}



/* Device operation function */

static struct file_operations led_fops = {

	.owner = THIS_MODULE,

	.open = led_open,

	.write = led_write,

};



/*

 * @description		: flatform The probe function of the driver. This function will be executed after the driver matches the device

 * @param - dev 	: platform equipment

 * @return 			: 0,success; Other negative values, failed

 */

static int led_probe(struct platform_device *dev)

{	

	int i = 0;

	int ressize[5];

	u32 val = 0;

	struct resource *ledsource[5];



	printk("led driver and device has matched!\r\n");

	/* 1,Get resources */

	for (i = 0; i < 5; i++) {

		ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* MEM type resources */

		if (!ledsource[i]) {

			dev_err(&dev->dev, "No MEM resource for always on\n");

			return -ENXIO;

		}

		ressize[i] = resource_size(ledsource[i]);	

	}	



	/* 2,Initialize LED */

	/* Register address mapping */

 	IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);

	SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);

  	SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);

	GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);

	GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);

	

	val = readl(IMX6U_CCM_CCGR1);

	val &= ~(3 << 26);				/* Clear previous settings */

	val |= (3 << 26);				/* Set new value */

	writel(val, IMX6U_CCM_CCGR1);



	/* Set gpio1_ The io03 multiplexing function multiplexes it into GPIO1_IO03 */

	writel(5, SW_MUX_GPIO1_IO03);

	writel(0x10B0, SW_PAD_GPIO1_IO03);



	/* Set GPIO1_IO03 is an output function */

	val = readl(GPIO1_GDIR);

	val &= ~(1 << 3);			/* Clear previous settings */

	val |= (1 << 3);			/* Set as output */

	writel(val, GPIO1_GDIR);



	/* LED1 is off by default */

	val = readl(GPIO1_DR);

	val |= (1 << 3) ;	

	writel(val, GPIO1_DR);

	

	/* Register character device driver */

	/*1,Create device number */

	if (leddev.major) {		/*  Defines the equipment number */

		leddev.devid = MKDEV(leddev.major, 0);

		register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);

	} else {						/* No device number defined */

		alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);	/* Application equipment No */

		leddev.major = MAJOR(leddev.devid);	/* Gets the master device number of the assigned number */

	}

	

	/* 2,Initialize cdev */

	leddev.cdev.owner = THIS_MODULE;

	cdev_init(&leddev.cdev, &led_fops);

	

	/* 3,Add a cdev */

	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);



	/* 4,Create class */

	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);

	if (IS_ERR(leddev.class)) {

		return PTR_ERR(leddev.class);

	}



	/* 5,Create device */

	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);

	if (IS_ERR(leddev.device)) {

		return PTR_ERR(leddev.device);

	}



	return 0;

}



/*

 * @description		: platform The remove function of the platform driver. This function will be executed when the platform driver is removed

 * @param - dev 	: platform equipment

 * @return 			: 0,success; Other negative values, failed

 */

static int led_remove(struct platform_device *dev)

{

	iounmap(IMX6U_CCM_CCGR1);

	iounmap(SW_MUX_GPIO1_IO03);

	iounmap(SW_PAD_GPIO1_IO03);

	iounmap(GPIO1_DR);

	iounmap(GPIO1_GDIR);



	cdev_del(&leddev.cdev);/*  Delete cdev */

	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* Logout equipment number */

	device_destroy(leddev.class, leddev.devid);

	class_destroy(leddev.class);

	return 0;

}



/* platform Drive structure */

static struct platform_driver led_driver = {

	.driver		= {

		.name	= "imx6ul-led",			/* Driver name, used to match the device */

	},

	.probe		= led_probe,

	.remove		= led_remove,

};

		

/*

 * @description	: Driver module loading function

 * @param 		: nothing

 * @return 		: nothing

 */

static int __init leddriver_init(void)

{

	return platform_driver_register(&led_driver);

}



/*

 * @description	: Driver module unloading function

 * @param 		: nothing

 * @return 		: nothing

 */

static void __exit leddriver_exit(void)

{

	platform_driver_unregister(&led_driver);

}



module_init(leddriver_init);

module_exit(leddriver_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("zuozhongkai");

Lines 34-122, traditional character device driver.
Lines 130 ~ 206 are the probe function. This function will be executed after the device and driver are matched. When the matching is successful, it will output "led driver and device has matched!" on the terminal Such statements. Initialize the LED and register the character device driver in the probe function. That is, put all the work done in the driver loading function into the probe function.
Lines 213 to 226 are the remove function, which will be executed when the platform driver is unloaded. In this function, you can release memory, log off character devices, etc. That is, put all the work in the original driver unloading function into the remove function.
Lines 229-235, platform_driver drives the structure. Note that the name field is "imx6ul led", which is the same as that in leddevice The device name field set in the C file is consistent.
Lines 242 ~ 245, drive the module to load the function. In this function, use platform_driver_register registers LEDs with the Linux kernel_ Driver driver.
Lines 252 ~ 255, the driver module unloading function, in which the platform is used_ driver_ Unregister uninstalls the LED from the Linux kernel_ Driver driver.

2. Test APP preparation

The content of the test APP is very simple, that is, turn on and off the LED lights and create a new ledapp C this file, and then enter the following contents in it:

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdlib.h"

#include "string.h"

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

file name 		:  ledApp.c

author 	  	:  Zuo Zhongkai

edition 	   	:  V1.0

describe 	   	:  platform driver test APP.

other 	   	:  nothing

usage method 	 : ./ Ledapp / dev / Plated 0 turns off the LED

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

Forum 	   	:  www.openedv.com

journal 	   	:  First edition v1 0 created by Zuo Zhongkai on August 16, 2019

***************************************************************/

#define LEDOFF 	0

#define LEDON 	1



/*

 * @description		: main main program

 * @param - argc 	: argv Number of array elements

 * @param - argv 	: Specific parameters

 * @return 			: 0 success; Other failures

 */

int main(int argc, char *argv[])

{

	int fd, retvalue;

	char *filename;

	unsigned char databuf[2];

	

	if(argc != 3){

		printf("Error Usage!\r\n");

		return -1;

	}



	filename = argv[1];



	/* Turn on the led driver */

	fd = open(filename, O_RDWR);

	if(fd < 0){

		printf("file %s open failed!\r\n", argv[1]);

		return -1;

	}

	

	databuf[0] = atoi(argv[2]);	/* What to do: turn on or off */

	retvalue = write(fd, databuf, sizeof(databuf));

	if(retvalue < 0){

		printf("LED Control Failed!\r\n");

		close(fd);

		return -1;

	}



	retvalue = close(fd); /* Close file */

	if(retvalue < 0){

		printf("file %s close failed!\r\n", argv[1]);

		return -1;

	}

	return 0;

}

5, Run test

depmod //This command needs to be run when the driver is loaded for the first time
modprobe leddevice.ko //Load device module
modprobe leddriver.ko //Load 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.

ls /sys/bus/platform/devices/

Check the / sys/bus/platform/devices / directory to see if our device exists. We are in leddevice Set the name field of leddevice(platform_device type) in C as "imx6ul led", that is, the device name is imx6ul led, so there must be a file with the name "imx6ul led" in / sys/bus/platform/devices / directory, otherwise it indicates that our device module failed to load, and the result is as shown in the figure:


Similarly, check the / sys/bus/platform/drivers / directory to see if the driver exists. We are at ledriver Setting led in C_ The name field of driver (platform_driver type) is "imx6ul led", so there will be a file named "imx6ul led" in the directory / sys/bus/platform/drivers /,

ls /sys/bus/platform/drivers/


After the driver and device are matched successfully, you can test the LED lamp drive. Enter the following command to turn on the LED lamp

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

Topics: Linux Embedded system