diff options
author | Thierry Reding <thierry.reding@avionic-design.de> | 2011-12-16 15:25:29 -0500 |
---|---|---|
committer | Thierry Reding <thierry.reding@avionic-design.de> | 2012-07-23 07:23:52 -0400 |
commit | 3e3ed6cdc49d758719c148a78c8b04c243ef74d0 (patch) | |
tree | 4898c961d49967089139c79ff222441a4495dca4 | |
parent | 4dce82c1e84007d38747533673c2289bfc6497d2 (diff) |
pwm-backlight: Add rudimentary device tree support
This commit adds very basic support for device tree probing. Currently,
only a PWM and a list of distinct brightness levels can be specified.
Enabling or disabling backlight power via GPIOs is not yet supported.
Reviewed-by: Shawn Guo <shawn.guo@linaro.org>
Reviewed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
-rw-r--r-- | Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt | 28 | ||||
-rw-r--r-- | drivers/video/backlight/Kconfig | 2 | ||||
-rw-r--r-- | drivers/video/backlight/pwm_bl.c | 149 | ||||
-rw-r--r-- | include/linux/pwm_backlight.h | 1 |
4 files changed, 159 insertions, 21 deletions
diff --git a/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt new file mode 100644 index 000000000000..1e4fc727f3b1 --- /dev/null +++ b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt | |||
@@ -0,0 +1,28 @@ | |||
1 | pwm-backlight bindings | ||
2 | |||
3 | Required properties: | ||
4 | - compatible: "pwm-backlight" | ||
5 | - pwms: OF device-tree PWM specification (see PWM binding[0]) | ||
6 | - brightness-levels: Array of distinct brightness levels. Typically these | ||
7 | are in the range from 0 to 255, but any range starting at 0 will do. | ||
8 | The actual brightness level (PWM duty cycle) will be interpolated | ||
9 | from these values. 0 means a 0% duty cycle (darkest/off), while the | ||
10 | last value in the array represents a 100% duty cycle (brightest). | ||
11 | - default-brightness-level: the default brightness level (index into the | ||
12 | array defined by the "brightness-levels" property) | ||
13 | |||
14 | Optional properties: | ||
15 | - pwm-names: a list of names for the PWM devices specified in the | ||
16 | "pwms" property (see PWM binding[0]) | ||
17 | |||
18 | [0]: Documentation/devicetree/bindings/pwm/pwm.txt | ||
19 | |||
20 | Example: | ||
21 | |||
22 | backlight { | ||
23 | compatible = "pwm-backlight"; | ||
24 | pwms = <&pwm 0 5000000>; | ||
25 | |||
26 | brightness-levels = <0 4 8 16 32 64 128 255>; | ||
27 | default-brightness-level = <6>; | ||
28 | }; | ||
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index fa2b03750316..4c9c02216351 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig | |||
@@ -245,7 +245,7 @@ config BACKLIGHT_CARILLO_RANCH | |||
245 | 245 | ||
246 | config BACKLIGHT_PWM | 246 | config BACKLIGHT_PWM |
247 | tristate "Generic PWM based Backlight Driver" | 247 | tristate "Generic PWM based Backlight Driver" |
248 | depends on HAVE_PWM | 248 | depends on PWM |
249 | help | 249 | help |
250 | If you have a LCD backlight adjustable by PWM, say Y to enable | 250 | If you have a LCD backlight adjustable by PWM, say Y to enable |
251 | this driver. | 251 | this driver. |
diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 342b7d7cbb63..057389d69a51 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c | |||
@@ -26,11 +26,13 @@ struct pwm_bl_data { | |||
26 | struct device *dev; | 26 | struct device *dev; |
27 | unsigned int period; | 27 | unsigned int period; |
28 | unsigned int lth_brightness; | 28 | unsigned int lth_brightness; |
29 | unsigned int *levels; | ||
29 | int (*notify)(struct device *, | 30 | int (*notify)(struct device *, |
30 | int brightness); | 31 | int brightness); |
31 | void (*notify_after)(struct device *, | 32 | void (*notify_after)(struct device *, |
32 | int brightness); | 33 | int brightness); |
33 | int (*check_fb)(struct device *, struct fb_info *); | 34 | int (*check_fb)(struct device *, struct fb_info *); |
35 | void (*exit)(struct device *); | ||
34 | }; | 36 | }; |
35 | 37 | ||
36 | static int pwm_backlight_update_status(struct backlight_device *bl) | 38 | static int pwm_backlight_update_status(struct backlight_device *bl) |
@@ -52,6 +54,11 @@ static int pwm_backlight_update_status(struct backlight_device *bl) | |||
52 | pwm_config(pb->pwm, 0, pb->period); | 54 | pwm_config(pb->pwm, 0, pb->period); |
53 | pwm_disable(pb->pwm); | 55 | pwm_disable(pb->pwm); |
54 | } else { | 56 | } else { |
57 | if (pb->levels) { | ||
58 | brightness = pb->levels[brightness]; | ||
59 | max = pb->levels[max]; | ||
60 | } | ||
61 | |||
55 | brightness = pb->lth_brightness + | 62 | brightness = pb->lth_brightness + |
56 | (brightness * (pb->period - pb->lth_brightness) / max); | 63 | (brightness * (pb->period - pb->lth_brightness) / max); |
57 | pwm_config(pb->pwm, brightness, pb->period); | 64 | pwm_config(pb->pwm, brightness, pb->period); |
@@ -83,17 +90,98 @@ static const struct backlight_ops pwm_backlight_ops = { | |||
83 | .check_fb = pwm_backlight_check_fb, | 90 | .check_fb = pwm_backlight_check_fb, |
84 | }; | 91 | }; |
85 | 92 | ||
93 | #ifdef CONFIG_OF | ||
94 | static int pwm_backlight_parse_dt(struct device *dev, | ||
95 | struct platform_pwm_backlight_data *data) | ||
96 | { | ||
97 | struct device_node *node = dev->of_node; | ||
98 | struct property *prop; | ||
99 | int length; | ||
100 | u32 value; | ||
101 | int ret; | ||
102 | |||
103 | if (!node) | ||
104 | return -ENODEV; | ||
105 | |||
106 | memset(data, 0, sizeof(*data)); | ||
107 | |||
108 | /* determine the number of brightness levels */ | ||
109 | prop = of_find_property(node, "brightness-levels", &length); | ||
110 | if (!prop) | ||
111 | return -EINVAL; | ||
112 | |||
113 | data->max_brightness = length / sizeof(u32); | ||
114 | |||
115 | /* read brightness levels from DT property */ | ||
116 | if (data->max_brightness > 0) { | ||
117 | size_t size = sizeof(*data->levels) * data->max_brightness; | ||
118 | |||
119 | data->levels = devm_kzalloc(dev, size, GFP_KERNEL); | ||
120 | if (!data->levels) | ||
121 | return -ENOMEM; | ||
122 | |||
123 | ret = of_property_read_u32_array(node, "brightness-levels", | ||
124 | data->levels, | ||
125 | data->max_brightness); | ||
126 | if (ret < 0) | ||
127 | return ret; | ||
128 | |||
129 | ret = of_property_read_u32(node, "default-brightness-level", | ||
130 | &value); | ||
131 | if (ret < 0) | ||
132 | return ret; | ||
133 | |||
134 | if (value >= data->max_brightness) { | ||
135 | dev_warn(dev, "invalid default brightness level: %u, using %u\n", | ||
136 | value, data->max_brightness - 1); | ||
137 | value = data->max_brightness - 1; | ||
138 | } | ||
139 | |||
140 | data->dft_brightness = value; | ||
141 | data->max_brightness--; | ||
142 | } | ||
143 | |||
144 | /* | ||
145 | * TODO: Most users of this driver use a number of GPIOs to control | ||
146 | * backlight power. Support for specifying these needs to be | ||
147 | * added. | ||
148 | */ | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static struct of_device_id pwm_backlight_of_match[] = { | ||
154 | { .compatible = "pwm-backlight" }, | ||
155 | { } | ||
156 | }; | ||
157 | |||
158 | MODULE_DEVICE_TABLE(of, pwm_backlight_of_match); | ||
159 | #else | ||
160 | static int pwm_backlight_parse_dt(struct device *dev, | ||
161 | struct platform_pwm_backlight_data *data) | ||
162 | { | ||
163 | return -ENODEV; | ||
164 | } | ||
165 | #endif | ||
166 | |||
86 | static int pwm_backlight_probe(struct platform_device *pdev) | 167 | static int pwm_backlight_probe(struct platform_device *pdev) |
87 | { | 168 | { |
88 | struct backlight_properties props; | ||
89 | struct platform_pwm_backlight_data *data = pdev->dev.platform_data; | 169 | struct platform_pwm_backlight_data *data = pdev->dev.platform_data; |
170 | struct platform_pwm_backlight_data defdata; | ||
171 | struct backlight_properties props; | ||
90 | struct backlight_device *bl; | 172 | struct backlight_device *bl; |
91 | struct pwm_bl_data *pb; | 173 | struct pwm_bl_data *pb; |
174 | unsigned int max; | ||
92 | int ret; | 175 | int ret; |
93 | 176 | ||
94 | if (!data) { | 177 | if (!data) { |
95 | dev_err(&pdev->dev, "failed to find platform data\n"); | 178 | ret = pwm_backlight_parse_dt(&pdev->dev, &defdata); |
96 | return -EINVAL; | 179 | if (ret < 0) { |
180 | dev_err(&pdev->dev, "failed to find platform data\n"); | ||
181 | return ret; | ||
182 | } | ||
183 | |||
184 | data = &defdata; | ||
97 | } | 185 | } |
98 | 186 | ||
99 | if (data->init) { | 187 | if (data->init) { |
@@ -109,21 +197,42 @@ static int pwm_backlight_probe(struct platform_device *pdev) | |||
109 | goto err_alloc; | 197 | goto err_alloc; |
110 | } | 198 | } |
111 | 199 | ||
112 | pb->period = data->pwm_period_ns; | 200 | if (data->levels) { |
201 | max = data->levels[data->max_brightness]; | ||
202 | pb->levels = data->levels; | ||
203 | } else | ||
204 | max = data->max_brightness; | ||
205 | |||
113 | pb->notify = data->notify; | 206 | pb->notify = data->notify; |
114 | pb->notify_after = data->notify_after; | 207 | pb->notify_after = data->notify_after; |
115 | pb->check_fb = data->check_fb; | 208 | pb->check_fb = data->check_fb; |
116 | pb->lth_brightness = data->lth_brightness * | 209 | pb->exit = data->exit; |
117 | (data->pwm_period_ns / data->max_brightness); | ||
118 | pb->dev = &pdev->dev; | 210 | pb->dev = &pdev->dev; |
119 | 211 | ||
120 | pb->pwm = pwm_request(data->pwm_id, "backlight"); | 212 | pb->pwm = pwm_get(&pdev->dev, NULL); |
121 | if (IS_ERR(pb->pwm)) { | 213 | if (IS_ERR(pb->pwm)) { |
122 | dev_err(&pdev->dev, "unable to request PWM for backlight\n"); | 214 | dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n"); |
123 | ret = PTR_ERR(pb->pwm); | 215 | |
124 | goto err_alloc; | 216 | pb->pwm = pwm_request(data->pwm_id, "pwm-backlight"); |
125 | } else | 217 | if (IS_ERR(pb->pwm)) { |
126 | dev_dbg(&pdev->dev, "got pwm for backlight\n"); | 218 | dev_err(&pdev->dev, "unable to request legacy PWM\n"); |
219 | ret = PTR_ERR(pb->pwm); | ||
220 | goto err_alloc; | ||
221 | } | ||
222 | } | ||
223 | |||
224 | dev_dbg(&pdev->dev, "got pwm for backlight\n"); | ||
225 | |||
226 | /* | ||
227 | * The DT case will set the pwm_period_ns field to 0 and store the | ||
228 | * period, parsed from the DT, in the PWM device. For the non-DT case, | ||
229 | * set the period from platform data. | ||
230 | */ | ||
231 | if (data->pwm_period_ns > 0) | ||
232 | pwm_set_period(pb->pwm, data->pwm_period_ns); | ||
233 | |||
234 | pb->period = pwm_get_period(pb->pwm); | ||
235 | pb->lth_brightness = data->lth_brightness * (pb->period / max); | ||
127 | 236 | ||
128 | memset(&props, 0, sizeof(struct backlight_properties)); | 237 | memset(&props, 0, sizeof(struct backlight_properties)); |
129 | props.type = BACKLIGHT_RAW; | 238 | props.type = BACKLIGHT_RAW; |
@@ -143,7 +252,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) | |||
143 | return 0; | 252 | return 0; |
144 | 253 | ||
145 | err_bl: | 254 | err_bl: |
146 | pwm_free(pb->pwm); | 255 | pwm_put(pb->pwm); |
147 | err_alloc: | 256 | err_alloc: |
148 | if (data->exit) | 257 | if (data->exit) |
149 | data->exit(&pdev->dev); | 258 | data->exit(&pdev->dev); |
@@ -152,16 +261,15 @@ err_alloc: | |||
152 | 261 | ||
153 | static int pwm_backlight_remove(struct platform_device *pdev) | 262 | static int pwm_backlight_remove(struct platform_device *pdev) |
154 | { | 263 | { |
155 | struct platform_pwm_backlight_data *data = pdev->dev.platform_data; | ||
156 | struct backlight_device *bl = platform_get_drvdata(pdev); | 264 | struct backlight_device *bl = platform_get_drvdata(pdev); |
157 | struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); | 265 | struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); |
158 | 266 | ||
159 | backlight_device_unregister(bl); | 267 | backlight_device_unregister(bl); |
160 | pwm_config(pb->pwm, 0, pb->period); | 268 | pwm_config(pb->pwm, 0, pb->period); |
161 | pwm_disable(pb->pwm); | 269 | pwm_disable(pb->pwm); |
162 | pwm_free(pb->pwm); | 270 | pwm_put(pb->pwm); |
163 | if (data->exit) | 271 | if (pb->exit) |
164 | data->exit(&pdev->dev); | 272 | pb->exit(&pdev->dev); |
165 | return 0; | 273 | return 0; |
166 | } | 274 | } |
167 | 275 | ||
@@ -195,11 +303,12 @@ static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend, | |||
195 | 303 | ||
196 | static struct platform_driver pwm_backlight_driver = { | 304 | static struct platform_driver pwm_backlight_driver = { |
197 | .driver = { | 305 | .driver = { |
198 | .name = "pwm-backlight", | 306 | .name = "pwm-backlight", |
199 | .owner = THIS_MODULE, | 307 | .owner = THIS_MODULE, |
200 | #ifdef CONFIG_PM | 308 | #ifdef CONFIG_PM |
201 | .pm = &pwm_backlight_pm_ops, | 309 | .pm = &pwm_backlight_pm_ops, |
202 | #endif | 310 | #endif |
311 | .of_match_table = of_match_ptr(pwm_backlight_of_match), | ||
203 | }, | 312 | }, |
204 | .probe = pwm_backlight_probe, | 313 | .probe = pwm_backlight_probe, |
205 | .remove = pwm_backlight_remove, | 314 | .remove = pwm_backlight_remove, |
diff --git a/include/linux/pwm_backlight.h b/include/linux/pwm_backlight.h index 63d2df43e61a..56f4a866539a 100644 --- a/include/linux/pwm_backlight.h +++ b/include/linux/pwm_backlight.h | |||
@@ -12,6 +12,7 @@ struct platform_pwm_backlight_data { | |||
12 | unsigned int dft_brightness; | 12 | unsigned int dft_brightness; |
13 | unsigned int lth_brightness; | 13 | unsigned int lth_brightness; |
14 | unsigned int pwm_period_ns; | 14 | unsigned int pwm_period_ns; |
15 | unsigned int *levels; | ||
15 | int (*init)(struct device *dev); | 16 | int (*init)(struct device *dev); |
16 | int (*notify)(struct device *dev, int brightness); | 17 | int (*notify)(struct device *dev, int brightness); |
17 | void (*notify_after)(struct device *dev, int brightness); | 18 | void (*notify_after)(struct device *dev, int brightness); |