[Linux] kernel driver Chapter 7 - device tree

1, Origin

  • Reduce junk code
  • Reduce the workload of driver development
  • Separation of driver code and device information
  • Refer to Open Fireware design
  • It is used to record the attribute information of various hardware devices in the hardware platform

2, Basic composition

There are two kinds of source files:

  1. xxxxx.dts is the abbreviation of device tree source
  2. xxxxx. The abbreviation dtsi means that the source file dtsi is used to include dtsi

In actual use, the dts file needs to be compiled into the corresponding binary file (. dtb file, dtb is the abbreviation of device tree binary) so that it can be stored in memory at runtime to speed up the speed of reading information

3, Basic grammar

  • The main content of dts file is composed of multiple nodes
  • Each node can contain 0 or more child nodes to form a tree relationship
  • Each dts file has a root node, and other nodes are its descendants
  • The root node is generally used to describe the hardware platform of the whole development board, and other nodes are used to represent the attribute information of specific equipment and bus
  • Each node can have multiple attributes, and each attribute is represented by a key value pair

Node syntax:

[label:] node-name[@unit-address] {    
	[properties definitions];    
	[child nodes];
};

label: Optional, node alias. In order to shorten the node access path, it can be used in subsequent nodes  &label To represent a reference to a specified node
node-name: Node name
unit-address: Device address: generally fill in the first address of the device register group or memory block
properties definitions: Attribute definition
child nodes:Child node

Attribute syntax:

[label:] property-name = value;
[label:] property-name;

Property can have no value
 There are three values for the attribute with value:
1. arrays of cells(1 One or more 32-bit data, 64 The 32-bit data is represented by two 32-bit data, separated by spaces),Expressed in angle brackets(< >)
2. string(character string), In double quotation marks(" ")
3. bytestring(1 One or more bytes, separated by spaces),Expressed in square brackets([])
4. use,Separated multiple values
    

4, Special node

4.1 root node

The root node represents the information of the whole development board

#Address cells / / in the reg attribute of the child node, how many u32 integers are used to describe the address
#Size cells / / in the reg attribute of the child node, how many u32 integers are used to describe the size
compatible      // Defines a series of strings used to specify which machine in the kernel_ Desc can support this device, that is, describe which platforms it is compatible with                         
model           // For example, there are two boards with basically the same configuration and their compatibility is the same. Then use the model to distinguish these two boards

4.2 /memory

The required node of all device tree files, which defines the layout of the physical memory of the system

device_type = "memory";
reg             //Used to specify the address and size of memory

4.3 /chosen

Pass the parameter used when the kernel starts

bootargs  //String, kernel startup parameter, the same as the bootargs set in u-boot

4.4 /cpus multi-core CPU support

/There are one or more cpu child nodes under the cpus node. The reg attribute is used in the cpu child nodes to indicate which cpu they are

Therefore, there are two attributes in / cpus:

#Address cells / / in the reg attribute of its child node, how many u32 integers are used to describe the address
#Size cells / / in the reg attribute of its child node, how many u32 integers are used to describe the size. The size must be set to 0

5, Common properties

5.1 phandle

Node ID in digital form. When the attribute value property represents a node in subsequent nodes, the corresponding node can be referenced

For example:

pic@10000000 {    
	phandle = <1>;    
	interrupt-controller;
};
another-device-node {    
	interrupt-parent = <1>;   // The above node is referenced with a phase value of 1
};

5.2 address ------------ important

reg attribute: indicates the memory region. Syntax:

reg = <address1 length1 [address2 length2] [address3 length3]>;

#How many u32 integers are used to describe the address in the address cells: reg attribute? Syntax:

#Address cells = < number >;

#How many u32 integers are used in size cells: reg attribute to describe size? Syntax:

#Size cells = < number >;

5.3 compatible ------------- important

The matching basis between driver and device (device node). The value of compatible can have more than one string to meet different requirements. Syntax:

compatible = "String 1","String 2",...;

5.4 interruption ------------ important

a. Attributes for interrupt controller node:

