设备树基础下的驱动

1、优缺点以及驱动的框架

上一个介绍的是总线驱动框架,是一种编程方式,将操作、资源、标准驱动分开,并没有使用到设备树,但是代码的框架和思路,与设备树基础下的驱动的框架和思路是一样的。

优点:各个功能进行划分开,方便对引脚进行修改,修改的时候,只需要对应修改设备树和驱动中对硬件的操作就可以了。

缺点:代码是比较冗余的。

与设备树相关的语法:设备树创建的基本语法,设备树基本性质的获得函数(大部分函数以“of_”开头,以及函数的头文件)。设备树的修改,设备树的编译,板子上设备树的修改。

设备树的修改和编译,以及板子上设备树的替代可以看自己的写的word笔记。

下面是设备树驱动代码的框架:

2、设备树结点添加代码

下面的宏要定义到设备树文件中。
#define GROUP_PIN(g,p) ((g<<16) | (p))

下面根节点里面的设备树节点的创建,也要添加到设备树里面。
设备树添加的内容与硬件是没有关系的,硬件的操作还要放在init(),ctl()函数中。
/ {
    100ask_led@0 {
        compatible = "100as,leddrv";
        pin = <GROUP_PIN(3, 1)>;
    };

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

3、chip_led.h代码

主要就是声明自己创建的chip_operation控制函数结构体。

#ifndef __chip_led_h__
#define __chip_led_h__
#include "board_led.h"
//创建的设备类,用于init和ctl函数
struct chip_operation
{
    int (*init) (int devnumber);
    int (*ctl)  (int devnumber, char state);
};
#endif

4、chip_led.c代码

主要实现了硬件操作函数、根据产生的设备树结点创建文件设备结点。

#include "chip_led.h"
#include "led_dev.h"
#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 <asm/io.h>  //ioremap()函数的头文件
#include <linux/of.h>



static int chip_board_resource[100];
static int chip_board_num=0;

//设备的基础物理地址
/* GPIO1_3     GPIO5_3 */
/* registers */
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 地址:0x02290000 + 0x08
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0;

//GPIO5_GDIR 地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;
//GPIO1_GDIR 地址:0x0209C004
static volatile unsigned int *GPIO1_GDIR;

//GPIO5_DR 地址:0x020AC000
static volatile unsigned int *GPIO5_DR;
//GPIO1_DR 地址:0x0209C000
static volatile unsigned int *GPIO1_DR;

//devnumber这个参数对应的是board_led.c文件里面的结构体的下标
int chip_dev_init (int devnumber)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    switch(devnumber)
    {
        case 0:
                IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14,4);
                GPIO5_GDIR = ioremap(0x020AC004,4);
                GPIO5_DR = ioremap(0x020AC000,4);
                //将引脚设置为gpio模式
                *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
                *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;
                //将引脚设置为输出模式
                *GPIO5_GDIR |= (1<<PIN(chip_board_resource[devnumber]));
                break;
        case 1:
                IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 = ioremap(0x02290000 + 0x08,4);
                GPIO1_GDIR = ioremap(0x0209C004,4);
                GPIO1_DR = ioremap(0x0209C000,4);
                //将引脚设置为gpio模式
                *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 &= ~0xf;
                *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 |= 0x5;
                //将引脚设置为输出模式
                *GPIO1_GDIR |= (1<<PIN(chip_board_resource[devnumber]));
                break;
        case 2:
                break;
        case 3:
                break;
        default:
                break;
    }
    return 0;
}



