summaryrefslogtreecommitdiff
path: root/drivers/video/backlight/sky81452-backlight.c
blob: 2749231f0385449b530287cfd7a021c4eeaab288 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// SPDX-License-Identifier: GPL-2.0-only
/*
 * sky81452-backlight.c	SKY81452 backlight driver
 *
 * Copyright 2014 Skyworks Solutions Inc.
 * Author : Gyungoh Yoo <jack.yoo@skyworksinc.com>
 */

#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>

/* registers */
#define SKY81452_REG0	0x00
#define SKY81452_REG1	0x01
#define SKY81452_REG2	0x02
#define SKY81452_REG4	0x04
#define SKY81452_REG5	0x05

/* bit mask */
#define SKY81452_CS	0xFF
#define SKY81452_EN	0x3F
#define SKY81452_IGPW	0x20
#define SKY81452_PWMMD	0x10
#define SKY81452_PHASE	0x08
#define SKY81452_ILIM	0x04
#define SKY81452_VSHRT	0x03
#define SKY81452_OCP	0x80
#define SKY81452_OTMP	0x40
#define SKY81452_SHRT	0x3F
#define SKY81452_OPN	0x3F

#define SKY81452_DEFAULT_NAME "lcd-backlight"
#define SKY81452_MAX_BRIGHTNESS	(SKY81452_CS + 1)

/**
 * struct sky81452_bl_platform_data - backlight platform data
 * @name:	backlight driver name.
 *		If it is not defined, default name is lcd-backlight.
 * @gpiod_enable:GPIO descriptor which control EN pin
 * @enable:	Enable mask for current sink channel 1, 2, 3, 4, 5 and 6.
 * @ignore_pwm:	true if DPWMI should be ignored.
 * @dpwm_mode:	true is DPWM dimming mode, otherwise Analog dimming mode.
 * @phase_shift:true is phase shift mode.
 * @short_detection_threshold:	It should be one of 4, 5, 6 and 7V.
 * @boost_current_limit:	It should be one of 2300, 2750mA.
 */
struct sky81452_bl_platform_data {
	const char *name;
	struct gpio_desc *gpiod_enable;
	unsigned int enable;
	bool ignore_pwm;
	bool dpwm_mode;
	bool phase_shift;
	unsigned int short_detection_threshold;
	unsigned int boost_current_limit;
};

#define CTZ(b) __builtin_ctz(b)

static int sky81452_bl_update_status(struct backlight_device *bd)
{
	const struct sky81452_bl_platform_data *pdata =
			dev_get_platdata(bd->dev.parent);
	const unsigned int brightness = (unsigned int)bd->props.brightness;
	struct regmap *regmap = bl_get_data(bd);
	int ret;

	if (brightness > 0) {
		ret = regmap_write(regmap, SKY81452_REG0, brightness - 1);
		if (ret < 0)
			return ret;

		return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN,
					pdata->enable << CTZ(SKY81452_EN));
	}

	return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN, 0);
}

static const struct backlight_ops sky81452_bl_ops = {
	.update_status = sky81452_bl_update_status,
};

static ssize_t sky81452_bl_store_enable(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct regmap *regmap = bl_get_data(to_backlight_device(dev));
	unsigned long value;
	int ret;

	ret = kstrtoul(buf, 16, &value);
	if (ret < 0)
		return ret;

	ret = regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN,
					value << CTZ(SKY81452_EN));
	if (ret < 0)
		return ret;

	return count;
}

static ssize_t sky81452_bl_show_open_short(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct regmap *regmap = bl_get_data(to_backlight_device(dev));
	unsigned int reg, value = 0;
	char tmp[3];
	int i, ret;

	reg = !strcmp(attr->attr.name, "open") ? SKY81452_REG5 : SKY81452_REG4;
	ret = regmap_read(regmap, reg, &value);
	if (ret < 0)
		return ret;

	if (value & SKY81452_SHRT) {
		*buf = 0;
		for (i = 0; i < 6; i++) {
			if (value & 0x01) {
				sprintf(tmp, "%d ", i + 1);
				strcat(buf, tmp);
			}
			value >>= 1;
		}
		strcat(buf, "\n");
	} else {
		strcpy(buf, "none\n");
	}

	return strlen(buf);
}

static ssize_t sky81452_bl_show_fault(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct regmap *regmap = bl_get_data(to_backlight_device(dev));
	unsigned int value = 0;
	int ret;

	ret = regmap_read(regmap, SKY81452_REG4, &value);
	if (ret < 0)
		return ret;

	*buf = 0;

	if (value & SKY81452_OCP)
		strcat(buf, "over-current ");

	if (value & SKY81452_OTMP)
		strcat(buf, "over-temperature");

	strcat(buf, "\n");
	return strlen(buf);
}

static DEVICE_ATTR(enable, S_IWGRP | S_IWUSR, NULL, sky81452_bl_store_enable);
static DEVICE_ATTR(open, S_IRUGO, sky81452_bl_show_open_short, NULL);
static DEVICE_ATTR(short, S_IRUGO, sky81452_bl_show_open_short, NULL);
static DEVICE_ATTR(fault, S_IRUGO, sky81452_bl_show_fault, NULL);

static struct attribute *sky81452_bl_attribute[] = {
	&dev_attr_enable.attr,
	&dev_attr_open.attr,
	&dev_attr_short.attr,
	&dev_attr_fault.attr,
	NULL
};

static const struct attribute_group sky81452_bl_attr_group = {
	.attrs = sky81452_bl_attribute,
};

