需求
设备树(DTS)我们基本都是通过of_get_xxx这样的内核API来被动获取设备结点的属性值,很少会去修改它,如果要修改某个属性值,必须在各个设备驱动之前修改,一般在board级别代码修改。那为什么要去修改呢,什么需求才要这样做呢?我们碰到了这样的需求了。
是这样的,我们的项目中一个产品在PCB改版时,硬件资源发生了改变(由DTS来配置的),其中一个使能脚的控制改变了,既要保持对前一个版本(内核有硬件版本号全局变量)的兼容又要对新版本兼容,这就限制你不能孤立的修改DTS引脚配置。我们以前的方法是用全局资源硬件版本号在每个需要改变驱动中进行手动判断修改,这就造成了设备驱动的纯洁性较差,且不易直观知晓历史版本修改记录。
我们现在的做法就是这个文章说要写的,后来想到只在board级别代码中根据硬件版本号,统一修改了DTS属性,这样在后面驱动加载时,驱动获得就是对应版本的配置,并且驱动不用做任何修改。具体下面有个例子。
自定义DTS修改函数
在进入具体的例子之前先贴上自定义函数,基于内核基本的open firmware(of.h)函数,
//----------------------------------------------------------------------------
// Provide a group of functions to change the node/property values
//----------------------------------------------------------------------------
extern rwlock_t devtree_lock;//全局设备树链表结构的读写锁
/*根据设备节点的属性名,打印所有属性值*/
void of_print_property(const struct device_node *node, const char *property_name)
{
const __be32 *intspec;
int len;
int value;
int i;
if (!node || !property_name)
return;
intspec = of_get_property(node, property_name, &len);
if (intspec) {
for (i = 0; i < len/sizeof(*intspec); i++) {
value = be32_to_cpup(intspec+i);
printk(KERN_INFO " [%d] = %08x
", i, value);
}
}
}
/*根据设备节点将一个属性拷贝给另一个属性*/
bool of_copy_property_value(const struct device_node *node,
const char *target_property_name, const char *src_property_name)
{
unsigned long flags;
const void *value;
struct property *pp;
if (!node || !target_property_name || !src_property_name)
return false;
pp = of_find_property(node, target_property_name, NULL);
if (!pp) {
printk(KERN_ERR "%s: %s/%s not found
", __FUNCTION__, node->name, target_property_name);
return false;
}
value = of_get_property(node, src_property_name, NULL);
if (!value) {
printk(KERN_ERR "%s: %s/%s not found
", __FUNCTION__, node->name, src_property_name);
return false;
}
write_lock_irqsave(&devtree_lock, flags);
pp->value = (void*)value;
write_unlock_irqrestore(&devtree_lock, flags);
return true;
}
/*根据设备节点,修改属性的gpio值*/
bool of_set_property_gpio(const struct device_node *node,
const char *property_name, const int gpio)
{
unsigned long flags;
const __be32 *intspec;
__be32 *pgpio;
if (!node || !property_name)
return false;
intspec = of_get_property(node, property_name, NULL);
if (!intspec) {
printk(KERN_ERR "%s: %s/%s not found
", __FUNCTION__, node->name, property_name);
return false;
}
pgpio = (__be32*)intspec + 1;
write_lock_irqsave(&devtree_lock, flags);
*pgpio = cpu_to_be32p(&gpio);
write_unlock_irqrestore(&devtree_lock, flags);
return true;
}
/*根据设备节点全路径,打印属性的值*/
void print_property(const char *node_name, const char *property_name)
{
struct device_node *node = of_find_node_by_path(node_name);
if (!node) {
printk(KERN_ERR "%s: node '%s' not found
", __FUNCTION__, node_name);
return;
}
of_print_property(node, property_name);
printk(KERN_INFO "%s: %s :
", __FUNCTION__, node_name);
of_node_put(node);
}
/*根据设备节点全路径,将一个属性拷贝给另一个属性*/
bool copy_property_value(const char *node_name,
const char *target_property_name, const char *src_property_name)
{
bool result;
struct device_node *node = of_find_node_by_path(node_name);
if (!node) {
printk(KERN_ERR "%s: node '%s' not found
", __FUNCTION__, node_name);
return false;
}
result = of_copy_property_value(node, target_property_name, src_property_name);
printk(KERN_INFO "%s: %s : (%s) -> (%s) %s
", __FUNCTION__, node_name,
src_property_name, target_property_name, result ? "successfully" : "failed");
of_node_put(node);
return result;
}
/*根据设备节点全路径,修改一个属性的gpio值*/
bool set_property_gpio(const char *node_name,
const char *property_name, const int gpio)
{
bool result;
struct device_node *node = of_find_node_by_path(node_name);
if (!node) {
printk(KERN_ERR "%s: node '%s' not found
", __FUNCTION__, node_name);
return false;
}
result = of_set_property_gpio(node, property_name, gpio);
printk(KERN_INFO "%s: %s : (%s) -> %d %s
", __FUNCTION__, node_name,
property_name, gpio, result ? "successfully" : "failed");
of_node_put(node);
return result;
}
我们对外只要用copy_property_value()和set_property_gpio()即可,但我们后来决定推荐用copy_property_value(),为什么?在DTS文件中添加新的属性,然后拷贝给原来的属性,DTS中还能更直观看见不同版本使其修改记录。
NFC例子
具体是NFC的enable引脚的例子。
以NFC为例,在PCB V5版本以前我们使用属性
nxp-pn544,nfc-enable= <&msmgpio 82 0x02> ;
即使用msmgpio 82脚且flag为2作为NFC的使能配置,到V6.1后使用
nxp-pn544,nfc-enable= <&pm8110_gpios 2 0x00>;
改用pm8110_gpios 的2脚且flag为0作为新使能配置,为了保持驱动不修改,那么就要不修改属性名而修改属性,即在board级代码中修改nxp-pn544,nfc-enable 的属性。
即需求为
属性
nxp-pn544,nfc-enable= <&msmgpio 82 0x02>;
要改为
nxp-pn544,nfc-enable= <&pm8110_gpios 2 0x00>;
我们使用copy_property_value函数,要修改2个地方
1.修改board-seuic-d500.dts
修改前:
&soc {
i2c@f9924000 {
...
nxp_pn544@28 {
compatible = "nxp,nfc-pn544";
reg = <0x28>;
interrupt-parent = <&msmgpio>;
interrupts = <84 0x2>;
nxp-pn544,nfc-enable = <&msmgpio 82 0x02>;
nxp-pn544,nfc-firm-gpio = <&msmgpio 94 0x02>;
nxp-pn544,nfc-int = <&msmgpio 84 0x02>;
nxp-pn544,nfc-power = <&msmgpio 42 0x02>;
};
...
}
}
修改后:
&soc {
i2c@f9924000 {
...
nxp_pn544@28 {
compatible = "nxp,nfc-pn544";
reg = <0x28>;
interrupt-parent = <&msmgpio>;
interrupts = <84 0x2>;
nxp-pn544,nfc-enable-v6.1 = <&pm8110_gpios 2 0x00>;
nxp-pn544,nfc-enable = <&msmgpio 82 0x02>;
nxp-pn544,nfc-firm-gpio = <&msmgpio 94 0x02>;
nxp-pn544,nfc-int = <&msmgpio 84 0x02>;
nxp-pn544,nfc-power = <&msmgpio 42 0x02>;
};
...
}
}
2.修改board-8610-gpiomux.c
void __init msm8610_init_board_d500(void)
{
if (pcb_version == 6) {
copy_property_value("/soc/i2c@f9924000/nxp_pn544@28",
"nxp-pn544,nfc-enable",
"nxp-pn544,nfc-enable-v6.1");
}
}
即设备D500 硬件版本为6时,将nxp-pn544,nfc-enable-v6.1属性拷贝给nxp-pn544,nfc-enable 用。
这样不管哪个版本都能使用正确的gpio配置了。