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