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