platform 设备驱动实验

目录

一、驱动的分隔与分离

1、分隔

2、分离

二、驱动的分层

1、platform 平台驱动模型简介

2、platform 总线

三、设备(device)编写

1、创建工作区

 3、添加设备入口与出口​编辑

4、设备注册与卸载

 5、platform_driver 结构体变量

6、实现leddevice_release函数 

  7、定义寄存器地址

8、实现led_resource函数

代码如下

验证

四、驱动(driver)编写之不用设备树版

 1、注册卸载驱动

2、platform驱动结构体及其函数

验证

3、probe 函数

无设备树版代码如下

五、使用设备树下编写platform

 编写driver.c

platform_driver结构体

目前代码如下


一、驱动的分隔与分离

1、分隔

        对于 Linux 这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了 Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久Linux 内核的文件数量就庞大到无法接受的地步。

        假如现在有三个平台 A、 B 和 C,这三个平台(这里的平台说的是 SOC)上都有 MPU6050 这个 I2C 接口的六轴传感器,按照我们写裸机 I2C 驱动的时候的思路,每个平台都有一个MPU6050的驱动,因此编写出来的最简单的驱动框架如下图

         每种平台下都有一个主机驱动和设备驱动,主机驱动肯定是必须要的,毕竟不同的平台其 I2C 控制器不同。但是右侧的设备驱动就没必要每个平台都写一个,因为不管对于那个 SOC 说, MPU6050 都是一样,通过 I2C 接口读写数据就行了,只需要一个 MPU6050 的驱动程序即可。如果再来几个 I2C 设备,比如 AT24C02、 FT5206(电容触摸屏)等,如果按照上图的写法,那么设备端的驱动将会重复的编写好几次。显然在 Linux 驱动程序中这种写法是不推荐的,最好的做法就是每个平台的 I2C 控制器都提供一个统一的接口(也叫做主机驱动),每个设备的话也只提供一个驱动程序(设备驱动),每个设备通过统一的 I2C接口驱动来访问,这样就可以大大简化驱动文件,如下

         实际的 I2C 驱动设备肯定有很多种,不止 MPU6050 这一个,那么实际的驱动架构如下

         这个就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来,比如 I2C、 SPI 等等都会采用驱动分隔的方式来简化驱动的开发。

2、分离

        将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获
取到设备信息),然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可,这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离(驱动-总线-设备 或 设备-驱动-总线)。总线就是驱动和设备信息的月老,负责给两者牵线搭桥

 当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。 Linux 内核中大量的驱动程序都采用总线、驱动和设备模式

二、驱动的分层

        Linux 下的驱动往往也是分层的,分层的目的也是为了在不同的层处理不同的内容。

1、platform 平台驱动模型简介

        上面说到了总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、 SPI、 USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?

为了解决此问题, Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device

2、platform 总线

        Linux系统内核使用bus_type结构体表示总线,此结构体定义在文件include/linux/device.h,其中有个match 函数,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,主要工作就是完成总线下的设备和驱动之间的匹配,因此每一条总线都必须实现此函数。 match 函数有两个参数: dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动

platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c

struct bus_type platform_bus_type = {
         .name = "platform",
         .dev_groups = platform_dev_groups,
         .match = platform_match,
         .uevent = platform_uevent,
         .pm = &platform_dev_pm_ops,
 };

platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数

驱动和设备的匹配有四种方法:

        ①OF 类型的匹配,也就是设备树采用的匹配方式。of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行

        ② ACPI 匹配方式
        ③id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型

        ④第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功

所以,编写代码的时候,就要一个驱动.c文件,一个设备.c文件,通过platform 总线进行匹配

三、设备(device)编写

1、创建工作区

文件如下左,makefile修改编译名即可

添加头文件

 3、添加设备入口与出口

4、设备注册与卸载

26行,在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量以及注册

35行,利用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,原型如下

int platform_driver_register (struct platform_driver *driver)

driver:要注册的 platform 驱动。返回值: 负数,失败; 0,成功

 42行,退出的时候要卸载,通过 platform_driver_unregister 函数卸载 platform 驱动,原型如下

void platform_driver_unregister(struct platform_driver *drv)

drv:要卸载的 platform 驱动。返回值: 无。

 5、platform_driver 结构体变量

70行,名字。name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设
备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-gpio”,那么此 name
字段也要设置为“xxx-gpio”。

71行,led等没有id,写-1

73行,释放资源,需要实现leddevice_release函数(这里可以不写,写出来方便感受执行过程)

