Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
[firefly-linux-kernel-4.4.55.git] / drivers / video / backlight / 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/backlight.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         const char *name;
80         struct regmap *regmap;
81         u16 addr;
82
83         struct pm8941_wled_config cfg;
84 };
85
86 static int pm8941_wled_update_status(struct backlight_device *bl)
87 {
88         struct pm8941_wled *wled = bl_get_data(bl);
89         u16 val = bl->props.brightness;
90         u8 ctrl = 0;
91         int rc;
92         int i;
93
94         if (bl->props.power != FB_BLANK_UNBLANK ||
95             bl->props.fb_blank != FB_BLANK_UNBLANK ||
96             bl->props.state & BL_CORE_FBBLANK)
97                 val = 0;
98
99         if (val != 0)
100                 ctrl = PM8941_WLED_REG_MOD_EN_BIT;
101
102         rc = regmap_update_bits(wled->regmap,
103                         wled->addr + PM8941_WLED_REG_MOD_EN,
104                         PM8941_WLED_REG_MOD_EN_MASK, ctrl);
105         if (rc)
106                 return rc;
107
108         for (i = 0; i < wled->cfg.num_strings; ++i) {
109                 u8 v[2] = { val & 0xff, (val >> 8) & 0xf };
110
111                 rc = regmap_bulk_write(wled->regmap,
112                                 wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i,
113                                 v, 2);
114                 if (rc)
115                         return rc;
116         }
117
118         rc = regmap_update_bits(wled->regmap,
119                         wled->addr + PM8941_WLED_REG_SYNC,
120                         PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL);
121         if (rc)
122                 return rc;
123
124         rc = regmap_update_bits(wled->regmap,
125                         wled->addr + PM8941_WLED_REG_SYNC,
126                         PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR);
127         return rc;
128 }
129
130 static int pm8941_wled_setup(struct pm8941_wled *wled)
131 {
132         int rc;
133         int i;
134
135         rc = regmap_update_bits(wled->regmap,
136                         wled->addr + PM8941_WLED_REG_OVP,
137                         PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp);
138         if (rc)
139                 return rc;
140
141         rc = regmap_update_bits(wled->regmap,
142                         wled->addr + PM8941_WLED_REG_BOOST,
143                         PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit);
144         if (rc)
145                 return rc;
146
147         rc = regmap_update_bits(wled->regmap,
148                         wled->addr + PM8941_WLED_REG_FREQ,
149                         PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq);
150         if (rc)
151                 return rc;
152
153         if (wled->cfg.cs_out_en) {
154                 u8 all = (BIT(wled->cfg.num_strings) - 1)
155                                 << PM8941_WLED_REG_SINK_SHFT;
156
157                 rc = regmap_update_bits(wled->regmap,
158                                 wled->addr + PM8941_WLED_REG_SINK,
159                                 PM8941_WLED_REG_SINK_MASK, all);
160                 if (rc)
161                         return rc;
162         }
163
164         for (i = 0; i < wled->cfg.num_strings; ++i) {
165                 u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i;
166
167                 rc = regmap_update_bits(wled->regmap,
168                                 addr + PM8941_WLED_REG_STR_MOD_EN_BASE,
169                                 PM8941_WLED_REG_STR_MOD_MASK,
170                                 PM8941_WLED_REG_STR_MOD_EN);
171                 if (rc)
172                         return rc;
173
174                 if (wled->cfg.ext_gen) {
175                         rc = regmap_update_bits(wled->regmap,
176                                         addr + PM8941_WLED_REG_STR_MOD_SRC_BASE,
177                                         PM8941_WLED_REG_STR_MOD_SRC_MASK,
178                                         PM8941_WLED_REG_STR_MOD_SRC_EXT);
179                         if (rc)
180                                 return rc;
181                 }
182
183                 rc = regmap_update_bits(wled->regmap,
184                                 addr + PM8941_WLED_REG_STR_SCALE_BASE,
185                                 PM8941_WLED_REG_STR_SCALE_MASK,
186                                 wled->cfg.i_limit);
187                 if (rc)
188                         return rc;
189
190                 rc = regmap_update_bits(wled->regmap,
191                                 addr + PM8941_WLED_REG_STR_CABC_BASE,
192                                 PM8941_WLED_REG_STR_CABC_MASK,
193                                 wled->cfg.cabc_en ?
194                                         PM8941_WLED_REG_STR_CABC_EN : 0);
195                 if (rc)
196                         return rc;
197         }
198
199         return 0;
200 }
201
202 static const struct pm8941_wled_config pm8941_wled_config_defaults = {
203         .i_boost_limit = 3,
204         .i_limit = 20,
205         .ovp = 2,
206         .switch_freq = 5,
207         .num_strings = 0,
208         .cs_out_en = false,
209         .ext_gen = false,
210         .cabc_en = false,
211 };
212
213 struct pm8941_wled_var_cfg {
214         const u32 *values;
215         u32 (*fn)(u32);
216         int size;
217 };
218
219 static const u32 pm8941_wled_i_boost_limit_values[] = {
220         105, 385, 525, 805, 980, 1260, 1400, 1680,
221 };
222
223 static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = {
224         .values = pm8941_wled_i_boost_limit_values,
225         .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values),
226 };
227
228 static const u32 pm8941_wled_ovp_values[] = {
229         35, 32, 29, 27,
230 };
231
232 static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = {
233         .values = pm8941_wled_ovp_values,
234         .size = ARRAY_SIZE(pm8941_wled_ovp_values),
235 };
236
237 static u32 pm8941_wled_num_strings_values_fn(u32 idx)
238 {
239         return idx + 1;
240 }
241
242 static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
243         .fn = pm8941_wled_num_strings_values_fn,
244         .size = 3,
245 };
246
247 static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
248 {
249         return 19200 / (2 * (1 + idx));
250 }
251
252 static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
253         .fn = pm8941_wled_switch_freq_values_fn,
254         .size = 16,
255 };
256
257 static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
258         .size = 26,
259 };
260
261 static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx)
262 {
263         if (idx >= cfg->size)
264                 return UINT_MAX;
265         if (cfg->fn)
266                 return cfg->fn(idx);
267         if (cfg->values)
268                 return cfg->values[idx];
269         return idx;
270 }
271
272 static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev)
273 {
274         struct pm8941_wled_config *cfg = &wled->cfg;
275         u32 val;
276         int rc;
277         u32 c;
278         int i;
279         int j;
280
281         const struct {
282                 const char *name;
283                 u32 *val_ptr;
284                 const struct pm8941_wled_var_cfg *cfg;
285         } u32_opts[] = {
286                 {
287                         "qcom,current-boost-limit",
288                         &cfg->i_boost_limit,
289                         .cfg = &pm8941_wled_i_boost_limit_cfg,
290                 },
291                 {
292                         "qcom,current-limit",
293                         &cfg->i_limit,
294                         .cfg = &pm8941_wled_i_limit_cfg,
295                 },
296                 {
297                         "qcom,ovp",
298                         &cfg->ovp,
299                         .cfg = &pm8941_wled_ovp_cfg,
300                 },
301                 {
302                         "qcom,switching-freq",
303                         &cfg->switch_freq,
304                         .cfg = &pm8941_wled_switch_freq_cfg,
305                 },
306                 {
307                         "qcom,num-strings",
308                         &cfg->num_strings,
309                         .cfg = &pm8941_wled_num_strings_cfg,
310                 },
311         };
312         const struct {
313                 const char *name;
314                 bool *val_ptr;
315         } bool_opts[] = {
316                 { "qcom,cs-out", &cfg->cs_out_en, },
317                 { "qcom,ext-gen", &cfg->ext_gen, },
318                 { "qcom,cabc", &cfg->cabc_en, },
319         };
320
321         rc = of_property_read_u32(dev->of_node, "reg", &val);
322         if (rc || val > 0xffff) {
323                 dev_err(dev, "invalid IO resources\n");
324                 return rc ? rc : -EINVAL;
325         }
326         wled->addr = val;
327
328         rc = of_property_read_string(dev->of_node, "label", &wled->name);
329         if (rc)
330                 wled->name = dev->of_node->name;
331
332         *cfg = pm8941_wled_config_defaults;
333         for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
334                 rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
335                 if (rc == -EINVAL) {
336                         continue;
337                 } else if (rc) {
338                         dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
339                         return rc;
340                 }
341
342                 c = UINT_MAX;
343                 for (j = 0; c != val; j++) {
344                         c = pm8941_wled_values(u32_opts[i].cfg, j);
345                         if (c == UINT_MAX) {
346                                 dev_err(dev, "invalid value for '%s'\n",
347                                         u32_opts[i].name);
348                                 return -EINVAL;
349                         }
350                 }
351
352                 dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c);
353                 *u32_opts[i].val_ptr = j;
354         }
355
356         for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
357                 if (of_property_read_bool(dev->of_node, bool_opts[i].name))
358                         *bool_opts[i].val_ptr = true;
359         }
360
361         cfg->num_strings = cfg->num_strings + 1;
362
363         return 0;
364 }
365
366 static const struct backlight_ops pm8941_wled_ops = {
367         .update_status = pm8941_wled_update_status,
368 };
369
370 static int pm8941_wled_probe(struct platform_device *pdev)
371 {
372         struct backlight_properties props;
373         struct backlight_device *bl;
374         struct pm8941_wled *wled;
375         struct regmap *regmap;
376         int rc;
377
378         regmap = dev_get_regmap(pdev->dev.parent, NULL);
379         if (!regmap) {
380                 dev_err(&pdev->dev, "Unable to get regmap\n");
381                 return -EINVAL;
382         }
383
384         wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
385         if (!wled)
386                 return -ENOMEM;
387
388         wled->regmap = regmap;
389
390         rc = pm8941_wled_configure(wled, &pdev->dev);
391         if (rc)
392                 return rc;
393
394         rc = pm8941_wled_setup(wled);
395         if (rc)
396                 return rc;
397
398         memset(&props, 0, sizeof(struct backlight_properties));
399         props.type = BACKLIGHT_RAW;
400         props.max_brightness = PM8941_WLED_REG_VAL_MAX;
401         bl = devm_backlight_device_register(&pdev->dev, wled->name,
402                                             &pdev->dev, wled,
403                                             &pm8941_wled_ops, &props);
404         if (IS_ERR(bl))
405                 return PTR_ERR(bl);
406
407         return 0;
408 };
409
410 static const struct of_device_id pm8941_wled_match_table[] = {
411         { .compatible = "qcom,pm8941-wled" },
412         {}
413 };
414 MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
415
416 static struct platform_driver pm8941_wled_driver = {
417         .probe = pm8941_wled_probe,
418         .driver = {
419                 .name = "pm8941-wled",
420                 .of_match_table = pm8941_wled_match_table,
421         },
422 };
423
424 module_platform_driver(pm8941_wled_driver);
425
426 MODULE_DESCRIPTION("pm8941 wled driver");
427 MODULE_LICENSE("GPL v2");