257 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (C) 2015-2019 Texas Instruments Incorporated - http://www.ti.com/4 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>5 *6 * Based on pwm_bl.c7 */8 9#include <linux/backlight.h>10#include <linux/leds.h>11#include <linux/module.h>12#include <linux/platform_device.h>13 14struct led_bl_data {15 struct device *dev;16 struct backlight_device *bl_dev;17 struct led_classdev **leds;18 bool enabled;19 int nb_leds;20 unsigned int *levels;21 unsigned int default_brightness;22 unsigned int max_brightness;23};24 25static void led_bl_set_brightness(struct led_bl_data *priv, int level)26{27 int i;28 int bkl_brightness;29 30 if (priv->levels)31 bkl_brightness = priv->levels[level];32 else33 bkl_brightness = level;34 35 for (i = 0; i < priv->nb_leds; i++)36 led_set_brightness(priv->leds[i], bkl_brightness);37 38 priv->enabled = true;39}40 41static void led_bl_power_off(struct led_bl_data *priv)42{43 int i;44 45 if (!priv->enabled)46 return;47 48 for (i = 0; i < priv->nb_leds; i++)49 led_set_brightness(priv->leds[i], LED_OFF);50 51 priv->enabled = false;52}53 54static int led_bl_update_status(struct backlight_device *bl)55{56 struct led_bl_data *priv = bl_get_data(bl);57 int brightness = backlight_get_brightness(bl);58 59 if (brightness > 0)60 led_bl_set_brightness(priv, brightness);61 else62 led_bl_power_off(priv);63 64 return 0;65}66 67static const struct backlight_ops led_bl_ops = {68 .update_status = led_bl_update_status,69};70 71static int led_bl_get_leds(struct device *dev,72 struct led_bl_data *priv)73{74 int i, nb_leds, ret;75 struct device_node *node = dev->of_node;76 struct led_classdev **leds;77 unsigned int max_brightness;78 unsigned int default_brightness;79 80 ret = of_count_phandle_with_args(node, "leds", NULL);81 if (ret < 0) {82 dev_err(dev, "Unable to get led count\n");83 return -EINVAL;84 }85 86 nb_leds = ret;87 if (nb_leds < 1) {88 dev_err(dev, "At least one LED must be specified!\n");89 return -EINVAL;90 }91 92 leds = devm_kzalloc(dev, sizeof(struct led_classdev *) * nb_leds,93 GFP_KERNEL);94 if (!leds)95 return -ENOMEM;96 97 for (i = 0; i < nb_leds; i++) {98 leds[i] = devm_of_led_get(dev, i);99 if (IS_ERR(leds[i]))100 return PTR_ERR(leds[i]);101 }102 103 /* check that the LEDs all have the same brightness range */104 max_brightness = leds[0]->max_brightness;105 for (i = 1; i < nb_leds; i++) {106 if (max_brightness != leds[i]->max_brightness) {107 dev_err(dev, "LEDs must have identical ranges\n");108 return -EINVAL;109 }110 }111 112 /* get the default brightness from the first LED from the list */113 default_brightness = leds[0]->brightness;114 115 priv->nb_leds = nb_leds;116 priv->leds = leds;117 priv->max_brightness = max_brightness;118 priv->default_brightness = default_brightness;119 120 return 0;121}122 123static int led_bl_parse_levels(struct device *dev,124 struct led_bl_data *priv)125{126 struct device_node *node = dev->of_node;127 int num_levels;128 u32 value;129 int ret;130 131 if (!node)132 return -ENODEV;133 134 num_levels = of_property_count_u32_elems(node, "brightness-levels");135 if (num_levels > 1) {136 int i;137 unsigned int db;138 u32 *levels = NULL;139 140 levels = devm_kzalloc(dev, sizeof(u32) * num_levels,141 GFP_KERNEL);142 if (!levels)143 return -ENOMEM;144 145 ret = of_property_read_u32_array(node, "brightness-levels",146 levels,147 num_levels);148 if (ret < 0)149 return ret;150 151 /*152 * Try to map actual LED brightness to backlight brightness153 * level154 */155 db = priv->default_brightness;156 for (i = 0 ; i < num_levels; i++) {157 if ((i && db > levels[i-1]) && db <= levels[i])158 break;159 }160 priv->default_brightness = i;161 priv->max_brightness = num_levels - 1;162 priv->levels = levels;163 } else if (num_levels >= 0)164 dev_warn(dev, "Not enough levels defined\n");165 166 ret = of_property_read_u32(node, "default-brightness-level", &value);167 if (!ret && value <= priv->max_brightness)168 priv->default_brightness = value;169 else if (!ret && value > priv->max_brightness)170 dev_warn(dev, "Invalid default brightness. Ignoring it\n");171 172 return 0;173}174 175static int led_bl_probe(struct platform_device *pdev)176{177 struct backlight_properties props;178 struct led_bl_data *priv;179 int ret, i;180 181 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);182 if (!priv)183 return -ENOMEM;184 185 platform_set_drvdata(pdev, priv);186 187 priv->dev = &pdev->dev;188 189 ret = led_bl_get_leds(&pdev->dev, priv);190 if (ret)191 return ret;192 193 ret = led_bl_parse_levels(&pdev->dev, priv);194 if (ret < 0) {195 dev_err(&pdev->dev, "Failed to parse DT data\n");196 return ret;197 }198 199 memset(&props, 0, sizeof(struct backlight_properties));200 props.type = BACKLIGHT_RAW;201 props.max_brightness = priv->max_brightness;202 props.brightness = priv->default_brightness;203 props.power = (priv->default_brightness > 0) ? BACKLIGHT_POWER_OFF :204 BACKLIGHT_POWER_ON;205 priv->bl_dev = backlight_device_register(dev_name(&pdev->dev),206 &pdev->dev, priv, &led_bl_ops, &props);207 if (IS_ERR(priv->bl_dev)) {208 dev_err(&pdev->dev, "Failed to register backlight\n");209 return PTR_ERR(priv->bl_dev);210 }211 212 for (i = 0; i < priv->nb_leds; i++) {213 mutex_lock(&priv->leds[i]->led_access);214 led_sysfs_disable(priv->leds[i]);215 mutex_unlock(&priv->leds[i]->led_access);216 }217 218 backlight_update_status(priv->bl_dev);219 220 return 0;221}222 223static void led_bl_remove(struct platform_device *pdev)224{225 struct led_bl_data *priv = platform_get_drvdata(pdev);226 struct backlight_device *bl = priv->bl_dev;227 int i;228 229 backlight_device_unregister(bl);230 231 led_bl_power_off(priv);232 for (i = 0; i < priv->nb_leds; i++)233 led_sysfs_enable(priv->leds[i]);234}235 236static const struct of_device_id led_bl_of_match[] = {237 { .compatible = "led-backlight" },238 { }239};240 241MODULE_DEVICE_TABLE(of, led_bl_of_match);242 243static struct platform_driver led_bl_driver = {244 .driver = {245 .name = "led-backlight",246 .of_match_table = led_bl_of_match,247 },248 .probe = led_bl_probe,249 .remove_new = led_bl_remove,250};251 252module_platform_driver(led_bl_driver);253 254MODULE_DESCRIPTION("LED based Backlight Driver");255MODULE_LICENSE("GPL");256MODULE_ALIAS("platform:led-backlight");257