75行,表示资源数量,也就是76行资源大小。会通过ARRAY_SIZE函数计算出led_resource结构体数组的资源

76行,资源,也就是设备信息。用来表示某一段内存。需要实现led_resource函数

6、实现leddevice_release函数 

这里没有需要的操作,就打印一个字符串 

  7、定义寄存器地址

定义REGISTER_LENGTH为4,方便下面计算长度 

8、实现led_resource函数

 start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址, name 表示资源名字(这里没用到), flags 表示资源类型,如果只有一段内粗那就定义一段内存即可,这里有无五段寄存器地址,所以要定义五个

代码如下

#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/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
/*
    文件名		: leddevice.c
    描述	   	: platform设备
*/

/* 寄存器物理地址 */
#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 			: 无
 */
void leddevice_release(struct device *dev)
{
    printk("leddevice_release\r\n");
}
/*  
 * 设备资源信息,也就是LED0所使用的所有寄存器
 * start 和 end 分别表示资源的起始和终止信息(内存起始和终止地址)
 * flags 表示资源类型
 */
static struct resource led_resource[] = {
    [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 leddevice = {
    .name   =   "imx6ull-led",
    .id     =   -1,/*表示此设备无ID*/
    .dev    =   {
                .release    =   leddevice_release,
    },
    .num_resources = ARRAY_SIZE(led_resource),
    .resource     = led_resource,

};

/*设备(模块)加载*/
static int __init leddevice_init(void)
{
    /*注册platfrom设备*/
    return platform_device_register(&leddevice);
}

/*设备(模块)卸载*/
static void __exit leddevice_exit(void)
{
    /*卸载platfrom设备*/
    platform_device_unregister(&leddevice);
}

module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

验证

启动开发板加载该驱动,进入下面路径

cd /sys/bus/platform/devices/ 

 查看是否存在"imx6ull-led",这个名字就是上面platform 设备结构体里面的 "name"

存在就说明设备 创建好了,下面编写驱动

四、驱动(driver)编写之不用设备树版

把leddevice.c复制一份为leddriver.c,留下头文件和驱动出入口及其函数即可

 修改makefile

 1、注册卸载驱动

 和注册卸载设备一样,只不过使用的函数名有点变化

2、platform驱动结构体及其函数

44行,name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是否相同

47行,platform 驱动的 probe 函数,当驱动与设备匹配成功以后此函数就会执行,以前在驱动入口 init 函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面。比如注册字符设备驱动、添加 cdev、创建类等等。

48行,platform_driver 结构体中的 remove 成员变量,当关闭 platfor备驱动的时候此函数就会执行,以前在驱动卸载 exit 函数里面要做的事情就放到此函数中来。比如,使用 iounmap 释放内存、删除 cdev,注销设备号等等

验证

 不管是先加载驱动还是先加载设备,只有驱动匹配到设备,就会执行proe函数,退出就会执行remove函数

3、probe 函数

  111行,定义一个指针数组

 115-123行,利用循环分别获取到设备上面的资源,原型如下

struct resource *platform_get_resource(struct platform_device *dev,

                                                                unsigned int type, unsigned int num)

功能:从设备中获取相关资源。
dev:平台设备。描述设备信息的,有设备树描述。
type: 资源类型。也就是设备资源信息中的flags
num: 资源索引。

125-129行,内存映射(注意每一段都要名字对应)。用resource_size函数计算映射的空间的大小

 其余的代码都和之前的点灯一样,只不过把以前在驱动入口 init 函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面,把驱动卸载 exit 函数里面要做的事情就放到此函数中来,包括测试APP以及效果都一样,下面直接附上驱动和APP代码

测试就是加载APP,对设备发送0或1

无设备树版代码如下

驱动

#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/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
/*
    文件名		: leddriver.c
    描述	   	: platform驱动
*/

#define LEDOFF 			0 /*关闭*/
#define LEDON 			1 /*打开*/
#define LEDDEV_CNT		1			/* 设备号长度 	*/
#define LEDDEV_NAME		"platled"	/* 设备名字 	*/

/* 寄存器名 (地址映射后的虚拟地址指针)*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* leddev设备结构体 */
struct leddev_dev{
	dev_t devid;			/* 设备号	*/
	struct cdev cdev;		/* cdev		*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备		*/
	int major;				/* 主设备号	*/	
    int minor;	            /* 次设备号	*/
};
struct leddev_dev leddev; 	/* led设备 */

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON){
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF){
		val = readl(GPIO1_DR);
		val |= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev;/*设置私有数据*/
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */
	if(ledstat == LEDON) {
		led_switch(LEDON);		/* 打开LED灯 */
	}else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	    /* 关闭LED灯 */
	}
	return 0;
}