int chip_dev_ctl  (int devnumber, char state)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    switch(devnumber)
    {
        case 0:
                if(!state)
                    //对数据寄存器的操作。
                    *GPIO5_DR&=~(0x00000001<<PIN(chip_board_resource[devnumber]));
                else
                    *GPIO5_DR|=(0x00000001<<PIN(chip_board_resource[devnumber]));
                printk("group:%d  pin:%d  state:%d\n",GROUP(chip_board_resource[devnumber]),PIN(chip_board_resource[devnumber]),state);
                break;
        case 1:
                if(!state)
                    *GPIO1_DR&=~(0x00000001<<PIN(chip_board_resource[devnumber]));
                else
                    *GPIO1_DR|=(0x00000001<<PIN(chip_board_resource[devnumber]));
                printk("group:%d  pin:%d  state:%d\n",GROUP(chip_board_resource[devnumber]),PIN(chip_board_resource[devnumber]),state);
                break;
        case 2:
                break;
        case 3:
                break;
        default:
                break;
    }
    return 0;
}


static struct chip_operation chip_dev_operation=
{
    .init = chip_dev_init,
    .ctl  = chip_dev_ctl,
};

//这个函数没有用到。
struct chip_operation* chip_get_operation(void)
{
    return &chip_dev_operation;
}


//使用设备树进行创建设备的时候,对比上次,只需要对下面的两个函数进行修改就可以了。
//并且不需要在注册platform_device设备结构体了,系统会根据设备树进行注册设备结构体。
int chip_dev_create_device(struct platform_device *pdev)
{
    struct device_node *np;
    int res;
    int led_pin;
    //通过函数的入口参数pdev获得of_node,
    //platform_device.dev.of_node 指向 device_node, 可以通过它获得其他属性
    np = pdev->dev.of_node;
    if (!np)
    {
        return -1;
    }
    
    res = of_property_read_u32(np, "pin", &led_pin);
    chip_board_resource[chip_board_num] = led_pin;
    led_dev_create_device(chip_board_num);
    chip_board_num++;
    return 0;
}



int chip_dev_destory_device(struct platform_device *pdev)
{
    struct device_node *np;
    int res,i;
    int led_pin;
    
    np = pdev->dev.of_node;
    if (!np)
    {
        return -1;    
    }

    res = of_property_read_u32(np, "pin", &led_pin);
    for(i=0 ; i<chip_board_num ; i++)
    {
        if(led_pin == chip_board_resource[i])
        {
            led_dev_destroy_device(i);
            chip_board_resource[i]=-1;
            break;
        }
    }
    
    for(i=0 ; i<chip_board_num ; i++)
    {
        if(chip_board_resource[i] != -1)
        {
            break;
        }
    }
    
    if(i==chip_board_num)
        chip_board_num=0;
    return 0;                  
}


static const struct of_device_id ask100_leds[] = {
    { .compatible = "100as,leddrv" },
    { },
};



static struct platform_driver chip_platform_driver =
{
    .probe      = chip_dev_create_device,
    .remove     = chip_dev_destory_device,
    //这里的name要和board中的结构体中的name相同才能进行匹配
    .driver     = {
        .name   = "100ask_led",
        .of_match_table = ask100_leds,  //表示支持设备树
    },
};


static int __init chip_gpio_drv_init(void)
{
    int err;    
    err = platform_driver_register(&chip_platform_driver);
    //这里要调用驱动代码里面的上面chip_dev_operation结构体的获得函数。
    led_dev_chip_operation(&chip_dev_operation);
    return 0;
}


static void __exit lchip_gpio_drv_exit(void)
{
    platform_driver_unregister(&chip_platform_driver);    
}


module_init(chip_gpio_drv_init);
module_exit(lchip_gpio_drv_exit);

MODULE_LICENSE("GPL");


5、led_dev.h代码

struct chip_operation结构体函数的获得函数,文件设备结点的创建、删除函数。

#ifndef __led_dev_h__
#define __led_dev_h__

#include "chip_led.h"
void led_dev_create_device(int which);//创建次设备节点
void led_dev_destroy_device(int which);//删除次设备节点
void led_dev_chip_operation(struct chip_operation* oper);//让led_dev.c文件获得operation结构体
//上面的三个函数都是chip_led.c文件里面进行调用的

#endif

6、led_dev.c代码

