程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux设备树-LED字符设备驱动

 ----------------------------------------------------------------------------------------------------------------------------

内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

linux驱动移植-LED字符设备驱动中,我们直接使用ioremap函数进行内存映射,得到GPIO有关寄存器的虚拟地址,然后操作寄存器对应的虚拟地址控制GPIO的目的。

这一节,我们将采用设备树向LED驱动传递GPIO相关寄存器的物理地址,然后利用of函数从设备树中获取需要的属性值,用获取到的属性值来初始化GPIO。

具体步骤如下:

  • 在dts文件中添加LED设备节点;
  • 在LED字符设备驱动中利用of函数获取设备树中相关属性;
  • 使用获取到的属性值初始化用到的GPIO;

一、修改设备树

1.1 硬件接线

查看Mini2440原理图、S3C2440数据手册,了解如何点亮LED。在Mini2440裸机开发之点亮LED中我们已经介绍了Mini2440 LED1~LED4的接线方式,以及寄存器的设置,这里简单说一下,就不具体介绍了;

  • LED1~LED4对应引脚GPB5~GPB8,以点亮LED1为例;
  • 配置控制寄存器GPBCON(0x56000010)的bit[11:10]=01,使GPB5引脚为输出模式;
  • 配置数据寄存器GPBDAT(0x56000014)的bit5=0,使GPB5引脚输出低电平;

1.2 修改设备树s3c2440-smdk2440.dts

新增led节点,用于描述一种名为 "mini2440-led" 的LED设备

led{
    compatible = "mini2440-led";
    status = "okay";
    reg = <0x56000010 0x04    /* GPBCON */
          0x56000014 0x04>;   /* GPBDATA */
}; 

其中:

  • compatible 属性表示该节点所描述的硬件设备与驱动程序的兼容性。mini2440-led 为自定义字符串,表示该设备所对应的驱动程序;
  • status = "okay" 表示该设备节点状态正常,可以被系统所使用;
  • reg 属性用于描述 LED 硬件寄存器的地址和长度信息,格式为 <起始地址 长度> <起始地址 长度> ...。在这里:
    • 0x56000010 表示 GPBCON 寄存器的地址,0x04 表示它的长度(4 bytes);
    • 0x56000014 表示 GPBDATA 寄存器的地址,同样是4 bytes;

通过这个 LED 节点的描述信息,设备树可以与相关的驱动程序进行匹配,从而实现对该LED硬件设备的初始化、控制等操作。

二、LED驱动程序

在/work/sambashare/drivers下创建23.led_dev_dts文件夹。用来保存LED驱动程序以及测试应用程序。

2.1 编写led_open、led_write函数

#define DTSLED_CNT      1
#define DTSLED_NAME     "dtsled"

/* 定义led结构体 */
struct led_dev {
    dev_t devid;                   /* 设备号 */
    struct cdev cdev;              /* 保存操作结构体的字符设备 */
    struct class *class;           /* class */
    struct device *device;         /* device */
    struct device_node *nd;        /* 设备节点 */

    unsigned long __iomem *gpbcon;     /* gpbcon寄存器 __iomem 表示该地址用于访问外设寄存器 */
    unsigned long __iomem *gpbdat;     /* gpbdat寄存器 */
};

/* 定义一个led设备 */
struct led_dev dts_led;

/* GPB5~GPB8配置为输出 */
static int led_open(struct inode *inode, struct file *file)
{
    u32 val;
    val = readl(dts_led.gpbcon);
    val &= ~((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16));
    val |= ((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16));
    writel(val,dts_led.gpbcon);
    return 0;
}

/* LED01~LED4点亮/熄灭 1:点亮 其他:熄灭 */
static void led_switch(int on_off)
{
    u32 val = readl(dts_led.gpbdat);
    if(on_off == 1){
         /* 点亮 */
        val &= ~((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8));
    }else{
         /* 熄灭 */
        val  |= ((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8));
    }
     writel(val, dts_led.gpbdat);
}

/* 点亮/熄灭 LED01~LED4 */
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    int val;

    int ret = copy_from_user(&val, buf, count);   /* 用户空间到内核空间传递数据 */
    if(ret < 0){
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    printk("value %d",val);

    led_switch(val);

    return 0;
}

2.2 注册LED驱动程序

