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库的简单应用。
几个知识点算是对上一周学习的总结。
2018.5
后面一篇博客用到了这个里面的例子。实验发现没有写相关的show函数,用cat也能cat到,如下。
分析代码,原因如下:
static void led_update_brightness(struct led_classdev *led_cdev)
{
if (led_cdev->brightness_get) /* 我们没写,这里不能通过 */
led_cdev->brightness = led_cdev->brightness_get(led_cdev);
}
static ssize_t led_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
/* no lock needed for this */
led_update_brightness(led_cdev); /* 什么都没做 */
return sprintf(buf, "%u\n", led_cdev->brightness); /* 直接打印led_dev中的brightness */
}
因为show打印的是brightness,即使没实现相关的硬件读brightness,因为srore设置了brightness,所以格式化打印到buf中的任然是正确的brightness。