platform总线通常使用基于两个部分,一个部分是device,一个部分是driver.
我以一个简单的led的例子为例总结platform总线的使用。
我使用的是s5pv210处理器,所以我以该处理器为例总结,其实linux驱动,对任何处理器都一样。
首先以实现device部分。
device主要是数据。
首先我在自定义一个结构体,最好是自己创建一个头文件,方便驱动部分好包含。
我定义的放在对应处理器的mach目录下面,因为这个目录下都是放的和处理器相关的头文件。
#ifndef __ASM_ARCH_LEDSGPIO_H #define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h" #define S5PV210_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */ #define S5PV210_LEDF_TRISTATE (1<<1) /* tristate to turn off */ struct s5pv210_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger; }; #endif /* __ASM_ARCH_LEDSGPIO_H */
很简单,主要就是led的一些信息
接下来就是填充这个结构体了。
/***************run add leds*******************/ static struct s5pv210_led_platdata s5pv210_led0_pdata = { .name = "led0", .gpio = S5PV210_GPJ0(3), .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "", }; static struct s5pv210_led_platdata s5pv210_led1_pdata = { .name = "led1", .gpio = S5PV210_GPJ0(4), .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "", }; static struct s5pv210_led_platdata s5pv210_led2_pdata = { .name = "led2", .gpio = S5PV210_GPJ0(5), .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "", }; static struct platform_device s5pv210_led0 = { .name = "s5pv210_led", .id = 0, .dev = { .platform_data = &s5pv210_led0_pdata, }, }; static struct platform_device s5pv210_led1 = { .name = "s5pv210_led", .id = 1, .dev = { .platform_data = &s5pv210_led1_pdata, }, }; static struct platform_device s5pv210_led2 = { .name = "s5pv210_led", .id = 2, .dev = { .platform_data = &s5pv210_led2_pdata, }, };
我的板子上有三个led,我就定义了三个相对应的结构体。
struct s5pv210_led_platdata这个结构体里面的gpio我使用了gpiolib库的定义。
name自己定义,flags可以自定义其作用,比如输入输出,上下拉等。
def_trigger可以表示该gpio可以表示某个设备运行等,这里我没有使用。
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; };
这个结构体比较重要,name非常重要,必须和驱动名一样,是匹配驱动的唯一途径。
id是表示区分同类设备。
我们这里关注struct device dev;
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;
因为这个平台数据指针,就是设计为了给驱动传参用的(看注释)。void *可以串任何类型的指针就更方便了。
而我这里就是把每个led自定义的结构体struct s5pv210_led_platdata的地址放了进去。
自己做一个结构体,把自己创建的平台设备数据放进去。
static struct platform_device *smdkv210_devices[] __initdata = { &s5pv210_led0, &s5pv210_led1, &s5pv210_led2, };利用下面这个函数注册进入,device这边就已经结束了。
platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));当然,比较简单的方式是把自己的设备数据,添加到原来的设备后面,就可以不用自己添加了。
接下来看驱动部分。
大的部分看就是平台数据的注册和卸载。
/* 平台总线模型驱动部分 */ 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_LICENSE("GPL"); MODULE_AUTHOR("run"); MODULE_DESCRIPTION("leds-s5pv210");
驱动的注册和卸载是固定的比较简单。但里面填充的驱动模型,这个最主要的。
下面看一下平台驱动模型的原型。
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的需要很简单,只需要填充probe和remove函数即可。
还有一个比较中要的是driver,这个里面的东西是和设备注册那时候的做匹配的。(名字匹配)
struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ #if defined(CONFIG_OF) const struct of_device_id *of_match_table; #endif int (*probe) (struct device *dev); int (*remove) (struct device *dev); void (*shutdown) (struct device *dev); int (*suspend) (struct device *dev, pm_message_t state); int (*resume) (struct device *dev); const struct attribute_group **groups; const struct dev_pm_ops *pm; struct driver_private *p; };
这里我们主要关注char *naem和 driver_private *p。
其中name主要是这个驱动的名字。为了和对应设备匹配,驱动名字必须和设备名字一致。
driver_private *p的使用可以看我的上一个博客。
接下来我们看最主要的连个函数probe和remove。
其中probe是负责设备初始化的。remove是负责卸载设备的时候做收尾工作的。
因为我们这里是led,所以使用了led的设备驱动模型。
所以初始化部分用的是led的设备模型初始化的。
/* 想当于初始化函数 */ static int s5pv210_led_probe(struct platform_device *pdev) { /* 得到设备传过来来的数据 */ struct s5pv210_led_platdata *pdata = pdev->dev.platform_data; struct s5pv210_gpio_led *led = NULL; int ret = -1; printk(KERN_INFO"-----------s5pv210_led_probe-------------\n"); led = kzalloc(sizeof(struct s5pv210_gpio_led),GFP_KERNEL); if(NULL == led) { dev_err(&pdev->dev, "no memoey for device \n"); return -ENOMEM; } /* 使用gpiolib库,申请gpio */ if(gpio_request(pdata->gpio,pdata->name)) { printk(KERN_ERR"gpio_request fail\n"); kfree(led); return -EINVAL; } else { /* 初始化gpio为输出 */ gpio_direction_output(pdata->gpio,1); } /* 把动态申请到的driver data绑定到相应的设备的私有数据中 */ platform_set_drvdata(pdev, led); led->cdev.name = pdata->name; led->cdev.brightness_set = s5pv210_led_brightness_set; led->cdev.brightness = 0; // led->cdev.flags = pdata->flags; //这个标志必须用内核定义的,自己定义的可能会和内核的重复 led->pdata = pdata; /* 利用led设备类模型注册该设备 */ ret = led_classdev_register(&pdev->dev,&led->cdev ); if(ret) { dev_err(&pdev->dev, "led classdev_register fail\n"); kfree(led); gpio_free(pdata->gpio); return ret; } return 0; }这个函数里注释比较明确,唯一需要说明的是flags,因为pdata->flags是我们子定义的功能,前面在设备部分说明已的是做输入输出标志或输出的默认电平标志。这里我们led是做输出的,默认给的高电平。所以没用这个标志,但千万不能赋值和led->cdev.flags.因为这个cdev是led的设备模型定义的标志,其作用已经固定了。我就是刚在开始赋值的,导致led被挂起。不能操纵。
可以看到下面led类里面就用到
static int led_suspend(struct device *dev, pm_message_t state) { struct led_classdev *led_cdev = dev_get_drvdata(dev); if (led_cdev->flags & LED_CORE_SUSPENDRESUME) led_classdev_suspend(led_cdev); return 0; } static int led_resume(struct device *dev) { struct led_classdev *led_cdev = dev_get_drvdata(dev); if (led_cdev->flags & LED_CORE_SUSPENDRESUME) led_classdev_resume(led_cdev); return 0; }
它们的定义如下。
struct led_classdev { const char *name; int brightness; int max_brightness; int flags; /* Lower 16 bits reflect status */ #define LED_SUSPENDED (1 << 0) /* Upper 16 bits reflect control information */ #define LED_CORE_SUSPENDRESUME (1 << 16) /* Set LED brightness level */ /* Must not sleep, use a workqueue if needed */ void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); /* Get LED brightness level */ enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); /* Activate hardware accelerated blink, delays are in * miliseconds and if none is provided then a sensible default * should be chosen. The call can adjust the timings if it can't * match the values specified exactly. */ int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off); struct device *dev; struct list_head node; /* LED Device list */ const char *default_trigger; /* Trigger to use */ #ifdef CONFIG_LEDS_TRIGGERS /* Protects the trigger data below */ struct rw_semaphore trigger_lock; struct led_trigger *trigger; struct list_head trig_list; void *trigger_data; #endif };
probe函数里注册led的驱动模型的时候需要用到一个brightness_set函数,这个函数就是led的设备模型中干活的函数,用来设置led的量灭的。
/* 设置led灯的亮灭 */ static void s5pv210_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct s5pv210_gpio_led * led = to_gpio(led_cdev); printk(KERN_INFO"led brightness set\n"); if(LED_OFF == brightness) { gpio_set_value(led->pdata->gpio, 1); } else { gpio_set_value(led->pdata->gpio, 0); } }
下面几个就是上面用到的一些的数据类型和工具函数。
/* 自定义结构体,主要是想包含平台数据 */ struct s5pv210_gpio_led { struct led_classdev cdev; struct s5pv210_led_platdata *pdata; }; /* 得到platform_device里面的platform_data */ static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev) { return platform_get_drvdata(dev); } /* 通过container_of宏,利用s5pv210_gpio_led 结构体里面的led_classdev结构体得到s5pv210_gpio_led */ static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev) { return container_of(led_cdev,struct s5pv210_gpio_led,cdev); }
总结:上面设备部分用到了palaform_device的一些知识。
下面驱动部分用到了platform_driver和led设备模型,以及gpiolib库的简单应用。
几个知识点算是对上一周学习的总结。