树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)

设备树

  • 1.设备树
    • 1.1设备树定义
    • 1.2 常用名词解释
    • 1.3设备树基本框架
    • 1.4设备树语法
      • 1.4.1节点
      • 1.4.2节点名称
      • 1.4.3节点别名
      • 1.4.4节点引用
      • 1.4.5属性
    • 1.5在设备树中添加自定义节点
      • 1.5.1节点查看
      • 1.5.2在设备树中添加自定义节点
      • 1.5.3 编译节点
      • 1.5.4运行编译生成的bcm2711-4-b.dtb
      • 1.5.5 查看节点是否创建成功
  • 2.bcm2711-rpi-4-b.dts文件修改内容
  • 3编写driver.c
  • 4.app.c
  • 5.运行情况

1.设备树

1.1设备树定义

设备树是一种描述硬件资源的数据结构,它通过bootloader将硬件资源传给内核,使得内核和硬件资源描述相对独立。

1.2 常用名词解释

<1>DT:Device Tree //设备树
<2>FDT:Flattened Device Tree //展开设备树|开放固件,设备树起源于OF,所以我们在设备树中可以看到很多有of字母的函数
<3>device tree source(dts) //设备树代码
<4>device tree source include(dtsi)//更通用的设备树代码,也就是相同芯片但不同平台都可以使用的代码
<5>device tree blob(dtb)//DTS编译后得到的DTB文件
<6>device tree compiler(dtc)//设备树编译器

1.3设备树基本框架

<1>设备树从根节点开始,每个设备都是一个节点。
<2>节点和节点之间可以互相嵌套,形成父子关系。
<3>设备的属性用key-value对(键值对)来描述,每个属性用分号结束

1.4设备树语法

1.4.1节点

节点就好比一颗大树,从树的主干开始,然后有一节一节的树枝。根节点就相当于大树的树干

/{
};

树枝就相当于设备树的子节点

/{		//根节点
		node1//子节点node1
		{
		};
		node2//子节点2
		{
		};
};//分号

树枝上的树枝就相当于设备树上的子子节点

/{ 		//根节点
		node1//子节点node1
		{
			child-node1//子子节点
			{
			};
		};
		node2//子节点node2
		{
		};
}

1.4.2节点名称

节点的命名有一个固定的格式

格式:<名称>[@<设备地址>]
<名称>节点的名称不是任意起的,一般要体现设备的类型,比如网口,应该命名为ethernet,
<设备地址>用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,他主要用来区分用。

注意事项:
<1>同一级的节点只要地址不一样,名字是可以不唯一的。
<2>设备地址是一个可选选项,可以不写。但为了容易区分和理解,一般是都写的。

1.4.3节点别名

给一个节点取小名,相当于 typedef unsigned int uint;
节点别名格式
节点名称别名:节点名称
例:

uart8:serial@02288000

uart8就是这个节点名称的别名,serial@02288000就是节点名称。

1.4.4节点引用

一般往一个节点里面添加内容的时候,不会直接把添加的内容写到节点里面,而是通过节点的引用来添加。
例:

&uart8{
			pinctrl-names = "default";
			pinctrl-0 = <&pinctrl_uart8>;
			status = "okay";
}

&uart8表示引用节点别名为uart8的节点,并往这个节点添加{}里面的内容。
注意事项
编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写,使用引用可以避免移植者四处找节点。如dts和dtsi里面都有根节点,但最终会合并成一个根节点。

1.4.5属性

(1) reg属性
reg属性用来描述一个设备的地址范围。
格式:

reg=

例子:

serial@02288000{
					reg = <101F2000 0x1000>;
			};

其中101F2000就是起始地址,0x1000就是长度。
(2) #address-cells 和 #size-cells属性
#address-cells用来设置子节点中reg地址的数量
#size-cells 用来设置子节点中reg地址长度的数量。
举例:

cpu{
		#address-cells = <1>;
		#size-cells = <1>;
		serial@101F2000{
				compatible = "serial";
				reg = <0x101F2000 0x1000 
							0x101f3000  0x0010>;
			};
};

其中#address-cells 和#size-cells均为1,也就是说我们子节点里面的reg属性里这个寄存器组的起始地址只有一个,长度也只有一个。所以分配了2个地址范围,第一个101F2000是起始地址,0x1000是长度。第二个0x101f3000是起始地址,0x0010是长度
(3) compatible 属性
compatible 是一个字符串列表,可以在代码中进行匹配。
举例:

compatible = "led"

这个就相当于platform总线下的device.c里面编写进行匹配的属性。
(4) status 属性
status 属性的值类型是字符串,常用的一个是okay,表示设备可以正常使用,一个是disable,表示设备不能正常使用。

1.5在设备树中添加自定义节点

1.5.1节点查看

方法1

cd /proc/device-tree/
ls
//查看设备树的model、compatible
cat model
cat compatible 

方法二

cd /sys/firmware/devicetree/base/
ls

1.5.2在设备树中添加自定义节点

根目录位于/home/kun/build_new/linux_kernel/arch/arm/boot/dts
/home/kun/build_new是我存放linux内核的路径
打开bcm2711-rpi-4-b.dts
找到根节点,在根节点末尾添加节点
树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)_第1张图片
节点代码如下

test1:test@0xfe200000{
		#address-cells = <1>;
		#size-cells = <1>;

		compatible = "test";

		reg = <0xfe200000 0x00000004>;
		status = "okay";
	};

树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)_第2张图片

1.5.3 编译节点

在内核目录下执行指令
注:我是64位,所以ARCH=arm64,32位的话,ARCH=arm32,然后编译器我是给他重命名为了aarch64-linux-gnu- 这个编译器为编译内核时使用的编译器

make  -j5 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs

