跳转至

02.platform设备驱动实验

说明

总结该方法编程有两种方式,第一种是设备树还没有的时候使用的是建立device文件和driver文件,device文件中写资源定义,driver文件中写字符设备的相关驱动。第二种就是设备树有了之后,就不需要写device文件,设备树会自动生成,只需要写driver文件然后相关名字和设备树定义的名字对应。

旧方式编程

device文件编写

主要完善platform_device设备结构体,

name              设备名字,注意和driver中的名字对应
id                id号
dev               设备相关函数   
    release
num_resources     资源数量
resource          资源,如寄存器地址等

代码编写:

#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 <linux/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 
 * 寄存器地址定义
 */
#define CCM_CCGR1_BASE              (0X020C406C)    
#define SW_MUX_GPIO1_IO03_BASE      (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE      (0X020E02F4)
#define GPIO1_DR_BASE               (0X0209C000)
#define GPIO1_GDIR_BASE             (0X0209C004)
#define REGISTER_LENGTH             4

/* @description     : 释放flatform设备模块的时候此函数会执行   
 * @param - dev     : 要释放的设备 
 * @return          : 无
 */
static void led_release(struct device *dev)
{
    printk("led device released!\r\n"); 
}

/*  
 * 设备资源信息,也就是LED0所使用的所有寄存器
 */
static struct resource led_resources[] = {
    [0] = {
        .start  = CCM_CCGR1_BASE,
        .end    = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
        .flags  = IORESOURCE_MEM,
    },  
    [1] = {
        .start  = SW_MUX_GPIO1_IO03_BASE,
        .end    = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
        .flags  = IORESOURCE_MEM,
    },
    [2] = {
        .start  = SW_PAD_GPIO1_IO03_BASE,
        .end    = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
        .flags  = IORESOURCE_MEM,
    },
    [3] = {
        .start  = GPIO1_DR_BASE,
        .end    = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
        .flags  = IORESOURCE_MEM,
    },
    [4] = {
        .start  = GPIO1_GDIR_BASE,
        .end    = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
        .flags  = IORESOURCE_MEM,
    },
};

/*
 * platform设备结构体 
 */
static struct platform_device led_device = {
    .name = "imx6ul-led",
    .id = -1,
    .dev = {
        .release = &led_release,
    },
    .num_resources = ARRAY_SIZE(led_resources),
    .resource = led_resources,
};

static int __init led_device_init(void)
{
    return platform_device_register(&led_device);
}

static void __exit led_device_exit(void)
{
    platform_device_unregister(&led_device);
}

module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");

driver文件编写

主要完善platform_device设备结构体,

driver              
    name             驱动名字,注意和device中的名字对应
probe                初始化执行的函数
remove               移除驱动执行的函数   

还有就是获取device中的资源。

driver主要代码编写:

static int led_probe(struct platform_device *dev)
{   
    int i = 0;
    int ressize[5];
    u32 val = 0;
    struct resource *ledsource[5];

    printk("led driver and device has matched!\r\n");
    /* 1、获取资源 */
    for (i = 0; i < 5; i++) {
        ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */
        if (!ledsource[i]) {
            dev_err(&dev->dev, "No MEM resource for always on\n");
            return -ENXIO;
        }
        ressize[i] = resource_size(ledsource[i]);   
    }   

    /* 2、初始化LED */
    /* 寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
    SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
    SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
    GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
    GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);

    /* 初始化LED相关 */

    /* 注册字符设备驱动 */

    return 0;
}

static int led_remove(struct platform_device *dev)
{
    /*取消映射 删除设备等*/
    return 0;
}

/* platform驱动结构体 */
static struct platform_driver led_driver = {
    .driver     = {
        .name   = "imx6ul-led",         /* 驱动名字,用于和设备匹配 */
    },
    .probe      = led_probe,
    .remove     = led_remove,
};

/*
 * @description : 驱动模块加载函数
 * @param       : 无
 * @return      : 无
 */
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/*
 * @description : 驱动模块卸载函数
 * @param       : 无
 * @return      : 无
 */
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");

设备树式编程

设备树式编程将device的生成工作交给了设备树,所以我们只需要修改driver相关的代码即可。

设备树文件编写

在设备树根下添加节点

alientekled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "alientek-led";
    status = "okay";
    reg = <
            0X020C406C 0X04/* CCM_CCGR1_BASE */
            0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
            0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
            0X0209C000 0X04 /* GPIO1_DR_BASE */
            0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};

属性#address-cells 和#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。 属性 compatbile 设置 alphaled 节点兼容性为“atkalpha-led”。 属性 status 设置状态为“okay”。 如“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地址为 0X020C406C,长度为 4 个字节。

然后编译dts。

make dtbs

然后将其放到tftpboot目录下,因为我启动用的tftp

cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ~/linux/tftpboot/ -f

可以进入到/proc/device-tree/base下查看是否有alphaled这个节点信息。

可以进入到/sys/firmware/devicetree/base/alphaled下查看是否有alphaled这个节点的相关信息,可以查看一下 compatible、status 等属性值是否和我们设置的一致。

然后再在驱动开发中的注册的时候。就可以通过of相关的函数获取设备信息。

如先定义一个设备节点

struct device_node *nd;

然后就可以通过节点名,获得节点信息

nd = of_find_node_by_path("/alphaled");

通过节点,就能够获取里面的各个节点信息

struct property *proper;
//获得compatible属性内容
proper = of_find_property(nd, "compatible", NULL);

int ret;
const char *str;
//获得status属性内容
ret = of_property_read_string(nd, "status", &str);

u32 regdata[14];
//获得reg属性内容
ret = of_property_read_u32_array(nd, "reg", regdata, 10);

driver文件编写

platform_driver结构体多加了一个匹配表信息,匹配表里面的compatible信息要和节点信息一样。

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
    { .compatible = "alientek-led" },
    { /* Sentinel */ }
};

static struct platform_driver led_driver = {
    .driver = {
        .name = "alientekled",
        .of_match_table = led_of_match
    },
    .probe  = led_probe,
    .remove = led_remove
};

然后就是获取数据的那一块代码变了,有两种方式。

第一种: 1、读节点信息 2、读取节点中的reg信息 3、ioremap地址映射

第二种: 1、读节点信息 2、of_iomap地址映射

driver主要不同的代码编写:

static int led_probe(struct platform_device *dev)
{   
    uint32_t val = 0;
    uint32_t regdata[14];
    int ret;

    //读节点信息,有两种方式
    led.nd = of_find_node_by_path("/alientekled");//正点原子
    //led.nd = dev->dev.of_node;                    //韦东山

    //第一种 读REG然后寄存器地址映射
    ret = of_property_read_u32_array(led.nd, "reg", regdata, 10);
    if(ret < 0) {
        printk("reg property read failed!\r\n");
    } 

    IMX6U_CCM_CCGR1   = ioremap(regdata[0], regdata[1]);
    SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
    SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
    GPIO1_DR          = ioremap(regdata[6], regdata[7]);
    GPIO1_GDIR        = ioremap(regdata[8], regdata[9]);

    // 第二种 使用节点信息,寄存器地址映射
    // IMX6U_CCM_CCGR1 = of_iomap(led.nd, 0);
    // SW_MUX_GPIO1_IO03 = of_iomap(led.nd, 1);
    // SW_PAD_GPIO1_IO03 = of_iomap(led.nd, 2);
    // GPIO1_DR = of_iomap(led.nd, 3);
    // GPIO1_GDIR = of_iomap(led.nd, 4);

    /* 初始化LED相关 */

    /* 注册字符设备驱动 */

    return 0;
}