linux内核支持platform平台总线,这是一种虚拟总线,满足之前讲的设备驱动模型。这里使用平台总线platform是区别于之前我们用手动创建mybus总线,同样完成设备与驱动分离的实现。
设备、驱动、总线三者关系:
由于平台总线的设计是基于设备模型的,引入平台总线,实现device(中断/地址)和driver(操作逻辑)分离
在硬件升级的时候,只需要修改device中信息即可(中断/地址)
实现一个driver代码能够驱动多个平台相似的模块,并且修改的代码量很少
平台总线三要素
bus总线对象
1.不需要自己创建,开机的时候自动创建。结构体对象
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
2.linux内核自动匹配设备驱动,匹配规则
- 优先匹配pdriver中的id_table,里面包含了支持不同的平台的名字
- 直接匹配driver中名字和device中名字
device设备对象
1.为驱动提供硬件信息,描述性内容。结构体对象
struct platform_device {
const char *name; //用于做匹配
int id; // 一般都是直接给-1
struct device dev; // 继承了device父类
u32 num_resources; // 资源的个数
struct resource *resource; // 资源:包括了一个设备的地址和中断
}
2.注册和注销
int platform_device_register(struct platform_device * pdev);
void platform_device_unregister(struct platform_device * pdev);
driver驱动对象
1.实现功能接口,动作性描述。结构体对象
struct platform_driver {
int (*probe)(struct platform_device *); //匹配成功之后被调用的函数
int (*remove)(struct platform_device *);//device移除的时候调用的函数
struct device_driver driver; //继承了driver父类
const struct platform_device_id *id_table; //如果driver支持多个平台,在列表中写出来
}
2.注册和注销
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv)
代码实现
设备描述模块 plat_led_pdev.ko
主要用来描述硬件相关的信息,将做好的设备结构对象注册到平台总线platform中。向所匹配的驱动程序提供硬件配置信息。
- 定义结构体对象,描述信息
- 注册平台总线
//plat_led_pdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define GPIO_REG_BASE 0x11400000
#define GPF3_CON GPIO_REG_BASE + 0x01E0
#define GPF3_SIZE 24
#define GPX1_CON GPIO_REG_BASE + 0x0C20
#define GPX1_SIZE 24
void mydev_release(struct device *dev);
// 一个设备可能有多个资源:
struct resource led_res[] = {
[0] = {
.start = GPF3_CON,
.end = GPF3_CON + GPF3_SIZE - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = GPX1_CON,
.end = GPX1_CON + GPX1_SIZE - 1,
.flags = IORESOURCE_MEM,
},
};
struct platform_device led_pdev = {
.name = "exynos4412_led", // 用于做匹配
.id = -1,
.num_resources = ARRAY_SIZE(led_res),
.resource = led_res,
.dev.release = mydev_release,
};
static int __init plat_led_dev_init(void)
{
//注册一个platform_devices
platform_device_register(&led_pdev);
return 0;
}
static void __exit plat_led_dev_exit(void)
{
platform_device_unregister(&led_pdev);
}
void mydev_release(struct device *dev)
{
printk("----------%s-------------\n", __FUNCTION__);
}
module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit);
MODULE_LICENSE("GPL");
可以在系统上查看到这个结点
驱动模块 plat_led_pdrv.ko
用于实现方法,驱动通过平台总线从设备那边获取到硬件信息,进行硬件初始化;同时为应用层提供IO接口。将驱动对象注册到paltform总线,系统匹配成功,将执行probe方法。
- 描述驱动对象 platform_driver
- 注册驱动到平台总线
- 构造一个全局变量(结构体对象),用于接收硬件信息
- 实现probe回调,从总线拿到设别信息进行硬件初始化
- 实现IO接口,在write接口中实现控制led
//plat_led_pdrv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
int led_pdrv_probe(struct platform_device *pdev); //匹配成功后的回调
int led_pdrv_remove(struct platform_device *pdev); //如果device从总线移除,跳转这个函数
ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos);
int led_pdrv_open(struct inode *inode, struct file *filp);
int led_pdrv_close(struct inode *inode, struct file *filp);
//设计一个全局的设备对象
struct led_dev{
int dev_major;
struct class *cls;
struct device *dev;
struct resource *res; //获取到的内存资源
void *reg_base; //表示物理地址映射之后的虚拟地址
};
struct led_dev *samsung_led;
const struct file_operations led_fops = {
.open = led_pdrv_open,
.release = led_pdrv_close,
.write = led_pdrv_write,
};
const struct platform_device_id led_id_table[] = {
{"exynos4412_led", 0x4444},
{"s5pv210_led", 0x2222},
{"s3c2410_led", 0x3333},
{"s3c6410_led", 0x3333},
};
struct platform_driver led_pdrv = {
.probe = led_pdrv_probe,
.remove = led_pdrv_remove,
.driver = {
.name = "samsung_led_drv",
//可以用于做匹配
},
.id_table = led_id_table,
};
static int __init plat_led_drv_init(void)
{
printk("-----%s------------\n", __FUNCTION__);
//注册一个pdrv
platform_driver_register(&led_pdrv);
return 0;
}
static void __exit plat_led_drv_exit(void)
{
printk("-----%s------------\n", __FUNCTION__);
platform_driver_unregister(&led_pdrv);
}
/*
a,注册设备号,并且注册fops--为用户提供一个设备标示,同时提供文件操作io接口
b, 创建设备节点
c, 初始化硬件
ioremap(地址); //地址从pdev需要获取
readl/writle();
d,实现各种io接口: xxx_open, xxx_read, ..
*/
int led_pdrv_probe(struct platform_device *pdev)
{
printk("-----%s------------\n", __FUNCTION__);
samsung_led = kzalloc(sizeof(struct led_dev), GFP_KERNEL);
if(samsung_led == NULL)
{
printk("kzalloc errorn\n");
return -ENOMEM;
}
samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops);
samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0),
NULL, "led0");
//获取资源
// 参数1: 从哪个pdev中获取资源
// 参数2: 资源类型
// 参数3: 表示获取同种资源的第几个
samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//地址映射
samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res));
//对寄存器进行配置--输出功能
writel((readl(samsung_led->reg_base) & ~(0xff<<16))| (0x11<<16) , samsung_led->reg_base);
return 0;
}
int led_pdrv_remove(struct platform_device *pdev)
{
printk("-----%s------------\n", __FUNCTION__);
iounmap(samsung_led->reg_base);
device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major, 0));
class_destroy(samsung_led->cls);
unregister_chrdev(samsung_led->dev_major, "led_drv");
kfree(samsung_led);
return 0;
}
ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int val;
int ret;
ret = copy_from_user(&val, buf, count);
if(ret > 0)
{
printk("copy_from_user error\n");
return -EFAULT;
}
if(val){ //亮
writel(readl(samsung_led->reg_base + 4) | (0x3<<4) , samsung_led->reg_base+4);
}else{
writel(readl(samsung_led->reg_base + 4) & ~(0x3<<4) , samsung_led->reg_base+4);
}
return count;
}
int led_pdrv_open(struct inode *inode, struct file *filp)
{
printk("-----%s------------\n", __FUNCTION__);
return 0;
}
int led_pdrv_close(struct inode *inode, struct file *filp)
{
printk("-----%s------------\n", __FUNCTION__);
return 0;
}
module_init(plat_led_drv_init);
module_exit(plat_led_drv_exit);
MODULE_LICENSE("GPL");
驱动注册到总线后,在系统上卡查看
应用程序 led_test
用来测试,驱动实现的接口。利用write写操作 最终要控制 led的闪烁。
//led_test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
int on =0;
fd = open("/dev/led0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
while(1)
{
on = 1;
write(fd, &on, 4);
sleep(1);
on = 0;
write(fd, &on, 4);
sleep(1);
}
close(1);
return 0;
}
编译 - 执行
可观察到led在闪烁。。。