分类: LINUX
2010-07-13 12:01:06
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();
/* I
at91_add_device_i
/* 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 I
* 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_(&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。