liupeng08305 发表于 2013-4-7 21:24:16

imx28 leds 平台总线驱动 转载

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

为什么是LED驱动呢,柿子总是软的好吃!其实也不全是了,led代码比较少,用它来入门平台总线驱动的写法还是不错的选择滴。IMX28的led驱动是使用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.

16.    struct device   *dev;

17.    struct list_head   node;   /* 串联起所有已经注册的led_classdev */

18.    const char   *default_trigger;    /* 默认触发器 */

19.    #ifdef CONFIG_LEDS_TRIGGERS

20.    /* 这个读写信号量保护触发器数据 */

21.    struct rw_semaphore   trigger_lock;

22.    struct led_trigger    *trigger; //触发器指针

23.   /*触发器使用的链表节点,用来连接同一触发器上的所有led_classdev */

24.    struct list_head   trig_list;

25.    void   *trigger_data; //触发器使用的私有数据

26.    #endif

27.};






触发器的结构体




点击(此处)折叠或打开

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.

8.      rwlock_t   leddev_list_lock; //保护链表的锁

9.      struct list_head led_cdevs;

10.

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

12.      struct list_head next_trig;

13.  };





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

点击(此处)折叠或打开

1.struct led_info {

2.   const char *name;   //led 的名字

3.   char *default_trigger; //默认触发器

4.   int flags;

5.};

6.

7.struct led_platform_data {

8.   int num_leds; //led 个数 imx28 有2个led

9.   struct led_info *leds; //每个led 信息

10.};



在来介绍下和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.

11.static void __exit leds_exit(void)

12.{

13.    class_destroy(leds_class);

14.}

15.

16.static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store);

17.static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL);

18.#ifdef CONFIG_LEDS_TRIGGERS

19.static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);

20.#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.

9.static ssize_t led_brightness_store(struct device *dev,

10.       struct device_attribute *attr, const char *buf, size_t size)

11.{

12.    /*取得led_classdev ,放在device的driver_data里。*/

13.    struct led_classdev *led_cdev = dev_get_drvdata(dev);

14.    ssize_t ret = -EINVAL;

15.    char *after;

16.    unsigned long state = simple_strtoul(buf, &after, 10); //字符串形式的亮度值转换成数字。

17.    size_t count = after - buf;

18.    if (*after && isspace(*after))

19.    count++;

20.    if (count == size) {

21.    ret = count;

22.    if (state == LED_OFF)

23.    led_trigger_remove(led_cdev);

24.    //如果亮度值为0,移除trigger led_cdev->trigger = NULL;

25.    led_set_brightness(led_cdev, state); //设置亮度值

26.    }

27.    return ret;

28.}







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_driver的probe函数。即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.pwm;

34.      if (pwmn >= CONFIG_MXS_PWM_CHANNELS) {//检测pwm的通道

35.      dev_err(&pdev->dev,

36.      ":PWM %d doesn't exist\n",

37.      i, pwmn);

38.      continue;

39.    }

40.    led = &(leds.leds.dev);

41.    led->name = leds.leds.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.

67.static struct mxs_pwm_led mx28evk_led_pwm = {

68.    = {

69.    .name = "led-pwm0",

70.    .pwm = 0,//通道0

71.    },

72.    = {

73.    .name = "led-pwm1",

74.    .pwm = 1,//通道1

75.    },

76.};

77.

78.struct mxs_pwm_leds_plat_data mx28evk_led_data = {

79.    .num = ARRAY_SIZE(mx28evk_led_pwm),

80.    .leds = mx28evk_led_pwm,

81.};

82.static struct resource mx28evk_led_res = {

83.    .flags = IORESOURCE_MEM,

84.    .start = PWM_PHYS_ADDR,

85.    .end = PWM_PHYS_ADDR + 0x3FFF, //pwm寄存器地址范围

86.};

87.

88.static void __init mx28evk_init_leds(void) //系统启动时候调用

89.{

90.    struct platform_device *pdev;

91.    pdev = mxs_get_device("mxs-leds", 0);

92.    if (pdev == NULL || IS_ERR(pdev))

93.    return;

94.    pdev->resource = &mx28evk_led_res;

95.    pdev->num_resources = 1;

96.    pdev->dev.platform_data = &mx28evk_led_data;

97.    mxs_add_device(pdev, 3);

98.}
点击(此处)折叠或打开

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.

13.static void mxs_pwm_led_brightness_set(struct led_classdev *pled,

14.    enum led_brightness value)

15.{

16.    struct mxs_pwm_led *pwm_led;

17.    pwm_led = container_of(pled, struct mxs_pwm_led, dev);

18.    if (pwm_led->pwm < CONFIG_MXS_PWM_CHANNELS) {

19.      //先关闭PWM

20.         __raw_writel(BF_PWM_CTRL_PWM_ENABLE(pwm_led->pwm),

21.               leds.base + HW_PWM_CTRL_CLR);

22.       //设置无效时间和无效时间

23.         __raw_writel(BF_PWM_ACTIVEn_INACTIVE(LED_FULL) |

24.                      BF_PWM_ACTIVEn_ACTIVE(value),

25.               leds.base + HW_PWM_ACTIVEn(pwm_led->pwm));

26.       //设置周期

27.       __raw_writel(BF_PWM_PERIODn_SETTINGS,

28.             leds.base + HW_PWM_PERIODn(pwm_led->pwm));

29.      //打开PWM

30.       __raw_writel(BF_PWM_CTRL_PWM_ENABLE(pwm_led->pwm),

31.             leds.base + HW_PWM_CTRL_SET);

32.    }

33.}







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

1:PWM Control and Status Register (HW_PWM_CTRL):PWM0-PWM7的控制,在驱动的probe时候设置,主要是设置PWM通道的可以状态。


2:PWM Channel 0 Active Register

   31:16   INACTIVE   RW         无效时间。
   15: 0      ACTIVE       RW         有效时间。

   

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


3:PWM 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时钟分频比率      

   

   0x0DIV_1 — Divide by 1.

   0x01DIV_2 — Divide by 2.

   0x02 DIV_4 — Divide by 4.

   0x03 DIV_8 — Divide by 8.

   0x04 DIV_16 — Divide by 16.

   0x05 DIV_64 — Divide by 64.

   0x06 DIV_256 — Divide by 256.

   0x07 DIV_1024 — Divide by 1024.


   19–18 INACTIVE_STATE   有效值的状态

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


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

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


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




         




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

    先写个简单的sh

    #viled.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+xled.sh

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


      开发板的led1在3种状态之间闪烁.



xiefy21 发表于 2013-8-14 10:56:28

mark……
顶一个…
页: [1]
查看完整版本: imx28 leds 平台总线驱动 转载