转载:https://blog.csdn.net/xiaopangzi313/article/details/52199407
上一节已经了解了展讯7731C_M Android6.0 充电指示灯实的关机部分,这一节主要介绍开机部分,也就是kernel部分。
kernel 部分主要功能是提供对硬件led控制接口,如设置亮度,获取亮度,设置颜色等。
一、整个guide-led 驱动调用逻辑序列图下
二、驱动框架的具体实现
1.定义平台驱动结构,注册platform 驱动文件:kernel/drivers/leds/leds-sprd-bltc-rgb.c
- static int __init sprd_leds_bltc_rgb_init(void)
- {
- PRINT_INFO("Locate in sprd_leds_bltc_rgb_init!\n");
- return platform_driver_register(&sprd_leds_bltc_rgb_driver);
- }
- module_init(sprd_leds_bltc_rgb_init);
- static const struct of_device_id sprd_leds_bltc_rgb_of_match[] = {
- { .compatible = "sprd,sprd-leds-bltc-rgb", },
- { }
- };
- static struct platform_driver sprd_leds_bltc_rgb_driver = {
- .driver = {
- .name = "sprd-leds-bltc-rgb",
- .owner = THIS_MODULE,
- .of_match_table = sprd_leds_bltc_rgb_of_match,
- },
- .probe = sprd_leds_bltc_rgb_probe,
- .remove = sprd_leds_bltc_rgb_remove,
- .shutdown = sprd_leds_bltc_rgb_shutdown,
- };
- sprd-leds-bltc-rgb {
- compatible = "sprd,sprd-leds-bltc-rgb";
- };
- static int sprd_leds_bltc_rgb_probe(struct platform_device *dev)
- {
- .....
- for (i = 0; i < SPRD_LED_TYPE_TOTAL; i++) {
- .....
- //将本地方法封装到brgb结构中
- brgb->cdev.brightness_set = sprd_leds_bltc_rgb_set;
- brgb->cdev.name = sprd_leds_rgb_name[i];
- brgb->cdev.brightness_get = NULL;
- brgb->enable = 0;
- //设置操作whiteled的寄存器地址
- #if CONFIG_OF
- brgb->sprd_bltc_base_addr = SPRD_ADI_BASE + 0x8440; /***0x40038440***/
- brgb->sprd_arm_module_en_addr = SPRD_ADI_BASE + 0x8800; /***0x40038800***/
- #endif
- spin_lock_init(&brgb->value_lock);
- mutex_init(&brgb->mutex);
- //默认关闭guide-led
- brgb->value = LED_OFF;
- //将本地私有的driver数据地址付给platform_dev中的driver_data
- platform_set_drvdata(dev, brgb);
- //创建class 下面的brightness节点
- ret = led_classdev_register(&dev->dev, &brgb->cdev);
- //创建设备节点 rising_time,falling_time,
- .....
- }
- return 0;
- err:
- if (i) {
- for (i = i-1; i >=0; i--) {
- if (!brgb)
- continue;
- led_classdev_unregister(&brgb->cdev);
- kfree(brgb);
- brgb = NULL;
- --brgb;
- }
- }
- return ret;
- }
4.led 创建brightneess 等节点
文件:kernel/drivers/leds/led-class.c
- ret = led_classdev_register(&dev->dev, &brgb->cdev);
- /**
- * led_classdev_register - register a new object of led_classdev class.
- * @parent: The device to register.
- * @led_cdev: the led_classdev structure for this device.
- */
- int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
- {
- led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
- "%s", led_cdev->name);
- .......
- return 0;
- }
- led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
- "%s", led_cdev->name);
- leds_class 对应leds 类,其中<span style="font-family: Arial, Helvetica, sans-serif;">leds_class->dev_attr 对应brightness</span>
- parents 对应platform 的dev
- led_dev 对应led_classdev 对象
- led_cdev 对应参数
- led_cdev->name:设备节点的名称
- 创建leds_class ==> 对应/sys/class/leds
- static int __init leds_init(void)
- {
- leds_class = class_create(THIS_MODULE, "leds");
- ....
- leds_class->dev_attrs = led_class_attrs; //这里为属性节点brightness定义信息
- return 0;
- }
- led_cdev->name ->
- brgb->cdev.name = sprd_leds_rgb_name[i];
- ==>对应 /sys/class/leds/red
- /sys/class/leds/red
- /sys/class/leds/red
- .....
- static char *sprd_leds_rgb_name[SPRD_LED_TYPE_TOTAL] = {
- "red",
- "green",
- "blue",
- "red_bl",
- "green_bl",
- "blue_bl",
- };
- static struct device_attribute led_class_attrs[] = {
- __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
- __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
- #ifdef CONFIG_LEDS_TRIGGERS
- __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
- #endif
- __ATTR_NULL,
- };
- static ssize_t led_brightness_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- /* no lock needed for this */
- led_update_brightness(led_cdev);
- return sprintf(buf, "%u\n", led_cdev->brightness);
- }
- static ssize_t led_brightness_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t size)
- {
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- .....
- ret = kstrtoul(buf, 10, &state);
- .....
- __led_set_brightness(led_cdev, state);
- return size;
- }
5.led_update_brightness的实现
- static void led_update_brightness(struct led_classdev *led_cdev)
- {
- if (led_cdev->brightness_get)
- led_cdev->brightness = led_cdev->brightness_get(led_cdev);
- }
6.创建工作队列
- INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
- static void set_brightness_delayed(struct work_struct *ws)
- {
- struct led_classdev *led_cdev =
- container_of(ws, struct led_classdev, set_brightness_work);
- led_stop_software_blink(led_cdev);
- __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
- }
- void led_stop_software_blink(struct led_classdev *led_cdev)
- {
- del_timer_sync(&led_cdev->blink_timer);
- led_cdev->blink_delay_on = 0;
- led_cdev->blink_delay_off = 0; //这两个个延迟时间用来控制指示灯闪灭程度的
- }
- static inline void __led_set_brightness(struct led_classdev *led_cdev,
- enum led_brightness value)
- {
- if (value > led_cdev->max_brightness)
- value = led_cdev->max_brightness;
- led_cdev->brightness = value;
- if (!(led_cdev->flags & LED_SUSPENDED))
- led_cdev->brightness_set(led_cdev, value);
- }
7.定义定时器,定时更新 brightness 值
- init_timer(&led_cdev->blink_timer);
- led_cdev->blink_timer.function = led_timer_function;
- led_cdev->blink_timer.data = (unsigned long)led_cdev;
- static void led_timer_function(unsigned long data)
- {
- struct led_classdev *led_cdev = (void *)data;
- unsigned long brightness;
- unsigned long delay;
- if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
- __led_set_brightness(led_cdev, LED_OFF);
- return;
- }
- if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) {
- led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
- return;
- }
- brightness = led_get_brightness(led_cdev);
- if (!brightness) {
- /* Time to switch the LED on. */
- brightness = led_cdev->blink_brightness;
- delay = led_cdev->blink_delay_on;
- } else {
- /* Store the current brightness value to be able
- * to restore it when the delay_off period is over.
- */
- led_cdev->blink_brightness = brightness;
- brightness = LED_OFF;
- delay = led_cdev->blink_delay_off;
- }
- __led_set_brightness(led_cdev, brightness);
- /* Return in next iteration if led is in one-shot mode and we are in
- * the final blink state so that the led is toggled each delay_on +
- * delay_off milliseconds in worst case.
- */
- if (led_cdev->flags & LED_BLINK_ONESHOT) {
- if (led_cdev->flags & LED_BLINK_INVERT) {
- if (brightness)
- led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
- } else {
- if (!brightness)
- led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
- }
- }
- mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
- //所以如果没有这mod_timer,func 只会执行一次,也就是timer第一次超时时执行的那次
- }
8.sprd_leds_bltc_rgb_set的实现
文件:kernel/drivers/leds/leds-sprd-bltc-rgb.c
- static void sprd_leds_bltc_rgb_set(struct led_classdev *bltc_rgb_cdev,enum led_brightness value)
- {
- struct sprd_leds_bltc_rgb *brgb;
- unsigned long flags;
- brgb = to_sprd_bltc_rgb(bltc_rgb_cdev);
- spin_lock_irqsave(&brgb->value_lock, flags);
- brgb->leds_flag = bltc_rgb_cdev->flags;
- brgb->value = value;
- spin_unlock_irqrestore(&brgb->value_lock, flags);
- if(1 == brgb->suspend) {
- PRINT_WARN("Do NOT change brightness in suspend mode\n");
- return;
- }
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_R]) == 0 || \
- strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_G]) == 0 || \
- strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_B]) == 0)
- sprd_leds_rgb_work(brgb);
- else
- sprd_leds_bltc_work(brgb);
- }
- static void sprd_leds_rgb_work(struct sprd_leds_bltc_rgb *brgb)
- {
- unsigned long flags;
- mutex_lock(&brgb->mutex);
- spin_lock_irqsave(&brgb->value_lock, flags);
- if (brgb->value == LED_OFF) {
- spin_unlock_irqrestore(&brgb->value_lock, flags);
- sprd_leds_bltc_rgb_set_brightness(brgb);
- goto out;
- }
- spin_unlock_irqrestore(&brgb->value_lock, flags);
- sprd_leds_bltc_rgb_enable(brgb);
- PRINT_INFO("sprd_leds_bltc_rgb_work_for rgb!\n");
- out:
- mutex_unlock(&brgb->mutex);
- }
- static void sprd_leds_bltc_rgb_enable(struct sprd_leds_bltc_rgb *brgb)
- {
- sprd_bltc_rgb_init(brgb);
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_R]) == 0) {
- sci_adi_set(brgb->sprd_bltc_base_addr + BLTC_CTRL, (0x1<<0)|(0x1<<1));
- brgb->bltc_addr = brgb->sprd_bltc_base_addr + BLTC_R_PRESCL + BLTC_DUTY_OFFSET;
- sprd_leds_bltc_rgb_set_brightness(brgb);
- }
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_G]) == 0) {
- sci_adi_set(brgb->sprd_bltc_base_addr + BLTC_CTRL, (0x1<<4)|(0x1<<5));
- brgb->bltc_addr = brgb->sprd_bltc_base_addr + BLTC_G_PRESCL + BLTC_DUTY_OFFSET;
- sprd_leds_bltc_rgb_set_brightness(brgb);
- }
- if(strcmp(brgb->cdev.name,sprd_leds_rgb_name[SPRD_LED_TYPE_B]) == 0) {
- sci_adi_set(brgb->sprd_bltc_base_addr + BLTC_CTRL, (0x1<<8)|(0x1<<9));
- brgb->bltc_addr = brgb->sprd_bltc_base_addr + BLTC_B_PRESCL + BLTC_DUTY_OFFSET;
- sprd_leds_bltc_rgb_set_brightness(brgb);
- }
- ....
- }
- static void sprd_leds_bltc_rgb_set_brightness(struct sprd_leds_bltc_rgb *brgb)
- {
- unsigned long brightness = brgb->value;
- unsigned long pwm_duty;
- pwm_duty = brightness;
- if(pwm_duty > 255)
- pwm_duty = 255;
- sci_adi_write(brgb->bltc_addr, (pwm_duty<<8)|PWM_MOD_COUNTER,0xffff);
- PRINT_INFO("reg:0x%1LX set_val:0x%08X brightness:%ld brightness_level:%ld(0~15)\n", \
- brgb->bltc_addr, sprd_leds_bltc_rgb_read(brgb->bltc_addr),brightness, pwm_duty);
- }
三、总结
本文着重分析开机状态充电指示灯kernel部分驱动实现,在上述的分析中,我们可以看到驱动主要做了以下工作:
1).建立相关的/sys节点,如/sys/class/leds/red/brightness ,并且提供相应的读写方法
2).提供对led whiteled寄存器控制方式,如本篇中有指定whiteled寄存器的地址,并提供了操作写寄存器的方法
sci_adi_write等
通过上述的工作,上层应用程序只需要操作相关的/sys节点即控制led的亮灭,当然这只是最基本的功能,该驱
动还提供了led 周期性亮灭灯的实现,可以用来实现呼吸灯灯操作,本文不再赘述!