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 245
	pb->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable",
						  GPIOD_OUT_HIGH);
246 247
	if (IS_ERR(pb->enable_gpio)) {
		ret = PTR_ERR(pb->enable_gpio);
248
		goto err_alloc;
249
	}
250

251 252 253 254 255 256 257
	/*
	 * 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");
258 259
		if (ret < 0) {
			dev_err(&pdev->dev, "failed to request GPIO#%d: %d\n",
260
				data->enable_gpio, ret);
261 262
			goto err_alloc;
		}
263 264

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

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

273
	pb->pwm = devm_pwm_get(&pdev->dev, NULL);
274
	if (IS_ERR(pb->pwm)) {
275 276 277 278
		ret = PTR_ERR(pb->pwm);
		if (ret == -EPROBE_DEFER)
			goto err_alloc;

279
		dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
280
		pb->legacy = true;
281 282 283 284
		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);
285
			goto err_alloc;
286 287 288 289 290 291 292 293
		}
	}

	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,
294 295
	 * set the period from platform data if it has not already been set
	 * via the PWM lookup table.
296
	 */
297 298 299
	pb->period = pwm_get_period(pb->pwm);
	if (!pb->period && (data->pwm_period_ns > 0)) {
		pb->period = data->pwm_period_ns;
300
		pwm_set_period(pb->pwm, data->pwm_period_ns);
301
	}
302

303
	pb->lth_brightness = data->lth_brightness * (pb->period / pb->scale);
304

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

316 317 318 319 320 321 322
	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;
	}

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

	platform_set_drvdata(pdev, bl);
	return 0;
328 329 330 331 332

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

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

	backlight_device_unregister(bl);
341
	pwm_backlight_power_off(pb);
342

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

348 349 350
	return 0;
}

351 352 353 354 355 356 357 358
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);
}

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

365
	if (pb->notify)
366
		pb->notify(pb->dev, 0);
367

368
	pwm_backlight_power_off(pb);
369

370 371
	if (pb->notify_after)
		pb->notify_after(pb->dev, 0);
372

373 374 375
	return 0;
}

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

	backlight_update_status(bl);
381

382 383
	return 0;
}
384
#endif
385

386 387 388 389 390 391 392 393
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
};
394

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

406
module_platform_driver(pwm_backlight_driver);
407 408 409

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