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