pwm_bl.c 9.6 KB
Newer Older
1 2 3 4 5
/*
 * linux/drivers/video/backlight/pwm_bl.c
 *
 * simple PWM based backlight control, board code has to setup
 * 1) pin configuration so PWM waveforms can output
6
 * 2) platform_data being correctly configured
7 8 9 10 11 12
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

13
#include <linux/gpio/consumer.h>
14
#include <linux/gpio.h>
15 16 17 18 19 20 21 22 23
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
24
#include <linux/regulator/consumer.h>
25
#include <linux/slab.h>
26 27 28

struct pwm_bl_data {
	struct pwm_device	*pwm;
29
	struct device		*dev;
30
	unsigned int		period;
31
	unsigned int		lth_brightness;
32
	unsigned int		*levels;
T
Thierry Reding 已提交
33
	bool			enabled;
34
	struct regulator	*power_supply;
35
	struct gpio_desc	*enable_gpio;
36
	unsigned int		scale;
37
	bool			legacy;
38 39
	int			(*notify)(struct device *,
					  int brightness);
40 41
	void			(*notify_after)(struct device *,
					int brightness);
42
	int			(*check_fb)(struct device *, struct fb_info *);
43
	void			(*exit)(struct device *);
44 45
};

46
static void pwm_backlight_power_on(struct pwm_bl_data *pb, int brightness)
47
{
48
	int err;
49

T
Thierry Reding 已提交
50 51 52
	if (pb->enabled)
		return;

53 54 55 56
	err = regulator_enable(pb->power_supply);
	if (err < 0)
		dev_err(pb->dev, "failed to enable power supply\n");

57 58
	if (pb->enable_gpio)
		gpiod_set_value(pb->enable_gpio, 1);
59

60
	pwm_enable(pb->pwm);
T
Thierry Reding 已提交
61
	pb->enabled = true;
62 63 64 65
}

static void pwm_backlight_power_off(struct pwm_bl_data *pb)
{
T
Thierry Reding 已提交
66 67 68
	if (!pb->enabled)
		return;

69 70
	pwm_config(pb->pwm, 0, pb->period);
	pwm_disable(pb->pwm);
T
Thierry Reding 已提交
71

72 73
	if (pb->enable_gpio)
		gpiod_set_value(pb->enable_gpio, 0);
74

75
	regulator_disable(pb->power_supply);
T
Thierry Reding 已提交
76
	pb->enabled = false;
77 78
}

79 80 81 82 83 84 85 86 87 88 89 90 91
static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
{
	unsigned int lth = pb->lth_brightness;
	int duty_cycle;

	if (pb->levels)
		duty_cycle = pb->levels[brightness];
	else
		duty_cycle = brightness;

	return (duty_cycle * (pb->period - lth) / pb->scale) + lth;
}

92 93
static int pwm_backlight_update_status(struct backlight_device *bl)
{
94
	struct pwm_bl_data *pb = bl_get_data(bl);
95
	int brightness = bl->props.brightness;
96
	int duty_cycle;
97

98 99 100
	if (bl->props.power != FB_BLANK_UNBLANK ||
	    bl->props.fb_blank != FB_BLANK_UNBLANK ||
	    bl->props.state & BL_CORE_FBBLANK)
101 102
		brightness = 0;

103
	if (pb->notify)
104
		brightness = pb->notify(pb->dev, brightness);
105

106 107
	if (brightness > 0) {
		duty_cycle = compute_duty_cycle(pb, brightness);
108
		pwm_config(pb->pwm, duty_cycle, pb->period);
109
		pwm_backlight_power_on(pb, brightness);
110
	} else
111
		pwm_backlight_power_off(pb);
112 113 114 115

	if (pb->notify_after)
		pb->notify_after(pb->dev, brightness);

116 117 118
	return 0;
}

119 120 121
static int pwm_backlight_check_fb(struct backlight_device *bl,
				  struct fb_info *info)
{
122
	struct pwm_bl_data *pb = bl_get_data(bl);
123 124 125 126

	return !pb->check_fb || pb->check_fb(pb->dev, info);
}

127
static const struct backlight_ops pwm_backlight_ops = {
128
	.update_status	= pwm_backlight_update_status,
129
	.check_fb	= pwm_backlight_check_fb,
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
#ifdef CONFIG_OF
static int pwm_backlight_parse_dt(struct device *dev,
				  struct platform_pwm_backlight_data *data)
{
	struct device_node *node = dev->of_node;
	struct property *prop;
	int length;
	u32 value;
	int ret;

	if (!node)
		return -ENODEV;

	memset(data, 0, sizeof(*data));

	/* determine the number of brightness levels */
	prop = of_find_property(node, "brightness-levels", &length);
	if (!prop)
		return -EINVAL;

	data->max_brightness = length / sizeof(u32);

	/* read brightness levels from DT property */
	if (data->max_brightness > 0) {
		size_t size = sizeof(*data->levels) * data->max_brightness;

		data->levels = devm_kzalloc(dev, size, GFP_KERNEL);
		if (!data->levels)
			return -ENOMEM;

		ret = of_property_read_u32_array(node, "brightness-levels",
						 data->levels,
						 data->max_brightness);
		if (ret < 0)
			return ret;

		ret = of_property_read_u32(node, "default-brightness-level",
					   &value);
		if (ret < 0)
			return ret;

		data->dft_brightness = value;
		data->max_brightness--;
	}

