系统相关
首页 > 系统相关> > Linux驱动开发十三.platform设备驱动——2.无设备树的platform驱动

Linux驱动开发十三.platform设备驱动——2.无设备树的platform驱动

作者:互联网

在上面一章我们借助Linux驱动分离和分层的概念引出来驱动——总线——设备的概念,然后引出来了platform这种基于设备驱动模型的驱动架构,我们下面通过使用来演示下platform架构是怎么使用的。

前面说过,platform驱动架构的设备端分为支持设备树和不支持设备树两种模式,下面我们先看看如何不借助设备树实现platform框架的驱动构建。

platform设备框架

我们先完成platform设备的基础框架。因为没有设备树,我们需要在驱动代码中使用platform_device结构体来描述我们需要操作都设备。但是platform_device结构体里的成员变量不是所有的我们都需要指定,这里只需要指定我们需要对内容就行了。

 1 struct platform_device leddevice = {
 2     .name = "imx6ull-led",
 3     .id = -1,       //-1表示设备无ID
 4     .dev = {
 5        .release =  leddevice_release,
 6     },
 7     .num_resources = ARRAY_SIZE(led_resources),     //资源大小
 8     .resource = led_resources,
 9 };
10 
11 static int __init leddevice_init(void)
12 {
13     //注册platform设备
14     return platform_device_register(&leddevice);
15 }
16 
17 static void __exit leddevice_exit(void)
18 {
19     platform_device_unregister(&leddevice);
20 }
21 
22 module_init(leddevice_init);
23 module_exit(leddevice_exit);
24 MODULE_LICENSE("GPL");
25 MODULE_AUTHOR("ZZQ");

注意一下我们在加载模块的函数中(11行)使用了plotform_device_register函数来将新的设备注册到内核里,函数的参数就是我们定义的platform_device结构体变量。同样在卸载模块时我们也要使用platform_device_unregister函数将相关资源释放掉。

设备资源

我们这次使用LED作为驱动对应的设备,在前面讲Linux驱动的时候,我们操作LED使用了5个GPIO相关的寄存器,这5个物理寄存器就是我们需要对硬件资源

 1 /**
 2  * @brief 寄存器物理地址
 3  * 
 4  */
 5 #define CCM_CCGR1_BASE          (0X020C406C)
 6 #define SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
 7 #define SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
 8 #define GPIO1_GDIR_BASE         (0X0209C004)
 9 #define GPIO1_DR_BASE           (0X0209C000)
10 
11 #define REGSITER_LENGTH         4
12 
13 //5个内存段
14 static struct resource led_resources[] = {
15     [0] = {
16     .start = CCM_CCGR1_BASE,
17     .end   = CCM_CCGR1_BASE + REGSITER_LENGTH -1,
18     .flags = IORESOURCE_MEM,
19     },
20     [1] = {
21     .start = SW_MUX_GPIO1_IO03_BASE,
22     .end   = SW_MUX_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
23     .flags = IORESOURCE_MEM,
24     },
25     [2] = {
26     .start = SW_PAD_GPIO1_IO03_BASE,
27     .end   = SW_PAD_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
28     .flags = IORESOURCE_MEM,
29     },
30     [3] = {
31     .start = GPIO1_GDIR_BASE,
32     .end   = GPIO1_GDIR_BASE + REGSITER_LENGTH -1,
33     .flags = IORESOURCE_MEM,
34     },
35     [4] = {
36     .start = GPIO1_DR_BASE,
37     .end   = GPIO1_DR_BASE + REGSITER_LENGTH -1,
38     .flags = IORESOURCE_MEM,
39     }, 
40 };

我们需要将操作的寄存器实际物理地址拿过来,还有个第11行的宏REGISTER_LENGTH表示我们一个寄存器是32位的占用了4个字节。这个资源的定义是在结构体resources里(include/linux/ioport.h)

 1 /*
 2  * Resources are tree-like, allowing
 3  * nesting etc..
 4  */
 5 struct resource {
 6     resource_size_t start;
 7     resource_size_t end;
 8     const char *name;
 9     unsigned long flags;
10     struct resource *parent, *sibling, *child;
11 };

主要用到成员就是start,即资源起始地址,end,资源结束地址,对于内存类型的资源,就是内存的起始和结束地址。还有flags,就是资源类型在下面定义了好多宏可以供我们使用,我们在程序中使用的IORESOURCE_REG就告诉我们该资源属于寄存器相关的,还有其他类型的。

