summaryrefslogtreecommitdiff
path: root/drivers/leds/leds-qpnp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/leds-qpnp.c')
-rw-r--r--drivers/leds/leds-qpnp.c176
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)