0


【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

前言:

本文展示LED驱动进化升级化蝶的过程,并由浅入深的对驱动程序框架的理念作进一步阐述。遇到搞不明白的,就不妨先点个灯吧。

LED驱动进化之路:(层次递进)

  1. 最简单的LED驱动程序
  2. 加入分层思想的LED驱动程序
  3. 加入分离思想的LED驱动程序
  4. 总线设备驱动模型下的LED驱动程序
  5. 加入设备树的LED驱动程序

参考:韦老师课程,Linux笔记老师课程(设备树部分)

https://blog.csdn.net/qq_33487044/article/details/126325656

https://www.bilibili.com/video/BV14f4y1Q7ti?p=12&spm_id_from=pageDriver&vd_source=cf66c4035cd726f1d3cb6a42cfd6da5f

过一遍驱动框架后,有了大体的认知,但还需要进一步的实践感受。

https://blog.csdn.net/weixin_42373086/article/details/130521999

一、最简单的LED驱动程序

1.1 字符设备驱动程序框架

最简单的驱动程序,一个设备app调用open时就提供给驱动程序里的drv_open,read→drv_read,write→drv_write,ioctrl→drv_ioctl。

编写驱动程序的步骤:跟hello驱动程序框架完全一致

  1. 确定主设备号,(可以自己定义,设置为0时会让内核分配)
  2. 定义自己的file_operations结构体管理驱动程序drv_open/drv_write等,实现对应的drv_open/drv_write等函数。---注:file_operations为核心
  3. 把file_operations结构体告诉内核:register_chrdev(提供主设备号)。
  4. 实现入口函数,安装驱动程序时,入口函数调用register_chrdev,相应的就有出口函数,卸载驱动程序时,出口函数调用unregister_chrdev
  5. 辅助性功能:提供设备信息,自动创建设备节点:class_write,device_create

1.2 程序实战

1.2.1 驱动程序(led_drive_simple.c)

具体的注释和解析都在代码中:

/* 说明:
    *1,本代码是跟学韦老师课程所编写,增加了注释和分析
    *2,采用的是UTF-8编码格式
    *3,简单LED驱动程序 led_drive_simple.c
    *4,参照内核字符设备驱动程序cm4040_cs.c
*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>

/*registers*/
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;

static struct class *led_class;
/*第一步:确定主设备号,这里由内核分配*/
static int major = 0;

/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:        led_drv_open
 *功能:        enable gpio                 使能gpio
             configure pin as gpio    设置引脚为GPIO
             configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
             *flip:要打开的文件
*返回参数: 如果成功返回0
*/            

static int led_drv_open(struct inode *inode, struct file *filp)
{
    //imx6ull默认使能GPIO5.
    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |=  0x5;

    *GPIO5_GDIR |= (1<<3);
    return 0;
}

/*
 *函数:      led_drv_write
 *功能:        copy_from_user:get data form app  获取app的数据,并设置gpio的寄存器
             set gpio register: out 1/0
 *传入参数:
             *flip:要写的文件
             *buf: 写的数据来自于buf
             *size:写多大的数据
             *offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
             size_t count, loff_t *ppos)
{
    char val;
    int err;
    err = copy_from_user(&val, buf, 1);
    if(val)
    {
        *GPIO5_DR &= ~(1<<3);
    }else
    {
        *GPIO5_DR |= (1<<3);    
    }
    return -1;
    
}

             
static struct file_operations led_fops = {
    .owner        = THIS_MODULE,
    .write        = led_drv_write,
    .open        = led_drv_open,

};

/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *功能:        ①注册主设备号    
             ②获取寄存器物理地址映射过来的虚拟地址
             ③辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
    /*
     *printk:判断一下是否调用了入口函数
     *__FILE__ :表示文件
     *__FUNCTION__ :当前函数名
     *__LINE__ :在文件的哪一行
    */    
    printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);

    //ioremap:物理地址映射到虚拟地址
    IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
    GPIO5_GDIR = ioremap(0x020AC004, 4);
    GPIO5_DR  = ioremap(0x020AC000, 4);

    
    
    //0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
    major = register_chrdev(0,"xixiwuli_led", &led_fops);    
    led_class = class_create(THIS_MODULE, "led_2345");
    if (IS_ERR(led_class))
        return PTR_ERR(led_class);
    if (major < 0) {
        class_destroy(led_class);
        return major;
    }
    //创建/dev/myled的设备节点
    device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");
    return 0;
}

/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{
    iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
    iounmap(GPIO5_GDIR);
    iounmap(GPIO5_DR);
    
    device_destroy(led_class, MKDEV(major, 0));
    class_destroy(led_class);
    unregister_chrdev(major,"xixiwuli_led");
}

//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

1.2.2 应用程序(led_test_simple.c)

