1. 驱动的分离和分层
1.1 驱动的分离
假设平台A、B、C都有MPU6050这个IIC接口的六轴传感器,那么驱动框架应该如上图所示。主机驱动是必须的,但设备驱动都是相同的,没必要每个平台都写一个驱动。
那么最好的做法就是每个平台的I2C控制器都提供一个统一的接口,每个设备也只提供一个驱动程序,每个设备通过统一的I2C接口驱动来访问,这样就大大方便了。
实际的I2C驱动设备肯定有很多种,所以实际的架构如上图所示。
这就是驱动的分离,就是将主机驱动和设备驱动分离开来,相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法获取到设备信息(比如从设备树中获取设备信息),根据获取到的信息来初始化设备。
这就相当于驱动只负责驱动,设备只负责设备,想办法将两者匹配即可,这就是Linux中的总线(bus)、设备(device)、驱动(driver)模型。总线就是帮助设备和驱动之间进行牵线搭桥。
1.3 驱动的分层
Linux下的驱动也是分层的,不同层会处理不同的内容。以input为例介绍一下驱动的分层。input子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸屏等,最底层的就是设备的原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给input核心层,input核心层会处理各种IO模型,并且提供file_operations操作集合,我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用管。
2. 实验程序编写
2.1 leddevice.c编写
首先我们来编写platform设备程序。
/*
* platform设备结构体
*/
static struct platform_device leddevice = {
.name = "imx6ul-led",
.id = -1,
.dev = {
.release = &led_release,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
需要定义一个platform设备结构体,其中存放有设备的名称、id、资源、资源数量等相关设备信息。
/*
* @description : 设备模块加载
* @param : 无
* @return : 无
*/
static int __init leddevice_init(void)
{
return platform_device_register(&leddevice);
}
/*
* @description : 设备模块注销
* @param : 无
* @return : 无
*/
static void __exit leddevice_exit(void)
{
platform_device_unregister(&leddevice);
}
在init中完成设备的注册,在exit注销设备。
2.2 leddriver.c编写
接下来开始编写platform驱动文件,首先是定义一个设备结构体。
struct leddev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
};
还需要定义file_operations结构体,存放设备操作函数。
/* 设备操作函数 */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移