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 
pm8941_wled_update_status(struct backlight_device * bl)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 
pm8941_wled_setup(struct pm8941_wled * wled)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 
pm8941_wled_num_strings_values_fn(u32 idx)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 
pm8941_wled_switch_freq_values_fn(u32 idx)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 
pm8941_wled_values(const struct pm8941_wled_var_cfg * cfg,u32 idx)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 
pm8941_wled_configure(struct pm8941_wled * wled,struct device * dev)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 
pm8941_wled_probe(struct platform_device * pdev)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");
433