1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Backlight driver for Analog Devices ADP5520/ADP5501 MFD PMICs 4 * 5 * Copyright 2009 Analog Devices Inc. 6 */ 7 8 #include <linux/kernel.h> 9 #include <linux/init.h> 10 #include <linux/platform_device.h> 11 #include <linux/fb.h> 12 #include <linux/backlight.h> 13 #include <linux/mfd/adp5520.h> 14 #include <linux/slab.h> 15 #include <linux/module.h> 16 17 struct adp5520_bl { 18 struct device *master; 19 struct adp5520_backlight_platform_data *pdata; 20 struct mutex lock; 21 unsigned long cached_daylight_max; 22 int id; 23 int current_brightness; 24 }; 25 26 static int adp5520_bl_set(struct backlight_device *bl, int brightness) 27 { 28 struct adp5520_bl *data = bl_get_data(bl); 29 struct device *master = data->master; 30 int ret = 0; 31 32 if (data->pdata->en_ambl_sens) { 33 if ((brightness > 0) && (brightness < ADP5020_MAX_BRIGHTNESS)) { 34 /* Disable Ambient Light auto adjust */ 35 ret |= adp5520_clr_bits(master, ADP5520_BL_CONTROL, 36 ADP5520_BL_AUTO_ADJ); 37 ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX, 38 brightness); 39 } else { 40 /* 41 * MAX_BRIGHTNESS -> Enable Ambient Light auto adjust 42 * restore daylight l3 sysfs brightness 43 */ 44 ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX, 45 data->cached_daylight_max); 46 ret |= adp5520_set_bits(master, ADP5520_BL_CONTROL, 47 ADP5520_BL_AUTO_ADJ); 48 } 49 } else { 50 ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX, brightness); 51 } 52 53 if (data->current_brightness && brightness == 0) 54 ret |= adp5520_set_bits(master, 55 ADP5520_MODE_STATUS, ADP5520_DIM_EN); 56 else if (data->current_brightness == 0 && brightness) 57 ret |= adp5520_clr_bits(master, 58 ADP5520_MODE_STATUS, ADP5520_DIM_EN); 59 60 if (!ret) 61 data->current_brightness = brightness; 62 63 return ret; 64 } 65 66 static int adp5520_bl_update_status(struct backlight_device *bl) 67 { 68 int brightness = bl->props.brightness; 69 70 if (bl->props.power != FB_BLANK_UNBLANK) 71 brightness = 0; 72 73 if (bl->props.fb_blank != FB_BLANK_UNBLANK) 74 brightness = 0; 75 76 return adp5520_bl_set(bl, brightness); 77 } 78 79 static int adp5520_bl_get_brightness(struct backlight_device *bl) 80 { 81 struct adp5520_bl *data = bl_get_data(bl); 82 int error; 83 uint8_t reg_val; 84 85 error = adp5520_read(data->master, ADP5520_BL_VALUE, ®_val); 86 87 return error ? data->current_brightness : reg_val; 88 } 89 90 static const struct backlight_ops adp5520_bl_ops = { 91 .update_status = adp5520_bl_update_status, 92 .get_brightness = adp5520_bl_get_brightness, 93 }; 94 95 static int adp5520_bl_setup(struct backlight_device *bl) 96 { 97 struct adp5520_bl *data = bl_get_data(bl); 98 struct device *master = data->master; 99 struct adp5520_backlight_platform_data *pdata = data->pdata; 100 int ret = 0; 101 102 ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX, 103 pdata->l1_daylight_max); 104 ret |= adp5520_write(master, ADP5520_DAYLIGHT_DIM, 105 pdata->l1_daylight_dim); 106 107 if (pdata->en_ambl_sens) { 108 data->cached_daylight_max = pdata->l1_daylight_max; 109 ret |= adp5520_write(master, ADP5520_OFFICE_MAX, 110 pdata->l2_office_max); 111 ret |= adp5520_write(master, ADP5520_OFFICE_DIM, 112 pdata->l2_office_dim); 113 ret |= adp5520_write(master, ADP5520_DARK_MAX, 114 pdata->l3_dark_max); 115 ret |= adp5520_write(master, ADP5520_DARK_DIM, 116 pdata->l3_dark_dim); 117 ret |= adp5520_write(master, ADP5520_L2_TRIP, 118 pdata->l2_trip); 119 ret |= adp5520_write(master, ADP5520_L2_HYS, 120 pdata->l2_hyst); 121 ret |= adp5520_write(master, ADP5520_L3_TRIP, 122 pdata->l3_trip); 123 ret |= adp5520_write(master, ADP5520_L3_HYS, 124 pdata->l3_hyst); 125 ret |= adp5520_write(master, ADP5520_ALS_CMPR_CFG, 126 ALS_CMPR_CFG_VAL(pdata->abml_filt, 127 ADP5520_L3_EN)); 128 } 129 130 ret |= adp5520_write(master, ADP5520_BL_CONTROL, 131 BL_CTRL_VAL(pdata->fade_led_law, 132 pdata->en_ambl_sens)); 133 134 ret |= adp5520_write(master, ADP5520_BL_FADE, FADE_VAL(pdata->fade_in, 135 pdata->fade_out)); 136 137 ret |= adp5520_set_bits(master, ADP5520_MODE_STATUS, 138 ADP5520_BL_EN | ADP5520_DIM_EN); 139 140 return ret; 141 } 142 143 static ssize_t adp5520_show(struct device *dev, char *buf, int reg) 144 { 145 struct adp5520_bl *data = dev_get_drvdata(dev); 146 int ret; 147 uint8_t reg_val; 148 149 mutex_lock(&data->lock); 150 ret = adp5520_read(data->master, reg, ®_val); 151 mutex_unlock(&data->lock); 152 153 if (ret < 0) 154 return ret; 155 156 return sprintf(buf, "%u\n", reg_val); 157 } 158 159 static ssize_t adp5520_store(struct device *dev, const char *buf, 160 size_t count, int reg) 161 { 162 struct adp5520_bl *data = dev_get_drvdata(dev); 163 unsigned long val; 164 int ret; 165 166 ret = kstrtoul(buf, 10, &val); 167 if (ret) 168 return ret; 169 170 mutex_lock(&data->lock); 171 adp5520_write(data->master, reg, val); 172 mutex_unlock(&data->lock); 173 174 return count; 175 } 176 177 static ssize_t adp5520_bl_dark_max_show(struct device *dev, 178 struct device_attribute *attr, char *buf) 179 { 180 return adp5520_show(dev, buf, ADP5520_DARK_MAX); 181 } 182 183 static ssize_t adp5520_bl_dark_max_store(struct device *dev, 184 struct device_attribute *attr, 185 const char *buf, size_t count) 186 { 187 return adp5520_store(dev, buf, count, ADP5520_DARK_MAX); 188 } 189 static DEVICE_ATTR(dark_max, 0664, adp5520_bl_dark_max_show, 190 adp5520_bl_dark_max_store); 191 192 static ssize_t adp5520_bl_office_max_show(struct device *dev, 193 struct device_attribute *attr, char *buf) 194 { 195 return adp5520_show(dev, buf, ADP5520_OFFICE_MAX); 196 } 197 198 static ssize_t adp5520_bl_office_max_store(struct device *dev, 199 struct device_attribute *attr, 200 const char *buf, size_t count) 201 { 202 return adp5520_store(dev, buf, count, ADP5520_OFFICE_MAX); 203 } 204 static DEVICE_ATTR(office_max, 0664, adp5520_bl_office_max_show, 205 adp5520_bl_office_max_store); 206 207 static ssize_t adp5520_bl_daylight_max_show(struct device *dev, 208 struct device_attribute *attr, char *buf) 209 { 210 return adp5520_show(dev, buf, ADP5520_DAYLIGHT_MAX); 211 } 212 213 static ssize_t adp5520_bl_daylight_max_store(struct device *dev, 214 struct device_attribute *attr, 215 const char *buf, size_t count) 216 { 217 struct adp5520_bl *data = dev_get_drvdata(dev); 218 int ret; 219 220 ret = kstrtoul(buf, 10, &data->cached_daylight_max); 221 if (ret < 0) 222 return ret; 223 224 return adp5520_store(dev, buf, count, ADP5520_DAYLIGHT_MAX); 225 } 226 static DEVICE_ATTR(daylight_max, 0664, adp5520_bl_daylight_max_show, 227 adp5520_bl_daylight_max_store); 228 229 static ssize_t adp5520_bl_dark_dim_show(struct device *dev, 230 struct device_attribute *attr, char *buf) 231 { 232 return adp5520_show(dev, buf, ADP5520_DARK_DIM); 233 } 234 235 static ssize_t adp5520_bl_dark_dim_store(struct device *dev, 236 struct device_attribute *attr, 237 const char *buf, size_t count) 238 { 239 return adp5520_store(dev, buf, count, ADP5520_DARK_DIM); 240 } 241 static DEVICE_ATTR(dark_dim, 0664, adp5520_bl_dark_dim_show, 242 adp5520_bl_dark_dim_store); 243 244 static ssize_t adp5520_bl_office_dim_show(struct device *dev, 245 struct device_attribute *attr, char *buf) 246 { 247 return adp5520_show(dev, buf, ADP5520_OFFICE_DIM); 248 } 249 250 static ssize_t adp5520_bl_office_dim_store(struct device *dev, 251 struct device_attribute *attr, 252 const char *buf, size_t count) 253 { 254 return adp5520_store(dev, buf, count, ADP5520_OFFICE_DIM); 255 } 256 static DEVICE_ATTR(office_dim, 0664, adp5520_bl_office_dim_show, 257 adp5520_bl_office_dim_store); 258 259 static ssize_t adp5520_bl_daylight_dim_show(struct device *dev, 260 struct device_attribute *attr, char *buf) 261 { 262 return adp5520_show(dev, buf, ADP5520_DAYLIGHT_DIM); 263 } 264 265 static ssize_t adp5520_bl_daylight_dim_store(struct device *dev, 266 struct device_attribute *attr, 267 const char *buf, size_t count) 268 { 269 return adp5520_store(dev, buf, count, ADP5520_DAYLIGHT_DIM); 270 } 271 static DEVICE_ATTR(daylight_dim, 0664, adp5520_bl_daylight_dim_show, 272 adp5520_bl_daylight_dim_store); 273 274 static struct attribute *adp5520_bl_attributes[] = { 275 &dev_attr_dark_max.attr, 276 &dev_attr_dark_dim.attr, 277 &dev_attr_office_max.attr, 278 &dev_attr_office_dim.attr, 279 &dev_attr_daylight_max.attr, 280 &dev_attr_daylight_dim.attr, 281 NULL 282 }; 283 284 static const struct attribute_group adp5520_bl_attr_group = { 285 .attrs = adp5520_bl_attributes, 286 }; 287 288 static int adp5520_bl_probe(struct platform_device *pdev) 289 { 290 struct backlight_properties props; 291 struct backlight_device *bl; 292 struct adp5520_bl *data; 293 int ret = 0; 294 295 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 296 if (data == NULL) 297 return -ENOMEM; 298 299 data->master = pdev->dev.parent; 300 data->pdata = dev_get_platdata(&pdev->dev); 301 302 if (data->pdata == NULL) { 303 dev_err(&pdev->dev, "missing platform data\n"); 304 return -ENODEV; 305 } 306 307 data->id = pdev->id; 308 data->current_brightness = 0; 309 310 mutex_init(&data->lock); 311 312 memset(&props, 0, sizeof(struct backlight_properties)); 313 props.type = BACKLIGHT_RAW; 314 props.max_brightness = ADP5020_MAX_BRIGHTNESS; 315 bl = devm_backlight_device_register(&pdev->dev, pdev->name, 316 data->master, data, &adp5520_bl_ops, 317 &props); 318 if (IS_ERR(bl)) { 319 dev_err(&pdev->dev, "failed to register backlight\n"); 320 return PTR_ERR(bl); 321 } 322 323 bl->props.brightness = ADP5020_MAX_BRIGHTNESS; 324 if (data->pdata->en_ambl_sens) 325 ret = sysfs_create_group(&bl->dev.kobj, 326 &adp5520_bl_attr_group); 327 328 if (ret) { 329 dev_err(&pdev->dev, "failed to register sysfs\n"); 330 return ret; 331 } 332 333 platform_set_drvdata(pdev, bl); 334 ret = adp5520_bl_setup(bl); 335 if (ret) { 336 dev_err(&pdev->dev, "failed to setup\n"); 337 if (data->pdata->en_ambl_sens) 338 sysfs_remove_group(&bl->dev.kobj, 339 &adp5520_bl_attr_group); 340 return ret; 341 } 342 343 backlight_update_status(bl); 344 345 return 0; 346 } 347 348 static int adp5520_bl_remove(struct platform_device *pdev) 349 { 350 struct backlight_device *bl = platform_get_drvdata(pdev); 351 struct adp5520_bl *data = bl_get_data(bl); 352 353 adp5520_clr_bits(data->master, ADP5520_MODE_STATUS, ADP5520_BL_EN); 354 355 if (data->pdata->en_ambl_sens) 356 sysfs_remove_group(&bl->dev.kobj, 357 &adp5520_bl_attr_group); 358 359 return 0; 360 } 361 362 #ifdef CONFIG_PM_SLEEP 363 static int adp5520_bl_suspend(struct device *dev) 364 { 365 struct backlight_device *bl = dev_get_drvdata(dev); 366 367 return adp5520_bl_set(bl, 0); 368 } 369 370 static int adp5520_bl_resume(struct device *dev) 371 { 372 struct backlight_device *bl = dev_get_drvdata(dev); 373 374 backlight_update_status(bl); 375 return 0; 376 } 377 #endif 378 379 static SIMPLE_DEV_PM_OPS(adp5520_bl_pm_ops, adp5520_bl_suspend, 380 adp5520_bl_resume); 381 382 static struct platform_driver adp5520_bl_driver = { 383 .driver = { 384 .name = "adp5520-backlight", 385 .pm = &adp5520_bl_pm_ops, 386 }, 387 .probe = adp5520_bl_probe, 388 .remove = adp5520_bl_remove, 389 }; 390 391 module_platform_driver(adp5520_bl_driver); 392 393 MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); 394 MODULE_DESCRIPTION("ADP5520(01) Backlight Driver"); 395 MODULE_LICENSE("GPL"); 396 MODULE_ALIAS("platform:adp5520-backlight"); 397