目录
一、驱动的分隔与分离
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结构体
目前代码如下
对于 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 等等都会采用驱动分隔的方式来简化驱动的开发。
将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获
取到设备信息),然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可,这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离(驱动-总线-设备 或 设备-驱动-总线)。总线就是驱动和设备信息的月老,负责给两者牵线搭桥
当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。 Linux 内核中大量的驱动程序都采用总线、驱动和设备模式
Linux 下的驱动往往也是分层的,分层的目的也是为了在不同的层处理不同的内容。
上面说到了总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、 SPI、 USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?
为了解决此问题, Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device
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 总线进行匹配
文件如下左,makefile修改编译名即可
添加头文件
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 驱动。返回值: 无。
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函数
这里没有需要的操作,就打印一个字符串
定义REGISTER_LENGTH为4,方便下面计算长度
start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址, name 表示资源名字(这里没用到), flags 表示资源类型,如果只有一段内粗那就定义一段内存即可,这里有无五段寄存器地址,所以要定义五个
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
文件名 : 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"
存在就说明设备 创建好了,下面编写驱动
把leddevice.c复制一份为leddriver.c,留下头文件和驱动出入口及其函数即可
修改makefile
和注册卸载设备一样,只不过使用的函数名有点变化
44行,name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是否相同
47行,platform 驱动的 probe 函数,当驱动与设备匹配成功以后此函数就会执行,以前在驱动入口 init 函数里面编写的字符设备驱动程序就全部放到此 probe 函数里面。比如注册字符设备驱动、添加 cdev、创建类等等。
48行,platform_driver 结构体中的 remove 成员变量,当关闭 platfor备驱动的时候此函数就会执行,以前在驱动卸载 exit 函数里面要做的事情就放到此函数中来。比如,使用 iounmap 释放内存、删除 cdev,注销设备号等等
不管是先加载驱动还是先加载设备,只有驱动匹配到设备,就会执行proe函数,退出就会执行remove函数
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
文件名 : 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
#include
#include
#include
#include
#include
#include
/*
argc:应用程序参数个数(argv数组元素个数)
argv:具体参数,也可以写作char **argv
./platledAPP <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;
}
有设备树的时候设备是由设备树描述的,因此不需要向总线注册设备,而是直接修改设备树。
只需要修改设备树,然后编写驱动
在“pinctl和gpio子系统”实验中,设备树已经添加,如下
先写下基本的出入口和platform注册和卸载
48行,name是在无设备树时用于和设备进行匹配,也就是上面写的实验,也作为驱动名字
49行,of_match_table就是用来和设备树匹配的,驱动和设备匹配成功以后,设备信息就会从设备树节点转为platform_device结构体,在led_of_math结构体数组中实现,也就是41-44行
41-44行,compatible就是设备树上的compatible,就是根据这个信息在设备树上匹配,如果设备树上有多条compatible信息,可以分开添加都driver上的compatible,只要其中有一条字符串相匹配即可匹配,/* sentinel */这个是用来表示结尾的,默认这样写即可
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
文件名 : 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
文件名 : 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不变