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/platform_device.h> 13 14 struct 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 25 static 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 else 33 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 41 static 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 54 static 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 else 62 led_bl_power_off(priv); 63 64 return 0; 65 } 66 67 static const struct backlight_ops led_bl_ops = { 68 .update_status = led_bl_update_status, 69 }; 70 71 static 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 123 static 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 brightness 153 * level 154 */ 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 175 static 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) ? FB_BLANK_POWERDOWN : 204 FB_BLANK_UNBLANK; 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 led_sysfs_disable(priv->leds[i]); 214 215 backlight_update_status(priv->bl_dev); 216 217 return 0; 218 } 219 220 static void led_bl_remove(struct platform_device *pdev) 221 { 222 struct led_bl_data *priv = platform_get_drvdata(pdev); 223 struct backlight_device *bl = priv->bl_dev; 224 int i; 225 226 backlight_device_unregister(bl); 227 228 led_bl_power_off(priv); 229 for (i = 0; i < priv->nb_leds; i++) 230 led_sysfs_enable(priv->leds[i]); 231 } 232 233 static const struct of_device_id led_bl_of_match[] = { 234 { .compatible = "led-backlight" }, 235 { } 236 }; 237 238 MODULE_DEVICE_TABLE(of, led_bl_of_match); 239 240 static struct platform_driver led_bl_driver = { 241 .driver = { 242 .name = "led-backlight", 243 .of_match_table = of_match_ptr(led_bl_of_match), 244 }, 245 .probe = led_bl_probe, 246 .remove_new = led_bl_remove, 247 }; 248 249 module_platform_driver(led_bl_driver); 250 251 MODULE_DESCRIPTION("LED based Backlight Driver"); 252 MODULE_LICENSE("GPL"); 253 MODULE_ALIAS("platform:led-backlight"); 254