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 | |
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(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 | |
47 | static 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 | |
68 | static 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 | |
96 | static const struct backlight_ops tps65217_bl_ops = { |
97 | .options = BL_CORE_SUSPENDRESUME, |
98 | .update_status = tps65217_bl_update_status, |
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(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 |
167 | static struct tps65217_bl_pdata * |
168 | tps65217_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 | |
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(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 |
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 | |
306 | static 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 | |
314 | module_platform_driver(tps65217_bl_driver); |
315 | |
316 | MODULE_DESCRIPTION("TPS65217 Backlight driver" ); |
317 | MODULE_LICENSE("GPL v2" ); |
318 | MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>" ); |
319 | |