Commit | Line | Data |
---|---|---|
2aec85b2 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
eebfdc17 MK |
2 | /* |
3 | * tps65217_bl.c | |
4 | * | |
5 | * TPS65217 backlight driver | |
6 | * | |
7 | * Copyright (C) 2012 Matthias Kaehlcke | |
8 | * Author: Matthias Kaehlcke <matthias@kaehlcke.net> | |
eebfdc17 MK |
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 | ||
20 | struct tps65217_bl { | |
21 | struct tps65217 *tps; | |
22 | struct device *dev; | |
23 | struct backlight_device *bl; | |
24 | bool is_enabled; | |
25 | }; | |
26 | ||
27 | static 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 | ||
47 | static 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 | ||
68 | static int tps65217_bl_update_status(struct backlight_device *bl) | |
69 | { | |
70 | struct tps65217_bl *tps65217_bl = bl_get_data(bl); | |
71 | int rc; | |
51d53e5b | 72 | int brightness = backlight_get_brightness(bl); |
eebfdc17 MK |
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 | ||
eebfdc17 MK |
96 | static const struct backlight_ops tps65217_bl_ops = { |
97 | .options = BL_CORE_SUSPENDRESUME, | |
98 | .update_status = tps65217_bl_update_status, | |
eebfdc17 MK |
99 | }; |
100 | ||
101 | static 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 | |
167 | static struct tps65217_bl_pdata * | |
168 | tps65217_bl_parse_dt(struct platform_device *pdev) | |
169 | { | |
170 | struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); | |
2b12dfa1 | 171 | struct device_node *node; |
eebfdc17 MK |
172 | struct tps65217_bl_pdata *pdata, *err; |
173 | u32 val; | |
174 | ||
2b12dfa1 | 175 | node = of_get_child_by_name(tps->dev->of_node, "backlight"); |
eebfdc17 MK |
176 | if (!node) |
177 | return ERR_PTR(-ENODEV); | |
178 | ||
179 | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | |
180 | if (!pdata) { | |
eebfdc17 MK |
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 | ||
4d22f8c3 | 225 | if (!of_property_read_u32(node, "default-brightness", &val)) { |
5158cc2d | 226 | if (val > 100) { |
4d22f8c3 MU |
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 | ||
eebfdc17 MK |
236 | of_node_put(node); |
237 | ||
238 | return pdata; | |
239 | ||
240 | err: | |
241 | of_node_put(node); | |
242 | ||
243 | return err; | |
244 | } | |
245 | #else | |
246 | static struct tps65217_bl_pdata * | |
247 | tps65217_bl_parse_dt(struct platform_device *pdev) | |
248 | { | |
249 | return NULL; | |
250 | } | |
251 | #endif | |
252 | ||
253 | static 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 | ||
511cb174 K |
261 | pdata = tps65217_bl_parse_dt(pdev); |
262 | if (IS_ERR(pdata)) | |
263 | return PTR_ERR(pdata); | |
eebfdc17 MK |
264 | |
265 | tps65217_bl = devm_kzalloc(&pdev->dev, sizeof(*tps65217_bl), | |
266 | GFP_KERNEL); | |
3d8e4b40 | 267 | if (tps65217_bl == NULL) |
eebfdc17 | 268 | return -ENOMEM; |
eebfdc17 MK |
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 | ||
626d0908 | 282 | tps65217_bl->bl = devm_backlight_device_register(&pdev->dev, pdev->name, |
eebfdc17 MK |
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 | ||
4d22f8c3 MU |
291 | tps65217_bl->bl->props.brightness = pdata->dft_brightness; |
292 | backlight_update_status(tps65217_bl->bl); | |
c6bed9de | 293 | platform_set_drvdata(pdev, tps65217_bl); |
eebfdc17 MK |
294 | |
295 | return 0; | |
296 | } | |
297 | ||
fcf13f0b EBS |
298 | #ifdef CONFIG_OF |
299 | static const struct of_device_id tps65217_bl_of_match[] = { | |
300 | { .compatible = "ti,tps65217-bl", }, | |
301 | { /* sentinel */ }, | |
302 | }; | |
303 | MODULE_DEVICE_TABLE(of, tps65217_bl_of_match); | |
304 | #endif | |
305 | ||
eebfdc17 MK |
306 | static struct platform_driver tps65217_bl_driver = { |
307 | .probe = tps65217_bl_probe, | |
eebfdc17 | 308 | .driver = { |
eebfdc17 | 309 | .name = "tps65217-bl", |
fcf13f0b | 310 | .of_match_table = of_match_ptr(tps65217_bl_of_match), |
eebfdc17 MK |
311 | }, |
312 | }; | |
313 | ||
c6bed9de | 314 | module_platform_driver(tps65217_bl_driver); |
eebfdc17 MK |
315 | |
316 | MODULE_DESCRIPTION("TPS65217 Backlight driver"); | |
317 | MODULE_LICENSE("GPL v2"); | |
318 | MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>"); |