Interrupt controller is a null attribute with no value, which is used to declare that the node receives interrupt signals, indicating that the node is an interrupt controller

#Interrupt cells this is the attribute of the interrupt controller node, which is used to identify how many units the controller needs as interrupt descriptors

b. Attributes for interrupt source device node:

Interrupt parent: identifies which interrupt controller this device node belongs to. If this attribute is not set, it will automatically attach to the parent node. Syntax:

interrupt-parent = <Reference an interrupt controller node>

interrupts is a list of interrupt identifiers, indicating each interrupt output signal. Syntax:

interrupts = <Interrupt number trigger mode>

1 low-to-high Rising edge trigger
2 high-to-low Falling edge trigger
4 high level  High level trigger
8 low level   Low level trigger

5.5 gpio ------------- important

gpio is also the most common IO port. Common attributes include:

a. For GPIO controllers:

gpio controller, a null attribute with no value, is used to indicate that this node describes a gpio controller

#GPIO cells, used to describe a GPIO pin with several cells

b. For GPIO consumer nodes:

gpio uses the properties of the node

xxx-gpio = <&quote GPIO controller GPIO Label working mode>
Working mode:
1 Low level active GPIO_ACTIVE_HIGH
0 High level active GPIO_ACTIVE_LOW

5.6 attribute setting routine

Generally speaking, the node attribute setting of each device will have some routines, such as which attributes can be set? How to set the attribute value? How do you know these routines? There are two ideas:

  1. Copy similar DTS. For example, if the platform of our own project is 4412, you can copy exynos 4412-tiny4412 dts,exynos4412-smdk4412. Similar DTS such as DTS
  2. Query documents in the kernel, such as documentation / devicetree / bindings / i2c / i2c imx Txt describes the i2c attribute setting method of imx platform; Documentation/devicetree/bindings/fb describes how to set properties such as lcd and lvds

6, Common interface

struct device_node corresponds to a node in the device tree
struct property corresponds to a property in the node

6.1 of_find_node_by_path

/**
include/of.h
of_find_node_by_path - Find the specified node by path
@path - The path of the node in the device tree, including the node name or the alias of the node
 Success: get the first address of the node; Failed: NULL
*/
struct device_node * of_find_node_by_path(const char *path);

6.2 of_find_property

/*
include/of.h
of_find_property - Extracts the value of the specified attribute
@np - Device node pointer
@name - Attribute name
@lenp - Number of bytes of attribute value
 Success: the first address of the attribute value; Failed: NULL
*/
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

6.3 of_get_named_gpio

/**
 * include/of_gpio.h
 * of_get_named_gpio - Extract gpio port from device tree
 * @np - Device node pointer
 * @propname - Attribute name
 * @index - gpio Port pin label 
 * Success: get the GPIO port number; Failure: negative number, absolute value is error code
 */
int of_get_named_gpio(struct device_node *np, const char *propname, int index);

6.4 irq_of_parse_and_map

/*
	Function: obtain the interrupt number in the device tree and map it
	Parameter: node: device node
		 index:Serial number
	Return value: Success: interrupt number 	 Failure: error code
*/
unsigned int irq_of_parse_and_map(struct device_node *node, int index);

6.5 read attribute value

of_property_read_string

/*
of_property_read_string - Extract string (attribute value)
@np - Device node pointer
@propname - Attribute name
@out_string - Output parameter, pointing to string (attribute value)
Success: 0; Failure: negative number, absolute value is error code
*/
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);

Read value

int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)

int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)

int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)

Determine whether the attribute exists

int of_property_read_bool(const struct device_node *np,const char *propname)

Read array

int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)

7, GPIO interface

Apply to GPIO kernel 1.7

int gpio_request(unsigned gpio,const char *label)

Function: in fact, let the kernel check whether the GPIO pin is occupied by other devices. If it is not occupied, return 0 and mark it with label to indicate that it is occupied by this device, otherwise return a negative number

gpio: obtained device number
label: device node attribute name

void gpio_free(unsigned gpio)

Function: remove the occupation mark of the device on the GPIO, indicating that the device returns the right to use the GPIO pin to the kernel, and then other devices can occupy the GPIO pin