#define IORESOURCE_IRQ        0x00000400
#define IORESOURCE_DMA        0x00000800
#define IORESOURCE_BUS        0x00001000

上面的就是总线类型的、DMA的还有中断类型

如果我们只有一个寄存器或者一段连续的寄存器进行操作,就可以定义一个resource变量,但是可以注意到LED操作的一批寄存器地址是不相连的,所以定义了一个结构体数组来表示。一般使用中,我们都是用结构体数组来表示可用资源的。

在上面的数组中,每个元素都是1个物理寄存器,寄存器结束地址是在起始地址上加了长度+3(起始地址为0时,0~3一共4个字节表示1个寄存器),还有flag是表示资源为寄存器类型。

platform设备结构体

现在回头看看我们声明的platform_device变量

1 struct platform_device leddevice = {
2     .name = "imx6ull-led",
3     .id = -1,       //-1表示设备无ID
4     .dev = {
5        .release =  leddevice_release,
6     },
7     .num_resources = ARRAY_SIZE(led_resources),     //资源大小
8     .resource = led_resources,
9 };

这里只声明了比较重要的几个成员

name是设备的名称,驱动就是通过这个name来匹配设备的。所以这个name和驱动里的name必须一致。

id作用我暂时还不了解,但是-1表示该设备无ID

dev是device结构体,一般来说我们需要对release变量绑定一个函数,表示释放platform_device的时候执行的函数。我们可以简单定义一个函数看一下执行过程

1 void leddevice_release(struct device *dev)
2 {
3     printk("device release\r\n");
4 }

重要的是后面两个和资源有关系的成员,num_resource表示资源的数量,我们用来一个函数ARRAY_SIZE获取了指定的资源对象的大小,对这段程序来说就是led_resources数组的大小。

最后就是使用的资源。我们用了5个寄存器,就把前面写Linux驱动时候LED使用的5个寄存器的宏定义拿过来定义在resource结构体里就行了。

这样就完成了基础的platform设备信息的构建

/**
 * @file leddevice.c
 * @author your name (you@domain.com)
 * @brief platform设备基础框架
 * @version 0.1
 * @date 2022-08-17
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/ide.h>
#include <linux/platform_device.h>

/**
 * @brief 寄存器物理地址
 * 
 */
#define CCM_CCGR1_BASE          (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
#define GPIO1_GDIR_BASE         (0X0209C004)
#define GPIO1_DR_BASE           (0X0209C000)

#define REGSITER_LENGTH         4

void leddevice_release(struct device *dev)
{
    printk("device release\r\n");
}

//5个内存段
static struct resource led_resources[] = {
    [0] = {
    .start = CCM_CCGR1_BASE,
    .end   = CCM_CCGR1_BASE + REGSITER_LENGTH -1,
    .flags = IORESOURCE_MEM,
    },
    [1] = {
    .start = SW_MUX_GPIO1_IO03_BASE,
    .end   = SW_MUX_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
    .flags = IORESOURCE_MEM,
    },
    [2] = {
    .start = SW_PAD_GPIO1_IO03_BASE,
    .end   = SW_PAD_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
    .flags = IORESOURCE_MEM,
    },
    [3] = {
    .start = GPIO1_GDIR_BASE,
    .end   = GPIO1_GDIR_BASE + REGSITER_LENGTH -1,
    .flags = IORESOURCE_MEM,
    },
    [4] = {
    .start = GPIO1_DR_BASE,
    .end   = GPIO1_DR_BASE + REGSITER_LENGTH -1,
    .flags = IORESOURCE_MEM,
    }, 
};



struct platform_device leddevice = {
    .name = "imx6ull-led",
    .id = -1,       //-1表示设备无ID
    .dev = {
       .release =  leddevice_release,
    },
    .num_resources = ARRAY_SIZE(led_resources),     //资源大小
    .resource = led_resources,
};

static int __init leddevice_init(void)
{
    //注册platform设备
    return platform_device_register(&leddevice);
}

static void __exit leddevice_exit(void)
{
    platform_device_unregister(&leddevice);
}

module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZZQ");
platform设备基础框架

这个设备框架完成好以后,修改make生成ko模块文件,复制到目标目录下加载一下,可以看到在bus目录下有我们定义的设备(imx6ull-led),对应device的name属性

 

 这时候就完成了设备端的构建,下面要我们来做驱动端。

