使用的开发板为正点原子STM32mp157。
参考资料为正点原子驱动开发教程,以及STM32mp芯片手册等。
前面学习了如何直接操作寄存器来点亮LED灯。
但是每次都去翻芯片手册查询寄存器的物理地址未免太过于繁琐,终于,这里引入了pinctrl和gpio子系统这个概念,瞬间为我们省去了很大的麻烦!
针对我们之前的程序,只需要对初始化led那里的程序进行修改,就能够实现这个实验了。
话不多说,上流程。
这次我们依然是从设备树中获取属性。
在获取属性之前,我们需要定义一个设备节点。
为了,方便,我定义在了根文件下。
//dada2023.3.3
gpioled{
compatible = "dada,led";//属性,用来匹配驱动
status = "okay"; //使能
led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;//定义pi0,并设置为低电平有效
};
但是在定义的时候,我出现了疑问,这个低电平有效到底定义在这里是什么意思?
因为在后面我们可以利用函数对gpio口的值进行设置,也会在设置是输出和输入的时候定义默认值。
在我的理解范围内,这里只是为了方便移植的时候,后来的人能够看懂这个引脚是低电平有效还是高电平有效,从而能作出修改。
但是还是存在着一些些疑问。
如果有人有更深的理解,可以讨论一下。
打开开发板,查看一下设备节点是否成功被创建。
发现成功被创建,说明咱们设备树修改的没有问题。
之后回到我们的驱动文件,之前做的实验把代码拷贝过来,从获取属性这里开始修改。
获取节点不变。
//获取设备节点
dtsled.nd = of_find_node_by_path("/gpioled");
if(dtsled.nd == NULL){
return -EINVAL;
}
定义一个字符指针。
const char *str;
获取compatible属性并匹配
//获取compatible属性
ret = of_property_read_string(dtsled.nd, "compatible", &str);
if (ret<0)
{
printk("Compatible find failed\r\n");
return -EINVAL;
}
else if (strcmp(str,"dada,led"))
{
printk("gpioled: Compatible match failed!\n");
}
看了一下of_property_read_string函数,发现用的是一个二级指针,于是对str进行取地址。至于为啥是二级指针,这个我还没有看,相当于传递的是一个字符指针的地址。
这里我对于strcmp函数的用法有点忘记了,所以专门去看了一下,简单的来说可以理解为两个字符相减,为0的话就相等,不为0就不相等。
获取status属性并匹配
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0){
printk("status read failed!\r\n");
return -EINVAL;
}else if(strcmp(str,"okay")){
return -EINVAL;
}
发现匹配上了,并且使能了之后,我们就可以获取gpio的编号了。
- 获取gpio编号
- 用获得的gpio编号,申请使用gpio
- 设置gpio模式
//获取设备树中的gpio属性,得到led的编号
dtsled.gpiodev = of_get_named_gpio(dtsled.nd,"led-gpio",0);
if(dtsled.gpiodev<0){
printk("Can't get led-gpio!\n");
return -EINVAL;
}
printk("led-gpio num = %d\r\n",dtsled.gpiodev);
//向gpio子系统申请使用gpio,使能gpio
ret = gpio_request(dtsled.gpiodev,"LED-GPIO");
if(ret){
printk(KERN_ERR "gpioled: Failed to request led-gpio\n");//疑问点
return ret;
}
//设置pi0为输出,并且默认上拉
ret = gpio_direction_output(dtsled.gpiodev,1);
if(ret < 0)
{
printk("Can't set gpio!\r\n");
}
到这里基本上LED的初始化就已经完成了。
附上完整代码。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#define LED_ON 1
#define LED_OFF 0
#define DTSLED_CNT 1
#define DTSLED_NAME "dtsled"
//定义dtsled设备结构体
struct dtsled_dev{
dev_t devid;//设备号
int major; //主设备号
int minor; //次设备号
struct cdev cdev;//注册函数
struct class *class;//节点
struct device *device;//设备
struct device_node *nd;//设备节点
int gpiodev;
};
struct dtsled_dev dtsled; //led设备
static int led_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &dtsled; /* 设置私有数据 */
return ret;
}
static int led_release(struct inode *inode, struct file *filp)
{
int ret = 0;
return ret;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt,
loff_t *offt)
{
int ret = 0;
return ret;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char databuf[1];//数据缓冲区
unsigned char ledstat;//灯的状态
struct dtsled_dev *dev = filp->private_data;//疑问点
ret = copy_from_user(databuf,buf,cnt);
if (ret < 0)
{
printk("kernel receviedata failed!\r\n");
ret = -EFAULT;
}
ledstat = databuf[0];//传进来灯的开关状态
if(ledstat == LED_ON) //开灯
{
gpio_set_value(dev->gpiodev,0);
}else if(ledstat == LED_OFF)//关灯
{
gpio_set_value(dev->gpiodev,1);
}
return 0;
}
const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
.read = led_read,
};
//入口函数
static int __init dtsled_init(void)
{
int ret = 0;
//u32 regdata[12];
const char *str;
//struct property *proper;
//获取设备树属性内容
//获取设备节点
dtsled.nd = of_find_node_by_path("/gpioled");
if(dtsled.nd == NULL){
return -EINVAL;
}
//获取compatible属性
ret = of_property_read_string(dtsled.nd, "compatible", &str);
if (ret<0)
{
printk("Compatible find failed\r\n");
return -EINVAL;
}
else if (strcmp(str,"dada,led"))
{
printk("gpioled: Compatible match failed!\n");
}
//获取status属性
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0){
printk("status read failed!\r\n");
return -EINVAL;
}else if(strcmp(str,"okay")){
return -EINVAL;
}
//获取设备树中的gpio属性,得到led的编号
dtsled.gpiodev = of_get_named_gpio(dtsled.nd,"led-gpio",0);
if(dtsled.gpiodev<0){
printk("Can't get led-gpio!\n");
return -EINVAL;
}
printk("led-gpio num = %d\r\n",dtsled.gpiodev);
//向gpio子系统申请使用gpio,使能gpio
ret = gpio_request(dtsled.gpiodev,"LED-GPIO");
if(ret){
printk(KERN_ERR "gpioled: Failed to request led-gpio\n");//疑问点
return ret;
}
//设置pi0为输出,并且默认上拉
ret = gpio_direction_output(dtsled.gpiodev,1);
if(ret < 0)
{
printk("Can't set gpio!\r\n");
}
//注册字符
//构造设备号
if(dtsled.major)//定义了主设备号
{
dtsled.devid = MKDEV(dtsled.major,0);
ret = register_chrdev_region(dtsled.devid,DTSLED_CNT,DTSLED_NAME);
if(ret<0){
printk("dtsled driver register failed!\r\n");
goto free_gpio;
}
}else {
ret = alloc_chrdev_region(&dtsled.devid,0,DTSLED_CNT,DTSLED_NAME);//没有定义就从内核申请
if(ret<0){
printk("dtsled driver register failed!\r\n");
goto free_gpio;
}
}
//添加字符设备
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev,&led_fops);//初始化
ret = cdev_add(&dtsled.cdev,dtsled.devid,DTSLED_CNT);
if(ret<0){
goto del_unregister;
}
//自动创建设备节点
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if (IS_ERR(dtsled.class)) {
pr_err("QAT: class_create failed for adf_ctl\n");
goto err_cdev_del;
}
//创建设备
dtsled.device = device_create(dtsled.class, NULL,
dtsled.devid,
NULL, DTSLED_NAME);
if (IS_ERR( dtsled.device)) {
pr_err("QAT: failed to create device\n");
goto destroy_class;
}
printk("led_init\r\n");
return 0;
destroy_class:
class_destroy(dtsled.class);
err_cdev_del:
cdev_del(&dtsled.cdev);
del_unregister:
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
free_gpio:
gpio_free(dtsled.gpiodev);
return -EIO;
}
//出口函数
static void __exit dtsled_fini(void)
{
device_destroy(dtsled.class,dtsled.devid);//删掉设备
cdev_del(&dtsled.cdev);//注销字符设备
class_destroy(dtsled.class);//删除设备节点
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);//注销设备号
gpio_free(dtsled.gpiodev);//释放gpio
printk("led_exit\r\n");
}
//注册驱动和卸载驱动
module_init(dtsled_init);
module_exit(dtsled_fini);
// 添加作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dada");
MODULE_INFO(intree, "Y");
记得加头文件!!!
在做的过程中,有一个小知识点。
.和->的区别是什么?
通过我的学习,简单总结了一下。
(*a).b=a->b
上面这句是最为精髓的地方。
简单来说,如果a是一个结构体指针,那么它是可以利用->的
如果a是一个结构体,那它是可以直接.的
当然,这些都是最基本的理解。
详细可以参考这片文章:http://t.csdn.cn/RFsQ2
最后,测试一下。
可以实现灯的亮灭。
成功!!!
继续加油!
有问题可以在评论区留言啊,我们一起进步!
还有什么你不理解我没注意到的地方,都可以指出来,查漏补缺。