Linux学习笔记(16)——基于设备树的LED设备驱动

  1. 在设备树文件(.dts)中添加LED相关信息
    led {
        #address-cell = <1>;		    /* 表示用一个32位的数来描述地址 */
        #size-cell = <1>;				/* 用1个32位的数来描述该地址的大小 */
        compatible = "led";
        status = "okey";
	    reg = < 0x020C406C 0x04    		/* ccm_ccgr1 */
                0x020E0068 0x04			/* sw_mux_gpio1_io03 */
                0x020E02F4 0x04			/* sw_pad_gpio1_io03 */
                0x0209C000 0x04			/* gpio1_dr */
                0x0209C004 0x04 >;		/* gpio1_gdir */
    };
  1. 编译设备树文件生成设备树目标文件(.dtb)
    编译设备树文件 glen@ubuntu:~/linux/imx6ull/linux/glen_linux$ make dtbs
    产生设备对目标文件

    CHK     include/config/kernel.release
    CHK     include/generated/uapi/linux/version.h
    CHK     include/generated/utsrelease.h
    make[1]: “include/generated/mach-types.h”已是最新。
    CHK     include/generated/bounds.h
    CHK     include/generated/asm-offsets.h
    CALL    scripts/checksyscalls.sh
    DTC     arch/arm/boot/dts/imx6ull-glen-emmc.dtb
    
  2. 编写根据设备树文件LED相关信息编写对应驱动程序及测试程序

/* 
 * 文件名   : dtsled.c
 * 作者     : glen  
 * 描述     : dtsled驱动文件
 */
#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 <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/string.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define DTSLED_CNT  1               /* 设备个数 */
#define DTSLED_NAME "dtsled"        /* 设备名称 */
#define LEDON       "ON"            /* 打开  */
#define LEDOFF      "OFF"           /* 关闭 */

/* 映射后的寄存器虚拟地址指针
 */
static void __iomem *ccm_ccgr1;
static void __iomem *sw_mux_gpio1_io03;
static void __iomem *sw_pad_gpio1_io03;
static void __iomem *gpio1_dr;
static void __iomem *gpio1_gdir;

/* dtsled设备结构体 */
struct dtsled_dev {
    dev_t devid;                    /* 设备号 */
    struct cdev cdev;               /* cdev */
    struct class *class;            /* 类 */
    struct device *device;          /* 设备 */
    int major;                      /* 主设备号 */
    int minor;                      /* 次设备号 */
    struct device_node *nd;         /* 设备节点 */    
};

struct dtsled_dev dtsled_dev;       /* led设备 */

/**
 * \brief   : LED控制
 * \par     : status    LEDON(打开) LEDOFF(关闭)
 * \retval  : 无
 */
void led_ctrl(const char *status)
{
    u32 val = 0;
    if (strcmp(status, LEDON) == 0) {
        val = readl(gpio1_dr);
        val &= ~(1 << 3);
        writel(val, gpio1_dr);
    } else if (strcmp(status, LEDOFF) == 0) {
        val = readl(gpio1_dr);
        val |= (1 << 3);
        writel(val, gpio1_dr);
    }
}

/**
 * \brief   : 打开设备
 * \par     : inode 传递给驱动
 *            filp  设备文件
 * \retval  : 0 成功  其它 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &dtsled_dev;
    return 0;
}

/**
 * \brief   : 从设备读取数据
 * \par     : filp  要读取的设备文件(文件描述符)
 *            buf   返回给用户的数据缓冲区
 *            cnt   要读取的数据长度
 *            offt  相对于文件首地址的偏移
 * \retval  : 读取的字节数, 如果为负值, 表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;    
}

/**
 * \brief   : 向设备写数据
 * \par     : filp  要写入的设备文件(文件描述符)
 *            buf   要写给用户的数据
 *            cnt   要写入的数据长度
 *            offt  相对于文件首地址的偏移
 * \retval  : 写入的字节数, 如果为负值, 表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int ret;
    u8 data_buf[4];

    ret = copy_from_user(data_buf, buf, ((cnt < sizeof(LEDOFF)) ? cnt : sizeof(LEDOFF)));
    if (ret < 0) {
        printk("Kernel write failed!\r\n");
        return -EFAULT;
    }

    led_ctrl((const char *)data_buf);
    return 0;    
}

/**
 * \brief   : 关闭/释放设备
 * \par     : inode 传递给驱动
 *            filp  设备文件
 * \retval  : 0 成功  其它 失败
 */
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   : 驱动入口函数
 * \par     : 无
 * \retval  : 无
 */
