]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - drivers/leds/leds-pm8941-wled.c
Merge branch 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus
[mirror_ubuntu-bionic-kernel.git] / drivers / leds / leds-pm8941-wled.c
1 /* Copyright (c) 2015, Sony Mobile Communications, AB.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13 #include <linux/kernel.h>
14 #include <linux/leds.h>
15 #include <linux/module.h>
16 #include <linux/of.h>
17 #include <linux/of_device.h>
18 #include <linux/regmap.h>
19
20 #define PM8941_WLED_REG_VAL_BASE 0x40
21 #define PM8941_WLED_REG_VAL_MAX 0xFFF
22
23 #define PM8941_WLED_REG_MOD_EN 0x46
24 #define PM8941_WLED_REG_MOD_EN_BIT BIT(7)
25 #define PM8941_WLED_REG_MOD_EN_MASK BIT(7)
26
27 #define PM8941_WLED_REG_SYNC 0x47
28 #define PM8941_WLED_REG_SYNC_MASK 0x07
29 #define PM8941_WLED_REG_SYNC_LED1 BIT(0)
30 #define PM8941_WLED_REG_SYNC_LED2 BIT(1)
31 #define PM8941_WLED_REG_SYNC_LED3 BIT(2)
32 #define PM8941_WLED_REG_SYNC_ALL 0x07
33 #define PM8941_WLED_REG_SYNC_CLEAR 0x00
34
35 #define PM8941_WLED_REG_FREQ 0x4c
36 #define PM8941_WLED_REG_FREQ_MASK 0x0f
37
38 #define PM8941_WLED_REG_OVP 0x4d
39 #define PM8941_WLED_REG_OVP_MASK 0x03
40
41 #define PM8941_WLED_REG_BOOST 0x4e
42 #define PM8941_WLED_REG_BOOST_MASK 0x07
43
44 #define PM8941_WLED_REG_SINK 0x4f
45 #define PM8941_WLED_REG_SINK_MASK 0xe0
46 #define PM8941_WLED_REG_SINK_SHFT 0x05
47
48 /* Per-'string' registers below */
49 #define PM8941_WLED_REG_STR_OFFSET 0x10
50
51 #define PM8941_WLED_REG_STR_MOD_EN_BASE 0x60
52 #define PM8941_WLED_REG_STR_MOD_MASK BIT(7)
53 #define PM8941_WLED_REG_STR_MOD_EN BIT(7)
54
55 #define PM8941_WLED_REG_STR_SCALE_BASE 0x62
56 #define PM8941_WLED_REG_STR_SCALE_MASK 0x1f
57
58 #define PM8941_WLED_REG_STR_MOD_SRC_BASE 0x63
59 #define PM8941_WLED_REG_STR_MOD_SRC_MASK 0x01
60 #define PM8941_WLED_REG_STR_MOD_SRC_INT 0x00
61 #define PM8941_WLED_REG_STR_MOD_SRC_EXT 0x01
62
63 #define PM8941_WLED_REG_STR_CABC_BASE 0x66
64 #define PM8941_WLED_REG_STR_CABC_MASK BIT(7)
65 #define PM8941_WLED_REG_STR_CABC_EN BIT(7)
66
67 struct pm8941_wled_config {
68 u32 i_boost_limit;
69 u32 ovp;
70 u32 switch_freq;
71 u32 num_strings;
72 u32 i_limit;
73 bool cs_out_en;
74 bool ext_gen;
75 bool cabc_en;
76 };
77
78 struct pm8941_wled {
79 struct regmap *regmap;
80 u16 addr;
81
82 struct led_classdev cdev;
83
84 struct pm8941_wled_config cfg;
85 };
86
87 static int pm8941_wled_set(struct led_classdev *cdev,
88 enum led_brightness value)
89 {
90 struct pm8941_wled *wled;
91 u8 ctrl = 0;
92 u16 val;
93 int rc;
94 int i;
95
96 wled = container_of(cdev, struct pm8941_wled, cdev);
97
98 if (value != 0)
99 ctrl = PM8941_WLED_REG_MOD_EN_BIT;
100
101 val = value * PM8941_WLED_REG_VAL_MAX / LED_FULL;
102
103 rc = regmap_update_bits(wled->regmap,
104 wled->addr + PM8941_WLED_REG_MOD_EN,
105 PM8941_WLED_REG_MOD_EN_MASK, ctrl);
106 if (rc)
107 return rc;
108
109 for (i = 0; i < wled->cfg.num_strings; ++i) {
110 u8 v[2] = { val & 0xff, (val >> 8) & 0xf };
111
112 rc = regmap_bulk_write(wled->regmap,
113 wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i,
114 v, 2);
115 if (rc)
116 return rc;
117 }
118
119 rc = regmap_update_bits(wled->regmap,
120 wled->addr + PM8941_WLED_REG_SYNC,
121 PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL);
122 if (rc)
123 return rc;
124
125 rc = regmap_update_bits(wled->regmap,
126 wled->addr + PM8941_WLED_REG_SYNC,
127 PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR);
128 return rc;
129 }
130
131 static void pm8941_wled_set_brightness(struct led_classdev *cdev,
132 enum led_brightness value)
133 {
134 if (pm8941_wled_set(cdev, value)) {
135 dev_err(cdev->dev, "Unable to set brightness\n");
136 return;
137 }
138 cdev->brightness = value;
139 }
140
141 static int pm8941_wled_setup(struct pm8941_wled *wled)
142 {
143 int rc;
144 int i;
145
146 rc = regmap_update_bits(wled->regmap,
147 wled->addr + PM8941_WLED_REG_OVP,
148 PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp);
149 if (rc)
150 return rc;
151
152 rc = regmap_update_bits(wled->regmap,
153 wled->addr + PM8941_WLED_REG_BOOST,
154 PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit);
155 if (rc)
156 return rc;
157
158 rc = regmap_update_bits(wled->regmap,
159 wled->addr + PM8941_WLED_REG_FREQ,
160 PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq);
161 if (rc)
162 return rc;
163
164 if (wled->cfg.cs_out_en) {
165 u8 all = (BIT(wled->cfg.num_strings) - 1)
166 << PM8941_WLED_REG_SINK_SHFT;
167
168 rc = regmap_update_bits(wled->regmap,
169 wled->addr + PM8941_WLED_REG_SINK,
170 PM8941_WLED_REG_SINK_MASK, all);
171 if (rc)
172 return rc;
173 }
174
175 for (i = 0; i < wled->cfg.num_strings; ++i) {
176 u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i;
177
178 rc = regmap_update_bits(wled->regmap,
179 addr + PM8941_WLED_REG_STR_MOD_EN_BASE,
180 PM8941_WLED_REG_STR_MOD_MASK,
181 PM8941_WLED_REG_STR_MOD_EN);
182 if (rc)
183 return rc;
184
185 if (wled->cfg.ext_gen) {
186 rc = regmap_update_bits(wled->regmap,
187 addr + PM8941_WLED_REG_STR_MOD_SRC_BASE,
188 PM8941_WLED_REG_STR_MOD_SRC_MASK,
189 PM8941_WLED_REG_STR_MOD_SRC_EXT);
190 if (rc)
191 return rc;
192 }
193
194 rc = regmap_update_bits(wled->regmap,
195 addr + PM8941_WLED_REG_STR_SCALE_BASE,
196 PM8941_WLED_REG_STR_SCALE_MASK,
197 wled->cfg.i_limit);
198 if (rc)
199 return rc;
200
201 rc = regmap_update_bits(wled->regmap,
202 addr + PM8941_WLED_REG_STR_CABC_BASE,
203 PM8941_WLED_REG_STR_CABC_MASK,
204 wled->cfg.cabc_en ?
205 PM8941_WLED_REG_STR_CABC_EN : 0);
206 if (rc)
207 return rc;
208 }
209
210 return 0;
211 }
212
213 static const struct pm8941_wled_config pm8941_wled_config_defaults = {
214 .i_boost_limit = 3,
215 .i_limit = 20,
216 .ovp = 2,
217 .switch_freq = 5,
218 .num_strings = 0,
219 .cs_out_en = false,
220 .ext_gen = false,
221 .cabc_en = false,
222 };
223
224 struct pm8941_wled_var_cfg {
225 const u32 *values;
226 u32 (*fn)(u32);
227 int size;
228 };
229
230 static const u32 pm8941_wled_i_boost_limit_values[] = {
231 105, 385, 525, 805, 980, 1260, 1400, 1680,
232 };
233
234 static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = {
235 .values = pm8941_wled_i_boost_limit_values,
236 .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values),
237 };
238
239 static const u32 pm8941_wled_ovp_values[] = {
240 35, 32, 29, 27,
241 };
242
243 static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = {
244 .values = pm8941_wled_ovp_values,
245 .size = ARRAY_SIZE(pm8941_wled_ovp_values),
246 };
247
248 static u32 pm8941_wled_num_strings_values_fn(u32 idx)
249 {
250 return idx + 1;
251 }
252
253 static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
254 .fn = pm8941_wled_num_strings_values_fn,
255 .size = 3,
256 };
257
258 static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
259 {
260 return 19200 / (2 * (1 + idx));
261 }
262
263 static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
264 .fn = pm8941_wled_switch_freq_values_fn,
265 .size = 16,
266 };
267
268 static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
269 .size = 26,
270 };
271
272 static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx)
273 {
274 if (idx >= cfg->size)
275 return UINT_MAX;
276 if (cfg->fn)
277 return cfg->fn(idx);
278 if (cfg->values)
279 return cfg->values[idx];
280 return idx;
281 }
282
283 static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev)
284 {
285 struct pm8941_wled_config *cfg = &wled->cfg;
286 u32 val;
287 int rc;
288 u32 c;
289 int i;
290 int j;
291
292 const struct {
293 const char *name;
294 u32 *val_ptr;
295 const struct pm8941_wled_var_cfg *cfg;
296 } u32_opts[] = {
297 {
298 "qcom,current-boost-limit",
299 &cfg->i_boost_limit,
300 .cfg = &pm8941_wled_i_boost_limit_cfg,
301 },
302 {
303 "qcom,current-limit",
304 &cfg->i_limit,
305 .cfg = &pm8941_wled_i_limit_cfg,
306 },
307 {
308 "qcom,ovp",
309 &cfg->ovp,
310 .cfg = &pm8941_wled_ovp_cfg,
311 },
312 {
313 "qcom,switching-freq",
314 &cfg->switch_freq,
315 .cfg = &pm8941_wled_switch_freq_cfg,
316 },
317 {
318 "qcom,num-strings",
319 &cfg->num_strings,
320 .cfg = &pm8941_wled_num_strings_cfg,
321 },
322 };
323 const struct {
324 const char *name;
325 bool *val_ptr;
326 } bool_opts[] = {
327 { "qcom,cs-out", &cfg->cs_out_en, },
328 { "qcom,ext-gen", &cfg->ext_gen, },
329 { "qcom,cabc", &cfg->cabc_en, },
330 };
331
332 rc = of_property_read_u32(dev->of_node, "reg", &val);
333 if (rc || val > 0xffff) {
334 dev_err(dev, "invalid IO resources\n");
335 return rc ? rc : -EINVAL;
336 }
337 wled->addr = val;
338
339 rc = of_property_read_string(dev->of_node, "label", &wled->cdev.name);
340 if (rc)
341 wled->cdev.name = dev->of_node->name;
342
343 wled->cdev.default_trigger = of_get_property(dev->of_node,
344 "linux,default-trigger", NULL);
345
346 *cfg = pm8941_wled_config_defaults;
347 for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
348 rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
349 if (rc == -EINVAL) {
350 continue;
351 } else if (rc) {
352 dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
353 return rc;
354 }
355
356 c = UINT_MAX;
357 for (j = 0; c != val; j++) {
358 c = pm8941_wled_values(u32_opts[i].cfg, j);
359 if (c == UINT_MAX) {
360 dev_err(dev, "invalid value for '%s'\n",
361 u32_opts[i].name);
362 return -EINVAL;
363 }
364 }
365
366 dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c);
367 *u32_opts[i].val_ptr = j;
368 }
369
370 for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
371 if (of_property_read_bool(dev->of_node, bool_opts[i].name))
372 *bool_opts[i].val_ptr = true;
373 }
374
375 cfg->num_strings = cfg->num_strings + 1;
376
377 return 0;
378 }
379
380 static int pm8941_wled_probe(struct platform_device *pdev)
381 {
382 struct pm8941_wled *wled;
383 struct regmap *regmap;
384 int rc;
385
386 regmap = dev_get_regmap(pdev->dev.parent, NULL);
387 if (!regmap) {
388 dev_err(&pdev->dev, "Unable to get regmap\n");
389 return -EINVAL;
390 }
391
392 wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
393 if (!wled)
394 return -ENOMEM;
395
396 wled->regmap = regmap;
397
398 rc = pm8941_wled_configure(wled, &pdev->dev);
399 if (rc)
400 return rc;
401
402 rc = pm8941_wled_setup(wled);
403 if (rc)
404 return rc;
405
406 wled->cdev.brightness_set = pm8941_wled_set_brightness;
407
408 rc = devm_led_classdev_register(&pdev->dev, &wled->cdev);
409 if (rc)
410 return rc;
411
412 platform_set_drvdata(pdev, wled);
413
414 return 0;
415 };
416
417 static const struct of_device_id pm8941_wled_match_table[] = {
418 { .compatible = "qcom,pm8941-wled" },
419 {}
420 };
421 MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
422
423 static struct platform_driver pm8941_wled_driver = {
424 .probe = pm8941_wled_probe,
425 .driver = {
426 .name = "pm8941-wled",
427 .of_match_table = pm8941_wled_match_table,
428 },
429 };
430
431 module_platform_driver(pm8941_wled_driver);
432
433 MODULE_DESCRIPTION("pm8941 wled driver");
434 MODULE_LICENSE("GPL v2");
435 MODULE_ALIAS("platform:pm8941-wled");