Linux driven device tree

14. Device tree

1. What is a device tree?

Device tree is a data structure that describes hardware resources. It transmits hardware resources to the kernel through bootloader, making the description of kernel and hardware resources relatively independent.

2. Origin of device tree

To understand why there is a device tree and how it comes from, we must first review how we wrote a driver before there was no device tree. Take the character device driver code framework as an example, let's review it together. Linux has laid a good framework for us to write any device driver. We just need to fill it in like a cloze.
Character device driver framework

Miscellaneous equipment drive frame

Through these frameworks, we can easily write our driver code. However, when we are very skilled in using this framework, we will find that although this method is very simple, it is not easy to expand. When we have many, many similar devices, if we complete according to this framework, we have to write this process many times, However, the only real difference between multiple similar devices is the fourth step of the framework, that is, the part of initializing the hardware. The codes of other steps are basically the same. This will cause a lot of duplicate code. However, when writing driver code, we should try our best to reuse the code, that is, a set of driver can serve as many devices as possible. If we still write according to this, it will not be in line with our rules.
In order to achieve this goal, we need to separate the common code from the different code to enhance the portability of our driver code. Therefore, the idea of device driver separation came into being. In Linux, we separate when writing code. Separation is to put some dissimilar things in dev.c and similar things in dri c. If we have many similar devices or platforms, we just need to modify dev.c, so that our repetitive work will be greatly reduced. This is the origin of platform bus.

What are the disadvantages of platform bus?

When we get used to using this method, we will find that if the soc remains unchanged, we have to modify the C file and recompile it every time we change a platform. And it will be left under arch / arm / plat XXX and arch / arm / Mach XXX

A lot of code about board level details. This is not to say that this method is not good, but from the perspective of the development of Linux, these codes are "junk code" relative to the Linux kernel, and there are many "junk codes", so there is the simple and rude words of Linux Torvalds:
This whole arm thing is a fusing pain in the ass in order to change this situation, the device tree has been introduced into ARM Linux to eliminate the "junk code" relative to the kernel, that is, the device tree file is used to describe these device information, that is, instead of device Although the C file is taken outside the kernel, the platform matching is basically the same. Compared with the previous method, using the device tree can not only remove a large amount of "junk code", but also adopt the text format for easy reading and modification. If we need to modify some resources, we don't need to recompile the kernel, but only need to compile the device tree source file into binary file, Just pass it to the kernel through bootloader. The kernel parses and expands it to get a topology of hardware. We can get the nodes and attributes of the device tree through the interface provided by the kernel. That is, for different mainboards of the same soc, the kernel only needs to replace the device tree file dtb to realize the differential support of different mainboards without replacing the kernel file.

The device tree replaces the device file and separates the hardware information from the kernel. The device tree is used to describe the hardware resources

3. Basic concepts of device tree

1. Why is it called a device tree

Because its syntax structure is like a tree, it is called a device tree

2. Interpretation of common terms

namefunction
DTDevice Tree
FDTFlattened Device Tree / / expand the device tree. / / open the firmware. The device tree originates from of, so we can see many functions with the letter of in the device tree. Functions beginning with of are related to the device tree
dtsDevice tree source device tree source code describes hardware resources
dtsidevice tree source include more general device tree code, that is, the code that can be used by the same chip but different platforms
dtbDevice tree blob DTB file binary file obtained after DTS compilation
dtcdevice tree compiler

4. Basic syntax of device tree

The device tree is a file describing hardware resources, which is a device file that replaces the platform bus

1. Basic framework of device tree

(1) The device tree starts from the root node, and each device is a node.

(2) Nodes and nodes can be nested with each other to form a parent-child relationship

(3) The attributes of the device are described with * * key value pairs (key value pairs) * *, and each attribute is ended with a semicolon

2. Device tree syntax

2.1 nodes

A node is like a big tree. It starts from the trunk of the tree and then branches one by one. This is called a node. What are the nodes in the code like. Let's take out the root node in the above template. As shown below, it is the root node

2.1.1 root node
//Root node
/ {

}; //Semicolon end
2.2.2 child nodes

The branch is equivalent to the child node of the device tree. Similarly, we pick out the child nodes, which are node1 and node2 in the root node,

/ {	//Root node
	node1 {	//Child node
	
	};//Semicolons are indispensable
	noed2 {	//Child node
        
    };
};

