blob: bf64a593fbf17ddabc505bdea1c4d36d8d636b15 [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>
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
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 {
79 struct regmap *regmap;
80 u16 addr;
81
82 struct led_classdev cdev;
83
84 struct pm8941_wled_config cfg;
85};
86
87static 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
131static 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
141static 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
213static 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
224struct pm8941_wled_var_cfg {
225 const u32 *values;
226 u32 (*fn)(u32);
227 int size;
228};
229
230static const u32 pm8941_wled_i_boost_limit_values[] = {
231 105, 385, 525, 805, 980, 1260, 1400, 1680,
232};
233
234static 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
239static const u32 pm8941_wled_ovp_values[] = {
240 35, 32, 29, 27,
241};
242
243static 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
248static u32 pm8941_wled_num_strings_values_fn(u32 idx)
249{
250 return idx + 1;
251}
252
253static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
254 .fn = pm8941_wled_num_strings_values_fn,
255 .size = 3,
256};
257
258static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
259{
260 return 19200 / (2 * (1 + idx));
261}
262
263static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
264 .fn = pm8941_wled_switch_freq_values_fn,
265 .size = 16,
266};
267
268static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
269 .size = 26,
270};
271
272static 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
283static 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
380static 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
417static const struct of_device_id pm8941_wled_match_table[] = {
418 { .compatible = "qcom,pm8941-wled" },
419 {}
420};
421MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
422
423static 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
431module_platform_driver(pm8941_wled_driver);
432
433MODULE_DESCRIPTION("pm8941 wled driver");
434MODULE_LICENSE("GPL v2");
435MODULE_ALIAS("platform:pm8941-wled");