static int led_init(void)
{
    int ret = 0;

    printk("%s enter.\n", __func__);

    /* 1. 动态分配字符设备号 */
    ret = alloc_chrdev_region(&dts_led.devid, 0, 1,DTSLED_NAME);  // ls /proc/devices看到的名字
    /* 返回值为负数,表示操作失败 */
    if (ret < 0) {
        printk("alloc char device region failed\n");
        goto faile_devid;
    }else{
        printk("alloc char device region success\n");
    }

    /* 2.初始化字符设备,添加字符设备 */
     cdev_init(&dts_led.cdev, &led_fops);
     ret = cdev_add(&dts_led.cdev, dts_led.devid, DTSLED_CNT);
     /* 返回值为负数,表示操作失败 */
     if (ret < 0) {
        printk("char device add failed\n");
        goto fail_cdev;
     }else{
        printk("char device add success\n");
     }

    /* 3.创建类,它会在sys目录下创建/sys/class/dtsled这个类  */
     dts_led.class = class_create(THIS_MODULE, DTSLED_NAME);
     if(IS_ERR(dts_led.class)){
         printk("create class failed\n");
         ret = PTR_ERR(dts_led.class);
         goto fail_class;
     }else{
          printk("create class success\n");
      }

    /* 4. 在/sys/class/led下创建dtsled设备,然后mdev通过这个自动创建/dev/dtsled这个设备节点 */
     dts_led.device = device_create(dts_led.class, NULL, dts_led.devid, NULL, DTSLED_NAME);
     if(IS_ERR(dts_led.device)){
         printk("create device failed\n");
         ret = PTR_ERR(dts_led.device);
         goto fail_device;
     }else{
         printk("create device success\n");
    }

    /* 5.通过路径来查找指定的led节点 */
    dts_led.nd = of_find_node_by_path("/led");
    if (dts_led.nd == NULL) {
        printk("led node can not found\n");
        ret = -EINVAL;
        goto fail_findnd;
    }else{
       printk("led node found\n");
    }

    /*6.初始化硬件*/
    dts_led.gpbcon = (unsigned int __iomem*)of_iomap(dts_led.nd,0);  /* 寻找设备节点中指定索引号的内存资源,并将其映射到内核虚拟地址空间中 */
    dts_led.gpbdat = (unsigned int __iomem*)of_iomap(dts_led.nd,1);

    return 0;
fail_findnd:
    device_destroy(dts_led.class, dts_led.devid);
fail_device:
    class_destroy(dts_led.class);
fail_class:
    cdev_del(&dts_led.cdev);
fail_cdev:
    unregister_chrdev_region(dts_led.devid, DTSLED_CNT);
faile_devid:
    return ret;
}

2.3 卸载LED驱动程序

static void __exit led_exit(void)
{
    /* 注销虚拟地址 */
    iounmap(dts_led.gpbcon);
    iounmap(dts_led.gpbdat);

    printk("led driver exit\n");
    /* 注销类、以及类设备 */
    device_destroy(dts_led.class, dts_led.devid);
    class_destroy(dts_led.class);

    cdev_del(&dts_led.cdev);
    unregister_chrdev_region(dts_led.devid, DTSLED_CNT);
    return;
}

使用iounmap取消of_iomap所做的映射。

2.4 led_dev.c完整代码   

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/io.h>

#define DTSLED_CNT      1
#define DTSLED_NAME     "dtsled"

/* 定义led结构体 */
struct led_dev {
    dev_t devid;                   /* 设备号 */
    struct cdev cdev;              /* 保存操作结构体的字符设备 */
    struct class *class;           /* class */
    struct device *device;         /* device */
    struct device_node *nd;        /* 设备节点 */

    unsigned long __iomem *gpbcon;     /* gpbcon寄存器 __iomem 表示该地址用于访问外设寄存器 */
    unsigned long __iomem *gpbdat;     /* gpbdat寄存器 */
};

/* 定义一个led设备 */
struct led_dev dts_led;

/* GPB5~GPB8配置为输出 */
static int led_open(struct inode *inode, struct file *file)
{
    u32 val;
    val = readl(dts_led.gpbcon);
    val &= ~((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16));
    val |= ((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16));
    writel(val,dts_led.gpbcon);
    return 0;
}

/* LED01~LED4点亮/熄灭 1:点亮 其他:熄灭 */
static void led_switch(int on_off)
{
    u32 val = readl(dts_led.gpbdat);
    if(on_off == 1){
         /* 点亮 */
        val &= ~((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8));
    }else{
         /* 熄灭 */
        val  |= ((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8));
    }
     writel(val, dts_led.gpbdat);
}

/* 点亮/熄灭 LED01~LED4 */
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    int val;

    int ret = copy_from_user(&val, buf, count);   /* 用户空间到内核空间传递数据 */
    if(ret < 0){
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    printk("value %d",val);

    led_switch(val);

    return 0;
}

static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,
    .open   =   led_open,
    .write  =   led_write,
};


