Signed-off-by: Dan Murphy <dmurphy@xxxxxx> --- drivers/leds/leds-cpcap.c | 291 +++++++++++++++++++++++++++++++++-- drivers/mfd/motorola-cpcap.c | 8 +- 2 files changed, 288 insertions(+), 11 deletions(-) diff --git a/drivers/leds/leds-cpcap.c b/drivers/leds/leds-cpcap.c index f0f28c442807..4088a52de26b 100644 --- a/drivers/leds/leds-cpcap.c +++ b/drivers/leds/leds-cpcap.c @@ -12,6 +12,7 @@ */ #include <linux/leds.h> +#include <linux/led-class-rgb.h> #include <linux/mfd/motorola-cpcap.h> #include <linux/module.h> #include <linux/mutex.h> @@ -28,6 +29,10 @@ struct cpcap_led_info { u16 limit; u16 init_mask; u16 init_val; + u16 rgb_combo; + u16 red_reg; + u16 green_reg; + u16 blue_reg; }; static const struct cpcap_led_info cpcap_led_red = { @@ -48,6 +53,15 @@ static const struct cpcap_led_info cpcap_led_blue = { .limit = 31, }; +static const struct cpcap_led_info cpcap_led_rgb = { + .red_reg = CPCAP_REG_REDC, + .green_reg = CPCAP_REG_GREENC, + .blue_reg = CPCAP_REG_BLUEC, + .rgb_combo = 0x1, + .mask = 0x03FF, + .limit = 31, +}; + /* aux display light */ static const struct cpcap_led_info cpcap_led_adl = { .reg = CPCAP_REG_ADLC, @@ -68,6 +82,7 @@ static const struct cpcap_led_info cpcap_led_cp = { struct cpcap_led { struct led_classdev led; + struct led_classdev_rgb rgb_cdev; const struct cpcap_led_info *info; struct device *dev; struct regmap *regmap; @@ -116,6 +131,11 @@ static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value) mutex_lock(&led->update_lock); + if (led->info->rgb_combo) { + err = 0; + goto exit; + } + if (value > LED_OFF) { err = cpcap_led_set_power(led, true); if (err) @@ -154,10 +174,232 @@ static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value) return err; } +static struct cpcap_led *rgbled_cdev_to_led(struct led_classdev_rgb *rgbled_cdev) +{ + return container_of(rgbled_cdev, struct cpcap_led, rgb_cdev); +} + +static int cpcap_led_set_red(struct led_classdev_rgb *rgbled_cdev, + enum led_brightness value) +{ + struct cpcap_led *led = rgbled_cdev_to_led(rgbled_cdev); + int brightness; + int err; + + mutex_lock(&led->update_lock); + + if (value > LED_OFF) { + err = cpcap_led_set_power(led, true); + if (err) + goto exit; + } + + if (value == LED_OFF) { + /* Avoid HW issue by turning off current before duty cycle */ + err = regmap_update_bits(led->regmap, + led->info->red_reg, led->info->mask, CPCAP_LED_NO_CURRENT); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + brightness = cpcap_led_val(value, LED_OFF); + } else { + brightness = cpcap_led_val(value, LED_ON); + } + + err = regmap_update_bits(led->regmap, led->info->red_reg, led->info->mask, + brightness); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + if (value == LED_OFF) + err = cpcap_led_set_power(led, false); + +exit: + mutex_unlock(&led->update_lock); + return err; +} + +static int cpcap_led_set_green(struct led_classdev_rgb *rgbled_cdev, + enum led_brightness value) +{ + struct cpcap_led *led = rgbled_cdev_to_led(rgbled_cdev); + int brightness; + int err; + + mutex_lock(&led->update_lock); + + if (value > LED_OFF) { + err = cpcap_led_set_power(led, true); + if (err) + goto exit; + } + + if (value == LED_OFF) { + /* Avoid HW issue by turning off current before duty cycle */ + err = regmap_update_bits(led->regmap, + led->info->green_reg, led->info->mask, CPCAP_LED_NO_CURRENT); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + brightness = cpcap_led_val(value, LED_OFF); + } else { + brightness = cpcap_led_val(value, LED_ON); + } + + err = regmap_update_bits(led->regmap, led->info->green_reg, led->info->mask, + brightness); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + if (value == LED_OFF) + err = cpcap_led_set_power(led, false); +exit: + mutex_unlock(&led->update_lock); + return err; +} + +static int cpcap_led_set_blue(struct led_classdev_rgb *rgbled_cdev, + enum led_brightness value) +{ + struct cpcap_led *led = rgbled_cdev_to_led(rgbled_cdev); + int brightness; + int err; + + mutex_lock(&led->update_lock); + + if (value > LED_OFF) { + err = cpcap_led_set_power(led, true); + if (err) + goto exit; + } + + if (value == LED_OFF) { + /* Avoid HW issue by turning off current before duty cycle */ + err = regmap_update_bits(led->regmap, + led->info->blue_reg, led->info->mask, CPCAP_LED_NO_CURRENT); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + brightness = cpcap_led_val(value, LED_OFF); + } else { + brightness = cpcap_led_val(value, LED_ON); + } + + err = regmap_update_bits(led->regmap, led->info->blue_reg, led->info->mask, + brightness); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + if (value == LED_OFF) + err = cpcap_led_set_power(led, false); + +exit: + mutex_unlock(&led->update_lock); + return err; +} +static int cpcap_led_set_color(struct led_classdev_rgb *rgbled_cdev) +{ + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + struct cpcap_led *led = rgbled_cdev_to_led(rgbled_cdev); + int red_brightness, green_brightness, blue_brightness; + int err; + + mutex_lock(&led->update_lock); + + if (colors->red > LED_OFF || colors->green > LED_OFF || + colors->blue > LED_OFF) { + err = cpcap_led_set_power(led, true); + if (err) + goto exit; + } + + if (colors->red == LED_OFF && colors->green == LED_OFF && + colors->blue == LED_OFF) { + /* Avoid HW issue by turning off current before duty cycle */ + err = regmap_update_bits(led->regmap, + led->info->red_reg, led->info->mask, CPCAP_LED_NO_CURRENT); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + err = regmap_update_bits(led->regmap, + led->info->green_reg, led->info->mask, CPCAP_LED_NO_CURRENT); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + err = regmap_update_bits(led->regmap, + led->info->blue_reg, led->info->mask, CPCAP_LED_NO_CURRENT); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + red_brightness = cpcap_led_val(colors->red, LED_OFF); + green_brightness = cpcap_led_val(colors->green, LED_OFF); + blue_brightness = cpcap_led_val(colors->blue, LED_OFF); + } else { + red_brightness = cpcap_led_val(colors->red, LED_ON); + green_brightness = cpcap_led_val(colors->green, LED_ON); + blue_brightness = cpcap_led_val(colors->blue, LED_ON); + } + + err = regmap_update_bits(led->regmap, led->info->red_reg, led->info->mask, + red_brightness); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + err = regmap_update_bits(led->regmap, led->info->green_reg, led->info->mask, + green_brightness); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + err = regmap_update_bits(led->regmap, led->info->blue_reg, led->info->mask, + blue_brightness); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + goto exit; + } + + if (colors->red == LED_OFF && colors->green == LED_OFF && + colors->blue == LED_OFF) + err = cpcap_led_set_power(led, false); + +exit: + mutex_unlock(&led->update_lock); + return err; +} + +static struct led_rgb_ops cpcap_led_rgb_ops = { + .set_color = cpcap_led_set_color, + .set_red_brightness = cpcap_led_set_red, + .set_green_brightness = cpcap_led_set_green, + .set_blue_brightness = cpcap_led_set_blue, +}; + static const struct of_device_id cpcap_led_of_match[] = { { .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red }, { .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green }, { .compatible = "motorola,cpcap-led-blue", .data = &cpcap_led_blue }, + { .compatible = "motorola,cpcap-led-rgb", .data = &cpcap_led_rgb }, { .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl }, { .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp }, {}, @@ -167,6 +409,7 @@ MODULE_DEVICE_TABLE(of, cpcap_led_of_match); static int cpcap_led_probe(struct platform_device *pdev) { const struct of_device_id *match; + struct led_classdev *led_cdev; struct cpcap_led *led; int err; @@ -181,7 +424,7 @@ static int cpcap_led_probe(struct platform_device *pdev) led->info = match->data; led->dev = &pdev->dev; - if (led->info->reg == 0x0000) { + if (!led->info->rgb_combo && led->info->reg == 0x0000) { dev_err(led->dev, "Unsupported LED"); return -ENODEV; } @@ -204,19 +447,49 @@ static int cpcap_led_probe(struct platform_device *pdev) } if (led->info->init_mask) { - err = regmap_update_bits(led->regmap, led->info->reg, - led->info->init_mask, led->info->init_val); - if (err) { - dev_err(led->dev, "regmap failed: %d", err); - return err; + if (led->info->rgb_combo) { + err = regmap_update_bits(led->regmap, led->info->red_reg, + led->info->init_mask, led->info->init_val); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + return err; + } + err = regmap_update_bits(led->regmap, led->info->green_reg, + led->info->init_mask, led->info->init_val); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + return err; + } + err = regmap_update_bits(led->regmap, led->info->blue_reg, + led->info->init_mask, led->info->init_val); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + return err; + } + } else { + err = regmap_update_bits(led->regmap, led->info->reg, + led->info->init_mask, led->info->init_val); + if (err) { + dev_err(led->dev, "regmap failed: %d", err); + return err; + } } } mutex_init(&led->update_lock); - led->led.max_brightness = led->info->limit; - led->led.brightness_set_blocking = cpcap_led_set; - err = devm_led_classdev_register(&pdev->dev, &led->led); + if (!led->info->rgb_combo) { + led->led.max_brightness = led->info->limit; + led->led.brightness_set_blocking = cpcap_led_set; + err = devm_led_classdev_register(&pdev->dev, &led->led); + } else { + led->rgb_cdev.ops = &cpcap_led_rgb_ops; + led_cdev = &led->rgb_cdev.led_cdev; + led_cdev->name = led->led.name; + led_cdev->brightness_set_blocking = cpcap_led_set; + err = led_classdev_rgb_register(&pdev->dev, &led->rgb_cdev); + } + if (err) { dev_err(led->dev, "Couldn't register LED: %d", err); return err; diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c index 20d9692640e1..beb7063559bb 100644 --- a/drivers/mfd/motorola-cpcap.c +++ b/drivers/mfd/motorola-cpcap.c @@ -253,11 +253,15 @@ static const struct mfd_cell cpcap_mfd_devices[] = { .of_compatible = "motorola,cpcap-led-blue", }, { .name = "cpcap-led", - .id = 3, + .id = 4, + .of_compatible = "motorola,cpcap-led-rgb", + }, { + .name = "cpcap-led", + .id = 5, .of_compatible = "motorola,cpcap-led-adl", }, { .name = "cpcap-led", - .id = 4, + .id = 6, .of_compatible = "motorola,cpcap-led-cp", }, { .name = "cpcap-codec", -- 2.20.1.98.gecbdaf0899