led驱动代码中无法给其他开发板重用,编程依据不清晰,如下,修改后尽量在代码中不直接修改寄存器。
把编程依据写到设备树中
减少垃圾代码
减轻驱动开发工作量
驱动代码和设备信息分离
参考Open Fireware设计
用来记录硬件平台中各种硬件设备的属性信息
设备树文件一共两种源文件:
xxxxx.dts dts是device tree source的缩写(类似于c语言中.c文件)
xxxxx.dtsi dtsi是device tree source include的缩写,意味着这样源文件用于被dts文件包含用(类似于c语言中的.h文件)
实际使用时,需要把dts文件编译成对应的二进制文件(.dtb文件,dtb是device tree binary的缩写 )便于运行时存放在内存加快读取信息的速度
节点语法:
[label:] node-name[@unit-address] {
[properties definitions];
[child nodes];
};
label: 可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用 &label 来表示引用指定节点 node-name: 节点名 unit-address: 设备地址,一般填写该设备寄存器组或内存块的首地址 properties definitions:属性定义 child nodes:子节点
属性语法:
[label:] property-name = value;
[label:] property-name;
属性可以无值 有值的属性,可以有三种取值: 1. arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示,空格分隔),用尖括号表示(< >) 2. string(字符串), 用双引号表示(" ") 3. bytestring(1个或多个字节,空格分隔),用方括号表示([]) 4. 用,分隔的多值
根节点表示整块开发板的信息
#address-cells // 在子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在子节点的reg属性中, 使用多少个u32整数来描述大小(size)
compatible // 哪些平台兼容性 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台
model // 哪款板子 比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子
region(内存区域块大小)
所有设备树文件的必需节点,它定义了系统物理内存的 layout。一般描述计算机的外接内存(SDRAM)多大,起始地址是多少。内部内存一般都有专门用途(SRAM ROM)
device_type = "memory";
reg //用来指定内存的地址、大小
传递内核启动时使用的参数parameter
bootargs //字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样
/cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu
所以 /cpus 中有以下2个属性:
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0
一般我们上面使用的都是soc芯片厂家的二次移植,作为修改。
我们自己来写,一般需要写属性来描述外设的起始地址、大小多大,中断号,GPIO引脚等。
数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点
如:
pic@10000000 {
phandle = <1>; //除了节点原名pic,节点别名,地址等,还可以使用节点标识来代表节点,数字不能重复
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
};
reg属性:表示内存区域region,语法:
reg = ;
// 第二个内存卡起始地址和大小 第三个内存卡起始地址和大小
#address-cells:reg属性中, 使用多少个u32整数来描述地址(address),语法:
#address-cells = <数字>;
#size-cells:reg属性中, 使用多少个u32整数来描述大小(size),语法:
#size-cells = <数字>;
驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,语法:
compatible = "字符串1","字符串2",...;
在平台属性中还会介绍
a. 中断控制器节点用的属性:
interrupt-controller 一个无值空属性用来声明这个node接收中断信号,表示该节点是一个中断控制器
#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符
b. 中断源设备节点用的属性:
interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,语法:
interrupt-parent = <引用某中断控制器节点>
interrupts 一个中断标识符列表,表示每一个中断输出信号,语法:
interrupts = <中断号 触发方式>
1 low-to-high 上升沿触发
2 high-to-low 下降沿触发
4 high level 高电平触发
8 low level 低电平触发
gpio也是最常见的IO口,常用的属性有:
a. 对于GPIO控制器:
gpio-controller,无值空属性,用来说明该节点描述的是一个gpio控制器
#gpio-cells,用来表示要用几个cell描述一个 GPIO引脚
b. 对于GPIO使用者节点:
gpio使用节点的属性
xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>
工作模式:
1 低电平有效 GPIO_ACTIVE_HIGH
0 高电平有效 GPIO_ACTIVE_LOW
一般来说,每一种设备的节点属性设置都会有一些套路,比如可以设置哪些属性?属性值怎么设置?那怎么知道这些套路呢,有两种思路:
抄类似的dts,比如我们自己项目的平台是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts这类相近的dts
查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法
struct device_node 对应设备树中的一个节点 struct property 对应节点中一个属性
/**
include/of.h
of_find_node_by_path - 通过路径查找指定节点
@path - 带全路径的节点名,也可以是节点的别名
成功:得到节点的首地址;失败:NULL
*/
struct device_node * of_find_node_by_path(const char *path);
of代表openfire
/*
include/of.h
of_find_property - 提取指定属性的值
@np - 设备节点指针
@name - 属性名称
@lenp - 属性值的字节数
成功:属性值的首地址;失败:NULL
*/
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
/**
* include/of_gpio.h
* of_get_named_gpio - 从设备树中提取gpio口
* @np - 设备节点指针
* @propname - 属性名
* @index - gpio口引脚标号
* 成功:得到GPIO口编号;失败:负数,绝对值是错误码
*/
int of_get_named_gpio(struct device_node *np, const char *propname, int index);
/*
功能:获得设备树中的中断号并进行映射
参数:node:设备节点
index:序号
返回值:成功:中断号 失败:错误码
*/
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
of_property_read_string
/*
of_property_read_string - 提取字符串(属性值)
@np - 设备节点指针
@propname - 属性名称
@out_string - 输出参数,指向字符串(属性值)
成功:0;失败:负数,绝对值是错误码
*/
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
读数值
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)
判断属性是否存在
int of_property_read_bool(const struct device_node *np,const char *propname)
读数组(如<>或者[])
int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)
int gpio_request(unsigned gpio,const char *label)
功能:其实就是让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数
void gpio_free(unsigned gpio)
功能:去除本设备对该GPIO的占用标记,表示本设备向内核归还对该GPIO引脚的使用权,此后其它设备可占用该GPIO引脚
int gpio_direction_input(unsigned gpio)
int gpio_direction_output(unsigned gpio,int value)
int gpio_get_value(unsigned gpio)
int gpio_set_value(unsigned gpio,int value)
在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)
..../linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
fs4412-leds {
compatible = "fs4412,led2-5"; //兼容性
led2-gpio = <&gpx2 7 0>; //GPIO接口设置
led3-gpio = <&gpx1 0 0>;
led4-gpio = <&gpf3 4 0>;
led5-gpio = <&gpf3 5 0>;
};
在linux内核源码的顶层目录下执行:make dtbs (生成对应的dtb文件)
cp ?????.dtb /tftpboot
编写驱动代码:
a. 通过本设备在设备树中的路径找到对应节点(struct device_node类型的地址值)
b. 调用 of_get_named_gpio 函数得到某个GPIO的编号(一般在init中完成)
c. struct leddev结构体中记录所有用到的GPIO编号(一般在init中完成)
d. 使用某个GPIO引脚前需先通过gpio_request函数向内核申请占用该引脚,不用该引脚时可通过gpio_free归还给内核(申请一般在init中完成,归还在exit中完成)
e. 通过gpio_direction_input和gpio_direction_output函数来设置某个GPIO的作用(一般在init中完成)
f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平
g. 通过gpio_set_value函数可以改变某个GPIO引脚的电平
找到开发板之前对应的dts文件
在根节点下面增加设备树led信息
重新编译make dtbs,生成dtb并拷贝到tftp目录下
leddrv_dt.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "leddrv.h"
int major = 11;
int minor = 0;
int myled_num = 1;
//不会对寄存器直接操作,改为对设备编号操作
struct myled_dev
{
struct cdev mydev;
unsigned int led2gpio;
unsigned int led3gpio;
unsigned int led4gpio;
unsigned int led5gpio;
};
struct myled_dev *pgmydev = NULL;
int myled_open(struct inode *pnode,struct file *pfile)
{
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct myled_dev,mydev));
return 0;
}
int myled_close(struct inode *pnode,struct file *pfile)
{
return 0;
}
void led_on(struct myled_dev *pmydev,int ledno)
{
switch(ledno)
{
case 2:
gpio_set_value(pmydev->led2gpio,1);
break;
case 3:
gpio_set_value(pmydev->led3gpio,1);
break;
case 4:
gpio_set_value(pmydev->led4gpio,1);
break;
case 5:
gpio_set_value(pmydev->led5gpio,1);
break;
}
}
void led_off(struct myled_dev *pmydev,int ledno)
{
switch(ledno)
{
case 2:
gpio_set_value(pmydev->led2gpio,0);
break;
case 3:
gpio_set_value(pmydev->led3gpio,0);
break;
case 4:
gpio_set_value(pmydev->led4gpio,0);
break;
case 5:
gpio_set_value(pmydev->led5gpio,0);
break;
}
}
long myled_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
struct myled_dev *pmydev = (struct myled_dev *)pfile->private_data;
if(arg < 2 || arg > 5)
{
return -1;
}
switch(cmd)
{
case MY_LED_ON:
led_on(pmydev,arg);
break;
case MY_LED_OFF:
led_off(pmydev,arg);
break;
default:
return -1;
}
return 0;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = myled_open,
.release = myled_close,
.unlocked_ioctl = myled_ioctl,
};
//申请gpio编号,init中会调用
void request_leds_gpio(struct myled_dev *pmydev,struct device_node *pnode)
{
pmydev->led2gpio = of_get_named_gpio(pnode,"led2-gpio",0);
gpio_request(pmydev->led2gpio,"led2");
pmydev->led3gpio = of_get_named_gpio(pnode,"led3-gpio",0);
gpio_request(pmydev->led3gpio,"led3");
pmydev->led4gpio = of_get_named_gpio(pnode,"led4-gpio",0);
gpio_request(pmydev->led4gpio,"led4");
pmydev->led5gpio = of_get_named_gpio(pnode,"led5-gpio",0);
gpio_request(pmydev->led5gpio,"led5");
}
void set_leds_gpio_output(struct myled_dev *pmydev)
{
gpio_direction_output(pmydev->led2gpio,0);
gpio_direction_output(pmydev->led3gpio,0);
gpio_direction_output(pmydev->led4gpio,0);
gpio_direction_output(pmydev->led5gpio,0);
}
void free_leds_gpio(struct myled_dev *pmydev)
{
gpio_free(pmydev->led2gpio);
gpio_free(pmydev->led3gpio);
gpio_free(pmydev->led4gpio);
gpio_free(pmydev->led5gpio);
}
int __init myled_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
struct device_node *pnode = NULL;
pnode = of_find_node_by_path("/fs4412-leds");
if(NULL == pnode)
{
printk("find node by path failed\n");
return -1;
}
/*申请设备号*/
ret = register_chrdev_region(devno,myled_num,"myled");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,myled_num,"myled");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
pgmydev = (struct myled_dev *)kmalloc(sizeof(struct myled_dev),GFP_KERNEL);
if(NULL == pgmydev)
{
unregister_chrdev_region(devno,myled_num);
printk("kmalloc failed\n");
return -1;
}
memset(pgmydev,0,sizeof(struct myled_dev)); //这里的memset并非c库的函数,而是内核自己实现的memset函数
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,myled_num);
/*ioremap*/
request_leds_gpio(pgmydev,pnode);
/*con-register set output*/
set_leds_gpio_output(pgmydev);
return 0;
}
void __exit myled_exit(void)
{
dev_t devno = MKDEV(major,minor);
/*iounmap*/
free_leds_gpio(pgmydev);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,myled_num);
kfree(pgmydev);
pgmydev = NULL;
}
MODULE_LICENSE("GPL");
module_init(myled_init);
module_exit(myled_exit);
查看头文件
修改Makefile
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
CONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o
obj-m += leddrv.o
obj-m += leddrv_dt.o
endif
编译
拷贝ko文件到rootfs下
testled_app.c
#include
#include
#include
#include
#include
#include
#include "leddrv.h"
int main(int argc,char *argv[])
{
int fd = -1;
int onoff = 0;
int no = 0;
if(argc < 4)
{
printf("The argument is too few\n");
return 1;
}
sscanf(argv[2],"%d",&onoff);
sscanf(argv[3],"%d",&no);
if(no < 2 || no > 5)
{
printf("len-no is invalid\n");
return 2;
}
fd = open(argv[1],O_RDONLY);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 3;
}
if(onoff)
{
ioctl(fd,MY_LED_ON,no);
}
else
{
ioctl(fd,MY_LED_OFF,no);
}
close(fd);
fd = -1;
return 0;
}
编译testled_app,把testled_app放入rootfs中
启动开发板insmod插入模块测试