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种状态之间闪烁.
mark……
顶一个…
页:
[1]