18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Backlight driver for Maxim MAX8925
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Marvell International Ltd.
68c2ecf20Sopenharmony_ci *      Haojian Zhuang <haojian.zhuang@marvell.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/init.h>
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/fb.h>
138c2ecf20Sopenharmony_ci#include <linux/i2c.h>
148c2ecf20Sopenharmony_ci#include <linux/backlight.h>
158c2ecf20Sopenharmony_ci#include <linux/mfd/max8925.h>
168c2ecf20Sopenharmony_ci#include <linux/slab.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define MAX_BRIGHTNESS		(0xff)
208c2ecf20Sopenharmony_ci#define MIN_BRIGHTNESS		(0)
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define LWX_FREQ(x)		(((x - 601) / 100) & 0x7)
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistruct max8925_backlight_data {
258c2ecf20Sopenharmony_ci	struct max8925_chip	*chip;
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci	int	current_brightness;
288c2ecf20Sopenharmony_ci	int	reg_mode_cntl;
298c2ecf20Sopenharmony_ci	int	reg_cntl;
308c2ecf20Sopenharmony_ci};
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic int max8925_backlight_set(struct backlight_device *bl, int brightness)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	struct max8925_backlight_data *data = bl_get_data(bl);
358c2ecf20Sopenharmony_ci	struct max8925_chip *chip = data->chip;
368c2ecf20Sopenharmony_ci	unsigned char value;
378c2ecf20Sopenharmony_ci	int ret;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	if (brightness > MAX_BRIGHTNESS)
408c2ecf20Sopenharmony_ci		value = MAX_BRIGHTNESS;
418c2ecf20Sopenharmony_ci	else
428c2ecf20Sopenharmony_ci		value = brightness;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	ret = max8925_reg_write(chip->i2c, data->reg_cntl, value);
458c2ecf20Sopenharmony_ci	if (ret < 0)
468c2ecf20Sopenharmony_ci		goto out;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	if (!data->current_brightness && brightness)
498c2ecf20Sopenharmony_ci		/* enable WLED output */
508c2ecf20Sopenharmony_ci		ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 1, 1);
518c2ecf20Sopenharmony_ci	else if (!brightness)
528c2ecf20Sopenharmony_ci		/* disable WLED output */
538c2ecf20Sopenharmony_ci		ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 1, 0);
548c2ecf20Sopenharmony_ci	if (ret < 0)
558c2ecf20Sopenharmony_ci		goto out;
568c2ecf20Sopenharmony_ci	dev_dbg(chip->dev, "set brightness %d\n", value);
578c2ecf20Sopenharmony_ci	data->current_brightness = value;
588c2ecf20Sopenharmony_ci	return 0;
598c2ecf20Sopenharmony_ciout:
608c2ecf20Sopenharmony_ci	dev_dbg(chip->dev, "set brightness %d failure with return value:%d\n",
618c2ecf20Sopenharmony_ci		value, ret);
628c2ecf20Sopenharmony_ci	return ret;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic int max8925_backlight_update_status(struct backlight_device *bl)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	return max8925_backlight_set(bl, backlight_get_brightness(bl));
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic int max8925_backlight_get_brightness(struct backlight_device *bl)
718c2ecf20Sopenharmony_ci{
728c2ecf20Sopenharmony_ci	struct max8925_backlight_data *data = bl_get_data(bl);
738c2ecf20Sopenharmony_ci	struct max8925_chip *chip = data->chip;
748c2ecf20Sopenharmony_ci	int ret;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	ret = max8925_reg_read(chip->i2c, data->reg_cntl);
778c2ecf20Sopenharmony_ci	if (ret < 0)
788c2ecf20Sopenharmony_ci		return -EINVAL;
798c2ecf20Sopenharmony_ci	data->current_brightness = ret;
808c2ecf20Sopenharmony_ci	dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness);
818c2ecf20Sopenharmony_ci	return ret;
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic const struct backlight_ops max8925_backlight_ops = {
858c2ecf20Sopenharmony_ci	.options	= BL_CORE_SUSPENDRESUME,
868c2ecf20Sopenharmony_ci	.update_status	= max8925_backlight_update_status,
878c2ecf20Sopenharmony_ci	.get_brightness	= max8925_backlight_get_brightness,
888c2ecf20Sopenharmony_ci};
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic void max8925_backlight_dt_init(struct platform_device *pdev)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	struct device_node *nproot = pdev->dev.parent->of_node, *np;
938c2ecf20Sopenharmony_ci	struct max8925_backlight_pdata *pdata;
948c2ecf20Sopenharmony_ci	u32 val;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (!nproot || !IS_ENABLED(CONFIG_OF))
978c2ecf20Sopenharmony_ci		return;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	pdata = devm_kzalloc(&pdev->dev,
1008c2ecf20Sopenharmony_ci			     sizeof(struct max8925_backlight_pdata),
1018c2ecf20Sopenharmony_ci			     GFP_KERNEL);
1028c2ecf20Sopenharmony_ci	if (!pdata)
1038c2ecf20Sopenharmony_ci		return;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	np = of_get_child_by_name(nproot, "backlight");
1068c2ecf20Sopenharmony_ci	if (!np) {
1078c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to find backlight node\n");
1088c2ecf20Sopenharmony_ci		return;
1098c2ecf20Sopenharmony_ci	}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (!of_property_read_u32(np, "maxim,max8925-dual-string", &val))
1128c2ecf20Sopenharmony_ci		pdata->dual_string = val;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	of_node_put(np);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	pdev->dev.platform_data = pdata;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic int max8925_backlight_probe(struct platform_device *pdev)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
1228c2ecf20Sopenharmony_ci	struct max8925_backlight_pdata *pdata;
1238c2ecf20Sopenharmony_ci	struct max8925_backlight_data *data;
1248c2ecf20Sopenharmony_ci	struct backlight_device *bl;
1258c2ecf20Sopenharmony_ci	struct backlight_properties props;
1268c2ecf20Sopenharmony_ci	struct resource *res;
1278c2ecf20Sopenharmony_ci	unsigned char value;
1288c2ecf20Sopenharmony_ci	int ret = 0;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	data = devm_kzalloc(&pdev->dev, sizeof(struct max8925_backlight_data),
1318c2ecf20Sopenharmony_ci			    GFP_KERNEL);
1328c2ecf20Sopenharmony_ci	if (data == NULL)
1338c2ecf20Sopenharmony_ci		return -ENOMEM;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_REG, 0);
1368c2ecf20Sopenharmony_ci	if (!res) {
1378c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "No REG resource for mode control!\n");
1388c2ecf20Sopenharmony_ci		return -ENXIO;
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci	data->reg_mode_cntl = res->start;
1418c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_REG, 1);
1428c2ecf20Sopenharmony_ci	if (!res) {
1438c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "No REG resource for control!\n");
1448c2ecf20Sopenharmony_ci		return -ENXIO;
1458c2ecf20Sopenharmony_ci	}
1468c2ecf20Sopenharmony_ci	data->reg_cntl = res->start;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	data->chip = chip;
1498c2ecf20Sopenharmony_ci	data->current_brightness = 0;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	memset(&props, 0, sizeof(struct backlight_properties));
1528c2ecf20Sopenharmony_ci	props.type = BACKLIGHT_RAW;
1538c2ecf20Sopenharmony_ci	props.max_brightness = MAX_BRIGHTNESS;
1548c2ecf20Sopenharmony_ci	bl = devm_backlight_device_register(&pdev->dev, "max8925-backlight",
1558c2ecf20Sopenharmony_ci					&pdev->dev, data,
1568c2ecf20Sopenharmony_ci					&max8925_backlight_ops, &props);
1578c2ecf20Sopenharmony_ci	if (IS_ERR(bl)) {
1588c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to register backlight\n");
1598c2ecf20Sopenharmony_ci		return PTR_ERR(bl);
1608c2ecf20Sopenharmony_ci	}
1618c2ecf20Sopenharmony_ci	bl->props.brightness = MAX_BRIGHTNESS;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, bl);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	value = 0;
1668c2ecf20Sopenharmony_ci	if (!pdev->dev.platform_data)
1678c2ecf20Sopenharmony_ci		max8925_backlight_dt_init(pdev);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	pdata = pdev->dev.platform_data;
1708c2ecf20Sopenharmony_ci	if (pdata) {
1718c2ecf20Sopenharmony_ci		if (pdata->lxw_scl)
1728c2ecf20Sopenharmony_ci			value |= (1 << 7);
1738c2ecf20Sopenharmony_ci		if (pdata->lxw_freq)
1748c2ecf20Sopenharmony_ci			value |= (LWX_FREQ(pdata->lxw_freq) << 4);
1758c2ecf20Sopenharmony_ci		if (pdata->dual_string)
1768c2ecf20Sopenharmony_ci			value |= (1 << 1);
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci	ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 0xfe, value);
1798c2ecf20Sopenharmony_ci	if (ret < 0)
1808c2ecf20Sopenharmony_ci		return ret;
1818c2ecf20Sopenharmony_ci	backlight_update_status(bl);
1828c2ecf20Sopenharmony_ci	return 0;
1838c2ecf20Sopenharmony_ci}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_cistatic struct platform_driver max8925_backlight_driver = {
1868c2ecf20Sopenharmony_ci	.driver		= {
1878c2ecf20Sopenharmony_ci		.name	= "max8925-backlight",
1888c2ecf20Sopenharmony_ci	},
1898c2ecf20Sopenharmony_ci	.probe		= max8925_backlight_probe,
1908c2ecf20Sopenharmony_ci};
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cimodule_platform_driver(max8925_backlight_driver);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Backlight Driver for Maxim MAX8925");
1958c2ecf20Sopenharmony_ciMODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
1968c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1978c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:max8925-backlight");
198