leds: qti-tri-led: Add breath feature for tri-led
Some PWM devices could output modulated waveforms with a predefined
pattern which can be used for controlling LED breathing behavior. Add
a sysfs parameter to make the LED device can output breathing pattern.
Change-Id: I1e2c60ea3c61bf14d382991b40536d0cdb39006c
Signed-off-by: Fenglin Wu <fenglinw@codeaurora.org>
diff --git a/drivers/leds/leds-qti-tri-led.c b/drivers/leds/leds-qti-tri-led.c
index f638bc9..c303893 100644
--- a/drivers/leds/leds-qti-tri-led.c
+++ b/drivers/leds/leds-qti-tri-led.c
@@ -53,6 +53,7 @@
u32 off_ms;
enum led_brightness brightness;
bool blink;
+ bool breath;
};
struct qpnp_led_dev {
@@ -66,6 +67,7 @@
const char *default_trigger;
u8 id;
bool blinking;
+ bool breathing;
};
struct qpnp_tri_led_chip {
@@ -119,6 +121,10 @@
pstate.enabled = !!(pwm->duty_ns != 0);
pstate.period = pwm->period_ns;
pstate.duty_cycle = pwm->duty_ns;
+ pstate.output_type = led->led_setting.breath ?
+ PWM_OUTPUT_MODULATED : PWM_OUTPUT_FIXED;
+ /* Use default pattern in PWM device */
+ pstate.output_pattern = NULL;
rc = pwm_apply_state(led->pwm_dev, &pstate);
if (rc < 0)
@@ -183,7 +189,9 @@
/* Use initial period if no blinking is required */
period_ns = led->pwm_setting.pre_period_ns;
- if (period_ns > INT_MAX / brightness)
+ if (brightness == LED_OFF)
+ duty_ns = 0;
+ else if (period_ns > INT_MAX / brightness)
duty_ns = (period_ns / LED_FULL) * brightness;
else
duty_ns = (period_ns * brightness) / LED_FULL;
@@ -207,9 +215,15 @@
if (led->led_setting.blink) {
led->cdev.brightness = LED_FULL;
led->blinking = true;
+ led->breathing = false;
+ } else if (led->led_setting.breath) {
+ led->cdev.brightness = LED_FULL;
+ led->blinking = false;
+ led->breathing = true;
} else {
led->cdev.brightness = led->led_setting.brightness;
led->blinking = false;
+ led->breathing = false;
}
return rc;
@@ -227,7 +241,7 @@
brightness = LED_FULL;
if (brightness == led->led_setting.brightness &&
- !led->blinking) {
+ !led->blinking && !led->breathing) {
mutex_unlock(&led->lock);
return 0;
}
@@ -238,6 +252,7 @@
else
led->led_setting.on_ms = 0;
led->led_setting.blink = false;
+ led->led_setting.breath = false;
rc = qpnp_tri_led_set(led);
if (rc)
@@ -273,14 +288,17 @@
if (*on_ms == 0) {
led->led_setting.blink = false;
+ led->led_setting.breath = false;
led->led_setting.brightness = LED_OFF;
} else if (*off_ms == 0) {
led->led_setting.blink = false;
+ led->led_setting.breath = false;
led->led_setting.brightness = led->cdev.brightness;
} else {
led->led_setting.on_ms = *on_ms;
led->led_setting.off_ms = *off_ms;
led->led_setting.blink = true;
+ led->led_setting.breath = false;
}
rc = qpnp_tri_led_set(led);
@@ -292,6 +310,52 @@
return rc;
}
+static ssize_t breath_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct qpnp_led_dev *led =
+ container_of(led_cdev, struct qpnp_led_dev, cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", led->led_setting.breath);
+}
+
+static ssize_t breath_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int rc;
+ bool breath;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct qpnp_led_dev *led =
+ container_of(led_cdev, struct qpnp_led_dev, cdev);
+
+ rc = kstrtobool(buf, &breath);
+ if (rc < 0)
+ return rc;
+
+ mutex_lock(&led->lock);
+ if (led->breathing == breath)
+ goto unlock;
+
+ led->led_setting.blink = false;
+ led->led_setting.breath = breath;
+ led->led_setting.brightness = breath ? LED_FULL : LED_OFF;
+ rc = qpnp_tri_led_set(led);
+ if (rc < 0)
+ dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n",
+ led->label, rc);
+
+unlock:
+ mutex_unlock(&led->lock);
+ return (rc < 0) ? rc : count;
+}
+
+static DEVICE_ATTR(breath, 0644, breath_show, breath_store);
+static const struct attribute *breath_attrs[] = {
+ &dev_attr_breath.attr,
+ NULL
+};
+
static int qpnp_tri_led_register(struct qpnp_tri_led_chip *chip)
{
struct qpnp_led_dev *led;
@@ -313,15 +377,30 @@
if (rc < 0) {
dev_err(chip->dev, "%s led class device registering failed, rc=%d\n",
led->label, rc);
- goto destroy;
+ goto err_out;
+ }
+
+ if (pwm_get_output_type_supported(led->pwm_dev)
+ & PWM_OUTPUT_MODULATED) {
+ rc = sysfs_create_files(&led->cdev.dev->kobj,
+ breath_attrs);
+ if (rc < 0) {
+ dev_err(chip->dev, "Create breath file for %s led failed, rc=%d\n",
+ led->label, rc);
+ goto err_out;
+ }
}
}
return 0;
-destroy:
- for (j = 0; j <= i; j++)
- mutex_destroy(&chip->leds[i].lock);
+err_out:
+ for (j = 0; j <= i; j++) {
+ if (j < i)
+ sysfs_remove_files(&chip->leds[j].cdev.dev->kobj,
+ breath_attrs);
+ mutex_destroy(&chip->leds[j].lock);
+ }
return rc;
}
@@ -483,8 +562,10 @@
struct qpnp_tri_led_chip *chip = dev_get_drvdata(&pdev->dev);
mutex_destroy(&chip->bus_lock);
- for (i = 0; i < chip->num_leds; i++)
+ for (i = 0; i < chip->num_leds; i++) {
+ sysfs_remove_files(&chip->leds[i].cdev.dev->kobj, breath_attrs);
mutex_destroy(&chip->leds[i].lock);
+ }
dev_set_drvdata(chip->dev, NULL);
return 0;
}