platform总线、设备、驱动

本文详细介绍了Linux平台驱动中的platform虚拟设备总线、struct platform_device和struct platform_driver结构,以及设备驱动注册、资源获取和设备匹配的过程。通过平台驱动,可以实现设备与驱动的分离,提高代码的可扩展性和跨平台性,同时利用资源管理、sysfs节点和电源管理等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

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");

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值