目录
设备树
DTC将设备树源码DTS编译成DTB。
每一个厂家的设备树写法都有不同之处,是一种ASCII文本文件,我们也只需在厂家提供的设备树文件上修改即可。
标准属性
compatible 属性
字符串
compatible = "arm,cortex-a7";
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
compatible 一般用于驱动和设备匹配。
model 属性
描述设备模块信息
model = "wm8960-audio";
status 属性
描述设备状态
值 | 描述 |
---|---|
“okay” | 表明设备是可操作的。 |
“disabled” | 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。 |
“fail” | 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。 |
“fail-sss” | 含义和“fail”相同,后面的 sss 部分是检测到的错误内容。 |
reg 属性
32 位无符号整数
reg = <address1 length1 address2 length2 address3 length3……>
reg = <0x02020000 0x4000>;
此 reg 属性中 address=0x02020000,length=0x4000。查阅《I.MX6ULL 参考手册》可知,I.MX6ULL 的 UART1 寄存器首地址为 0x02020000,但是 UART1 的地址长度(范围)并没 有 0x4000 这么多,这里我们重点是获取 UART1 寄存器首地址。
#address-cells 和#size-cells 属性
#address-cells 表明 address 这个数据所占用的字长,
#size-cells 表明 length 这个数据所占用的字长
ranges 属性
值 | 描述 |
---|---|
child-bus-address | 子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。 |
parent-bus-address | 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。 |
length | 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。 |
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换, 对于我们所使用的 I.MX6ULL来说,子地址空间和父地址空间完全相同
设备树查找匹配设备
向节点追加或修改内容
多个“/”根节点会合并。
label: node-name@unit-address
cpu0:cpu@0
可以直接通过&label 来访问这个节点,比如通过&cpu0 就可以访问“cpu@0”这个节点
&cpu0{
/* 要追加或修改的内容 */
};
led设备树驱动
修改设备树文件
在根节点中创建设备节点
/* flynnsin led不使用pinctrl 和 gpio子系统设备树节点 */
alphaled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "flynnsin-led";
status = "okay";
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
编写led驱动
创建结构体
/* dtsled设备结构体 */
struct dtsled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
**struct device_node *nd; /* 设备节点 */**
};
/* 获取设备树中的属性数据 */
/* 1、获取设备节点:alphaled */
/* 寻找设备树是否有该节点 */
dtsled.nd = of_find_node_by_path("/alphaled");
if(dtsled.nd == NULL) {
printk("alphaled node nost find!\r\n");
return -EINVAL;
} else {
printk("alphaled node find!\r\n");
}
/* 2、获取compatible属性内容 */
/* 匹配设备树节点,也就是通过比对compatible属性 */
proper = of_find_property(dtsled.nd, "compatible", NULL);
if(proper == NULL) {
printk("compatible property find failed\r\n");
} else {
printk("compatible = %s\r\n", (char*)proper->value);
}
/* 3、获取status属性内容 */
/* 看该节点是否使能 */
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0){
printk("status read failed!\r\n");
} else {
printk("status = %s\r\n",str);
}
/* 4、获取reg属性内容 */
/* 读取里面寄存器地址及字节 */
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
if(ret < 0) {
printk("reg property read failed!\r\n");
} else {
u8 i = 0;
printk("reg data:\r\n");
for(i = 0; i < 10; i++)
printk("%#X ", regdata[i]);
printk("\r\n");
}
/* 初始化LED */
/* 两种方式映射寄存器地址 */
#if 0
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif
pinctrl 和 gpio 子系统led设备树驱动
对于linux里实现了很多子系统来方便驱动开发
修改设备树文件
添加 pinctrl 节点
在 iomuxc 节点添加pinctrl节点信息
/* flynnsin led pinctrl节点*/
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
linux\arch\arm\boot\dts\imx6ul-pinfunc.h
#define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x0068 0x02F4 0x0000 0x5 0x0
<mux_reg conf_reg input_reg mux_mode input_val>
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清楚以前的设置 */
val |= (3 << 26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* 3、设置GPIO1_IO03的复用功能,将其复用为
* GPIO1_IO03,最后设置IO属性。
*/
writel(5, SW_MUX_GPIO1_IO03);
/*寄存器SW_PAD_GPIO1_IO03设置IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 5、默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
是不是非常熟悉,除了使能时钟,时钟可能在别的地方使能了,设置复用,对应设置mux_reg 寄存器值(也就是模式)mux_mode 为5,然后就是设置电气特性0x10B0。
添加设备节点
也就是包含pinctal节点,设置端口及电平有效值。
/* flynnsin led 使用子系统设备节点 */
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "flynnsin-led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
检查 PIN 是否被其他外设使用
这一点非常重要!!!,把别人实现的注释掉。
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
/* MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 */
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
>;
};
&tsc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
/* xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; */
measure-delay-time = <0xffff>;
pre-charge-time = <0xfff>;
status = "okay";
};
编写led驱动
创建结构体
/* gpioled设备结构体 */
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* led所使用的GPIO编号 */
};
获取设备树节点数据
/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
printk("led-gpio num = %d\r\n", gpioled.led_gpio);
/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
操作gpio子系统
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
}
return 0;
测试
/proc # cd device-tree/
/sys/firmware/devicetree/base # ls
#address-cells interrupt-controller@00a01000
#size-cells memory
aliases model
**alphaled** name
backlight pxp_v4l2
chosen regulators
clocks reserved-memory
compatible soc
cpus sound
**gpioled** spi4
可以看到这两个节点。
测试没有子系统驱动
/lib/modules/4.1.15 # modprobe dtsled.ko
modprobe: module dtsled.ko not found in modules.dep
/lib/modules/4.1.15 # depmod
/lib/modules/4.1.15 # modprobe dtsled.ko
alphaled node find!
compatible = flynnsin-led
status = okay
reg data:
0X20C406C 0X4 0X20E0068 0X4 0X20E02F4 0X4 0X209C000 0X4 0X209C004 0X4
dtsled major=249,minor=0
/lib/modules/4.1.15 # ls /dev/dtsled -l
crw-rw---- 1 0 0 249, 0 Jan 1 01:27 /dev/dtsled
/lib/modules/4.1.15 # ./ledapp /dev/dtsled 1
/lib/modules/4.1.15 # ./ledapp /dev/dtsled 0
/lib/modules/4.1.15 # rmmod dtsled
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: G
测试子系统驱动
只有系统第一次启动测试需要输入depmod。
/lib/modules/4.1.15 # modprobe gpioled.ko
gpioled node find!
led-gpio num = 3
gpioled major=249,minor=0
/lib/modules/4.1.15 # ls /dev/gpioled -l
crw-rw---- 1 0 0 249, 0 Jan 1 01:31 /dev/gpioled
/lib/modules/4.1.15 # ./ledapp /dev/dtsled 1
file /dev/dtsled open failed!
/lib/modules/4.1.15 # ./ledapp /dev/gpioled 1
/lib/modules/4.1.15 # ./ledapp /dev/gpioled 0
/lib/modules/4.1.15 # rmmod gpioled.ko
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: G
修改驱动查找设备树节点名字
gpioled.nd = of_find_node_by_path("/flynnsinled");
if(gpioled.nd == NULL) {
printk("flynnsinled node not find!\r\n");
return -EINVAL;
} else {
printk("flynnsinled node find!\r\n");
}
编译测试
flynnsinled node not find!
flynnsinled node not find!
modprobe: can't load module gpioled.ko (gpioled.ko): Invalid argument