首页 > 代码库 > 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平台总线工作原理(三)