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内核自动匹配设备驱动,匹配规则

  1. 优先匹配pdriver中的id_table,里面包含了支持不同的平台的名字
  2. 直接匹配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中。向所匹配的驱动程序提供硬件配置信息。

  1. 定义结构体对象,描述信息
  2. 注册平台总线
//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方法。

  1. 描述驱动对象 platform_driver
  2. 注册驱动到平台总线
  3. 构造一个全局变量(结构体对象),用于接收硬件信息
  4. 实现probe回调,从总线拿到设别信息进行硬件初始化
  5. 实现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在闪烁。。。