/*设备操作集*/
static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,
    .open   =   led_open,
    .write  =   led_write,
};


static int led_probe(struct  platform_device *dev)
{
    int i=0, ret=0;
    unsigned int val = 0;
    struct resource *ledsource[5];
    
    /*初始化led,字符设备驱动*/
    /*1,从设备中获取资源*/
    for(i=0;i<5;i++)
    {/* 依次获取MEM类型资源 */
        ledsource[i] = platform_get_resource(dev , IORESOURCE_MEM , i);
        if(ledsource[i] == NULL)
        {
            return -ENXIO;
        }

    }
    /*内存映射*/
    IMX6U_CCM_CCGR1     =   ioremap(ledsource[0]->start,resource_size(ledsource[0]));
    SW_MUX_GPIO1_IO03   =   ioremap(ledsource[1]->start,resource_size(ledsource[1]));
    SW_PAD_GPIO1_IO03   =   ioremap(ledsource[2]->start,resource_size(ledsource[2]));
    GPIO1_DR            =   ioremap(ledsource[3]->start,resource_size(ledsource[3]));
    GPIO1_GDIR          =   ioremap(ledsource[4]->start,resource_size(ledsource[4]));

    val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);				/* 清除以前的设置 */
	val |= (3 << 26);				/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */
	writel(5, SW_MUX_GPIO1_IO03);
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);			/* 清除以前的设置 */
	val |= (1 << 3);			/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 默认关闭LED1 */
	val = readl(GPIO1_DR);
	val |= (1 << 3) ;	
	writel(val, GPIO1_DR);

    /*2,注册字符设备*/
    leddev.major = 0;
    if(leddev.major){/*给定设备号*/
        leddev.devid = MKDEV(leddev.major,0);
        ret = register_chrdev_region(leddev.devid,LEDDEV_CNT,LEDDEV_NAME);
    }else{/*未给定设备号*/
        ret = alloc_chrdev_region(&leddev.devid,0,LEDDEV_CNT,LEDDEV_NAME);
        leddev.major = MAJOR(leddev.devid);
        leddev.minor = MINOR(leddev.devid);
    }
    if(ret < 0)
    {
        printk("leddev chrdev error \r\n");
        goto fail_devid;
    }
    printk("leddev major =%d .minor = %d\r\n", leddev.major, leddev.minor);
    /*3,注册字符设备*/
    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev,&led_fops);
    ret = cdev_add(&leddev.cdev,leddev.devid,LEDDEV_CNT);
    if(ret < 0)
    {
        goto fail_cdev;
    }
    /*4,自动创建设备节点*/
        /*创建类 */
    leddev.class = class_create(THIS_MODULE,LEDDEV_NAME);
    if(IS_ERR(leddev.class))
    {
        ret = PTR_ERR(leddev.class);
        goto fail_class;
    }
    /*创建设备 */
    leddev.device = device_create(leddev.class,NULL,
                                leddev.devid,NULL,LEDDEV_NAME);
    if(IS_ERR(leddev.device))
    {
        ret = PTR_ERR(leddev.device);
        goto fail_device;
    }                          
    printk("led driver proe\r\n");
    return 0;
fail_device:
    class_destroy(leddev.class);
fail_class:
    cdev_del(&leddev.cdev);
fail_cdev:
    unregister_chrdev_region(leddev.devid,LEDDEV_CNT);
fail_devid:
    return ret;

};
static int led_remove(struct platform_device *dev)
{
    /*取消地址映射*/
    iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	cdev_del(&leddev.cdev);/*  删除cdev */
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
	device_destroy(leddev.class, leddev.devid);
	class_destroy(leddev.class);
	
    printk("led driver remove\r\n");
    return 0;
}

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

/*驱动(模块)加载*/
static int __init leddriver_init(void)
{
    /*注册platfrom驱动*/
    return platform_driver_register(&led_driver);
}

