基于sysfs的LED驱动编写以及应用程序的编写
***************************************************************************************************************************
作者:EasyWave 时间:2012.05.01
类别:linux驱动开发 声明:转载,请保留链接
***************************************************************************************************************************
编写基于sysfs的驱动,在linux内核中有很多可以参考的代码,我们只需要将其移植到自己的平台上即可。由于需要在linux内核启动时,LED一直闪烁,等到linux启动完毕之后,整个LED的控制权就交给应用程序,因此是需要用到timer_list的结构体的。
一:timer_list结构体
使用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。 time_list结构里expires是标明这个时钟的周期,单位采用jiffies的单位。function就是时间到了以后的回调函数,它的参数就是timer_list中的data。 data这个参数在初始化时钟的时候赋值,一般赋给它设备的device结构指针。在预置时间到系统调用function,同时系统把这个time_list从定时队列里清除。所以如果需要一直使用定时函数,要在function里再次调用add_timer()把这个timer_list加进定时队列。
管理定时器的接口
创建定时器需要先定义它:
struct timer_list leds_report_timer;
接着需要通过一个辅助函数初始化定时器数据结构的内部值,初始化必须在使用其他定时器管理函数对定时器进行操作前完成。
init_timer(&leds_report_timer);
现在可以填充结构中需要的值了(与定时器直接相关的三个参数):
leds_report_timer.expires = jiffies + delay; // 定时器超时时的节拍数
leds_report_timer.data = 0; // 给定时器处理函数传入参数0
leds_report_timer.function = my_function; // 定时器超时时调用的函数
leds_report_timer.expires表示超时时间,它是以节拍为单位的绝对计数值。如果当前jiffies计数等于或大于leds_report_timer.expires,那么leds_report指向的处理函数就会开始执行,另外该函数还要使用长整型参数 leds_report_timer.data。如果你不需要这个参数,可以简单地传递0(或任何其他值)给处理函数。
最后,必须激活定时器:
add_timer(&leds_report_timer);
大功告成,定时器可以工作了!但请注意定时值的重要性。当前节拍计数大于或等于指定的超时值时,内核就开始执行定时器处理函数。虽然内核可以保证不会在超时时间到期前运行定时器处理函数,但是有可能延误定时器的执行 。一般来说,定时器都在超时后马上就回执行,但是也有可能被推迟到一下时钟节拍才能运行,所以不能用定时器来实现任何硬实时任务。
有时可能需要更改已经激活的定时器超时时间,所以内核通过函数mod_timer()来实现该功能,该函数可以改变指定的定时器超时时间:
mod_timer(&leds_report_timer, jiffies+new_delay);
mod_timer()函数也可操作那些已经初始化,但还没有被激活的定时器,如果定时器未被激活,mod_timer()会激活它。如果调用时定时器未被激活,该函数返回0;否则返回1。但不论哪种情况,一旦从mod_timer()函数返回,定时器 都将被激活而且设置了新的定时值。
如果需要在定时器超时前停止定时器,可以使用del_timer()函数:
del_timer(&leds_report_timer);
被激活或未被激活的定时器都可以使用该函数,如果定时器还未被激活,该函数返回0;否则返回1。注意,你不需要为已经超时的定时器调用该函数,因为它们超时后会自动被删除。
当删除定时器时,必须小心一个潜在的竞争条件。当del_timer()返回后,可以保真的只是: 定时器不会再被激活(也就是,将来不会执行),但是在多处理机器上定时器中断可能已经在其他处理器上运行了,所以删除定时器时需要等 待可能在其他处理器上运行的定时器处理程序都退出,这时就要使用del_timer_sync()函数执行删除工作:
del_timer_sync(&leds_report_timer);
和del_timer()函数不同,del_timer_sync()函数不能在中断上下文中使用。
二:LED驱动
因为我用的NUC950,而NUC950的linux2.6.35的内核中并没有包含LED的驱动程序,因此就自己写了一个基于sysfs的LED驱动,同时需要添加了reg-leds.h文件,具体代码如下:
/*
* arch/arm/mach-nuc900/include/mach/regs-leds.h
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation;version 2 of the License.
*
*/
#ifndef __ASM_ARCH_REGLEDS_H
#define __ASM_ARCH_REGLEDS_H
#define NUC900_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */
#define NUC900_LEDF_TRISTATE (1<<1) /* tristate to turn off */
struct nuc900_led_platdata {
unsigned int gpio_group;
unsigned int gpio_pin;
unsigned int flags;
char *name;
char *def_trigger;
};
#endif /* __ASM_ARCH_REGLEDS_H */
同时还需要添加平台设备platform_device,代码如下:
#if defined(CONFIG_LEDS_NUC900)
/* LED */
static struct nuc900_led_platdata nuc900_pdata_led = {
.gpio_group = GPIO_GROUP_D,
.gpio_pin = 0, //GPIOD0
.flags = NUC900_LEDF_ACTLOW,
.name = "led",
.def_trigger = "timer",
};
static struct platform_device nuc900_device_led = {
.name = "nuc900-led",
.id = 0,
.dev = {
.platform_data = &nuc900_pdata_led,
},
};
#endif //It is enable support led if you have configured the CONFIG_LEDS_NUC900.
将上面的平台设备加入到平台设备的初始化列表中,即可。
#if defined(CONFIG_LEDS_NUC900)
&nuc900_device_led,
#endif
最后是LED驱动代码的编写,整个代码参考了三星的LED驱动,针对NUC950修改了代码。具体的代码如下:
/* drivers/leds/leds-nuc900.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation;version 2 of the License.
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/nuc900_gpio.h>
#include <mach/regs-leds.h>
/* our context */
#define DEF_LEDS_DELAY1 HZ/2
#define DEF_LEDS_DELAY2 HZ/2
struct nuc900_gpio_led {
struct led_classdev cdev;
struct nuc900_led_platdata *pdata;
};
static struct nuc900_gpio_led *leds;
static struct timer_list leds_report_timer;
static int blink_cnt = 0;
static void nuc900_led_timer_init(void);
static inline struct nuc900_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
return platform_get_drvdata(dev);
}
static inline struct nuc900_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
return container_of(led_cdev, struct nuc900_gpio_led, cdev);
}
static void nuc900_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct nuc900_gpio_led *led = to_gpio(led_cdev);
struct nuc900_led_platdata *pd = led->pdata;
/* there will be a short delay between setting the output and
* going from output to input when using tristate. */
if(value) //
{
nuc900_led_timer_init();
}
else
{
del_timer(&leds_report_timer);
}
nuc900_gpio_set(pd->gpio_group, pd->gpio_pin, (value ? 1 : 0) ^
(pd->flags & NUC900_LEDF_ACTLOW));
}
static int nuc900_led_remove(struct platform_device *dev)
{
leds = pdev_to_gpio(dev);
led_classdev_unregister(&leds->cdev);
del_timer(&leds_report_timer);
kfree(&leds_report_timer);
kfree(leds);
return 0;
}
static void nuc900_blink_led(enum led_brightness value)
{
struct nuc900_led_platdata *pd = leds->pdata;
/* there will be a short delay between setting the output and
* going from output to input when using tristate. */
nuc900_gpio_set(pd->gpio_group, pd->gpio_pin, (value ? 1 : 0) ^
(pd->flags & NUC900_LEDF_ACTLOW));
}
static void leds_report(unsigned long data)
{
leds_report_timer.expires = jiffies + data;
add_timer(&leds_report_timer);
// printk("leds blink test.\r\n");
if(blink_cnt == 0)
blink_cnt = 1;
else if(blink_cnt == 1)
blink_cnt = 0;
nuc900_blink_led(blink_cnt);
}
static void nuc900_led_timer_init(void)
{
init_timer(&leds_report_timer);
leds_report_timer.function = leds_report;
leds_report_timer.data = DEF_LEDS_DELAY2;
leds_report_timer.expires = jiffies + DEF_LEDS_DELAY1;
add_timer(&leds_report_timer);
}
static int nuc900_led_probe(struct platform_device *dev)
{
struct nuc900_led_platdata *pdata = dev->dev.platform_data;
// struct nuc900_gpio_led *led;
int ret;
leds = kzalloc(sizeof(struct nuc900_gpio_led), GFP_KERNEL);
if (leds == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}
ret = nuc900_gpio_configure(pdata->gpio_group, pdata->gpio_pin);
if (!ret) {
printk(" leds gpio group %s registration failed\n", pdata->name);
return -ENODEV;
}
platform_set_drvdata(dev, leds);
leds->cdev.brightness_set = nuc900_led_set;
leds->cdev.default_trigger = pdata->def_trigger;
leds->cdev.name = pdata->name;
leds->cdev.flags |= LED_CORE_SUSPENDRESUME;
leds->pdata = pdata;
nuc900_led_timer_init();
/* no point in having a pull-up if we are always driving */
if (pdata->flags & NUC900_LEDF_ACTLOW) {
nuc900_gpio_set_output(pdata->gpio_group, pdata->gpio_pin);
nuc900_gpio_set(pdata->gpio_group, pdata->gpio_pin, 0);
printk("Enter nuc900 gpio set LED on.\r\n");
}
/* register our new led device */
ret = led_classdev_register(&dev->dev, &leds->cdev);
if (ret < 0) {
dev_err(&dev->dev, "led_classdev_register failed\n");
kfree(leds);
return ret;
}
return 0;
}
static struct platform_driver nuc900_led_driver = {
.probe = nuc900_led_probe,
.remove = nuc900_led_remove,
.driver = {
.name = "nuc900-led",
.owner = THIS_MODULE,
},
};
static int __init nuc900_led_init(void)
{
pr_info("NUC900 SoC LEDS Driver.\r\n");
return platform_driver_register(&nuc900_led_driver);
}
static void __exit nuc900_led_exit(void)
{
platform_driver_unregister(&nuc900_led_driver);
}
module_init(nuc900_led_init);
module_exit(nuc900_led_exit);
MODULE_AUTHOR("EasyWave <wavemcu@163.com>");
MODULE_DESCRIPTION("NUC900 LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:nuc900_led");
三:应用程序如何控制LED
void write_int(char *path, int i)
{
char buf[1024];
int fd = open(path, O_RDWR);
if (fd < 0) {
perror("open");
exit(1);
}
sprintf(buf, "%d", i);
if (write(fd, buf, strlen(buf)) != strlen(buf)) {
perror("write");
exit(1);
}
close(fd);
}
void set_led(int on)
{
write_int("/sys/devices/platform/leds-gpio/leds/brightness", on);
}
就是这么简单。就可以控制sysfs的LED驱动的关与开。
更多推荐
所有评论(0)