2.2.2 grandson node

/{	//Root node
	node1{	//Child node
        child-node1{	//Grandson node
            
        };
	};//Semicolons are indispensable
	noed2{	//Child node
        
    };
};
2.2 node name

The naming of nodes has a fixed format.

<name>[@<Device address>]

< name > the name of the node is not arbitrary. Generally, it should reflect the type of equipment rather than the model of characteristics. For example, the network port should be named ethernet rather than one at random, such as 111

< device address > is the base address used to access the device. But it doesn't mean to describe an address (not the address obtained in the driver) in the process of operation. It is mainly used to distinguish

matters needing attention:

(1) as long as the addresses of nodes at the same level are different, the name can not be unique.

(2) the device address is optional and can not be written. But in order to distinguish and understand, it is generally written

2.3 node alias

When we find a node, we must write the complete node path. If the node name is very long, it is very inconvenient for us to reference. Therefore, the device tree allows us to label the reference (alias) for the node in the following form.

uart8:serial@022288000

uart8 is the alias of this node, serial@022288000 Is the node name

2.4 node reference

Generally, when adding content to a node, the added content is not written directly to the node, but added through the reference of the node

for example

&uart8 {
	pinctrl-name = "default";
	pinctrl-0 = <&pinctrl_uart8>;
	status = "okey";
};

&Uart8 refers to the node whose alias is uart8, and add the following content to this node

	pinctrl-name = "default";
	pinctrl-0 = <&pinctrl_uart8>;
	status = "okey";

matters needing attention:

When compiling the device tree, different attribute information of the same node will be merged, and the same attributes of the same node will be rewritten. Using references can avoid the transplant from looking for nodes everywhere. For example, both dts and dtsi have root nodes, but they will eventually be merged into one root node.

2.5 properties
(1) reg attribute

The reg attribute is used to describe the address range of a device.

Format:

reg=<add1 length1 [add2 length2]...>

example

seral@02288000{
	reg = <0x101F200 0x1000>;	
};

Where 0x101F200 is the starting address and 0x1000 is the length

(2) #address cells and #size cells properties

#Address cells is used to set the number of reg addresses in child nodes

#Size cells is used to set the number of reg address lengths in child nodes

example

cpu{
	#address-cells = <1>;
	#size-cells = <1>;
	serial@02288000{
		compatible = "serial";
		reg = <0x101F200 0x1000>;
	};
};
(3) compatible attribute

compatible is a list of strings that can be matched in code. Is to match the device name and driver name in the platform

compatible = "my_led"
(4) status attribute

The value type of the status attribute is a string. Here we just need to remember two common ones,

valuedescribe
okayThe equipment can be used normally
disableThe equipment cannot be used normally
3. Custom device node

Take led as an example. As mentioned earlier, the device tree is the text describing hardware resources, that is, the device of the platform bus

Add a device node under the root node. The node name is led and myled is an alias

	/* Add custom node*/
	myled:led{
		compatible="myled";  //describe
		reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
		status = "okay";		  //okay, not okey
				
	};

Then execute make dtbs

Compile a separate device tree file, make exynos4412 iTOP elite DTB can be followed by the device tree file we need to generate

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-YUMCUJzi-1619336495815)(linux driver. assets/image-20210416175155914.png)]

Then copy the device tree to memory through TFTP

Then you can see the newly added device node led in / proc/devices directory

5. Get device tree resource of function

device_node structure description node

Devices are "hung" to the device tree in the form of nodes. Therefore, in order to obtain other attribute information of the device, you must first obtain the node of the device. The Linux kernel uses device_node structure, which is defined in include / Linux / of H down

