Linux驱动开发(7)------- platform平台总线


一,platform平台总线简介


1、何为平台总线
(1)相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的
(2)回顾裸机中讲的,CPU与外部通信的2种方式:地址总线式连接和专用接口式连接平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设

2、平台总线下管理的2员大将
(1)platform工作体系都定义在drivers/base/platform.c
(2)两个结构体:platform_device和platform_driver


platform_device


struct platform_device {
	const char	* name;			// 平台总线下设备的名字
	int		id;
	struct device	dev;		// 所有设备通用的属性部分
	u32		num_resources;		// 设备使用到的resource的个数
	struct resource	* resource;	// 设备使用到的资源数组的首地址

	const struct platform_device_id	*id_entry;	// 设备ID表

	/* arch specific additions */
	struct pdev_archdata	archdata;			// 自留地,用来提供扩展性的
};


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;	// 设备ID表
};

二,platform平台总线工作原理

1、平台总线体系的工作流程

  • 第一步:系统启动时在bus系统中注册platform。
  • 第二步:内核移植的人负责提供platform_device
  • 第三步:写驱动的人负责提供platform_driver
  • 第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了

2、平台总线的匹配方法match

static int platform_match(struct device *dev, struct device_driver *dr
{
 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);
}

(1)每种总线(不光是platform,usb、i2c那些也是)都会带一个match方法,match方法用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但是实际上一般都是看name的
(2)platform_match函数就是平台总线的匹配方法。该函数的工作方法是:

  • 如果有id_table就说明驱动可能支持多个设备,所以这时候要去对比id_table中所有的name,只要找到一个相同的就匹配上了不再找了
  • 如果找完id_table都还没找到就说明每匹配上
  • 如果没有id_table或者每匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败

3、platform设备和驱动的注册过程
(1)两个接口函数platform_device_register和platform_driver_register


platform_driver_register


int platform_driver_register(struct platform_driver *drv)
{
	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_device_register


int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	return platform_device_add(pdev);
}

2、platdata

在这里插入图片描述

(1)platdata其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称····)
(2)这些数据在设备和驱动match之后,会由设备方转给驱动方。驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。
(3)这样做的好处是
驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性

3、probe函数的功能和意义
probe在设备驱动被注册到内核中的时候,被总线型驱动调用。
总线驱动类似于用轮训方法bai探测总线上的所有设备,将设备的识别型信息和关键数据结构 (pci ids, usb ids, i2c ids and etc.)传递给probe函数,probe就会识别是否是自己负责驱动的设备,并负责完成该设备的初始化操作。


三,使用platform平台总线的LED驱动代码实践


#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>


#define X210_LED_OFF	1			// X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON		0			// 所以1是灭,0是亮

struct s5pv210_gpio_led {
	struct led_classdev		 cdev;
	struct s5pv210_led_platdata	*pdata;
};

static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
	return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
	return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}


// 这个函数就是要去完成具体的硬件读写任务的
static void s5pv210_led_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	struct s5pv210_gpio_led *p = to_gpio(led_cdev);
	
	printk(KERN_INFO "s5pv210_led_set\n");
	
	// 在这里根据用户设置的值来操作硬件
	// 用户设置的值就是value
	if (value == LED_OFF)
	{
		// 用户给了个0,希望LED灭
		gpio_set_value(p->pdata->gpio, X210_LED_OFF);
	}
	else
	{
		// 用户给的是非0,希望LED亮
		gpio_set_value(p->pdata->gpio, X210_LED_ON);
	}
}


static int s5pv210_led_probe(struct platform_device *dev)
{
	// 用户insmod安装驱动模块时会调用该函数
	// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
	int ret = -1;
	struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
	struct s5pv210_gpio_led *led;
	
	printk(KERN_INFO "----s5pv210_led_probe---\n");

	led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
	if (led == NULL) {
		dev_err(&dev->dev, "No memory for device\n");
		return -ENOMEM;
	}

	platform_set_drvdata(dev, led);
	

	// 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
	if (gpio_request(pdata->gpio, pdata->name)) 
	{
		printk(KERN_ERR "gpio_request failed\n");
	} 
	else 
	{
		// 设置为输出模式,并且默认输出1让LED灯灭
		gpio_direction_output(pdata->gpio, 1);
	}
	
	// led1
	led->cdev.name = pdata->name;
	led->cdev.brightness = 0;	
	led->cdev.brightness_set = s5pv210_led_set;
	led->pdata = pdata;
	
	ret = led_classdev_register(&dev->dev, &led->cdev);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	return 0;
}

static int s5pv210_led_remove(struct platform_device *dev)
{
	struct s5pv210_gpio_led *p = pdev_to_gpio(dev);

	led_classdev_unregister(&p->cdev);
	gpio_free(p->pdata->gpio);
	kfree(p);								// kfee放在最后一步
	
	return 0;
}

static struct platform_driver s5pv210_led_driver = {
	.probe		= s5pv210_led_probe,
	.remove		= s5pv210_led_remove,
	.driver		= {
		.name		= "s5pv210_led",
		.owner		= THIS_MODULE,
	},
};

static int __init s5pv210_led_init(void)
{
	return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void)
{
	platform_driver_unregister(&s5pv210_led_driver);
}

module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");							// 描述模块的许可证
MODULE_AUTHOR("aston <[email protected]>");		// 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver");		// 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led");					// 描述模块的别名信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章