不同架构有不同指令集
不同的系统级芯片的若干控制器的使用方法不同
鼠标,打印机等设备的控制器使用方法一样
platform总线就是使用 platform_match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备
platform_match(设备,驱动) //platform总线匹配设备和驱动
platform_driver 结构体描画platform驱动(Driver 驱动程序,司机,车手,驾驶员)
struct platform_driver {
int (*probe)(struct platform_device *); //当驱动与设备匹配成功以后 probe 函数就会执行
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; //device_driver 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量
const struct platform_device_id *id_table; //id_table 是个表(也就是数组)
bool prevent_deferred_probe;
};
platform 设备- -传感器,打印机,鼠标等等
platform_device 结构体中的resource 结构体resource 表示资源,也就是设备信息,比如外设寄存器等。
struct resource {
resource_size_t start; //资源的起始信息
resource_size_t end; //资源的终止信息
const char *name; //资源名字
unsigned long flags; //资源类型
struct resource *parent, *sibling, *child;
};
platform_device 结构体来段描述设备,现在早都改用设备树去描述了(Device 设备,装置)
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override;
struct mfd_cell *mfd_cell;
struct pdev_archdata archdata;
};
platform框架的API函数
int platform_driver_register (要注册的platform驱动) //向Linux内核注册一个 platform 驱动 void platform_driver_unregister(要拆卸的platform驱动) //向Linux内核拆卸一个 platform 驱动 int platform_match(设备,驱动) //platform总线匹配设备和驱动 int platform_device_register(要注册的platform设备) //向Linux内核注册一个platform设备 void platform_device_unregister(要拆卸的platform设备) //向Linux内核拆卸一个platform设备 传统过时的拆卸设备的过程如下: cdev_del(); unregister_chrdev_region(); device_destroy(); class_destroy();platform框架使用示例 platform 设备框架
#define PERIPH1_REGISTER_BASE (0x20000000)
#define PERIPH2_REGISTER_BASE (0x020E0068)
#define REGISTER_LENGTH 4 //寄存器长度
static struct resource xxx_resources[] = { //一共有两个资源
[0] = {
.start = PERIPH1_REGISTER_BASE, //资源的起始信息为外设1寄存器首地址
.end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),//资源的终止信息为外设1寄存器首地址+寄存器长度-1
.flags = IORESOURCE_MEM, //资源为内存类型的
},
[1] = {
.start = PERIPH2_REGISTER_BASE, //资源的起始信息为外设2寄存器首地址
.end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),//资源的终止信息为外设2寄存器首地址+寄存器长度-1
.flags = IORESOURCE_MEM, //资源为内存类型的
},
};
static struct platform_device xxxdevice = {
.name = "xxx-gpio", //注意 name 字段要和所使用的驱动中的 name 字段一致,否则驱动和设备无法匹配成功
.id = -1,
.num_resources = ARRAY_SIZE(xxx_resources),//num_resources 表示资源大小,其实就是数组 xxx_resources的元素数量
.resource = xxx_resources,
};
static int __init xxxdevice_init(void)
{
return platform_device_register(&xxxdevice); //向 Linux 内核注册一个 platform 驱动,Register 注册帐户,寄存器,登记
}
static void __exit xxx_resourcesdevice_exit(void)
{
platform_device_unregister(&xxxdevice); //向 Linux 内核拆卸一个 platform 驱动,Register 注册帐户,寄存器,登记
}
module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
platform 驱动框架
struct xxx_dev{
struct cdev cdev;
};
struct xxx_dev xxxdev;
static int xxx_open(struct inode *inode, struct file *filp) //字符设备驱动open操作函数
{
return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)//字符设备驱动write操作函数
{
return 0;
}
static struct file_operations xxx_fops = { //字符设备驱动操作集,注册字符设备驱动要指针xxx_fops
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
};
static int xxx_probe(struct platform_device *dev) // platform驱动的probe函数,驱动与设备匹配成功以后此函数就会执行
{
......
cdev_init(&xxxdev.cdev, &xxx_fops);
return 0;
}
static int xxx_remove(struct platform_device *dev) //当关闭 platform备驱动的时候此函数就会执行,使用 iounmap 释放内存、删除 cdev,注销设备号等等
{
......
cdev_del(&xxxdev.cdev);
return 0;
}
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx-gpio" }, //匹配项的 compatible 值为“xxx-gpio”,因此当设备树中设备节点的 compatible 属性值为“xxx-gpio”的时候此设备就会与此驱动匹配
{ } //of_device_id 表最后一个匹配项必须是空的
};
static struct platform_driver xxx_driver = {
.driver = {
.name = "xxx", //name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同
.of_match_table = xxx_of_match, //of_match_table 属性就是用于设备树下的驱动与设备检查
},
.probe = xxx_probe, // platform驱动的probe函数,驱动与设备匹配成功以后此函数就会执行
.remove = xxx_remove, //当关闭 platform备驱动的时候此函数就会执行,使用 iounmap 释放内存、删除
};
static int __init xxxdriver_init(void)
{
return platform_driver_register(&xxx_driver); //向 Linux 内核注册一个 platform 驱动,Register 注册帐户,寄存器,登记
}
static void __exit xxxdriver_exit(void)
{
platform_driver_unregister(&xxx_driver); //向 Linux 内核拆卸一个 platform 驱动,Register 注册帐户,寄存器,登记
}
module_init(xxxdriver_init); //指定为驱动的入口函数xxxdriver_init
module_exit(xxxdriver_exit); //指定为驱动的出口函数xxxdriver_exit
MODULE_LICENSE("GPL"); // 指定LICENSE许可为GPL
MODULE_AUTHOR("zuozhongkai"); // 指定作者信息为zuozhongkai
设备树下的 platform
当 Linux 内核支持了设备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述,Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成 platform_device 形式于内核某空间,至于设备树到 platform_device 的具体过程就不去详细的追究了。
总之Linux内核织成的platform_device,和我们写最终结果一样
#includeMISC杂项驱动,即驱动开发方式的杂项框架#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 static void led_release(struct device *dev) { printk("led device released!rn"); } static struct resource led_resources[] = { [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, }, }; static struct platform_device leddevice = { .name = "imx6ul-led", .id = -1, .dev = { .release = &led_release, }, .num_resources = ARRAY_SIZE(led_resources), .resource = led_resources, }; static int __init leddevice_init(void) { return platform_device_register(&leddevice); } static void __exit leddevice_exit(void) { platform_device_unregister(&leddevice); } module_init(xxxdriver_init); //指定为驱动的入口函数xxxdriver_init module_exit(xxxdriver_exit); //指定为驱动的出口函数xxxdriver_exit MODULE_LICENSE("GPL"); // 指定LICENSE许可为GPL MODULE_AUTHOR("zuozhongkai"); // 指定作者信息为zuozhongkai
当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。
所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
杂项框架的API函数
int misc_register(要注册的 MISC 设备) //注册一个 MISC 设备 int misc_deregister(要注销的 MISC 设备) //注销掉 MISC 设备 传统过时的删除设备的过程如下 cdev_del(); unregister_chrdev_region(); device_destroy(); class_destroy();Linux INPUT 子系统框架
input、 pinctrl、 gpio 子系统都是 Linux 内核针对某一类设备而创建的框架
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
......
......
bool devres_managed;
};
input框架的API函数
struct input_dev *input_allocate_device(void) //申请一个input设备结构体变量 int input_register_device(要注册的input_dev) //初始化要注册的input设备结构体变量 void input_free_device(需要释放的input_dev) //注销input设备 void input_unregister_device(struct input_dev *dev) //注销掉前面注册的input设备结构体变量 //input_event 函数可以上报所有的事件类型和事件值, Linux 内核也提供了其他的针对具体事件的上报函数 void input_event(需要上报的input_dev,上报的事件类型,我们要注册的按键值,事件值) //上报指定的事件以及对应的值 void input_report_rel(struct input_dev *dev, unsigned int code, int value) void input_report_abs(struct input_dev *dev, unsigned int code, int value) void input_report_ff_status(struct input_dev *dev, unsigned int code, int value) void input_report_switch(struct input_dev *dev, unsigned int code, int value) void input_mt_sync(struct input_dev *dev) void input_sync(需要上报同步事件的 input_dev) //告诉 Linux 内核 input 子系统上报结束input 驱动编写流程=注册input_dev+上报输入事件 注册input_dev
struct input_dev *inputdev;
static int __init xxx_init(void)
{
......
inputdev = input_allocate_device();
inputdev->name = "test_inputdev";
__set_bit(EV_KEY, inputdev->evbit);
__set_bit(EV_REP, inputdev->evbit);
__set_bit(KEY_0, inputdev->keybit);
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORd(KEY_0)] |=
BIT_MASK(KEY_0);
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
input_register_device(inputdev);
......
return 0;
}
static void __exit xxx_exit(void)
{
input_unregister_device(inputdev);
input_free_device(inputdev);
}
上报输入事件
事件上报参考代码:消抖定时器中断函数中将按键值上报给 Linux 内核,即按键抖动时过一段定时执行中断,中断调用上报函数
void timer_function(unsigned long arg)
{
unsigned char value;
value = gpio_get_value(keydesc->gpio);
if(value == 0){
input_report_key(inputdev, KEY_0, 1);
input_sync(inputdev);
} else {
input_report_key(inputdev, KEY_0, 0);
input_sync(inputdev);
}
}



