msm-pm660l.dtsi相关节点。
qcom,leds@d000 {
compatible = "qcom,leds-qpnp";
reg = <0xd000 0x100>;
label = "rgb";
red_led: qcom,rgb_0 {
label = "rgb";
qcom,id = <3>;
qcom,mode = "pwm";
pwms = <&pm660l_pwm_3 0 0>;
qcom,pwm-us = <2000>;
qcom,max-current = <12>;
qcom,default-state = "off";
linux,name = "red";
qcom,start-idx = <0>;
qcom,idx-len = <10>;
qcom,duty-pcts =
[00 19 32 4b 64
64 46 32 19 00];
qcom,use-blink;
};
qcom,mode = "pwm";模式为PWM ,另外的两个模式是manual、lpg。一个是手动模式,另一个不太清楚。
qcom,pwm-us = <2000>;表示PWM的一个周期是2s。
qcom,max-current = <12>;最大电流12mA
qcom,duty-pcts = [00 19 32 4b 64 64 46 32 19 00];亮度值从0到64,再从64到0。实现呼吸灯功能,若想实现闪烁,值改为0与64即可。
LPG模式可选属性
qcom,pause-lo:在周期的低端暂停
qcom,pause-hi:在周期的高端暂停
qcom,ramp-step-ms:每个周期间隔(ms)
qcom,lut-flags:lut配置中使用的标志
Leds-qpnp.c (kernel\msm-4.4\drivers\leds)节点解析文件
static struct platform_driver qpnp_leds_driver = {
.driver = {
.name = "qcom,leds-qpnp",
.of_match_table = spmi_match_table,
},
.probe = qpnp_leds_probe,
.remove = qpnp_leds_remove,
};
//进入probe函数
static int qpnp_leds_probe(struct platform_device *pdev)
{
struct qpnp_led_data *led, *led_array;
unsigned int base;
struct device_node *node, *temp;
int rc, i, num_leds = 0, parsed_leds = 0;
const char *led_label;
bool regulator_probe = false;
node = pdev->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_array = devm_kcalloc(&pdev->dev, num_leds, sizeof(*led_array),
GFP_KERNEL);
if (!led_array)
return -ENOMEM;
for_each_child_of_node(node, temp) {
led = &led_array[parsed_leds];
led->num_leds = num_leds;
led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!led->regmap) {
dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
return -EINVAL;
}
led->pdev = pdev;
rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
if (rc < 0) {
dev_err(&pdev->dev,
"Couldn't find reg in node = %s rc = %d\n",
pdev->dev.of_node->full_name, rc);
goto fail_id_check;
}
led->base = base;
rc = of_property_read_string(temp, "label", &led_label);
if (rc < 0) {
dev_err(&led->pdev->dev,
"Failure reading label, rc = %d\n", rc);
goto fail_id_check;
}
rc = of_property_read_string(temp, "linux,name",
&led->cdev.name);
if (rc < 0) {
dev_err(&led->pdev->dev,
"Failure reading led name, rc = %d\n", rc);
goto fail_id_check;
}
rc = of_property_read_u32(temp, "qcom,max-current",
&led->max_current);
if (rc < 0) {
dev_err(&led->pdev->dev,
"Failure reading max_current, rc = %d\n", rc);
goto fail_id_check;
}
rc = of_property_read_u32(temp, "qcom,id", &led->id);
if (rc < 0) {
dev_err(&led->pdev->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->pdev->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;
if (strcmp(led_label, "wled") == 0) {
rc = qpnp_get_config_wled(led, temp);
if (rc < 0) {
dev_err(&led->pdev->dev,
"Unable to read wled config data\n");
goto fail_id_check;
}
} else if (strcmp(led_label, "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->pdev->dev,
"Unable to read flash config data\n");
goto fail_id_check;
}
} else if (strcmp(led_label, "rgb") == 0) {
rc = qpnp_get_config_rgb(led, temp);
if (rc < 0) {
dev_err(&led->pdev->dev,
"Unable to read rgb config data\n");
goto fail_id_check;
}
} else if (strcmp(led_label, "mpp") == 0) {
rc = qpnp_get_config_mpp(led, temp);
if (rc < 0) {
dev_err(&led->pdev->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->pdev->dev,
"Unable to read gpio config data\n");
goto fail_id_check;
}
} else if (strcmp(led_label, "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->pdev->dev,
"Unable to read kpdbl config data\n");
goto fail_id_check;
}
} else {
dev_err(&led->pdev->dev, "No LED matching label\n");
rc = -EINVAL;
goto fail_id_check;
}
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);
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(&pdev->dev, &led->cdev);
if (rc) {
dev_err(&pdev->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;
}
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(&pdev->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->cdev.brightness_set = qpnp_led_set;这里和led_class联系起来了。
static void qpnp_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct qpnp_led_data *led;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
if (value < LED_OFF) {
dev_err(&led->pdev->dev, "Invalid brightness value\n");
return;
}
if (value > led->cdev.max_brightness)
value = led->cdev.max_brightness;
led->cdev.brightness = value;
if (led->in_order_command_processing)
queue_work(led->workqueue, &led->work);
else
schedule_work(&led->work);
}
设置了灯的亮度值,并在 结尾开启了一个work队列,也就是INIT_WORK(&led->work, qpnp_led_work);最后调用到下面函数
static int qpnp_rgb_set(struct qpnp_led_data *led)
{
int rc;
int duty_us, duty_ns, period_us;
if (led->cdev.brightness) {
if (!led->rgb_cfg->pwm_cfg->blinking)
led->rgb_cfg->pwm_cfg->mode =
led->rgb_cfg->pwm_cfg->default_mode;
if (led->rgb_cfg->pwm_cfg->mode == PWM_MODE) {
rc = pwm_change_mode(led->rgb_cfg->pwm_cfg->pwm_dev,
PM_PWM_MODE_PWM);
if (rc < 0) {
dev_err(&led->pdev->dev,
"Failed to set PWM mode, rc = %d\n",
rc);
return rc;
}
period_us = led->rgb_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->rgb_cfg->pwm_cfg->pwm_dev,
duty_us,
period_us);
} else {
duty_ns = ((period_us * NSEC_PER_USEC) /
LED_FULL) * led->cdev.brightness;
rc = pwm_config(
led->rgb_cfg->pwm_cfg->pwm_dev,
duty_ns,
period_us * NSEC_PER_USEC);
}
if (rc < 0) {
dev_err(&led->pdev->dev,
"pwm config failed\n");
return rc;
}
}
rc = qpnp_led_masked_write(led,
RGB_LED_EN_CTL(led->base),
led->rgb_cfg->enable, led->rgb_cfg->enable);
if (rc) {
dev_err(&led->pdev->dev,
"Failed to write led enable reg\n");
return rc;
}
if (!led->rgb_cfg->pwm_cfg->pwm_enabled) {
pwm_enable(led->rgb_cfg->pwm_cfg->pwm_dev);
led->rgb_cfg->pwm_cfg->pwm_enabled = 1;
}
} else {
led->rgb_cfg->pwm_cfg->mode =
led->rgb_cfg->pwm_cfg->default_mode;
if (led->rgb_cfg->pwm_cfg->pwm_enabled) {
pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
}
rc = qpnp_led_masked_write(led,
RGB_LED_EN_CTL(led->base),
led->rgb_cfg->enable, RGB_LED_DISABLE);
if (rc) {
dev_err(&led->pdev->dev,
"Failed to write led enable reg\n");
return rc;
}
}
led->rgb_cfg->pwm_cfg->blinking = false;
qpnp_dump_regs(led, rgb_pwm_debug_regs, ARRAY_SIZE(rgb_pwm_debug_regs));
return 0;
}
qpnp_led_masked_write,这个函数的作用就是把相应的信息写入到寄存器中 ,使能led。
中间部分的配置,根据led_label=rgb进入qpnp_get_config_rgb()开始配置led。
static int qpnp_get_config_rgb(struct qpnp_led_data *led,
struct device_node *node)
{
int rc;
u8 led_mode;
const char *mode;
led->rgb_cfg = devm_kzalloc(&led->pdev->dev,
sizeof(struct rgb_config_data), GFP_KERNEL);
if (!led->rgb_cfg)
return -ENOMEM;
if (led->id == QPNP_ID_RGB_RED)
led->rgb_cfg->enable = RGB_LED_ENABLE_RED;
else if (led->id == QPNP_ID_RGB_GREEN)
led->rgb_cfg->enable = RGB_LED_ENABLE_GREEN;
else if (led->id == QPNP_ID_RGB_BLUE)
led->rgb_cfg->enable = RGB_LED_ENABLE_BLUE;
else
return -EINVAL;
rc = of_property_read_string(node, "qcom,mode", &mode);
if (!rc) {
led_mode = qpnp_led_get_mode(mode);
if ((led_mode == MANUAL_MODE) || (led_mode == -EINVAL)) {
dev_err(&led->pdev->dev, "Selected mode not supported for rgb\n");
return -EINVAL;
}
led->rgb_cfg->pwm_cfg = devm_kzalloc(&led->pdev->dev,
sizeof(struct pwm_config_data),
GFP_KERNEL);
if (!led->rgb_cfg->pwm_cfg) {
dev_err(&led->pdev->dev,
"Unable to allocate memory\n");
return -ENOMEM;
}
led->rgb_cfg->pwm_cfg->mode = led_mode;
led->rgb_cfg->pwm_cfg->default_mode = led_mode;
} else {
return rc;
}
rc = qpnp_get_config_pwm(led->rgb_cfg->pwm_cfg, led->pdev, node);
if (rc < 0) {
if (led->rgb_cfg->pwm_cfg->pwm_dev)
pwm_put(led->rgb_cfg->pwm_cfg->pwm_dev);
return rc;
}
return 0;
}
先解析出led的模式,有三种模式。
static int qpnp_led_get_mode(const char *mode)
{
if (strcmp(mode, "manual") == 0)
return MANUAL_MODE;
else if (strcmp(mode, "pwm") == 0)
return PWM_MODE;
else if (strcmp(mode, "lpg") == 0)
return LPG_MODE;
else
return -EINVAL;
};
随后解析出PWM的配置
static int qpnp_get_config_pwm(struct pwm_config_data *pwm_cfg,
struct platform_device *pdev,
struct device_node *node)
{
struct property *prop;
int rc, i, lut_max_size;
u32 val;
u8 *temp_cfg;
const char *led_label;
pwm_cfg->pwm_dev = of_pwm_get(node, NULL);
if (IS_ERR(pwm_cfg->pwm_dev)) {
rc = PTR_ERR(pwm_cfg->pwm_dev);
dev_err(&pdev->dev, "Cannot get PWM device rc:(%d)\n", rc);
pwm_cfg->pwm_dev = NULL;
return rc;
}
if (pwm_cfg->mode != MANUAL_MODE) {
rc = of_property_read_u32(node, "qcom,pwm-us", &val);
if (!rc)
pwm_cfg->pwm_period_us = val;
else
return rc;
}
pwm_cfg->use_blink =
of_property_read_bool(node, "qcom,use-blink");
if (pwm_cfg->mode == LPG_MODE || pwm_cfg->use_blink) {
pwm_cfg->duty_cycles =
devm_kzalloc(&pdev->dev,
sizeof(struct pwm_duty_cycles), GFP_KERNEL);
if (!pwm_cfg->duty_cycles) {
dev_err(&pdev->dev, "Unable to allocate memory\n");
rc = -ENOMEM;
goto bad_lpg_params;
}
prop = of_find_property(node, "qcom,duty-pcts",
&pwm_cfg->duty_cycles->num_duty_pcts);
if (!prop) {
dev_err(&pdev->dev, "Looking up property node qcom,duty-pcts failed\n");
rc = -ENODEV;
goto bad_lpg_params;
} else if (!pwm_cfg->duty_cycles->num_duty_pcts) {
dev_err(&pdev->dev, "Invalid length of duty pcts\n");
rc = -EINVAL;
goto bad_lpg_params;
}
rc = of_property_read_string(node, "label", &led_label);
if (rc < 0) {
dev_err(&pdev->dev,
"Failure reading label, rc = %d\n", rc);
return rc;
}
if (strcmp(led_label, "kpdbl") == 0)
lut_max_size = PWM_GPLED_LUT_MAX_SIZE;
else
lut_max_size = PWM_LUT_MAX_SIZE;
pwm_cfg->duty_cycles->duty_pcts =
devm_kzalloc(&pdev->dev,
sizeof(int) * lut_max_size,
GFP_KERNEL);
if (!pwm_cfg->duty_cycles->duty_pcts) {
dev_err(&pdev->dev, "Unable to allocate memory\n");
rc = -ENOMEM;
goto bad_lpg_params;
}
pwm_cfg->old_duty_pcts =
devm_kzalloc(&pdev->dev,
sizeof(int) * lut_max_size,
GFP_KERNEL);
if (!pwm_cfg->old_duty_pcts) {
dev_err(&pdev->dev, "Unable to allocate memory\n");
rc = -ENOMEM;
goto bad_lpg_params;
}
temp_cfg = devm_kzalloc(&pdev->dev,
pwm_cfg->duty_cycles->num_duty_pcts *
sizeof(u8), GFP_KERNEL);
if (!temp_cfg) {
dev_err(&pdev->dev, "Failed to allocate memory for duty pcts\n");
rc = -ENOMEM;
goto bad_lpg_params;
}
memcpy(temp_cfg, prop->value,
pwm_cfg->duty_cycles->num_duty_pcts);
for (i = 0; i < pwm_cfg->duty_cycles->num_duty_pcts; i++)
pwm_cfg->duty_cycles->duty_pcts[i] =
(int) temp_cfg[i];
rc = of_property_read_u32(node, "qcom,start-idx", &val);
if (!rc) {
pwm_cfg->lut_params.start_idx = val;
pwm_cfg->duty_cycles->start_idx = val;
} else
goto bad_lpg_params;
pwm_cfg->lut_params.lut_pause_hi = 0;
rc = of_property_read_u32(node, "qcom,pause-hi", &val);
if (!rc)
pwm_cfg->lut_params.lut_pause_hi = val;
else if (rc != -EINVAL)
goto bad_lpg_params;
pwm_cfg->lut_params.lut_pause_lo = 0;
rc = of_property_read_u32(node, "qcom,pause-lo", &val);
if (!rc)
pwm_cfg->lut_params.lut_pause_lo = val;
else if (rc != -EINVAL)
goto bad_lpg_params;
pwm_cfg->lut_params.ramp_step_ms =
QPNP_LUT_RAMP_STEP_DEFAULT;
rc = of_property_read_u32(node, "qcom,ramp-step-ms", &val);
if (!rc)
pwm_cfg->lut_params.ramp_step_ms = val;
else if (rc != -EINVAL)
goto bad_lpg_params;
pwm_cfg->lut_params.flags = QPNP_LED_PWM_FLAGS;
rc = of_property_read_u32(node, "qcom,lut-flags", &val);
if (!rc)
pwm_cfg->lut_params.flags = (u8) val;
else if (rc != -EINVAL)
goto bad_lpg_params;
pwm_cfg->lut_params.idx_len =
pwm_cfg->duty_cycles->num_duty_pcts;
}
return 0;
bad_lpg_params:
pwm_cfg->use_blink = false;
if (pwm_cfg->mode == PWM_MODE) {
dev_err(&pdev->dev, "LPG parameters not set for blink mode, defaulting to PWM mode\n");
return 0;
}
return rc;
};
配置完后就能通过brightness_set调用qpnp_led_set了。