imx28 leds 平台总线驱动-凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 1125442
  • 博文数量: 146
  • 博客积分: 190
  • 博客等级: 入伍新兵
  • 技术积分: 5225
  • 用 户 组: 普通用户
  • 注册时间: 2012-06-06 08:24
个人简介

慢行者

文章分类

全部博文(146)

文章存档

2013年(145)

2012年(1)

相关博文
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·

分类: linux

2013-03-22 15:32:30

imx28  leds 平台总线驱动

     成都莱得科技

led驱动从实质上来说很简单,只是在linux平台总线下,要理清这层层的关系还是要费点功夫了。

为什么是led驱动呢,柿子总是软的好吃!其实也不全是了,led代码比较少,用它来入门平台总线驱动的写法还是不错的选择滴。imx28led驱动是使用pwm调节亮度的,驱动是挂接在linux 标准led platform总线模型上的,即leds 子系统。进入正题:

leds的代码结构:

# led core

obj-$(config_new_leds) = led-core.o

obj-$(config_leds_class) = led-class.o

obj-$(config_leds_triggers) = led-triggers.o

obj-$(config_leds_gpio) = leds-gpio.o 

obj-$(config_leds_mxs) = leds-mxs-pwm.o

主要的文件就这么几个了。先来看看led-core.c 吧,这个文件无比的具有个性和让人兴奋,个性是只有变量的声明,兴奋是代码极其的少。

declare_rwsem(leds_list_lock);

export_symbol_gpl(leds_list_lock);

list_head(leds_list); 

export_symbol_gpl(leds_list);

所有代码就怎么几行,而且就是声明了2个变量。貌似注定打酱油来得,不过还真不是打酱油哦。leds_list_lock: 防止竞态,并发的锁,leds_list: 链接所有注册的led的全局链表 .2个全局数据无比的重要,后面你就会发现了。


在开始分析led-class.c之前,先来介绍下一些重要的数据结构:

/include/linux/leds.h 

亮度描述:



点击(此处)折叠或打开

  1. enum led_brightness {
  2.     led_off    = 0, //熄灭
  3.     led_half    = 127,
  4.     led_full    = 255,
  5. };



led的实例 


点击(此处)折叠或打开

  1. struct led_classdev {
  2.     const char     *name;
  3.     int     brightness; //当前亮度,imx28 通过控制pwm控制led端的电压
  4.     int     max_brightness; //最大亮度 led_full
  5.     int     flags; //标志,目前只支持 led_suspended
  6.     /*设置led的亮度,不可以睡眠*/
  7.     void     (*brightness_set)(struct led_classdev *led_cdev,
  8.     enum led_brightness brightness);
  9.     /* 获取亮度 */
  10.     enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
  11.     /* 激活硬件加速闪烁 */
  12.     int     (*blink_set)(struct led_classdev *led_cdev,
  13.      unsigned long *delay_on,
  14.      unsigned long *delay_off);

  15.     struct device     *dev;
  16.     struct list_head     node;     /* 串联起所有已经注册的led_classdev */
  17.     const char     *default_trigger;    /* 默认触发器 */
  18.     #ifdef config_leds_triggers
  19.     /* 这个读写信号量保护触发器数据 */
  20.     struct rw_semaphore     trigger_lock;
  21.     struct led_trigger    *trigger; //触发器指针
  22.      /*触发器使用的链表节点,用来连接同一触发器上的所有led_classdev */
  23.     struct list_head     trig_list;
  24.     void     *trigger_data; //触发器使用的私有数据
  25.     #endif
  26. };



触发器的结构体 


点击(此处)折叠或打开

  1.   struct led_trigger {
  2.         const char     *name; //触发器名字
  3.        /*led_classdev和触发器建立连接时会调用这个方法。*/
  4.         void        (*activate)(struct led_classdev *led_cdev);
  5.       /*led_classdev和触发器取消连接时会调用这个方法 */
  6.         void        (*deactivate)(struct led_classdev *led_cdev);

  7.         rwlock_t     leddev_list_lock; //保护链表的锁
  8.         struct list_head led_cdevs;

  9.        /* 连接下一个已注册触发器的链表节点,所有已注册的触发器都会被加入一个全局链表*/
  10.         struct list_head next_trig;
  11.   };