#include<linux/of.h>
struct device_node {
	const char *name;	// Node name
	const char *type;	// Equipment type
	phandle phandle;
	const char *full_name;	//Node full name
	struct fwnode_handle fwnode;
	struct	property *properties;	//attribute
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent;	//Parent node
	struct	device_node *child;		//Child node
	struct	device_node *sibling;
	struct	kobject kobj;
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	const char *path_component_name;
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

The property structure describes the property

#include<linux/of.h>
struct property {
	char	*name;		//The attribute name compatible is the attribute name
	int	length;			//Attribute length 	 The length is "compatible" 5
	void	*value;		//Attribute value t, e.g. test
	struct property *next;	//Next attribute
	unsigned long _flags;
	unsigned int unique_id;
	struct bin_attribute attr;
};

Steps to obtain resources in the device tree file node

1. Find the node we are looking for
2. Get the attribute value we need

1. Common of functions for finding nodes

(1) of_find_node_by_path function
#include<linux/of.h>
static inline struct device_node *of_find_node_by_path(const char *path)
{
	return of_find_node_opts_by_path(path, NULL);
}
//The node is returned successfully, and NULL is returned in case of failure

Path: the node name with full path. You can use the alias of the node. For example, the user-defined node "/ led" added in the previous section is the full path name of the node

(2) of_get_parent function

Used to get the parent node of the specified node (if there is a parent node)

#include<linux/of.h>
extern struct device_node *of_get_parent(const struct device_node *node);
//Parent node found successfully 		 Failed NULL 

Node node of the parent node to find (current node)

(3) of_get_next_child function
#include<linux/of.h>
extern struct device_node *of_get_next_child(const struct device_node *node,
					     struct device_node *prev);
//The child node is returned successfully, and NULL is returned in case of failure

Node parent node (current node)

prev the previous child node, that is, from which child node to start iterative search for the next child node. It can be set to NULL, indicating that it starts from the first child node

These all work with the first function of_find_node_by_path to use

2. Get the properties of the device node

#include<linux/of.h>
extern struct property *of_find_property(const struct device_node *np,
					 const char *name,
					 int *lenp);
//The return value returns the property found successfully and NULL if failed 
parameterdescribe
npDevice node
nameAttribute name
lenpNumber of attribute values
1. Read integer properties such as reg

Some attributes have only one integer value. These four functions are used to read the attributes with only one integer value. They are used to read the attribute values of u8, u16, u32 and u64 types respectively

of_property_read_u8
of_property_read_u16
of_property_read_u32
of_property_read_u64
static inline int of_property_read_u8(const struct device_node *np,
				       const char *propname,
				       u8 *out_value)
{
	return of_property_read_u8_array(np, propname, out_value, 1);
}

static inline int of_property_read_u16(const struct device_node *np,
				       const char *propname,
				       u16 *out_value)
{
	return of_property_read_u16_array(np, propname, out_value, 1);
}

static inline int of_property_read_u32(const struct device_node *np,
				       const char *propname,
				       u32 *out_value)
{
	return of_property_read_u32_array(np, propname, out_value, 1);
}
extern int of_property_read_u64(const struct device_node *np,
				const char *propname, u64 *out_value);
//0 is returned for success and negative number is returned for failure
parameterdescribe
npDevice node
pronameThe name of the property to read
out_valueRead value
2. Read integer array such as reg
of_property_read_u8_array
of_property_read_u16_array
of_property_read_u32_array
of_property_read_u64_array
static inline int of_property_read_u8_array(const struct device_node *np,
					    const char *propname,
					    u8 *out_values, size_t sz);
static inline int of_property_read_u16_array(const struct device_node *np,
					    const char *propname,
					    u16 *out_values, size_t sz);
static inline int of_property_read_u32_array(const struct device_node *np,
					    const char *propname,
					    u32 *out_values, size_t sz);
static inline int of_property_read_u64_array(const struct device_node *np,
					    const char *propname,
					    u64 *out_values, size_t sz);
// The return value returns 0 for success and negative for failure
parameterdescribe
npDevice node
pronameThe name of the property to read
out_valueRead the values of u32, u64 and u16, respectively
szWhen the number of array elements to be read is 1, it is the same as the reading integer above
3. Read string properties

For example, read status = "okay"

static inline int of_property_read_string(const struct device_node *np,
					  const char *propname,
					  const char **out_string);
//0 is returned for success and negative number is returned for failure
parameterdescribe
npDevice node
pronameThe name of the property to read
out_stringRead string value
example

When writing the device tree file, the status = "okay" cannot be written wrong, otherwise it will not match