具体的注释和解析都在代码中:

/* 说明:
    *1,本代码是跟学韦老师课程所编,增加了注释和理解
    *2,采用的是UTF-8编码格式
    *3,简单LED应用程序 led_test_simple.c
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

//led_test_simple /dev/myled on
//led_test_simple /dev/myled off

int main(int argc, char** argv)
{
    int fd;
    char status;
    
    if(argc != 3)
    {
        printf("usage: %s <dev> <on|off>\n", argv[0]);
        printf("eg: %s /dev/myled on\n", argv[0]);
        printf("eg: %s /dev/myled off\n", argv[0]);
    }

    //open
    fd = open(argv[1], O_RDWR);
    if(fd < 0)
    {
        printf("can not open%s\n",argv[1]);
        return -1;
    }
    
    //write
    if(strcmp(argv[2], "on") == 0)
    {
        status = 1;
        write(fd, &status, 1);
    }    
    if(strcmp(argv[2], "off") == 0)
    {
        status = 0;
        write(fd, &status, 1);
    }
    return 0;

}

1.2.3 Makefile代码

具体的注释和解析都在代码中:

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o led_test_simple led_test_simple.c 

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f led_test_simple

obj-m    += led_drive_simple.o

1.3 运行测试

1.3.1 首先编译内核(如果没编译过)

参照这篇文章https://blog.csdn.net/weixin_42373086/article/details/129796348?spm=1001.2014.3001.5501

1.3.2 设置交叉编译工具链(Ubuntu)

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

1.3.3 编译(Ubuntu)

make编译后,将.ko文件和 led_test_simple复制到nfs挂载的文件夹下。

make
cp *.ko led_test_simple ~/nfs_rootfs/

1.3.4 上机测试(开发板)

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
//复制到开发板上
cp /mnt/led_drive_simple.ko ./
cp /mnt/led_test_simple ./
//安装驱动模块
insmod led_drive_simple.ko
 
//查询是否有我们的hello程序
cat /proc/devices
lsmod
 
//查询是否有我们的设备节点
ls /dev/myled -l
//打开
./led_test_simple /dev/myled on
//关闭
./led_test_simple /dev/myled off

查询设备结果:

点灯和关灯:

二、LED驱动程序(分层)

2.1 分层设计思想

如果对于多个板子,去驱动同一种设备(例如去点个灯),都要像上面的方式从头到尾写一个对应的驱动程序,是件非常麻烦的事情。

简而言之,这里LED驱动是否能支持多个板子?如何实现呢?

针对上述的情况,就要应用到分层的思想,我们先要将驱动拆为通用的框架(leddrv.c)、具体的硬件操作(board_X.c),如下图所示:

这里再进一步,以面向对象的思想,抽象出一个结构体,每个单板相关的boardX.c实现自己的led_operations结构体,供上层的leddrv.c调用。

struct led_operations
{
    int (*init) (int which); /*初始化LED,which---哪个LED*/
    int (*ctl) (int which, int status);/*控制LED,which-哪个LED,status:1/0亮灭*/
};

2.2 程序实战

相较最简框架下的驱动程序,代码里注释差异点。

2.2.1 头文件(led_opr.h)

这个头文件是作为board_imx6ull.c和led_drive.c之间的桥梁。

//定义这个宏,防止二次调用
//例子LED调用LEDA,LEDA调用这个头文件,未加时,会二次调用这个头文件
//加了之后,第二次调用时,就不再其效用了。
#ifndef _LED_OPR_H
#define _LED_OPR_H

struct led_operations
{
    int num;
    int (*init) (int which); /*初始化LED,which---哪个LED*/
    int (*ctl) (int which, char status);/*控制LED,which-哪个LED,status:1/0亮灭*/
};

struct led_operations *get_board_led_opr(void);

#endif

2.2.2 驱动程序(board_imx6ull.c)

从这里我们可以看到,对应板子上的硬件操作都在这个程序(这层)里去实现了。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

#include "led_opr.h"

/*registers*/
//CCM_CCGR1:0x20C406C
static volatile unsigned int* CCM_CCGR1;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;

/*
 *函数:        board_demo_led_init
 *功能:        enable gpio                 使能gpio
             configure pin as gpio    设置引脚为GPIO
             configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
             *which:哪个LED
 *返回参数:如果成功返回0
*/

static int board_demo_led_init(int which)
{
    unsigned int val;
    //printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
    if(which == 0)
    {
        if(!CCM_CCGR1)
        {
            //ioremap:物理地址映射到虚拟地址
            CCM_CCGR1 = ioremap(0x20C406C, 4);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
            GPIO5_GDIR = ioremap(0x020AC004, 4);
            GPIO5_DR  = ioremap(0x020AC000, 4);
        }

        
        *CCM_CCGR1 |= (3<<30);
        val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
        val &= ~0xf;
        val |=  0x5;
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

        *GPIO5_GDIR |= (1<<3);
    }
    return 0;
}