static int __init led_init(void)
{
    u32 val = 0;
    int ret;
    u32 reg_data[14];
    const char *str;
    struct property *prop;

    /* 获取设备树中的属性数据 
     * 1. 获取设备节点 led
     */
    dtsled_dev.nd = of_find_node_by_path("/led");
    if (dtsled_dev.nd == NULL) {
        printk("LED node can not found!\r\n");
        return -EINVAL;
    } else {
        printk("LED node has been found!\r\n");
    }

    /* 2. 获取compatible属性内容 */
    prop = of_find_property(dtsled_dev.nd, "compatible", NULL);
    if (prop == NULL) {
        printk("Compatible proterty can't be found!\r\n");
    } else {
        printk("compatible = %s\r\n", (char *)prop->value);
    }

    /* 3. 获取status属性内容 */
    ret = of_property_read_string(dtsled_dev.nd, "status", &str);
    if (ret < 0) {
        printk("Status readed failed!\r\n");
    } else {
        printk("status = %s\r\n", str);
    }

    /* 4. 获取reg属性内容 */
    ret = of_property_read_u32_array(dtsled_dev.nd, "reg", reg_data, 10);
    if (ret < 0) {
        printk("reg property read failed!\r\n");
    } else {
        u8 i = 0;
        printk("reg data:");
        for (i = 0; i < 10; i++) {
            printk("%#X\t", reg_data[i]);
        }
        printk("\r\n");
    }

    /* 初始化LED */
    ccm_ccgr1 = of_iomap(dtsled_dev.nd, 0);
    sw_mux_gpio1_io03 = of_iomap(dtsled_dev.nd, 1);
    sw_pad_gpio1_io03 = of_iomap(dtsled_dev.nd, 2);
    gpio1_dr = of_iomap(dtsled_dev.nd, 3);
    gpio1_gdir = of_iomap(dtsled_dev.nd, 4);

    /* 使能GPIO1时钟 */
    val = readl(ccm_ccgr1);
    val |= (3 << 26);
    writel(val, ccm_ccgr1);

    /* 设置GPIO1_IO03的复用功能, 将其复用为GPIO1_IO03 */
    writel(5, sw_mux_gpio1_io03);

    /* 设置GPIO1_IO03的电气属性 */
    writel(0x10B0, sw_pad_gpio1_io03);

    /* 设置GPIO1_IO03为输出 */
    val = readl(gpio1_gdir);
    val |= (1 << 3);
    writel(val, gpio1_gdir);

    /* 默认关闭LED */
    val = readl(gpio1_dr);
    val |= (1 << 3);
    writel(val, gpio1_dr);

    /* 注册字符设备驱动 
     * 创建设备号
     */
    if (dtsled_dev.major) {
        /* 已定义了主设备号 */
        dtsled_dev.devid = MKDEV(dtsled_dev.major, 0);

        /* 申请设备号 */
        register_chrdev_region(dtsled_dev.devid, DTSLED_CNT, DTSLED_NAME);
    } else {
        /* 申请设备号 */
        alloc_chrdev_region(&dtsled_dev.devid, 0, DTSLED_CNT, DTSLED_NAME);

        /* 获取分配设备号的主设备号 */
        dtsled_dev.major = MAJOR(dtsled_dev.devid);

        /* 获取分配设备号的次设备号 */
        dtsled_dev.minor = MINOR(dtsled_dev.devid);
    }
    printk("dtsled_dev major=%d, minor=%d\r\n", dtsled_dev.major, dtsled_dev.minor);

    /* 初始化cdev */
    dtsled_dev.cdev.owner = THIS_MODULE;
    cdev_init(&dtsled_dev.cdev, &dtsled_fops);

    /* 添加一个cdev */
    cdev_add(&dtsled_dev.cdev, dtsled_dev.devid, DTSLED_CNT);

    /* 创建类 */
    dtsled_dev.class = class_create(THIS_MODULE, DTSLED_NAME);
    if (IS_ERR(dtsled_dev.class)) {
        return PTR_ERR(dtsled_dev.class);
    }

    /* 创建设备 */
    dtsled_dev.device = device_create(dtsled_dev.class, NULL, dtsled_dev.devid, NULL, DTSLED_NAME);
    if (IS_ERR(dtsled_dev.device)) {
        return PTR_ERR(dtsled_dev.device);
    }

    return 0;
}

/**
 * \brief   : 驱动出口函数
 * \par     : 无
 * \retval  : 无
 */
static void __exit led_exit(void)
{
    /* 取消映射 */
    iounmap(ccm_ccgr1);
    iounmap(sw_mux_gpio1_io03);
    iounmap(sw_pad_gpio1_io03);
    iounmap(gpio1_dr);
    iounmap(gpio1_gdir);

    /* 注销字符设备驱动 */
    cdev_del(&dtsled_dev.cdev);

    /* 注销设备号 */
    unregister_chrdev_region(dtsled_dev.devid, DTSLED_CNT);

    device_destroy(dtsled_dev.class, dtsled_dev.devid);
    class_destroy(dtsled_dev.class);
}

/*
 * 设备注册入口和出口
 */
module_init(led_init);
module_exit(led_exit);

/* 模块的许可证声明 
 * static const char __UNIQUE_ID_license__COUNTER__[] \
 *   __used __attribute__((section(".modinfo"), unused, aligned(1))) \
 *  = "license=GPL";
 */