/*驱动(模块)卸载*/
static void __exit leddriver_exit(void)
{
    /*卸载platfrom驱动*/
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

app

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

/*
    argc:应用程序参数个数(argv数组元素个数)
    argv:具体参数,也可以写作char **argv
    ./platledAPP <filename> <0:1>   0表示关灯,1表示开灯
    ./platledAPP  /dev/platled 0    关灯
    ./platledAPP  /dev/platled 1    开灯
*/
int main(int argc, char *argv[])
{
    int fd,retvalue;
    char *filename;
    unsigned char databuf[1];

    /*判断命令行输入参数是否正确*/
    if(argc != 3){
        printf("error usage!\r\n");
        return -1;
    }
    /*用指针指向文件*/
    filename = argv[1];
    /*打开文件*/
    fd = open(filename , O_RDWR);
    if(fd < 0){
        printf("file open failed\r\n",filename);
        return -1;
    }
    /*获取控制开关的数字*/
    databuf[0] = atoi(argv[2]);/*将字符转换为数字*/
    /*如果输入的控制命令不是1或者0直接退出*/
    if(((int)databuf[0]) < 0 || ((int)databuf[0]) >1)
    {
        printf("control parameter error\r\n");
        close(fd);
        return -1;
    }
    /*写入操作*/
    retvalue = write(fd,databuf,sizeof(databuf));
    if(retvalue < 0){
        printf("LED control failed\r\n");
        close(fd);
        return -1;
    }
    /*关闭文件*/
    close(fd);

    return 0;
}

五、使用设备树下编写platform

有设备树的时候设备是由设备树描述的,因此不需要向总线注册设备,而是直接修改设备树。

只需要修改设备树,然后编写驱动

在“pinctl和gpio子系统”实验中,设备树已经添加,如下

 编写driver.c

先写下基本的出入口和platform注册和卸载

platform_driver结构体

 48行,name是在无设备树时用于和设备进行匹配,也就是上面写的实验,也作为驱动名字

49行,of_match_table就是用来和设备树匹配的,驱动和设备匹配成功以后,设备信息就会从设备树节点转为platform_device结构体,在led_of_math结构体数组中实现,也就是41-44行

41-44行,compatible就是设备树上的compatible,就是根据这个信息在设备树上匹配,如果设备树上有多条compatible信息,可以分开添加都driver上的compatible,只要其中有一条字符串相匹配即可匹配,/* sentinel */这个是用来表示结尾的,默认这样写即可

目前代码如下

#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/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
/*
    文件名		: leddriver.c
    描述	   	: platform驱动
*/

static int led_probe(struct platform_device *dev)
{
    printk("led probe\r\n");
    return 0;
}
static int led_remove(struct platform_device *dev)
{
    printk("led_remove\r\n");
    return 0;
}
struct of_device_id led_of_math[] = {
    {.compatible = "my,my_gpioled"},
    {/* sentinel */},
};

struct platform_driver led_driver = { 
    .driver = {
        .name = "imx6ull-led",/*无设备树时用于和设备进行匹配,也是驱动名字*/
        .of_match_table = led_of_math,/*设备树匹配表*/
    },
    .probe = led_probe,
    .remove = led_remove,
};

/*驱动(模块)加载*/
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/*驱动(模块)卸载*/
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

编译加载在开发板能打印出probe和卸载的remove即说明匹配成功

        剩下的操作和“pinctl和gpio子系统”实验一样,把以前在驱动入口 init 函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面,把驱动卸载 exit 函数里面要做的事情就放到此函数中来,包括测试APP以及效果都一样。所以直接附上如下代码

#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/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
/*
    文件名		: leddriver.c
    描述	   	: platform驱动
*/

#define LEDDEV_CNT		1				/* 设备号长度 	*/
#define LEDDEV_NAME		"dtsplatled"	/* 设备名字 	*/
#define LEDOFF 			0
#define LEDON 			1

/*leddev设备结构体*/
struct leddev_dev {
    dev_t   devid;               /* 设备号*/
    int     major;               /* 主设备号*/
    int     minor;               /* 次设备号*/
    struct cdev cdev;            /* cdev*/
    struct class *class;         /*类   */
    struct device *device;       /*设备 */
    struct device_node *nd;    /*设备节点*/
    int led_gpio;                /* led所使用的GPIO编号*/
};
struct leddev_dev leddev;       /* led设备 */
/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	if (sta == LEDON )
		gpio_set_value(leddev.led_gpio, 0);
	else if (sta == LEDOFF)
		gpio_set_value(leddev.led_gpio, 1);	
}
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &leddev;/*设置私有数据*/
    return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
	unsigned char databuf[2];
	unsigned char ledstat;
    retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {

		printk("kernel write failed!\r\n");
		return -EFAULT;
	}
	
	ledstat = databuf[0];
	if (ledstat == LEDON) {
		led_switch(LEDON);
	} else if (ledstat == LEDOFF) {
		led_switch(LEDOFF);
	}
	return 0;
}
/*设备操作集*/
static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,
    .open   =   led_open,
    .write  =   led_write,
};