/*
 *函数:        board_demo_led_init
 *功能:        set gpio register: out 1/0

 *传入参数:
             *which:哪个LED
             *status: 状态1/0,亮灭
 *返回参数:如果成功返回0
*/
static int board_demo_led_ctl(int which, char status)
{
    if(which == 0)
    {
        if(status)
        {
            *GPIO5_DR &= ~(1<<3);
        }else
        {
            *GPIO5_DR |= (1<<3);
        }
    }
    return 0;
}

static struct led_operations board_demo_led_opr =
{
    .num  = 1,
    .init = board_demo_led_init,
    .ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_demo_led_opr;
}

2.2.3 驱动程序(led_drive.c)

之前的框架是在这里实现设备注册和硬件操作,现在只去实现注册设备驱动等通用功能。

/* 说明:
    *1,本代码是跟学韦老师课程所编写,增加了注释和分析
    *2,采用的是UTF-8编码格式
    *3,LED驱动程序(分层思想) led_drive.c
*/

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

#include "led_opr.h"

static struct class *led_class;
struct led_operations *p_ledopr;

/*第一步:确定主设备号,这里由内核分配*/
static int major = 0; 

/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:        led_drv_open
 *差异点:      根据次设备号初始化LED
 *传入参数:
             *flip:要打开的文件
 *返回参数:如果成功返回0
*/            

static int led_drv_open(struct inode *inode, struct file *filp)
{
    int minor = iminor(inode);/*次设备号*/

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    p_ledopr->init(minor);
    return 0;
}

/*
 *函数:       led_drv_write
 *差异点:        根据次设备号和status控制LED
 *功能:        copy_from_user,从app中获取数据
 *传入参数:
             *flip:要写的文件
             *buf: 写的数据来自于buf
             *size:写多大的数据
             *offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
             size_t count, loff_t *ppos)
{
    char status;
    int err;

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = copy_from_user(&status, buf, 1);
    
    /*根据次设备号控制LED*/
    struct inode* inode = file_inode(filp);
    int minor = iminor(inode) & 0x0f;
    p_ledopr->ctl(minor, status);
    return -1;
    
}

             
static struct file_operations led_fops = {
    .owner        = THIS_MODULE,
    .write        = led_drv_write,
    .open        = led_drv_open,

};

/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *差异点:      有多个次设备号
 *功能:        ①注册主设备号    
             ②辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
    /*
     *printk:判断一下是否调用了入口函数
     *__FILE__ :表示文件
     *__FUNCTION__ :当前函数名
     *__LINE__ :在文件的哪一行
    */    
    printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
    

        
    //0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
    major = register_chrdev(0,"xixiwuli_led", &led_fops);    
    led_class = class_create(THIS_MODULE, "led_2345_class");
    int i;
    if (IS_ERR(led_class))
        return PTR_ERR(led_class);
    if (major < 0) {
        class_destroy(led_class);
        return major;
    }
    
    //通过p_ledopr,可以操作调用单板相关的代码
    p_ledopr = get_board_led_opr();
    
    //创建/dev/myled2的设备节点,多个次设备号控制多个LED
    for(i = 0;i < p_ledopr->num; i++)
    {
        device_create(led_class, NULL, MKDEV(major, i), NULL, "myled%d",i);
    }

    return 0;
}

/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{

    int i;
    for(i = 0; i < p_ledopr->num; i++)
    {
        device_destroy(led_class, MKDEV(major, i));
    }
    class_destroy(led_class);
    unregister_chrdev(major,"xixiwuli_led");
}

//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

2.2.4 应用程序(led_test.c)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

//led_test /dev/myled0 on
//led_test /dev/myled0 off

int main(int argc, char** argv)
{
    int fd;
    char status = 0;
    
    if(argc != 3)
    {
        printf("usage: %s <dev> <on|off>\n", argv[0]);
        printf("eg: %s /dev/myled on\n", argv[0]);
        printf("eg: %s /dev/myled off\n", argv[0]);
    }

    //open
    fd = open(argv[1], O_RDWR);
    if(fd < 0)
    {
        printf("can not open %s\n",argv[1]);
        return -1;
    }
    
    //write
    if (strcmp(argv[2], "on") == 0)
    {
        status = 1;
    }

    write(fd, &status, 1);
    close(fd);
    return 0;

}

2.2.5 Makefile代码

这里有小的变动,将board_imx6ull.c和led_drive.c编译在一起。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f led_test

#led_drive.c和board_demo.c编译成xixiwuli_led.ko
xixiwuli_led-y := led_drive.o board_imx6ull.o
obj-m    += xixiwuli_led.o

