首页 > 代码库 > linux设备驱动之platform平台总线工作原理(三)

linux设备驱动之platform平台总线工作原理(三)



设备为数据,驱动为加工着


1、以led-s3c24xx.c为例来分析platform设备和驱动的注册过程


其中关于led的驱动数据结构为

static struct platform_driver s3c24xx_led_driver = {
	.probe		= s3c24xx_led_probe,
	.remove		= s3c24xx_led_remove,
	.driver		= {
		.name		= "s3c24xx_led",
		.owner		= THIS_MODULE,
	},
};

struct 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;
};


关于led的设备数据结构为

static struct platform_device mini2440_led1 = {
	.name		= "s3c24xx_led",
	.id		= 1,
	.dev		= {
		.platform_data	=http://www.mamicode.com/ &mini2440_led1_pdata,>

其中设备的name和驱动中的name一致,设备数据结构中的id值用来区分不同的led设备,因为一个板子上可能有多个led,用这个id值来进行区分不同的led灯,这个id值可以自己随意去排,比如有四个led,id值就可以设置为从1到4的数字,还有一种方法是,我们不去指定的id的值,给id值设置为-1.表示板子上led的id,由内核去帮我们分配。成员dev中的platform_data是设备的数据部分。

struct platform_device的数据结构内容为

struct platform_device {
	const char	* name;
	int		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;

	const struct platform_device_id	*id_entry;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

可以看到dev成员变量确实在struct platform_device结构体中,看一下这个struct platform_device结构体中dev变量的结构体类型struct device的数据结构内容

struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	struct device_type	*type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn‘t touch it */
	struct dev_pm_info	power;

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	u64		*dma_mask;	/* dma mask (if dma‘able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma‘ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
	/* arch specific additions */
	struct dev_archdata	archdata;
#ifdef CONFIG_OF
	struct device_node	*of_node;
#endif

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
};


其中可以看到在这个struct device结构体类型中有一个void *platform_data;成员,这个成员是一个可以指向任意一个类型的指针,他就是设备的数据部分。回到led设备的数据结构中

static struct platform_device mini2440_led1 = {
	.name		= "s3c24xx_led",
	.id		= 1,
	.dev		= {
		.platform_data	=http://www.mamicode.com/ &mini2440_led1_pdata,>

struct device类型的成员dev中的成员platform_data,这个platform_data是一个可以指向任意一个类型的指针,这个位置是一个留白位置,我们可以自己写一个

东西,将写完的这个东西绑定到这个platform_data中成员中,这个void *类型的platform_data成员变量所指向的东西是留给我们的,因为是void *类型的,所以我们可以自己去定义一个类型然后绑定到这里面去,这个地方可以说是一个设备中特有的数据部分,因为内核设备和驱动在设计的时候,并不能把所有的设备有的东西都定义好,所以有了这么一个void *类型的成员变量让我们去定义设备特有的数据部分。

这个led的设备数据结构中的platform_data成员变量指向的是一个min2440_led1_pdata这么一个数据结构,这个数据结构是写led设备的人自己定义的一个数据结构。看下这个mini2440_led1_pdata变量的数据结构

static struct s3c24xx_led_platdata mini2440_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

看下这个mini2440_led1_pdata变量的结构体类型 struct s3c24xx_led_platdata

struct s3c24xx_led_platdata {
	unsigned int		 gpio;
	unsigned int		 flags;

	char			*name;
	char			*def_trigger;
};

这是一个自己定义的一个数据结构类型,并不是内核事先定义的一个数据结构类型,因为设备中的platform_data成员指向的部分的内容是什么是由我们来决定的。这个struct s3c24xx_led_platdata定义的是led所特有的一些数据部分,platform平台总线的不同设备的platform_data肯定是不一样的,是由我们来去定义这个不一样的地方的,platfrom_data成员指向的那个数据结构是关键,是用来区分platform平台总线什么设备的,这个设备的特有部分的。

针对于这个led的设备中platform_data成员指向的led设备特有的自己定义的数据结构struct s3c24xx_led_platdata中,gpio表示这个led所用的引脚号,flags对应的led的属性。name表示led的名字,def_trigger可能表示这个led灯是要跟其他的硬件绑定起来的。

下面在继续分下这个min2440_led1_pdata变量,led设备特有的数据部分,移植的人自己定义的

static struct s3c24xx_led_platdata mini2440_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

name名字为led1,用到的gpio偏移为S3C2410_GPB(5),属性flags为.....,def_trgiger值为heatbeat,说明这个led1将来是用作为心跳灯的。

我们将led特有的数据部分写完后,然后绑定到plotform_data成员中,让这个成员指向我们写的led特有的设备数据部分,在来看下怎么绑定的,有点绕

static struct platform_device mini2440_led1 = {
	.name		= "s3c24xx_led",
	.id		= 1,
	.dev		= {
		.platform_data	=http://www.mamicode.com/ &mini2440_led1_pdata,>

将mini2440_led1_pdata这个设备特有的数据结构绑定到platform_data成员中,特有的数据部分我们实现为led设备特有的数据部分,最后这个struct plat_device 类型的mini2440_led1设备的数据结构就填充好了,之后使用register函数将这个mini2440_led1变量进行注册就行,这个变量就表示一个led的设备。


总结:platform_data其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称.....),在注册一个平台设备的时候,我们为这个设备先提供这些设备数据,这些数据在我们的设备和驱动match之后,match函数会被调用会识别设备和驱动,将设备和驱动匹配上,会由设备方转给驱动方,提供的这些数据就是为了将来设备和驱动匹配上后,设备数据结构中会将这些数据部分转给驱动,驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。这样做的好处是我们的驱动源码中不携带数据,只负责对硬件的操作方法,但是并不知道我们具体操作的硬件是哪一个硬件,驱动是通过设备和驱动match匹配上之后,设备将我们提供的数据部分转交给驱动后,驱动才知道要操作哪个设备,因为数据中包括了设备的所用到的资源(譬如设备对应的gpio、使用到的中断号等等),而驱动又有对这一类型设备的操作方法,所以驱动拿到这个设备的数据后,直接用操作方法就可以操作这个设备了。数据和操作逻辑分开实现是一个好的设计逻辑,因为这样,这个驱动就具有一般性,当硬件换了的时候,也就是设备用的gpio换了的时候,我们不需要改驱动的源码,而是在移植的时候,将更换硬件后的设备的资源进行更改就行,驱动源码是不用变的。


既然说设备和驱动match之后,设备会将我们提供的这个设备的特有的数据部分转交给驱动,然后驱动就可以知道用自己的方法操作哪个硬件了,那么设备将数据部分转交给驱动是转交的呢,在probe函数中

static int s3c24xx_led_probe(struct platform_device *dev)
{
	struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
	struct s3c24xx_gpio_led *led;
	int ret;

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

	platform_set_drvdata(dev, led);

	led->cdev.brightness_set = s3c24xx_led_set;
	led->cdev.default_trigger = pdata->def_trigger;
	led->cdev.name = pdata->name;
	led->cdev.flags |= LED_CORE_SUSPENDRESUME;

	led->pdata = pdata;

	/* no point in having a pull-up if we are always driving */

	if (pdata->flags & S3C24XX_LEDF_TRISTATE) {
		s3c2410_gpio_setpin(pdata->gpio, 0);
		s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);
	} else {
		s3c2410_gpio_pullup(pdata->gpio, 0);
		s3c2410_gpio_setpin(pdata->gpio, 0);
		s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);
	}

	/* register our new led device */

	ret = led_classdev_register(&dev->dev, &led->cdev);
	if (ret < 0) {
		dev_err(&dev->dev, "led_classdev_register failed\n");
		kfree(led);
		return ret;
	}

	return 0;
}

我们可以看到在led驱动代码probe函数中开始有一行代码

struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;

这行代码就是将设备的数据部分转交给了驱动,因为驱动的probe函数中定义了一个struct s3c24xx_led_platdata类型的pdata结构体指针,而probe函数的参数是

struct platform_device类型的dev指针,经过上面的分析我们知道,这个一个设备的数据结构,这个设备的数据结构中有一个成员变量dev,这个dev是一个struct device类型的结构体,这个struct device类型的结构体中有一个platform_data成员变量,用来指向我们写的这个设备所特有的数据部分,在这个led中,这个特有的数据部分的数据类型为s3c24xx_led_platdata,因此我们从上面的代码就可以看出来,驱动代码的probe函数中,通过pdata这个指针指向了led设备的特有数据部分platform_data,这就是设备将我们提供的数据部分转交给驱动的过程。


本文出自 “whylinux” 博客,请务必保留此出处http://whylinux.blog.51cto.com/10900429/1930747

linux设备驱动之platform平台总线工作原理(三)