LED indicator 简介
所谓的LED indicator就是手机上面充电发短信的灯,有时会亮绿灯,有时会亮红灯,有时1起亮(橙灯),主要用于提示用户电量、短信、电话。
bring up LED
要想点亮LED,就要了解其电路。
以下是我工作中案子的led。
VPH_PWR是系统默许电。处于高电平状态。
这里可以看到2个LED遭到pmi8953的mpp2\mmp4控制。下面重要的是如何将mmp2\mpp4置起来。
bring up LED的步骤
现在我们基本上是采取pwm来控制LED。那末我们是如何来控制的呢?
通过soc->pmi8952(内部pwm)->mpp2/mpp4的方式去控制,那末咋么控制,首先你得晓得如何通过它的协议对这2pin做操作的。
根据pmi8952的寄存器描写文件,可以知道pmi8952这颗ic是挂在spi总线上的。
以下是我大致总结出来的步骤
-
查看spec肯定其如何IC如何挂载,mpp2/mpp4的地址与描写、
-
肯定led是如何初始化的
-
debug方式点亮LED,保证硬件的可靠性
-
led是如何控制mpp2/mpp4的
-
pmi8952的内部寄存器是如何控制pwm的
-
修改dtsi
dtsi修改
具体的dtsi在这里展现。以供后面解说为什么要这样做!
qcom,leds@a100 {
status = "okay";
compatible = "qcom,leds-qpnp";
reg = <0xa100 0x100>;
label = "mpp";
qcom,led_mpp_2{
label = "mpp";
linux,name = "red";
linux,default-trigger = "none";
qcom,default-state = "off";
qcom,max-current = <40>;
qcom,current-setting = <5>;
qcom,id = <6>;
qcom,mode = "pwm";
qcom,source-sel = <8>;
qcom,mode-ctrl = <0x60>;
qcom,vin-ctrl=<3>;
pwms = <&pmi8950_pwm 0 0>;
qcom,pwm-us = <100>; };
};
qcom,leds@a300 {
status = "okay";
compatible = "qcom,leds-qpnp";
reg = <0xa300 0x100>;
label = "mpp";
qcom,led_mpp_2{
label = "mpp";
linux,name = "green";
linux,default-trigger = "none";
qcom,default-state = "off";
qcom,max-current = <40>;
qcom,current-setting = <5>;
qcom,id = <6>;
qcom,mode = "pwm";
qcom,source-sel = <8>;
qcom,mode-ctrl = <0x60>;
qcom,vin-ctrl=<3>;
pwms = <&pmi8950_pwm 0 0>;
qcom,pwm-us = <100>; };
};
LED初始化启动
在android源码/kernel/driver/leds/led_qpnp.c就能够看出它是如何做初始化的动作的。
下面是led driver 的devices tree
static struct spmi_driver qpnp_leds_driver = {
.driver = {
.name = "qcom,leds-qpnp",
.of_match_table = spmi_match_table,
},
.probe = qpnp_leds_probe,
.remove = qpnp_leds_remove,
}; static int __init qpnp_led_init(void)
{ return spmi_driver_register(&qpnp_leds_driver);
}
module_init(qpnp_led_init);
可以看到其name为qcom,leds-qpnp,那末它将会去找dts中的qcom,leds-qpnp字段做解析动作
probe函数:
//当传进来的值就已是qcom,leds-qpnp了 static int qpnp_leds_probe(struct spmi_device *spmi)
{
struct qpnp_led_data *led, *led_array;
struct resource *led_resource;
struct device_node *node, *temp;
int rc, i, num_leds = 0, parsed_leds = 0;
const char *led_label;
bool regulator_probe = false;
printk("[LED] qpnp_leds_probe\n");
//获得LEDdevices tree spi节点并且遍历节点+++++
node = spmi->dev.of_node; if (node == NULL)
return -ENODEV;
temp = NULL;
while ((temp = of_get_next_child(node, temp)))
num_leds++; if (!num_leds)
return -ECHILD;
//为每个led节点分配空间,资源等等
led_array = devm_kzalloc(&spmi->dev,
(sizeof(struct qpnp_led_data) * num_leds), GFP_KERNEL); if (!led_array) {
dev_err(&spmi->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
for_each_child_of_node(node, temp) {
led = &led_array[parsed_leds];
led->num_leds = num_leds;
led->spmi_dev = spmi;
led_resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0); if (!led_resource) {
dev_err(&spmi->dev, "Unable to get LED base address\n");
rc = -ENXIO;
goto fail_id_check;
}
led->base = led_resource->start;
rc = of_property_read_string(temp, "label", &led_label);//找到dtsi的label项给变量led_label if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Failure reading label, rc = %d\n", rc);
goto fail_id_check;
}
rc = of_property_read_string(temp, "linux,name",
&led->cdev.name);//找到dtsi的linux,name项给变量led->cdev.name if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Failure reading led name, rc = %d\n", rc);
goto fail_id_check;
}
printk("[LED] linux,name : %s\n", led->cdev.name); if(!strcmp(led->cdev.name,"button-backlight"))//判断其是否是虚拟按键的led
{
printk("[LED] : Copy Led structure\n");
copy_led = led;
}
rc = of_property_read_u32(temp, "qcom,max-current",
&led->max_current);//读取最大电量 if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Failure reading max_current, rc = %d\n", rc);
goto fail_id_check;
}
rc = of_property_read_u32(temp, "qcom,id", &led->id);//读取id,如果是led indicator1般为6.具体为什么是6后面分析 if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Failure reading led id, rc = %d\n", rc);
goto fail_id_check;
}
rc = qpnp_get_common_configs(led, temp);//这个函数对我们不大 if (rc) {
dev_err(&led->spmi_dev->dev, "Failure reading common led configuration," \ " rc = %d\n", rc);
goto fail_id_check;
}
led->cdev.brightness_set = qpnp_led_set;
led->cdev.brightness_get = qpnp_led_get;
//比对led_label,很明显我们走的是mpp方式,具体他如何实现的,我也将贴出来,但在此不做详解 if (strncmp(led_label, "wled", sizeof("wled")) == 0) {
rc = qpnp_get_config_wled(led, temp); if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Unable to read wled config data\n");
goto fail_id_check;
}
} else if (strncmp(led_label, "flash", sizeof("flash"))
== 0) { if (!of_find_property(node, "flash-boost-supply", NULL))
regulator_probe = true;
rc = qpnp_get_config_flash(led, temp, ®ulator_probe); if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Unable to read flash config data\n");
goto fail_id_check;
}
} else if (strncmp(led_label, "rgb", sizeof("rgb")) == 0) {
rc = qpnp_get_config_rgb(led, temp); if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Unable to read rgb config data\n");
goto fail_id_check;
}
} else if (strncmp(led_label, "mpp", sizeof("mpp")) == 0) {
rc = qpnp_get_config_mpp(led, temp); if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Unable to read mpp config data\n");
goto fail_id_check;
}
} else if (strcmp(led_label, "gpio") == 0) {
rc = qpnp_get_config_gpio(led, temp); if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Unable to read gpio config data\n");
goto fail_id_check;
}
} else if (strncmp(led_label, "kpdbl", sizeof("kpdbl")) == 0) {
bitmap_zero(kpdbl_leds_in_use, NUM_KPDBL_LEDS);
is_kpdbl_master_turn_on = false;
rc = qpnp_get_config_kpdbl(led, temp); if (rc < 0) {
dev_err(&led->spmi_dev->dev, "Unable to read kpdbl config data\n");
goto fail_id_check;
}
} else {
dev_err(&led->spmi_dev->dev, "No LED matching label\n");
rc = -EINVAL;
goto fail_id_check;
}
//既不是flash led那末在此会新建锁,并作work queue的动作 if (led->id != QPNP_ID_FLASH1_LED0 &&
led->id != QPNP_ID_FLASH1_LED1)
mutex_init(&led->lock);
led->in_order_command_processing = of_property_read_bool
(temp, "qcom,in-order-command-processing"); if (led->in_order_command_processing) {
/*
* the command order from user space needs to be
* maintained use ordered workqueue to prevent
* concurrency
*/
led->workqueue = alloc_ordered_workqueue
("led_workqueue", 0); if (!led->workqueue) {
rc = -ENOMEM;
goto fail_id_check;
}
} INIT_WORK(&led->work, qpnp_led_work);
//可以初始化led
rc = qpnp_led_initialize(led); if (rc < 0)
goto fail_id_check;
rc = qpnp_led_set_max_brightness(led); if (rc < 0)
goto fail_id_check;
rc = led_classdev_register(&spmi->dev, &led->cdev); if (rc) {
dev_err(&spmi->dev, "unable to register led %d,rc=%d\n",
led->id, rc);
goto fail_id_check;
} if (led->id == QPNP_ID_FLASH1_LED0 ||
led->id == QPNP_ID_FLASH1_LED1) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&led_attr_group); if (rc)
goto fail_id_check;
}
//看mpp进入哪一种mode,明显选择的是pwm if (led->id == QPNP_ID_LED_MPP) { if (!led->mpp_cfg->pwm_cfg)
break; if (led->mpp_cfg->pwm_cfg->mode == PWM_MODE) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&pwm_attr_group); if (rc)
goto fail_id_check;
} if (led->mpp_cfg->pwm_cfg->use_blink) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&blink_attr_group); if (rc)
goto fail_id_check;
rc = sysfs_create_group(&led->cdev.dev->kobj,
&lpg_attr_group); if (rc)
goto fail_id_check;
} else if (led->mpp_cfg->pwm_cfg->mode == LPG_MODE) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&lpg_attr_group); if (rc)
goto fail_id_check;
}
} else if ((led->id == QPNP_ID_RGB_RED) ||
(led->id == QPNP_ID_RGB_GREEN) ||
(led->id == QPNP_ID_RGB_BLUE)) { if (led->rgb_cfg->pwm_cfg->mode == PWM_MODE) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&pwm_attr_group); if (rc)
goto fail_id_check;
} if (led->rgb_cfg->pwm_cfg->use_blink) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&blink_attr_group); if (rc)
goto fail_id_check;
rc = sysfs_create_group(&led->cdev.dev->kobj,
&lpg_attr_group); if (rc)
goto fail_id_check;
} else if (led->rgb_cfg->pwm_cfg->mode == LPG_MODE) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&lpg_attr_group); if (rc)
goto fail_id_check;
}
} else if (led->id == QPNP_ID_KPDBL) { if (led->kpdbl_cfg->pwm_cfg->mode == PWM_MODE) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&pwm_attr_group); if (rc)
goto fail_id_check;
} if (led->kpdbl_cfg->pwm_cfg->use_blink) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&blink_attr_group); if (rc)
goto fail_id_check;
rc = sysfs_create_group(&led->cdev.dev->kobj,
&lpg_attr_group); if (rc)
goto fail_id_check;
} else if (led->kpdbl_cfg->pwm_cfg->mode == LPG_MODE) {
rc = sysfs_create_group(&led->cdev.dev->kobj,
&lpg_attr_group); if (rc)
goto fail_id_check;
}
}
/* configure default state */ if (led->default_on) {
led->cdev.brightness = led->cdev.max_brightness;
__qpnp_led_work(led, led->cdev.brightness); if (led->turn_off_delay_ms > 0)
qpnp_led_turn_off(led);
} else led->cdev.brightness = LED_OFF;
parsed_leds++;
}
dev_set_drvdata(&spmi->dev, led_array);
return 0; fail_id_check:
for (i = 0; i < parsed_leds; i++) { if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
led_array[i].id != QPNP_ID_FLASH1_LED1)
mutex_destroy(&led_array[i].lock); if (led_array[i].in_order_command_processing)
destroy_workqueue(led_array[i].workqueue);
led_classdev_unregister(&led_array[i].cdev);
}
return rc;
}
以下是led_label选择为mpp的函数配置,其道理还是和前面的1致。
static int qpnp_get_config_mpp(struct qpnp_led_data *led,
struct device_node *node)
{
int rc;
u32 val;
u8 led_mode;
const char *mode;
led->mpp_cfg = devm_kzalloc(&led->spmi_dev->dev,
sizeof(struct mpp_config_data), GFP_KERNEL); if (!led->mpp_cfg) {
dev_err(&led->spmi_dev->dev, "Unable to allocate memory\n"); return -ENOMEM;
} if (of_find_property(of_get_parent(node), "mpp-power-supply", NULL)) {
led->mpp_cfg->mpp_reg = regulator_get(&led->spmi_dev->dev, "mpp-power"); if (IS_ERR(led->mpp_cfg->mpp_reg)) {
rc = PTR_ERR(led->mpp_cfg->mpp_reg);
dev_err(&led->spmi_dev->dev, "MPP regulator get failed(%d)\n", rc); return rc;
}
} if (led->mpp_cfg->mpp_reg) {
rc = of_property_read_u32(of_get_parent(node), "qcom,mpp-power-max-voltage", &val); if (!rc)
led->mpp_cfg->max_uV = val; else goto err_config_mpp;
rc = of_property_read_u32(of_get_parent(node), "qcom,mpp-power-min-voltage", &val); if (!rc)
led->mpp_cfg->min_uV = val; else goto err_config_mpp;
} else {
rc = of_property_read_u32(of_get_parent(node), "qcom,mpp-power-max-voltage", &val); if (!rc)
dev_warn(&led->spmi_dev->dev, "No regulator specified\n");
rc = of_property_read_u32(of_get_parent(node), "qcom,mpp-power-min-voltage", &val); if (!rc)
dev_warn(&led->spmi_dev->dev, "No regulator specified\n");
}
led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN;
rc = of_property_read_u32(node, "qcom,current-setting", &val); if (!rc) { if (led->mpp_cfg->current_setting < LED_MPP_CURRENT_MIN)
led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN; else if (led->mpp_cfg->current_setting > LED_MPP_CURRENT_MAX)
led->mpp_cfg->current_setting = LED_MPP_CURRENT_MAX; else led->mpp_cfg->current_setting = (u8) val;
} else if (rc != -EINVAL)
goto err_config_mpp;
led->mpp_cfg->source_sel = LED_MPP_SOURCE_SEL_DEFAULT;
rc = of_property_read_u32(node, "qcom,source-sel", &val); if (!rc)
led->mpp_cfg->source_sel = (u8) val; else if (rc != -EINVAL)
goto err_config_mpp;
led->mpp_cfg->mode_ctrl = LED_MPP_MODE_SINK;
rc = of_property_read_u32(node, "qcom,mode-ctrl", &val); if (!rc)
led->mpp_cfg->mode_ctrl = (u8) val; else if (rc != -EINVAL)
goto err_config_mpp;
led->mpp_cfg->vin_ctrl = LED_MPP_VIN_CTRL_DEFAULT;
rc = of_property_read_u32(node, "qcom,vin-ctrl", &val); if (!rc)
led->mpp_cfg->vin_ctrl = (u8) val; else if (rc != -EINVAL)
goto err_config_mpp;
led->mpp_cfg->min_brightness = 0;
rc = of_property_read_u32(node, "qcom,min-brightness", &val); if (!rc)
led->mpp_cfg->min_brightness = (u8) val; else if (rc != -EINVAL)
goto err_config_mpp;
rc = of_property_read_string(node, "qcom,mode", &mode); if (!rc) {
led_mode = qpnp_led_get_mode(mode);
led->mpp_cfg->pwm_mode = led_mode; if (led_mode == MANUAL_MODE) return MANUAL_MODE; else if (led_mode == -EINVAL) {
dev_err(&led->spmi_dev->dev, "Selected mode not " \ "supported for mpp.\n");
rc = -EINVAL;
goto err_config_mpp;
}
led->mpp_cfg->pwm_cfg = devm_kzalloc(&led->spmi_dev->dev,
sizeof(struct pwm_config_data),
GFP_KERNEL); if (!led->mpp_cfg->pwm_cfg) {
dev_err(&led->spmi_dev->dev, "Unable to allocate memory\n");
rc = -ENOMEM;
goto err_config_mpp;
}
led->mpp_cfg->pwm_cfg->mode = led_mode;
led->mpp_cfg->pwm_cfg->default_mode = led_mode;
} else return rc;
rc = qpnp_get_config_pwm(led->mpp_cfg->pwm_cfg, led->spmi_dev, node); if (rc < 0)
goto err_config_mpp; return 0;
err_config_mpp: if (led->mpp_cfg->mpp_reg)
regulator_put(led->mpp_cfg->mpp_reg); return rc;
}
remove函数
该函数的主要工作就是去掉work queue的动作释放内存,在此便不在详细解释了。
static int qpnp_leds_remove(struct spmi_device *spmi)
{
struct qpnp_led_data *led_array = dev_get_drvdata(&spmi->dev);
int i, parsed_leds = led_array->num_leds;
for (i = 0; i < parsed_leds; i++) { cancel_work_sync(&led_array[i].work); if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
led_array[i].id != QPNP_ID_FLASH1_LED1)
mutex_destroy(&led_array[i].lock); if (led_array[i].in_order_command_processing)
destroy_workqueue(led_array[i].workqueue);
led_classdev_unregister(&led_array[i].cdev);
switch (led_array[i].id) { case QPNP_ID_WLED:
break; case QPNP_ID_FLASH1_LED0: case QPNP_ID_FLASH1_LED1: if (led_array[i].flash_cfg->flash_reg_get)
regulator_put(led_array[i].flash_cfg-> \
flash_boost_reg); if (led_array[i].flash_cfg->torch_enable) if (!led_array[i].flash_cfg->no_smbb_support)
regulator_put(led_array[i].
flash_cfg->torch_boost_reg);
sysfs_remove_group(&led_array[i].cdev.dev->kobj,
&led_attr_group);
break; case QPNP_ID_RGB_RED: case QPNP_ID_RGB_GREEN: case QPNP_ID_RGB_BLUE: if (led_array[i].rgb_cfg->pwm_cfg->mode == PWM_MODE)
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &pwm_attr_group); if (led_array[i].rgb_cfg->pwm_cfg->use_blink) {
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &blink_attr_group);
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &lpg_attr_group);
} else if (led_array[i].rgb_cfg->pwm_cfg->mode\
== LPG_MODE)
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &lpg_attr_group);
break; case QPNP_ID_LED_MPP: if (!led_array[i].mpp_cfg->pwm_cfg)
break; if (led_array[i].mpp_cfg->pwm_cfg->mode == PWM_MODE)
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &pwm_attr_group); if (led_array[i].mpp_cfg->pwm_cfg->use_blink) {
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &blink_attr_group);
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &lpg_attr_group);
} else if (led_array[i].mpp_cfg->pwm_cfg->mode\
== LPG_MODE)
sysfs_remove_group(&led_array[i].cdev.dev->\
kobj, &lpg_attr_group); if (led_array[i].mpp_cfg->mpp_reg)
regulator_put(led_array[i].mpp_cfg->mpp_reg);
break; case QPNP_ID_KPDBL: if (led_array[i].kpdbl_cfg->pwm_cfg->mode == PWM_MODE)
sysfs_remove_group(&led_array[i].cdev.dev->
kobj, &pwm_attr_group); if (led_array[i].kpdbl_cfg->pwm_cfg->use_blink) {
sysfs_remove_group(&led_array[i].cdev.dev->
kobj, &blink_attr_group);
sysfs_remove_group(&led_array[i].cdev.dev->
kobj, &lpg_attr_group);
} else if (led_array[i].kpdbl_cfg->pwm_cfg->mode
== LPG_MODE)
sysfs_remove_group(&led_array[i].cdev.dev->
kobj, &lpg_attr_group);
break; default: dev_err(&led_array[i].spmi_dev->dev, "Invalid LED(%d)\n",
led_array[i].id);
return -EINVAL;
}
}
return 0;
}
小结
从代码中可以看出led的初始化就是建立1个device tree然后做各种资源配置,而这些资源配置全部来自于dtsi,最后建立1个work queue进行工作。
debug模式下点亮led
其实高通不可能让你直接很好地完成1些事情,当你看完spec后,做的第1件事,就是通太高通给的debug方式进行分析,确保你的硬件没问题的情况下,做配置才是最安全的。
找bus
其实我们很容易找到高通将led挂在spi的哪一个bus上了。
比如:
&spmi_bus {
qcom.pmi8950@2
很明显可以看到应当是在bus2上
debug
cd /sys/kernel/debug/spmi/spmi-0/ echo 0x2a100 > address echo 0x100 > count
cat data echo 0x2a146 > address echo 0x80 > data echo 0x2a140 > address echo 0x11 > data
这个debug就是看spec轻易就可以做到的。
mpp2/mpp4控制led
qpnp_led_initialize函数将唤起mpp的配置。
static int qpnp_led_initialize(struct qpnp_led_data *led)
{
int rc = 0;
switch (led->id) { case QPNP_ID_WLED:
rc = qpnp_wled_init(led); if (rc)
dev_err(&led->spmi_dev->dev, "WLED initialize failed(%d)\n", rc);
break; case QPNP_ID_FLASH1_LED0: case QPNP_ID_FLASH1_LED1:
rc = qpnp_flash_init(led); if (rc)
dev_err(&led->spmi_dev->dev, "FLASH initialize failed(%d)\n", rc);
break; case QPNP_ID_RGB_RED: case QPNP_ID_RGB_GREEN: case QPNP_ID_RGB_BLUE:
rc = qpnp_rgb_init(led); if (rc)
dev_err(&led->spmi_dev->dev, "RGB initialize failed(%d)\n", rc);
break; case QPNP_ID_LED_MPP:
rc = qpnp_mpp_init(led); if (rc)
dev_err(&led->spmi_dev->dev, "MPP initialize failed(%d)\n", rc);
break; case QPNP_ID_LED_GPIO:
rc = qpnp_gpio_init(led); if (rc)
dev_err(&led->spmi_dev->dev, "GPIO initialize failed(%d)\n", rc);
break; case QPNP_ID_KPDBL:
rc = qpnp_kpdbl_init(led); if (rc)
dev_err(&led->spmi_dev->dev, "KPDBL initialize failed(%d)\n", rc);
break;
default:
dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id); return -EINVAL;
} return rc;
}
static int qpnp_get_common_configs(struct qpnp_led_data *led,
struct device_node *node)
{
int rc;
u32 val;
const char *temp_string;
led->cdev.default_trigger = LED_TRIGGER_DEFAULT;
rc = of_property_read_string(node, "linux,default-trigger", &temp_string); if (!rc)
led->cdev.default_trigger = temp_string; else if (rc != -EINVAL) return rc;
led->default_on = false;
rc = of_property_read_string(node, "qcom,default-state", &temp_string); if (!rc) { if (strncmp(temp_string, "on", sizeof("on")) == 0)
led->default_on = true;
} else if (rc != -EINVAL) return rc;
led->turn_off_delay_ms = 0;
rc = of_property_read_u32(node, "qcom,turn-off-delay-ms", &val); if (!rc)
led->turn_off_delay_ms = val; else if (rc != -EINVAL) return rc; return 0;
}
那末mpp会如何初始化呢?
又是根据dtsi进行配置。
static int qpnp_mpp_init(struct qpnp_led_data *led)
{
int rc;
u8 val; if (led->max_current < LED_MPP_CURRENT_MIN || led->max_current > LED_MPP_CURRENT_MAX) {
dev_err(&led->spmi_dev->dev, "max current for mpp is not valid\n"); return -EINVAL;
}
val = (led->mpp_cfg->current_setting / LED_MPP_CURRENT_PER_SETTING) - 1; if (val < 0)
val = 0; rc = qpnp_led_masked_write(led, LED_MPP_VIN_CTRL(led->base),
LED_MPP_VIN_MASK, led->mpp_cfg->vin_ctrl); if (rc) {
dev_err(&led->spmi_dev->dev, "Failed to write led vin control reg\n"); return rc;
} rc = qpnp_led_masked_write(led, LED_MPP_SINK_CTRL(led->base),
LED_MPP_SINK_MASK, val); if (rc) {
dev_err(&led->spmi_dev->dev, "Failed to write sink control reg\n"); return rc;
} if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
rc = qpnp_pwm_init(led->mpp_cfg->pwm_cfg, led->spmi_dev,
led->cdev.name); if (rc) {
dev_err(&led->spmi_dev->dev, "Failed to initialize pwm\n"); return rc;
}
} return 0;
相应的work queue也将会对mpp进行相干的设置
static void __qpnp_led_work(struct qpnp_led_data *led,
enum led_brightness value)
{
int rc; if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
mutex_lock(&flash_lock); else mutex_lock(&led->lock);
switch (led->id) { case QPNP_ID_WLED:
rc = qpnp_wled_set(led); if (rc < 0)
dev_err(&led->spmi_dev->dev, "WLED set brightness failed (%d)\n", rc);
break; case QPNP_ID_FLASH1_LED0: case QPNP_ID_FLASH1_LED1:
rc = qpnp_flash_set(led); if (rc < 0)
dev_err(&led->spmi_dev->dev, "FLASH set brightness failed (%d)\n", rc);
break; case QPNP_ID_RGB_RED: case QPNP_ID_RGB_GREEN: case QPNP_ID_RGB_BLUE:
rc = qpnp_rgb_set(led); if (rc < 0)
dev_err(&led->spmi_dev->dev, "RGB set brightness failed (%d)\n", rc);
break; case QPNP_ID_LED_MPP:
rc = qpnp_mpp_set(led); if (rc < 0)
dev_err(&led->spmi_dev->dev, "MPP set brightness failed (%d)\n", rc);
break; case QPNP_ID_LED_GPIO:
rc = qpnp_gpio_set(led); if (rc < 0)
dev_err(&led->spmi_dev->dev, "GPIO set brightness failed (%d)\n",
rc);
break; case QPNP_ID_KPDBL:
rc = qpnp_kpdbl_set(led); if (rc < 0)
dev_err(&led->spmi_dev->dev, "KPDBL set brightness failed (%d)\n", rc);
break; default: dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id);
break;
} if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
mutex_unlock(&flash_lock); else mutex_unlock(&led->lock);
}
}
static int qpnp_mpp_set(struct qpnp_led_data *led)
{
int rc;
u8 val;
int duty_us, duty_ns, period_us; if (led->cdev.brightness) { if (led->mpp_cfg->mpp_reg && !led->mpp_cfg->enable) {
rc = regulator_set_voltage(led->mpp_cfg->mpp_reg,
led->mpp_cfg->min_uV,
led->mpp_cfg->max_uV); if (rc) {
dev_err(&led->spmi_dev->dev, "Regulator voltage set failed rc=%d\n",
rc); return rc;
}
rc = regulator_enable(led->mpp_cfg->mpp_reg); if (rc) {
dev_err(&led->spmi_dev->dev, "Regulator enable failed(%d)\n", rc);
goto err_reg_enable;
}
}
led->mpp_cfg->enable = true; if (led->cdev.brightness < led->mpp_cfg->min_brightness) {
dev_warn(&led->spmi_dev->dev, "brightness is less than supported..." \ "set to minimum supported\n");
led->cdev.brightness = led->mpp_cfg->min_brightness;
} if (led->mpp_cfg->pwm_mode != MANUAL_MODE) { if (!led->mpp_cfg->pwm_cfg->blinking) {
led->mpp_cfg->pwm_cfg->mode = led->mpp_cfg->pwm_cfg->default_mode;
led->mpp_cfg->pwm_mode = led->mpp_cfg->pwm_cfg->default_mode;
}
} if (led->mpp_cfg->pwm_mode == PWM_MODE) { period_us = led->mpp_cfg->pwm_cfg->pwm_period_us; if (period_us > INT_MAX / NSEC_PER_USEC) {
duty_us = (period_us * led->cdev.brightness) / LED_FULL;
rc = pwm_config_us(
led->mpp_cfg->pwm_cfg->pwm_dev,
duty_us,
period_us);
} else {
duty_ns = ((period_us * NSEC_PER_USEC) / LED_FULL) * led