平台设备相关的led数据结构 

点击(此处)折叠或打开

  1. struct led_info {
  2.      const char *name;     //led 的名字
  3.      char *default_trigger; //默认触发器
  4.      int flags;
  5. };

  6. struct led_platform_data {
  7.      int num_leds; //led 个数 imx28 有2个led
  8.      struct led_info *leds; //每个led 信息
  9. };

在来介绍下和imx28平台相关的数据结构

arch/arm/plat-mxs/include/mach/device.h


点击(此处)折叠或打开

  1. struct mxs_pwm_led {
  2.     struct led_classdev dev; //每个led对应一个led_classdev
  3.     const char *name; //名字
  4.     unsigned int pwm; //pwm通道,最大8个通道
  5. };
  6. struct mxs_pwm_leds_plat_data {
  7.     unsigned int num;
  8.     struct mxs_pwm_led *leds;
  9. };
  10. struct mxs_pwm_leds {
  11.     struct clk *pwm_clk; //pwm 模块的时钟
  12.     unsigned int base; //寄存器基地址
  13.     unsigned int led_num; //led 个数,这里开发板只有2个led
  14.     struct mxs_pwm_led *leds; //指向2个led
  15. };



继续分析 driver/leds/led-class.c ,初始化函数在sys/class 目录下产生leds子目录,按照leds模型注册的设备都会产生这个目录。


点击(此处)折叠或打开

  1. static int __init leds_init(void)
  2. {
  3.     leds_class = class_create(this_module, "leds");
  4.     if (is_err(leds_class))
  5.         return ptr_err(leds_class);
  6.     leds_class->suspend = led_suspend;
  7.     leds_class->resume = led_resume;
  8.     return 0;
  9. }

  10. static void __exit leds_exit(void)
  11. {
  12.     class_destroy(leds_class);
  13. }

  14. static device_attr(brightness, 0644, led_brightness_show, led_brightness_store);
  15. static device_attr(max_brightness, 0444, led_max_brightness_show, null);
  16. #ifdef config_leds_triggers
  17. static device_attr(trigger, 0644, led_trigger_show, led_trigger_store);
  18. #endif


属性文件,在用户空间可以访问。后面会有提及到.



点击(此处)折叠或打开

  1. int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
  2. {
  3.     int rc;
  4.     /*创建device 在leds_class生成的目录下*/
  5.     led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
  6.       "%s", led_cdev->name);
  7.     if (is_err(led_cdev->dev))
  8.         return ptr_err(led_cdev->dev);
  9.     /* register the attributes */
  10.     rc = device_create_file(led_cdev->dev, &dev_attr_brightness);
  11.     //在sys/class/leds/led-pwmx 下创建一个属性文件。
  12.     if (rc)
  13.         goto err_out;
  14.     #ifdef config_leds_triggers
  15.     init_rwsem(&led_cdev->trigger_lock);
  16.     #endif
  17.     /* add to the list of leds */
  18.     down_write(&leds_list_lock);
  19.     list_add_tail(&led_cdev->node, &leds_list);//将新的led加入全局链表
  20.     up_write(&leds_list_lock);
  21.     if (!led_cdev->max_brightness)
  22.         led_cdev->max_brightness = led_full;
  23.     rc = device_create_file(led_cdev->dev, &dev_attr_max_brightness);
  24.     if (rc)
  25.         goto err_out_attr_max;
  26.     led_update_brightness(led_cdev);//获取led的当前亮度 更新led_cdev->brightness
  27.     #ifdef config_leds_triggers
  28.     rc = device_create_file(led_cdev->dev, &dev_attr_trigger);
  29.     if (rc)
  30.         goto err_out_led_list;
  31.     led_trigger_set_default(led_cdev);//为led_cdev设置默认的触发器
  32.     #endif
  33.     printk(kern_info "registered led device: %s\n",
  34.     led_cdev->name);
  35.         return 0;
  36.     #ifdef config_leds_triggers
  37.     err_out_led_list:
  38.     device_remove_file(led_cdev->dev, &dev_attr_max_brightness);
  39.     #endif
  40.     err_out_attr_max:
  41.     device_remove_file(led_cdev->dev, &dev_attr_brightness);
  42.     list_del(&led_cdev->node);
  43.     err_out:
  44.         device_unregister(led_cdev->dev);
  45.     return rc;
  46. }


