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