platform驱动框架

在完成platform的设备端以后,我们要学习驱动的构建。

 

 1 /**
 2  * @file leddriver.c
 3  * @author your name (you@domain.com)
 4  * @brief platfrom驱动框架
 5  * @version 0.1
 6  * @date 2022-08-18
 7  * 
 8  * @copyright Copyright (c) 2022
 9  * 
10  */
11 #include <linux/module.h>
12 #include <linux/kernel.h>
13 #include <linux/init.h>
14 #include <linux/fs.h>
15 #include <linux/uaccess.h>
16 #include <linux/io.h>
17 #include <linux/types.h>
18 #include <linux/cdev.h>
19 #include <linux/device.h>
20 #include <linux/of.h>
21 #include <linux/of_address.h>
22 #include <linux/of_irq.h>
23 #include <linux/gpio.h>
24 #include <linux/of_gpio.h>
25 #include <linux/irq.h>
26 #include <linux/interrupt.h>
27 #include <linux/fcntl.h>
28 #include <linux/ide.h>
29 #include <linux/platform_device.h>
30 
31 
32 static int led_probe(struct platform_device *dev)
33 {
34     printk("led driver device match\r\n");
35     return 0;
36 }
37 
38 static int led_remove(struct platform_device *dev)
39 {
40     printk("led driver remove\r\n");
41     return 0;
42 }
43 
44 
45 static struct platform_driver leddriver = {
46     .driver = {
47         .name = "imx6ull-led",      //驱动name,在和dev匹配时候使用
48     },
49     .probe = led_probe,
50     .remove = led_remove,
51 };
52 
53 static int __init leddriver_init(void)
54 {
55     //注册platform驱动
56     return platform_driver_register(&leddriver);
57 }
58 
59 static void __exit leddriver_exit(void)
60 {
61     //卸载platform驱动
62     platform_driver_unregister(&leddriver);
63 }
64 
65 module_init(leddriver_init);
66 module_exit(leddriver_exit);
67 MODULE_LICENSE("GPL");
68 MODULE_AUTHOR("ZZQ");

 驱动的框架很简单,主要就是platform_driver的定义,先把驱动的结构体拿出来

1 static struct platform_driver leddriver = {
2     .driver = {
3         .name = "imx6ull-led",      //驱动name,在和dev匹配时候使用
4     },
5     .probe = led_probe,
6     .remove = led_remove,
7 };

没有什么复杂的,一个成员变量是device_driver结构体类型的driver,主要是用来个name成员来和device里的name来进行匹配,所以值必须是imx6ull-led,还有个probe,是在匹配完成后执行的函数,remove是驱动卸载后执行的函数。

 make以后加载一下模块,看看效果。下面是分别加载设备和驱动,以及分别卸载设备和驱动的效果

在卸载驱动和设备的时候都会执行驱动里的remove函数,卸载device的时候会执行device文件里的leddevice_exit函数,所以多打印了一行信息。

驱动端资源获取

因为我们在设备中定义了寄存器资源,也就是点亮LED时候需要用到的5个寄存器,在驱动中是要获取到这5个寄存器地址的。在驱动中有个函数是实现这个功能的

extern struct resource *platform_get_resource(struct platform_device *,
                          unsigned int, unsigned int);

参数platform是要获取资源的dev。第二个整形参数是要获取资源的类型(对应资源里的flags成员属性)。第三个整形数据是要获取指定类型数据的索引。

 1 //内存映射后的地址指针
 2 static void __iomem *IMX6UL_CCM_CCGR1;
 3 static void __iomem *IMX6UL_SW_MUX_GPIO1_IO03;
 4 static void __iomem *IMX6UL_SW_PAD_GPIO1_IO03;
 5 static void __iomem *IMX6UL_GPIO1_GDIR;
 6 static void __iomem *IMX6UL_GPIO1_DR;
 7 
 8 int i=0;
 9 struct resource *ledsource[5];
10 
11 for(i;i<5;i++)
12 {
13     ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
14     if(ledsource[i] == NULL)
15         {
16         return -EINVAL;
17         }
18 }
19 
20 IMX6UL_CCM_CCGR1 = ioremap(ledsource[0]->start,resource_size(ledsource[0]));
21 IMX6UL_SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start,resource_size(ledsource[1]));
22 IMX6UL_SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start,resource_size(ledsource[2]));
23 IMX6UL_GPIO1_GDIR = ioremap(ledsource[3]->start,resource_size(ledsource[3]));
24 IMX6UL_GPIO1_DR = ioremap(ledsource[4]->start,resource_size(ledsource[3]));

