blob: c704c3236034fd43dd5ccd719007b28b26d99736 [file] [log] [blame]
Courtney Cavin93c64f12015-03-12 08:47:08 -07001/* 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>
Bjorn Andersson7ddbc242015-07-21 17:44:49 -070014#include <linux/backlight.h>
Courtney Cavin93c64f12015-03-12 08:47:08 -070015#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
67struct 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
78struct pm8941_wled {
Bjorn Andersson7ddbc242015-07-21 17:44:49 -070079 const char *name;
Courtney Cavin93c64f12015-03-12 08:47:08 -070080 struct regmap *regmap;
81 u16 addr;
82
Courtney Cavin93c64f12015-03-12 08:47:08 -070083 struct pm8941_wled_config cfg;
84};
85
Bjorn Andersson7ddbc242015-07-21 17:44:49 -070086static int pm8941_wled_update_status(struct backlight_device *bl)
Courtney Cavin93c64f12015-03-12 08:47:08 -070087{
Bjorn Andersson7ddbc242015-07-21 17:44:49 -070088 struct pm8941_wled *wled = bl_get_data(bl);
89 u16 val = bl->props.brightness;
Courtney Cavin93c64f12015-03-12 08:47:08 -070090 u8 ctrl = 0;
Courtney Cavin93c64f12015-03-12 08:47:08 -070091 int rc;
92 int i;
93
Bjorn Andersson7ddbc242015-07-21 17:44:49 -070094 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;
Courtney Cavin93c64f12015-03-12 08:47:08 -070098
Bjorn Andersson7ddbc242015-07-21 17:44:49 -070099 if (val != 0)
Courtney Cavin93c64f12015-03-12 08:47:08 -0700100 ctrl = PM8941_WLED_REG_MOD_EN_BIT;
101
Courtney Cavin93c64f12015-03-12 08:47:08 -0700102 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
Courtney Cavin93c64f12015-03-12 08:47:08 -0700130static 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
202static 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
213struct pm8941_wled_var_cfg {
214 const u32 *values;
215 u32 (*fn)(u32);
216 int size;
217};
218
219static const u32 pm8941_wled_i_boost_limit_values[] = {
220 105, 385, 525, 805, 980, 1260, 1400, 1680,
221};
222
223static 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
228static const u32 pm8941_wled_ovp_values[] = {
229 35, 32, 29, 27,
230};
231
232static 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
237static u32 pm8941_wled_num_strings_values_fn(u32 idx)
238{
239 return idx + 1;
240}
241
242static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
243 .fn = pm8941_wled_num_strings_values_fn,
244 .size = 3,
245};
246
247static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
248{
249 return 19200 / (2 * (1 + idx));
250}
251
252static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
253 .fn = pm8941_wled_switch_freq_values_fn,
254 .size = 16,
255};
256
257static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
258 .size = 26,
259};
260
261static 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
272static 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
Bjorn Andersson7ddbc242015-07-21 17:44:49 -0700328 rc = of_property_read_string(dev->of_node, "label", &wled->name);
Courtney Cavin93c64f12015-03-12 08:47:08 -0700329 if (rc)
Bjorn Andersson7ddbc242015-07-21 17:44:49 -0700330 wled->name = dev->of_node->name;
Courtney Cavin93c64f12015-03-12 08:47:08 -0700331
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
Bjorn Andersson7ddbc242015-07-21 17:44:49 -0700366static const struct backlight_ops pm8941_wled_ops = {
367 .update_status = pm8941_wled_update_status,
368};
369
Courtney Cavin93c64f12015-03-12 08:47:08 -0700370static int pm8941_wled_probe(struct platform_device *pdev)
371{
Bjorn Andersson7ddbc242015-07-21 17:44:49 -0700372 struct backlight_properties props;
373 struct backlight_device *bl;
Courtney Cavin93c64f12015-03-12 08:47:08 -0700374 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
Bjorn Andersson7ddbc242015-07-21 17:44:49 -0700398 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);
Courtney Cavin93c64f12015-03-12 08:47:08 -0700406
407 return 0;
408};
409
410static const struct of_device_id pm8941_wled_match_table[] = {
411 { .compatible = "qcom,pm8941-wled" },
412 {}
413};
414MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
415
416static 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
424module_platform_driver(pm8941_wled_driver);
425
426MODULE_DESCRIPTION("pm8941 wled driver");
427MODULE_LICENSE("GPL v2");