	/* Add custom node*/
	myled:led{
		compatible="myled";  //describe
		reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
		status = "okay";		
				
	};
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
/*  1,Find the node we are looking for
    2,Get the attribute value we need
*/
struct device_node *dev_no;
static int hello_init(void)
{
    //Get node information
    dev_no = of_find_node_by_path("/led");
    if(NULL == dev_no)
    {  
        printk("of find node by path is error!\n");
        return -1;
    }
    printk("device_node name:%s\n", dev_no->name);
    printk("device_node type:%s\n", dev_no->type);    
    printk("device_node full_name:%s\n", dev_no->full_name);
    printk("device_node property value:%s\n", dev_no->properties->value);
    printk("device_node property name:%s\n", dev_no->properties->name);
    /*Get reg property*/
    u32 reg_value[4]; 
    int ret = of_property_read_u32_array(dev_no, "reg", reg_value, 4);
    if (ret != 0)
    {
        printk("of property read u32 array is error!\n");
        return -1;
    }
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        printk("reg_value[%d] : 0x%x\n", i, reg_value[i]);
    }
    /*Get status attribute*/
    const char * out_value = NULL;  
    ret = of_property_read_string(dev_no, "status", &out_value);
    printk("device_node property status value :%s\n", out_value); 
	printk("hello world\n");
	return 0;
}
static void hello_exit(void)
{
	printk("byb byb\n");  // printk is required for the print function here, because printf is a C library function
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

4,of_ get_ named_ The GPIO function obtains the GPIO label

This function obtains the GPIO number, because all API functions related to GPIO in the Linux kernel use the GPIO number, which will be similar to < &gpl2 0 GPIO in the number of devices_ ACTIVE_ The attribute information of high > is converted to the corresponding GPIO number

#include<linux/of_gpio.h>

int of_get_named_gpio(struct device_node *np,const char *propname, int index);
// Return value: GPIO number is returned successfully, and negative number is returned in case of failure
parameterdescribe
npDevice node
propnameContains the property name to get GPIO information
indexBecause a property may contain multiple gpios, this parameter specifies which GPIO number to obtain. If there is only one GPIO information, this parameter is 0
5. The physical address under the platform of the device tree is mapped to the virtual address_ Iomap function

Function: * * of_ The iomap function is used for direct memory mapping. * * previously, we used the ioremap function to complete the mapping from physical address to virtual address. Of is usually used in the platform driver of the device tree_ Iomap function, the two functions are the same

void __iomem *of_iomap(struct device_node *device, int index);
//The return value returns the first address after mapping successfully, and NULL if it fails
parameterdescribe
deviceDevice node
indexThe segment to complete memory mapping in the reg attribute. If the reg attribute has only one segment, the index will be set to 0. If the two segments are 1, they will increase in turn

6. Platform platform bus driver under the device tree

The device tree matches the driver

Struct of is used_ device_ The compatible = "xxx" in the ID structure is similar to the platform bus, but the matching object has changed

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};
struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
	enum probe_type probe_type;

	const struct of_device_id	*of_match_table;   //Device tree platform bus matching
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};
struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];	//Match value
	const void *data;
};

driver matches the device tree

struct platform_device_id id_table = {
	.name = "myledxxx"     //Same as compatible in the device tree
};
const struct of_device_id dev_node[] = {  // The highest priority among the three
    {.compatible="myledxxx"},
    {}
};
struct platform_driver pdriver = {
	.probe = led_probe,
	.remove = led_remove,
	.driver = {
		.name = "myled", // Same name as device
		.owner = THIS_MODULE,
		.of_match_table = dev_node
	},
	.id_table = &id_table			//Here is a pointer 			//  Priority ratio driver Name high priority
};
int _init()
{
    platform_driver_register(&pdriver);  // Register platform_driver
}

Priority among the three_ match_ table> id_ table > driver. name

Enter probe function after successful matching

probe function to obtain resources

After the resource matching is successful, the kernel will fill the platform with resources_ Device, then the device tree information we need is also included

On platform_ device device device_ Under node

1. Direct acquisition
    	printk("name : %s\n", pdev->dev.of_node->name);  
2. Acquisition method using device free tree platform

Using platform_get_resource method

	
	led_resource_con = platform_get_resource(pdev, IORESOURCE_MEM, 0); //Get GPL2CON
	if (NULL == led_resource_con)
	{
		printk("platform_get_resource is error!\n");
		return -EBUSY;
	}
	led_resource_dat = platform_get_resource(pdev, IORESOURCE_MEM, 1); //Get GPL2DAT
	if (NULL == led_resource_dat)
	{
		printk("platform_get_resource is error!\n");
		return -EBUSY;
	}
