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 */
>;
}
- 第 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
编译完成以后得到 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
/ #
如果没有“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
/ #
三、驱动程序编写
/************************************************************
* 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");
- 在设备结构体 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;
}
五、运行验证
开发板上电,将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 #
驱动加载成功以后就可以使用 dts_led_app 软件来测试驱动是否工作正常,输入如下命令打开LED 灯:
# ./dts_led_app /dev/dts_led 1 //打开 LED 灯
输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭 LED 灯:
# ./dts_led_app /dev/dts_led 0 //关闭 LED 灯
输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否熄灭。如果要卸载驱动的话输入如下命令即可:
rmmod dts_led.ko
- 分享
- 举报
-
易百纳用户01878 2023-08-15 13:17:28回复 举报好
-
浏览量:512次2024-02-21 17:08:25
-
2024-01-02 18:00:05
-
浏览量:870次2023-06-12 14:34:15
-
浏览量:1365次2023-05-16 14:10:29
-
浏览量:1316次2023-11-15 11:12:53
-
浏览量:2555次2023-11-10 15:46:07
-
浏览量:2622次2017-12-13 10:09:35
-
浏览量:1028次2023-08-10 11:15:37
-
浏览量:918次2023-05-06 13:40:15
-
浏览量:1725次2023-05-16 15:43:05
-
浏览量:1112次2023-08-10 14:03:57
-
浏览量:1505次2019-11-20 09:10:33
-
浏览量:2516次2020-07-20 19:03:11
-
浏览量:2819次2020-07-20 20:14:23
-
浏览量:2484次2020-07-21 18:59:28
-
浏览量:3225次2020-07-20 19:36:33
-
浏览量:4969次2021-07-26 17:38:48
-
浏览量:2310次2020-08-18 20:05:11
-
浏览量:4059次2020-08-30 12:27:19
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
Debug
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明