2.3 运行测试

前三个步骤跟上述完全一致,就不再赘述。

2.3.1 上机测试(开发板)

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
//复制到开发板上
cp /mnt/xixiwuli_led.ko ./
cp /mnt/led_test ./
//安装驱动模块
insmod xixiwuli_led.ko
 
//查询是否有我们的hello程序
cat /proc/devices
lsmod
 
//查询是否有我们的设备节点
ls /dev/myled0 -l
//打开
./led_test /dev/myled0 on
//关闭
./led_test /dev/myled0 off
//程序运行完,可以卸载相应的模块
rmmod xixiwuli_led.ko

查询设备结果:

点灯和关灯:

2.4 总结

从结果上来看,相较于简单的驱动框架下的驱动程序,也能够很好的实现点灯关灯。

但结构上,他很好的将通用功能和硬件操作部分做了分离,分别对应led_drive_stra.c和board_imx6ull.c,再加入一个设备时,也只需要创建一个board_X.c就可以了。

这样的驱动程序有了更好的拓展性,可以支持多个板子。也从这里我们能更好的理解了Linux驱动 = 驱动框架 + 硬件操作,相信大家也开始慢慢感受到驱动框架的魅力

三、LED驱动程序(分离)

3.1 分离设计思想

上述的方式,我们能够发现board_X.c里跟芯片硬件绑定的太死,如果我们要换个灯点亮,就需要修改代码,重新编译,那么如何解决呢?

简而言之,如何解决硬件操作不灵活的问题?

针对上述的问题, 就要应用到分离的设计思想。对于某一款芯片,引脚操作是类似的,可以写出一个通用的驱动程序,进行一个左右分离,一个定义资源(board_X.c),一个定义硬件的通用操作(chipY_gpio.c),如下图所示:

这里就抽象出一个led_resource结构体,来表达具体资源是怎么样的。(沿用面向对象的思想)

3.2 程序实战

这里相较于第二节的程序,board_imx6ull.c文件要分成两个文件board_A_led.c和chip_imx6ull_gpio.c。所以这里主要阐述led_resource.h、board_A_led.c、chip_imx6ull_gpio.c以及Makefile代码。具体实现框架如下:

3.2.1 头文件(led_resource.h)

led_resource.h里的结构体led_resource定义资源。

#ifndef _LED_RE_H
#define _LED_RE_H

/*GPIO5*/
/*bit[31:16] = group*/
/*bit[15:0]  = pin*/

//可以用以下的宏,表示GPIO引脚
#define GROUP(x)         (x>>16)
#define PIN(x)            (x&0xFFFF)
#define GROUP_PIN(g,p)    ((g<<16) | (p))

struct led_resource
{
    int pin;
};

struct led_resource *get_led_resource(void);

#endif

3.2.2 驱动程序(board_A_led.c)

这里的资源:GPIO引脚,这里初始设置为GPIO5_3。

#include "led_resource.h"

static struct led_resource board_A_led = {
    .pin = GROUP_PIN(5,3),    
};

struct led_resource * get_led_resource(void)
{
    return &board_A_led;
};

3.2.3 驱动程序(chip_imx6ull_gpio.c)

这里具体实现GPIO通用硬件操作,以GPIO5_3为例,先不涉及繁杂的硬件操作。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

#include "led_opr.h"
#include "led_resource.h"

static struct led_resource *led_rsc;

/*阐述说明:
 *现阶段仅以展示分离设计思想
 *以下的为GPIO5_3需要设置的寄存器绝对物理地址,后续可以按照基址表示GPIO组内多引脚。
 *定义好多个基址,可以实现表示多个GPIO组
*/

/*
//CCM_CCGR1:0x20C406C
static volatile unsigned int* CCM_CCGR1;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;
*/

/*
 *函数:        board_demo_led_init
 *功能:        enable gpio                 使能gpio
             configure pin as gpio    设置引脚为GPIO
             configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
             *which:哪个LED
 *返回参数:如果成功返回0
*/

static int board_demo_led_init(int which)
{
    unsigned int val;
    //printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);

    if(!led_rsc)
    {
        led_rsc = get_led_resource();
    }
    printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin));
    switch(GROUP(led_rsc->pin))
    {
        case 0:
        {
            printk("init pin of group 0 ...\n");
            break;
        }
        case 1:
        {
            printk("init pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("init pin of group 2 ...\n");
            break;
        }
        case 3:
        {
            printk("init pin of group 3 ...\n");
            break;
        }
    }
/*
    if(which == 0)
    {
        if(!CCM_CCGR1)
        {
            //ioremap:物理地址映射到虚拟地址
            CCM_CCGR1 = ioremap(0x20C406C, 4);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
            GPIO5_GDIR = ioremap(0x020AC004, 4);
            GPIO5_DR  = ioremap(0x020AC000, 4);
        }

        
        *CCM_CCGR1 |= (3<<30);
        val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
        val &= ~0xf;
        val |=  0x5;
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

        *GPIO5_GDIR |= (1<<3);
    }
*/
    return 0;
}