led_classdev_register注册的struct led_classdev会被加入leds_list链表.(led-core中定义了这个list)


注销led_classdev:

void led_classdev_unregister(struct led_classdev *led_cdev)


led_brightness_show 和 led_brightness_store 分别显示和设置亮度 

led_trigger_show用于读取当前触发器的名字,led_trigger_store用于指定触发器的名字, 它会寻找所有已注册的触发器,找到同名的并设置为当前led的触发器。 


点击(此处)折叠或打开

  1. static ssize_t led_brightness_show(struct device *dev,
  2.        struct device_attribute *attr, char *buf)
  3. {
  4.     struct led_classdev *led_cdev = dev_get_drvdata(dev);
  5.     led_update_brightness(led_cdev);
  6.     return sprintf(buf, "%u\n", led_cdev->brightness); //显示当前亮度
  7. }

  8. static ssize_t led_brightness_store(struct device *dev,
  9.        struct device_attribute *attr, const char *buf, size_t size)
  10. {
  11.     /*取得led_classdev ,放在device的driver_data里。*/
  12.     struct led_classdev *led_cdev = dev_get_drvdata(dev);
  13.     ssize_t ret = -einval;
  14.     char *after;
  15.     unsigned long state = simple_strtoul(buf, &after, 10); //字符串形式的亮度值转换成数字。
  16.     size_t count = after - buf;
  17.     if (*after && isspace(*after))
  18.     count;
  19.     if (count == size) {
  20.     ret = count;
  21.     if (state == led_off)
  22.     led_trigger_remove(led_cdev);
  23.     //如果亮度值为0,移除trigger led_cdev->trigger = null;
  24.     led_set_brightness(led_cdev, state); //设置亮度值
  25.     }
  26.     return ret;
  27. }



led_trigger分析 /driver/leds/led-triggers.c 

注册触发器

int led_trigger_register(struct led_trigger *trigger)

注册的trigger会加入到全局链表trigger_list中。

首选确定触发器的名字不存在,然后遍历所有注册的触发器,如果发现那个led_classdev 的 default_trigger和这个触发器名字相同,就将这个触发器设置成led_classdev 的默认触发器。

设置触发器上所有的led为某个亮度

void led_trigger_event(struct led_trigger *trigger, enum led_brightness brightness)

触发器和led的连接

void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)//建立连接

//建立连接的时候会调用触发器的activate方法

void led_trigger_remove(struct led_classdev *led_cdev)//取消连接。

取消连接的时候会调用触发器的deactivate方法

void led_trigger_set_default(struct led_classdev *led_cdev)

//在所有已注册的触发器中寻找led_cdev的默认触发器并调用 led_trigger_set建立连接

 led trigger --->>> led设备模型 ---->>>led硬件

trigger好比是控制led类设备的算法,这个算法决定着led什么时候亮什么时候暗。led trigger类设备可以是现实的硬件设备,比如ide硬盘,也可以是系统心跳等事件。 


imx28 led 驱动:

/driver/leds/leds-mxs-pwm.c



点击(此处)折叠或打开

  1. static struct platform_driver mxs_pwm_led_driver = {
  2.     .probe = mxs_pwm_led_probe,
  3.     .remove = __devexit_p(mxs_pwm_led_remove),
  4.     .driver = {
  5.     .name = "mxs-leds",
  6.     },
  7. };
  8. static int __init mxs_pwm_led_init(void)
  9. {
  10.     return platform_driver_register(&mxs_pwm_led_driver);
  11. }
  12. static void __exit mxs_pwm_led_exit(void)
  13. {
  14.     platform_driver_unregister(&mxs_pwm_led_driver);
  15. }



mxs_pwm_led_init 里面会调用platform_driver_register,最终会调到mxs_pwm_led_driverprobe函数。即mxs_pwm_led_probe。进去一探究竟!


