summaryrefslogtreecommitdiffstats
path: root/drivers/video/backlight/ktd253-backlight.c
blob: d7b287cffd5cb6ef69915df6ee514b1f8521ba07 (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
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Backlight driver for the Kinetic KTD253
 * Based on code and know-how from the Samsung GT-S7710
 * Gareth Phillips <gareth.phillips@samsung.com>
 */
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>

/* Current ratio is n/32 from 1/32 to 32/32 */
#define KTD253_MIN_RATIO 1
#define KTD253_MAX_RATIO 32
#define KTD253_DEFAULT_RATIO 13

#define KTD253_T_LOW_NS (200 + 10) /* Additional 10ns as safety factor */
#define KTD253_T_HIGH_NS (200 + 10) /* Additional 10ns as safety factor */
#define KTD253_T_OFF_MS 3

struct ktd253_backlight {
	struct device *dev;
	struct backlight_device *bl;
	struct gpio_desc *gpiod;
	u16 ratio;
};

static int ktd253_backlight_update_status(struct backlight_device *bl)
{
	struct ktd253_backlight *ktd253 = bl_get_data(bl);
	int brightness = backlight_get_brightness(bl);
	u16 target_ratio;
	u16 current_ratio = ktd253->ratio;
	unsigned long flags;

	dev_dbg(ktd253->dev, "new brightness/ratio: %d/32\n", brightness);

	target_ratio = brightness;

	if (target_ratio == current_ratio)
		/* This is already right */
		return 0;

	if (target_ratio == 0) {
		gpiod_set_value_cansleep(ktd253->gpiod, 0);
		/*
		 * We need to keep the GPIO low for at least this long
		 * to actually switch the KTD253 off.
		 */
		msleep(KTD253_T_OFF_MS);
		ktd253->ratio = 0;
		return 0;
	}

	if (current_ratio == 0) {
		gpiod_set_value_cansleep(ktd253->gpiod, 1);
		ndelay(KTD253_T_HIGH_NS);
		/* We always fall back to this when we power on */
		current_ratio = KTD253_MAX_RATIO;
	}

	/*
	 * WARNING:
	 * The loop to set the correct current level is performed
	 * with interrupts disabled as it is timing critical.
	 * The maximum number of cycles of the loop is 32
	 * so the time taken will be (T_LOW_NS + T_HIGH_NS + loop_time) * 32,
	 */
	local_irq_save(flags);
	while (current_ratio != target_ratio) {
		/*
		 * These GPIO operations absolutely can NOT sleep so no
		 * _cansleep suffixes, and no using GPIO expanders on
		 * slow buses for this!
		 */
		gpiod_set_value(ktd253->gpiod, 0);
		ndelay(KTD253_T_LOW_NS);
		gpiod_set_value(ktd253->gpiod, 1);
		ndelay(KTD253_T_HIGH_NS);
		/* After 1/32 we loop back to 32/32 */
		if (current_ratio == KTD253_MIN_RATIO)
			current_ratio = KTD253_MAX_RATIO;
		else
			current_ratio--;
	}
	local_irq_restore(flags);
	ktd253->ratio = current_ratio;

	dev_dbg(ktd253->dev, "new ratio set to %d/32\n", target_ratio);

	return 0;
}

static const struct backlight_ops ktd253_backlight_ops = {
	.options	= BL_CORE_SUSPENDRESUME,
	.update_status	= ktd253_backlight_update_status,
};

static int ktd253_backlight_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct backlight_device *bl;
	struct ktd253_backlight *ktd253;
	u32 max_brightness;
	u32 brightness;
	int ret;

	ktd253 = devm_kzalloc(dev, sizeof(*ktd253), GFP_KERNEL);
	if (!ktd253)
		return -ENOMEM;
	ktd253->dev = dev;

	ret = device_property_read_u32(dev, "max-brightness", &max_brightness);
	if (ret)
		max_brightness = KTD253_MAX_RATIO;
	if (max_brightness > KTD253_MAX_RATIO) {
		/* Clamp brightness to hardware max */
		dev_err(dev, "illegal max brightness specified\n");
		max_brightness = KTD253_MAX_RATIO;
	}

	ret = device_property_read_u32(dev, "default-brightness", &brightness);
	if (ret)
		brightness = KTD253_DEFAULT_RATIO;
	if (brightness > max_brightness) {
		/* Clamp default brightness to max brightness */
		dev_err(dev, "default brightness exceeds max brightness\n");
		brightness = max_brightness;
	}

	ktd253->gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
	if (IS_ERR(ktd253->gpiod)) {
		ret = PTR_ERR(ktd253->gpiod);
		if (ret != -EPROBE_DEFER)
			dev_err(dev, "gpio line missing or invalid.\n");
		return ret;
	}
	gpiod_set_consumer_name(ktd253->gpiod, dev_name(dev));
	/* Bring backlight to a known off state */
	msleep(KTD253_T_OFF_MS);

	bl = devm_backlight_device_register(dev, dev_name(dev), dev, ktd253,
					    &ktd253_backlight_ops, NULL);
	if (IS_ERR(bl)) {
		dev_err(dev, "failed to register backlight\n");
		return PTR_ERR(bl);
	}
	bl->props.max_brightness = max_brightness;
	/* When we just enable the GPIO line we set max brightness */
	if (brightness) {
		bl->props.brightness = brightness;
		bl->props.power = FB_BLANK_UNBLANK;
	} else {
		bl->props.brightness = 0;
		bl->props.power = FB_BLANK_POWERDOWN;
	}

	ktd253->bl = bl;
	platform_set_drvdata(pdev, bl);
	backlight_update_status(bl);

	return 0;
}

static const struct of_device_id ktd253_backlight_of_match[] = {
	{ .compatible = "kinetic,ktd253" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ktd253_backlight_of_match);

static struct platform_driver ktd253_backlight_driver = {
	.driver = {
		.name = "ktd253-backlight",
		.of_match_table = ktd253_backlight_of_match,
	},
	.probe		= ktd253_backlight_probe,
};
module_platform_driver(ktd253_backlight_driver);

MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("Kinetic KTD253 Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ktd253-backlight");