7.2 setting GPIO direction

int gpio_direction_input(unsigned gpio)

int gpio_direction_output(unsigned gpio,int value)

value:0-low level, 1-high level

7.3 reading and writing GPIO data

int gpio_get_value(unsigned gpio)

int gpio_set_value(unsigned gpio,int value)

8, led driver tree

  1. Add the node of this device under the root node of the source file of the device tree (this node contains the resource information used by this device)
    ..../linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
fs4412-leds {
	compatible = "fs4412,led2-5";
	led2-gpio = <&gpx2 7 0>;
	led3-gpio = <&gpx1 0 0>;
	led4-gpio = <&gpf3 4 0>;
	led5-gpio = <&gpf3 5 0>;
};
  1. Execute in the top-level directory of linux kernel source code: make dtbs (generate the corresponding dtb file)
  2. cp ???.dtb /tftpboot
  3. Write driver code:
    a. Find the corresponding node (address value of struct device_node type) through the path of the device in the device tree
    b. Call of_ get_ named_ The GPIO function gets the number of a GPIO
    c. Record all GPIO numbers used in the struct leddev structure
    d. Before using a GPIO pin, you need to pass GPIO_ The request function requests the kernel to occupy this pin. When this pin is not used, it can be used through gpio_free return to kernel
    e. Through gpio_direction_input and gpio_direction_output function to set the function of a GPIO
    f. Through GPIO_ get_ The value function can obtain the current level of a GPIO pin
    g. Through GPIO_ set_ The value function can change the level of a GPIO pin

Example program:
dev_led2:

/*************************************************************************
  > File Name: led2.c
  > Author: xiuchengzhen
  > CSDN: xiuchengzhen.blog.csdn.net
  > Created Time: Tue 10 May 2022 07:29:52 PM PDT
 ************************************************************************/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>

#include "dev_led2.h"

int major = 11;   //Main equipment No
int minor = 0;    //Secondary equipment No
int dev_num = 1;  //Number of equipment

struct pdev  //Equipment structure
{
	struct cdev leddev;  //led structure
	int led2_num;   //GPIO port number
	int led3_num;
	int led4_num;
	int led5_num;
};

struct pdev *pleddev = NULL;

int led_open(struct inode *pnode, struct file *pfile)	//open device
{
	/* Apply for GPIO pin occupation */
	gpio_request(pleddev->led2_num, "led2");
	gpio_request(pleddev->led3_num, "led3");
	gpio_request(pleddev->led4_num, "led4");
	gpio_request(pleddev->led5_num, "led5");

	return 0;
}

int led_release(struct inode *pnode, struct file *pfile)	//Turn off the device
{
	/* Request return of GPIO pin */
	gpio_free(pleddev->led2_num);
	gpio_free(pleddev->led3_num);
	gpio_free(pleddev->led4_num);
	gpio_free(pleddev->led5_num);
	
	return 0;
}

long leddev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
		case LED2_ON:
			gpio_direction_output(pleddev->led2_num,1);
			break;
		case LED2_OFF:
			gpio_direction_output(pleddev->led2_num,0);
			break;
		case LED3_ON:
			gpio_direction_output(pleddev->led3_num,1);
			break;
		case LED3_OFF:
			gpio_direction_output(pleddev->led3_num,0);
			break;
		case LED4_ON:
			gpio_direction_output(pleddev->led4_num,1);
			break;
		case LED4_OFF:
			gpio_direction_output(pleddev->led4_num,0);
			break;
		case LED5_ON:
			gpio_direction_output(pleddev->led5_num,1);
			break;
		case LED5_OFF:
			gpio_direction_output(pleddev->led5_num,0);
			break;
		default: 
			printk("cmd error!\n");
			return -1;
	}
	return 0;
}

struct file_operations fops={
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_release,
	.unlocked_ioctl = leddev_ioctl,
};

