目录
一、工程环境
修改makefile
detled.c文件
修改设备树文件
将使用到的外设添加进设备树,这里继续用led为例
设备节点添加到比较明显的地方,也就是一级节点
编译复制查看
把dtb文件复制到tftpboot目录下,启动开发板,检查设备属性文件
添加的属性都存在,下面开始编写驱动
二、驱动框架和编译
1、 注册字符设备、添加字符设备
....
#define DTSLED_CNT 1 /*设备号个数*/
#define DTSLED_NAME "dtsled" /*设备号名字*/
/*dtsled设备结构体*/
struct dtsled_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*字符设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
}dtsled; /*设备*/
/*字符设备操作集*/
static const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
};
/*入口函数*/
static int __init dtsled_init(void){
int ret =0;
/*注册字符设备*/
/*1、申请设备号*/
dtsled.major = 0; /*设备号由内涵分配*/
if(dtsled.major){ /*定义了设备号*/
dtsled.devid = MKDEV(dtsled.major,0);/*构建设备号*/
/*分配指定的设备编号*/
ret = register_chrdev_region(dtsled.devid,DTSLED_CNT,DTSLED_NAME);
}else{ /*没有给定设备号*/
/*申请设备号*/
ret = alloc_chrdev_region(&dtsled.devid,0,DTSLED_CNT,DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);/*提取主设备号*/
dtsled.minor = MINOR(dtsled.devid);/*提取次设备号*/
}
if(ret < 0){
goto fail_devid;
}
/*2、添加字符设备*/
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev,&dtsled_fops);/*初始化的 cdev 结构体变量*/
ret = cdev_add(&dtsled.cdev,dtsled.devid,DTSLED_CNT);/*添加字符设备*/
if(ret < 0){
goto fail_cdev;
}
printk("dtsled_init");
return 0;
fail_cdev:/*假如注册设备号失败就注销释放*/
unregister_chrdev_region(dtsled.devid,DTSLED_CNT);
fail_devid:
return ret;
}
/*出口函数*/
static void __exit dtsled_exit(void){
/*删除字符设备*/
cdev_del(&dtsled.cdev);
/*注销释放设备号*/
unregister_chrdev_region(dtsled.devid,DTSLED_CNT);
}
....
2、添加自动创建设备节点、设置私有数据
/*dtsled设备结构体*/
struct dtsled_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*字符设备*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
}dtsled; /*设备*/
static int dtsled_open(struct inode *inode, struct file *filp){
filp->private_data = &dtsled;/*设置私有数据*/
return 0;
}
static ssize_t dtsled_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt){
/*取出私有数据*/
struct dtsled_dev *dev =(struct dtsled_dev *)filp->private_data;
return 0;
}
static int dtsled_release(struct inode *inode, struct file *filp){
/*取出私有数据*/
struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
return 0;
}
/*字符设备操作集*/
static const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.write = dtsled_write,
.open = dtsled_open,
.release = dtsled_release,
};
/*入口函数*/
static int __init dtsled_init(void){
int ret =0;
/*注册字符设备*/
/*1、申请设备号*/
dtsled.major = 0; /*设备号由内涵分配*/
if(dtsled.major){ /*定义了设备号*/
dtsled.devid = MKDEV(dtsled.major,0);/*构建设备号*/
/*分配指定的设备编号*/
ret = register_chrdev_region(dtsled.devid,DTSLED_CNT,DTSLED_NAME);
}else{ /*没有给定设备号*/
/*申请设备号*/
ret = alloc_chrdev_region(&dtsled.devid,0,DTSLED_CNT,DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);/*提取主设备号*/
dtsled.minor = MINOR(dtsled.devid);/*提取次设备号*/
}
if(ret < 0){
goto fail_devid;
}
/*2、添加字符设备*/
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev,&dtsled_fops);/*初始化的 cdev 结构体变量*/
ret = cdev_add(&dtsled.cdev,dtsled.devid,DTSLED_CNT);/*添加字符设备*/
if(ret < 0){
goto fail_cdev;
}
/*3、自动创建设备节点*/
dtsled.class = class_create(THIS_MODULE,DTSLED_NAME);
if(IS_ERR(dtsled.class)){
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
dtsled.device = device_create(dtsled.class,NULL,dtsled.devid,NULL,DTSLED_NAME);
if(IS_ERR(dtsled.device)){
ret = PTR_ERR(dtsled.device);
goto fail_device;
}
printk("dtsled_init");
return 0;
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid,DTSLED_CNT);
fail_devid:
return ret;
}
/*出口函数*/
static void __exit dtsled_exit(void){
/*删除字符设备*/
cdev_del(&dtsled.cdev);
/*注销释放设备号*/
unregister_chrdev_region(dtsled.devid,DTSLED_CNT);
/*摧毁设备*/
device_destroy(dtsled.class,dtsled.devid);
/*摧毁类*/
class_destroy(dtsled.class);
}
到这就完成基本的驱动框架
编译验证
cat /proc/devices
3、获取设备树属性内容
①获取status属性数据
在入口函数在后面添加
/* 获取设备树属性内容*/
dtsled.nd = of_find_node_by_path("/myled");/*从路径找节点*/
if(dtsled.nd == NULL){
ret = -EINVAL;
goto fail_findnd;
}
/*获取属性*/
ret = of_property_read_string(dtsled.nd,"status",&str);
if(ret < 0){
goto fail_rs;
}else{
printk("status=%s\r\n",str);
}
printk("dtsled_init\r\n");
return 0;
fail_rs:
fail_findnd:
device_destroy(dtsled.class,dtsled.devid);
编译验证
②、获取compatible属性数据
ret = of_property_read_string(dtsled.nd,"compatible",&str);
if(ret < 0){
goto fail_rs;
}else{
printk("compatible=%s\r\n",str);
}
ret = of_property_read_u32_array(dtsled.nd,"reg",regdata,10);
if(ret < 0){
goto fail_rs;
③、获取reg属性数据
ret = of_property_read_u32_array(dtsled.nd,"reg",regdata,10);
if(ret < 0){
goto fail_rs;
}else{
printk("reg data:\r\n");
for(i=0; i<10;i++){
printk("%#X ",regdata[i]);
}
printk("\r\n");
}
printk("dtsled_init\r\n");
return 0;
fail_rs:
fail_findnd:
device_destroy(dtsled.class,dtsled.devid);
编译验证
三、添加led灯驱动
#define DTSLED_CNT 1 /*设备号个数*/
#define DTSLED_NAME "dtsled" /*设备号名字*/
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/*dtsled设备结构体*/
struct dtsled_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*字符设备*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct device_node *nd; /*设备节点*/
}dtsled; /*设备*/
/*LED打开/g关闭*/
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
static int dtsled_open(struct inode *inode, struct file *filp){
filp->private_data = &dtsled;/*设置私有数据*/
return 0;
}
static ssize_t dtsled_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt){
/*取出私有数据*/
struct dtsled_dev *dev =(struct dtsled_dev *)filp->private_data;
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
return -EFAULT;
}
led_switch(databuf[0]);
return 0;
}
static int dtsled_release(struct inode *inode, struct file *filp){
/*取出私有数据*/
struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
return 0;
}
/*字符设备操作集*/
static const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.write = dtsled_write,
.open = dtsled_open,
.release = dtsled_release,
};
/*入口函数*/
static int __init dtsled_init(void){
int ret =0;
const char *str;
u8 i=0;
u32 regdata[10];
unsigned int val=0;
/*注册字符设备*/
/*1、申请设备号*/
dtsled.major = 0; /*设备号由内核分配*/
if(dtsled.major){ /*定义了设备号*/
dtsled.devid = MKDEV(dtsled.major,0);/*构建设备号*/
/*分配指定的设备编号*/
ret = register_chrdev_region(dtsled.devid,DTSLED_CNT,DTSLED_NAME);
}else{ /*没有给定设备号*/
/*申请设备号*/
ret = alloc_chrdev_region(&dtsled.devid,0,DTSLED_CNT,DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);/*提取主设备号*/
dtsled.minor = MINOR(dtsled.devid);/*提取次设备号*/
}
if(ret < 0){
goto fail_devid;
}
/*2、添加字符设备*/
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev,&dtsled_fops);/*初始化的 cdev 结构体变量*/
ret = cdev_add(&dtsled.cdev,dtsled.devid,DTSLED_CNT);/*添加字符设备*/
if(ret < 0){
goto fail_cdev;
}
/*3、自动创建设备节点*/
dtsled.class = class_create(THIS_MODULE,DTSLED_NAME);
if(IS_ERR(dtsled.class)){
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
dtsled.device = device_create(dtsled.class,NULL,dtsled.devid,NULL,DTSLED_NAME);
if(IS_ERR(dtsled.device)){
ret = PTR_ERR(dtsled.device);
goto fail_device;
}
/* 获取设备树属性内容*/
dtsled.nd = of_find_node_by_path("/myled");/*从路径找节点*/
if(dtsled.nd == NULL){
ret = -EINVAL;
goto fail_findnd;
}
/*获取属性*/
ret = of_property_read_string(dtsled.nd,"status",&str);
if(ret < 0){
goto fail_rs;
}else{
printk("status=%s\r\n",str);
}
ret = of_property_read_string(dtsled.nd,"compatible",&str);
if(ret < 0){
goto fail_rs;
}else{
printk("compatible=%s\r\n",str);
}
/*获取reg属性*/
ret = of_property_read_u32_array(dtsled.nd,"reg",regdata,10);
if(ret < 0){
goto fail_rs;
}else{
printk("reg data:\r\n");
for(i=0; i<10;i++){
printk("%#X ",regdata[i]);
}
printk("\r\n");
}
/*LED灯初始化*/
/* 1、寄存器地址映射 ,要对应reg属性值*/
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]);
/* 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);
printk("dtsled_init\r\n");
return 0;
fail_rs:
fail_findnd:
device_destroy(dtsled.class,dtsled.devid);
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid,DTSLED_CNT);
fail_devid:
return ret;
}
/*出口函数*/
static void __exit dtsled_exit(void){
unsigned int val=0;
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/*删除字符设备*/
cdev_del(&dtsled.cdev);
/*注销释放设备号*/
unregister_chrdev_region(dtsled.devid,DTSLED_CNT);
/*摧毁设备*/
device_destroy(dtsled.class,dtsled.devid);
/*摧毁类*/
class_destroy(dtsled.class);
printk("dtsled_exit\r\n");
}
/*注册和卸载驱动*/
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");
编译验证
继续利用之前编写的ledAPP进行测试开光灯
开
关
of_iomap函数
在上面代码中, 在获取reg属性到获取寄存器地址可以用of_iomap函数替换,如下
#if 0
ret = of_property_read_u32_array(dtsled.nd,"reg",regdata,10);
if(ret < 0){
goto fail_rs;
}else{
printk("reg data:\r\n");
for(i=0; i<10;i++){
printk("%#X ",regdata[i]);
}
printk("\r\n");
}
/*LED灯初始化*/
/* 1、寄存器地址映射 ,要对应reg属性值*/
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]);
#endif
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);
使用该函数需要头文件 #include <linux/of_address.h>,原型如下
of_iomap(struct device_node *device, int index)
参数device为设备节点, index为reg 属性中要完成内存映射的段
就像本章reg从0到4共有5段,reg 属性的值一般是以(address, length)为一段,所以是五段
用of_iomap函数代换上面代码的效果是一样的。