3. Device acquisition method of TF platform
    /*  Obtain hardware resources from resources with device tree */
    /*Get reg*/
    u32 regbuf[4];
    int ret = of_property_read_u32_array(pdev->dev.of_node, "reg", reg_buf, 4);
    if (0 != ret)
    {
        printk("of property read u32 array is error!\n");
        goto err_region;
    }
    printk("led_con:0x%x\n", regbuf[0]);
    printk("led_con length :0x%x\n", regbuf[1]);
    printk("led_dat:0x%x\n", regbuf[2]);
    printk("led_dat length :0x%x\n", regbuf[3]);

The physical address under the platform of the device tree is mapped to the virtual address_ Iomap function

Function: * * of_ The iomap function is used for direct memory mapping. * * previously, we used the ioremap function to complete the mapping from physical address to virtual address. Of is usually used in the platform driver of the device tree_ Iomap function, the two functions are the same

void __iomem *of_iomap(struct device_node *device, int index);
//The return value returns the first address after mapping successfully, and NULL if it fails
parameterdescribe
deviceDevice node
indexThe segment to complete memory mapping in the reg attribute. If the reg attribute has only one segment, the index will be set to 0. If the two segments are 1, they will increase in turn
myled:led{
	compatible="myled";  //describe
	reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
	status = "okay";				
};

For example, the device tree node index is set to 2

Question 1: how to operate the obtained virtual address mapping; Since there is only one address, how do the mapped address areas determine con and dat

After obtaining resources, register miscellaneous devices or character devices

7. led miscellaneous equipment of deivce tree platform

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>		//struct plantform_devide / plantform_driver
#include <linux/ioport.h>				// 
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/of.h>	
#include <linux/of_address.h>		//io_map
struct resource * led_resource_con;
struct resource * led_resource_dat;
static unsigned int * led_con;			//con address of ioremap mapping
static unsigned int * led_dat;
static unsigned int length_con,length_dat;
static unsigned int * Led_GPL2_add;

ssize_t led_read (struct file *file, char __user *user, size_t n, loff_t * loff)
{
	return 0;
}

ssize_t led_write (struct file *file, const char __user *user, size_t n, loff_t * loff)
{
	char buf[128] = {0};
	if (copy_from_user(buf, user, n) != 0)
	{
		printk("copy from user is error!\n");
		return -1;
	}
	if (buf[0] == 0x1)
	{
		*led_dat = 0x1;
	}
	else if (buf[0] == 0)
	{
		*led_dat = 0;
	}
	return 0;
}
int led_open (struct inode *inode, struct file *file)
{
	//Set level direction
	*led_con |= 0x1; // output
	
	return 0;
}
int led_release (struct inode *inode, struct file *file)
{
	return 0;
}

/*file_operations initialization*/
struct file_operations misc_fops = {
	.owner = THIS_MODULE,
	/*File operation set*/
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = led_release,

};
/*Miscellaneous device initialization*/
struct miscdevice miscdev = {
	.minor = MISC_DYNAMIC_MINOR, //Automatically assign secondary equipment number
	.name  = "my_led", 
	.fops = &misc_fops,
};

