为树莓派4B编写platform驱动LED灯(采用DTS方式提供硬件信息)

最近学习了Linux platform driver子系统和dts的基本知识,写一个platform字符设备驱动来操纵GPIO控制LED灯,采用DTB文件来提供硬件信息,纯粹练手。

修改DTS文件

要修改DTS文件首先要确定自己使用的开发板所用DTB的版本,RPI 4B采用的是bcm2711-rpi-4-b.dts。下载一个在arch/arm/boot/dts/目录下有这个dts文件的linux内核源码,我下的是linux-5.10.31。通过查阅bcm2711的datasheet,找到和GPIO相关的物理地址,RPI4B的GPIO物理地址偏移在0xfe200000。本实现希望的是通过读写GPIO14来控制外接的LED灯,所以需要使用的寄存器有:

0xfe200004 --- GPFSEL1
0xfe20001c --- GPSET0
0xfe200028 --- GPCLR0

于是在dts文件的根节点下添加节点:

dts_led {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "dts_led";
        status = "okay";
        reg = < 0xfe200000 0x04 /*BCM_GPIO_BASE*/
                0xfe200004 0x04 /*GPFSEL1*/
                0xfe20001c 0x04 /*GPSET0*/
                0xfe200028 0x04 >; /*GPCLR0*/
};

编译需要自行配置交叉编译链,将编译得到的新的dtb替换原先的dtb。
若一切顺利,进入RPI 4B后在/proc/device-tree目录下可以看到dts_led节点。

编写驱动

驱动方面采用platform driver字符设备驱动,通过dtb文件的dts_led节点来获取硬件信息。
下面直接上代码

/*
        面向RPI的LED驱动,对GPIO Pin 14的读写
        platform driver版本 - dts
*/
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h> /*copy from ... copy to...*/
#include <linux/device.h> /*device_create class_create*/
#include <linux/slab.h> /*kzalloc kfree*/
#include <asm/io.h> /*readl writel ioremap*/
#include <linux/platform_device.h> /*platform_driver_register*/
#include <linux/of.h> /*struct device_node*/
#include <linux/of_address.h>

#define DTS_LED_MAJOR 233
#define GPFSEL1         (0x4/4)
#define GPSET0          (0x1c/4)
#define GPCLR0          (0x28/4)


static struct class *dts_led_class;
static struct device *dts_led_device;
static struct dts_led_dev *dts_led_devp;
static dev_t devno;


struct dts_led_dev {
        unsigned int __iomem *bcm_gpio_base;
        unsigned int *gpfsel1;
        unsigned int *gpset0;
        unsigned int *gpclr0;
        struct device_node *nd; /*存放设备节点信息*/
        struct cdev cdev;
};


/*修改GPFSEL1,将GPIO Pin14设置为output功能*/
static int dts_led_open(struct inode *inode, struct file *filp){
        unsigned int sel1_value = 0;
        sel1_value = readl(dts_led_devp->gpfsel1);

        /*将sel1寄存器的12-14位修改为001*/
        sel1_value |= (1<<12);
        sel1_value &= ~(1<<13);
        sel1_value &= ~(1<<14);

        writel(sel1_value, dts_led_devp->gpfsel1);
        printk(KERN_NOTICE "dts_led_open sel1_value = 0x%x\r\n", sel1_value);
        return 0;
}

static ssize_t dts_led_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){
        return 0;
}


static ssize_t dts_led_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos){
        unsigned char databuf[1];
        if(copy_from_user(databuf, buf, size)){ // 读取用户空间写入的数据
                printk(KERN_NOTICE "Failed to write platform_led!\r\n");
                return -EFAULT;
        }

        unsigned char ledstat = databuf[0];
        unsigned int set0_value = readl(dts_led_devp->gpset0);
        unsigned int clr0_value = readl(dts_led_devp->gpclr0);
        if(ledstat == 1){
                /*打开LED*/
                set0_value |= (1<<14);
                writel(set0_value, dts_led_devp->gpset0);
        }
        else{
                /*关闭LED*/
                clr0_value |= (1<<14);
                writel(clr0_value, dts_led_devp->gpclr0);
        }
        return 0;
}

struct file_operations dts_led_fops = {
        .owner = THIS_MODULE,
        .open = dts_led_open,
        .read = dts_led_read,
        .write = dts_led_write,
};