177
	data->enable_gpio = -EINVAL;
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
	return 0;
}

static struct of_device_id pwm_backlight_of_match[] = {
	{ .compatible = "pwm-backlight" },
	{ }
};

MODULE_DEVICE_TABLE(of, pwm_backlight_of_match);
#else
static int pwm_backlight_parse_dt(struct device *dev,
				  struct platform_pwm_backlight_data *data)
{
	return -ENODEV;
}
#endif

195 196
static int pwm_backlight_probe(struct platform_device *pdev)
{
J
Jingoo Han 已提交
197
	struct platform_pwm_backlight_data *data = dev_get_platdata(&pdev->dev);
198 199
	struct platform_pwm_backlight_data defdata;
	struct backlight_properties props;
200 201
	struct backlight_device *bl;
	struct pwm_bl_data *pb;
202
	int ret;
203

204
	if (!data) {
205 206 207 208 209 210 211
		ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
		if (ret < 0) {
			dev_err(&pdev->dev, "failed to find platform data\n");
			return ret;
		}

		data = &defdata;
212
	}
213

214 215 216 217 218 219
	if (data->init) {
		ret = data->init(&pdev->dev);
		if (ret < 0)
			return ret;
	}

220
	pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);
221 222 223 224
	if (!pb) {
		ret = -ENOMEM;
		goto err_alloc;
	}
225

226
	if (data->levels) {
227 228 229 230 231 232
		unsigned int i;

		for (i = 0; i <= data->max_brightness; i++)
			if (data->levels[i] > pb->scale)
				pb->scale = data->levels[i];

233 234
		pb->levels = data->levels;
	} else
235
		pb->scale = data->max_brightness;
236

237
	pb->notify = data->notify;
238
	pb->notify_after = data->notify_after;
239
	pb->check_fb = data->check_fb;
240
	pb->exit = data->exit;
241
	pb->dev = &pdev->dev;
T
Thierry Reding 已提交
242
	pb->enabled = false;
243

244
	pb->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable");
245 246
	if (IS_ERR(pb->enable_gpio)) {
		ret = PTR_ERR(pb->enable_gpio);
247
		goto err_alloc;
248
	}
249

250 251 252 253 254 255 256
	/*
	 * Compatibility fallback for drivers still using the integer GPIO
	 * platform data. Must go away soon.
	 */
	if (!pb->enable_gpio && gpio_is_valid(data->enable_gpio)) {
		ret = devm_gpio_request_one(&pdev->dev, data->enable_gpio,
					    GPIOF_OUT_INIT_HIGH, "enable");
257 258
		if (ret < 0) {
			dev_err(&pdev->dev, "failed to request GPIO#%d: %d\n",
259
				data->enable_gpio, ret);
260 261
			goto err_alloc;
		}
262 263

		pb->enable_gpio = gpio_to_desc(data->enable_gpio);
264 265
	}

266 267 268
	if (pb->enable_gpio)
		gpiod_direction_output(pb->enable_gpio, 1);

269 270 271
	pb->power_supply = devm_regulator_get(&pdev->dev, "power");
	if (IS_ERR(pb->power_supply)) {
		ret = PTR_ERR(pb->power_supply);
272
		goto err_alloc;
273
	}
274

275
	pb->pwm = devm_pwm_get(&pdev->dev, NULL);
