pwm_bl.c 5.2 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 13 14 15 16 17 18 19 20 21
 *
 * 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.
 */

#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>
22
#include <linux/slab.h>
23 24 25

struct pwm_bl_data {
	struct pwm_device	*pwm;
26
	struct device		*dev;
27
	unsigned int		period;
28
	unsigned int		lth_brightness;
29 30
	int			(*notify)(struct device *,
					  int brightness);
31 32
	void			(*notify_after)(struct device *,
					int brightness);
33
	int			(*check_fb)(struct device *, struct fb_info *);
34 35 36 37 38 39 40 41 42 43 44 45 46 47
};

static int pwm_backlight_update_status(struct backlight_device *bl)
{
	struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
	int brightness = bl->props.brightness;
	int max = bl->props.max_brightness;

	if (bl->props.power != FB_BLANK_UNBLANK)
		brightness = 0;

	if (bl->props.fb_blank != FB_BLANK_UNBLANK)
		brightness = 0;

48
	if (pb->notify)
49
		brightness = pb->notify(pb->dev, brightness);
50

51 52 53 54
	if (brightness == 0) {
		pwm_config(pb->pwm, 0, pb->period);
		pwm_disable(pb->pwm);
	} else {
55 56 57
		brightness = pb->lth_brightness +
			(brightness * (pb->period - pb->lth_brightness) / max);
		pwm_config(pb->pwm, brightness, pb->period);
58 59
		pwm_enable(pb->pwm);
	}
60 61 62 63

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

64 65 66 67 68 69 70 71
	return 0;
}

static int pwm_backlight_get_brightness(struct backlight_device *bl)
{
	return bl->props.brightness;
}

72 73 74 75 76 77 78 79
static int pwm_backlight_check_fb(struct backlight_device *bl,
				  struct fb_info *info)
{
	struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);

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

80
static const struct backlight_ops pwm_backlight_ops = {
81 82
	.update_status	= pwm_backlight_update_status,
	.get_brightness	= pwm_backlight_get_brightness,
83
	.check_fb	= pwm_backlight_check_fb,
84 85 86 87
};

static int pwm_backlight_probe(struct platform_device *pdev)
{
88
	struct backlight_properties props;
89 90 91
	struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
	struct backlight_device *bl;
	struct pwm_bl_data *pb;
92
	int ret;
93

94 95
	if (!data) {
		dev_err(&pdev->dev, "failed to find platform data\n");
96
		return -EINVAL;
97
	}
98

99 100 101 102 103 104
	if (data->init) {
		ret = data->init(&pdev->dev);
		if (ret < 0)
			return ret;
	}

105
	pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);
106
	if (!pb) {
107
		dev_err(&pdev->dev, "no memory for state\n");
108 109 110
		ret = -ENOMEM;
		goto err_alloc;
	}
111 112

	pb->period = data->pwm_period_ns;
113
	pb->notify = data->notify;
114
	pb->notify_after = data->notify_after;
115
	pb->check_fb = data->check_fb;
116 117
	pb->lth_brightness = data->lth_brightness *
		(data->pwm_period_ns / data->max_brightness);
118
	pb->dev = &pdev->dev;
119 120

	pb->pwm = pwm_request(data->pwm_id, "backlight");
121
	if (IS_ERR(pb->pwm)) {
122
		dev_err(&pdev->dev, "unable to request PWM for backlight\n");
123
		ret = PTR_ERR(pb->pwm);
124
		goto err_alloc;
125 126
	} else
		dev_dbg(&pdev->dev, "got pwm for backlight\n");
127

128
	memset(&props, 0, sizeof(struct backlight_properties));
M
Matthew Garrett 已提交
129
	props.type = BACKLIGHT_RAW;
130 131 132
	props.max_brightness = data->max_brightness;
	bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
				       &pwm_backlight_ops, &props);
133 134
	if (IS_ERR(bl)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
135 136
		ret = PTR_ERR(bl);
		goto err_bl;
137 138 139 140 141 142 143
	}

	bl->props.brightness = data->dft_brightness;
	backlight_update_status(bl);

	platform_set_drvdata(pdev, bl);
	return 0;
144 145 146 147 148 149 150

err_bl:
	pwm_free(pb->pwm);
err_alloc:
	if (data->exit)
		data->exit(&pdev->dev);
	return ret;
151 152 153 154
}

static int pwm_backlight_remove(struct platform_device *pdev)
{
155
	struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
156 157 158 159 160 161 162
	struct backlight_device *bl = platform_get_drvdata(pdev);
	struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);

	backlight_device_unregister(bl);
	pwm_config(pb->pwm, 0, pb->period);
	pwm_disable(pb->pwm);
	pwm_free(pb->pwm);
163 164
	if (data->exit)
		data->exit(&pdev->dev);
165 166 167 168
	return 0;
}

#ifdef CONFIG_PM
169
static int pwm_backlight_suspend(struct device *dev)
170
{
171
	struct backlight_device *bl = dev_get_drvdata(dev);
172 173
	struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);

174
	if (pb->notify)
175
		pb->notify(pb->dev, 0);
176 177
	pwm_config(pb->pwm, 0, pb->period);
	pwm_disable(pb->pwm);
178 179
	if (pb->notify_after)
		pb->notify_after(pb->dev, 0);
180 181 182
	return 0;
}

183
static int pwm_backlight_resume(struct device *dev)
184
{
185
	struct backlight_device *bl = dev_get_drvdata(dev);
186 187 188 189

	backlight_update_status(bl);
	return 0;
}
190 191 192 193

static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend,
			 pwm_backlight_resume);

194 195 196 197 198 199
#endif

static struct platform_driver pwm_backlight_driver = {
	.driver		= {
		.name	= "pwm-backlight",
		.owner	= THIS_MODULE,
200 201 202
#ifdef CONFIG_PM
		.pm	= &pwm_backlight_pm_ops,
#endif
203 204 205 206 207
	},
	.probe		= pwm_backlight_probe,
	.remove		= pwm_backlight_remove,
};

208
module_platform_driver(pwm_backlight_driver);
209 210 211

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