#ifdef CONFIG_OF
static struct sky81452_bl_platform_data *sky81452_bl_parse_dt(
							struct device *dev)
{
	struct device_node *np = dev->of_node;
	struct sky81452_bl_platform_data *pdata;
	int num_entry;
	unsigned int sources[6];
	int ret;

	if (!np) {
		dev_err(dev, "backlight node not found.\n");
		return ERR_PTR(-ENODATA);
	}

	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
	if (!pdata)
		return ERR_PTR(-ENOMEM);

	of_property_read_string(np, "name", &pdata->name);
	pdata->ignore_pwm = of_property_read_bool(np, "skyworks,ignore-pwm");
	pdata->dpwm_mode = of_property_read_bool(np, "skyworks,dpwm-mode");
	pdata->phase_shift = of_property_read_bool(np, "skyworks,phase-shift");
	pdata->gpiod_enable = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH);

	ret = of_property_count_u32_elems(np, "led-sources");
	if (ret < 0) {
		pdata->enable = SKY81452_EN >> CTZ(SKY81452_EN);
	} else {
		num_entry = ret;
		if (num_entry > 6)
			num_entry = 6;

		ret = of_property_read_u32_array(np, "led-sources", sources,
					num_entry);
		if (ret < 0) {
			dev_err(dev, "led-sources node is invalid.\n");
			return ERR_PTR(-EINVAL);
		}

		pdata->enable = 0;
		while (--num_entry)
			pdata->enable |= (1 << sources[num_entry]);
	}

	ret = of_property_read_u32(np,
			"skyworks,short-detection-threshold-volt",
			&pdata->short_detection_threshold);
	if (ret < 0)
		pdata->short_detection_threshold = 7;

	ret = of_property_read_u32(np, "skyworks,current-limit-mA",
			&pdata->boost_current_limit);
	if (ret < 0)
		pdata->boost_current_limit = 2750;

	return pdata;
}
#else
static struct sky81452_bl_platform_data *sky81452_bl_parse_dt(
							struct device *dev)
{
	return ERR_PTR(-EINVAL);
}
#endif

static int sky81452_bl_init_device(struct regmap *regmap,
		struct sky81452_bl_platform_data *pdata)
{
	unsigned int value;

	value = pdata->ignore_pwm ? SKY81452_IGPW : 0;
	value |= pdata->dpwm_mode ? SKY81452_PWMMD : 0;
	value |= pdata->phase_shift ? 0 : SKY81452_PHASE;

	if (pdata->boost_current_limit == 2300)
		value |= SKY81452_ILIM;
	else if (pdata->boost_current_limit != 2750)
		return -EINVAL;

	if (pdata->short_detection_threshold < 4 ||
				pdata->short_detection_threshold > 7)
		return -EINVAL;
	value |= (7 - pdata->short_detection_threshold) << CTZ(SKY81452_VSHRT);

	return regmap_write(regmap, SKY81452_REG2, value);
}

static int sky81452_bl_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct regmap *regmap = dev_get_drvdata(dev->parent);
	struct sky81452_bl_platform_data *pdata;
	struct backlight_device *bd;
	struct backlight_properties props;
	const char *name;
	int ret;

	pdata = sky81452_bl_parse_dt(dev);
	if (IS_ERR(pdata))
		return PTR_ERR(pdata);

	ret = sky81452_bl_init_device(regmap, pdata);
	if (ret < 0) {
		dev_err(dev, "failed to initialize. err=%d\n", ret);
		return ret;
	}

	memset(&props, 0, sizeof(props));
	props.max_brightness = SKY81452_MAX_BRIGHTNESS;
	name = pdata->name ? pdata->name : SKY81452_DEFAULT_NAME;
	bd = devm_backlight_device_register(dev, name, dev, regmap,
						&sky81452_bl_ops, &props);
	if (IS_ERR(bd)) {
		dev_err(dev, "failed to register. err=%ld\n", PTR_ERR(bd));
		return PTR_ERR(bd);
	}

	platform_set_drvdata(pdev, bd);

	ret = sysfs_create_group(&bd->dev.kobj, &sky81452_bl_attr_group);
	if (ret < 0) {
		dev_err(dev, "failed to create attribute. err=%d\n", ret);
		return ret;
	}

	return ret;
}

static void sky81452_bl_remove(struct platform_device *pdev)
{
	const struct sky81452_bl_platform_data *pdata =
						dev_get_platdata(&pdev->dev);
	struct backlight_device *bd = platform_get_drvdata(pdev);

	sysfs_remove_group(&bd->dev.kobj, &sky81452_bl_attr_group);

	bd->props.power = BACKLIGHT_POWER_ON;
	bd->props.brightness = 0;
	backlight_update_status(bd);

	if (pdata->gpiod_enable)
		gpiod_set_value_cansleep(pdata->gpiod_enable, 0);
}

#ifdef CONFIG_OF
static const struct of_device_id sky81452_bl_of_match[] = {
	{ .compatible = "skyworks,sky81452-backlight", },
	{ }
};
MODULE_DEVICE_TABLE(of, sky81452_bl_of_match);
#endif

static struct platform_driver sky81452_bl_driver = {
	.driver = {
		.name = "sky81452-backlight",
		.of_match_table = of_match_ptr(sky81452_bl_of_match),
	},
	.probe = sky81452_bl_probe,
	.remove = sky81452_bl_remove,
};

module_platform_driver(sky81452_bl_driver);

MODULE_DESCRIPTION("Skyworks SKY81452 backlight driver");
MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@skyworksinc.com>");
MODULE_LICENSE("GPL v2");