static int led_init(void)
{
    int ret = 0;

    printk("%s enter.\n", __func__);

    /* 1. 动态分配字符设备号 */
    ret = alloc_chrdev_region(&dts_led.devid, 0, 1,DTSLED_NAME);  // ls /proc/devices看到的名字
    /* 返回值为负数,表示操作失败 */
    if (ret < 0) {
        printk("alloc char device region failed\n");
        goto faile_devid;
    }else{
        printk("alloc char device region success\n");
    }

    /* 2.初始化字符设备,添加字符设备 */
     cdev_init(&dts_led.cdev, &led_fops);
     ret = cdev_add(&dts_led.cdev, dts_led.devid, DTSLED_CNT);
     /* 返回值为负数,表示操作失败 */
     if (ret < 0) {
        printk("char device add failed\n");
        goto fail_cdev;
     }else{
        printk("char device add success\n");
     }

    /* 3.创建类,它会在sys目录下创建/sys/class/dtsled这个类  */
     dts_led.class = class_create(THIS_MODULE, DTSLED_NAME);
     if(IS_ERR(dts_led.class)){
         printk("create class failed\n");
         ret = PTR_ERR(dts_led.class);
         goto fail_class;
     }else{
          printk("create class success\n");
      }

    /* 4. 在/sys/class/led下创建dtsled设备,然后mdev通过这个自动创建/dev/dtsled这个设备节点 */
     dts_led.device = device_create(dts_led.class, NULL, dts_led.devid, NULL, DTSLED_NAME);
     if(IS_ERR(dts_led.device)){
         printk("create device failed\n");
         ret = PTR_ERR(dts_led.device);
         goto fail_device;
     }else{
         printk("create device success\n");
    }

    /* 5.通过路径来查找指定的led节点 */
    dts_led.nd = of_find_node_by_path("/led");
    if (dts_led.nd == NULL) {
        printk("led node can not found\n");
        ret = -EINVAL;
        goto fail_findnd;
    }else{
       printk("led node found\n");
    }

    /*6.初始化硬件*/
    dts_led.gpbcon = (unsigned int __iomem*)of_iomap(dts_led.nd,0);  /* 寻找设备节点中指定索引号的内存资源,并将其映射到内核虚拟地址空间中 */
    dts_led.gpbdat = (unsigned int __iomem*)of_iomap(dts_led.nd,1);

    return 0;
fail_findnd:
    device_destroy(dts_led.class, dts_led.devid);
fail_device:
    class_destroy(dts_led.class);
fail_class:
    cdev_del(&dts_led.cdev);
fail_cdev:
    unregister_chrdev_region(dts_led.devid, DTSLED_CNT);
faile_devid:
    return ret;
}