MODULE_LICENSE("GPL");

/* 模块的作者声明
 * static const char __UNIQUE_ID_author__COUNTER__[]					  \
 * __used __attribute__((section(".modinfo"), unused, aligned(1)))	  \
 * = "author=glen_cao"
 */
MODULE_AUTHOR("glen");
/*
 * 文件名   :  dtsled_test.c
 * 作者     :  glen
 * 描述     :  dtsled测试程序
 */
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/**
 * @brief   : main函数
 * @par     : argc  argv数组元素的个数
 *            argv  参数数组
 * @retval  : 0 成功    其它 失败
 */
int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    char data_buf[1];

    if (argc != 3) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }
    
    /* 向/dev/led文件写入数据 */
    ret = write(fd, argv[2], sizeof(argv[2]));
    if (ret < 0) {
        printf("LED control failed!\r\n");
        close(fd);
        return -1;
    } else {
        printf("Write parameter is %s\r\n", argv[2]);
    }

    /* 关闭文件 */
    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}
  1. 编译LED驱动和测试文件,并复制到NFS根文件系统
    4.1 编译产生dtsled.ko文件:
glen@ubuntu:~/linux/imx6ull/linux/driver/4_dtsled$ make
make -C /home/glen/linux/imx6ull/linux/glen_linux M=/home/glen/linux/imx6ull/linux/driver/4_dtsled modules
make[1]: 进入目录“/home/glen/linux/imx6ull/linux/glen_linux”
  CC [M]  /home/glen/linux/imx6ull/linux/driver/4_dtsled/dtsled.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/glen/linux/imx6ull/linux/driver/4_dtsled/dtsled.mod.o
  LD [M]  /home/glen/linux/imx6ull/linux/driver/4_dtsled/dtsled.ko
make[1]: 离开目录“/home/glen/linux/imx6ull/linux/glen_linux”
glen@ubuntu:~/linux/imx6ull/linux/driver/4_dtsled$ 

4.2 编译产生目标文件

glen@ubuntu:~/linux/imx6ull/linux/driver/4_dtsled$ arm-linux-gnueabihf-gcc dtsled_test.c -o dtsled_test

4.3 把目标文件复制到NFS根文件系统:

glen@ubuntu:~/linux/imx6ull/linux/driver/4_dtsled$ sudo cp dtsled.ko dtsled_test ../../../../nfs/rootfs/lib/modules/4.1.15/ -f
  1. 测试程序
    5.1 重启开发板,查看设备树文件:
/sys/firmware/devicetree/base # ls
#address-cells                 memory
#size-cells                    model
aliases                        name
backlight                      pxp_v4l2
chosen                         regulators
clocks                         reserved-memory
compatible                     soc
cpus                           sound
interrupt-controller@00a01000  spi4
led

可以看到,已经存在led文件夹。

5.2 加载LED驱动模块:

/lib/modules/4.1.15 # insmod dtsled.ko
LED node has been found!
compatible = led
status = okey
reg data:0X20C406C      0X4     0X20E0068       0X4     0X20E02F4       0X4     0X209C000       0X4     0X209C004       0X4
dtsled_dev major=249, minor=0

5.3 打开LED

/lib/modules/4.1.15 # ./dtsled_test /dev/dtsled ON
Write parameter is ON

在目标板上可以观察到LED灯亮起来。

5.4 关闭LED

/lib/modules/4.1.15 # ./dtsled_test /dev/dtsled OFF
Write parameter is OFF

在目标板上可以观察到LED灯灭掉。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux设备驱动程序是用于控制和管理硬件设备的软件模块。学习Linux设备驱动程序可以帮助开发人员理解和掌握Linux内核的工作原理,以及如何编写和调试设备驱动程序。 以下是一些学习Linux设备驱动程序的笔记和建议: 1. 理解Linux设备模型:Linux设备模型是一种用于管理设备的框架,它提供了一种统一的方式来表示和操作设备学习Linux设备模型可以帮助你理解设备的注册、初始化和销毁过程。 2. 学习字符设备驱动程序:字符设备是一种以字节为单位进行读写的设备,如串口、终端等。学习字符设备驱动程序可以帮助你了解字符设备的打开、关闭、读写等操作,并学习如何实现设备文件的注册和操作。 3. 学习设备驱动程序:块设备是一种以块为单位进行读写的设备,如硬盘、闪存等。学习设备驱动程序可以帮助你了解块设备的分区、缓存、IO调度等操作,并学习如何实现块设备的注册和操作。 4. 学习中断处理:中断是设备向处理器发送信号的一种机制,用于通知处理器设备的状态变化。学习中断处理可以帮助你了解中断的注册、处理和释放过程,并学习如何编写中断处理程序。 5. 学习设备驱动程序的调试技巧:设备驱动程序的调试是一个重要的技能,可以帮助你快速定位和解决问题。学习设备驱动程序的调试技巧可以帮助你理解和使用调试工具,如 printk、kprobe等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值