点击(此处)折叠或打开

  1. static int __devinit mxs_pwm_led_probe(struct platform_device *pdev)
  2. {
  3.     struct mxs_pwm_leds_plat_data *plat_data;
  4.     struct resource *res;
  5.     struct led_classdev *led;
  6.     unsigned int pwmn;
  7.     int leds_in_use = 0, rc = 0;
  8.     int i;
  9.     plat_data = (struct mxs_pwm_leds_plat_data *)pdev->dev.platform_data;
  10.     //pdev->dev.platform_data 在mx28evk_init_leds()设置了
  11.     if (plat_data == null)
  12.         return -enodev;
  13.     res = platform_get_resource(pdev, ioresource_mem, 0);
  14.     //得到资源,即pwm寄存器的物理地址
  15.     if (res == null)
  16.         return -enodev;
  17.     leds.base = (unsigned int)io_address(res->start);
  18.      //物理地址转化为虚拟地址(静态映射方式)
  19.     mxs_reset_block((void __iomem *)leds.base, 1);
  20.     leds.led_num = plat_data->num;
  21.     if (leds.led_num <= 0 || leds.led_num > config_mxs_pwm_channels)
  22.         return -efault;
  23.     leds.leds = plat_data->leds;
  24.     if (leds.leds == null)
  25.         return -efault;
  26.     leds.pwm_clk = clk_get(&pdev->dev, "pwm"); //得到pwm控制器的时钟
  27.     if (is_err(leds.pwm_clk)) {
  28.         rc = ptr_err(leds.pwm_clk);
  29.         return rc;
  30.     }
  31.     clk_enable(leds.pwm_clk); //开启时钟
  32.     for (i = 0; i < leds.led_num; i) {
  33.         pwmn = leds.leds[i].pwm;
  34.         if (pwmn >= config_mxs_pwm_channels) {//检测pwm的通道
  35.         dev_err(&pdev->dev,
  36.         "[led-pwm%d]:pwm %d doesn't exist\n",
  37.         i, pwmn);
  38.         continue;
  39.     }
  40.     led = &(leds.leds[i].dev);
  41.     led->name = leds.leds[i].name;
  42.     led->brightness = led_half;
  43.     led->flags = 0;
  44.     led->brightness_set = mxs_pwm_led_brightness_set;
  45.     led->default_trigger = 0;
  46.     //led_trigger_register 中会设置default_trigger
  47.     rc = led_classdev_register(&pdev->dev, led);
  48.     if (rc < 0) {
  49.         dev_err(&pdev->dev,
  50.         "unable to register led device %d (err=%d)\n",
  51.         i, rc);
  52.         continue;
  53.     }
  54.     leds_in_use;
  55.     /* set default brightness */
  56.     mxs_pwm_led_brightness_set(led, led_half);
  57.     }
  58.     if (leds_in_use == 0) {
  59.         dev_info(&pdev->dev, "no pwm leds available\n");
  60.         clk_disable(leds.pwm_clk);
  61.         clk_put(leds.pwm_clk);
  62.         return -enodev;
  63.     }
  64.     return 0;
  65. }

  66. static struct mxs_pwm_led mx28evk_led_pwm[2] = {
  67.     [0] = {
  68.     .name = "led-pwm0",
  69.     .pwm = 0,//通道0
  70.     },
  71.     [1] = {
  72.     .name = "led-pwm1",
  73.     .pwm = 1,//通道1
  74.     },
  75. };

  76. struct mxs_pwm_leds_plat_data mx28evk_led_data = {
  77.     .num = array_size(mx28evk_led_pwm),
  78.     .leds = mx28evk_led_pwm,
  79. };
  80. static struct resource mx28evk_led_res = {
  81.     .flags = ioresource_mem,
  82.     .start = pwm_phys_addr,
  83.     .end = pwm_phys_addr 0x3fff, //pwm寄存器地址范围
  84. };

  85. static void __init mx28evk_init_leds(void) //系统启动时候调用
  86. {
  87.     struct platform_device *pdev;
  88.     pdev = mxs_get_device("mxs-leds", 0);
  89.     if (pdev == null || is_err(pdev))
  90.     return;
  91.     pdev->resource = &mx28evk_led_res;
  92.     pdev->num_resources = 1;
  93.     pdev->dev.platform_data = &mx28evk_led_data;
  94.     mxs_add_device(pdev, 3);
  95. }

