diff options
author | Thierry Reding <treding@nvidia.com> | 2013-08-30 09:10:14 -0400 |
---|---|---|
committer | Thierry Reding <treding@nvidia.com> | 2013-12-17 12:09:51 -0500 |
commit | 280921de7241ee63184c8baa89ec3fe0122dedb3 (patch) | |
tree | 10f9170b4018bfa46431a39336b4ce3782dd3de1 /drivers/gpu/drm/panel/panel-simple.c | |
parent | aead40ea0b53a0e28d34adf7bb923ecb2968c04a (diff) |
drm/panel: Add simple panel support
Add a driver for simple panels. Such panels can have a regulator that
provides the supply voltage and a separate GPIO to enable the panel.
Optionally the panels can have a backlight associated with them so it
can be enabled or disabled according to the panel's power management
mode.
Support is added for two panels: An AU Optronics 10.1" WSVGA and a
Chunghwa Picture Tubes 10.1" WXGA panel.
Signed-off-by: Thierry Reding <treding@nvidia.com>
Diffstat (limited to 'drivers/gpu/drm/panel/panel-simple.c')
-rw-r--r-- | drivers/gpu/drm/panel/panel-simple.c | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c new file mode 100644 index 000000000000..767b7bef199f --- /dev/null +++ b/drivers/gpu/drm/panel/panel-simple.c | |||
@@ -0,0 +1,417 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013, NVIDIA Corporation. All rights reserved. | ||
3 | * | ||
4 | * Permission is hereby granted, free of charge, to any person obtaining a | ||
5 | * copy of this software and associated documentation files (the "Software"), | ||
6 | * to deal in the Software without restriction, including without limitation | ||
7 | * the rights to use, copy, modify, merge, publish, distribute, sub license, | ||
8 | * and/or sell copies of the Software, and to permit persons to whom the | ||
9 | * Software is furnished to do so, subject to the following conditions: | ||
10 | * | ||
11 | * The above copyright notice and this permission notice (including the | ||
12 | * next paragraph) shall be included in all copies or substantial portions | ||
13 | * of the Software. | ||
14 | * | ||
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL | ||
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
21 | * DEALINGS IN THE SOFTWARE. | ||
22 | */ | ||
23 | |||
24 | #include <linux/backlight.h> | ||
25 | #include <linux/gpio.h> | ||
26 | #include <linux/module.h> | ||
27 | #include <linux/of_gpio.h> | ||
28 | #include <linux/of_platform.h> | ||
29 | #include <linux/platform_device.h> | ||
30 | #include <linux/regulator/consumer.h> | ||
31 | |||
32 | #include <drm/drmP.h> | ||
33 | #include <drm/drm_crtc.h> | ||
34 | #include <drm/drm_panel.h> | ||
35 | |||
36 | struct panel_desc { | ||
37 | const struct drm_display_mode *modes; | ||
38 | unsigned int num_modes; | ||
39 | |||
40 | struct { | ||
41 | unsigned int width; | ||
42 | unsigned int height; | ||
43 | } size; | ||
44 | }; | ||
45 | |||
46 | /* TODO: convert to gpiod_*() API once it's been merged */ | ||
47 | #define GPIO_ACTIVE_LOW (1 << 0) | ||
48 | |||
49 | struct panel_simple { | ||
50 | struct drm_panel base; | ||
51 | bool enabled; | ||
52 | |||
53 | const struct panel_desc *desc; | ||
54 | |||
55 | struct backlight_device *backlight; | ||
56 | struct regulator *supply; | ||
57 | struct i2c_adapter *ddc; | ||
58 | |||
59 | unsigned long enable_gpio_flags; | ||
60 | int enable_gpio; | ||
61 | }; | ||
62 | |||
63 | static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) | ||
64 | { | ||
65 | return container_of(panel, struct panel_simple, base); | ||
66 | } | ||
67 | |||
68 | static int panel_simple_get_fixed_modes(struct panel_simple *panel) | ||
69 | { | ||
70 | struct drm_connector *connector = panel->base.connector; | ||
71 | struct drm_device *drm = panel->base.drm; | ||
72 | struct drm_display_mode *mode; | ||
73 | unsigned int i, num = 0; | ||
74 | |||
75 | if (!panel->desc) | ||
76 | return 0; | ||
77 | |||
78 | for (i = 0; i < panel->desc->num_modes; i++) { | ||
79 | const struct drm_display_mode *m = &panel->desc->modes[i]; | ||
80 | |||
81 | mode = drm_mode_duplicate(drm, m); | ||
82 | if (!mode) { | ||
83 | dev_err(drm->dev, "failed to add mode %ux%u@%u\n", | ||
84 | m->hdisplay, m->vdisplay, m->vrefresh); | ||
85 | continue; | ||
86 | } | ||
87 | |||
88 | drm_mode_set_name(mode); | ||
89 | |||
90 | drm_mode_probed_add(connector, mode); | ||
91 | num++; | ||
92 | } | ||
93 | |||
94 | connector->display_info.width_mm = panel->desc->size.width; | ||
95 | connector->display_info.height_mm = panel->desc->size.height; | ||
96 | |||
97 | return num; | ||
98 | } | ||
99 | |||
100 | static int panel_simple_disable(struct drm_panel *panel) | ||
101 | { | ||
102 | struct panel_simple *p = to_panel_simple(panel); | ||
103 | |||
104 | if (!p->enabled) | ||
105 | return 0; | ||
106 | |||
107 | if (p->backlight) { | ||
108 | p->backlight->props.power = FB_BLANK_POWERDOWN; | ||
109 | backlight_update_status(p->backlight); | ||
110 | } | ||
111 | |||
112 | if (gpio_is_valid(p->enable_gpio)) { | ||
113 | if (p->enable_gpio_flags & GPIO_ACTIVE_LOW) | ||
114 | gpio_set_value(p->enable_gpio, 1); | ||
115 | else | ||
116 | gpio_set_value(p->enable_gpio, 0); | ||
117 | } | ||
118 | |||
119 | regulator_disable(p->supply); | ||
120 | p->enabled = false; | ||
121 | |||
122 | return 0; | ||
123 | } | ||
124 | |||
125 | static int panel_simple_enable(struct drm_panel *panel) | ||
126 | { | ||
127 | struct panel_simple *p = to_panel_simple(panel); | ||
128 | int err; | ||
129 | |||
130 | if (p->enabled) | ||
131 | return 0; | ||
132 | |||
133 | err = regulator_enable(p->supply); | ||
134 | if (err < 0) { | ||
135 | dev_err(panel->dev, "failed to enable supply: %d\n", err); | ||
136 | return err; | ||
137 | } | ||
138 | |||
139 | if (gpio_is_valid(p->enable_gpio)) { | ||
140 | if (p->enable_gpio_flags & GPIO_ACTIVE_LOW) | ||
141 | gpio_set_value(p->enable_gpio, 0); | ||
142 | else | ||
143 | gpio_set_value(p->enable_gpio, 1); | ||
144 | } | ||
145 | |||
146 | if (p->backlight) { | ||
147 | p->backlight->props.power = FB_BLANK_UNBLANK; | ||
148 | backlight_update_status(p->backlight); | ||
149 | } | ||
150 | |||
151 | p->enabled = true; | ||
152 | |||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static int panel_simple_get_modes(struct drm_panel *panel) | ||
157 | { | ||
158 | struct panel_simple *p = to_panel_simple(panel); | ||
159 | int num = 0; | ||
160 | |||
161 | /* probe EDID if a DDC bus is available */ | ||
162 | if (p->ddc) { | ||
163 | struct edid *edid = drm_get_edid(panel->connector, p->ddc); | ||
164 | if (edid) { | ||
165 | num += drm_add_edid_modes(panel->connector, edid); | ||
166 | kfree(edid); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | /* add hard-coded panel modes */ | ||
171 | num += panel_simple_get_fixed_modes(p); | ||
172 | |||
173 | return num; | ||
174 | } | ||
175 | |||
176 | static const struct drm_panel_funcs panel_simple_funcs = { | ||
177 | .disable = panel_simple_disable, | ||
178 | .enable = panel_simple_enable, | ||
179 | .get_modes = panel_simple_get_modes, | ||
180 | }; | ||
181 | |||
182 | static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) | ||
183 | { | ||
184 | struct device_node *backlight, *ddc; | ||
185 | struct panel_simple *panel; | ||
186 | enum of_gpio_flags flags; | ||
187 | int err; | ||
188 | |||
189 | panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); | ||
190 | if (!panel) | ||
191 | return -ENOMEM; | ||
192 | |||
193 | panel->enabled = false; | ||
194 | panel->desc = desc; | ||
195 | |||
196 | panel->supply = devm_regulator_get(dev, "power"); | ||
197 | if (IS_ERR(panel->supply)) | ||
198 | return PTR_ERR(panel->supply); | ||
199 | |||
200 | panel->enable_gpio = of_get_named_gpio_flags(dev->of_node, | ||
201 | "enable-gpios", 0, | ||
202 | &flags); | ||
203 | if (gpio_is_valid(panel->enable_gpio)) { | ||
204 | unsigned int value; | ||
205 | |||
206 | if (flags & OF_GPIO_ACTIVE_LOW) | ||
207 | panel->enable_gpio_flags |= GPIO_ACTIVE_LOW; | ||
208 | |||
209 | err = gpio_request(panel->enable_gpio, "enable"); | ||
210 | if (err < 0) { | ||
211 | dev_err(dev, "failed to request GPIO#%u: %d\n", | ||
212 | panel->enable_gpio, err); | ||
213 | return err; | ||
214 | } | ||
215 | |||
216 | value = (panel->enable_gpio_flags & GPIO_ACTIVE_LOW) != 0; | ||
217 | |||
218 | err = gpio_direction_output(panel->enable_gpio, value); | ||
219 | if (err < 0) { | ||
220 | dev_err(dev, "failed to setup GPIO%u: %d\n", | ||
221 | panel->enable_gpio, err); | ||
222 | goto free_gpio; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | backlight = of_parse_phandle(dev->of_node, "backlight", 0); | ||
227 | if (backlight) { | ||
228 | panel->backlight = of_find_backlight_by_node(backlight); | ||
229 | of_node_put(backlight); | ||
230 | |||
231 | if (!panel->backlight) { | ||
232 | err = -EPROBE_DEFER; | ||
233 | goto free_gpio; | ||
234 | } | ||
235 | } | ||
236 | |||
237 | ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); | ||
238 | if (ddc) { | ||
239 | panel->ddc = of_find_i2c_adapter_by_node(ddc); | ||
240 | of_node_put(ddc); | ||
241 | |||
242 | if (!panel->ddc) { | ||
243 | err = -EPROBE_DEFER; | ||
244 | goto free_backlight; | ||
245 | } | ||
246 | } | ||
247 | |||
248 | drm_panel_init(&panel->base); | ||
249 | panel->base.dev = dev; | ||
250 | panel->base.funcs = &panel_simple_funcs; | ||
251 | |||
252 | err = drm_panel_add(&panel->base); | ||
253 | if (err < 0) | ||
254 | goto free_ddc; | ||
255 | |||
256 | dev_set_drvdata(dev, panel); | ||
257 | |||
258 | return 0; | ||
259 | |||
260 | free_ddc: | ||
261 | if (panel->ddc) | ||
262 | put_device(&panel->ddc->dev); | ||
263 | free_backlight: | ||
264 | if (panel->backlight) | ||
265 | put_device(&panel->backlight->dev); | ||
266 | free_gpio: | ||
267 | if (gpio_is_valid(panel->enable_gpio)) | ||
268 | gpio_free(panel->enable_gpio); | ||
269 | |||
270 | return err; | ||
271 | } | ||
272 | |||
273 | static int panel_simple_remove(struct device *dev) | ||
274 | { | ||
275 | struct panel_simple *panel = dev_get_drvdata(dev); | ||
276 | |||
277 | drm_panel_detach(&panel->base); | ||
278 | drm_panel_remove(&panel->base); | ||
279 | |||
280 | panel_simple_disable(&panel->base); | ||
281 | |||
282 | if (panel->ddc) | ||
283 | put_device(&panel->ddc->dev); | ||
284 | |||
285 | if (panel->backlight) | ||
286 | put_device(&panel->backlight->dev); | ||
287 | |||
288 | if (gpio_is_valid(panel->enable_gpio)) | ||
289 | gpio_free(panel->enable_gpio); | ||
290 | |||
291 | regulator_disable(panel->supply); | ||
292 | |||
293 | return 0; | ||
294 | } | ||
295 | |||
296 | static const struct drm_display_mode auo_b101aw03_mode = { | ||
297 | .clock = 51450, | ||
298 | .hdisplay = 1024, | ||
299 | .hsync_start = 1024 + 156, | ||
300 | .hsync_end = 1024 + 156 + 8, | ||
301 | .htotal = 1024 + 156 + 8 + 156, | ||
302 | .vdisplay = 600, | ||
303 | .vsync_start = 600 + 16, | ||
304 | .vsync_end = 600 + 16 + 6, | ||
305 | .vtotal = 600 + 16 + 6 + 16, | ||
306 | .vrefresh = 60, | ||
307 | }; | ||
308 | |||
309 | static const struct panel_desc auo_b101aw03 = { | ||
310 | .modes = &auo_b101aw03_mode, | ||
311 | .num_modes = 1, | ||
312 | .size = { | ||
313 | .width = 223, | ||
314 | .height = 125, | ||
315 | }, | ||
316 | }; | ||
317 | |||
318 | static const struct drm_display_mode chunghwa_claa101wb01_mode = { | ||
319 | .clock = 69300, | ||
320 | .hdisplay = 1366, | ||
321 | .hsync_start = 1366 + 48, | ||
322 | .hsync_end = 1366 + 48 + 32, | ||
323 | .htotal = 1366 + 48 + 32 + 20, | ||
324 | .vdisplay = 768, | ||
325 | .vsync_start = 768 + 16, | ||
326 | .vsync_end = 768 + 16 + 8, | ||
327 | .vtotal = 768 + 16 + 8 + 16, | ||
328 | .vrefresh = 60, | ||
329 | }; | ||
330 | |||
331 | static const struct panel_desc chunghwa_claa101wb01 = { | ||
332 | .modes = &chunghwa_claa101wb01_mode, | ||
333 | .num_modes = 1, | ||
334 | .size = { | ||
335 | .width = 223, | ||
336 | .height = 125, | ||
337 | }, | ||
338 | }; | ||
339 | |||
340 | static const struct of_device_id platform_of_match[] = { | ||
341 | { | ||
342 | .compatible = "auo,b101aw03", | ||
343 | .data = &auo_b101aw03, | ||
344 | }, { | ||
345 | .compatible = "chunghwa,claa101wb01", | ||
346 | .data = &chunghwa_claa101wb01 | ||
347 | }, { | ||
348 | .compatible = "simple-panel", | ||
349 | }, { | ||
350 | /* sentinel */ | ||
351 | } | ||
352 | }; | ||
353 | MODULE_DEVICE_TABLE(of, platform_of_match); | ||
354 | |||
355 | static int panel_simple_platform_probe(struct platform_device *pdev) | ||
356 | { | ||
357 | const struct of_device_id *id; | ||
358 | |||
359 | id = of_match_node(platform_of_match, pdev->dev.of_node); | ||
360 | if (!id) | ||
361 | return -ENODEV; | ||
362 | |||
363 | return panel_simple_probe(&pdev->dev, id->data); | ||
364 | } | ||
365 | |||
366 | static int panel_simple_platform_remove(struct platform_device *pdev) | ||
367 | { | ||
368 | return panel_simple_remove(&pdev->dev); | ||
369 | } | ||
370 | |||
371 | static struct platform_driver panel_simple_platform_driver = { | ||
372 | .driver = { | ||
373 | .name = "panel-simple", | ||
374 | .owner = THIS_MODULE, | ||
375 | .of_match_table = platform_of_match, | ||
376 | }, | ||
377 | .probe = panel_simple_platform_probe, | ||
378 | .remove = panel_simple_platform_remove, | ||
379 | }; | ||
380 | |||
381 | static const struct drm_display_mode panasonic_vvx10f004b00_mode = { | ||
382 | .clock = 157200, | ||
383 | .hdisplay = 1920, | ||
384 | .hsync_start = 1920 + 154, | ||
385 | .hsync_end = 1920 + 154 + 16, | ||
386 | .htotal = 1920 + 154 + 16 + 32, | ||
387 | .vdisplay = 1200, | ||
388 | .vsync_start = 1200 + 17, | ||
389 | .vsync_end = 1200 + 17 + 2, | ||
390 | .vtotal = 1200 + 17 + 2 + 16, | ||
391 | .vrefresh = 60, | ||
392 | }; | ||
393 | |||
394 | static const struct panel_desc panasonic_vvx10f004b00 = { | ||
395 | .modes = &panasonic_vvx10f004b00_mode, | ||
396 | .num_modes = 1, | ||
397 | .size = { | ||
398 | .width = 217, | ||
399 | .height = 136, | ||
400 | }, | ||
401 | }; | ||
402 | |||
403 | static int __init panel_simple_init(void) | ||
404 | { | ||
405 | return platform_driver_register(&panel_simple_platform_driver); | ||
406 | } | ||
407 | module_init(panel_simple_init); | ||
408 | |||
409 | static void __exit panel_simple_exit(void) | ||
410 | { | ||
411 | platform_driver_unregister(&panel_simple_platform_driver); | ||
412 | } | ||
413 | module_exit(panel_simple_exit); | ||
414 | |||
415 | MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); | ||
416 | MODULE_DESCRIPTION("DRM Driver for Simple Panels"); | ||
417 | MODULE_LICENSE("GPL and additional rights"); | ||