/*
 *函数:        board_demo_led_init
 *功能:        set gpio register: out 1/0
 *传入参数:
             *which:哪个LED
             *status: 状态1/0,亮灭
 *返回参数:如果成功返回0
*/
static int board_demo_led_ctl(int which, char status)
{
    printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin));
    switch(GROUP(led_rsc->pin))
    {
        case 0:
        {
            printk("set pin of group 0 ...\n");
            break;
        }
        case 1:
        {
            printk("set pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("set pin of group 2 ...\n");
            break;
        }
        case 3:
        {
            printk("set pin of group 3 ...\n");
            break;
        }
    }

/*
    if(which == 0)
    {
        if(status)
        {
            *GPIO5_DR &= ~(1<<3);
        }else
        {
            *GPIO5_DR |= (1<<3);
        }
    }
*/
    return 0;
}

static struct led_operations board_demo_led_opr =
{
    .num  = 1,
    .init = board_demo_led_init,
    .ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_demo_led_opr;
}

3.2.4 Makefile代码

这里有小的变动,将led_drive.c、board_A_led.c以及chip_imx6ull_gpio.c编译在一起。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f led_test

#led_drive.c、board_A_led.c、chip_imx6ull_gpio.c编译成xixiwuli.ko
xixiwuli_led-y := led_drive.o board_A_led.o chip_imx6ull_gpio.o
obj-m    += xixiwuli_led.o

3.3 运行测试

跟上面所述基本一致。

//打开
./led_test /dev/myled0 on
//关闭
./led_test /dev/myled0 off
dmesg

打印演示:

3.4 总结

经过上述分离设计思想的实践,我们会发现在需要变更要控制的引脚时(打开另一个LED时),我们只需要改动board_A_led.c就可以了。

相较于第二节的部分,拓展性和灵活性又得到了进一步的提升。

四、LED驱动程序(总线设备驱动模型)

4.1 总线设备驱动模型

上一节的内容里,我们可以进一步发现如果我们处理多个不同的设备,例如LED、LCD等等,我们都要再定义一个相应的resource.h,这个是不现实的。

基于上述的问题,提出了总线设备驱动模型,它是分离思想的进一步实现。

后续进一步采用bus总线来管理platform_device /platform_driver。如下图所示:

4.2 程序实战

在第三节程序基础上进一步进阶到总线设备驱动框架,需要抽象出platform_device和platform_driver结构体,并实现它们之间的匹配配对。

具体的各个步骤如下:

1.分配/设置/注册 platform_device结构体

  • 在里面定义所用资源,指定设备名字
  1. 分配/设置/注册 platform_driver结构体
  • 在其中probe函数里,分配/设置/注册file_operations结构体
  • 并从platform_device中确定所用硬件资源,动态实现device_create
  • 指定platform_driver的名字

相较第三节内容,展示内容改动的部分。

4.2.1 头文件(led_drive.h)

声明底层到上层的注册函数。

#ifndef _LEDDRV_H
#define _LEDDRV_H

#include "led_opr.h"

void led_device_create(int minor);
void led_device_destory(int minor);
void register_led_operations(struct led_operations *opr);

#endif /* _LEDDRV_H */

4.2.2 驱动程序(led_drive.c)

这里属于驱动程序的最上层,具体阐述和分析,详见代码。

/* 说明:
    *1,本代码是跟学韦老师课程所编写,增加了注释和分析
    *2,采用的是UTF-8编码格式
    *3,LED驱动程序(总线设备驱动模型) led_drive.c
*/

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

#include "led_opr.h"

/*第一步:确定主设备号,这里由内核分配*/
static int major = 0; 
static struct class *led_class;
struct led_operations *p_ledopr;

/*
 *阐述说明
 *定义给底层(chip_imx6ull_gpio.c)去调用的
*/
void led_device_create(int minor)
{

    device_create(led_class, NULL, MKDEV(major, minor), NULL, "myled%d", minor);
}

void led_device_destory(int minor)
{
    
    device_destroy(led_class, MKDEV(major, minor));
}

//定义底层向上层注册函数,避免交叉编译
void register_led_operations(struct led_operations *opr)
{
    p_ledopr = opr;
}
EXPORT_SYMBOL(led_device_create);
EXPORT_SYMBOL(led_device_destory);
EXPORT_SYMBOL(register_led_operations);

/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:        led_drv_open
 *差异点:      根据次设备号初始化LED
 *传入参数:
             *flip:要打开的文件
 *返回参数:如果成功返回0
*/            

