387 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Backlight driver for Analog Devices ADP5520/ADP5501 MFD PMICs4 *5 * Copyright 2009 Analog Devices Inc.6 */7 8#include <linux/kernel.h>9#include <linux/init.h>10#include <linux/platform_device.h>11#include <linux/fb.h>12#include <linux/backlight.h>13#include <linux/mfd/adp5520.h>14#include <linux/slab.h>15#include <linux/module.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 adjust42 * restore daylight l3 sysfs brightness43 */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 return adp5520_bl_set(bl, backlight_get_brightness(bl));69}70 71static int adp5520_bl_get_brightness(struct backlight_device *bl)72{73 struct adp5520_bl *data = bl_get_data(bl);74 int error;75 uint8_t reg_val;76 77 error = adp5520_read(data->master, ADP5520_BL_VALUE, ®_val);78 79 return error ? data->current_brightness : reg_val;80}81 82static const struct backlight_ops adp5520_bl_ops = {83 .update_status = adp5520_bl_update_status,84 .get_brightness = adp5520_bl_get_brightness,85};86 87static int adp5520_bl_setup(struct backlight_device *bl)88{89 struct adp5520_bl *data = bl_get_data(bl);90 struct device *master = data->master;91 struct adp5520_backlight_platform_data *pdata = data->pdata;92 int ret = 0;93 94 ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX,95 pdata->l1_daylight_max);96 ret |= adp5520_write(master, ADP5520_DAYLIGHT_DIM,97 pdata->l1_daylight_dim);98 99 if (pdata->en_ambl_sens) {100 data->cached_daylight_max = pdata->l1_daylight_max;101 ret |= adp5520_write(master, ADP5520_OFFICE_MAX,102 pdata->l2_office_max);103 ret |= adp5520_write(master, ADP5520_OFFICE_DIM,104 pdata->l2_office_dim);105 ret |= adp5520_write(master, ADP5520_DARK_MAX,106 pdata->l3_dark_max);107 ret |= adp5520_write(master, ADP5520_DARK_DIM,108 pdata->l3_dark_dim);109 ret |= adp5520_write(master, ADP5520_L2_TRIP,110 pdata->l2_trip);111 ret |= adp5520_write(master, ADP5520_L2_HYS,112 pdata->l2_hyst);113 ret |= adp5520_write(master, ADP5520_L3_TRIP,114 pdata->l3_trip);115 ret |= adp5520_write(master, ADP5520_L3_HYS,116 pdata->l3_hyst);117 ret |= adp5520_write(master, ADP5520_ALS_CMPR_CFG,118 ALS_CMPR_CFG_VAL(pdata->abml_filt,119 ADP5520_L3_EN));120 }121 122 ret |= adp5520_write(master, ADP5520_BL_CONTROL,123 BL_CTRL_VAL(pdata->fade_led_law,124 pdata->en_ambl_sens));125 126 ret |= adp5520_write(master, ADP5520_BL_FADE, FADE_VAL(pdata->fade_in,127 pdata->fade_out));128 129 ret |= adp5520_set_bits(master, ADP5520_MODE_STATUS,130 ADP5520_BL_EN | ADP5520_DIM_EN);131 132 return ret;133}134 135static ssize_t adp5520_show(struct device *dev, char *buf, int reg)136{137 struct adp5520_bl *data = dev_get_drvdata(dev);138 int ret;139 uint8_t reg_val;140 141 mutex_lock(&data->lock);142 ret = adp5520_read(data->master, reg, ®_val);143 mutex_unlock(&data->lock);144 145 if (ret < 0)146 return ret;147 148 return sprintf(buf, "%u\n", reg_val);149}150 151static ssize_t adp5520_store(struct device *dev, const char *buf,152 size_t count, int reg)153{154 struct adp5520_bl *data = dev_get_drvdata(dev);155 unsigned long val;156 int ret;157 158 ret = kstrtoul(buf, 10, &val);159 if (ret)160 return ret;161 162 mutex_lock(&data->lock);163 adp5520_write(data->master, reg, val);164 mutex_unlock(&data->lock);165 166 return count;167}168 169static ssize_t adp5520_bl_dark_max_show(struct device *dev,170 struct device_attribute *attr, char *buf)171{172 return adp5520_show(dev, buf, ADP5520_DARK_MAX);173}174 175static ssize_t adp5520_bl_dark_max_store(struct device *dev,176 struct device_attribute *attr,177 const char *buf, size_t count)178{179 return adp5520_store(dev, buf, count, ADP5520_DARK_MAX);180}181static DEVICE_ATTR(dark_max, 0664, adp5520_bl_dark_max_show,182 adp5520_bl_dark_max_store);183 184static ssize_t adp5520_bl_office_max_show(struct device *dev,185 struct device_attribute *attr, char *buf)186{187 return adp5520_show(dev, buf, ADP5520_OFFICE_MAX);188}189 190static ssize_t adp5520_bl_office_max_store(struct device *dev,191 struct device_attribute *attr,192 const char *buf, size_t count)193{194 return adp5520_store(dev, buf, count, ADP5520_OFFICE_MAX);195}196static DEVICE_ATTR(office_max, 0664, adp5520_bl_office_max_show,197 adp5520_bl_office_max_store);198 199static ssize_t adp5520_bl_daylight_max_show(struct device *dev,200 struct device_attribute *attr, char *buf)201{202 return adp5520_show(dev, buf, ADP5520_DAYLIGHT_MAX);203}204 205static ssize_t adp5520_bl_daylight_max_store(struct device *dev,206 struct device_attribute *attr,207 const char *buf, size_t count)208{209 struct adp5520_bl *data = dev_get_drvdata(dev);210 int ret;211 212 ret = kstrtoul(buf, 10, &data->cached_daylight_max);213 if (ret < 0)214 return ret;215 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 NULL274};275 276static const struct attribute_group adp5520_bl_attr_group = {277 .attrs = adp5520_bl_attributes,278};279 280static int 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 = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);288 if (data == NULL)289 return -ENOMEM;290 291 data->master = pdev->dev.parent;292 data->pdata = dev_get_platdata(&pdev->dev);293 294 if (data->pdata == NULL) {295 dev_err(&pdev->dev, "missing platform data\n");296 return -ENODEV;297 }298 299 data->id = pdev->id;300 data->current_brightness = 0;301 302 mutex_init(&data->lock);303 304 memset(&props, 0, sizeof(struct backlight_properties));305 props.type = BACKLIGHT_RAW;306 props.max_brightness = ADP5020_MAX_BRIGHTNESS;307 bl = devm_backlight_device_register(&pdev->dev, pdev->name,308 data->master, data, &adp5520_bl_ops,309 &props);310 if (IS_ERR(bl)) {311 dev_err(&pdev->dev, "failed to register backlight\n");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 return ret;323 }324 325 platform_set_drvdata(pdev, bl);326 ret = adp5520_bl_setup(bl);327 if (ret) {328 dev_err(&pdev->dev, "failed to setup\n");329 if (data->pdata->en_ambl_sens)330 sysfs_remove_group(&bl->dev.kobj,331 &adp5520_bl_attr_group);332 return ret;333 }334 335 backlight_update_status(bl);336 337 return 0;338}339 340static void adp5520_bl_remove(struct platform_device *pdev)341{342 struct backlight_device *bl = platform_get_drvdata(pdev);343 struct adp5520_bl *data = bl_get_data(bl);344 345 adp5520_clr_bits(data->master, ADP5520_MODE_STATUS, ADP5520_BL_EN);346 347 if (data->pdata->en_ambl_sens)348 sysfs_remove_group(&bl->dev.kobj,349 &adp5520_bl_attr_group);350}351 352#ifdef CONFIG_PM_SLEEP353static int adp5520_bl_suspend(struct device *dev)354{355 struct backlight_device *bl = dev_get_drvdata(dev);356 357 return adp5520_bl_set(bl, 0);358}359 360static int adp5520_bl_resume(struct device *dev)361{362 struct backlight_device *bl = dev_get_drvdata(dev);363 364 backlight_update_status(bl);365 return 0;366}367#endif368 369static SIMPLE_DEV_PM_OPS(adp5520_bl_pm_ops, adp5520_bl_suspend,370 adp5520_bl_resume);371 372static struct platform_driver adp5520_bl_driver = {373 .driver = {374 .name = "adp5520-backlight",375 .pm = &adp5520_bl_pm_ops,376 },377 .probe = adp5520_bl_probe,378 .remove_new = adp5520_bl_remove,379};380 381module_platform_driver(adp5520_bl_driver);382 383MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");384MODULE_DESCRIPTION("ADP5520(01) Backlight Driver");385MODULE_LICENSE("GPL");386MODULE_ALIAS("platform:adp5520-backlight");387