详情参见教材206页~208页
我们的LED是基于 GPIO的,为此,内核有两个对应的驱动程序,分别时GPIO驱动和LED驱动,基于GPIO的LED的驱动调用了GPIO驱动导出的函数,这一节我们并不关心GPIO的驱动(后面会有详细说明)。关于LED驱动,内核文档Documentation/leds/leds-class.txt有简单的描述,它实现了一个leds类,通过sysfs的接口才对LED进行控制,所以它并没有使用字符设备驱动的框架。驱动代码的实现qing参见Linux3.14/drivers/leds/leds-gpio.c。
既然驱动已经实现了,那么我们要怎么来让他工作起来呢?首先要配置内核,确保驱动被选配了。再内核源码下进行make ARCH=arm menuconfig命令,按照下面的选项进行选择。
Device Driver --->
[*] LED Support --->
<*> LED Class Support
<*> LED Support for GPIO connected LEDs
[*] LED Trigger support --->
选好配置后,保存,使用下面的命令重新编译内核,然后复制到TFTP服务器指定的目录下。
配置好后添加设备树节点
vim arch/arm/boot/dts/exynos4412-fs4412.dts
compatible属性为gpio-leds,可以和LED驱动匹配。每个节点中的label是出现在sys目录下的子目录名字。gpios则制定了该LED所连接的GPIO口,第三个值为0表示高电平点亮LED灯,为1表示低电平点亮LED灯。default-state属性的值为off,则表示默认情况下LED灯是熄灭的,为on则默认点亮。修正好后使用下面的命令编译设设备树,然后复制到指定目录。
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- dtbs
cp arch/arm/boot/dts/exynos4412-fs4412.dtb ../../tftpboot/
重启开发板,使用下面的命令看到对应的设备目录
[root@farsight ]# ls -l /sys/class/leds/
total 0
lrwxrwxrwx 1 0 0 0 Jan 1 00:00 fs4412-led2 -> ../../devices/fs4412_leds.3/leds/fs4412-led2
lrwxrwxrwx 1 0 0 0 Jan 1 00:00 fs4412-led3 -> ../../devices/fs4412_leds.3/leds/fs4412-led3
lrwxrwxrwx 1 0 0 0 Jan 1 00:00 fs4412-led4 -> ../../devices/fs4412_leds.3/leds/fs4412-led4
lrwxrwxrwx 1 0 0 0 Jan 1 00:00 fs4412-led5 -> ../../devices/fs4412_leds.3/leds/fs4412-led5
lrwxrwxrwx 1 0 0 0 Jan 1 00:00 mmc0:: -> ../../devices/12530000.sdhci/leds/mmc0::
fs4412-led2、fs4412-led3、fs4412-led4、fs4412-led5分别对应了4个LED灯,在每个目录下都有一个brightness文件,通过该文件可以获取LED当前的亮度,通过写该文件可以修改LED灯的亮度。因为这些LED灯饰连接在GPIO端口上面所以亮度只有0和1,0是熄灭,1是点亮
如下
[root@farsight ]# cat /sys/class/leds/fs4412-led2/brightness
0
[root@farsight ]# echo "1" > /sys/class/leds/fs4412-led2/brightness
当然也可以编写一个应用程序来控制LED灯的亮灭,应用测试代码如下
“程序源码/examples/ex1”
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#define LED_DEV_PATH "/sys/class/leds/led%d/brightness"
#define ON 1
#define OFF 0
int fs4412_set_led(unsigned int lednum, unsigned int mode)
{
int fd;
int ret;
char devpath[128];
char *on = "1\n";
char *off = "0\n";
char *m = NULL;
snprintf(devpath, sizeof(devpath), LED_DEV_PATH, lednum);
fd = open(devpath, O_WRONLY);
if (fd == -1) {
perror("fsled->open");
return -1;
}
if (mode == ON)
m = on;
else
m = off;
ret = write(fd, m, strlen(m));
if (ret == -1) {
perror("fsled->write");
close(fd);
return -1;
}
close(fd);
return 0;
}
int main(int argc, char *argv[])
{
unsigned int lednum = 2;
while (1) {
fs4412_set_led(lednum, ON);
usleep(500000);
fs4412_set_led(lednum, OFF);
usleep(500000);
lednum++;
if (lednum > 5)
lednum = 2;
}
}
led子系统(一)
在Linux系统中为了更好的管理LED设备,设计了LED系统,基于LED子系统编写驱动非常简单。
一 LED子系统
在Linux内核源码树下的drivers/leds目录下有以下文件:
led-class.c : 提供了注册led设备的函数接口以及用户空间操作LED设备属性文件
led-core.c : 提供了一些LED亮度和闪烁的函数接口
led-triggers.c : 提供了对trigger触发器的操作函数接口
这些文件构成了led子系统框架。
在Linux源码树下的drivers/leds/trigger目录下可以看到Linux内核提供的触发器驱动,常用的触发器有:
1. 心跳灯触发器(heartbeat)
在drivers/leds/trigger/ledtrig-heartbeat.c中定义了一个名为"heartbeat"的心跳触发器,它可以控制所有与之建立连接的led会不停的闪烁。这个触发器用来指示内核是否已经挂掉。如果与之建立连接的led不再闪烁了,说明内核已经挂掉了。这就是“心跳”的含义,和从人的心脏是否跳动来判断人是否死亡的原理是类似的。
2.闪烁定时触发器(timer)
在drivers/leds/trigger/ledtrig-timer.c中定义了一个名为“timer”的触发器。当某个led_classdev与之连接后,这个触发器会在/sys/class/leds//下创建两个属性文件delay_on/delay_off。用户空间往这两个文件中写入数据后,相应的led会按照设置的高低电平的时间(单位毫秒)来闪烁。如果led_classdev注册了硬件闪烁的接口led_cdev->blink_set就是用硬件控制闪烁,否则用软件定时器来控制闪烁。
3.default-on触发器
在drivers/leds/trigger/ledtrig-default-on.c中实现了一个名为“default-on”的触发器。这个触发器只定义了activate成员函数。它的activate函数的定义如下:
static void defon_trig_activate(struct led_classdev *led_cdev)
{
led_set_brightness(led_cdev, LED_FULL);
}
也就是说,点亮led只能是最亮的亮度,无法调节。一旦ledl_classdev与之建立了连接,就一直处于最亮的状态,直到取消和触发器的连接。
4.背光触发器(backlight)
这个触发器驱动与LCD驱动有关联,用来设置LCD背光的亮度。
注意:
LED设备驱动可以和Linux内核中提供的LED触发器驱动关联,LED触发器驱动体现的是操作LED的一些算法。
二 基于LED子系统编写LED驱动
1.填充struct led_classdev 结构体
@name : 名字
@brightness : 记录当前LED灯的亮度
@max_brightness: 最大LED灯的亮度
@brightness_set : 指定设置LED灯亮度的函数
2.注册struct led_classdev设备
下面我们看一下,Linux内核中,三星基于LED子系统编写的LED设备驱动。
led子系统(二)
Linux内核自带了一个通用的LED驱动,在Linux 3.14内核中,我们只需要在设备树中配置以下自己平台LED设备的硬件信息,就可以使用Linux内核通用的LED驱动了,真的简单呢!
一、在设备树中添加 LED设备信息
我们可以在Linux内核源码树下的 Documentation/devicetree/bindings/leds/目录下找到LED设备树编写的相关说明文档。这里对接Linux内核通用LED驱动drivers/leds/leds-gpio.c为例,编写LED设备树节点。
相关属性解析如下:
二、在Linux内核中配置leds-gpio.c驱动
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/err.h>
MODULE_AUTHOR("Raphael Assent <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>");
MODULE_DESCRIPTION("GPIO LED driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:leds-gpio");
struct gpio_led_data{
struct led_classdev cdev;
unsigned gpio;
struct work_struct work;
u8 new_level;
u8 can_sleep;
u8 active_low;
u8 blinking;
int (*platform_gpio_blink_set)(unsigned gpio, int state,
unsigned long *delay_on, unsigned long *delay_off);
};
static void gpio_led_work(struct work_struct *work)
{
struct gpio_led_data *led_dat = container_of(work, struct gpio_led_data, work);
if (led_dat->blinking) {
led_dat->platform_gpio_blink_set(led_dat->gpio,
led_dat->new_level,
NULL,NULL);
led_dat->blinking = 0;
} else
gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level);
}
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;
if (led_dat->can_sleep) {
led_dat->new_level = level;
schedule_work(&led_dat->work);
}else{
if (led_dat->blinking){
led_dat->platform_gpio_blink_set(led_dat->gpio, level,
NULL, NULL);
led_dat->blinking = 0;
}else
gpio_set_value(led_dat->gpio, level);
}
}
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);
led_dat->blinking = 1;
return led_dat->platform_gpio_blink_set(led_dat->gpio, GPIO_LED_BLINK,
delay_on, delay_off);
}
/*
@template 从设备树中解析的信息
@led_dat 描述led设备的结构体
@parent pdev->dev
@blink_set NULL
*/
static int create_gpio_led(const struct gpio_led *template,
struct gpio_led_data *led_dat, struct device *parent,
int (*blink_set)(unsigned, int, unsigned long *, unsigned long *))
{
int ret, state;
led_dat->gpio = -1;
/*skip leds that aren't available*/
if (!gpio_is_valid(template->gpio)) {
dev_info(parent, "Skiping unavailable LED gpio %d (%s)\n",
template->gpio, template->name);
return 0;
}
/*请求获取一个gpio*/
ret = devm_gpio_request(parent, template->gpio, template->name);
if (ret < 0)
return ret;
/*初始化struct gpio_led_data结构体中的struct led_classdev结构体成员*/
led_dat->cdev.name = template->name;
led_dat->cdev.default_trigger = template->default_trigger;
/*初始化struct gpio_led_data结构体的成员*/
led_dat->gpio = template->gpio;
led_dat->can_sleep = gpio_cansleep(template->gpio);
led_dat->active_low = template->active_low;
led_dat->blinking = 0;
//没有设置
if(blink_set) {
led_dat->platform_gpio_blink_set = blink_set;
led_dat->cdev.blink_set = gpio_blink_set;
}
/*struct led_classdev中led灯亮度设置*/
led_dat->cdev.brightness_set = gpio_led_set;
/*如果设备树中指定的默认状态时keep,则读取gpio当前状态,
然后和设备树中指定的led激活状态做异或运算,相同为0,不同为1
*/
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low;
else
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
/*确定当前需要设定的led state : 开还是关闭*/
led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
if (!template->retain_state_suspended)
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
//设置gpio输出电平
ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
if (ret < 0)
return ret;
INIT_WORK(&led_dat->work, gpio_led_work);
ret = led_classdev_register(parent, &led_dat->cdev);
if(ret < 0)
return ret;
return 0;
}
static void delete_gpio_led(struct gpio_led_data *led)
{
if (!gpio_is_valid(led->gpio))
return;
led_classdev_unregister(&led->cdev);
cancel_work_sync(&led->work);
}
struct gpio_leds_priv {
int num_leds;
struct gpio_led_data leds[];
};
static inline int sizeof_gpio_leds_priv(int num_leds)
{
return sizeof(struct gpio_leds_priv) +
(sizeof(struct gpio_led_data) * num_leds);
}
/* Code to create from OpenFirmware platform devices */
#ifdef CONFIG_OF_GPIO
static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node, *child;
struct gpio_leds_priv *priv;
int count, ret;
/*count LEDs in this device, so we know how much to allocate*/
/*获取设备树中子节点的个数*/
count = of_get_available_child_count(np);
if(!count)
return ERR_PTR(-ENODEV);
/*判断每个子节点的gpio属性是否存在*/
for_each_available_child_of_node(np, child)
if(of_get_gpio(child, 0) == -EPROBE_DEFER)
return ERR_PTR(-EPROBE_DEFER);
/*分配内存空间:struct gpio_leds_priv + n * sizeof(struct gpio_led_data)*/
priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count),
GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
/*遍历每个子节点*/
for_each_available_child_of_node(np, child) {
struct gpio_led led = {};
enum of_gpio_flags flags;
const char *state;
/*获取每个led节点关联的属性值*/
led.gpio = of_get_gpio_flags(child, 0, &flags);
led.active_low = flags & OF_GPIO_ACTIVE_LOW;
led.name = of_get_property(child, "lable", NULL) ? : child->name;
led.default_trigger = of_get_property(child, "linux, default-trigger", NULL);
state = of_get_property(child, "default-state", NULL);
if (state) {
if (!strcmp(state, "keep"))
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
else if (!strcmp(state, "on"))
led.default_state = LEDS_GPIO_DEFSTATE_ON;
else
led.default_state = LEDS_GPIO_DEFSTATE_OFF;
}
/*创建gpio led*/
ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],&pdev->dev, NULL);
if (ret < 0) {
of_node_put(child);
goto err;
}
}
return priv;
err:
for (count = priv->num_leds - 2; count >= 0; count--)
delete_gpio_led(&priv->leds[count]);
return ERR_PTR(-ENODEV);
}
static const struct of_device_id of_gpio_leds_match[] = {
{ .compatible = "gpio-leds", },
{},
};
#else /* CONFIG_OF_GPIO */
static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
{
return ERR_PTR(-ENODEV);
}
#endif /* CONFIG_OF_GPIO */
static int gpio_led_probe(struct platform_device *pdev)
{
/*platform_device结构体中保存的平台数据*/
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct gpio_leds_priv *priv;
int i, ret = 0;
/*没有设备树的时候,驱动的写法*/
if (pdata && pdata->num_leds) {
priv = devm_kzalloc(&pdev->dev,sizeof_gpio_leds_priv(pdata->num_leds),
GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->num_leds = pdata->num_leds;
for(i = 0; i < priv->num_leds; i++) {
ret = create_gpio_led(&pdata->leds[i], &priv->leds[i],&pdev->dev, pdata->
gpio_blink_set);
if (ret < 0){
/*On failure: unwind the led creations*/
for(i = i - 1; i >= 0; i--)
delete_gpio_led(&priv->leds[i]);
return ret;
}
}
} else {
/*设备树的写法*/
priv = gpio_leds_create_of(pdev);
if (IS_ERR(priv))
return PTR_ERR(priv);
}
platform_set_drvdata(pdev, priv);
return 0;
}
static int gpio_led_remove(struct platform_device *pdev)
{
struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < priv->num_leds; i++)
delete_gpio_led(&priv->leds[i]);
return 0;
}
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.remove = gpio_led_remove,
.driver = {
.name = "leds-gpio",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_gpio_leds_match),
},
};
module_platform_driver(gpio_led_driver);