因为我们用的寄存器资源一共5组,就用for循环5次获取了对应的寄存器。最后5行是在拿到了物理寄存器地址以后进行映射拿到映射后的地址指针。在映射的时候,使用了source结构体里的start元素,表示内存起始地址,还有个函数可以直接用来获取资源的大小

1 static inline resource_size_t resource_size(const struct resource *res)
2 {
3     return res->end - res->start + 1;
4 }

当然我们也可以直接用每个资源成员变量end-start+1的方法获取到资源的大小。

在前面说过,这个我们主要去编写的驱动是probe里面要执行的内容,所以获取资源、设备初始化设么的都要在这个probe函数里。并且这个platform框架是不包含dev下面节点生成的功能的,我们还需要把以前那个字符设备框架放过来,可以单做一个函数放在前面,然后再probe里面调用一下就可以了,然后模块卸载的过程,包括ioremap资源的释放,可以都放在remove 对应的函数中。因为字符设备框架还涉及到文件操作集合,我就把前面讲linux驱动一开始写led那个部分的文件操作集合直接拿过来了

/**
 * @file leddriver.c
 * @author your name (you@domain.com)
 * @brief platfrom驱动框架
 * @version 0.1
 * @date 2022-08-18
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/ide.h>
#include <linux/platform_device.h>

#define LEDOFF  0
#define LEDON   1

#define DEV_NAME "platform_led"
#define DEV_COUNT 1
//内存映射后的地址指针
static void __iomem *IMX6UL_CCM_CCGR1;
static void __iomem *IMX6UL_SW_MUX_GPIO1_IO03;
static void __iomem *IMX6UL_SW_PAD_GPIO1_IO03;
static void __iomem *IMX6UL_GPIO1_GDIR;
static void __iomem *IMX6UL_GPIO1_DR;


struct new_dev
{
    dev_t dev_id;
    int major;
    int minor;
    struct class *class;
    struct device *device;
    struct cdev cdev;
    struct device_node *dev_nd;
    int dev_gpio;
};

struct new_dev dev;


static int led_open(struct inode *inode, struct file *filp)
{
    printk("dev open!\r\n");
    return 0;
}


static ssize_t led_read(struct file *filp, 
                               __user char *buf,
                               size_t count, 
                               loff_t *ppos)
{
    int ret = 0;
    printk("dev read data!\r\n");

    if (ret == 0){
        return 0;
    }
    else{
        printk("kernel read data error!");
        return -1;
    }
}

static void led_switch(u8 sta)
{
    printk("led sta change %d\r\n",sta);
    u32 val = 0;    
    if(sta == LEDON){
        val = readl(IMX6UL_GPIO1_DR);
        val &= ~(1<<3);
        writel(val,IMX6UL_GPIO1_DR);
    }
    else if(sta == LEDOFF){
        val = readl(IMX6UL_GPIO1_DR);
        val |= (1<<3);
        writel(val,IMX6UL_GPIO1_DR);
    }
}


/**
 * @brief 改变LED状态
 * 
 * @param file 
 * @param buf 
 * @param count 
 * @param ppos 
 * @return ssize_t 
 */
static ssize_t led_write(struct file *file, 
                        const char __user *buf, 
                        size_t count, 
                        loff_t *ppos)
{   
    int ret = 0;
    printk("led write called\r\n");
    unsigned char databuf[1];                   //待写入的参数
    ret = copy_from_user(databuf,buf,count);    //获取从用户空间传递来的参数

    if (ret == 0){
        led_switch(databuf[0]);                 //根据参数改变LED状态
    }
    else{
        printk("kernelwrite err!\r\n");
        return -EFAULT;
    }
} 

