Linux驱动之设备树添加LED驱动
本章我们就开始第一个基于设备树的 Linux 驱动实验,还是点LED灯。
本章重点考察如下知识点:
①、在 imx6ul-14x14-evk.dtsi 文件中创建相应的设备节点。
②、编写驱动程序,获取设备树中的相关属性值。
③、使用获取到的有关属性值来初始化 LED 所使用的 GPIO。
一、开发环境
- CPU:IMX6ULL
- 内核版本:Linux-5.19
二、设备树中添加LED节点
在根节点“/”下创建一个名为“dts_led”的子节点,打开 imx6ul-14x14-evk.dtsi 文件,在根节点“/”最后面输入如下所示内容:
dts_led {
#address-cells = <1>;
#size-cells = <1>;
compatible = "dts_led";
status = "okay";
reg = <
0x020C406C 0x04 /* CCM_CCGR1_REGBASE */
0x020E0068 0x04 /* SW_MUX_GPIO1_IO03_REGBASE */
0x020E02F4 0x04 /* SW_PAD_GPIO1_IO03_REGBASE */
0x0209C000 0x04 /* GPIO1_DR_BASE */
0x0209C004 0x04 /* GPIO1_GDIR_BASE */
>;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 第 2、 3 行,属性#address-cells 和#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。
- 第 4 行,属性 compatbile 设置dts_led 节点兼容性为“dts_led”。
- 第 5 行,属性 status 设置状态为“okay”。
- 第 6~10 行, reg 属性,非常重要!reg 属性设置了驱动里面所要使用的寄存器物理地址,比如第 6 行的“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地址为 0X020C406C,长度为 4 个字节。
设备树修改完成以后输入如下命令重新编译一下 imx6ul-14x14-evk.dtsi:
make dtbs
- 1
编译完成以后得到 imx6ull-toto.dtb,使用新的 imx6ull-toto.dtb 启动Linux 内核。Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有“dts_led”这个节点,结果如下所示:
/ # ls /proc/device-tree/
#address-cells clock-osc pmu
#size-cells compatible regulator-can-3v3
aliases cpus regulator-peri-3v3
backlight-display dts_led regulator-sd1-vmmc
chosen memory@80000000 soc
clock-cli model sound-wm8960
clock-di0 name spi4
clock-di1 panel timer
/ #
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果没有“dts_led”节点的话请重点查看下面两点:
①、检查设备树修改是否成功,也就是 dts_led 节点是否为根节点“/”的子节点。
②、检查是否使用新的设备树启动的 Linux 内核。
可以进入/sys/firmware/devicetree/base/的 dts_led 目录中,查看一下都有哪些属性文件,结果如下所示:
/ # ls /sys/firmware/devicetree/base/dts_led/
#address-cells compatible reg
#size-cells name status
/ #
- 1
- 2
- 3
- 4
三、驱动程序编写
/************************************************************
* Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
* Description:
* Version: 1.0
* Autor: toto
* Date: Do not edit
* LastEditors: Seven
* LastEditTime: Do not edit
************************************************************/
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#define DTS_LED_CNT 1 /* 设备号数量 */
#define DTS_LED_NAME "dts_led" /* 设备名字 */
#define LED_ON 1 /* 开打 */
#define LED_OFF 0 /* 关灯 */
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *VM_CCM_CCGR1;
static void __iomem *VM_SW_MUX_GPIO1_IO03;
static void __iomem *VM_SW_PAD_GPIO1_IO03;
static void __iomem *VM_GPIO1_DR;
static void __iomem *VM_GPIO1_GDIR;
/* dts_led设备结构体 */
struct dts_led_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
};
struct dts_led_dev dtsled;
/*
* @Brief LED打开/关闭
* @Call Internal or External
* @Param state: 1-打开 0-关闭
* @Note NOne
* @RetVal 无
*/
void led_switch(u8 state)
{
u32 val = 0;
if(state == LED_ON)
{
val = readl(VM_GPIO1_DR);
val &= ~(1 << 3);
writel(val, VM_GPIO1_DR);
}
else if(state == LED_OFF)
{
val = readl(VM_GPIO1_DR);
val |= (1 << 3);
writel(val, VM_GPIO1_DR);
}
else
{
printk("%s state:%d invalid\n", __func__, state);
}
}
/*
* @Brief 打开设备
* @Call Internal or External
* @Param inode:
* @Param filp:设备文件
* @Note NOne
* @RetVal 0:成功 其他值:失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
/* 设置私有数据 */
filp->private_data = &dtsled;
return 0;
}
/*
* @Brief 从设备读数据
* @Call Internal or External
* @Param filp:要打开的设备文件描述符
* @Param buf:返回给用户空间的数据地址
* @Param cnt:要读取的数据长度
* @Param offt:相对于文件首地址的偏移
* @Note NOne
* @RetVal 读取的字节数,若为负值,表示读失败
*/
static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @Brief 写数据到设备
* @Call Internal or External
* @Param filp:要打开的设备文件描述符
* @Param buf:要写入设备的数据地址
* @Param cnt:要写入的数据长度
* @Param offt:相对于文件首地址的偏移
* @Note NOne
* @RetVal 写入的字节数,若为负值,表示写失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retval;
unsigned char databuf[1];
unsigned char ledstat;
retval = copy_from_user(databuf, buf, cnt);
if(retval < 0)
{
printk("%s copy_from_user failed\n", __func__);
return -EFAULT;
}
ledstat = databuf[0];
if(ledstat != LED_ON && ledstat != LED_OFF)
{
printk("%s ledstat:%d invalid\n", __func__, ledstat);
return -1;
}
/*打开、关闭LED*/
led_switch(ledstat);
return 0;
}
/*
* @Brief 关闭/释放设备
* @Call Internal or External
* @Param inode:
* @Param filp:要关闭的设备文件描述符
* @Note NOne
* @RetVal NOne
*/
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @Brief 驱动入口函数
* @Call Internal or External
* @Param None
* @Note NOne
* @RetVal NOne
*/
static int __init led_init(void)
{
u32 val = 0, regdata[14];
int ret;
const char *str;
struct property *proper;
/* 1.获取设备树中的属性 */
dtsled.nd = of_find_node_by_path("/dts_led");
if(dtsled.nd == NULL)
{
printk("dts_led node can not found\n");
return -EINVAL;
}
/* 2.获取 compatible 属性 */
proper = of_find_property(dtsled.nd, "compatible", NULL);
if(proper == NULL)
{
printk("compatible property not find\n");
return -EINVAL;
}
/* 3.获取 status 属性 */
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0)
{
printk("status property read failed\n");
return -EINVAL;
}
/* 4.获取 reg 属性内容 */
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
if(ret < 0)
{
printk("reg property read failed\n");
return -EINVAL;
}
/* 5.物理地址映射为虚拟地址 */
VM_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
VM_SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
VM_SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
VM_GPIO1_DR = of_iomap(dtsled.nd, 3);
VM_GPIO1_GDIR = of_iomap(dtsled.nd, 4);
/* 6.使能 GPIO1 时钟 */
val = readl(VM_CCM_CCGR1);
val |= (3 << 26);
writel(val, VM_CCM_CCGR1);
/* 设置 GPIO1_IO03 的复用功能, 将其复用为
* GPIO1_IO03, 最后设置IO属性
*/
writel(5, VM_SW_MUX_GPIO1_IO03);
/* 寄存器 GPIO1_IO03 设置 IO 属性 */
writel(0x10B0, VM_SW_PAD_GPIO1_IO03);
/* 寄存器 GPIO1_IO03 为输出功能 */
val = readl(VM_GPIO1_GDIR);
val |= (1 << 3);
writel(val, VM_GPIO1_GDIR);
/* 默认关闭LED */
val = readl(VM_GPIO1_DR);
val |= (1 << 3);
writel(val, VM_GPIO1_DR);
/* 创建设备号 */
if(dtsled.major) /* 定义了设备号 */
{
dtsled.devid = MKDEV(dtsled.major, 0);
register_chrdev_region(dtsled.devid, DTS_LED_CNT,
DTS_LED_NAME);
}
else
{
/* 申请设备号 */
alloc_chrdev_region(&dtsled.devid, 0, DTS_LED_CNT,
DTS_LED_NAME);
/* 获取主设备号 */
dtsled.major = MAJOR(dtsled.devid);
/* 获取次设备号 */
dtsled.minor = MINOR(dtsled.devid);
}
printk("%s new_chrdev major:%d minor:%d\n", __func__,
dtsled.major, dtsled.minor);
/* 初始化cdev */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
/* 添加一个cdev */
cdev_add(&dtsled.cdev, dtsled.devid, DTS_LED_CNT);
/* 创建类 */
dtsled.class = class_create(THIS_MODULE, DTS_LED_NAME);
if(IS_ERR(dtsled.class))
{
return PTR_ERR(dtsled.class);
}
/* 创建设备 */
dtsled.device = device_create(dtsled.class, NULL,
dtsled.devid, NULL, DTS_LED_NAME);
if(IS_ERR(dtsled.device))
{
return PTR_ERR(dtsled.device);
}
return 0;
}
/*
* @Brief 驱动出口函数
* @Call Internal or External
* @Param None
* @Note NOne
* @RetVal NOne
*/
static void __exit led_exit(void)
{
/* 取消寄存器物理地址映射为虚拟地址的转换 */
iounmap(VM_CCM_CCGR1);
iounmap(VM_SW_MUX_GPIO1_IO03);
iounmap(VM_SW_PAD_GPIO1_IO03);
iounmap(VM_GPIO1_DR);
iounmap(VM_GPIO1_GDIR);
/* 注销字符设备 */
/* 删除cdev */
cdev_del(&dtsled.cdev);
/* 释放分配的设备号 */
unregister_chrdev_region(dtsled.devid, DTS_LED_CNT);
device_destroy(dtsled.class, dtsled.devid);
class_destroy(dtsled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 在设备结构体 dtsled_dev 中添加了成员变量 nd, nd 是 device_node 结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加 device_node 指针变量来存放这个节点。
- 通过 of_find_node_by_path 函数得到 dts_led 节点,后续其他的 OF 函数要使用 device_node。
- 通过 of_find_property 函数获取 dts_led 节点的 compatible 属性,返回值为property 结构体类型指针变量, property 的成员变量 value 表示属性值。
- 通过 of_property_read_string 函数获取 dts_led 节点的 status 属性值。
- 通过 of_property_read_u32_array 函数获取 dts_led 节点的 reg 属性所有值,并且将获取到的值都存放到 regdata 数组中。
- 使用 of_iomap 函数一次性完成读取 reg 属性以及内存映射, of_iomap 函数是设备树推荐使用的 OF 函数。
四、测试程序编写
测试程序具体代码如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define LEDON 1
#define LEDOFF 0
/*
* @Brief main 主程序
* @Call Internal or External
* @Param argc:
* @Param argv:
* @Note NOne
* @RetVal 0-成功;其他-失败
*/
int main(int argc, char *argv[])
{
int fd, retval;
char *filename;
unsigned char databuf[1];
if(argc != 3)
{
printf("argc != 3\n");
return -1;
}
filename = argv[1];
/*打开驱动文件*/
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("open filename:%d failed\n", filename);
return -1;
}
/* 要执行的操作:打开或关闭 */
databuf[0] = atoi(argv[2]);
retval = write(fd, databuf, sizeof(databuf));
if(retval < 0)
{
printf("write file:%s failed\n", filename);
}
/*关闭文件*/
close(fd);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
五、运行验证
开发板上电,将dts_led.ko 和 dts_led_app 这两个文件拷贝到 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 目录中,输入如下命令加载 dts_led.ko 驱动模块:
/lib/modules/5.19.0-g794a2f7be62d-dirty # ls
chrdev_driver.ko led_chrdev.ko modules.dep
dts_led.ko modules.alias modules.symbols
/lib/modules/5.19.0-g794a2f7be62d-dirty # insmod dts_led.ko
[ 132.383363] dts_led: loading out-of-tree module taints kernel.
[ 132.394631] led_init new_chrdev major:242 minor:0
/lib/modules/5.19.0-g794a2f7be62d-dirty #
- 1
- 2
- 3
- 4
- 5
- 6
- 7
驱动加载成功以后就可以使用 dts_led_app 软件来测试驱动是否工作正常,输入如下命令打开LED 灯:
# ./dts_led_app /dev/dts_led 1 //打开 LED 灯
- 1
输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭 LED 灯:
# ./dts_led_app /dev/dts_led 0 //关闭 LED 灯
- 1
输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否熄灭。如果要卸载驱动的话输入如下命令即可:
rmmod dts_led.ko
- 1
- 分享
- 举报
-
易百纳用户01878 2023-08-15 13:17:28回复 举报好
-
浏览量:927次2024-02-21 17:08:25
-
2024-01-02 18:00:05
-
浏览量:1050次2023-06-12 14:34:15
-
浏览量:1483次2023-05-16 14:10:29
-
浏览量:1627次2023-11-15 11:12:53
-
浏览量:3538次2023-11-10 15:46:07
-
浏览量:2782次2017-12-13 10:09:35
-
浏览量:1203次2023-08-10 11:15:37
-
浏览量:1866次2023-05-16 15:43:05
-
浏览量:1021次2023-05-06 13:40:15
-
浏览量:1363次2023-08-10 14:03:57
-
浏览量:2649次2020-07-20 19:03:11
-
浏览量:2928次2020-07-20 20:14:23
-
浏览量:2600次2020-07-21 18:59:28
-
浏览量:3315次2020-07-20 19:36:33
-
浏览量:1585次2019-11-20 09:10:33
-
浏览量:5190次2021-07-26 17:38:48
-
浏览量:2481次2020-08-18 20:05:11
-
浏览量:4327次2020-08-30 12:27:19
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖

Debug






举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明