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 215 strict_strtoul(buf, 10, &data->cached_daylight_max); 216 return adp5520_store(dev, buf, count, ADP5520_DAYLIGHT_MAX); 217 } 218 static DEVICE_ATTR(daylight_max, 0664, adp5520_bl_daylight_max_show, 219 adp5520_bl_daylight_max_store); 220 221 static 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 227 static 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 } 233 static DEVICE_ATTR(dark_dim, 0664, adp5520_bl_dark_dim_show, 234 adp5520_bl_dark_dim_store); 235 236 static 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 242 static 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 } 248 static DEVICE_ATTR(office_dim, 0664, adp5520_bl_office_dim_show, 249 adp5520_bl_office_dim_store); 250 251 static 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 257 static 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 } 263 static DEVICE_ATTR(daylight_dim, 0664, adp5520_bl_daylight_dim_show, 264 adp5520_bl_daylight_dim_store); 265 266 static 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 276 static const struct attribute_group adp5520_bl_attr_group = { 277 .attrs = adp5520_bl_attributes, 278 }; 279 280 static int __devinit 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 = kzalloc(sizeof(*data), GFP_KERNEL); 288 if (data == NULL) 289 return -ENOMEM; 290 291 data->master = pdev->dev.parent; 292 data->pdata = pdev->dev.platform_data; 293 294 if (data->pdata == NULL) { 295 dev_err(&pdev->dev, "missing platform data\n"); 296 kfree(data); 297 return -ENODEV; 298 } 299 300 data->id = pdev->id; 301 data->current_brightness = 0; 302 303 mutex_init(&data->lock); 304 305 memset(&props, 0, sizeof(struct backlight_properties)); 306 props.max_brightness = ADP5020_MAX_BRIGHTNESS; 307 bl = backlight_device_register(pdev->name, data->master, data, 308 &adp5520_bl_ops, &props); 309 if (IS_ERR(bl)) { 310 dev_err(&pdev->dev, "failed to register backlight\n"); 311 kfree(data); 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 backlight_device_unregister(bl); 323 kfree(data); 324 } 325 326 platform_set_drvdata(pdev, bl); 327 ret |= adp5520_bl_setup(bl); 328 backlight_update_status(bl); 329 330 return ret; 331 } 332 333 static int __devexit adp5520_bl_remove(struct platform_device *pdev) 334 { 335 struct backlight_device *bl = platform_get_drvdata(pdev); 336 struct adp5520_bl *data = bl_get_data(bl); 337 338 adp5520_clr_bits(data->master, ADP5520_MODE_STATUS, ADP5520_BL_EN); 339 340 if (data->pdata->en_ambl_sens) 341 sysfs_remove_group(&bl->dev.kobj, 342 &adp5520_bl_attr_group); 343 344 backlight_device_unregister(bl); 345 kfree(data); 346 347 return 0; 348 } 349 350 #ifdef CONFIG_PM 351 static int adp5520_bl_suspend(struct platform_device *pdev, 352 pm_message_t state) 353 { 354 struct backlight_device *bl = platform_get_drvdata(pdev); 355 return adp5520_bl_set(bl, 0); 356 } 357 358 static int adp5520_bl_resume(struct platform_device *pdev) 359 { 360 struct backlight_device *bl = platform_get_drvdata(pdev); 361 362 backlight_update_status(bl); 363 return 0; 364 } 365 #else 366 #define adp5520_bl_suspend NULL 367 #define adp5520_bl_resume NULL 368 #endif 369 370 static struct platform_driver adp5520_bl_driver = { 371 .driver = { 372 .name = "adp5520-backlight", 373 .owner = THIS_MODULE, 374 }, 375 .probe = adp5520_bl_probe, 376 .remove = __devexit_p(adp5520_bl_remove), 377 .suspend = adp5520_bl_suspend, 378 .resume = adp5520_bl_resume, 379 }; 380 381 static int __init adp5520_bl_init(void) 382 { 383 return platform_driver_register(&adp5520_bl_driver); 384 } 385 module_init(adp5520_bl_init); 386 387 static void __exit adp5520_bl_exit(void) 388 { 389 platform_driver_unregister(&adp5520_bl_driver); 390 } 391 module_exit(adp5520_bl_exit); 392 393 MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); 394 MODULE_DESCRIPTION("ADP5520(01) Backlight Driver"); 395 MODULE_LICENSE("GPL"); 396 MODULE_ALIAS("platform:adp5520-backlight"); 397