static struct file_operations dev_fops= {
    .owner = THIS_MODULE,
    .open = led_open,
    // .release = led_release,
    .read = led_read,
    .write = led_write,
};


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

    dev.major =0;   //主设备号设置为0,由系统分配设备号
    
    /*程序中未经指定设备号,直接注册设备*/
    if(dev.major){                      
        dev.dev_id = MKDEV(dev.major,0);                        //调用MKDEV函数构建设备号
        ret = register_chrdev_region(dev.dev_id,1,DEV_NAME);    //注册设备
    }
    /*程序中未指定设备号,申请设备号*/
    else{
        ret = alloc_chrdev_region(&dev.dev_id,0,1,DEV_NAME);
        dev.major = MAJOR(dev.dev_id);
        dev.minor = MINOR(dev.dev_id);
    }
    if(ret<0){
        printk("new device region err!\r\n");
        return -1;
    }
    printk("dev_t = %d,major = %d,minor = %d\r\n",dev.dev_id,dev.major,dev.minor);

    dev.cdev.owner = THIS_MODULE;

    cdev_init(&dev.cdev, &dev_fops);

    ret = cdev_add(&dev.cdev,dev.dev_id, 1);

    dev.class = class_create(THIS_MODULE,DEV_NAME);
    if (IS_ERR(dev.class))
        {return PTR_ERR(dev.class);}

    dev.device = device_create(dev.class, NULL,dev.dev_id,NULL,DEV_NAME);
    if (IS_ERR(dev.device))
        {return PTR_ERR(dev.device);}
    printk(&dev.device);

    return 0;
}




static int led_probe(struct platform_device *dev)
{
    printk("led driver device match\r\n");
    
    int i=0;
    int ret = 0;
    unsigned int val = 0;
    struct resource *ledsource[5];

    for(i;i<5;i++){
    ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
    if(ledsource[i] == NULL){
        return -EINVAL;
    }
    }

    IMX6UL_CCM_CCGR1 = ioremap(ledsource[0]->start,resource_size(ledsource[0]));
    IMX6UL_SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start,resource_size(ledsource[1]));
    IMX6UL_SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start,resource_size(ledsource[2]));
    IMX6UL_GPIO1_GDIR = ioremap(ledsource[3]->start,resource_size(ledsource[3]));
    IMX6UL_GPIO1_DR = ioremap(ledsource[4]->start,resource_size(ledsource[3]));

    val = readl(IMX6UL_CCM_CCGR1);  //读取CCM_CCGR1的值
    val &= ~(3<<26);                //清除bit26、27
    val |= (3<<26);                //bit26、27置1
    writel(val, IMX6UL_CCM_CCGR1);
    printk("CCM init finished!\r\n");

    /*GPIO初始化*/
    writel(0x5, IMX6UL_SW_MUX_GPIO1_IO03);
    writel(0x10B0, IMX6UL_SW_PAD_GPIO1_IO03);

    val = readl(IMX6UL_GPIO1_GDIR);
    val |= 1<<3;                        //bit3=1,设置为输出
    writel(val, IMX6UL_GPIO1_GDIR);

    val = readl(IMX6UL_GPIO1_DR);
    val &= ~(1<<3);
    writel(val,IMX6UL_GPIO1_DR);

    dev_init();
    return 0;
}

static int led_remove(struct platform_device *plt_dev)
{
    printk("led driver remove\r\n");

    iounmap(IMX6UL_CCM_CCGR1);
    iounmap(IMX6UL_SW_MUX_GPIO1_IO03);
    iounmap(IMX6UL_SW_PAD_GPIO1_IO03);
    iounmap(IMX6UL_GPIO1_DR);
    iounmap(IMX6UL_GPIO1_GDIR);
    cdev_del(&dev.cdev);
    //注销设备号
    unregister_chrdev_region(dev.dev_id,1);
    device_destroy(dev.class,dev.dev_id);
    class_destroy(dev.class);

    return 0;
}


static struct platform_driver leddriver = {
    .driver = {
        .name = "imx6ull-led",      //驱动name,在和dev匹配时候使用
    },
    .probe = led_probe,
    .remove = led_remove,
};


static int __init leddriver_init(void)
{
    //注册platform驱动
    return platform_driver_register(&leddriver);
}

static void __exit leddriver_exit(void)
{
    //鞋子platform驱动
    platform_driver_unregister(&leddriver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZZQ");
platform驱动led

整个驱动就是这样了,最后把操作led的APP也重新搞过来,在加载完成led的驱动和设备以后,就可以通过APP文件操作LED的点亮与熄灭了

 

标签:GPIO1,led,platform,BASE,Linux,device,驱动,include
来源: https://www.cnblogs.com/yinsedeyinse/p/16594804.html