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 = bl->props.brightness; 58 59 if (bl->props.power != FB_BLANK_UNBLANK || 60 bl->props.fb_blank != FB_BLANK_UNBLANK || 61 bl->props.state & BL_CORE_FBBLANK) 62 brightness = 0; 63 64 if (brightness > 0) 65 led_bl_set_brightness(priv, brightness); 66 else 67 led_bl_power_off(priv); 68 69 return 0; 70 } 71 72 static const struct backlight_ops led_bl_ops = { 73 .update_status = led_bl_update_status, 74 }; 75 76 static int led_bl_get_leds(struct device *dev, 77 struct led_bl_data *priv) 78 { 79 int i, nb_leds, ret; 80 struct device_node *node = dev->of_node; 81 struct led_classdev **leds; 82 unsigned int max_brightness; 83 unsigned int default_brightness; 84 85 ret = of_count_phandle_with_args(node, "leds", NULL); 86 if (ret < 0) { 87 dev_err(dev, "Unable to get led count\n"); 88 return -EINVAL; 89 } 90 91 nb_leds = ret; 92 if (nb_leds < 1) { 93 dev_err(dev, "At least one LED must be specified!\n"); 94 return -EINVAL; 95 } 96 97 leds = devm_kzalloc(dev, sizeof(struct led_classdev *) * nb_leds, 98 GFP_KERNEL); 99 if (!leds) 100 return -ENOMEM; 101 102 for (i = 0; i < nb_leds; i++) { 103 leds[i] = devm_of_led_get(dev, i); 104 if (IS_ERR(leds[i])) 105 return PTR_ERR(leds[i]); 106 } 107 108 /* check that the LEDs all have the same brightness range */ 109 max_brightness = leds[0]->max_brightness; 110 for (i = 1; i < nb_leds; i++) { 111 if (max_brightness != leds[i]->max_brightness) { 112 dev_err(dev, "LEDs must have identical ranges\n"); 113 return -EINVAL; 114 } 115 } 116 117 /* get the default brightness from the first LED from the list */ 118 default_brightness = leds[0]->brightness; 119 120 priv->nb_leds = nb_leds; 121 priv->leds = leds; 122 priv->max_brightness = max_brightness; 123 priv->default_brightness = default_brightness; 124 125 return 0; 126 } 127 128 static int led_bl_parse_levels(struct device *dev, 129 struct led_bl_data *priv) 130 { 131 struct device_node *node = dev->of_node; 132 int num_levels; 133 u32 value; 134 int ret; 135 136 if (!node) 137 return -ENODEV; 138 139 num_levels = of_property_count_u32_elems(node, "brightness-levels"); 140 if (num_levels > 1) { 141 int i; 142 unsigned int db; 143 u32 *levels = NULL; 144 145 levels = devm_kzalloc(dev, sizeof(u32) * num_levels, 146 GFP_KERNEL); 147 if (!levels) 148 return -ENOMEM; 149 150 ret = of_property_read_u32_array(node, "brightness-levels", 151 levels, 152 num_levels); 153 if (ret < 0) 154 return ret; 155 156 /* 157 * Try to map actual LED brightness to backlight brightness 158 * level 159 */ 160 db = priv->default_brightness; 161 for (i = 0 ; i < num_levels; i++) { 162 if ((i && db > levels[i-1]) && db <= levels[i]) 163 break; 164 } 165 priv->default_brightness = i; 166 priv->max_brightness = num_levels - 1; 167 priv->levels = levels; 168 } else if (num_levels >= 0) 169 dev_warn(dev, "Not enough levels defined\n"); 170 171 ret = of_property_read_u32(node, "default-brightness-level", &value); 172 if (!ret && value <= priv->max_brightness) 173 priv->default_brightness = value; 174 else if (!ret && value > priv->max_brightness) 175 dev_warn(dev, "Invalid default brightness. Ignoring it\n"); 176 177 return 0; 178 } 179 180 static int led_bl_probe(struct platform_device *pdev) 181 { 182 struct backlight_properties props; 183 struct led_bl_data *priv; 184 int ret, i; 185 186 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 187 if (!priv) 188 return -ENOMEM; 189 190 platform_set_drvdata(pdev, priv); 191 192 priv->dev = &pdev->dev; 193 194 ret = led_bl_get_leds(&pdev->dev, priv); 195 if (ret) 196 return ret; 197 198 ret = led_bl_parse_levels(&pdev->dev, priv); 199 if (ret < 0) { 200 dev_err(&pdev->dev, "Failed to parse DT data\n"); 201 return ret; 202 } 203 204 memset(&props, 0, sizeof(struct backlight_properties)); 205 props.type = BACKLIGHT_RAW; 206 props.max_brightness = priv->max_brightness; 207 props.brightness = priv->default_brightness; 208 props.power = (priv->default_brightness > 0) ? FB_BLANK_POWERDOWN : 209 FB_BLANK_UNBLANK; 210 priv->bl_dev = backlight_device_register(dev_name(&pdev->dev), 211 &pdev->dev, priv, &led_bl_ops, &props); 212 if (IS_ERR(priv->bl_dev)) { 213 dev_err(&pdev->dev, "Failed to register backlight\n"); 214 return PTR_ERR(priv->bl_dev); 215 } 216 217 for (i = 0; i < priv->nb_leds; i++) 218 led_sysfs_disable(priv->leds[i]); 219 220 backlight_update_status(priv->bl_dev); 221 222 return 0; 223 } 224 225 static int led_bl_remove(struct platform_device *pdev) 226 { 227 struct led_bl_data *priv = platform_get_drvdata(pdev); 228 struct backlight_device *bl = priv->bl_dev; 229 int i; 230 231 backlight_device_unregister(bl); 232 233 led_bl_power_off(priv); 234 for (i = 0; i < priv->nb_leds; i++) 235 led_sysfs_enable(priv->leds[i]); 236 237 return 0; 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 = of_match_ptr(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