platform虚拟设备总线,来挂接一些内部资源;
设备:struct platform_device dev;
驱动:struct platform_driver dri;
platform虚拟设备总线编写设备驱动流程图:
设备:platform_device
struct platform_device
{
const char * name;/*设备的名字,与驱动的名字相匹配*/
int id; /*与驱动的绑定有关,一般为-1*/
struct device dev;/*设备结构体,说明platform_device 派生于device*/
u32 num_resources;/*设备所使用各类资源数量*/
struct resource * resource;/*指向资源数组,数量由num_resources指定*/
.....
};
struct resource
{
resource_size_t start;/*资源的开始地址*/
resource_size_t end;/*资源结束地址*/
const char * name;/*资源的结束地址*/
unsigned long flags;/*资源的类型*/
struct resource *parent,*sibling,*child;/*用于构建资源的树形结构*/
};
/*start、end、flags这3个字段分别标明资源的开始地址、结束地址和资源地址,flags可以是IORESOURCE_IO、IORESOURCE_IRQ、IORESOURCE_DMA等;
*start、end的含义会随着flags的改变而改变,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device 占据的内存的开始地址和结束地址;
*当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始和结束值,如果只使用一个中断号,开始和结束值相同;*/
在具体的设备驱动中,我们可以通过platform_get_resource()函数来获取定义的resource资源,函数定义如下:
struct resource *platform_get_resource(struct platform_device *,unsigned int ,unsigned int);
如LED的设备资源:
struct resource led_res[]= {
[0] = {
.start = 0x11000C40,
.end = 0x11000C40 +7,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 0x11000C20,
.end = 0x11000C20 +7,
.flags = IORESOURCE_MEM,
},
[2] = {
.start = 0x114001E0,
.end = 0x114001E0 +7,
.flags = IORESOURCE_MEM,
},
};
获得资源:
db->addr_res = platform_get_resource(pdev,IORESOURCE_MEM,0);
db->addr_res = platform_get_resource(pdev,IORESOURCE_MEM,1);
db->addr_res = platform_get_resource(pdev,IORESOURCE_MEM,2);
设备驱动中描述设备的代码除了可以定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存、DMA通道以外,可能还会有一些配置信息。因此,platform提供了一个数据结构体platform_data来描述额外的配置信息。platform_data结构体形式可以由程序员自主定义,
驱动:platform_driver
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 struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
platform_driver设备驱动主要是probe()、remove()成员函数的实现,在probe()函数中申请设备所需要的资源;在remove()函数中,移除所占用的资源;
驱动注册函数platform_driver_register()函数:与之对应的注销驱动功能函数:platform_driver_unregister()函数;
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;/*平台总线类型*/
if (drv->probe)
drv->driver.probe = platform_drv_probe;/*默认的探测函数*/
if (drv->remove)
drv->driver.remove = platform_drv_remove;/*默认的移除函数*/
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;/*默认的关闭函数*/
return driver_register(&drv->driver);/*将驱动注册到系统中*/
}
总线:platform_bus_type
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
匹配函数match(),它来表明platform_device和platform_driver之间是如何匹配的;
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
匹配platform_device和platform_driver主要看两者的name字段是否一样,如一样就把设备和驱动绑定在一起;
引入platform虚拟总线的优点:
1.使得设备被挂载在一个总线上,因此,符合Linux的设备驱动模型,结果是,各个对应的资源配套的sysfs节点、电源管理功能都成为可能;
2.可以隔离设备和驱动。在设备代码中定义使用的资源、具体的配置信息等;在驱动代码中,需要通过相应的函数去获取资源和数据,做到设备代码和驱动代码的分离,使得驱动代码具有更好的可扩展性和跨平台性;
platform虚拟总线的驱动实例,以点亮LED为例:
设备程序:device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
void hello_release(struct device *dev)
{
printk("hello release\n");
}
struct resource led_res[]= {
[0] = {
.start = 0x11000C40,
.end = 0x11000C40 +7,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 0x11000C20,
.end = 0x11000C20 +7,
.flags = IORESOURCE_MEM,
},
[2] = {
.start = 0x114001E0,
.end = 0x114001E0 +7,
.flags = IORESOURCE_MEM,
},
};
struct platform_device pdev = {
.name = "go",
.id = 0,
.dev.release = hello_release,
.resource = led_res,
.num_resources = ARRAY_SIZE(led_res),
};
static int __init hello_init(void)
{
printk("device init !\n");
platform_device_register(&pdev);
return 0;
}
static void __exit hello_exit(void)
{
printk("device exit\n");
platform_device_unregister(&pdev);
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
驱动程序:driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
dev_t devno;
int major = 0;
int minor = 0;
struct cdev mycdev;
struct class *pcls;
struct device *pdev;
/*2、内核中不能直接访问物理地址,要先进行映射*/
void * pgpx2con;
void * pgpx2dat;
void * pgpx1con;
void * pgpx1dat;
void * pgpf3con;
void * pgpf3dat;
/*3、根据DS寄存器说明进行初始化*/
int fs4412_led_init(void)
{
/*配置 gpx2_7为输出模式*/
writel((readl(pgpx2con)&(~(0xf<<28)))|0x1<<28, pgpx2con); //gpx2_7
writel( (readl(pgpx1con)&(~(0xf<<0)) )|(0x1<<0), pgpx1con); //gpx1_0
writel( (readl(pgpf3con)&(~(0xff<<16)) )|(0x11<<16), pgpf3con); //gpf3_4/5
return 0;
}
int fs4412_led_off(int ledno);
/*4、控制gpx2_7 data寄存器 输出高电平 点亮LED*/
int fs4412_led_on(int ledno)
{
switch (ledno)
{
case 0:
writel(readl(pgpx2dat)|0x1<<7, pgpx2dat);
break;
case 1:
writel( readl(pgpx1dat)|(0x1<<0), pgpx1dat);
break;
case 2:
writel( readl(pgpf3dat)|(0x1<<4), pgpf3dat);
break;
case 3:
writel( readl(pgpf3dat)|(0x1<<5), pgpf3dat);
default:
fs4412_led_off(0);
fs4412_led_off(1);
fs4412_led_off(2);
fs4412_led_off(3);
break;
}
return 0;
}
/*5、控制gpx2_7 data寄存器 输出低电平 熄灭LED*/
int fs4412_led_off(int ledno)
{
switch (ledno)
{
case 0:
writel(readl(pgpx2dat)&(~(0x1<<7)), pgpx2dat);
break;
case 1:
writel( readl(pgpx1dat)&(~(0x1<<0)), pgpx1dat);
break;
case 2:
writel( readl(pgpf3dat)&(~(0x1<<4)), pgpf3dat);
break;
case 3:
writel( readl(pgpf3dat)&(~(0x1<<5)), pgpf3dat);
default:
break;
}
return 0;
}
/*6、释放资源 */
int fs4412_led_unmap(void)
{
iounmap(pgpx2con);
iounmap(pgpx2dat);
iounmap(pgpx1con);
iounmap(pgpx1dat);
iounmap(pgpf3con);
iounmap(pgpf3dat);
return 0;
}
int hello_open(struct inode *inode, struct file *file)
{
printk("hello open \n");
return 0;
}
int hello_release(struct inode *inode, struct file *file)
{
printk("hello release \n");
return 0;
}
ssize_t hello_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
copy_to_user(buf, &major, 4);
return size;
}
ssize_t hello_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ledno = 0;
copy_from_user(&ledno, buf, 4);
fs4412_led_on(ledno);
return size;
}
#define MAGIC_NUM 'k'
#define LED_ON _IOW(MAGIC_NUM, 0, int) //MAGIC_NUM << 24 |0 <<16 | 1<< 14 |4
#define LED_OFF _IOW(MAGIC_NUM, 1, int) //MAGIC_NUM << 24|1 <<16 | 1<< 14 |4
long hello_ctl(struct file *file, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd)!= 'k')
{
printk("Invalid magic mum\n");
return EINVAL;
}
switch(cmd){
if(_IOC_DIR(cmd) != _IOC_WRITE)
{
printk("Invalid magic mum\n");
return EINVAL;
}
case LED_ON:
fs4412_led_on(arg);
break;
case LED_OFF:
fs4412_led_off(arg);
break;
default:
break;
}
}
struct file_operations fops = {
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
.unlocked_ioctl = hello_ctl,
};
int hello_probe(struct platform_device *pdev) /*match成功会调用 到*/
{
printk("hello probe \n");
/* 通过 platform_device 获取 resource信息 */
pgpx2con = ioremap(pdev->resource[0].start, pdev->resource[0].end-pdev->resource[0].start+1);
pgpx2dat = pgpx2con+4;//申请的初始地址上加上4个字节
pgpx1con = ioremap(pdev->resource[1].start, pdev->resource[1].end-pdev->resource[1].start+1);
pgpx1dat = pgpx1con+4;
pgpf3con = ioremap(pdev->resource[2].start, pdev->resource[2].end-pdev->resource[2].start+1);
pgpf3dat = pgpf3con+4;
devno = MKDEV(major,minor);
/*1、申请设备号,并初始化cdev*/
major = register_chrdev(major, "led", &fops);
printk("major from register chrdev:%d\n", major);
devno = MKDEV(major,minor);
/*2、自动创建设备节点 /dev/hello */
pcls = class_create(THIS_MODULE, "led");
pdev = device_create(pcls, NULL, devno, NULL, "led");
fs4412_led_init();
return 0;
}
int hello_remove(struct platform_device *pdev) /*unmatch 时会调用*/
{
printk("hello release\n");
return 0;
}
struct platform_driver pdrv = {
.probe = hello_probe,
.remove = hello_remove,
.driver.name = "go",/*struct driver 结构体来匹配的*/
};
static int __init hello_init(void)
{
printk("driver init !\n");
/*使用这个驱动的时候,会通过总线去匹配设备的信息,
* 然后把设备的物理信息加载进来,然后进行引脚的配置
*/
platform_driver_register(&pdrv);//去内核匹配这个驱动的设备信息
return 0;
}
static void __exit hello_exit(void)
{
printk("driver exit\n");
unregister_chrdev(major, "led");
device_destroy(pcls, devno);
class_destroy(pcls);
fs4412_led_unmap();
platform_driver_unregister(&pdrv);
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
module_AUTHOR("woshishui918");