18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * TI LP8788 MFD - backlight driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2012 Texas Instruments 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Milo(Woogyom) Kim <milo.kim@ti.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/backlight.h> 118c2ecf20Sopenharmony_ci#include <linux/err.h> 128c2ecf20Sopenharmony_ci#include <linux/mfd/lp8788.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/pwm.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci/* Register address */ 198c2ecf20Sopenharmony_ci#define LP8788_BL_CONFIG 0x96 208c2ecf20Sopenharmony_ci#define LP8788_BL_EN BIT(0) 218c2ecf20Sopenharmony_ci#define LP8788_BL_PWM_INPUT_EN BIT(5) 228c2ecf20Sopenharmony_ci#define LP8788_BL_FULLSCALE_SHIFT 2 238c2ecf20Sopenharmony_ci#define LP8788_BL_DIM_MODE_SHIFT 1 248c2ecf20Sopenharmony_ci#define LP8788_BL_PWM_POLARITY_SHIFT 6 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define LP8788_BL_BRIGHTNESS 0x97 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define LP8788_BL_RAMP 0x98 298c2ecf20Sopenharmony_ci#define LP8788_BL_RAMP_RISE_SHIFT 4 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define MAX_BRIGHTNESS 127 328c2ecf20Sopenharmony_ci#define DEFAULT_BL_NAME "lcd-backlight" 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct lp8788_bl_config { 358c2ecf20Sopenharmony_ci enum lp8788_bl_ctrl_mode bl_mode; 368c2ecf20Sopenharmony_ci enum lp8788_bl_dim_mode dim_mode; 378c2ecf20Sopenharmony_ci enum lp8788_bl_full_scale_current full_scale; 388c2ecf20Sopenharmony_ci enum lp8788_bl_ramp_step rise_time; 398c2ecf20Sopenharmony_ci enum lp8788_bl_ramp_step fall_time; 408c2ecf20Sopenharmony_ci enum pwm_polarity pwm_pol; 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistruct lp8788_bl { 448c2ecf20Sopenharmony_ci struct lp8788 *lp; 458c2ecf20Sopenharmony_ci struct backlight_device *bl_dev; 468c2ecf20Sopenharmony_ci struct lp8788_backlight_platform_data *pdata; 478c2ecf20Sopenharmony_ci enum lp8788_bl_ctrl_mode mode; 488c2ecf20Sopenharmony_ci struct pwm_device *pwm; 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic struct lp8788_bl_config default_bl_config = { 528c2ecf20Sopenharmony_ci .bl_mode = LP8788_BL_REGISTER_ONLY, 538c2ecf20Sopenharmony_ci .dim_mode = LP8788_DIM_EXPONENTIAL, 548c2ecf20Sopenharmony_ci .full_scale = LP8788_FULLSCALE_1900uA, 558c2ecf20Sopenharmony_ci .rise_time = LP8788_RAMP_8192us, 568c2ecf20Sopenharmony_ci .fall_time = LP8788_RAMP_8192us, 578c2ecf20Sopenharmony_ci .pwm_pol = PWM_POLARITY_NORMAL, 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic inline bool is_brightness_ctrl_by_pwm(enum lp8788_bl_ctrl_mode mode) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci return mode == LP8788_BL_COMB_PWM_BASED; 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic inline bool is_brightness_ctrl_by_register(enum lp8788_bl_ctrl_mode mode) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci return mode == LP8788_BL_REGISTER_ONLY || 688c2ecf20Sopenharmony_ci mode == LP8788_BL_COMB_REGISTER_BASED; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic int lp8788_backlight_configure(struct lp8788_bl *bl) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci struct lp8788_backlight_platform_data *pdata = bl->pdata; 748c2ecf20Sopenharmony_ci struct lp8788_bl_config *cfg = &default_bl_config; 758c2ecf20Sopenharmony_ci int ret; 768c2ecf20Sopenharmony_ci u8 val; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci /* 798c2ecf20Sopenharmony_ci * Update chip configuration if platform data exists, 808c2ecf20Sopenharmony_ci * otherwise use the default settings. 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_ci if (pdata) { 838c2ecf20Sopenharmony_ci cfg->bl_mode = pdata->bl_mode; 848c2ecf20Sopenharmony_ci cfg->dim_mode = pdata->dim_mode; 858c2ecf20Sopenharmony_ci cfg->full_scale = pdata->full_scale; 868c2ecf20Sopenharmony_ci cfg->rise_time = pdata->rise_time; 878c2ecf20Sopenharmony_ci cfg->fall_time = pdata->fall_time; 888c2ecf20Sopenharmony_ci cfg->pwm_pol = pdata->pwm_pol; 898c2ecf20Sopenharmony_ci } 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci /* Brightness ramp up/down */ 928c2ecf20Sopenharmony_ci val = (cfg->rise_time << LP8788_BL_RAMP_RISE_SHIFT) | cfg->fall_time; 938c2ecf20Sopenharmony_ci ret = lp8788_write_byte(bl->lp, LP8788_BL_RAMP, val); 948c2ecf20Sopenharmony_ci if (ret) 958c2ecf20Sopenharmony_ci return ret; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci /* Fullscale current setting */ 988c2ecf20Sopenharmony_ci val = (cfg->full_scale << LP8788_BL_FULLSCALE_SHIFT) | 998c2ecf20Sopenharmony_ci (cfg->dim_mode << LP8788_BL_DIM_MODE_SHIFT); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci /* Brightness control mode */ 1028c2ecf20Sopenharmony_ci switch (cfg->bl_mode) { 1038c2ecf20Sopenharmony_ci case LP8788_BL_REGISTER_ONLY: 1048c2ecf20Sopenharmony_ci val |= LP8788_BL_EN; 1058c2ecf20Sopenharmony_ci break; 1068c2ecf20Sopenharmony_ci case LP8788_BL_COMB_PWM_BASED: 1078c2ecf20Sopenharmony_ci case LP8788_BL_COMB_REGISTER_BASED: 1088c2ecf20Sopenharmony_ci val |= LP8788_BL_EN | LP8788_BL_PWM_INPUT_EN | 1098c2ecf20Sopenharmony_ci (cfg->pwm_pol << LP8788_BL_PWM_POLARITY_SHIFT); 1108c2ecf20Sopenharmony_ci break; 1118c2ecf20Sopenharmony_ci default: 1128c2ecf20Sopenharmony_ci dev_err(bl->lp->dev, "invalid mode: %d\n", cfg->bl_mode); 1138c2ecf20Sopenharmony_ci return -EINVAL; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci bl->mode = cfg->bl_mode; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci return lp8788_write_byte(bl->lp, LP8788_BL_CONFIG, val); 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic void lp8788_pwm_ctrl(struct lp8788_bl *bl, int br, int max_br) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci unsigned int period; 1248c2ecf20Sopenharmony_ci unsigned int duty; 1258c2ecf20Sopenharmony_ci struct device *dev; 1268c2ecf20Sopenharmony_ci struct pwm_device *pwm; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (!bl->pdata) 1298c2ecf20Sopenharmony_ci return; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci period = bl->pdata->period_ns; 1328c2ecf20Sopenharmony_ci duty = br * period / max_br; 1338c2ecf20Sopenharmony_ci dev = bl->lp->dev; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* request PWM device with the consumer name */ 1368c2ecf20Sopenharmony_ci if (!bl->pwm) { 1378c2ecf20Sopenharmony_ci pwm = devm_pwm_get(dev, LP8788_DEV_BACKLIGHT); 1388c2ecf20Sopenharmony_ci if (IS_ERR(pwm)) { 1398c2ecf20Sopenharmony_ci dev_err(dev, "can not get PWM device\n"); 1408c2ecf20Sopenharmony_ci return; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci bl->pwm = pwm; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* 1468c2ecf20Sopenharmony_ci * FIXME: pwm_apply_args() should be removed when switching to 1478c2ecf20Sopenharmony_ci * the atomic PWM API. 1488c2ecf20Sopenharmony_ci */ 1498c2ecf20Sopenharmony_ci pwm_apply_args(pwm); 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci pwm_config(bl->pwm, duty, period); 1538c2ecf20Sopenharmony_ci if (duty) 1548c2ecf20Sopenharmony_ci pwm_enable(bl->pwm); 1558c2ecf20Sopenharmony_ci else 1568c2ecf20Sopenharmony_ci pwm_disable(bl->pwm); 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int lp8788_bl_update_status(struct backlight_device *bl_dev) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct lp8788_bl *bl = bl_get_data(bl_dev); 1628c2ecf20Sopenharmony_ci enum lp8788_bl_ctrl_mode mode = bl->mode; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci if (bl_dev->props.state & BL_CORE_SUSPENDED) 1658c2ecf20Sopenharmony_ci bl_dev->props.brightness = 0; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci if (is_brightness_ctrl_by_pwm(mode)) { 1688c2ecf20Sopenharmony_ci int brt = bl_dev->props.brightness; 1698c2ecf20Sopenharmony_ci int max = bl_dev->props.max_brightness; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci lp8788_pwm_ctrl(bl, brt, max); 1728c2ecf20Sopenharmony_ci } else if (is_brightness_ctrl_by_register(mode)) { 1738c2ecf20Sopenharmony_ci u8 brt = bl_dev->props.brightness; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci lp8788_write_byte(bl->lp, LP8788_BL_BRIGHTNESS, brt); 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci return 0; 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic const struct backlight_ops lp8788_bl_ops = { 1828c2ecf20Sopenharmony_ci .options = BL_CORE_SUSPENDRESUME, 1838c2ecf20Sopenharmony_ci .update_status = lp8788_bl_update_status, 1848c2ecf20Sopenharmony_ci}; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int lp8788_backlight_register(struct lp8788_bl *bl) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci struct backlight_device *bl_dev; 1898c2ecf20Sopenharmony_ci struct backlight_properties props; 1908c2ecf20Sopenharmony_ci struct lp8788_backlight_platform_data *pdata = bl->pdata; 1918c2ecf20Sopenharmony_ci int init_brt; 1928c2ecf20Sopenharmony_ci char *name; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci props.type = BACKLIGHT_PLATFORM; 1958c2ecf20Sopenharmony_ci props.max_brightness = MAX_BRIGHTNESS; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci /* Initial brightness */ 1988c2ecf20Sopenharmony_ci if (pdata) 1998c2ecf20Sopenharmony_ci init_brt = min_t(int, pdata->initial_brightness, 2008c2ecf20Sopenharmony_ci props.max_brightness); 2018c2ecf20Sopenharmony_ci else 2028c2ecf20Sopenharmony_ci init_brt = 0; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci props.brightness = init_brt; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* Backlight device name */ 2078c2ecf20Sopenharmony_ci if (!pdata || !pdata->name) 2088c2ecf20Sopenharmony_ci name = DEFAULT_BL_NAME; 2098c2ecf20Sopenharmony_ci else 2108c2ecf20Sopenharmony_ci name = pdata->name; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci bl_dev = backlight_device_register(name, bl->lp->dev, bl, 2138c2ecf20Sopenharmony_ci &lp8788_bl_ops, &props); 2148c2ecf20Sopenharmony_ci if (IS_ERR(bl_dev)) 2158c2ecf20Sopenharmony_ci return PTR_ERR(bl_dev); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci bl->bl_dev = bl_dev; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci return 0; 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic void lp8788_backlight_unregister(struct lp8788_bl *bl) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci struct backlight_device *bl_dev = bl->bl_dev; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci backlight_device_unregister(bl_dev); 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic ssize_t lp8788_get_bl_ctl_mode(struct device *dev, 2308c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci struct lp8788_bl *bl = dev_get_drvdata(dev); 2338c2ecf20Sopenharmony_ci enum lp8788_bl_ctrl_mode mode = bl->mode; 2348c2ecf20Sopenharmony_ci char *strmode; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci if (is_brightness_ctrl_by_pwm(mode)) 2378c2ecf20Sopenharmony_ci strmode = "PWM based"; 2388c2ecf20Sopenharmony_ci else if (is_brightness_ctrl_by_register(mode)) 2398c2ecf20Sopenharmony_ci strmode = "Register based"; 2408c2ecf20Sopenharmony_ci else 2418c2ecf20Sopenharmony_ci strmode = "Invalid mode"; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci return scnprintf(buf, PAGE_SIZE, "%s\n", strmode); 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic DEVICE_ATTR(bl_ctl_mode, S_IRUGO, lp8788_get_bl_ctl_mode, NULL); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cistatic struct attribute *lp8788_attributes[] = { 2498c2ecf20Sopenharmony_ci &dev_attr_bl_ctl_mode.attr, 2508c2ecf20Sopenharmony_ci NULL, 2518c2ecf20Sopenharmony_ci}; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic const struct attribute_group lp8788_attr_group = { 2548c2ecf20Sopenharmony_ci .attrs = lp8788_attributes, 2558c2ecf20Sopenharmony_ci}; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic int lp8788_backlight_probe(struct platform_device *pdev) 2588c2ecf20Sopenharmony_ci{ 2598c2ecf20Sopenharmony_ci struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); 2608c2ecf20Sopenharmony_ci struct lp8788_bl *bl; 2618c2ecf20Sopenharmony_ci int ret; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci bl = devm_kzalloc(lp->dev, sizeof(struct lp8788_bl), GFP_KERNEL); 2648c2ecf20Sopenharmony_ci if (!bl) 2658c2ecf20Sopenharmony_ci return -ENOMEM; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci bl->lp = lp; 2688c2ecf20Sopenharmony_ci if (lp->pdata) 2698c2ecf20Sopenharmony_ci bl->pdata = lp->pdata->bl_pdata; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, bl); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci ret = lp8788_backlight_configure(bl); 2748c2ecf20Sopenharmony_ci if (ret) { 2758c2ecf20Sopenharmony_ci dev_err(lp->dev, "backlight config err: %d\n", ret); 2768c2ecf20Sopenharmony_ci goto err_dev; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci ret = lp8788_backlight_register(bl); 2808c2ecf20Sopenharmony_ci if (ret) { 2818c2ecf20Sopenharmony_ci dev_err(lp->dev, "register backlight err: %d\n", ret); 2828c2ecf20Sopenharmony_ci goto err_dev; 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group); 2868c2ecf20Sopenharmony_ci if (ret) { 2878c2ecf20Sopenharmony_ci dev_err(lp->dev, "register sysfs err: %d\n", ret); 2888c2ecf20Sopenharmony_ci goto err_sysfs; 2898c2ecf20Sopenharmony_ci } 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci backlight_update_status(bl->bl_dev); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci return 0; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cierr_sysfs: 2968c2ecf20Sopenharmony_ci lp8788_backlight_unregister(bl); 2978c2ecf20Sopenharmony_cierr_dev: 2988c2ecf20Sopenharmony_ci return ret; 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic int lp8788_backlight_remove(struct platform_device *pdev) 3028c2ecf20Sopenharmony_ci{ 3038c2ecf20Sopenharmony_ci struct lp8788_bl *bl = platform_get_drvdata(pdev); 3048c2ecf20Sopenharmony_ci struct backlight_device *bl_dev = bl->bl_dev; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci bl_dev->props.brightness = 0; 3078c2ecf20Sopenharmony_ci backlight_update_status(bl_dev); 3088c2ecf20Sopenharmony_ci sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group); 3098c2ecf20Sopenharmony_ci lp8788_backlight_unregister(bl); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci return 0; 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_cistatic struct platform_driver lp8788_bl_driver = { 3158c2ecf20Sopenharmony_ci .probe = lp8788_backlight_probe, 3168c2ecf20Sopenharmony_ci .remove = lp8788_backlight_remove, 3178c2ecf20Sopenharmony_ci .driver = { 3188c2ecf20Sopenharmony_ci .name = LP8788_DEV_BACKLIGHT, 3198c2ecf20Sopenharmony_ci }, 3208c2ecf20Sopenharmony_ci}; 3218c2ecf20Sopenharmony_cimodule_platform_driver(lp8788_bl_driver); 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Texas Instruments LP8788 Backlight Driver"); 3248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Milo Kim"); 3258c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3268c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:lp8788-backlight"); 327