点击(此处)折叠或打开

  1. #define bm_pwm_ctrl_pwm_enable    ((1<<(config_mxs_pwm_channels)) - 1)
  2. #define bf_pwm_ctrl_pwm_enable(n) ((1<<(n)) & bm_pwm_ctrl_pwm_enable)
  3. #define bf_pwm_periodn_settings     \
  4. (bf_pwm_periodn_cdiv(5) | /* divide by 64 */     \
  5.  bf_pwm_periodn_inactive_state(3) | /* low */
  6.  bf_pwm_periodn_active_state(2) | /* high */     \
  7.  bf_pwm_periodn_period(led_full)) /* 255 cycles */
  8.  //24mhz 进行64分频
  9.  //这里代码注释好像有问题。通过查看数据手册,inactive_state(3)时 pwm output
  10.  //定义为1,active_state(2) 时,为0
  11.  //一个pwm为256个clk

  12. static void mxs_pwm_led_brightness_set(struct led_classdev *pled,
  13.     enum led_brightness value)
  14. {
  15.     struct mxs_pwm_led *pwm_led;
  16.     pwm_led = container_of(pled, struct mxs_pwm_led, dev);
  17.     if (pwm_led->pwm < config_mxs_pwm_channels) {
  18.         //先关闭pwm
  19.          __raw_writel(bf_pwm_ctrl_pwm_enable(pwm_led->pwm),
  20.                leds.base hw_pwm_ctrl_clr);
  21.        //设置无效时间和无效时间
  22.          __raw_writel(bf_pwm_activen_inactive(led_full) |
  23.                       bf_pwm_activen_active(value),
  24.                leds.base hw_pwm_activen(pwm_led->pwm));
  25.        //设置周期
  26.        __raw_writel(bf_pwm_periodn_settings,
  27.              leds.base hw_pwm_periodn(pwm_led->pwm));
  28.       //打开pwm
  29.        __raw_writel(bf_pwm_ctrl_pwm_enable(pwm_led->pwm),
  30.              leds.base hw_pwm_ctrl_set);
  31.     }
  32. }



看看几个主要的寄存器吧:

1pwm control and status register (hw_pwm_ctrl)pwm0-pwm7的控制,在驱动的probe时候设置,主要是设置pwm通道的可以状态。

2pwm channel 0 active register

   31:16     inactive     rw         无效时间。 
   15: 0      active       rw         有效时间。 

   

有效和无效只是相对的,因为在pwm channel n period register中有效时间和无效时间都会有对应的状态,在设置寄存器时只要把有效时间及有效时间的状态、无效时间和无效时间的状态对应的设置成我们想要的效果就可以了。 

3pwm channel period register 

   31:27      rsrvd2       保留位。 
   26        hsadc_out   pwm output to high speed adc

   25        hsadc_clk_sel  hsadc clock select for pwm output   

  24      matt_sel         多芯片附件模式 

  23      matt多芯片附件模式 

  22-20   cdiv 时钟分频比率      

  

     0x0 div_1 — divide by 1. 

     0x01 div_2 — divide by 2. 

     0x02div_4 — divide by 4. 

     0x03div_8 — divide by 8. 

     0x04div_16 — divide by 16. 

     0x05div_64 — divide by 64. 

     0x06div_256 — divide by 256. 

     0x07div_1024 — divide by 1024.

   19–18inactive_state  有效值的状态

        0x0 表示hi_z(高阻)。 
        0x2 表示低电平。 
        0x3 表示高电平。 

   17:16   active_state     无效时间的状态,设置值含义同上。    

   15:0    period      波形周期,一个波形周期等于此值减一的时钟周期。 

官方文档给出了个pwm应用的例子:


          

    到此,leds 也分析完毕,现在在用户空间测试:

    先写个简单的sh 

    #vi led.sh

    #!bin/sh

       path=/sys/class/leds/led-pwm1/brightness

       led_full=255

       led_off=0

       led_half=127

       while true

       do

           echo $led_full > $path

           sleep 1

           echo $led_off > $path

           sleep 1

           echo $led_half > $path

           sleep 1

       done


      #chmod u x led.sh 

      # . ./led.sh  (或者 source led.sh

      开发板的led13种状态之间闪烁.

阅读(3263) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图