STM32mp157字符设备实验—pinctrl和gpio子系统实验

使用的开发板为正点原子STM32mp157。

参考资料为正点原子驱动开发教程,以及STM32mp芯片手册等。


前面学习了如何直接操作寄存器来点亮LED灯。

但是每次都去翻芯片手册查询寄存器的物理地址未免太过于繁琐,终于,这里引入了pinctrlgpio子系统这个概念,瞬间为我们省去了很大的麻烦!

针对我们之前的程序,只需要对初始化led那里的程序进行修改,就能够实现这个实验了。

话不多说,上流程。

这次我们依然是从设备树中获取属性。

在获取属性之前,我们需要定义一个设备节点。

为了,方便,我定义在了根文件下。

//dada2023.3.3
	gpioled{
		compatible = "dada,led";//属性,用来匹配驱动
		status = "okay";        //使能
		led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;//定义pi0,并设置为低电平有效

	};

但是在定义的时候,我出现了疑问,这个低电平有效到底定义在这里是什么意思?

因为在后面我们可以利用函数对gpio口的值进行设置,也会在设置是输出和输入的时候定义默认值。

在我的理解范围内,这里只是为了方便移植的时候,后来的人能够看懂这个引脚是低电平有效还是高电平有效,从而能作出修改。

但是还是存在着一些些疑问。

如果有人有更深的理解,可以讨论一下。


打开开发板,查看一下设备节点是否成功被创建。

820d581eb7ec438f896f88b655bdbb08.png

发现成功被创建,说明咱们设备树修改的没有问题。

之后回到我们的驱动文件,之前做的实验把代码拷贝过来,从获取属性这里开始修改。

获取节点不变。

//获取设备节点
    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的编号了。

  1. 获取gpio编号
  2. 用获得的gpio编号,申请使用gpio
  3. 设置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


最后,测试一下。0cce0d6490ba45dfa71acd0cc57bd50d.png

 可以实现灯的亮灭。

成功!!!

继续加油!


有问题可以在评论区留言啊,我们一起进步!

还有什么你不理解我没注意到的地方,都可以指出来,查漏补缺。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值