int led_probe(struct platform_device *pdev)		//The pdev here refers to the pdev in the device
{
    printk("led probe\n");
	/*The first method to obtain hardware resources*/
//	printk("%s\n", pdev->resource[0].name);
    printk("name : %s\n", pdev->dev.of_node->name);
	/*Second*/
	/*Get hardware resources without device tree*/
#if 0
	led_resource_con = platform_get_resource(pdev, IORESOURCE_MEM, 0); //Get GPL2CON
	if (NULL == led_resource_con)
	{
		printk("platform_get_resource is error!\n");
		return -EBUSY;
	}
	led_resource_dat = platform_get_resource(pdev, IORESOURCE_MEM, 1); //Get GPL2DAT
	if (NULL == led_resource_dat)
	{
		printk("platform_get_resource is error!\n");
		return -EBUSY;
	}
    printk("led_con:0x%x\n", led_resource_con->start);
    printk("led_dat:0x%x\n", led_resource_dat->start);
	length_con = led_resource_con->end - led_resource_con->start + 1;
	length_dat = led_resource_dat->end - led_resource_dat->start + 1;


#endif 
    /*  Obtain hardware resources from resources with device tree */
    /*Get reg*/
    u32 regbuf[4];
    int ret = of_property_read_u32_array(pdev->dev.of_node, "reg", regbuf, 4);
    if (0 != ret)
    {
        printk("of property read u32 array is error!\n");
        goto err_region;
    }
    printk("led_con:0x%x\n", regbuf[0]);
    printk("led_con length :0x%x\n", regbuf[1]);
    printk("led_dat:0x%x\n", regbuf[2]);
    printk("led_dat length :0x%x\n", regbuf[3]);
    /* Mapping of physical address and virtual address under the device tree*/
#if 0
    Led_GPL2_add = of_iomap(pdev->dev.of_node, 1);
    if (NULL == Led_GPL2_add)
    {
        printk("of iomap is error!\n");
        return -1;
    }
    led_con = Led_GPL2_add;
    led_dat = Led_GPL2_add ;
    printk("led_con:0x%x\n", led_con);
    printk("led_dat:0x%x\n", led_dat);
#endif 
	/*Mapping physical addresses to virtual addresses*/
	led_con = ioremap( regbuf[0], regbuf[1]);
	if (NULL == led_con){
		printk("ioremap is error!\n");
		goto err_region;
	}
	led_dat = ioremap(regbuf[2], regbuf[3]);
	if (NULL == led_dat){
		printk("ioremap is error!\n");
		goto err_region;
	} 	
	printk("led probe is success!\n");
	return 0;

err_region:
	release_mem_region(led_resource_con->start, length_con);
	release_mem_region(led_resource_dat->start, length_dat);
	return - EBUSY;

}
int led_remove(struct platform_device *pdev)
{
	printk("led remove is success!\n");
	return 0;
}
/*platform_driver initialization*/

struct platform_device_id id_table = {
	.name = "myledxxx"     //Same as compatible in the device tree
};
const struct of_device_id dev_node[] = {  // The highest priority among the three
    {.compatible="myledxxx"},
    {}
};
struct platform_driver pdriver = {
	.probe = led_probe,
	.remove = led_remove,
	.driver = {
		.name = "myled", // Same name as device
		.owner = THIS_MODULE,
		.of_match_table = dev_node
	},
	.id_table = &id_table			//Here is a pointer 			//  Priority ratio driver Name high priority
};

static int hello_init(void)
{
	int ret; 
	//Register platform driver
	ret	= platform_driver_register(&pdriver);
	if (0 != ret)
	{
		printk("platform driver register is error!\n");
		return ret;
	}
	//Register miscellaneous equipment and generate equipment node
	ret = misc_register(&miscdev);
	if (ret != 0)
	{
		printk("misc deregister is error!\n");
		return ret;
	}
	printk("hello world\n");
	return 0;
}
static void hello_exit(void)
{
	printk("byb byb\n");  // printk is required for the print function here, because printf is a C library function
    platform_driver_unregister(&pdriver); 	//cancellation
    misc_deregister(&miscdev);				//Uninstall miscellaneous equipment
}


module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

8. led character device of deivce tree platform

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
/*  
    1,Register platform driver
    2,Build file_operations
    3,Get hardware resources
    4,Register character device driver
    4,Generate character device node
*/  


static unsigned int * led_con;
static unsigned int * led_dat;
struct device *device_cdev; 
struct class *class_cdev;


ssize_t led_read (struct file *file, char __user * user, size_t size, loff_t * loff )
{
    printk("read is success!\n");
    return 0;
}
ssize_t led_write (struct file *file, const char __user *user, size_t size, loff_t * loff)
{   char buf[128] = {0};
    int ret ;
    printk("write is success!\n");
    ret  = copy_from_user(buf, user, size);
    if (0 != ret)
    {
        printk("copy form user is error!\n");
        return ret;
    }
    if (buf[0] == 1 || buf[0] == 0)
    * led_dat = buf[0];

    return 0;
}
int led_open(struct inode *inode, struct file *file)
{
    *led_con |= 0x1;
    printk("open is success!\n");
    return 0;
}
int led_release (struct inode *inode, struct file *file)
{
    printk("release is success!\n");
    return 0;
}

