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