***************************************************************************************************************************
作者: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驱动的关与开。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