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