diff options
Diffstat (limited to 'drivers/leds/leds-qpnp.c')
-rw-r--r-- | drivers/leds/leds-qpnp.c | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/drivers/leds/leds-qpnp.c b/drivers/leds/leds-qpnp.c index e3cfffb5c563..0eec6d0f52d4 100644 --- a/drivers/leds/leds-qpnp.c +++ b/drivers/leds/leds-qpnp.c @@ -268,6 +268,8 @@ enum qpnp_leds { QPNP_ID_MAX, }; +#define QPNP_ID_TO_RGB_IDX(id) (id - QPNP_ID_RGB_RED) + /* current boost limit */ enum wled_current_boost_limit { WLED_CURR_LIMIT_105mA, @@ -558,6 +560,15 @@ struct qpnp_led_data { int turn_off_delay_ms; }; +/** + * struct rgb_sync - rgb led synchrnize structure + */ +struct rgb_sync { + struct led_classdev cdev; + struct platform_device *pdev; + struct qpnp_led_data *led_data[3]; +}; + static DEFINE_MUTEX(flash_lock); static struct pwm_device *kpdbl_master; static u32 kpdbl_master_period_us; @@ -2722,6 +2733,130 @@ static ssize_t blink_store(struct device *dev, return count; } +static inline void rgb_lock_leds(struct rgb_sync *rgb) +{ + int i; + + for (i = 0; i < 3; i++) { + if (rgb->led_data[i]) { + flush_work(&rgb->led_data[i]->work); + mutex_lock(&rgb->led_data[i]->lock); + } + } +} + +static inline void rgb_unlock_leds(struct rgb_sync *rgb) +{ + int i; + + for (i = 0; i < 3; i++) { + if (rgb->led_data[i]) { + mutex_unlock(&rgb->led_data[i]->lock); + } + } +} + +static void rgb_disable_leds(struct rgb_sync *rgb) +{ + int i; + struct qpnp_led_data *led; + + //TODO Implement synchronized off + for (i = 0; i < 3; i++) { + led = rgb->led_data[i]; + if (led && led->rgb_cfg->pwm_cfg->pwm_enabled) { + led->rgb_cfg->pwm_cfg->mode = + led->rgb_cfg->pwm_cfg->default_mode; + led->rgb_cfg->pwm_cfg->blinking = false; + pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev); + led->rgb_cfg->pwm_cfg->pwm_enabled = 0; + } + } +} + +/** + * Should only be called when all RGB leds are off + */ +static int rgb_enable_leds(struct rgb_sync *rgb) +{ + struct qpnp_led_data *led; + struct pwm_device *pwm_dev[3]; + int i, rc; + + for (i = 0; i < 3; i++) { + led = rgb->led_data[i]; + if (!led) + continue; + + led->rgb_cfg->pwm_cfg->mode = LPG_MODE; + pwm_free(led->rgb_cfg->pwm_cfg->pwm_dev); + qpnp_pwm_init(led->rgb_cfg->pwm_cfg, led->pdev, led->cdev.name); + pwm_dev[i] = led->rgb_cfg->pwm_cfg->pwm_dev; + } + + if (i == 0) + return 0; + + rc = pwm_enable_synchronized(pwm_dev, i); + if (rc) { + dev_err(&rgb->pdev->dev, "Unable to enable pwms\n"); + return rc; + } + + for (i = 0; i < 3; i++) { + led = rgb->led_data[i]; + if (!led) + continue; + led->rgb_cfg->pwm_cfg->blinking = true; + led->rgb_cfg->pwm_cfg->pwm_enabled = 1; + } + + return rc; +} + +static ssize_t rgb_blink_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rgb_sync *rgb_sync; + struct qpnp_led_data *led = NULL; + unsigned long blinking; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + ssize_t rc = -EINVAL, i; + u8 enable = 0; + + rc = kstrtoul(buf, 10, &blinking); + if (rc) + return rc; + rgb_sync = container_of(led_cdev, struct rgb_sync, cdev); + + rgb_lock_leds(rgb_sync); + for (i = 0; i < 3; i++) { + if (rgb_sync->led_data[i]) { + led = rgb_sync->led_data[i]; + enable |= led->rgb_cfg->enable; + } + } + + if (!led) + return count; + + rc = qpnp_led_masked_write(led, + RGB_LED_EN_CTL(led->base), + enable, blinking ? enable : RGB_LED_DISABLE); + if (rc) { + dev_err(&led->pdev->dev, + "Failed to write led enable reg\n"); + rgb_unlock_leds(rgb_sync); + return rc; + } + rgb_disable_leds(rgb_sync); + if (blinking) + rgb_enable_leds(rgb_sync); + rgb_unlock_leds(rgb_sync); + return count; +} + static DEVICE_ATTR(led_mode, 0664, NULL, led_mode_store); static DEVICE_ATTR(strobe, 0664, NULL, led_strobe_type_store); static DEVICE_ATTR(pwm_us, 0664, NULL, pwm_us_store); @@ -2732,6 +2867,7 @@ static DEVICE_ATTR(ramp_step_ms, 0664, NULL, ramp_step_ms_store); static DEVICE_ATTR(lut_flags, 0664, NULL, lut_flags_store); static DEVICE_ATTR(duty_pcts, 0664, NULL, duty_pcts_store); static DEVICE_ATTR(blink, 0664, NULL, blink_store); +static DEVICE_ATTR(rgb_blink, 0664, NULL, rgb_blink_store); static struct attribute *led_attrs[] = { &dev_attr_led_mode.attr, @@ -2763,6 +2899,11 @@ static struct attribute *blink_attrs[] = { NULL }; +static struct attribute *rgb_blink_attrs[] = { + &dev_attr_rgb_blink.attr, + NULL +}; + static const struct attribute_group pwm_attr_group = { .attrs = pwm_attrs, }; @@ -2775,6 +2916,10 @@ static const struct attribute_group blink_attr_group = { .attrs = blink_attrs, }; +static const struct attribute_group rgb_blink_attr_group = { + .attrs = rgb_blink_attrs, +}; + static int qpnp_flash_init(struct qpnp_led_data *led) { int rc; @@ -3872,6 +4017,7 @@ static int qpnp_leds_probe(struct platform_device *pdev) int rc, i, num_leds = 0, parsed_leds = 0; const char *led_label; bool regulator_probe = false; + struct rgb_sync *rgb_sync = NULL; node = pdev->dev.of_node; if (node == NULL) @@ -3889,6 +4035,29 @@ static int qpnp_leds_probe(struct platform_device *pdev) if (!led_array) return -ENOMEM; + if (of_property_read_bool(node, "qcom,rgb-sync")) { + rgb_sync = devm_kzalloc(&pdev->dev, + sizeof(struct rgb_sync), GFP_KERNEL); + if (!rgb_sync) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + kfree(led_array); + return -ENOMEM; + } + rgb_sync->cdev.name = "rgb"; + rgb_sync->pdev = pdev; + rc = led_classdev_register(&pdev->dev, &rgb_sync->cdev); + if (rc) { + dev_err(&pdev->dev, "unable to register rgb %d\n", rc); + goto fail_id_check; + } + rc = sysfs_create_group(&rgb_sync->cdev.dev->kobj, + &rgb_blink_attr_group); + if (rc) { + dev_err(&pdev->dev, "unable to create rgb sysfs %d\n", rc); + goto fail_id_check; + } + } + for_each_child_of_node(node, temp) { led = &led_array[parsed_leds]; led->num_leds = num_leds; @@ -4092,6 +4261,9 @@ static int qpnp_leds_probe(struct platform_device *pdev) &lpg_attr_group); if (rc) goto fail_id_check; + + if (rgb_sync) + rgb_sync->led_data[QPNP_ID_TO_RGB_IDX(led->id)] = led; } else if (led->rgb_cfg->pwm_cfg->mode == LPG_MODE) { rc = sysfs_create_group(&led->cdev.dev->kobj, &lpg_attr_group); @@ -4138,6 +4310,10 @@ static int qpnp_leds_probe(struct platform_device *pdev) return 0; fail_id_check: + if (rgb_sync) { + led_classdev_unregister(&rgb_sync->cdev); + kfree(rgb_sync); + } for (i = 0; i < parsed_leds; i++) { if (led_array[i].id != QPNP_ID_FLASH1_LED0 && led_array[i].id != QPNP_ID_FLASH1_LED1) |