ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2022-08-18 23:30:08  阅读:167  来源: 互联网

标签:GPIO1 led platform BASE Linux device 驱动 include


在上面一章我们借助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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有