一、platform设备模型
从Linux 2.6起引入了一套新的驱动管理和注册机制,platform_device和platform_driver,Linux中大部分的设备驱动都可以使用这套机制。platform是一条虚拟总线。设备用platform_device表示,驱动用platform_driver进行注册,linux platform driver机制和传统的device driver机制(通过driver_register进行注册)相比,一个明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动中使用这些资源时通过platform device提供的标准结构进行申请并使用。这样提高了驱动和资源的独立性,并且具有较好的可移植性和安全性(这些标准接口是安全的)。当我们将设备和驱动注册到虚拟总线上(内核)时,会互相寻找一次对方。如果device和driver中的name这个字符串是想相同的话platform_bus就会调用driver中的.probe函数。设备和驱动的关系是多对一的关系,即多个相同设备可使用一个driver,靠device(设备)中的id号来区别。
通过platform机制开发底层驱动的大致流程为:
定义platform_deviece -->注册platform_device -->定义platform_driver --> 注册platform_driver。
二、注册platform_device
在内核源码中添加arch/arm/mach-s5pv210/include/mach/leds-gpio.h文件
/* arch/arm/mach-s5pv210/include/mach/leds-gpio.h
*
* Copyright (c) 2006 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <[email protected]>
*
* s5pv210 - LEDs GPIO connector
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#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 */
在内核arch/arm/mach-s5pv210/mach-x210.c文件中添加如下代码
/* LEDS */
static struct s5pv210_led_platdata x210_led1_pdata = {
.name = "led1",
.gpio = S5PV210_GPJ0(3),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct s5pv210_led_platdata x210_led2_pdata = {
.name = "led2",
.gpio = S5PV210_GPJ0(4),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct s5pv210_led_platdata x210_led3_pdata = {
.name = "led3",
.gpio = S5PV210_GPJ0(5),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct platform_device x210_led1 = {
.name = "s5pv210_led",
.id = 0,
.dev = {
.platform_data = &x210_led1_pdata,
},
};
static struct platform_device x210_led2 = {
.name = "s5pv210_led",
.id = 1,
.dev = {
.platform_data = &x210_led2_pdata,
},
};
static struct platform_device x210_led3 = {
.name = "s5pv210_led",
.id = 2,
.dev = {
.platform_data = &x210_led3_pdata,
},
};
static struct platform_device *smdkc110_devices[] __initdata = {
...
&x210_led1,
&x210_led2,
&x210_led3,
};
重新编译内核,三个led设备就会被注册到platform中,在/sys/bus/platform/devices中可以进行查看
三、注册platform_driver
#include <linux/module.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>
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 *led = to_gpio(led_cdev);
struct s5pv210_led_platdata *pd = led->pdata;
printk(KERN_INFO "s5pv210_led_set\n");
/* 设置gpio状态 */
if (value == LED_OFF)
gpio_set_value(pd->gpio, 1);
else
gpio_set_value(pd->gpio, 0);
}
static int s5pv210_led_probe(struct platform_device *dev)
{
struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
struct s5pv210_gpio_led *led;
int ret = -1;
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");
return -EINVAL;
}
/* 设置gpio方向为输出 */
gpio_direction_output(pdata->gpio, 1);
led->cdev.name = pdata->name;
led->cdev.brightness = 0;
led->cdev.brightness_set = s5pv210_led_set;
led->pdata = pdata;
/* 注册led设备 */
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 *led = pdev_to_gpio(dev);
struct s5pv210_led_platdata *pd = led->pdata;
/* 注销led设备 */
led_classdev_unregister(&led->cdev);
/* 释放gpio资源 */
gpio_free(pd->gpio);
kfree(led);
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("lsm"); // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
加载驱动之后,在/sys/bus/platform/drivers中可以进行查看
四、测试
加载好led驱动之后,进入/sys/class/leds