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 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 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 78static 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 89static const struct backlight_ops adp5520_bl_ops = { 90 .update_status = adp5520_bl_update_status, 91 .get_brightness = adp5520_bl_get_brightness, 92}; 93 94static 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 142static 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 155static 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 173static 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 179static 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} 185static DEVICE_ATTR(dark_max, 0664, adp5520_bl_dark_max_show, 186 adp5520_bl_dark_max_store); 187 188static 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 194static 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} 200static DEVICE_ATTR(office_max, 0664, adp5520_bl_office_max_show, 201 adp5520_bl_office_max_store); 202 203static 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 209static 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} 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 __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 333static 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 351static 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 358static 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 370static 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 381static int __init adp5520_bl_init(void) 382{ 383 return platform_driver_register(&adp5520_bl_driver); 384} 385module_init(adp5520_bl_init); 386 387static void __exit adp5520_bl_exit(void) 388{ 389 platform_driver_unregister(&adp5520_bl_driver); 390} 391module_exit(adp5520_bl_exit); 392 393MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); 394MODULE_DESCRIPTION("ADP5520(01) Backlight Driver"); 395MODULE_LICENSE("GPL"); 396MODULE_ALIAS("platform:adp5520-backlight"); 397