static int led_drv_open(struct inode *inode, struct file *filp)
{
    int minor = iminor(inode);/*次设备号*/

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    p_ledopr->init(minor);
    return 0;
}

/*
 *函数:       led_drv_write
 *差异点:        根据次设备号和status控制LED
 *功能:        copy_from_user,从app中获取数据
 *传入参数:
             *flip:要写的文件
             *buf: 写的数据来自于buf
             *size:写多大的数据
             *offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
             size_t count, loff_t *ppos)
{
    char status;
    int err;

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = copy_from_user(&status, buf, 1);
    
    /*根据次设备号控制LED*/
    struct inode* inode = file_inode(filp);
    int minor = iminor(inode) & 0x0f;
    p_ledopr->ctl(minor, status);
    return 1;
    
}

             
static struct file_operations led_fops = {
    .owner        = THIS_MODULE,
    .write        = led_drv_write,
    .open        = led_drv_open,

};

/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *功能:        ①注册主设备号    
             ②辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
    /*
     *printk:判断一下是否调用了入口函数
     *__FILE__ :表示文件
     *__FUNCTION__ :当前函数名
     *__LINE__ :在文件的哪一行
    */    
    printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
    
        
    //0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
    major = register_chrdev(0,"100ask_led", &led_fops);    
    led_class = class_create(THIS_MODULE, "led_2345_class");
    int i;
    if (IS_ERR(led_class))
        return PTR_ERR(led_class);
    if (major < 0) {
        class_destroy(led_class);
        return major;
    }
    
    return 0;
}

/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{

    class_destroy(led_class);
    unregister_chrdev(major,"100ask_led");
}

//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

4.2.3 驱动程序(board_A_led.c)

本程序主要定义一些资源,编写实现platform_device结构体。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_resource.h"

static void led_dev_release(struct device *dev)
{
}

//定义一个资源数组
static struct resource resources[] = {
        {
                .start = GROUP_PIN(5,3),
                .flags = IORESOURCE_IRQ,
        },
        {
                .start = GROUP_PIN(3,1),
                .flags = IORESOURCE_IRQ,
        },

};

/*
 *name:平台名称
 *num_resources:资源个数
 *resource:引入资源数组
*/
static struct platform_device board_A_led_dev =
{
    .name = "100ask_led",
    .num_resources = ARRAY_SIZE(resources),
    .resource = resources,
    .dev = {
                .release = led_dev_release,
         },
        
    
};
/*入口函数,patform_device
 *功能:注册设备
*/
static int led_dev_init(void)
{
    int err;
    err = platform_device_register(&board_A_led_dev);
    return 0;
}

