159243Sobrien// SPDX-License-Identifier: GPL-2.0-only 259243Sobrien/* 359243Sobrien * TI LP8788 MFD - backlight driver 459243Sobrien * 559243Sobrien * Copyright 2012 Texas Instruments 659243Sobrien * 759243Sobrien * Author: Milo(Woogyom) Kim <milo.kim@ti.com> 859243Sobrien */ 959243Sobrien 1059243Sobrien#include <linux/backlight.h> 1159243Sobrien#include <linux/err.h> 1259243Sobrien#include <linux/mfd/lp8788.h> 1359243Sobrien#include <linux/module.h> 1459243Sobrien#include <linux/platform_device.h> 1559243Sobrien#include <linux/pwm.h> 1659243Sobrien#include <linux/slab.h> 17100616Smp 1859243Sobrien/* Register address */ 1959243Sobrien#define LP8788_BL_CONFIG 0x96 2059243Sobrien#define LP8788_BL_EN BIT(0) 2159243Sobrien#define LP8788_BL_PWM_INPUT_EN BIT(5) 2259243Sobrien#define LP8788_BL_FULLSCALE_SHIFT 2 2359243Sobrien#define LP8788_BL_DIM_MODE_SHIFT 1 2459243Sobrien#define LP8788_BL_PWM_POLARITY_SHIFT 6 2559243Sobrien 2659243Sobrien#define LP8788_BL_BRIGHTNESS 0x97 2759243Sobrien 2859243Sobrien#define LP8788_BL_RAMP 0x98 2959243Sobrien#define LP8788_BL_RAMP_RISE_SHIFT 4 3059243Sobrien 3159243Sobrien#define MAX_BRIGHTNESS 127 3259243Sobrien#define DEFAULT_BL_NAME "lcd-backlight" 3359243Sobrien 3459243Sobrienstruct lp8788_bl_config { 3559243Sobrien enum lp8788_bl_ctrl_mode bl_mode; 3669408Sache enum lp8788_bl_dim_mode dim_mode; 3759243Sobrien enum lp8788_bl_full_scale_current full_scale; 3869408Sache enum lp8788_bl_ramp_step rise_time; 3959243Sobrien enum lp8788_bl_ramp_step fall_time; 4059243Sobrien enum pwm_polarity pwm_pol; 4159243Sobrien}; 4259243Sobrien 4359243Sobrienstruct lp8788_bl { 4459243Sobrien struct lp8788 *lp; 4559243Sobrien struct backlight_device *bl_dev; 4659243Sobrien struct lp8788_backlight_platform_data *pdata; 4759243Sobrien enum lp8788_bl_ctrl_mode mode; 4859243Sobrien struct pwm_device *pwm; 49231990Smp}; 5059243Sobrien 51145479Smpstatic struct lp8788_bl_config default_bl_config = { 5259243Sobrien .bl_mode = LP8788_BL_REGISTER_ONLY, 5359243Sobrien .dim_mode = LP8788_DIM_EXPONENTIAL, 5459243Sobrien .full_scale = LP8788_FULLSCALE_1900uA, 55167465Smp .rise_time = LP8788_RAMP_8192us, 5659243Sobrien .fall_time = LP8788_RAMP_8192us, 57167465Smp .pwm_pol = PWM_POLARITY_NORMAL, 58167465Smp}; 59167465Smp 6059243Sobrienstatic inline bool is_brightness_ctrl_by_pwm(enum lp8788_bl_ctrl_mode mode) 6159243Sobrien{ 6259243Sobrien return mode == LP8788_BL_COMB_PWM_BASED; 6359243Sobrien} 6459243Sobrien 6559243Sobrienstatic inline bool is_brightness_ctrl_by_register(enum lp8788_bl_ctrl_mode mode) 6659243Sobrien{ 6759243Sobrien return mode == LP8788_BL_REGISTER_ONLY || 6859243Sobrien mode == LP8788_BL_COMB_REGISTER_BASED; 6959243Sobrien} 7059243Sobrien 7159243Sobrienstatic int lp8788_backlight_configure(struct lp8788_bl *bl) 7259243Sobrien{ 7359243Sobrien struct lp8788_backlight_platform_data *pdata = bl->pdata; 7459243Sobrien struct lp8788_bl_config *cfg = &default_bl_config; 7559243Sobrien int ret; 7659243Sobrien u8 val; 7759243Sobrien 7859243Sobrien /* 7959243Sobrien * Update chip configuration if platform data exists, 8059243Sobrien * otherwise use the default settings. 8159243Sobrien */ 8259243Sobrien if (pdata) { 83167465Smp cfg->bl_mode = pdata->bl_mode; 84167465Smp cfg->dim_mode = pdata->dim_mode; 8559243Sobrien cfg->full_scale = pdata->full_scale; 86145479Smp cfg->rise_time = pdata->rise_time; 87167465Smp cfg->fall_time = pdata->fall_time; 88167465Smp cfg->pwm_pol = pdata->pwm_pol; 8959243Sobrien } 90167465Smp 91167465Smp /* Brightness ramp up/down */ 9259243Sobrien val = (cfg->rise_time << LP8788_BL_RAMP_RISE_SHIFT) | cfg->fall_time; 9359243Sobrien ret = lp8788_write_byte(bl->lp, LP8788_BL_RAMP, val); 9459243Sobrien if (ret) 9559243Sobrien return ret; 9659243Sobrien 9759243Sobrien /* Fullscale current setting */ 9859243Sobrien val = (cfg->full_scale << LP8788_BL_FULLSCALE_SHIFT) | 9959243Sobrien (cfg->dim_mode << LP8788_BL_DIM_MODE_SHIFT); 10059243Sobrien 10159243Sobrien /* Brightness control mode */ 10259243Sobrien switch (cfg->bl_mode) { 10359243Sobrien case LP8788_BL_REGISTER_ONLY: 10469408Sache val |= LP8788_BL_EN; 10559243Sobrien break; 10659243Sobrien case LP8788_BL_COMB_PWM_BASED: 10759243Sobrien case LP8788_BL_COMB_REGISTER_BASED: 10859243Sobrien val |= LP8788_BL_EN | LP8788_BL_PWM_INPUT_EN | 10959243Sobrien (cfg->pwm_pol << LP8788_BL_PWM_POLARITY_SHIFT); 11059243Sobrien break; 11159243Sobrien default: 11259243Sobrien dev_err(bl->lp->dev, "invalid mode: %d\n", cfg->bl_mode); 11359243Sobrien return -EINVAL; 11459243Sobrien } 11559243Sobrien 11659243Sobrien bl->mode = cfg->bl_mode; 11769408Sache 11859243Sobrien return lp8788_write_byte(bl->lp, LP8788_BL_CONFIG, val); 11959243Sobrien} 12059243Sobrien 12159243Sobrienstatic void lp8788_pwm_ctrl(struct lp8788_bl *bl, int br, int max_br) 12259243Sobrien{ 12359243Sobrien unsigned int period; 12459243Sobrien unsigned int duty; 12559243Sobrien struct device *dev; 126167465Smp struct pwm_device *pwm; 12759243Sobrien 12859243Sobrien if (!bl->pdata) 12959243Sobrien return; 130167465Smp 131167465Smp period = bl->pdata->period_ns; 132167465Smp duty = br * period / max_br; 133167465Smp dev = bl->lp->dev; 13459243Sobrien 135167465Smp /* request PWM device with the consumer name */ 136167465Smp if (!bl->pwm) { 137167465Smp pwm = devm_pwm_get(dev, LP8788_DEV_BACKLIGHT); 138167465Smp if (IS_ERR(pwm)) { 139167465Smp dev_err(dev, "can not get PWM device\n"); 140167465Smp return; 141167465Smp } 142167465Smp 143167465Smp bl->pwm = pwm; 144167465Smp 145167465Smp /* 146167465Smp * FIXME: pwm_apply_args() should be removed when switching to 147167465Smp * the atomic PWM API. 148167465Smp */ 149167465Smp pwm_apply_args(pwm); 150167465Smp } 151167465Smp 152167465Smp pwm_config(bl->pwm, duty, period); 153167465Smp if (duty) 15459243Sobrien pwm_enable(bl->pwm); 155167465Smp else 15659243Sobrien pwm_disable(bl->pwm); 157167465Smp} 15859243Sobrien 159167465Smpstatic int lp8788_bl_update_status(struct backlight_device *bl_dev) 160167465Smp{ 16169408Sache struct lp8788_bl *bl = bl_get_data(bl_dev); 162167465Smp enum lp8788_bl_ctrl_mode mode = bl->mode; 16369408Sache 164167465Smp if (bl_dev->props.state & BL_CORE_SUSPENDED) 16559243Sobrien bl_dev->props.brightness = 0; 16659243Sobrien 16759243Sobrien if (is_brightness_ctrl_by_pwm(mode)) { 16859243Sobrien int brt = bl_dev->props.brightness; 16959243Sobrien int max = bl_dev->props.max_brightness; 17059243Sobrien 171167465Smp lp8788_pwm_ctrl(bl, brt, max); 17259243Sobrien } else if (is_brightness_ctrl_by_register(mode)) { 17359243Sobrien u8 brt = bl_dev->props.brightness; 17459243Sobrien 17559243Sobrien lp8788_write_byte(bl->lp, LP8788_BL_BRIGHTNESS, brt); 17659243Sobrien } 17759243Sobrien 17859243Sobrien return 0; 17959243Sobrien} 18059243Sobrien 18159243Sobrienstatic const struct backlight_ops lp8788_bl_ops = { 18259243Sobrien .options = BL_CORE_SUSPENDRESUME, 18359243Sobrien .update_status = lp8788_bl_update_status, 18459243Sobrien}; 18559243Sobrien 18659243Sobrienstatic int lp8788_backlight_register(struct lp8788_bl *bl) 18759243Sobrien{ 18859243Sobrien struct backlight_device *bl_dev; 189167465Smp struct backlight_properties props; 190167465Smp struct lp8788_backlight_platform_data *pdata = bl->pdata; 19159243Sobrien int init_brt; 19259243Sobrien char *name; 19359243Sobrien 19459243Sobrien memset(&props, 0, sizeof(struct backlight_properties)); 19559243Sobrien props.type = BACKLIGHT_PLATFORM; 19659243Sobrien props.max_brightness = MAX_BRIGHTNESS; 19759243Sobrien 19859243Sobrien /* Initial brightness */ 19959243Sobrien if (pdata) 20059243Sobrien init_brt = min_t(int, pdata->initial_brightness, 20159243Sobrien props.max_brightness); 20259243Sobrien else 20359243Sobrien init_brt = 0; 20459243Sobrien 20559243Sobrien props.brightness = init_brt; 20659243Sobrien 20759243Sobrien /* Backlight device name */ 20859243Sobrien if (!pdata || !pdata->name) 20959243Sobrien name = DEFAULT_BL_NAME; 21059243Sobrien else 211316957Sdchagin name = pdata->name; 212316957Sdchagin 213167465Smp bl_dev = backlight_device_register(name, bl->lp->dev, bl, 214316957Sdchagin &lp8788_bl_ops, &props); 215316957Sdchagin if (IS_ERR(bl_dev)) 21659243Sobrien return PTR_ERR(bl_dev); 217316957Sdchagin 218316957Sdchagin bl->bl_dev = bl_dev; 21959243Sobrien 220167465Smp return 0; 22159243Sobrien} 22259243Sobrien 223167465Smpstatic void lp8788_backlight_unregister(struct lp8788_bl *bl) 22459243Sobrien{ 22559243Sobrien struct backlight_device *bl_dev = bl->bl_dev; 22659243Sobrien 22759243Sobrien backlight_device_unregister(bl_dev); 22859243Sobrien} 22959243Sobrien 23059243Sobrienstatic ssize_t lp8788_get_bl_ctl_mode(struct device *dev, 23159243Sobrien struct device_attribute *attr, char *buf) 23259243Sobrien{ 23359243Sobrien struct lp8788_bl *bl = dev_get_drvdata(dev); 23459243Sobrien enum lp8788_bl_ctrl_mode mode = bl->mode; 23559243Sobrien char *strmode; 23659243Sobrien 237231990Smp if (is_brightness_ctrl_by_pwm(mode)) 23859243Sobrien strmode = "PWM based"; 23959243Sobrien else if (is_brightness_ctrl_by_register(mode)) 24059243Sobrien strmode = "Register based"; 24159243Sobrien else 24259243Sobrien strmode = "Invalid mode"; 24359243Sobrien 24459243Sobrien return scnprintf(buf, PAGE_SIZE, "%s\n", strmode); 24559243Sobrien} 24659243Sobrien 24759243Sobrienstatic DEVICE_ATTR(bl_ctl_mode, S_IRUGO, lp8788_get_bl_ctl_mode, NULL); 24859243Sobrien 24959243Sobrienstatic struct attribute *lp8788_attributes[] = { 25059243Sobrien &dev_attr_bl_ctl_mode.attr, 25159243Sobrien NULL, 25259243Sobrien}; 25359243Sobrien 25459243Sobrienstatic const struct attribute_group lp8788_attr_group = { 25559243Sobrien .attrs = lp8788_attributes, 25659243Sobrien}; 25759243Sobrien 25859243Sobrienstatic int lp8788_backlight_probe(struct platform_device *pdev) 25959243Sobrien{ 26059243Sobrien struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); 26159243Sobrien struct lp8788_bl *bl; 26259243Sobrien int ret; 26359243Sobrien 26459243Sobrien bl = devm_kzalloc(lp->dev, sizeof(struct lp8788_bl), GFP_KERNEL); 26559243Sobrien if (!bl) 26659243Sobrien return -ENOMEM; 26759243Sobrien 26859243Sobrien bl->lp = lp; 26959243Sobrien if (lp->pdata) 27059243Sobrien bl->pdata = lp->pdata->bl_pdata; 27159243Sobrien 27259243Sobrien platform_set_drvdata(pdev, bl); 27359243Sobrien 27459243Sobrien ret = lp8788_backlight_configure(bl); 27559243Sobrien if (ret) { 27659243Sobrien dev_err(lp->dev, "backlight config err: %d\n", ret); 27759243Sobrien goto err_dev; 27859243Sobrien } 27959243Sobrien 28059243Sobrien ret = lp8788_backlight_register(bl); 28159243Sobrien if (ret) { 28259243Sobrien dev_err(lp->dev, "register backlight err: %d\n", ret); 28359243Sobrien goto err_dev; 28459243Sobrien } 28559243Sobrien 28659243Sobrien ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group); 28759243Sobrien if (ret) { 28859243Sobrien dev_err(lp->dev, "register sysfs err: %d\n", ret); 28959243Sobrien goto err_sysfs; 29059243Sobrien } 29159243Sobrien 29259243Sobrien backlight_update_status(bl->bl_dev); 29359243Sobrien 29459243Sobrien return 0; 29559243Sobrien 29659243Sobrienerr_sysfs: 297167465Smp lp8788_backlight_unregister(bl); 29859243Sobrienerr_dev: 29959243Sobrien return ret; 30059243Sobrien} 301167465Smp 302167465Smpstatic void lp8788_backlight_remove(struct platform_device *pdev) 30359243Sobrien{ 30459243Sobrien struct lp8788_bl *bl = platform_get_drvdata(pdev); 30559243Sobrien struct backlight_device *bl_dev = bl->bl_dev; 30659243Sobrien 30759243Sobrien bl_dev->props.brightness = 0; 30859243Sobrien backlight_update_status(bl_dev); 30959243Sobrien sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group); 31059243Sobrien lp8788_backlight_unregister(bl); 31159243Sobrien} 31259243Sobrien 31359243Sobrienstatic struct platform_driver lp8788_bl_driver = { 31459243Sobrien .probe = lp8788_backlight_probe, 31559243Sobrien .remove_new = lp8788_backlight_remove, 31659243Sobrien .driver = { 31759243Sobrien .name = LP8788_DEV_BACKLIGHT, 31859243Sobrien }, 31959243Sobrien}; 32059243Sobrienmodule_platform_driver(lp8788_bl_driver); 32159243Sobrien 32259243SobrienMODULE_DESCRIPTION("Texas Instruments LP8788 Backlight Driver"); 32359243SobrienMODULE_AUTHOR("Milo Kim"); 32483098SmpMODULE_LICENSE("GPL"); 32583098SmpMODULE_ALIAS("platform:lp8788-backlight"); 32683098Smp