第一章 linux驱动之设备与驱动
第三章 linux驱动之总线详解
1.DTS 、DTC 和 DTB的关系:dts是设备树的源文件,通过DTC转换中二进制的DTB文件。
2.DTS中描述了一些设备的资源,进程可以通过DTB文件直接读取设备中的一些信息。
2.通过of函数读取dts中的内容
设备节点结构体
struct device_node {
const char *name; /* 节点名字 */
const char *type; /* 设备类型 */
phandle phandle;
const char *full_name; /* 节点全名 */
struct fwnode_handle fwnode;
struct property *properties; /* 属性 */
struct property *deadprops; /* removed 属性 */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 子节点 */
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
};
属性结构体
struct property { char *name; /* 属性名字 */
int length; /* 属性长度 */
void *value; /* 属性值 */
struct property *next; /* 下一个属性 */
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
进程通过of函数将dts中某个节点或者某个内容返回到进程中定义的struct device_node或struct property 中。
inline struct device_node *of_find_node_by_path(const char *path)
struct device_node *of_get_parent(const struct device_node *node)//找父节点
property *of_find_property(const struct device_node *np,const char *name,int *lenp)//找名字叫name的属性值
int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)//out_value为读到的数组值
nt of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)//找属性为Proname的数组,//out_value为读到的数组值
void __iomem *of_iomap(struct device_node *np,int index)//index 为寄存器reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置 0。
/**********添加我们要查找的节点的代码*******************************/
// of_find_node_by_path函数通过路径查找节点
test_device_node = of_find_node_by_path("/test");
/**********获取compatible属性内容的代码****************************/
// of_find_property函数查找节点属性
test_node_property = of_find_property(test_device_node, "compatible", &size);
/**********获取reg属性内容的代码**********************************/
ret = of_property_read_u32_array(test_device_node, "reg", out_values, 2);
/*********************映射物理地址**************************/
vir_gpio_dr = of_iomap(pdev->dev.of_node, 0);
特别注意:
当驱动和设备配对成功时,会调用int beep_probe(struct platform_device *pdev),而pdev->dev.of_node也指向了设备树的某个节点,所以此时一般不需要调用of_find_node_by_path("/test")去返回节点。
/*
* @Author: topeet
* @Description: 设备树下的平台总线驱动,匹配成功后,去设备树文件中获取硬件信息,然后物理地址映射为虚拟地址,接下来可以注册字符设备和杂项设备
*/
#include
#include
#include
#include
#include
struct device_node *test_device_node;
struct property *test_node_property;
int size;
int ret = 0;
u32 out_values[2] = {0};
const char *str;
struct device_node *test_device_node;
struct property *test_node_property;
unsigned int *vir_gpio_dr;
int beep_probe(struct platform_device *pdev)
{ //匹配成功以后,进入到probe函数
printk("beep_probe\n");
/*********************方法一:直接获取节点**************************/
//printk("node name is %s\n",pdev->dev.of_node->name);
/*********************方法二:通过函数获取硬件资源**************************/
/* test_device_node = of_find_node_by_path("/test"); //获得设备节点
if(test_device_node == NULL){
printk("of_find_node_by_path is error \n");
return -1;
}*/
//将此段代码注释掉
test_node_property = of_find_property(test_device_node, "compatible", &size);
ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 2);
if (ret < 0)
{
printk("of_property_read_u32_array is error \n");
return -1;
}
printk("out_values[0] is 0x%08x\n", out_values[0]);
printk("out_values[1] is 0x%08x\n", out_values[1]);
/*********************映射物理地址**************************/
vir_gpio_dr = of_iomap(pdev->dev.of_node, 0);
if (vir_gpio_dr == NULL)
{
printk("of_iomap is error \n");
return -1;
}
printk("of_iomap is ok \n");
return 0;
}
int beep_remove(struct platform_device *pdev)
{
printk("beep_remove\n");
return 0;
}
const struct platform_device_id beep_idtable = {
.name = "beep_test",
};
const struct of_device_id of_match_table_test[] = {
{.compatible = "test1234"},
{},
};
struct platform_driver beep_driver = {
//3. 在beep_driver结构体中完成了beep_probe和beep_remove
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test",
.of_match_table = of_match_table_test //接下来我们改一下驱动,让他来匹配设备树里面test的节点
},
.id_table = &beep_idtable //4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配
};
static int beep_driver_init(void)
{
// 1.我们看驱动文件要从init函数开始看
int ret = 0;
//2. 在init函数里面注册了platform_driver
ret = platform_driver_register(&beep_driver);
if (ret < 0)
{
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok \n");
return 0;
}
static void beep_driver_exit(void)
{
platform_driver_unregister(&beep_driver);
printk("gooodbye! \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
pinctrl子系统就是imx6ul-pinfunc.h,该文件中设置了引脚寄存器的各种可能情况。
驱动开发人员在fsl,pins中设置了引脚的寄存器,就不用把引脚的相关寄存器都找到然后设置了。
/*添加rgb_led节点*/
rgb_led{
#address-cells = <1>;
#size-cells = <1>;
pinctrl-names = "default";
compatible = "fire,rgb-led";
pinctrl-0 = <&pinctrl_rgb_led>
rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>;//设置gpio1中的4号引脚为red。开发人员可以通过of_get_named_gpio得到此处的具体某个引脚。
rgb_led_green = <&gpio4 20 GPIO_ACTIVE_LOW>;
rgb_led_blue = <&gpio4 19 GPIO_ACTIVE_LOW>;
status = "okay";
};
//在iomuxc节点中添加pinctrl子节点
iomuxc节点:
汇总所需引脚的配置信息
pinctrl子系统预存iomux节点信息
相当于一个寄存器配置器的集合。
所有pin的控制器都属于这一节点。
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
/*----------新添加的内容--------------*/
pinctrl_rgb_led:rgb_led{
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x000010B1
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B1
MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x000010B1
>;
};
GPIO子系统是指一套可以读取设备树引脚信息的函数
通过of_get_named_gpio得到rgb_led_red,rgb_led_green,rgb_led_blue
int of_get_named_gpio(struct device_node *np,const char *propname,int index)
gpio_request(leddev.led0, "led0");//gpio_request 函数用于申请一个 GPIO 管脚
gpio_direction_output(leddev.led0, 1);
gpio_set_value(leddev.led0, 0);
gpio_direction_input(key.irqkeydesc[0].gpio);
gpio_get_value(keydesc->gpio); gpio_free(leddev.led0);
tatic int led_probe(struct platform_device *pdv)
{
unsigned int register_data = 0; //用于保存读取得到的寄存器值
int ret = 0; //用于保存申请设备号的结果
printk(KERN_EMERG "\t match successed \n");
/*------------------第一部分---------------*/
/*获取RGB的设备树节点*/
rgb_led_device_node = of_find_node_by_path("/rgb_led");
if(rgb_led_device_node == NULL)
{
printk(KERN_EMERG "\t get rgb_led failed! \n");
}
/*------------------第二部分---------------*/
rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);
rgb_led_green = of_get_named_gpio(rgb_led_device_node, "rgb_led_green", 0);
rgb_led_blue = of_get_named_gpio(rgb_led_device_node, "rgb_led_blue", 0);
printk("rgb_led_red = %d,\n rgb_led_green = %d,\n rgb_led_blue = %d,\n", rgb_led_red,\
rgb_led_green,rgb_led_blue);
/*------------------第三部分---------------*/
gpio_direction_output(rgb_led_red, 1);
gpio_direction_output(rgb_led_green, 1);
gpio_direction_output(rgb_led_blue, 1);
使用函数of_get_named_gpio函数获取GPIO号,读取成功则返回读取得到的GPIO号。 “rgb_led_red”指定GPIO
的名字,这个参数要与rgb_led设备树节点中GPIO属性名对应, 参数“0”指定引脚索引,我们的设备树中一条属性中
只定义了一个引脚,我们只有一个所以设置为0。
/*------------------第四部分---------------*/
/*---------------------注册 字符设备部分-----------------*/
//第一步
//采用动态分配的方式,获取设备编号,次设备号为0,
//设备名称为rgb-leds,可通过命令cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc led_devno\n");
goto alloc_err;
}
//第二步
//关联字符设备结构体cdev与文件操作结构体file_operations
led_chr_dev.owner = THIS_MODULE;
cdev_init(&led_chr_dev, &led_chr_dev_fops);
//第三步
//添加设备至cdev_map散列表中
ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
//第四步
/*创建类 */
class_led = class_create(THIS_MODULE, DEV_NAME);
/*创建设备*/
device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
return 0;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(led_devno, DEV_CNT);
printk("\n error! \n");
alloc_err:
return -1;
}