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