在这里插入图片描述
编译后会给出修改后的bcm2711-rpi-4-b.dtb文件位置
(/home/kun/build_new/linux_kernel/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dts)

1.5.4运行编译生成的bcm2711-4-b.dtb

将编译生成的bcm2711-rpi-4-b.dtb拷贝到树莓派的/boot目录下
然后reboot即可。

1.5.5 查看节点是否创建成功

具体指令如下

cd /proc/device-tree
ls
cd test@0xfe200000/
ls
cat name
cat status

树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)_第3张图片

2.bcm2711-rpi-4-b.dts文件修改内容

在根节点下增加以下内容
树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)_第4张图片

gpio4:gpio4@0xfe200000{
		#address-cells = <1>;
		#size-cells = <1>;

		compatible = "test";
		reg = <0xfe200000 0x00000004
			   0xfe20001c 0x00000004
			   0xfe200028 0x00000004>;
		status = "okay";
	};

3编写driver.c

#include <linux/init.h>  
#include <linux/module.h> 
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>

unsigned int *vir_gpio4_dr=NULL;
unsigned int *vir_gpio4_h=NULL;
unsigned int *vir_gpio4_l=NULL;

u32 out_values[6]; //用于存储从设备树获取的reg数据

struct resource *gpio4_dir;
struct resource *gpio4_h;
struct resource *gpio4_l;


const struct of_device_id of_match_table_test[] = {
	{ .compatible = "test_led"},
	{}

};

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
	char kbuf[64] = {0};
	if ( copy_from_user( kbuf, ubuf, size) != 0)
	{
		printk( "copy_from_user error\n ");
		return -1;
	}
	printk( "kbuf is %s\n ", kbuf);
	*vir_gpio4_dr |= (001<<(3*4));
	if( kbuf[0] == 1)
	{
		
		*vir_gpio4_h |=(1<<4);
	}
	else if( kbuf[0]==0)
	{
		*vir_gpio4_l |=(1<<4);
	}
	return 0;
}

int misc_release( struct inode *inode, struct file *file)
{
	printk( "hello misc_relaease bye bye \n ");
	return 0;
}

int misc_open( struct inode *inode, struct file *file)
{
	printk( "hello misc_open\n ");
	return 0;
}
//文件操作集
struct file_operations misc_fops = {
		.owner = THIS_MODULE, 
		.open = misc_open,
		.release = misc_release,
		.write = misc_write,
		};
//miscdevice 结构体
struct miscdevice misc_dev = {
	.minor = MISC_DYNAMIC_MINOR, 
	.name = "hello_misc",
	.fops = &misc_fops,
};


int led_probe( struct platform_device *pdev)
{
	//5匹配成功后进入probe函数
	int ret;

	printk( "led_probe\n");

	//注册杂项设备
	ret = misc_register( &misc_dev); 
	if (ret < 0)
	{
		printk( "misc registe is error \n");
	}
	printk( "misc registe is succeed \n");

	//获取设备树里面reg属性的值
	
	ret = of_property_read_u32_array( pdev->dev.of_node, "reg", out_values, 6);
	
	if( ret<0){
		printk("of_property_read_u32_array is error \n");
		return -1;
	}

	//对物理地址进行虚拟映射
	
	vir_gpio4_dr = ioremap( out_values[0],out_values[1]);
	if( vir_gpio4_dr== NULL )
	{
		printk( "gpio4dr ioremap error\n");
		return EBUSY;
	}
	
	vir_gpio4_h = ioremap( out_values[2],out_values[3]);
	if( vir_gpio4_h== NULL)
	{
		printk( "gpio4h ioremap error\n");
		return EBUSY;
	}

	vir_gpio4_l = ioremap( out_values[4],out_values[5]);
	if( vir_gpio4_l == NULL)
	{
		printk( "gpio4l ioremap error\n");
		return EBUSY;
	}
	printk( "gpio ioremap success\n");

	return 0;

}

int led_remove( struct platform_device *pdev)
{
	printk("led_remove\n");
	return 0;
}
struct platform_driver led_driver ={   
	//3.在led_driver结构体中完成了led_probe和led_remove

	.probe = led_probe,
	.remove = led_remove,

	//4.在driver结构体里面填写匹配名字,让他匹配设备树里面的led_test节点
	.driver = {
		.owner = THIS_MODULE,
		.name = "led_test",                //匹配名字,匹配成功后进入probe函数
		.of_match_table = of_match_table_test//优先匹配of_match_table
	},
};
  
static int led_driver_init( void)
{
	//1.看驱动文件先从init函数看
	int ret = 0;
	//2.在init函数里面注册了platform_driver
	ret = platform_driver_register( &led_driver);
	if( ret<0)
	{
		printk( "platform_driver_register error \n");
	}
	printk( "platform_driver_register ok \n");
	return 0;
}

static void led_driver_exit(void)
{
	misc_deregister( &misc_dev); //卸载杂项设备
	printk( "misc gooodbye! \n");
	iounmap( vir_gpio4_dr);
	iounmap( vir_gpio4_h);
	iounmap( vir_gpio4_l);

	// platform 驱动卸载
	platform_driver_unregister( &led_driver);
	printk( "goodbye! \n");
}

module_init( led_driver_init);
module_exit( led_driver_exit);

MODULE_LICENSE( "GPL");

4.app.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
	int fd;
	fd = open("/dev/hello_misc",O_RDWR);//打开设备节点
	if(fd < 0)
	{
		perror("open error \n");
		return fd;
	}
	buf[0]=atoi(argv[1]);
	write(fd,buf,sizeof(buf)); //向内核层写数据
	close(fd);
	return 0;
}

5.运行情况

树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)_第5张图片
树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)_第6张图片

你可能感兴趣的:(linux,linux,驱动开发)