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(tps: 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(tps: 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_dev: bl);
71 int rc;
72 int brightness = backlight_get_brightness(bd: bl);
73
74 if (brightness > 0) {
75 rc = tps65217_reg_write(tps: tps65217_bl->tps,
76 TPS65217_REG_WLEDCTRL2,
77 val: 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(tps: 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(tps: 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(tps: tps65217_bl->tps,
152 TPS65217_REG_WLEDCTRL1,
153 TPS65217_WLEDCTRL1_FDIM_MASK,
154 val: 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(dev: 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(node: tps->dev->of_node, name: "backlight");
176 if (!node)
177 return ERR_PTR(error: -ENODEV);
178
179 pdata = devm_kzalloc(dev: &pdev->dev, size: sizeof(*pdata), GFP_KERNEL);
180 if (!pdata) {
181 err = ERR_PTR(error: -ENOMEM);
182 goto err;
183 }
184
185 pdata->isel = TPS65217_BL_ISET1;
186 if (!of_property_read_u32(np: node, propname: "isel", out_value: &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(error: -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(np: node, propname: "fdim", out_value: &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(error: -EINVAL);
221 goto err;
222 }
223 }
224
225 if (!of_property_read_u32(np: node, propname: "default-brightness", out_value: &val)) {
226 if (val > 100) {
227 dev_err(&pdev->dev,
228 "invalid 'default-brightness' value in the device tree\n");
229 err = ERR_PTR(error: -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(dev: 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(ptr: pdata))
263 return PTR_ERR(ptr: pdata);
264
265 tps65217_bl = devm_kzalloc(dev: &pdev->dev, size: 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(dev: &pdev->dev, name: pdev->name,
283 parent: tps65217_bl->dev, devdata: tps65217_bl,
284 ops: &tps65217_bl_ops, props: &bl_props);
285 if (IS_ERR(ptr: tps65217_bl->bl)) {
286 dev_err(tps65217_bl->dev,
287 "registration of backlight device failed: %d\n", rc);
288 return PTR_ERR(ptr: tps65217_bl->bl);
289 }
290
291 tps65217_bl->bl->props.brightness = pdata->dft_brightness;
292 backlight_update_status(bd: tps65217_bl->bl);
293 platform_set_drvdata(pdev, data: 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

source code of linux/drivers/video/backlight/tps65217_bl.c