PM / Domains: Try power off masters in error path of __pm_genpd_poweron()
[linux/fpc-iii.git] / drivers / leds / leds-pm8941-wled.c
blobbf64a593fbf17ddabc505bdea1c4d36d8d636b15
1 /* Copyright (c) 2015, Sony Mobile Communications, AB.
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.
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.
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>
20 #define PM8941_WLED_REG_VAL_BASE 0x40
21 #define PM8941_WLED_REG_VAL_MAX 0xFFF
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)
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
35 #define PM8941_WLED_REG_FREQ 0x4c
36 #define PM8941_WLED_REG_FREQ_MASK 0x0f
38 #define PM8941_WLED_REG_OVP 0x4d
39 #define PM8941_WLED_REG_OVP_MASK 0x03
41 #define PM8941_WLED_REG_BOOST 0x4e
42 #define PM8941_WLED_REG_BOOST_MASK 0x07
44 #define PM8941_WLED_REG_SINK 0x4f
45 #define PM8941_WLED_REG_SINK_MASK 0xe0
46 #define PM8941_WLED_REG_SINK_SHFT 0x05
48 /* Per-'string' registers below */
49 #define PM8941_WLED_REG_STR_OFFSET 0x10
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)
55 #define PM8941_WLED_REG_STR_SCALE_BASE 0x62
56 #define PM8941_WLED_REG_STR_SCALE_MASK 0x1f
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
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)
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;
78 struct pm8941_wled {
79 struct regmap *regmap;
80 u16 addr;
82 struct led_classdev cdev;
84 struct pm8941_wled_config cfg;
87 static int pm8941_wled_set(struct led_classdev *cdev,
88 enum led_brightness value)
90 struct pm8941_wled *wled;
91 u8 ctrl = 0;
92 u16 val;
93 int rc;
94 int i;
96 wled = container_of(cdev, struct pm8941_wled, cdev);
98 if (value != 0)
99 ctrl = PM8941_WLED_REG_MOD_EN_BIT;
101 val = value * PM8941_WLED_REG_VAL_MAX / LED_FULL;
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;
109 for (i = 0; i < wled->cfg.num_strings; ++i) {
110 u8 v[2] = { val & 0xff, (val >> 8) & 0xf };
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;
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;
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;
131 static void pm8941_wled_set_brightness(struct led_classdev *cdev,
132 enum led_brightness value)
134 if (pm8941_wled_set(cdev, value)) {
135 dev_err(cdev->dev, "Unable to set brightness\n");
136 return;
138 cdev->brightness = value;
141 static int pm8941_wled_setup(struct pm8941_wled *wled)
143 int rc;
144 int i;
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;
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;
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;
164 if (wled->cfg.cs_out_en) {
165 u8 all = (BIT(wled->cfg.num_strings) - 1)
166 << PM8941_WLED_REG_SINK_SHFT;
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;
175 for (i = 0; i < wled->cfg.num_strings; ++i) {
176 u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i;
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;
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;
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;
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;
210 return 0;
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,
224 struct pm8941_wled_var_cfg {
225 const u32 *values;
226 u32 (*fn)(u32);
227 int size;
230 static const u32 pm8941_wled_i_boost_limit_values[] = {
231 105, 385, 525, 805, 980, 1260, 1400, 1680,
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),
239 static const u32 pm8941_wled_ovp_values[] = {
240 35, 32, 29, 27,
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),
248 static u32 pm8941_wled_num_strings_values_fn(u32 idx)
250 return idx + 1;
253 static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
254 .fn = pm8941_wled_num_strings_values_fn,
255 .size = 3,
258 static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
260 return 19200 / (2 * (1 + idx));
263 static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
264 .fn = pm8941_wled_switch_freq_values_fn,
265 .size = 16,
268 static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
269 .size = 26,
272 static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx)
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;
283 static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev)
285 struct pm8941_wled_config *cfg = &wled->cfg;
286 u32 val;
287 int rc;
288 u32 c;
289 int i;
290 int j;
292 const struct {
293 const char *name;
294 u32 *val_ptr;
295 const struct pm8941_wled_var_cfg *cfg;
296 } u32_opts[] = {
298 "qcom,current-boost-limit",
299 &cfg->i_boost_limit,
300 .cfg = &pm8941_wled_i_boost_limit_cfg,
303 "qcom,current-limit",
304 &cfg->i_limit,
305 .cfg = &pm8941_wled_i_limit_cfg,
308 "qcom,ovp",
309 &cfg->ovp,
310 .cfg = &pm8941_wled_ovp_cfg,
313 "qcom,switching-freq",
314 &cfg->switch_freq,
315 .cfg = &pm8941_wled_switch_freq_cfg,
318 "qcom,num-strings",
319 &cfg->num_strings,
320 .cfg = &pm8941_wled_num_strings_cfg,
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, },
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;
337 wled->addr = val;
339 rc = of_property_read_string(dev->of_node, "label", &wled->cdev.name);
340 if (rc)
341 wled->cdev.name = dev->of_node->name;
343 wled->cdev.default_trigger = of_get_property(dev->of_node,
344 "linux,default-trigger", NULL);
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;
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;
366 dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c);
367 *u32_opts[i].val_ptr = j;
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;
375 cfg->num_strings = cfg->num_strings + 1;
377 return 0;
380 static int pm8941_wled_probe(struct platform_device *pdev)
382 struct pm8941_wled *wled;
383 struct regmap *regmap;
384 int rc;
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;
392 wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
393 if (!wled)
394 return -ENOMEM;
396 wled->regmap = regmap;
398 rc = pm8941_wled_configure(wled, &pdev->dev);
399 if (rc)
400 return rc;
402 rc = pm8941_wled_setup(wled);
403 if (rc)
404 return rc;
406 wled->cdev.brightness_set = pm8941_wled_set_brightness;
408 rc = devm_led_classdev_register(&pdev->dev, &wled->cdev);
409 if (rc)
410 return rc;
412 platform_set_drvdata(pdev, wled);
414 return 0;
417 static const struct of_device_id pm8941_wled_match_table[] = {
418 { .compatible = "qcom,pm8941-wled" },
421 MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
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,
431 module_platform_driver(pm8941_wled_driver);
433 MODULE_DESCRIPTION("pm8941 wled driver");
434 MODULE_LICENSE("GPL v2");
435 MODULE_ALIAS("platform:pm8941-wled");