1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * tps65217_bl.c
4 *
5 * TPS65217 backlight driver
6 *
7 * Copyright (C) 2012 Matthias Kaehlcke
8 * Author: Matthias Kaehlcke <matthias@kaehlcke.net>
9 */
10
11#include <linux/kernel.h>
12#include <linux/backlight.h>
13#include <linux/err.h>
14#include <linux/fb.h>
15#include <linux/mfd/tps65217.h>
16#include <linux/module.h>
17#include <linux/platform_device.h>
18#include <linux/slab.h>
19
20struct tps65217_bl {
21	struct tps65217 *tps;
22	struct device *dev;
23	struct backlight_device *bl;
24	bool is_enabled;
25};
26
27static int tps65217_bl_enable(struct tps65217_bl *tps65217_bl)
28{
29	int rc;
30
31	rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
32			TPS65217_WLEDCTRL1_ISINK_ENABLE,
33			TPS65217_WLEDCTRL1_ISINK_ENABLE, TPS65217_PROTECT_NONE);
34	if (rc) {
35		dev_err(tps65217_bl->dev,
36			"failed to enable backlight: %d\n", rc);
37		return rc;
38	}
39
40	tps65217_bl->is_enabled = true;
41
42	dev_dbg(tps65217_bl->dev, "backlight enabled\n");
43
44	return 0;
45}
46
47static int tps65217_bl_disable(struct tps65217_bl *tps65217_bl)
48{
49	int rc;
50
51	rc = tps65217_clear_bits(tps65217_bl->tps,
52				TPS65217_REG_WLEDCTRL1,
53				TPS65217_WLEDCTRL1_ISINK_ENABLE,
54				TPS65217_PROTECT_NONE);
55	if (rc) {
56		dev_err(tps65217_bl->dev,
57			"failed to disable backlight: %d\n", rc);
58		return rc;
59	}
60
61	tps65217_bl->is_enabled = false;
62
63	dev_dbg(tps65217_bl->dev, "backlight disabled\n");
64
65	return 0;
66}
67
68static int tps65217_bl_update_status(struct backlight_device *bl)
69{
70	struct tps65217_bl *tps65217_bl = bl_get_data(bl);
71	int rc;
72	int brightness = backlight_get_brightness(bl);
73
74	if (brightness > 0) {
75		rc = tps65217_reg_write(tps65217_bl->tps,
76					TPS65217_REG_WLEDCTRL2,
77					brightness - 1,
78					TPS65217_PROTECT_NONE);
79		if (rc) {
80			dev_err(tps65217_bl->dev,
81				"failed to set brightness level: %d\n", rc);
82			return rc;
83		}
84
85		dev_dbg(tps65217_bl->dev, "brightness set to %d\n", brightness);
86
87		if (!tps65217_bl->is_enabled)
88			rc = tps65217_bl_enable(tps65217_bl);
89	} else {
90		rc = tps65217_bl_disable(tps65217_bl);
91	}
92
93	return rc;
94}
95
96static const struct backlight_ops tps65217_bl_ops = {
97	.options	= BL_CORE_SUSPENDRESUME,
98	.update_status	= tps65217_bl_update_status,
99};
100
101static int tps65217_bl_hw_init(struct tps65217_bl *tps65217_bl,
102			struct tps65217_bl_pdata *pdata)
103{
104	int rc;
105
106	rc = tps65217_bl_disable(tps65217_bl);
107	if (rc)
108		return rc;
109
110	switch (pdata->isel) {
111	case TPS65217_BL_ISET1:
112		/* select ISET_1 current level */
113		rc = tps65217_clear_bits(tps65217_bl->tps,
114					TPS65217_REG_WLEDCTRL1,
115					TPS65217_WLEDCTRL1_ISEL,
116					TPS65217_PROTECT_NONE);
117		if (rc) {
118			dev_err(tps65217_bl->dev,
119				"failed to select ISET1 current level: %d)\n",
120				rc);
121			return rc;
122		}
123
124		dev_dbg(tps65217_bl->dev, "selected ISET1 current level\n");
125
126		break;
127
128	case TPS65217_BL_ISET2:
129		/* select ISET2 current level */
130		rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
131				TPS65217_WLEDCTRL1_ISEL,
132				TPS65217_WLEDCTRL1_ISEL, TPS65217_PROTECT_NONE);
133		if (rc) {
134			dev_err(tps65217_bl->dev,
135				"failed to select ISET2 current level: %d\n",
136				rc);
137			return rc;
138		}
139
140		dev_dbg(tps65217_bl->dev, "selected ISET2 current level\n");
141
142		break;
143
144	default:
145		dev_err(tps65217_bl->dev,
146			"invalid value for current level: %d\n", pdata->isel);
147		return -EINVAL;
148	}
149
150	/* set PWM frequency */
151	rc = tps65217_set_bits(tps65217_bl->tps,
152			TPS65217_REG_WLEDCTRL1,
153			TPS65217_WLEDCTRL1_FDIM_MASK,
154			pdata->fdim,
155			TPS65217_PROTECT_NONE);
156	if (rc) {
157		dev_err(tps65217_bl->dev,
158			"failed to select PWM dimming frequency: %d\n",
159			rc);
160		return rc;
161	}
162
163	return 0;
164}
165
166#ifdef CONFIG_OF
167static struct tps65217_bl_pdata *
168tps65217_bl_parse_dt(struct platform_device *pdev)
169{
170	struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
171	struct device_node *node;
172	struct tps65217_bl_pdata *pdata, *err;
173	u32 val;
174
175	node = of_get_child_by_name(tps->dev->of_node, "backlight");
176	if (!node)
177		return ERR_PTR(-ENODEV);
178
179	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
180	if (!pdata) {
181		err = ERR_PTR(-ENOMEM);
182		goto err;
183	}
184
185	pdata->isel = TPS65217_BL_ISET1;
186	if (!of_property_read_u32(node, "isel", &val)) {
187		if (val < TPS65217_BL_ISET1 ||
188			val > TPS65217_BL_ISET2) {
189			dev_err(&pdev->dev,
190				"invalid 'isel' value in the device tree\n");
191			err = ERR_PTR(-EINVAL);
192			goto err;
193		}
194
195		pdata->isel = val;
196	}
197
198	pdata->fdim = TPS65217_BL_FDIM_200HZ;
199	if (!of_property_read_u32(node, "fdim", &val)) {
200		switch (val) {
201		case 100:
202			pdata->fdim = TPS65217_BL_FDIM_100HZ;
203			break;
204
205		case 200:
206			pdata->fdim = TPS65217_BL_FDIM_200HZ;
207			break;
208
209		case 500:
210			pdata->fdim = TPS65217_BL_FDIM_500HZ;
211			break;
212
213		case 1000:
214			pdata->fdim = TPS65217_BL_FDIM_1000HZ;
215			break;
216
217		default:
218			dev_err(&pdev->dev,
219				"invalid 'fdim' value in the device tree\n");
220			err = ERR_PTR(-EINVAL);
221			goto err;
222		}
223	}
224
225	if (!of_property_read_u32(node, "default-brightness", &val)) {
226		if (val > 100) {
227			dev_err(&pdev->dev,
228				"invalid 'default-brightness' value in the device tree\n");
229			err = ERR_PTR(-EINVAL);
230			goto err;
231		}
232
233		pdata->dft_brightness = val;
234	}
235
236	of_node_put(node);
237
238	return pdata;
239
240err:
241	of_node_put(node);
242
243	return err;
244}
245#else
246static struct tps65217_bl_pdata *
247tps65217_bl_parse_dt(struct platform_device *pdev)
248{
249	return NULL;
250}
251#endif
252
253static int tps65217_bl_probe(struct platform_device *pdev)
254{
255	int rc;
256	struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
257	struct tps65217_bl *tps65217_bl;
258	struct tps65217_bl_pdata *pdata;
259	struct backlight_properties bl_props;
260
261	pdata = tps65217_bl_parse_dt(pdev);
262	if (IS_ERR(pdata))
263		return PTR_ERR(pdata);
264
265	tps65217_bl = devm_kzalloc(&pdev->dev, sizeof(*tps65217_bl),
266				GFP_KERNEL);
267	if (tps65217_bl == NULL)
268		return -ENOMEM;
269
270	tps65217_bl->tps = tps;
271	tps65217_bl->dev = &pdev->dev;
272	tps65217_bl->is_enabled = false;
273
274	rc = tps65217_bl_hw_init(tps65217_bl, pdata);
275	if (rc)
276		return rc;
277
278	memset(&bl_props, 0, sizeof(struct backlight_properties));
279	bl_props.type = BACKLIGHT_RAW;
280	bl_props.max_brightness = 100;
281
282	tps65217_bl->bl = devm_backlight_device_register(&pdev->dev, pdev->name,
283						tps65217_bl->dev, tps65217_bl,
284						&tps65217_bl_ops, &bl_props);
285	if (IS_ERR(tps65217_bl->bl)) {
286		dev_err(tps65217_bl->dev,
287			"registration of backlight device failed: %d\n", rc);
288		return PTR_ERR(tps65217_bl->bl);
289	}
290
291	tps65217_bl->bl->props.brightness = pdata->dft_brightness;
292	backlight_update_status(tps65217_bl->bl);
293	platform_set_drvdata(pdev, tps65217_bl);
294
295	return 0;
296}
297
298#ifdef CONFIG_OF
299static const struct of_device_id tps65217_bl_of_match[] = {
300	{ .compatible = "ti,tps65217-bl", },
301	{ /* sentinel */ },
302};
303MODULE_DEVICE_TABLE(of, tps65217_bl_of_match);
304#endif
305
306static struct platform_driver tps65217_bl_driver = {
307	.probe		= tps65217_bl_probe,
308	.driver		= {
309		.name	= "tps65217-bl",
310		.of_match_table = of_match_ptr(tps65217_bl_of_match),
311	},
312};
313
314module_platform_driver(tps65217_bl_driver);
315
316MODULE_DESCRIPTION("TPS65217 Backlight driver");
317MODULE_LICENSE("GPL v2");
318MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>");
319