主要实现主设备号的创建,设备类的创建,文件设备结点的创建、删除函数,struct chip_operation结构体获得函数的实现,文件的打开、关闭、读、写代码的实现。

#include "led_dev.h"
#include "chip_led.h"
#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>

static int major = 0;//主设备号
static struct class *led_class;//设备类
static struct chip_operation* led_chip_operation;//init和ctl的控制函数的结构体

void led_dev_create_device(int which)
{
    device_create(led_class, NULL, MKDEV(major, which) , 0, "mydevled%d", which);
}

void led_dev_destroy_device(int which)
{
    device_destroy(led_class, MKDEV(major, which));
}

void led_dev_chip_operation(struct chip_operation* oper)
{
    led_chip_operation = oper;
}

//EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,
//不用修改内核代码就可以在您的内核模块中直接调用,
//即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
EXPORT_SYMBOL(led_dev_create_device);
EXPORT_SYMBOL(led_dev_destroy_device);
EXPORT_SYMBOL(led_dev_chip_operation);






static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{    //变量的定义最好在函数的最前面,不然会产生不符合C90的语法错误
    int err;
    char mystate;
    struct inode *inode;
    int minor;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    //根据应用层传来的fd获得次设备的名字
    inode = file_inode(file);
    //根据次设备的名字获得次设备号
    minor = iminor(inode);
    
    err = copy_from_user(&mystate, buf, 1);
    //printk("minor:%d   state:%d\n",minor,mystate);
    //调用chip_led.c文件中的结构体内函数,进行控制
    //得到的minor是board_led.c文件中的结构体的下标。
    led_chip_operation->ctl(minor,mystate);
    return 0;
}

static int led_drv_open (struct inode *node, struct file *file)
{
    int minor;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    //根据传入的设备驱动的名称获得次设备号
    minor = iminor(node);
    //调用chip_led.c文件中的结构体内函数,进行初始化
    //得到的minor是board_led.c文件中的结构体的下标。
    led_chip_operation->init(minor);
    return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static struct file_operations led_file_operation=
{
    .owner     = THIS_MODULE,
    .open    = led_drv_open,
    .read    = led_drv_read,
    .write   = led_drv_write,
    .release = led_drv_close,
};

static int __init led_init(void)
{    //注册函数里面只注册了主设备号、创建了设备类,并没有创建次设备节点和设备号
    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    major = register_chrdev(0, "100ask_led_dev", &led_file_operation);
    led_class = class_create(THIS_MODULE, "100ask_led_dev");
    err = PTR_ERR(led_class);
    if (IS_ERR(led_class))
    {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(major, "100ask_led_dev");
        return -1;
    }
    return 0;
}

static void __exit led_exit(void)
{    //只注销了设备类和主设备节点。
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    class_destroy(led_class);
    unregister_chrdev(major, "100ask_led_dev");
}

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

7、led_test.c代码

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


/*
    ./les_test /dev/mydevled0 on
    ./les_test /dev/mydevled0 off
    ./les_test /dev/mydevled1 on
    ./les_test /dev/mydevled1 off
*/
int main(int argc, char** argv)
{
    int fd;
    unsigned char data;
    if(argc != 3)
    {
        printf("Usage:%s is err\n",argv[0]);
        printf("      ./led_test /dev/mydevled0 on\n");
        printf("      ./led_test /dev/mydevled0 off\n");
        return -1;
    }
    
    fd = open(argv[1], O_RDWR);
    if(fd == -1)
    {
        printf("%s can not open\n",argv[1]);
        return -1;
    }
    
    if(strcmp(argv[2],"on") == 0)
    {
        data=0;
        write(fd, &data, 1);
    }else if(strcmp(argv[2],"off") == 0)  //这里最好判断一下,不然识别不了off
    {
        data=1;
        write(fd, &data, 1);  //写入数据的格式和位数要注意,后面copy_from_user()要对应获得数据
    }
    return 0;
}

8、Makefile代码

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/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 += chip_led.o led_dev.o
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值