/*2,Get hardware resources */
int ledprobe(struct platform_device *pdev)
{
    u32 regbuf[4];
    /*Get the physical address of the register in the reg attribute, and then map the physical address to the virtual address*/
    int ret = of_property_read_u32_array(pdev->dev.of_node, "reg", regbuf, 4);
    if (0 != ret)
    {
        printk("of property read u32 array is error!\n");
        return -1;
    }
    printk("led_con:0x%x\n", regbuf[0]);
    printk("led_con length :0x%x\n", regbuf[1]);
    printk("led_dat:0x%x\n", regbuf[2]);
    printk("led_dat length :0x%x\n", regbuf[3]);
    /* Physical address and virtual address mapping*/
    led_con = ioremap(regbuf[0], regbuf[1]);
    if (NULL == led_con)
    {
        printk("ioremap is error!\n");
        return -1;
    }
    led_dat = ioremap(regbuf[2], regbuf[3]);
    if (NULL == led_dat)
    {
        printk("ioremap is error!\n");
        return -1;
    }
    return 0;    
}
int ledremove(struct platform_device *pdev)
{
    return 0;
}

struct of_device_id of_match_table = {      // Match with the device tree node
    .compatible = "myledxxx"
};

/*1,Initialize platform driver*/
struct platform_driver pdev = {
    .probe = ledprobe,          // And of_ match_ After the table matches successfully, enter the probe function to obtain hardware resources
    .remove = ledremove,
    .driver = {
        .name = "myledxxx",     //Used when there is no device tree name matches device
        .owner = THIS_MODULE,
        .of_match_table = &of_match_table,
    }
};
//3. Register character device driver
/*3.1 Assign equipment number*/
dev_t dev_number;
/*3.2 Define cdev*/
struct cdev cdev_;

/*3.3 Build file_operation structure*/
struct file_operations fop = {
    .owner = THIS_MODULE,
    .open    = led_open,
    .release = led_release,
    .write   = led_write,
    .read    = led_read,
};

static int char_driver_init(void)
{
    /*1,Register platform driver*/
    int ret = platform_driver_register(&pdev);
    if (0 != ret)
    {   
        printk("platform driver register is error!\n");
        return -1;
    }
    /*3.1 Assign equipment number (dynamically assign equipment number)*/
    ret = alloc_chrdev_region(&dev_number, 0, 1, "my_led");
    if (0 != ret)
    {
        printk("alloc chrdev region is error!\n");
        return ret;
    }
    /*3.4 Initialize cdev*/
    
    cdev_.owner = THIS_MODULE;
    cdev_init(&cdev_, &fop);
    /*3.5 Register character device to kernel*/
    ret = cdev_add(&cdev_, dev_number, 1);
    if (0 != ret)
    {
        printk("cdev add is error!\n");
        return -1;
    }
    /*4,Generate device node*/
    /*4.1 Create character device class*/
   class_cdev =  class_create(THIS_MODULE, "my_led");
    if (NULL == class_cdev)
    {
        printk("class create is error!\n");
        return -1;
    }
    /*Generate device node*/
    device_cdev = device_create (class_cdev, NULL, dev_number, NULL,  "my_led");
    if (NULL == device_cdev)
    {
        printk("device create is error!\n");
    }
    return 0;
};

static void char_driver_exit(void)
{
    device_destroy(class_cdev, dev_number); // Uninstall device node
    class_destroy(class_cdev);              //Uninstall device class
    cdev_del(&cdev_);                       //Uninstall cdev
    iounmap(led_con);                       // Unmap address
    iounmap(led_dat);                   
    unregister_chrdev_region(dev_number, 1);// Logout equipment number
    
    platform_driver_unregister(&pdev);       // Unregister platform driver
   
}

module_init(char_driver_init);
module_exit(char_driver_exit);
MODULE_LICENSE("GPL");

On the failure of matching the device tree with the driver

1. After burning the device tree, enter the * * / sys/devices/platform * * directory to check whether there are any device tree nodes we added

2. Of of the driver part_ match_ The compatible of table must be the same as that in the device tree

3. If the device tree node we added cannot be found in * * / sys/devices/platform * *, check the device tree file to see if the status attribute is written correctly

status two commonly used attributes "okay" and "disable"

Keywords: Embedded system Linux Driver

Added by Gorillas on Sat, 19 Feb 2022 15:13:39 +0200