平台总线(platform_bus)的需求来源?
随着soc的升级,S3C2440->S3C6410->S5PV210->4412,以前的程序就得重新写一遍,做着大量的重复工作,
人们为了提高效率,发现控制器的操作逻辑(方法)是一样的,只有寄存器地址不一样,如果将与硬件有关的
代码(platform_device)和驱动代码(platform_driver)分开,升级soc后,因为驱动方式一样,
只需要修改与硬件有关的代码就可以,实现一个驱动控制多个设备。
平台(platform)总线是一种虚拟的总线,在 /sys/bus/platform 目录可以看到。
平台总线三要素:平台总线、平台设备、平台驱动
平台总线原则:先分离,后合并
分离:
将设备信息封装成 platform_device,将驱动信息封装成 platform_driver,并为各自起名称,
然后将 platform_device 中的 struct device 和 platform_driver 中的 struct device_driver 分别注册到设备链表和驱动链表中。
int platform_device_register(struct platform_device *pdev)
return platform_device_add(pdev);
ret = device_add(&pdev->dev);
int platform_driver_register(struct platform_driver *drv)
return driver_register(&drv->driver);
ret = bus_add_driver(drv);
合并:
在系统每注册一个设备(驱动)时,平台总线会找与之匹配的驱动(设备),匹配原则是名称相同。
装载(insmod)时设备和驱动没有顺序,卸载(rmmod)时必须先卸载设备文件,因为卸载设备会调用驱动中的 remove 函数
// 下面的“|”表示包含于上一个之中
// 描述设备的信息
struct platform_device {
const char * name; // 用于和platform_driver进行匹配的名字--自定义
int id; // 一般直接填-1,区分不同的控制组
struct device dev; // 父类
|
void (*release)(struct device *dev); // 设备卸载时调用的函数
void *platform_data; // 匹配后传递的自定义数据
... ...
u32 num_resources; // 资源的个数
struct resource * resource; // 描述资源信息
|
resource_size_t start; // 起始位置
resource_size_t end; // 结束位置
const char *name; // 自定义
unsigned long flags; // 区分不同的资源,一般是 内存或者中断资源
... ...
};
// 描述设备的操作方法
struct platform_driver {
int (*probe)(struct platform_device *); // 设备和驱动匹配后调用的函数
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; // 父类
|
const char *name; // 该名字可以用于匹配,但比id_table中的name优先级低
// 此名称在 /sys/bus/platform/drivers/xxx
... ...
const struct platform_device_id *id_table;
|
char name[PLATFORM_NAME_SIZE]; // 用于和platform_device的名字进行匹配,优先级高
};
// 平台总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match, // 用于匹配,此函数可以看出匹配名称的优先级
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
// 注册 platform_devive
int platform_device_register(struct platform_device *pdev);
// 注销 platform_devive
void platform_device_unregister(struct platform_device *pdev);
// 注册 platform_driver
int platform_driver_register(struct platform_driver *drv);
// 注销 platform_driver
void platform_driver_unregister(struct platform_driver *drv);
// 批量注册pdev
int platform_add_devices(struct platform_device **devs, int num);
获取资源的接口:
// 通过类型和编号获取资源
// 参数1:pdev
// 参数2:获取的资源类型
// 参数3:获取的资源编号
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
// 通过类型和名称获取资源
// 参数3:获取资源的名称
struct resource * platform_get_resource_byname(struct platform_device * dev,unsigned int type,const char * name);
// 通过编号获取中断资源
int platform_get_irq(struct platform_device * dev,unsigned int num);
// 通过名称获取中断资源
int platform_get_irq_byname(struct platform_device * dev,const char * name);
// 重点:资源编号一定是按照同种类型来排序的
struct resource led_res[] = {
[] = { // 获取内存资源时,此内存资源的编号为0
... ...
.flags = IORESOURCE_MEM,
},
[] = { // 获取中断资源时,此中断资源的编号为0
... ...
.flags = IORESOURCE_IRQ,
},
[] = { // 获取内存资源时,此内存资源的编号为1
... ...
.flags = IORESOURCE_MEM,
},
};
plat_led_dev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h> #include "plat_led.h" #define GPL2_0 0x11000100
#define GP_SIZE 8 // 平台自定义数据,与硬件相关
struct regled led_reg = {
.ctl_clr = 0x0f,
.ctl_set = 0x01,
.dat_clr = 0x01,
.dat_set = 0x01,
}; static struct resource led_res[] = {
[] = {
.start = GPL2_0,
.end = GPL2_0 + GP_SIZE - ,
.name = "led0",
.flags = IORESOURCE_MEM,
},
[] = {
.start = ,
.end = ,
.name = "virt_irq",
.flags = IORESOURCE_IRQ,
},
}; void plat_led_release(struct device *dev)
{
// 为了卸载模块时不报错误
} struct platform_device led_pdev = {
.name = "plat_led",
.id = -,
.dev = {
.platform_data = &led_reg,
.release = plat_led_release,
},
.num_resources = ARRAY_SIZE(led_res),
.resource = led_res,
}; static int __init plat_led_dev_init(void)
{
platform_device_register(&led_pdev); return ;
} static void __exit plat_led_dev_exit(void)
{
platform_device_unregister(&led_pdev);
} module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aaron Lee");
plat_led_drv.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/platform_device.h> #include <asm/io.h>
#include <asm/uaccess.h> #include "plat_led.h" struct samsung *platled; static int platled_open(struct inode *inode, struct file *fops)
{
writeb(readb(platled->reg_base+) & (~platled->reg->dat_clr), platled->reg_base+); return ;
} static int platled_close(struct inode *inode, struct file *fops)
{
return ;
} static ssize_t platled_write(struct file *fops, const char __user *buf, size_t size, loff_t *fpos)
{
int value;
//暂时忽略返回值
copy_from_user(&value, buf, size); if (value)
writeb((readb(platled->reg_base+) & (~platled->reg->dat_clr)) | platled->reg->dat_set, platled->reg_base+);
else
writeb(readb(platled->reg_base+) & (~platled->reg->dat_clr), platled->reg_base+); return ;
} const struct file_operations platled_fops = {
.open = platled_open,
.release = platled_close,
.write = platled_write,
}; static int led_register(void)
{
int ret; platled = kmalloc(sizeof(struct samsung), GFP_KERNEL);
if (platled == NULL)
{
printk("kmalloc fail!\n");
return -ENOMEM;
} platled->major = register_chrdev(, "plat_led", &platled_fops);
if (platled->major < )
{
printk("register_chrdev fail!\n");
ret = -EFAULT;
goto chrdev_err;
} platled->cls = class_create(THIS_MODULE, "plat_led");
if (platled->cls < )
{
printk("class_create fail!\n");
ret = -EFAULT;
goto class_err;
} platled->dev = device_create(platled->cls, NULL, MKDEV(platled->major, ), NULL, "plat_led");
if (platled->dev < )
{
printk("device_create fail!\n");
ret = -EFAULT;
goto device_err;
} return ; device_err:
class_destroy(platled->cls); class_err:
unregister_chrdev(platled->major, "plat_led"); chrdev_err:
kfree(platled); return ret;
} static void led_unregister(void)
{
device_destroy(platled->cls, MKDEV(platled->major, ));
class_destroy(platled->cls);
unregister_chrdev(platled->major, "plat_led");
kfree(platled);
} int plat_led_probe(struct platform_device *pdev)
{
int ret;
struct resource *res; ret = led_register();
if (ret < )
{
printk("plat_led_probe fail\n");
return -EFAULT;
} res = platform_get_resource(pdev, IORESOURCE_MEM, );
if (res == NULL)
{
printk("platform_get_resource fail\n");
return -ENODEV;
}
// 获取平台自定义数据
platled->reg = pdev->dev.platform_data; platled->reg_base = ioremap(res->start, resource_size(res));
writel((readl(platled->reg_base) & (~platled->reg->ctl_clr)) | platled->reg->ctl_set, platled->reg_base); return ;
} int plat_led_remove(struct platform_device *pdev)
{
iounmap(platled->reg_base);
led_unregister(); return ;
} const struct platform_device_id led_id_table[] = {
{"plat_led", 0x1234}, //第二个整数是自定义
}; struct platform_driver led_pdrv = {
.probe = plat_led_probe,
.remove = plat_led_remove,
.driver = {
.name = "red_led",
},
.id_table = led_id_table,// name用于匹配
}; static int __init plat_led_drv_init(void)
{
platform_driver_register(&led_pdrv); return ;
} static void __exit plat_led_drv_exit(void)
{
platform_driver_unregister(&led_pdrv);
} module_init(plat_led_drv_init);
module_exit(plat_led_drv_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aaron Lee");
plat_led.h
#ifndef __PLAT_LED_H_
#define __PLAT_LED_H_ struct regled {
unsigned long ctl_clr;
unsigned long ctl_set;
unsigned long dat_clr;
unsigned long dat_set;
}; struct samsung {
int major;
struct class *cls;
struct device *dev;
struct regled *reg;
void *reg_base;
}; #endif