static void __exit led_exit(void)
{
    /* 注销虚拟地址 */
    iounmap(dts_led.gpbcon);
    iounmap(dts_led.gpbdat);

    printk("led driver exit\n");
    /* 注销类、以及类设备 */
    device_destroy(dts_led.class, dts_led.devid);
    class_destroy(dts_led.class);

    cdev_del(&dts_led.cdev);
    unregister_chrdev_region(dts_led.devid, DTSLED_CNT);
    return;
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
View Code

2.5 Makefile

KERN_DIR :=/work/sambashare/linux-5.2.8-dt
all:
    make -C $(KERN_DIR) M=`pwd` modules 
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m += led_dev.o

三、LED驱动测试应用程序

在23.led_dev_dts下创建test文件夹,保存测试应用程序。

3.1 main.c

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

void print_usage(char *file)
{
    printf("Usage:\n");
    printf("%s <dev> <on|off>\n",file);
    printf("eg. \n");
    printf("%s /dev/dtsled on\n", file);
    printf("%s /dev/dtsled off\n", file);
}

int main(int argc,char **argv)
{
    int fd;
    int val;
    char *filename;

    if (argc != 3){
        print_usage(argv[0]);
        return 0;
    }

    filename = argv[1];
    fd = open(filename,O_RDWR);
    if(fd == -1){
        printf("can't open %s!\n",filename);
        return 0;
    }

   if (!strcmp("on", argv[2])){
        // 亮灯
        val = 1;
        printf("%s on!\n",filename);
        write(fd, &val, 4);
    }else if (!strcmp("off", argv[2])){
        // 灭灯
        val = 0;
        printf("%s off!\n",filename);
        write(fd, &val, 4);
    }else{
        print_usage(argv[0]);
    }

    return 0;
}

3.2 Makefile

all:
    arm-linux-gcc -march=armv4t -o main main.c
clean:
    rm -rf *.o main

四、烧录开发板测试

4.1 编译设备树

root@zhengyang:/work/sambashare/linux-5.2.8-dt# make dtbs
  DTC     arch/arm/boot/dts/s3c2416-smdk2416.dtb
  DTC     arch/arm/boot/dts/s3c2440-smdk2440.dtb

编译设备树文件,把前面配置过的arch/arm/boot/dts里的dts文件编译成dtb文件。

将s3c2440-smdk2440.dtb复制到tftp服务器路径下:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# cp /work/sambashare/linux-5.2.8-dt/arch/arm/boot/dts/s3c2440-smdk2440.dtb /work/tftpboot/

4.2 编译驱动

执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:

root@zhengyang:/work/sambashare/linux-5.2.8-dt# cd /work/sambashare/drivers/23.led_dev_dts
root@zhengyang:/work/sambashare/drivers/23.led_dev_dts# make
root@zhengyang:/work/sambashare/drivers/23.led_dev_dts# cp /work/sambashare/drivers/23.led_dev_dts/led_dev.ko  /work/nfs_root/rootfs

4.3 启动内核

uboot启动后,将dtb下载到内存地址0x30001000中:

SMDK2440 # tftp 0x30001000 s3c2440-smdk2440.dtb

然后将内核镜像加载到内存0x30008000地址:

nand read 0x30008000 kernel;

然后可以使用如下命令启动内核:

SMDK2440 # bootm 0x30008000 - 0x30001000   // 无设备树时,直接bootm 0x30008000
//bootm  uImage地址  ramdisk地址  设备树镜像地址

安装驱动:

[root@zy:/]# insmod led_dev.ko
alloc char device region success
char device add success
create class success
create device success
led node found

查看设备节点文件:

[root@zy:/]# ls /dev/dtsled -l
crw-rw----    1 0        0         249,   0 Mar 29 11:21 /dev/dtsled

查看类:

[root@zy:/]# ls /sys/class/dtsled -l
total 0
lrwxrwxrwx    1 0        0                0 Mar 29 11:35 dtsled -> ../../devices/virtual/dtsled/dtsled
[root@zy:/]# ls /sys/class/dtsled/dtsled/ -l
total 0
-r--r--r--    1 0        0             4096 Mar 29 11:21 dev
drwxr-xr-x    2 0        0                0 Mar 29 11:35 power
lrwxrwxrwx    1 0        0                0 Mar 29 11:35 subsystem -> ../../../../class/dtsled
-rw-r--r--    1 0        0             4096 Mar 29 11:35 uevent

在linux的/sys/firmware/devicetree/base 目录下可以查看到 led 节点,如下图所示:

[root@zy:/]# ls /sys/firmware/devicetree/base/
#address-cells                 memory@30000000
#size-cells                    model
aliases                        name
chosen                         nand@4e000000
clock-controller@4c000000      pinctrl@56000000
clocks                         rtc@57000000
compatible                     serial@50000000
cpus                           serial@50004000
i2c@54000000                   serial@50008000
interrupt-controller@4a000000  srom-cs4@20000000
interrupt-parent               timer@51000000
led                            watchdog@53000000

进入led节点的目录下可以查看到led节点的属性,如下图所示:

[root@zy:/]# ls /sys/firmware/devicetree/base/led -l
total 0
-r--r--r--    1 0        0               13 Mar 29 11:37 compatible
-r--r--r--    1 0        0                4 Mar 29 11:37 name
-r--r--r--    1 0        0               16 Mar 29 11:37 reg
-r--r--r--    1 0        0                5 Mar 29 11:37 status

通过过cat或 hexdump命令可以查看属性的值,如下图所示:

[root@zy:/sys/firmware/devicetree/base/led]# cat compatible
mini2440-led

由于我当前使用的根文件系统不支持hexdump命令,因此无法查看reg文件的值。

4.4 编译测试应用程序

执行make命令编译测试应用程序,并将测试应用程序拷贝到nfs文件系统:

root@zhengyang:/work/sambashare/drivers/23.led_dev_dts# cd test/
root@zhengyang:/work/sambashare/drivers/23.led_dev_dts/test# make clean
rm -rf *.o main
root@zhengyang:/work/sambashare/drivers/23.led_dev_dts/test# make
arm-linux-gcc -march=armv4t -o main main.c
root@zhengyang:/work/sambashare/drivers/23.led_dev_dts/test# cp ./main /work/nfs_root/rootfs

运行应用程序:

[root@zy:/]# ./main /dev/dtsled on
/dev/dtsled on!
[root@zy:/]# ./main /dev/dtsled off
/dev/dtsled off!

可以看到LED1~LED4同时点亮和同时熄灭。

如果你想单独控制每一个LED,那需要为每个LED编写对应的驱动程序,这里就不演示了。

4.5 卸载LED驱动

通过用lsmod可以查看当前安装了哪些驱动:

[root@zy:/]# lsmod
led_dev 2476 0 - Live 0xbf000000 (O)

卸载时直接运行:

[root@zy:/]# rmmod led_dev
led driver exit

五、代码下载

Young / s3c2440_project[drivers]

参考文章

[1]Linux字符设备驱动之LED驱动(基于设备树)

[2]中断系统中的设备树__使用设备树描述按键中断

[3]一、linux驱动开发-2.2-设备树下的LED驱动

posted @ 2023-04-24 23:06  大奥特曼打小怪兽  阅读(201)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步