xref: /linux/drivers/video/backlight/88pm860x_bl.c (revision 62b31a045757eac81fed94b19df47418a0818528)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2d07e8bf8SHaojian Zhuang /*
3d07e8bf8SHaojian Zhuang  * Backlight driver for Marvell Semiconductor 88PM8606
4d07e8bf8SHaojian Zhuang  *
5d07e8bf8SHaojian Zhuang  * Copyright (C) 2009 Marvell International Ltd.
6d07e8bf8SHaojian Zhuang  *	Haojian Zhuang <haojian.zhuang@marvell.com>
7d07e8bf8SHaojian Zhuang  */
8d07e8bf8SHaojian Zhuang 
9d07e8bf8SHaojian Zhuang #include <linux/init.h>
10d07e8bf8SHaojian Zhuang #include <linux/kernel.h>
112e57d567SHaojian Zhuang #include <linux/of.h>
12d07e8bf8SHaojian Zhuang #include <linux/platform_device.h>
13adb70483SHaojian Zhuang #include <linux/slab.h>
14d07e8bf8SHaojian Zhuang #include <linux/fb.h>
15d07e8bf8SHaojian Zhuang #include <linux/i2c.h>
16d07e8bf8SHaojian Zhuang #include <linux/backlight.h>
17d07e8bf8SHaojian Zhuang #include <linux/mfd/88pm860x.h>
18355b200bSPaul Gortmaker #include <linux/module.h>
19d07e8bf8SHaojian Zhuang 
20d07e8bf8SHaojian Zhuang #define MAX_BRIGHTNESS		(0xFF)
21d07e8bf8SHaojian Zhuang #define MIN_BRIGHTNESS		(0)
22d07e8bf8SHaojian Zhuang 
232550326aSRandy Dunlap #define CURRENT_BITMASK		(0x1F << 1)
24d07e8bf8SHaojian Zhuang 
25d07e8bf8SHaojian Zhuang struct pm860x_backlight_data {
26d07e8bf8SHaojian Zhuang 	struct pm860x_chip *chip;
27d07e8bf8SHaojian Zhuang 	struct i2c_client *i2c;
28d07e8bf8SHaojian Zhuang 	int	current_brightness;
29d07e8bf8SHaojian Zhuang 	int	port;
30d07e8bf8SHaojian Zhuang 	int	pwm;
31d07e8bf8SHaojian Zhuang 	int	iset;
32a6ccdcd9SHaojian Zhuang 	int	reg_duty_cycle;
33a6ccdcd9SHaojian Zhuang 	int	reg_always_on;
34a6ccdcd9SHaojian Zhuang 	int	reg_current;
35d07e8bf8SHaojian Zhuang };
36d07e8bf8SHaojian Zhuang 
371efc1581SJett.Zhou static int backlight_power_set(struct pm860x_chip *chip, int port,
381efc1581SJett.Zhou 		int on)
391efc1581SJett.Zhou {
401efc1581SJett.Zhou 	int ret = -EINVAL;
411efc1581SJett.Zhou 
421efc1581SJett.Zhou 	switch (port) {
43a6ccdcd9SHaojian Zhuang 	case 0:
441efc1581SJett.Zhou 		ret = on ? pm8606_osc_enable(chip, WLED1_DUTY) :
451efc1581SJett.Zhou 			pm8606_osc_disable(chip, WLED1_DUTY);
461efc1581SJett.Zhou 		break;
47a6ccdcd9SHaojian Zhuang 	case 1:
481efc1581SJett.Zhou 		ret = on ? pm8606_osc_enable(chip, WLED2_DUTY) :
491efc1581SJett.Zhou 			pm8606_osc_disable(chip, WLED2_DUTY);
501efc1581SJett.Zhou 		break;
51a6ccdcd9SHaojian Zhuang 	case 2:
521efc1581SJett.Zhou 		ret = on ? pm8606_osc_enable(chip, WLED3_DUTY) :
531efc1581SJett.Zhou 			pm8606_osc_disable(chip, WLED3_DUTY);
541efc1581SJett.Zhou 		break;
551efc1581SJett.Zhou 	}
561efc1581SJett.Zhou 	return ret;
571efc1581SJett.Zhou }
581efc1581SJett.Zhou 
59d07e8bf8SHaojian Zhuang static int pm860x_backlight_set(struct backlight_device *bl, int brightness)
60d07e8bf8SHaojian Zhuang {
61d07e8bf8SHaojian Zhuang 	struct pm860x_backlight_data *data = bl_get_data(bl);
62d07e8bf8SHaojian Zhuang 	struct pm860x_chip *chip = data->chip;
63d07e8bf8SHaojian Zhuang 	unsigned char value;
64d07e8bf8SHaojian Zhuang 	int ret;
65d07e8bf8SHaojian Zhuang 
66d07e8bf8SHaojian Zhuang 	if (brightness > MAX_BRIGHTNESS)
67d07e8bf8SHaojian Zhuang 		value = MAX_BRIGHTNESS;
68d07e8bf8SHaojian Zhuang 	else
69d07e8bf8SHaojian Zhuang 		value = brightness;
70d07e8bf8SHaojian Zhuang 
711efc1581SJett.Zhou 	if (brightness)
721efc1581SJett.Zhou 		backlight_power_set(chip, data->port, 1);
731efc1581SJett.Zhou 
74a6ccdcd9SHaojian Zhuang 	ret = pm860x_reg_write(data->i2c, data->reg_duty_cycle, value);
75d07e8bf8SHaojian Zhuang 	if (ret < 0)
76d07e8bf8SHaojian Zhuang 		goto out;
77d07e8bf8SHaojian Zhuang 
78d07e8bf8SHaojian Zhuang 	if ((data->current_brightness == 0) && brightness) {
79d07e8bf8SHaojian Zhuang 		if (data->iset) {
80a6ccdcd9SHaojian Zhuang 			ret = pm860x_set_bits(data->i2c, data->reg_current,
812550326aSRandy Dunlap 					      CURRENT_BITMASK, data->iset);
82d07e8bf8SHaojian Zhuang 			if (ret < 0)
83d07e8bf8SHaojian Zhuang 				goto out;
84d07e8bf8SHaojian Zhuang 		}
85d07e8bf8SHaojian Zhuang 		if (data->pwm) {
86d07e8bf8SHaojian Zhuang 			ret = pm860x_set_bits(data->i2c, PM8606_PWM,
87d07e8bf8SHaojian Zhuang 					      PM8606_PWM_FREQ_MASK, data->pwm);
88d07e8bf8SHaojian Zhuang 			if (ret < 0)
89d07e8bf8SHaojian Zhuang 				goto out;
90d07e8bf8SHaojian Zhuang 		}
91d07e8bf8SHaojian Zhuang 		if (brightness == MAX_BRIGHTNESS) {
92d07e8bf8SHaojian Zhuang 			/* set WLED_ON bit as 100% */
93a6ccdcd9SHaojian Zhuang 			ret = pm860x_set_bits(data->i2c, data->reg_always_on,
94d07e8bf8SHaojian Zhuang 					      PM8606_WLED_ON, PM8606_WLED_ON);
95d07e8bf8SHaojian Zhuang 		}
96d07e8bf8SHaojian Zhuang 	} else {
97d07e8bf8SHaojian Zhuang 		if (brightness == MAX_BRIGHTNESS) {
98d07e8bf8SHaojian Zhuang 			/* set WLED_ON bit as 100% */
99a6ccdcd9SHaojian Zhuang 			ret = pm860x_set_bits(data->i2c, data->reg_always_on,
100d07e8bf8SHaojian Zhuang 					      PM8606_WLED_ON, PM8606_WLED_ON);
101d07e8bf8SHaojian Zhuang 		} else {
102d07e8bf8SHaojian Zhuang 			/* clear WLED_ON bit since it's not 100% */
103a6ccdcd9SHaojian Zhuang 			ret = pm860x_set_bits(data->i2c, data->reg_always_on,
104d07e8bf8SHaojian Zhuang 					      PM8606_WLED_ON, 0);
105d07e8bf8SHaojian Zhuang 		}
106d07e8bf8SHaojian Zhuang 	}
107d07e8bf8SHaojian Zhuang 	if (ret < 0)
108d07e8bf8SHaojian Zhuang 		goto out;
109d07e8bf8SHaojian Zhuang 
1101efc1581SJett.Zhou 	if (brightness == 0)
1111efc1581SJett.Zhou 		backlight_power_set(chip, data->port, 0);
1121efc1581SJett.Zhou 
113d07e8bf8SHaojian Zhuang 	dev_dbg(chip->dev, "set brightness %d\n", value);
114d07e8bf8SHaojian Zhuang 	data->current_brightness = value;
115d07e8bf8SHaojian Zhuang 	return 0;
116d07e8bf8SHaojian Zhuang out:
11720c5a932SJingoo Han 	dev_dbg(chip->dev, "set brightness %d failure with return value: %d\n",
11820c5a932SJingoo Han 		value, ret);
119d07e8bf8SHaojian Zhuang 	return ret;
120d07e8bf8SHaojian Zhuang }
121d07e8bf8SHaojian Zhuang 
122d07e8bf8SHaojian Zhuang static int pm860x_backlight_update_status(struct backlight_device *bl)
123d07e8bf8SHaojian Zhuang {
124*51d53e5bSSam Ravnborg 	return pm860x_backlight_set(bl, backlight_get_brightness(bl));
125d07e8bf8SHaojian Zhuang }
126d07e8bf8SHaojian Zhuang 
127d07e8bf8SHaojian Zhuang static int pm860x_backlight_get_brightness(struct backlight_device *bl)
128d07e8bf8SHaojian Zhuang {
129d07e8bf8SHaojian Zhuang 	struct pm860x_backlight_data *data = bl_get_data(bl);
130d07e8bf8SHaojian Zhuang 	struct pm860x_chip *chip = data->chip;
131d07e8bf8SHaojian Zhuang 	int ret;
132d07e8bf8SHaojian Zhuang 
133a6ccdcd9SHaojian Zhuang 	ret = pm860x_reg_read(data->i2c, data->reg_duty_cycle);
134d07e8bf8SHaojian Zhuang 	if (ret < 0)
135d07e8bf8SHaojian Zhuang 		goto out;
136d07e8bf8SHaojian Zhuang 	data->current_brightness = ret;
137d07e8bf8SHaojian Zhuang 	dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness);
138d07e8bf8SHaojian Zhuang 	return data->current_brightness;
139d07e8bf8SHaojian Zhuang out:
140d07e8bf8SHaojian Zhuang 	return -EINVAL;
141d07e8bf8SHaojian Zhuang }
142d07e8bf8SHaojian Zhuang 
143acc2472eSLionel Debroux static const struct backlight_ops pm860x_backlight_ops = {
144d07e8bf8SHaojian Zhuang 	.options	= BL_CORE_SUSPENDRESUME,
145d07e8bf8SHaojian Zhuang 	.update_status	= pm860x_backlight_update_status,
146d07e8bf8SHaojian Zhuang 	.get_brightness	= pm860x_backlight_get_brightness,
147d07e8bf8SHaojian Zhuang };
148d07e8bf8SHaojian Zhuang 
1492e57d567SHaojian Zhuang #ifdef CONFIG_OF
1502e57d567SHaojian Zhuang static int pm860x_backlight_dt_init(struct platform_device *pdev,
1512e57d567SHaojian Zhuang 				    struct pm860x_backlight_data *data,
1522e57d567SHaojian Zhuang 				    char *name)
1532e57d567SHaojian Zhuang {
154c84c3833SAxel Lin 	struct device_node *nproot, *np;
1552e57d567SHaojian Zhuang 	int iset = 0;
156c84c3833SAxel Lin 
157c6f77bc2SGeert Uytterhoeven 	nproot = of_get_child_by_name(pdev->dev.parent->of_node, "backlights");
1582e57d567SHaojian Zhuang 	if (!nproot) {
1592e57d567SHaojian Zhuang 		dev_err(&pdev->dev, "failed to find backlights node\n");
1602e57d567SHaojian Zhuang 		return -ENODEV;
1612e57d567SHaojian Zhuang 	}
1622e57d567SHaojian Zhuang 	for_each_child_of_node(nproot, np) {
1633cee7a7dSRob Herring 		if (of_node_name_eq(np, name)) {
1642e57d567SHaojian Zhuang 			of_property_read_u32(np, "marvell,88pm860x-iset",
1652e57d567SHaojian Zhuang 					     &iset);
1662e57d567SHaojian Zhuang 			data->iset = PM8606_WLED_CURRENT(iset);
1672e57d567SHaojian Zhuang 			of_property_read_u32(np, "marvell,88pm860x-pwm",
1682e57d567SHaojian Zhuang 					     &data->pwm);
169f85de2d9SJulia Lawall 			of_node_put(np);
1702e57d567SHaojian Zhuang 			break;
1712e57d567SHaojian Zhuang 		}
1722e57d567SHaojian Zhuang 	}
173c84c3833SAxel Lin 	of_node_put(nproot);
1742e57d567SHaojian Zhuang 	return 0;
1752e57d567SHaojian Zhuang }
1762e57d567SHaojian Zhuang #else
1772e57d567SHaojian Zhuang #define pm860x_backlight_dt_init(x, y, z)	(-1)
1782e57d567SHaojian Zhuang #endif
1792e57d567SHaojian Zhuang 
180d07e8bf8SHaojian Zhuang static int pm860x_backlight_probe(struct platform_device *pdev)
181d07e8bf8SHaojian Zhuang {
182d07e8bf8SHaojian Zhuang 	struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
183c512794cSJingoo Han 	struct pm860x_backlight_pdata *pdata = dev_get_platdata(&pdev->dev);
184d07e8bf8SHaojian Zhuang 	struct pm860x_backlight_data *data;
185d07e8bf8SHaojian Zhuang 	struct backlight_device *bl;
186d07e8bf8SHaojian Zhuang 	struct resource *res;
187a19a6ee6SMatthew Garrett 	struct backlight_properties props;
188d07e8bf8SHaojian Zhuang 	char name[MFD_NAME_SIZE];
189a6ccdcd9SHaojian Zhuang 	int ret = 0;
190d07e8bf8SHaojian Zhuang 
191ce969228SJulia Lawall 	data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_backlight_data),
192ce969228SJulia Lawall 			    GFP_KERNEL);
193d07e8bf8SHaojian Zhuang 	if (data == NULL)
194d07e8bf8SHaojian Zhuang 		return -ENOMEM;
195a6ccdcd9SHaojian Zhuang 	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "duty cycle");
196a6ccdcd9SHaojian Zhuang 	if (!res) {
197a6ccdcd9SHaojian Zhuang 		dev_err(&pdev->dev, "No REG resource for duty cycle\n");
1983a1f9462SJingoo Han 		return -ENXIO;
199a6ccdcd9SHaojian Zhuang 	}
200a6ccdcd9SHaojian Zhuang 	data->reg_duty_cycle = res->start;
201a6ccdcd9SHaojian Zhuang 	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "always on");
202a6ccdcd9SHaojian Zhuang 	if (!res) {
2031a84db56SMasanari Iida 		dev_err(&pdev->dev, "No REG resource for always on\n");
2043a1f9462SJingoo Han 		return -ENXIO;
205a6ccdcd9SHaojian Zhuang 	}
206a6ccdcd9SHaojian Zhuang 	data->reg_always_on = res->start;
207a6ccdcd9SHaojian Zhuang 	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "current");
208a6ccdcd9SHaojian Zhuang 	if (!res) {
209a6ccdcd9SHaojian Zhuang 		dev_err(&pdev->dev, "No REG resource for current\n");
2103a1f9462SJingoo Han 		return -ENXIO;
211a6ccdcd9SHaojian Zhuang 	}
212a6ccdcd9SHaojian Zhuang 	data->reg_current = res->start;
213a6ccdcd9SHaojian Zhuang 
214a6ccdcd9SHaojian Zhuang 	memset(name, 0, MFD_NAME_SIZE);
215a6ccdcd9SHaojian Zhuang 	sprintf(name, "backlight-%d", pdev->id);
216a6ccdcd9SHaojian Zhuang 	data->port = pdev->id;
217d07e8bf8SHaojian Zhuang 	data->chip = chip;
2182fe2380eSJingoo Han 	data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
219d07e8bf8SHaojian Zhuang 	data->current_brightness = MAX_BRIGHTNESS;
2202e57d567SHaojian Zhuang 	if (pm860x_backlight_dt_init(pdev, data, name)) {
221a6ccdcd9SHaojian Zhuang 		if (pdata) {
222d07e8bf8SHaojian Zhuang 			data->pwm = pdata->pwm;
223d07e8bf8SHaojian Zhuang 			data->iset = pdata->iset;
224d07e8bf8SHaojian Zhuang 		}
2252e57d567SHaojian Zhuang 	}
226d07e8bf8SHaojian Zhuang 
227a19a6ee6SMatthew Garrett 	memset(&props, 0, sizeof(struct backlight_properties));
228bb7ca747SMatthew Garrett 	props.type = BACKLIGHT_RAW;
229a19a6ee6SMatthew Garrett 	props.max_brightness = MAX_BRIGHTNESS;
230f829c9efSJingoo Han 	bl = devm_backlight_device_register(&pdev->dev, name, &pdev->dev, data,
231a19a6ee6SMatthew Garrett 					&pm860x_backlight_ops, &props);
232d07e8bf8SHaojian Zhuang 	if (IS_ERR(bl)) {
233d07e8bf8SHaojian Zhuang 		dev_err(&pdev->dev, "failed to register backlight\n");
234d07e8bf8SHaojian Zhuang 		return PTR_ERR(bl);
235d07e8bf8SHaojian Zhuang 	}
236d07e8bf8SHaojian Zhuang 	bl->props.brightness = MAX_BRIGHTNESS;
237d07e8bf8SHaojian Zhuang 
238d07e8bf8SHaojian Zhuang 	platform_set_drvdata(pdev, bl);
239d07e8bf8SHaojian Zhuang 
240d07e8bf8SHaojian Zhuang 	/* read current backlight */
241d07e8bf8SHaojian Zhuang 	ret = pm860x_backlight_get_brightness(bl);
242d07e8bf8SHaojian Zhuang 	if (ret < 0)
243f829c9efSJingoo Han 		return ret;
244d07e8bf8SHaojian Zhuang 
245d07e8bf8SHaojian Zhuang 	backlight_update_status(bl);
246d07e8bf8SHaojian Zhuang 	return 0;
247d07e8bf8SHaojian Zhuang }
248d07e8bf8SHaojian Zhuang 
249d07e8bf8SHaojian Zhuang static struct platform_driver pm860x_backlight_driver = {
250d07e8bf8SHaojian Zhuang 	.driver		= {
251d07e8bf8SHaojian Zhuang 		.name	= "88pm860x-backlight",
252d07e8bf8SHaojian Zhuang 	},
253d07e8bf8SHaojian Zhuang 	.probe		= pm860x_backlight_probe,
254d07e8bf8SHaojian Zhuang };
255d07e8bf8SHaojian Zhuang 
25681178e02SAxel Lin module_platform_driver(pm860x_backlight_driver);
257d07e8bf8SHaojian Zhuang 
258d07e8bf8SHaojian Zhuang MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606");
259d07e8bf8SHaojian Zhuang MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
260d07e8bf8SHaojian Zhuang MODULE_LICENSE("GPL");
261d07e8bf8SHaojian Zhuang MODULE_ALIAS("platform:88pm860x-backlight");
262