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设备程序。
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、资源、资源数量等相关设备信息。
static int __init leddevice_init(void)
{
return platform_device_register(&leddevice);
}
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;
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,
};
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) {
led0_switch(LEDON);
}else if(ledstat == LEDOFF) {
led0_switch(LEDOFF);
}
return 0;
}
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev;
return 0;
}
????紧接着,编写led_probe函数,当驱动与设备匹配后,就会调用函数,它的功能如下:
- 获取设备资源;
- 进行管脚映射,完成硬件方面的工作;
- 注册字符设备驱动,对第一步中定义的设备结构体leddev中的成员进行初始化,如创建设备号、创建cdev结构体、创建类、创建设备。
static int led_probe(struct platform_device *dev)
{
int i = 0;
int ressize[5];
u32 val = 0;
struct resource *ledsource[5];
printk("led driver and device has matched!\r\n");
for (i = 0; i < 5; i++) {
ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
if (!ledsource[i]) {
dev_err(&dev->dev, "No MEM resource for always on\n");
return -ENXIO;
}
ressize[i] = resource_size(ledsource[i]);
}
IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);
writel(5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR);
val |= (1 << 3) ;
writel(val, GPIO1_DR);
if (leddev.major) {
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
} else {
alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
}
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
if (IS_ERR(leddev.class)) {
return PTR_ERR(leddev.class);
}
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
if (IS_ERR(leddev.device)) {
return PTR_ERR(leddev.device);
}
return 0;
}
????编写platform驱动的remove函数,它会取消地址映射,删除cedv结构体,注销设备号,完成设备的卸载、类的卸载。
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);
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
????剩下的就是编写platform的驱动结构体和init以及exit函数。
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led",
},
.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);
}
3. 测试
????测试APP的编写过程与前面LED驱动的APP基本一致,就不过多赘述。直接进行测试。
depmod //第一次加载驱动的时候需要运行此命令
modprobe leddevice.ko //加载设备模块
modprobe leddriver.ko //加载驱动模块
????在/sys/bus/platform/devices/目录下查询有没有设备文件,下面的情况说明设备加载成功。 ????紧接着在/sys/bus/platform/drivers/查询是否有驱动文件,有的话说明驱动模块加载成功。 ????加载成功后platfomr总线就会进行匹配,匹配成功如下图所示。 ????输入以下命令进行测试:
./ledApp /dev/platled 1 //打开 LED 灯
./ledApp /dev/platled 0 //关闭 LED 灯
rmmod leddevice.ko
rmmod leddriver.ko
4. 总结
leddevice.c:
- 定义platform设备结构体,存放名字、id、设备资源等;
- init函数完成设备的注册,exit函数完成设备的注销。
leddrivers.c:
- 定义一个led设备结构体,存放设备号、类、cdev结构体等;
- 定义file_operations结构体存放操作函数如open、write;
- 编写led_probe函数,匹配成功后,进行管脚的映射,设备资源的加载,并进行字符设备驱动的注册,就是对第一步中led设备结构体的初始化;
- 编写led_remove函数,取消地址映射,进行字符设备的卸载,类的卸载;
- 编写驱动结构体,并编写驱动模块卸载函数、驱动模块加载函数。
|