int __init led2_init(void)
{
	dev_t devno = MKDEV(major, minor);
	int ret = -1;

	if((ret = register_chrdev_region(devno, dev_num, "dev_led")) < 0)
	{
		ret = alloc_chrdev_region(&devno, minor, dev_num, "dev_led");
		if(ret < 0)
		{
			printk("alloc_chrdev_region fail!\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* The device allocates memory space */
	pleddev = (struct pdev *)kmalloc(sizeof(struct pdev), GFP_KERNEL);
	if(pleddev == NULL)
	{
		unregister_chrdev_region(devno, dev_num);
		printk("pmydev malloc fail!\n");
		return -1;
	}

	/* Device initialization */
	cdev_init(&pleddev->leddev, &fops);
	pleddev->leddev.owner = THIS_MODULE;

	/* Device addition */
	cdev_add(&pleddev->leddev, devno, dev_num);

	/* Equipment number acquisition */
	struct device_node *led_node = of_find_node_by_path("/fs4412-leds"); 
	if(led_node == NULL)
	{
		printk("device_node get fail!\n");
		return -1;
	}
	pleddev->led2_num = of_get_named_gpio(led_node, "led2-gpio", 0);
	pleddev->led3_num = of_get_named_gpio(led_node, "led3-gpio", 0);
	pleddev->led4_num = of_get_named_gpio(led_node, "led4-gpio", 0);
	pleddev->led5_num = of_get_named_gpio(led_node, "led5-gpio", 0);


	return 0;
}

void __exit led2_exit(void)
{
	dev_t devno = MKDEV(major, minor);

	/* Logout device */
	cdev_del(&pleddev->leddev);

	/* Logout equipment number */
	unregister_chrdev_region(devno, dev_num);

	/* Free up space */
	kfree(pleddev);

}

MODULE_LICENSE("GPL");
module_init(led2_init);
module_exit(led2_exit);

dev_led2.h:

/*************************************************************************
	> File Name: mytest.h
	> Author: xiuchengzhen
	> CSDN: xiuchengzhen.blog.csdn.net
	> Created Time: Mon 16 May 2022 05:39:30 AM PDT
 ************************************************************************/

#ifndef DEVLED_H
#define DEVLED_H

#include <asm/ioctl.h>



#define IOCTL_TYPE 'k'
#define LED2_ON  _IOR(IOCTL_TYPE, 1,int *)
#define LED2_OFF _IOR(IOCTL_TYPE, 2,int *)
#define LED3_ON  _IOR(IOCTL_TYPE, 3,int *)
#define LED3_OFF _IOR(IOCTL_TYPE, 4,int *)
#define LED4_ON  _IOR(IOCTL_TYPE, 5,int *)
#define LED4_OFF _IOR(IOCTL_TYPE, 6,int *)
#define LED5_ON  _IOR(IOCTL_TYPE, 7,int *)
#define LED5_OFF _IOR(IOCTL_TYPE, 8,int *)


#endif

dev_app.c:

/*************************************************************************
	> File Name: led_app.c
	> Author: xiuchengzhen
	> CSDN: xiuchengzhen.blog.csdn.net
	> Created Time: Fri 20 May 2022 01:26:49 AM PDT
 ************************************************************************/

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <time.h>

#include "dev_led.h"
	
int main(int argc, const char *argv[])
{
	int ret;

	if(argc < 2)
	{
		printf("Run without file!\n");
		return -1;
	}

	/* Open device file */
	int fd = open("/dev/dev_led", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	while(1)
	{
		ioctl(fd, LED2_ON, 0);
		sleep(1);
		ioctl(fd, LED2_OFF, 0);
		sleep(1);
		ioctl(fd, LED3_ON, 0);
		sleep(1);
		ioctl(fd, LED3_OFF, 0);
		sleep(1);
		ioctl(fd, LED4_ON, 0);
		sleep(1);
		ioctl(fd, LED4_OFF, 0);
		sleep(1);
		ioctl(fd, LED5_ON, 0);
		sleep(1);
		ioctl(fd, LED5_OFF, 0);
		sleep(1);
	}

	/* Turn off the device */
	close(fd);

}

That's it!

Tags: Linux Operation & Maintenance

Posted by phpsir on Sun, 22 May 2022 03:45:21 +0300