static int dts_led_probe(struct platform_device *pdev){
        printk(KERN_NOTICE "%s", "dts_led load!\r\n");
        int dts_led_major = DTS_LED_MAJOR;
        devno = MKDEV(dts_led_major, 0);

        int ret = 0;

        if(dts_led_major){
                ret = register_chrdev_region(devno, 1, "dts_led"); // 若platform_led的设备号已写死,向
内核申请devno设备号,名为platform_led
        }
        else{
                ret = alloc_chrdev_region(&devno, 0, 1, "dts_led"); // 若reg_led的设备号需要内核分配
                dts_led_major = MAJOR(devno);
        }

        if(ret<0){
                printk(KERN_NOTICE "Failed to assign the devno!\r\n");
                return ret;
        }

        dts_led_devp = kzalloc(sizeof(struct dts_led_dev), GFP_KERNEL);
        if(dts_led_devp == NULL){
                printk(KERN_NOTICE "Failed to assign mem for dts_led!\r\n");
                ret = -ENOMEM;
                goto fail;
        }

        /*从dtb文件读取硬件信息*/
        /*1. 获取设备节点*/
        dts_led_devp->nd = of_find_node_by_path("/dts_led");
        if(dts_led_devp->nd == NULL){
                printk(KERN_NOTICE "dts_led node can not found!\r\n");
                return -EINVAL;
        }
        else{
                printk(KERN_NOTICE "dts_led node found successfully!\r\n");
        }

        u32 regdata[8];
        /*2.获取reg属性内容*/
        ret = of_property_read_u32_array(dts_led_devp->nd, "reg", regdata, 8);
        if(ret<0){
                printk(KERN_NOTICE "reg property read Failed!\r\n");
                goto fail;
        }

        /*将物理地址映射到虚拟地址*/
        dts_led_devp->gpfsel1 = ioremap(regdata[2], regdata[3]);
        dts_led_devp->gpset0 = ioremap(regdata[4], regdata[5]);
        dts_led_devp->gpclr0 = ioremap(regdata[6], regdata[7]);

        /*现在已经成功获得有效的devno和cdev了,下面进行cdev的注册*/
        cdev_init(&dts_led_devp->cdev, &dts_led_fops);
        dts_led_devp->cdev.owner = THIS_MODULE;
        ret = cdev_add(&dts_led_devp->cdev, devno, 1);
        if(ret){
                printk(KERN_NOTICE "Failed to register the cdev to the kernel!\r\n");
                goto fail;
        }

        /*现在完成了设备的注册,下面进行class和device的创建*/
        dts_led_class = class_create(THIS_MODULE, "dts_led");
        dts_led_device = device_create(dts_led_class, NULL, devno, NULL, "dts_led0");
        return 0;
fail:
        unregister_chrdev_region(devno, 1);
        kfree(dts_led_devp);
        return ret;

}

static int dts_led_remove(struct platform_device *dev){
        device_destroy(dts_led_class, devno);
        class_unregister(dts_led_class);
        class_destroy(dts_led_class);
        cdev_del(&dts_led_devp->cdev);
        unregister_chrdev_region(devno, 1);
        kfree(dts_led_devp);
        printk(KERN_NOTICE "dts_led exit!\r\n");
        return 0;

}

static const struct of_device_id dts_led_of_match[] = {
        {.compatible = "dts_led"},
};


static struct platform_driver platform_led_driver = {
        .driver = {
                .name = "dts_led",
                .of_match_table = dts_led_of_match,
        },
        .probe = dts_led_probe,
        .remove = dts_led_remove,
};


static int __init dts_led_driver_init(void){
        return platform_driver_register(&platform_led_driver);
}

static void __exit dts_led_driver_exit(void){
        platform_driver_unregister(&platform_led_driver);
}

module_init(dts_led_driver_init);
module_exit(dts_led_driver_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Alex Tang");

测试程序

打开dts_led0设备文件进行读写,写入1将打开LED,写入0将关闭LED

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[]){
        if(argc < 2){
                fprintf(stderr, "Usage: %s [0/1]\n", argv[0]);
                exit(EXIT_FAILURE);
        }

        int fd = open("/dev/dts_led0", O_RDWR, S_IRUSR|S_IWUSR);

        unsigned char databuf[1];
        databuf[0] = atoi(argv[1]);

        if(write(fd, databuf, sizeof(databuf))){
                fprintf(stderr, "write dts_led Failed!\n");
                exit(EXIT_FAILURE);
        }
        return 0;

}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值