Platform 设备之 gpio-led 分析
led 测试
以使用的 9263 板子为例,首先看 board-sam9263ek.c 的 ek_board_init 函数,
static void __init ek_board_init(void)
{
/* Serial */
at91_add_device_serial();
/* USB Host */
at91_add_device_usbh(&ek_usbh_data);
/* USB Device */
at91_add_device_udc(&ek_udc_data);
/* SPI */
at91_set_gpio_output(AT91_PIN_PE20, 1); /* select spi0 clock */
at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));
/* Touchscreen */
ek_add_device_ts();
/* MMC */
at91_add_device_mmc(1, &ek_mmc_data);
/* Ethernet */
at91_add_device_eth(&ek_macb_data);
/* NAND */
ek_add_device_nand();
/* I2C */
at91_add_device_i2c(NULL, 0);
/* LCD Controller */
at91_add_device_lcdc(&ek_lcdc_data);
/* Push Buttons */
ek_add_device_buttons();
/* AC97 */
at91_add_device_ac97(&ek_ac97_data);
/* LEDs */
at91_gpio_leds(ek_leds, ARRAY_SIZE(ek_leds));
at91_pwm_leds(ek_pwm_led, ARRAY_SIZE(ek_pwm_led));
/* shutdown controller, wakeup button (5 msec low) */
at91_sys_write(AT91_SHDW_MR, AT91_SHDW_CPTWK0_(10) | AT91_SHDW_WKMODE0_LOW
| AT91_SHDW_RTTWKEN);
}
然后我们看看 ek_leds 的定义,如下
/*
* LEDs ... these could all be PWM-driven, for variable brightness
*/
static struct gpio_led ek_leds[] = {
{ /* "right" led, green, userled2 (could be driven by pwm2) */
.name = "ds2",
.gpio = AT91_PIN_PB23,
.active_low = 1,
.default_trigger = "none",
},
{ /* "power" led, yellow (could be driven by pwm0) */
.name = "ds3",
.gpio = AT91_PIN_PB24,
.default_trigger = "heartbeat",
}
};
因为我使用的板子上有 2 个 led 灯接的是 pb23 和 pb24 ,然后再 make menuconfig 里面配置,如下
Device Drivers à [*]LED Support à [*]LED class support
[*]LED support for GPIO connected LEDS
[*]LED Trigger support
[*]LED heartbeat trigger
然后编译,成功后,下载进入板子,启动开发板,会发现 ds3 不停的闪烁,就是上面的 heartbeat.
测试下 ds2, 如下
# echo 1 > /sys/class/leds/ds2/brightness
ds2 会亮。
继续测试
# echo 0 > /sys/class/leds/ds2/brightness
ds2 会灭掉。
测试就到此为止,接下来,分析下它的实现原理。
原理跟踪
1, 相关重要结构体介绍
struct platform_device {
const char * name; // 设备名
int id; // 设备编号
struct device dev; // device 结构
u32 num_resources; // 设备所使用的各类资源数量
struct resource * resource; // 资源 主要是 io 内存和 irq
};
* platform_device.name ... which is also used to for driver matching.
可用于和驱动匹配
* platform_device.id ... the device instance number, or else "-1" to indicate there's only one.
设备的编号,如果是唯一设备,则用 -1 表示。
/* For the leds-gpio driver */
struct gpio_led {
const char *name;
char *default_trigger;
unsigned gpio;
u8 active_low;
};
这里的 name 会显示在 /sys 相关的子目录下,如本例中的 /sys/class/leds/ds2
default_trigger 是用来和匹配某个设备闪烁的名字,比如本例中的 heartbeat 和 none , none 表示默认不闪烁。
Gpio 就不用多解释了,就是所用的那个 pin 。
active_low 可以参考下面函数的红色部分
static void gpio_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct gpio_led_data *led_dat =
container_of(led_cdev, struct gpio_led_data, cdev);
int level;
if (value == LED_OFF)
level = 0;
else
level = 1;
if (led_dat->active_low)
level = !level;
/* Setting GPIOs with I2C/etc requires a task context, and we don't
* seem to have a reliable way to know if we're already in one; so
* let's just assume the worst.
*/
if (led_dat->can_sleep) {
led_dat->new_level = level;
schedule_work(&led_dat->work);
} else
gpio_set_value(led_dat->gpio, level);
}
也就是说,当 active_low 为 1 时,取值刚好相反,特意测试了下,把 ds2 的 active_low 取值为 0 后,一开机, ds2 就亮了
# echo 1 > /sys/class/leds/ds2/brightness
ds2 会灭。
继续测试
# echo 0 > /sys/class/leds/ds2/brightness
ds2 会亮。
struct gpio_led_platform_data {
int num_leds; // led 灯的个数
struct gpio_led *leds; // leds 指针 , 参考 struct gpio_led
int (*gpio_blink_set)(unsigned gpio,
unsigned long *delay_on,
unsigned long *delay_off);
};
gpio_blink_set 如下
static int gpio_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
struct gpio_led_data *led_dat =
container_of(led_cdev, struct gpio_led_data, cdev);
return led_dat->platform_gpio_blink_set(led_dat->gpio, delay_on, delay_off);
}
简单的分析下,
struct gpio_led_data *led_dat =
container_of(led_cdev, struct gpio_led_data, cdev);
这里调用的 container_of 是通过结构体成员的指针找到对应结构体的指针,这个技巧在 linux 内核编程使用常用。本例中 container_of 的第一个参数是结构体成员的指针,第 2 个参数为整个结构体的类型,第 3 个参数为传入的第一个参数即结构体成员的类型, container_of() 返回的是整个结构体的指针。
得到 led_dat 后,然后返回 led_dat->platform_gpio_blink_set(led_dat->gpio, delay_on, delay_off); 这个现在还没有用到过,如果用到了,再补充。
上面又有个新的结构体,如下
struct gpio_led_data {
struct led_classdev cdev; //
unsigned gpio; // IO pin
struct work_struct work;
u8 new_level;
u8 can_sleep;
u8 active_low;
int (*platform_gpio_blink_set)(unsigned gpio,
unsigned long *delay_on, unsigned long *delay_off);
};
这个结构题里面就是 struct led_classdev cdev; 相对复杂一点 , 放到后面具体的实例讲解,先说简单的。
struct work_struct work;
u8 new_level;
u8 can_sleep;
这 3 个主要是为了 led 的睡眠,可暂不考虑。
u8 active_low;
int (*platform_gpio_blink_set)(unsigned gpio,
unsigned long *delay_on, unsigned long *delay_off);
这 2 个可以参考 struct gpio_led 和 struct gpio_led_platform_data
2, 流程跟踪
(1)at91_gpio_leds(ek_leds, ARRAY_SIZE(ek_leds));
进入到函数里面如下
/* ------------------------------------------------------------------------- */
#if defined(CONFIG_NEW_LEDS)
/*
* New cross-platform LED support.
*/
static struct gpio_led_platform_data led_data;
static struct platform_device at91_gpio_leds_device = {
.name = "leds-gpio",
.id = -1,
.dev.platform_data = &led_data,
};
void __init at91_gpio_leds(struct gpio_led *leds, int nr)
{
int i;
if (!nr)
return;
for (i = 0; i < nr; i++)
at91_set_gpio_output(leds[i].gpio, leds[i].active_low); // 设置为输出,低
led_data.leds = leds; // led 指针
led_data.num_leds = nr; // led 数量
platform_device_register(&at91_gpio_leds_device); // 平台设备注册
}
#else
void __init at91_gpio_leds(struct gpio_led *leds, int nr) {}
#endif
进入 platform_device_register(&at91_gpio_leds_device); // 平台设备注册
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
这里知道是怎么回事就行了,暂不深究。
调用 platform_device_register 后,若没问题,则注册平台设备成功。
然后我们进入到下一步 platform_driver
(2) gpio-led 的 platform_driver
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.remove = __devexit_p(gpio_led_remove),
.suspend = gpio_led_suspend,
.resume = gpio_led_resume,
.driver = {
.name = "leds-gpio",
.owner = THIS_MODULE,
},
};
static int __init gpio_led_init(void)
{
return platform_driver_register(&gpio_led_driver);
}
static void __exit gpio_led_exit(void)
{
platform_driver_unregister(&gpio_led_driver);
}
这里先简单介绍 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 (*suspend_late)(struct platform_device *, pm_message_t state); //
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *); // 恢复
struct pm_ext_ops *pm;
struct device_driver driver;
};
然后看看 platform_driver_register 干了些什么 ?
/**
* platform_driver_register
* @drv: platform driver structure
*/
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
if (drv->pm)
drv->driver.pm = &drv->pm->base;
return driver_register(&drv->driver);
}
然后看下 gpio_led_driver 就比较好理解了,该 platform_driver 只有 probe , remove , suspend , resume 4 个函数指针。
.driver = {
.name = "leds-gpio",
.owner = THIS_MODULE,
},
这里的 name 用来表示匹配 platform_device 里的 name ,如果匹配了,则开始下一步的探测。
测试如下:
# cat /sys/class/leds/ds2/uevent
PHYSDEVPATH=/devices/platform/leds-gpio
PHYSDEVBUS=platform
PHYSDEVDRIVER=leds-gpio
然后开始下一步的探测 ----probe
(3) gpio-led 的 gpio_led_probe
static int gpio_led_probe(struct platform_device *pdev
{
struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
struct gpio_led *cur_led;
struct gpio_led_data *leds_data, *led_dat;
int i, ret = 0;
if (!pdata)
return -EBUSY;
leds_data = kzalloc(sizeof(struct gpio_led_data) * pdata->num_leds,
GFP_KERNEL);
if (!leds_data)
return -ENOMEM;
for (i = 0; i < pdata->num_leds; i++) {
cur_led = &pdata->leds[i];
led_dat = &leds_data[i];
ret = gpio_request(cur_led->gpio, cur_led->name);
if (ret < 0)
goto err;
led_dat->cdev.name = cur_led->name;
led_dat->cdev.default_trigger = cur_led->default_trigger;
led_dat->gpio = cur_led->gpio;
led_dat->can_sleep = gpio_cansleep(cur_led->gpio);
led_dat->active_low = cur_led->active_low;
if (pdata->gpio_blink_set) {
led_dat->platform_gpio_blink_set = pdata->gpio_blink_set;
led_dat->cdev.blink_set = gpio_blink_set;
}
led_dat->cdev.brightness_set = gpio_led_set;
led_dat->cdev.brightness = LED_OFF;
gpio_direction_output(led_dat->gpio, led_dat->active_low);
INIT_WORK(&led_dat->work, gpio_led_work);
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret < 0) {
gpio_free(led_dat->gpio);
goto err;
}
}
platform_set_drvdata(pdev, leds_data);
return 0;
err:
if (i > 0) {
for (i = i - 1; i >= 0; i--) {
led_classdev_unregister(&leds_data[i].cdev);
cancel_work_sync(&leds_data[i].work);
gpio_free(leds_data[i].gpio);
}
}
kfree(leds_data);
return ret;
}
struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
得到 platform_device ------pdev 结构体里面 dev 的私有平台指针 platform_data
struct gpio_led *cur_led; // 用于指向当前 gpio_led_platform_data 里面的 gpio_led
struct gpio_led_data *leds_data, *led_dat;
leds_data 用于申请内存空间的指针
led_dat 用于指向当前 gpio_led_data 内存地址
leds_data = kzalloc(sizeof(struct gpio_led_data) * pdata->num_leds,
GFP_KERNEL);
if (!leds_data)
return -ENOMEM;
申请内存,若申请不到返回错误
然后看看循环里面的
cur_led = &pdata->leds[i];
led_dat = &leds_data[i];
用于指向当前 gpio_led_platform_data 里面的 gpio_led
led_dat 用于指向当前 gpio_led_data 内存地址
ret = gpio_request(cur_led->gpio, cur_led->name);
if (ret < 0)
goto err;
申请 gpio 口,若失败返回错误。实际上这里总会成功,代码如下
static inline int gpio_request(unsigned gpio, const char *label)
{
return 0;
}
led_dat->cdev.name = cur_led->name;
led_dat->cdev.default_trigger = cur_led->default_trigger;
led_dat->gpio = cur_led->gpio;
led_dat->can_sleep = gpio_cansleep(cur_led->gpio);
led_dat->active_low = cur_led->active_low;
就是把 cur_led 里面相应的数据赋给 led_dat
if (pdata->gpio_blink_set) {
led_dat->platform_gpio_blink_set = pdata->gpio_blink_set;
led_dat->cdev.blink_set = gpio_blink_set;
}
如果有设置 gpio_blink_set ,则把相应的指针都指向它,显然我们是没有设置的。
led_dat->cdev.brightness_set = gpio_led_set;
led_dat->cdev.brightness = LED_OFF;
把 gpio_led_set 指向类结构体 led_classdev 里的 brightness_set 。
gpio_direction_output(led_dat->gpio, led_dat->active_low);
设置相应 io 口为输出,并指定值
INIT_WORK(&led_dat->work, gpio_led_work);
准备好 gpio_led 的原子操作,可以查看 workqueue.h
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret < 0) {
gpio_free(led_dat->gpio);
goto err;
}
终于到注册设备了,可以松口气了。
进入到 led_classdev_register
/**
* led_classdev_register - register a new object of led_classdev class.
* @dev: The device to register.
* @led_cdev: the led_classdev structure for this device.
*/
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
int rc;
led_cdev->dev = device_create_drvdata(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev);
/* register the attributes */
rc = device_create_file(led_cdev->dev, &dev_attr_brightness);
if (rc)
goto err_out;
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
up_write(&leds_list_lock);
led_update_brightness(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
rc = device_create_file(led_cdev->dev, &dev_attr_trigger);
if (rc)
goto err_out_led_list;
led_trigger_set_default(led_cdev);
#endif
printk(KERN_INFO "Registered led device: %s/n",
led_cdev->name);
return 0;
#ifdef CONFIG_LEDS_TRIGGERS
err_out_led_list:
device_remove_file(led_cdev->dev, &dev_attr_brightness);
list_del(&led_cdev->node);
#endif
err_out:
device_unregister(led_cdev->dev);
return rc;
}
简单分析
led_cdev->dev = device_create_drvdata(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
查看 device_create_drvdata ,其实也就是 device_create ,如下
#define device_create_drvdata device_create
看下 device_create 功能,下面的注释说的很清楚了,主要就是创建一个设备并在 sysfs 下注册。
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
本例中这步就把设备注册上了,设备号之类的也分配了。
接下来继续看 led_classdev_register
/* register the attributes */
rc = device_create_file(led_cdev->dev, &dev_attr_brightness);
if (rc)
goto err_out;
增加属性
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
up_write(&leds_list_lock);
放到 led 队列中去
led_update_brightness(led_cdev);
更新 led_cdev 的属性
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
rc = device_create_file(led_cdev->dev, &dev_attr_trigger);
if (rc)
goto err_out_led_list;
led_trigger_set_default(led_cdev);
#endif
主要是增加 led_cdev 的 dev_attr_trigger 属性。
如果一切顺利, led_classdev_register 就注册成功了。
然后回到 gpio_led_probe 函数
最后还有一句
platform_set_drvdata(pdev, leds_data);
设为平台设备私有数据。
(4) gpio-led 的其他
static int __devexit gpio_led_remove(struct platform_device *pdev)
{
int i;
struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
struct gpio_led_data *leds_data;
leds_data = platform_get_drvdata(pdev);
for (i = 0; i < pdata->num_leds; i++) {
led_classdev_ unregister (&leds_data[i].cdev);
cancel_work_sync(&leds_data[i].work);
gpio_free(leds_data[i].gpio);
}
kfree(leds_data);
return 0;
}
移除时先通过 platform_get_drvdata 得到指针地址,先释放掉相关资源,如释放掉注册的 class ,工作队列,申请的 IO 资源,最后是否掉申请的指针 leds_data 。