/*出口函数,platform_device*/
static void led_dev_exit(void)
{
    platform_device_unregister(&board_A_led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

4.2.4 驱动程序(chip_imx6ull_gpio.c)

这里主体实现platform_driver结构体。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_drive.h"
#include "led_opr.h"
#include "led_resource.h"

static int g_ledpins[100];
static int g_ledcnt = 0;

/*阐述说明:
 *现阶段仅以示意总线设备驱动模型框架
*/

/*
 *函数:        board_imx6ull_led_init
 *功能:        获取gpio引脚信息
 *输入参数:which---哪个引脚
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_init(int which)
{
    //printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
    printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
    switch(GROUP(g_ledpins[which]))
    {
        case 0:
        {
            printk("init pin of group 0 ...\n");
            break;
        }
        case 1:
        {
            printk("init pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("init pin of group 2 ...\n");
            break;
        }
        case 3:
        {
            printk("init pin of group 3 ...\n");
            break;
        }
    }

    return 0;
}

/*
 *函数:        board_imx6ull_led_init
 *功能:        打印某个gpio引脚设置信息
 *输入参数:which---哪一个引脚
             status:LED状态,1/0亮灭
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_ctl(int which, char status)
{
    printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
    switch(GROUP(g_ledpins[which]))
    {
        case 0:
        {
            printk("set pin of group 0 ...\n");
            break;
        }
        case 1:
        {
            printk("set pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("set pin of group 2 ...\n");
            break;
        }
        case 3:
        {
            printk("set pin of group 3 ...\n");
            break;
        }
    }

    return 0;
}

static struct led_operations board_imx6ull_led_opr =
{
    .init = board_imx6ull_led_init,
    .ctl  = board_imx6ull_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_imx6ull_led_opr;
}

/*
 *函数:chip_imx6ull_gpio_led_probe
 *功能:记录引脚
         创建设备,device_create
*/
static int chip_imx6ull_gpio_led_probe(struct platform_device *dev)
{
    int i = 0;
    struct resource *res;
    while(1)
    {
        //设备、哪一类资源、第几个资源
        res = platform_get_resource(dev,IORESOURCE_IRQ, i++);
        if(!res)
            break;
        g_ledpins[g_ledcnt] = res->start;

        /*创建设备*/
        led_device_create(g_ledcnt);
        g_ledcnt++;
        
    }
    return 0;
}

/*
 *函数:chip_imx6ull_gpio_led_remove
 *功能:注销设备,device_destory
*/
static int chip_imx6ull_gpio_led_remove(struct platform_device *dev)
{
    int i;
    for(i = 0; i < g_ledcnt; i++)
    {
        led_device_destory(i);
    }
    g_ledcnt = 0;
    return 0;
}

static struct platform_driver chip_imx6ull_gpio_driver = {
    .probe      = chip_imx6ull_gpio_led_probe,
    .remove     = chip_imx6ull_gpio_led_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};

/*入口函数,platform_driver
 *功能:注册设备
         底层到上层的注册
*/
static int chip_imx6ull_gpio_drv_init(void)
{
    int err;
    err = platform_driver_register(&chip_imx6ull_gpio_driver);
    register_led_operations(&board_imx6ull_led_opr);
    return 0;
}

/*出口函数,paltform_driver*/
static void chip_imx6ull_gpio_drv_exit(void)
{
    platform_driver_unregister(&chip_imx6ull_gpio_driver);
}

module_init(chip_imx6ull_gpio_drv_init);
module_exit(chip_imx6ull_gpio_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

4.2.5 Makefile代码

上述程序的变动,Makefile也有一定的改动。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f led_test
    
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m    += led_drive.o chip_imx6ull_gpio.o board_A_led.o

4.3 测试运行

//复制到开发板上
cp /mnt/board_A_led.ko ./
cp /mnt/chip_imx6ull_gpio.ko ./
cp /mnt/led_drive.ko ./
cp /mnt/led_test ./

//安装驱动模块
insmod board_A_led.ko
insmod led_drive.ko
insmod chip_imx6ull_gpio.ko
//GPIO5_3
./led_test  /dev/myled0 on
./led_test  /dev/myled0 off

//GPIO3_1
./led_test  /dev/myled1 on
./led_test  /dev/myled1 off
dmesg

打印结果:

4.4 总结

相较第三节上代码上的变化,可以感受到总线设备驱动模型框架的优点,在原有基础上进一步实现了对device和driver的分离。

在应对处理不同的设备时,这种方式就能更好的进行管理。

五、LED驱动程序(设备树)

5.1 设备树

5.1.1 设备树背景

我们从第四节的内容中发现,如果我们修改LED所用的GPIO引脚,我们需要修改board_A_led.c代码,之后重新编译加载驱动。

随着ARM芯片的流行,内核中针对不同厂商的开发保存里大量类似,没有技术含量的文件。

那么针对上述的问题,有没有好的解决方案呢?

上述问题的核心,就是在于是用.c文件来配置资源。这里就引入了专门的配置文件,这里就是用设备树来实现这一点。

采用设备树后,许多硬件的细节可以直接通过它传递给Linux,而不再需要在内核中进行大量的冗余编码,它通过bootloader将硬件资源传给内核,使得内核和文件资源描述相对独立。

最终的效果,设备在脚本里,驱动在c里。

5.1.2 设备树简述

设备树包含DTC (device tree compiler),DTS (device tree source) 和DTB (device tree blob)。

dtc、dts/dtsi和dtb的关系:
dts和dtsi源文件会经过dtc编译器编译成dtb二进制文件,dtb文件最后会被放到系统中被内核解析。

具体使用的语法和设备树解析,详细参照:

https://blog.csdn.net/qq_33487044/article/details/126325656

怎么使用设备树写驱动程序?

注意的地方:

  • 1.设备树节点与platform_driver能匹配
  • 2.设备树节点指定资源,platform_driver获得资源

5.2 程序实战

设备树内容繁杂,不在这里详细阐述。这里的程序实现简单功能和走完整个流程。

后面主要展示改动的部分程序,设备树文件、驱动程序(chip_imx6ull_gpio.c),board_A_led.c文件则是不需要了。

5.2.1 设备树dts文件(100ask_led.dts)

修改设备树,添加设备节点100ask_led@0和100ask_led@1。

#define GROUP_PIN(g,p) ((g<<16) | (p))

/ {
    100ask_led@0 {
        compatible = "100as,leddrv";
        pin = <GROUP_PIN(3, 1)>;
    };

    100ask_led@1 {
        compatible = "100as,leddrv";
        pin = <GROUP_PIN(5, 8)>;
    };

};

imx6ull pro开发板里内核源码目录中arch/arm/boot/dts/100ask_imx6ull-14x14.dts,修改编译后得到arch/arm/boot/dts/100ask_imx6ull-14x14.dtb。

详细步骤:(PC端)

5.2.2 驱动程序(chip_imx6ull_gpio.c)

这里主要设置of_match_table成员,用于设备树节点和platform_driver匹配之后。probe函数里获取资源的方式转变,通过读取设备树文件获取资源。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_drive.h"
#include "led_opr.h"
#include "led_resource.h"

static int g_ledpins[100];
static int g_ledcnt = 0;

/*阐述说明:
 *现阶段仅以示意总线设备驱动模型框架
*/

/*
 *函数:        board_imx6ull_led_init
 *功能:        获取gpio引脚信息
 *输入参数:which---哪个引脚
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_init(int which)
{
    //printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
    printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
    switch(GROUP(g_ledpins[which]))
    {
        case 0:
        {
            printk("init pin of group 0 ...\n");
            break;
        }
        case 1:
        {
            printk("init pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("init pin of group 2 ...\n");
            break;
        }
        case 3:
        {
            printk("init pin of group 3 ...\n");
            break;
        }
    }

    return 0;
}

/*
 *函数:        board_imx6ull_led_init
 *功能:        打印某个gpio引脚设置信息
 *输入参数:which---哪一个引脚
             status:LED状态,1/0亮灭
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_ctl(int which, char status)
{
    printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
    switch(GROUP(g_ledpins[which]))
    {
        case 0:
        {
            printk("set pin of group 0 ...\n");
            break;
        }
        case 1:
        {
            printk("set pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("set pin of group 2 ...\n");
            break;
        }
        case 3:
        {
            printk("set pin of group 3 ...\n");
            break;
        }
    }

    return 0;
}

static struct led_operations board_imx6ull_led_opr =
{
    .init = board_imx6ull_led_init,
    .ctl  = board_imx6ull_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_imx6ull_led_opr;
}

/*
 *函数:chip_imx6ull_gpio_led_probe
 *功能:记录引脚
         创建设备,device_create
*/
static int chip_imx6ull_gpio_led_probe(struct platform_device *dev)
{
    int i = 0;
    struct resource *res;
    struct device_node *p;
    int led_pin;

    p = dev->dev.of_node;
    if(!p)
        return -1;

    //读取pin属性的值保存在led_pin变量里
    int err = of_property_read_u32(p, "pin", &led_pin);    
    g_ledpins[g_ledcnt] = led_pin;
    
    /*创建设备*/
    led_device_create(g_ledcnt);
    g_ledcnt++;
        
    return 0;
}

/*
 *函数:chip_imx6ull_gpio_led_remove
 *功能:注销设备,device_destory
*/
static int chip_imx6ull_gpio_led_remove(struct platform_device *dev)
{
    int i;
    for(i = 0; i < g_ledcnt; i++)
    {
        led_device_destory(i);
    }
    g_ledcnt = 0;
    return 0;
}

//of_match_table成员,用于设备树节点和platform_driver的匹配上
static const struct of_device_id ask100_leds[] = {
    { .compatible = "100as,leddrv" },
    { },
};

static struct platform_driver chip_imx6ull_gpio_driver = {
    .probe      = chip_imx6ull_gpio_led_probe,
    .remove     = chip_imx6ull_gpio_led_remove,
    .driver     = {
        .name   = "100ask_led",
        .of_match_table = ask100_leds,
    },
};

/*入口函数,platform_driver
 *功能:注册设备
         底层到上层的注册
*/
static int chip_imx6ull_gpio_drv_init(void)
{
    int err;
    err = platform_driver_register(&chip_imx6ull_gpio_driver);
    register_led_operations(&board_imx6ull_led_opr);
    return 0;
}

/*出口函数,paltform_driver*/
static void chip_imx6ull_gpio_drv_exit(void)
{
    platform_driver_unregister(&chip_imx6ull_gpio_driver);
}

module_init(chip_imx6ull_gpio_drv_init);
module_exit(chip_imx6ull_gpio_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

5.2.3 Makefile代码

相较上节的内容,这里不再需要board_A_led.c。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f led_test
    
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m    += led_drive.o chip_imx6ull_gpio.o

5.3 总结

测试程序:(设备树加载情况)

//加载设备树文件
cp /mnt/100ask_imx6ull-14x14.dtb /boot/
reboot
cd /sys/firmware/devicetree/base/
ls -ld *100ask*
cd 100ask_led@0
ls
cat compatible
hexdump pin

后续led_test测试程序的运行结果跟上一章节完全一致,这里是管理资源的方式发生了转变。

** 相信大家在过完上述LED的驱动进化之路,会有一种酣畅淋漓的感觉。**


本文转载自: https://blog.csdn.net/weixin_42373086/article/details/130659276
版权归原作者 希希雾里 所有, 如有侵权,请联系我们删除。

“【Linux】遇事不决,可先点灯,LED驱动的进化之路---1”的评论:

还没有评论