我们再谈一下平台总线。
前面讲平台总线是可以让drv和dev实现分离。主要作用是用于平台升级。
现在结合设备树来看一下 平台总线的好处。(平台总线是虚拟总线,用于一些ADC,PWM这样的没有实体总线连接(iIC spi…)的一些设备,为了管理这些设备统一虚拟出平台总线来管理)
1)首先我们要了解到
当我们在设备树文件上写了应该设备后(属于我们的直接定义的设备,可以有2^20个子设备(前面说可以放无限个是错误的)
它就会把设备树的信息放在/sys/bus/plaoform/device下(那我要是iic设备就会放在/sys/bus/i2c下)
2)这样我们只要写DRV程序就可以了,利用platfrom根据compatinle进行匹配,然后用
platform_get_resource拿到硬件信息;
platform_get_irq拿到中断号
这样就可以在驱动程序中拿到硬件资源了,并且我们只需要写驱动程序,而硬件信息只需要修改设备树就可以了。
就不用像以前一样,还要写dev写drv写bus。。。这么麻烦。
———————————————————————————————————————
申请设备号的三种方式:
1)
register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
在 2.4 的内核我们使用 register_chrdev(0, “hello”, &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 file_operation 结构体,也就是说内核最多支持 256 个字符设备驱动程序。
- register_chrdev_region(dev, 1, FSPWM_DEV_NAME);
cdev_init(&fspwm->cdev, &fspwm_ops);
cdev_add(&fspwm->cdev, dev, 1);
register_chrdev_region() 函数用于分配指定的设备编号范围。它需要手动初始化cdev并且将cdev加入到内核的cdev链表中。
3)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
alloc_chrdev_region() 函数用于动态申请设备编号范围。
——————————————————————————————————————
创建设备节点的两种方式。
对设备进行操作,其实就是对cdev进行一些操作。cdev里面有主设备号以及file_operatorion.
那么创建设备节点就可以便于用户找到主设备号并进行file_operatorion.操作。
1)手动创建:利用命令 :mknod /dev/(你自定义设备的名字) c(字符设备)/d(块设备) 主设备号(一定要和你申请的一样)
2)自动创建:对于热插拔事件,我们应用程序要完成,插入加载模块,并且自动创建设备节点,拔自动卸载模块,和注销设备节点。
对于普通事件我们也一般采用自动创建的方式。
:class_create(THIS_MODULE,“xxx”);
:device_create(…);
————————————————————————————————————————
我们还是讲怎么ADC驱动吧!!!!
平台总线驱动的另一种模板:
我们之前的都是module_init module_exit。。。实例化应该drv,设置匹配的complation 在init里面把drv注册进去,在exit里面移除。
。。。。千篇一律。。
那么我们在内核中,把这种固定的用平台总线的驱动封装了一个函数
module_platform_driver(fspwm_drv);
然后我们就可以这样做:
tatic const struct of_device_id fspwm_of_matches[] = {
{
.compatible = "fs4412,fspwm", },
{
/* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fspwm_of_matches);
struct platform_driver fspwm_drv = {
.driver = {
.name = "fspwm",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(fspwm_of_matches),
},
.probe = fspwm_probe,
.remove = fspwm_remove,
};
module_platform_driver(fspwm_drv);
其实module_platform_driver(fspwm_drv);里面也调用了init 和exit就是进行了封装而已。
——————————————————————————————————————
我们还是讲ADC驱动吧。。。这有什么讲的?我们模型都有了,驱动不是照套吗?
1)写设备树。
2)按上面的编写驱动代码模型。匹配成功之后调用proble。
3)在proble里面申请设备号,创建设备节点,获取设备信息。地址映射。
这样我们就拿到硬件信息了,还有虚拟地址。
那么不管他是ADC,PWM,KEY(当然key是用input子系统好一点),Led…都是对一些地址进行一些读写操作。
那我们来看一下对ADC需要哪些操作?
我们在proble下面肯定要对一些寄存器进行初始化:
writel((1 << 16) | (1 << 14) | (19 << 6), fsadc->adccon);
1<< 16是设置精度为12;
1<<14使能分频
19<<6设置为20倍分频。
初始化好了,之后我们就该操作了。我们要的是把继电器中连续变化的模拟量转化成数字量
可以看到我们的电位器连接到ain3口。所以我们要选择adcmux为0011
我们去读这个ADCDAT的0~11位就可以读到结果,
当我们数据转化的时候就会触发中断去读ADCDAT的数据,然后读取完毕,就会利用clrint清除中断。
————————————————详情请看arm——ADC裸机开发
驱动代码:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include "fsadc.h"
#define FSADC_MAJOR 256
#