GPIO子系统实现LED操作,支持sys文件系统开关LED

详情参见教材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_classdevled灯亮度设置*/

   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);

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
GPIO子系统框架是Linux内核中用于管理GPIO设备的框架,它提供了一种标准的方式来描述和管理GPIO设备。GPIO子系统框架的实现步骤如下: 1. 定义GPIO设备对象:GPIO设备对象是GPIO子系统框架的核心部分。它包含了GPIO设备的名称、GPIO号、方向和状态等信息。在定义GPIO设备对象时,需要使用`struct gpio_chip`结构体,它定义了GPIO设备对象的基本属性。 2. 定义GPIO操作函数:GPIO操作函数是实现GPIO设备操作的核心代码。GPIO操作函数通常包括初始化和清理函数,以及实现GPIO操作的读写函数和控制函数等。在定义GPIO操作函数时,需要使用`struct gpio_chip`结构体中定义的回调函数接口。 3. 注册GPIO设备对象:在Linux内核中,GPIO设备对象需要通过`gpiochip_add()`函数进行注册。在注册GPIO设备对象时,需要指定GPIO号范围、GPIO操作函数等参数。 4. 注销GPIO设备对象:在GPIO设备不再使用时,需要通过`gpiochip_remove()`函数进行注销。在注销GPIO设备对象时,需要释放相关资源并取消注册。 5. 调用GPIO操作函数:在使用GPIO设备时,可以通过`gpio_direction_input()`、`gpio_direction_output()`、`gpio_set_value()`、`gpio_get_value()`等函数调用GPIO操作函数。 以上是GPIO子系统框架的实现步骤。在编写GPIO驱动程序时,您需要了解这些步骤的基本原理和使用方法,并根据具体的硬件和需求来选择和使用适当的GPIO操作函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值