static int led_probe(struct platform_device *dev)
{
    int ret=0;
    printk("led probe\r\n");
    /*1、设置设备号*/
    leddev.major = 0;
    if(leddev.major){/*给定设备号*/
        leddev.devid = MKDEV(leddev.major,0);
        ret = register_chrdev_region(leddev.devid,LEDDEV_CNT,LEDDEV_NAME);
    }else{/*未给定设备号*/
        ret = alloc_chrdev_region(&leddev.devid,0,LEDDEV_CNT,LEDDEV_NAME);
        leddev.major = MAJOR(leddev.devid);
        leddev.minor = MINOR(leddev.devid);
    }
    if(ret < 0){
        goto fail_devid;
    }
    printk("gpioled major=%d ,gpioled minor =%d\r\n",leddev.major,leddev.minor);
    /*初始化cdev*/
    leddev.cdev.owner = THIS_MODULE;
    cdev_init(&leddev.cdev,&led_fops);
     /*向 Linux 系统添加这个字符设备*/
    ret = cdev_add(&leddev.cdev,leddev.devid,LEDDEV_CNT);
    if(ret < 0){
        goto fail_cdev;
    }
    /*设备文件节点的自动创建与删除*/
    /*创建类*/
    leddev.class = class_create(THIS_MODULE,LEDDEV_NAME);
    if(IS_ERR(leddev.class)){
        ret = PTR_ERR(leddev.class);
        goto fail_class;
    }
    /*在类下创建设备,生成/dev/dtsplatled这个设备文件*/
    leddev.device = device_create(leddev.class,NULL,leddev.devid,NULL,LEDDEV_NAME);
    if(IS_ERR(leddev.device)){
        ret = PTR_ERR(leddev.device);
        goto fail_device;
    }
#if 0
    /*获取设备节点*/
    leddev.nd = of_find_node_by_path("/gpioled");
#endif
    leddev.nd = dev->dev.of_node;/*获取设备节点*/
    if(leddev.nd == NULL){
        ret = -EINVAL;
        goto fail_findnd;
    }

    /*获取led所对应的gpio*/
    leddev.led_gpio = of_get_named_gpio(leddev.nd,"led-gpios",0);
    if(leddev.led_gpio<0){
        printk("can't find led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnd;
    }
    printk("led gpio num =%d\r\n",leddev.led_gpio);

    /*申请GPIO*/
    ret = gpio_request(leddev.led_gpio,"led-gpio");
    if(ret ){
        printk("led gpio request failed\r\n");
        ret = EINVAL;
        goto fail_findnd;
    }
    /*使用IO,设置为输出*/
    ret = gpio_direction_output(leddev.led_gpio,1);/*设置默认高电平*/
    if(ret){
        goto fail_setoutput;
    }
    /*输出低电平,点亮LED*/
    gpio_set_value(leddev.led_gpio,0);


    return 0;
fail_setoutput:
    gpio_free(leddev.led_gpio);
fail_findnd:
fail_device:
    class_destroy(leddev.class);
fail_class:
    cdev_del(&leddev.cdev);
fail_cdev:
    unregister_chrdev_region(leddev.devid,LEDDEV_CNT);
fail_devid:
    return ret;
}
static int led_remove(struct platform_device *dev)
{
    gpio_set_value(leddev.led_gpio, 1); 	/* 卸载驱动的时候关闭LED */
	gpio_free(leddev.led_gpio);				/* 释放IO 			*/

	cdev_del(&leddev.cdev);				/*  删除cdev */
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
	device_destroy(leddev.class, leddev.devid);/*摧毁设备*/
	class_destroy(leddev.class);/*删除类*/

    printk("led_remove\r\n");
    return 0;
}
/* 匹配列表 */
struct of_device_id led_of_math[] = {
    {.compatible = "my,my_gpioled"},
    {/* sentinel */},
};
/* platform驱动结构体 */
struct platform_driver led_driver = { 
    .driver = {
        .name = "imx6ull-led",/*无设备树时用于和设备进行匹配,也是驱动名字*/
        .of_match_table = led_of_math,/*设备树匹配表*/
    },
    .probe = led_probe,
    .remove = led_remove,
};

/*驱动(模块)加载*/
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/*驱动(模块)卸载*/
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

因为是使用设备树,所以不需要编写device.c,测试的APP不变

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值