276
	if (IS_ERR(pb->pwm)) {
277
		dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
278
		pb->legacy = true;
279 280 281 282
		pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
		if (IS_ERR(pb->pwm)) {
			dev_err(&pdev->dev, "unable to request legacy PWM\n");
			ret = PTR_ERR(pb->pwm);
283
			goto err_alloc;
284 285 286 287 288 289 290 291
		}
	}

	dev_dbg(&pdev->dev, "got pwm for backlight\n");

	/*
	 * The DT case will set the pwm_period_ns field to 0 and store the
	 * period, parsed from the DT, in the PWM device. For the non-DT case,
292 293
	 * set the period from platform data if it has not already been set
	 * via the PWM lookup table.
294
	 */
295 296 297
	pb->period = pwm_get_period(pb->pwm);
	if (!pb->period && (data->pwm_period_ns > 0)) {
		pb->period = data->pwm_period_ns;
298
		pwm_set_period(pb->pwm, data->pwm_period_ns);
299
	}
300

301
	pb->lth_brightness = data->lth_brightness * (pb->period / pb->scale);
302

303
	memset(&props, 0, sizeof(struct backlight_properties));
M
Matthew Garrett 已提交
304
	props.type = BACKLIGHT_RAW;
305 306 307
	props.max_brightness = data->max_brightness;
	bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
				       &pwm_backlight_ops, &props);
308 309
	if (IS_ERR(bl)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
310
		ret = PTR_ERR(bl);
311
		goto err_alloc;
312 313
	}

314 315 316 317 318 319 320
	if (data->dft_brightness > data->max_brightness) {
		dev_warn(&pdev->dev,
			 "invalid default brightness level: %u, using %u\n",
			 data->dft_brightness, data->max_brightness);
		data->dft_brightness = data->max_brightness;
	}

321 322 323 324 325
	bl->props.brightness = data->dft_brightness;
	backlight_update_status(bl);

	platform_set_drvdata(pdev, bl);
	return 0;
326 327 328 329 330

err_alloc:
	if (data->exit)
		data->exit(&pdev->dev);
	return ret;
331 332 333 334 335
}

static int pwm_backlight_remove(struct platform_device *pdev)
{
	struct backlight_device *bl = platform_get_drvdata(pdev);
336
	struct pwm_bl_data *pb = bl_get_data(bl);
337 338

	backlight_device_unregister(bl);
339
	pwm_backlight_power_off(pb);
340

341 342
	if (pb->exit)
		pb->exit(&pdev->dev);
343 344
	if (pb->legacy)
		pwm_free(pb->pwm);
345

346 347 348
	return 0;
}

349 350 351 352 353 354 355 356
static void pwm_backlight_shutdown(struct platform_device *pdev)
{
	struct backlight_device *bl = platform_get_drvdata(pdev);
	struct pwm_bl_data *pb = bl_get_data(bl);

	pwm_backlight_power_off(pb);
}

357
#ifdef CONFIG_PM_SLEEP
358
static int pwm_backlight_suspend(struct device *dev)
359
{
360
	struct backlight_device *bl = dev_get_drvdata(dev);
361
	struct pwm_bl_data *pb = bl_get_data(bl);
362

363
	if (pb->notify)
364
		pb->notify(pb->dev, 0);
365

366
	pwm_backlight_power_off(pb);
367

368 369
	if (pb->notify_after)
		pb->notify_after(pb->dev, 0);
370

371 372 373
	return 0;
}

374
static int pwm_backlight_resume(struct device *dev)
375
{
376
	struct backlight_device *bl = dev_get_drvdata(dev);
377 378

	backlight_update_status(bl);
379

380 381
	return 0;
}
382
#endif
383

384 385 386 387 388 389 390 391
static const struct dev_pm_ops pwm_backlight_pm_ops = {
#ifdef CONFIG_PM_SLEEP
	.suspend = pwm_backlight_suspend,
	.resume = pwm_backlight_resume,
	.poweroff = pwm_backlight_suspend,
	.restore = pwm_backlight_resume,
#endif
};
392

393 394
static struct platform_driver pwm_backlight_driver = {
	.driver		= {
395 396 397
		.name		= "pwm-backlight",
		.pm		= &pwm_backlight_pm_ops,
		.of_match_table	= of_match_ptr(pwm_backlight_of_match),
398 399 400
	},
	.probe		= pwm_backlight_probe,
	.remove		= pwm_backlight_remove,
401
	.shutdown	= pwm_backlight_shutdown,
402 403
};

404
module_platform_driver(pwm_backlight_driver);
405 406 407

MODULE_DESCRIPTION("PWM based Backlight Driver");
MODULE_LICENSE("GPL");
408
MODULE_ALIAS("platform:pwm-backlight");