Commit | Line | Data |
---|---|---|
1a396789 BB |
1 | /* |
2 | * Copyright (C) 2014 Traphandler | |
3 | * Copyright (C) 2014 Free Electrons | |
4 | * Copyright (C) 2014 Atmel | |
5 | * | |
6 | * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> | |
7 | * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify it | |
10 | * under the terms of the GNU General Public License version 2 as published by | |
11 | * the Free Software Foundation. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
16 | * more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License along with | |
19 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
20 | */ | |
21 | ||
22 | #include <linux/clk.h> | |
23 | #include <linux/irq.h> | |
24 | #include <linux/irqchip.h> | |
25 | #include <linux/module.h> | |
26 | #include <linux/pm_runtime.h> | |
27 | ||
28 | #include "atmel_hlcdc_dc.h" | |
29 | ||
30 | #define ATMEL_HLCDC_LAYER_IRQS_OFFSET 8 | |
31 | ||
6b22cadc BB |
32 | static const struct atmel_hlcdc_layer_desc atmel_hlcdc_at91sam9n12_layers[] = { |
33 | { | |
34 | .name = "base", | |
35 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
36 | .regs_offset = 0x40, | |
37 | .id = 0, | |
38 | .type = ATMEL_HLCDC_BASE_LAYER, | |
39 | .nconfigs = 5, | |
40 | .layout = { | |
41 | .xstride = { 2 }, | |
42 | .default_color = 3, | |
43 | .general_config = 4, | |
44 | }, | |
45 | }, | |
46 | }; | |
47 | ||
48 | static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9n12 = { | |
49 | .min_width = 0, | |
50 | .min_height = 0, | |
51 | .max_width = 1280, | |
52 | .max_height = 860, | |
53 | .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9n12_layers), | |
54 | .layers = atmel_hlcdc_at91sam9n12_layers, | |
55 | }; | |
56 | ||
348ef85f BB |
57 | static const struct atmel_hlcdc_layer_desc atmel_hlcdc_at91sam9x5_layers[] = { |
58 | { | |
59 | .name = "base", | |
60 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
61 | .regs_offset = 0x40, | |
62 | .id = 0, | |
63 | .type = ATMEL_HLCDC_BASE_LAYER, | |
64 | .nconfigs = 5, | |
65 | .layout = { | |
66 | .xstride = { 2 }, | |
67 | .default_color = 3, | |
68 | .general_config = 4, | |
69 | .disc_pos = 5, | |
70 | .disc_size = 6, | |
71 | }, | |
72 | }, | |
73 | { | |
74 | .name = "overlay1", | |
75 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
76 | .regs_offset = 0x100, | |
77 | .id = 1, | |
78 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
79 | .nconfigs = 10, | |
80 | .layout = { | |
81 | .pos = 2, | |
82 | .size = 3, | |
83 | .xstride = { 4 }, | |
84 | .pstride = { 5 }, | |
85 | .default_color = 6, | |
86 | .chroma_key = 7, | |
87 | .chroma_key_mask = 8, | |
88 | .general_config = 9, | |
89 | }, | |
90 | }, | |
91 | { | |
92 | .name = "high-end-overlay", | |
93 | .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, | |
94 | .regs_offset = 0x280, | |
95 | .id = 2, | |
96 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
97 | .nconfigs = 17, | |
98 | .layout = { | |
99 | .pos = 2, | |
100 | .size = 3, | |
101 | .memsize = 4, | |
102 | .xstride = { 5, 7 }, | |
103 | .pstride = { 6, 8 }, | |
104 | .default_color = 9, | |
105 | .chroma_key = 10, | |
106 | .chroma_key_mask = 11, | |
107 | .general_config = 12, | |
108 | .csc = 14, | |
109 | }, | |
110 | }, | |
111 | { | |
112 | .name = "cursor", | |
113 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
114 | .regs_offset = 0x340, | |
115 | .id = 3, | |
116 | .type = ATMEL_HLCDC_CURSOR_LAYER, | |
117 | .nconfigs = 10, | |
118 | .max_width = 128, | |
119 | .max_height = 128, | |
120 | .layout = { | |
121 | .pos = 2, | |
122 | .size = 3, | |
123 | .xstride = { 4 }, | |
124 | .default_color = 6, | |
125 | .chroma_key = 7, | |
126 | .chroma_key_mask = 8, | |
127 | .general_config = 9, | |
128 | }, | |
129 | }, | |
130 | }; | |
131 | ||
132 | static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9x5 = { | |
133 | .min_width = 0, | |
134 | .min_height = 0, | |
135 | .max_width = 800, | |
136 | .max_height = 600, | |
137 | .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9x5_layers), | |
138 | .layers = atmel_hlcdc_at91sam9x5_layers, | |
139 | }; | |
140 | ||
1a396789 BB |
141 | static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sama5d3_layers[] = { |
142 | { | |
143 | .name = "base", | |
144 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
145 | .regs_offset = 0x40, | |
146 | .id = 0, | |
147 | .type = ATMEL_HLCDC_BASE_LAYER, | |
148 | .nconfigs = 7, | |
149 | .layout = { | |
150 | .xstride = { 2 }, | |
151 | .default_color = 3, | |
152 | .general_config = 4, | |
153 | .disc_pos = 5, | |
154 | .disc_size = 6, | |
155 | }, | |
156 | }, | |
157 | { | |
158 | .name = "overlay1", | |
159 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
160 | .regs_offset = 0x140, | |
161 | .id = 1, | |
162 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
163 | .nconfigs = 10, | |
164 | .layout = { | |
165 | .pos = 2, | |
166 | .size = 3, | |
167 | .xstride = { 4 }, | |
168 | .pstride = { 5 }, | |
169 | .default_color = 6, | |
170 | .chroma_key = 7, | |
171 | .chroma_key_mask = 8, | |
172 | .general_config = 9, | |
173 | }, | |
174 | }, | |
175 | { | |
176 | .name = "overlay2", | |
177 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
178 | .regs_offset = 0x240, | |
179 | .id = 2, | |
180 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
181 | .nconfigs = 10, | |
182 | .layout = { | |
183 | .pos = 2, | |
184 | .size = 3, | |
185 | .xstride = { 4 }, | |
186 | .pstride = { 5 }, | |
187 | .default_color = 6, | |
188 | .chroma_key = 7, | |
189 | .chroma_key_mask = 8, | |
190 | .general_config = 9, | |
191 | }, | |
192 | }, | |
193 | { | |
194 | .name = "high-end-overlay", | |
195 | .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, | |
196 | .regs_offset = 0x340, | |
197 | .id = 3, | |
198 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
199 | .nconfigs = 42, | |
200 | .layout = { | |
201 | .pos = 2, | |
202 | .size = 3, | |
203 | .memsize = 4, | |
204 | .xstride = { 5, 7 }, | |
205 | .pstride = { 6, 8 }, | |
206 | .default_color = 9, | |
207 | .chroma_key = 10, | |
208 | .chroma_key_mask = 11, | |
209 | .general_config = 12, | |
210 | .csc = 14, | |
211 | }, | |
212 | }, | |
213 | { | |
214 | .name = "cursor", | |
215 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
216 | .regs_offset = 0x440, | |
217 | .id = 4, | |
218 | .type = ATMEL_HLCDC_CURSOR_LAYER, | |
219 | .nconfigs = 10, | |
220 | .max_width = 128, | |
221 | .max_height = 128, | |
222 | .layout = { | |
223 | .pos = 2, | |
224 | .size = 3, | |
225 | .xstride = { 4 }, | |
226 | .pstride = { 5 }, | |
227 | .default_color = 6, | |
228 | .chroma_key = 7, | |
229 | .chroma_key_mask = 8, | |
230 | .general_config = 9, | |
231 | }, | |
232 | }, | |
233 | }; | |
234 | ||
235 | static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = { | |
236 | .min_width = 0, | |
237 | .min_height = 0, | |
238 | .max_width = 2048, | |
239 | .max_height = 2048, | |
240 | .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers), | |
241 | .layers = atmel_hlcdc_sama5d3_layers, | |
242 | }; | |
243 | ||
5b9fb5e6 BB |
244 | static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sama5d4_layers[] = { |
245 | { | |
246 | .name = "base", | |
247 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
248 | .regs_offset = 0x40, | |
249 | .id = 0, | |
250 | .type = ATMEL_HLCDC_BASE_LAYER, | |
251 | .nconfigs = 7, | |
252 | .layout = { | |
253 | .xstride = { 2 }, | |
254 | .default_color = 3, | |
255 | .general_config = 4, | |
256 | .disc_pos = 5, | |
257 | .disc_size = 6, | |
258 | }, | |
259 | }, | |
260 | { | |
261 | .name = "overlay1", | |
262 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
263 | .regs_offset = 0x140, | |
264 | .id = 1, | |
265 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
266 | .nconfigs = 10, | |
267 | .layout = { | |
268 | .pos = 2, | |
269 | .size = 3, | |
270 | .xstride = { 4 }, | |
271 | .pstride = { 5 }, | |
272 | .default_color = 6, | |
273 | .chroma_key = 7, | |
274 | .chroma_key_mask = 8, | |
275 | .general_config = 9, | |
276 | }, | |
277 | }, | |
278 | { | |
279 | .name = "overlay2", | |
280 | .formats = &atmel_hlcdc_plane_rgb_formats, | |
281 | .regs_offset = 0x240, | |
282 | .id = 2, | |
283 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
284 | .nconfigs = 10, | |
285 | .layout = { | |
286 | .pos = 2, | |
287 | .size = 3, | |
288 | .xstride = { 4 }, | |
289 | .pstride = { 5 }, | |
290 | .default_color = 6, | |
291 | .chroma_key = 7, | |
292 | .chroma_key_mask = 8, | |
293 | .general_config = 9, | |
294 | }, | |
295 | }, | |
296 | { | |
297 | .name = "high-end-overlay", | |
298 | .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, | |
299 | .regs_offset = 0x340, | |
300 | .id = 3, | |
301 | .type = ATMEL_HLCDC_OVERLAY_LAYER, | |
302 | .nconfigs = 42, | |
303 | .layout = { | |
304 | .pos = 2, | |
305 | .size = 3, | |
306 | .memsize = 4, | |
307 | .xstride = { 5, 7 }, | |
308 | .pstride = { 6, 8 }, | |
309 | .default_color = 9, | |
310 | .chroma_key = 10, | |
311 | .chroma_key_mask = 11, | |
312 | .general_config = 12, | |
313 | .csc = 14, | |
314 | }, | |
315 | }, | |
316 | }; | |
317 | ||
318 | static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d4 = { | |
319 | .min_width = 0, | |
320 | .min_height = 0, | |
321 | .max_width = 2048, | |
322 | .max_height = 2048, | |
323 | .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d4_layers), | |
324 | .layers = atmel_hlcdc_sama5d4_layers, | |
325 | }; | |
1a396789 | 326 | static const struct of_device_id atmel_hlcdc_of_match[] = { |
6b22cadc BB |
327 | { |
328 | .compatible = "atmel,at91sam9n12-hlcdc", | |
329 | .data = &atmel_hlcdc_dc_at91sam9n12, | |
330 | }, | |
348ef85f BB |
331 | { |
332 | .compatible = "atmel,at91sam9x5-hlcdc", | |
333 | .data = &atmel_hlcdc_dc_at91sam9x5, | |
334 | }, | |
34649c40 NF |
335 | { |
336 | .compatible = "atmel,sama5d2-hlcdc", | |
337 | .data = &atmel_hlcdc_dc_sama5d4, | |
338 | }, | |
1a396789 BB |
339 | { |
340 | .compatible = "atmel,sama5d3-hlcdc", | |
341 | .data = &atmel_hlcdc_dc_sama5d3, | |
342 | }, | |
5b9fb5e6 BB |
343 | { |
344 | .compatible = "atmel,sama5d4-hlcdc", | |
345 | .data = &atmel_hlcdc_dc_sama5d4, | |
346 | }, | |
1a396789 BB |
347 | { /* sentinel */ }, |
348 | }; | |
d6ec5ec9 | 349 | MODULE_DEVICE_TABLE(of, atmel_hlcdc_of_match); |
1a396789 BB |
350 | |
351 | int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, | |
352 | struct drm_display_mode *mode) | |
353 | { | |
354 | int vfront_porch = mode->vsync_start - mode->vdisplay; | |
355 | int vback_porch = mode->vtotal - mode->vsync_end; | |
356 | int vsync_len = mode->vsync_end - mode->vsync_start; | |
357 | int hfront_porch = mode->hsync_start - mode->hdisplay; | |
358 | int hback_porch = mode->htotal - mode->hsync_end; | |
359 | int hsync_len = mode->hsync_end - mode->hsync_start; | |
360 | ||
361 | if (hsync_len > 0x40 || hsync_len < 1) | |
362 | return MODE_HSYNC; | |
363 | ||
364 | if (vsync_len > 0x40 || vsync_len < 1) | |
365 | return MODE_VSYNC; | |
366 | ||
367 | if (hfront_porch > 0x200 || hfront_porch < 1 || | |
368 | hback_porch > 0x200 || hback_porch < 1 || | |
369 | mode->hdisplay < 1) | |
370 | return MODE_H_ILLEGAL; | |
371 | ||
372 | if (vfront_porch > 0x40 || vfront_porch < 1 || | |
373 | vback_porch > 0x40 || vback_porch < 0 || | |
374 | mode->vdisplay < 1) | |
375 | return MODE_V_ILLEGAL; | |
376 | ||
377 | return MODE_OK; | |
378 | } | |
379 | ||
380 | static irqreturn_t atmel_hlcdc_dc_irq_handler(int irq, void *data) | |
381 | { | |
382 | struct drm_device *dev = data; | |
383 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
384 | unsigned long status; | |
385 | unsigned int imr, isr; | |
386 | int i; | |
387 | ||
388 | regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_IMR, &imr); | |
389 | regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); | |
390 | status = imr & isr; | |
391 | if (!status) | |
392 | return IRQ_NONE; | |
393 | ||
394 | if (status & ATMEL_HLCDC_SOF) | |
395 | atmel_hlcdc_crtc_irq(dc->crtc); | |
396 | ||
397 | for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { | |
398 | struct atmel_hlcdc_layer *layer = dc->layers[i]; | |
399 | ||
400 | if (!(ATMEL_HLCDC_LAYER_STATUS(i) & status) || !layer) | |
401 | continue; | |
402 | ||
403 | atmel_hlcdc_layer_irq(layer); | |
404 | } | |
405 | ||
406 | return IRQ_HANDLED; | |
407 | } | |
408 | ||
409 | static struct drm_framebuffer *atmel_hlcdc_fb_create(struct drm_device *dev, | |
1eb83451 | 410 | struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd) |
1a396789 BB |
411 | { |
412 | return drm_fb_cma_create(dev, file_priv, mode_cmd); | |
413 | } | |
414 | ||
415 | static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev) | |
416 | { | |
417 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
418 | ||
419 | if (dc->fbdev) { | |
420 | drm_fbdev_cma_hotplug_event(dc->fbdev); | |
421 | } else { | |
422 | dc->fbdev = drm_fbdev_cma_init(dev, 24, | |
423 | dev->mode_config.num_crtc, | |
424 | dev->mode_config.num_connector); | |
425 | if (IS_ERR(dc->fbdev)) | |
426 | dc->fbdev = NULL; | |
427 | } | |
428 | } | |
429 | ||
9b190610 BB |
430 | struct atmel_hlcdc_dc_commit { |
431 | struct work_struct work; | |
432 | struct drm_device *dev; | |
433 | struct drm_atomic_state *state; | |
434 | }; | |
435 | ||
436 | static void | |
437 | atmel_hlcdc_dc_atomic_complete(struct atmel_hlcdc_dc_commit *commit) | |
438 | { | |
439 | struct drm_device *dev = commit->dev; | |
440 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
441 | struct drm_atomic_state *old_state = commit->state; | |
442 | ||
443 | /* Apply the atomic update. */ | |
444 | drm_atomic_helper_commit_modeset_disables(dev, old_state); | |
445 | drm_atomic_helper_commit_planes(dev, old_state, false); | |
446 | drm_atomic_helper_commit_modeset_enables(dev, old_state); | |
447 | ||
448 | drm_atomic_helper_wait_for_vblanks(dev, old_state); | |
449 | ||
450 | drm_atomic_helper_cleanup_planes(dev, old_state); | |
451 | ||
452 | drm_atomic_state_free(old_state); | |
453 | ||
454 | /* Complete the commit, wake up any waiter. */ | |
455 | spin_lock(&dc->commit.wait.lock); | |
456 | dc->commit.pending = false; | |
457 | wake_up_all_locked(&dc->commit.wait); | |
458 | spin_unlock(&dc->commit.wait.lock); | |
459 | ||
460 | kfree(commit); | |
461 | } | |
462 | ||
463 | static void atmel_hlcdc_dc_atomic_work(struct work_struct *work) | |
464 | { | |
465 | struct atmel_hlcdc_dc_commit *commit = | |
466 | container_of(work, struct atmel_hlcdc_dc_commit, work); | |
467 | ||
468 | atmel_hlcdc_dc_atomic_complete(commit); | |
469 | } | |
470 | ||
471 | static int atmel_hlcdc_dc_atomic_commit(struct drm_device *dev, | |
472 | struct drm_atomic_state *state, | |
473 | bool async) | |
474 | { | |
475 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
476 | struct atmel_hlcdc_dc_commit *commit; | |
477 | int ret; | |
478 | ||
479 | ret = drm_atomic_helper_prepare_planes(dev, state); | |
480 | if (ret) | |
481 | return ret; | |
482 | ||
483 | /* Allocate the commit object. */ | |
484 | commit = kzalloc(sizeof(*commit), GFP_KERNEL); | |
485 | if (!commit) { | |
486 | ret = -ENOMEM; | |
487 | goto error; | |
488 | } | |
489 | ||
490 | INIT_WORK(&commit->work, atmel_hlcdc_dc_atomic_work); | |
491 | commit->dev = dev; | |
492 | commit->state = state; | |
493 | ||
494 | spin_lock(&dc->commit.wait.lock); | |
495 | ret = wait_event_interruptible_locked(dc->commit.wait, | |
496 | !dc->commit.pending); | |
497 | if (ret == 0) | |
498 | dc->commit.pending = true; | |
499 | spin_unlock(&dc->commit.wait.lock); | |
500 | ||
501 | if (ret) { | |
502 | kfree(commit); | |
503 | goto error; | |
504 | } | |
505 | ||
506 | /* Swap the state, this is the point of no return. */ | |
507 | drm_atomic_helper_swap_state(dev, state); | |
508 | ||
509 | if (async) | |
510 | queue_work(dc->wq, &commit->work); | |
511 | else | |
512 | atmel_hlcdc_dc_atomic_complete(commit); | |
513 | ||
514 | return 0; | |
515 | ||
516 | error: | |
517 | drm_atomic_helper_cleanup_planes(dev, state); | |
518 | return ret; | |
519 | } | |
520 | ||
1a396789 BB |
521 | static const struct drm_mode_config_funcs mode_config_funcs = { |
522 | .fb_create = atmel_hlcdc_fb_create, | |
523 | .output_poll_changed = atmel_hlcdc_fb_output_poll_changed, | |
2389fc13 | 524 | .atomic_check = drm_atomic_helper_check, |
9b190610 | 525 | .atomic_commit = atmel_hlcdc_dc_atomic_commit, |
1a396789 BB |
526 | }; |
527 | ||
528 | static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) | |
529 | { | |
530 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
531 | struct atmel_hlcdc_planes *planes; | |
532 | int ret; | |
533 | int i; | |
534 | ||
535 | drm_mode_config_init(dev); | |
536 | ||
537 | ret = atmel_hlcdc_create_outputs(dev); | |
538 | if (ret) { | |
539 | dev_err(dev->dev, "failed to create panel: %d\n", ret); | |
540 | return ret; | |
541 | } | |
542 | ||
543 | planes = atmel_hlcdc_create_planes(dev); | |
544 | if (IS_ERR(planes)) { | |
545 | dev_err(dev->dev, "failed to create planes\n"); | |
546 | return PTR_ERR(planes); | |
547 | } | |
548 | ||
549 | dc->planes = planes; | |
550 | ||
551 | dc->layers[planes->primary->layer.desc->id] = | |
552 | &planes->primary->layer; | |
553 | ||
554 | if (planes->cursor) | |
555 | dc->layers[planes->cursor->layer.desc->id] = | |
556 | &planes->cursor->layer; | |
557 | ||
558 | for (i = 0; i < planes->noverlays; i++) | |
559 | dc->layers[planes->overlays[i]->layer.desc->id] = | |
560 | &planes->overlays[i]->layer; | |
561 | ||
562 | ret = atmel_hlcdc_crtc_create(dev); | |
563 | if (ret) { | |
564 | dev_err(dev->dev, "failed to create crtc\n"); | |
565 | return ret; | |
566 | } | |
567 | ||
568 | dev->mode_config.min_width = dc->desc->min_width; | |
569 | dev->mode_config.min_height = dc->desc->min_height; | |
570 | dev->mode_config.max_width = dc->desc->max_width; | |
571 | dev->mode_config.max_height = dc->desc->max_height; | |
572 | dev->mode_config.funcs = &mode_config_funcs; | |
573 | ||
574 | return 0; | |
575 | } | |
576 | ||
577 | static int atmel_hlcdc_dc_load(struct drm_device *dev) | |
578 | { | |
579 | struct platform_device *pdev = to_platform_device(dev->dev); | |
580 | const struct of_device_id *match; | |
581 | struct atmel_hlcdc_dc *dc; | |
582 | int ret; | |
583 | ||
584 | match = of_match_node(atmel_hlcdc_of_match, dev->dev->parent->of_node); | |
585 | if (!match) { | |
586 | dev_err(&pdev->dev, "invalid compatible string\n"); | |
587 | return -ENODEV; | |
588 | } | |
589 | ||
590 | if (!match->data) { | |
591 | dev_err(&pdev->dev, "invalid hlcdc description\n"); | |
592 | return -EINVAL; | |
593 | } | |
594 | ||
595 | dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL); | |
596 | if (!dc) | |
597 | return -ENOMEM; | |
598 | ||
599 | dc->wq = alloc_ordered_workqueue("atmel-hlcdc-dc", 0); | |
600 | if (!dc->wq) | |
601 | return -ENOMEM; | |
602 | ||
9b190610 | 603 | init_waitqueue_head(&dc->commit.wait); |
1a396789 BB |
604 | dc->desc = match->data; |
605 | dc->hlcdc = dev_get_drvdata(dev->dev->parent); | |
606 | dev->dev_private = dc; | |
607 | ||
608 | ret = clk_prepare_enable(dc->hlcdc->periph_clk); | |
609 | if (ret) { | |
610 | dev_err(dev->dev, "failed to enable periph_clk\n"); | |
611 | goto err_destroy_wq; | |
612 | } | |
613 | ||
614 | pm_runtime_enable(dev->dev); | |
615 | ||
8c4b4b0d | 616 | ret = drm_vblank_init(dev, 1); |
1a396789 | 617 | if (ret < 0) { |
8c4b4b0d | 618 | dev_err(dev->dev, "failed to initialize vblank\n"); |
1a396789 BB |
619 | goto err_periph_clk_disable; |
620 | } | |
621 | ||
8c4b4b0d | 622 | ret = atmel_hlcdc_dc_modeset_init(dev); |
1a396789 | 623 | if (ret < 0) { |
8c4b4b0d | 624 | dev_err(dev->dev, "failed to initialize mode setting\n"); |
1a396789 BB |
625 | goto err_periph_clk_disable; |
626 | } | |
627 | ||
8c4b4b0d BB |
628 | drm_mode_config_reset(dev); |
629 | ||
1a396789 BB |
630 | pm_runtime_get_sync(dev->dev); |
631 | ret = drm_irq_install(dev, dc->hlcdc->irq); | |
632 | pm_runtime_put_sync(dev->dev); | |
633 | if (ret < 0) { | |
634 | dev_err(dev->dev, "failed to install IRQ handler\n"); | |
635 | goto err_periph_clk_disable; | |
636 | } | |
637 | ||
638 | platform_set_drvdata(pdev, dev); | |
639 | ||
640 | drm_kms_helper_poll_init(dev); | |
641 | ||
642 | /* force connectors detection */ | |
643 | drm_helper_hpd_irq_event(dev); | |
644 | ||
645 | return 0; | |
646 | ||
647 | err_periph_clk_disable: | |
648 | pm_runtime_disable(dev->dev); | |
649 | clk_disable_unprepare(dc->hlcdc->periph_clk); | |
650 | ||
651 | err_destroy_wq: | |
652 | destroy_workqueue(dc->wq); | |
653 | ||
654 | return ret; | |
655 | } | |
656 | ||
657 | static void atmel_hlcdc_dc_unload(struct drm_device *dev) | |
658 | { | |
659 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
660 | ||
661 | if (dc->fbdev) | |
662 | drm_fbdev_cma_fini(dc->fbdev); | |
663 | flush_workqueue(dc->wq); | |
664 | drm_kms_helper_poll_fini(dev); | |
665 | drm_mode_config_cleanup(dev); | |
666 | drm_vblank_cleanup(dev); | |
667 | ||
668 | pm_runtime_get_sync(dev->dev); | |
669 | drm_irq_uninstall(dev); | |
670 | pm_runtime_put_sync(dev->dev); | |
671 | ||
672 | dev->dev_private = NULL; | |
673 | ||
674 | pm_runtime_disable(dev->dev); | |
675 | clk_disable_unprepare(dc->hlcdc->periph_clk); | |
676 | destroy_workqueue(dc->wq); | |
677 | } | |
678 | ||
679 | static int atmel_hlcdc_dc_connector_plug_all(struct drm_device *dev) | |
680 | { | |
681 | struct drm_connector *connector, *failed; | |
682 | int ret; | |
683 | ||
684 | mutex_lock(&dev->mode_config.mutex); | |
685 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) { | |
686 | ret = drm_connector_register(connector); | |
687 | if (ret) { | |
688 | failed = connector; | |
689 | goto err; | |
690 | } | |
691 | } | |
692 | mutex_unlock(&dev->mode_config.mutex); | |
693 | return 0; | |
694 | ||
695 | err: | |
696 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) { | |
697 | if (failed == connector) | |
698 | break; | |
699 | ||
700 | drm_connector_unregister(connector); | |
701 | } | |
702 | mutex_unlock(&dev->mode_config.mutex); | |
703 | ||
704 | return ret; | |
705 | } | |
706 | ||
707 | static void atmel_hlcdc_dc_connector_unplug_all(struct drm_device *dev) | |
708 | { | |
709 | mutex_lock(&dev->mode_config.mutex); | |
222b9094 | 710 | drm_connector_unregister_all(dev); |
1a396789 BB |
711 | mutex_unlock(&dev->mode_config.mutex); |
712 | } | |
713 | ||
1a396789 BB |
714 | static void atmel_hlcdc_dc_lastclose(struct drm_device *dev) |
715 | { | |
716 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
717 | ||
718 | drm_fbdev_cma_restore_mode(dc->fbdev); | |
719 | } | |
720 | ||
721 | static int atmel_hlcdc_dc_irq_postinstall(struct drm_device *dev) | |
722 | { | |
723 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
724 | unsigned int cfg = 0; | |
725 | int i; | |
726 | ||
727 | /* Enable interrupts on activated layers */ | |
728 | for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { | |
729 | if (dc->layers[i]) | |
730 | cfg |= ATMEL_HLCDC_LAYER_STATUS(i); | |
731 | } | |
732 | ||
733 | regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, cfg); | |
734 | ||
735 | return 0; | |
736 | } | |
737 | ||
738 | static void atmel_hlcdc_dc_irq_uninstall(struct drm_device *dev) | |
739 | { | |
740 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
741 | unsigned int isr; | |
742 | ||
743 | regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, 0xffffffff); | |
744 | regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); | |
745 | } | |
746 | ||
88e72717 TR |
747 | static int atmel_hlcdc_dc_enable_vblank(struct drm_device *dev, |
748 | unsigned int pipe) | |
1a396789 BB |
749 | { |
750 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
751 | ||
752 | /* Enable SOF (Start Of Frame) interrupt for vblank counting */ | |
753 | regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, ATMEL_HLCDC_SOF); | |
754 | ||
755 | return 0; | |
756 | } | |
757 | ||
88e72717 TR |
758 | static void atmel_hlcdc_dc_disable_vblank(struct drm_device *dev, |
759 | unsigned int pipe) | |
1a396789 BB |
760 | { |
761 | struct atmel_hlcdc_dc *dc = dev->dev_private; | |
762 | ||
763 | regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, ATMEL_HLCDC_SOF); | |
764 | } | |
765 | ||
766 | static const struct file_operations fops = { | |
767 | .owner = THIS_MODULE, | |
768 | .open = drm_open, | |
769 | .release = drm_release, | |
770 | .unlocked_ioctl = drm_ioctl, | |
771 | #ifdef CONFIG_COMPAT | |
772 | .compat_ioctl = drm_compat_ioctl, | |
773 | #endif | |
774 | .poll = drm_poll, | |
775 | .read = drm_read, | |
776 | .llseek = no_llseek, | |
777 | .mmap = drm_gem_cma_mmap, | |
778 | }; | |
779 | ||
780 | static struct drm_driver atmel_hlcdc_dc_driver = { | |
e14c71c8 | 781 | .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | |
aa690a9e BB |
782 | DRIVER_MODESET | DRIVER_PRIME | |
783 | DRIVER_ATOMIC, | |
1a396789 BB |
784 | .lastclose = atmel_hlcdc_dc_lastclose, |
785 | .irq_handler = atmel_hlcdc_dc_irq_handler, | |
786 | .irq_preinstall = atmel_hlcdc_dc_irq_uninstall, | |
787 | .irq_postinstall = atmel_hlcdc_dc_irq_postinstall, | |
788 | .irq_uninstall = atmel_hlcdc_dc_irq_uninstall, | |
b44f8408 | 789 | .get_vblank_counter = drm_vblank_no_hw_counter, |
1a396789 BB |
790 | .enable_vblank = atmel_hlcdc_dc_enable_vblank, |
791 | .disable_vblank = atmel_hlcdc_dc_disable_vblank, | |
792 | .gem_free_object = drm_gem_cma_free_object, | |
793 | .gem_vm_ops = &drm_gem_cma_vm_ops, | |
e14c71c8 BB |
794 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, |
795 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
796 | .gem_prime_import = drm_gem_prime_import, | |
797 | .gem_prime_export = drm_gem_prime_export, | |
798 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, | |
799 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, | |
800 | .gem_prime_vmap = drm_gem_cma_prime_vmap, | |
801 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, | |
802 | .gem_prime_mmap = drm_gem_cma_prime_mmap, | |
1a396789 BB |
803 | .dumb_create = drm_gem_cma_dumb_create, |
804 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, | |
805 | .dumb_destroy = drm_gem_dumb_destroy, | |
806 | .fops = &fops, | |
807 | .name = "atmel-hlcdc", | |
808 | .desc = "Atmel HLCD Controller DRM", | |
809 | .date = "20141504", | |
810 | .major = 1, | |
811 | .minor = 0, | |
812 | }; | |
813 | ||
814 | static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) | |
815 | { | |
816 | struct drm_device *ddev; | |
817 | int ret; | |
818 | ||
819 | ddev = drm_dev_alloc(&atmel_hlcdc_dc_driver, &pdev->dev); | |
820 | if (!ddev) | |
821 | return -ENOMEM; | |
822 | ||
1a396789 BB |
823 | ret = atmel_hlcdc_dc_load(ddev); |
824 | if (ret) | |
825 | goto err_unref; | |
826 | ||
827 | ret = drm_dev_register(ddev, 0); | |
828 | if (ret) | |
829 | goto err_unload; | |
830 | ||
831 | ret = atmel_hlcdc_dc_connector_plug_all(ddev); | |
832 | if (ret) | |
833 | goto err_unregister; | |
834 | ||
835 | return 0; | |
836 | ||
837 | err_unregister: | |
838 | drm_dev_unregister(ddev); | |
839 | ||
840 | err_unload: | |
841 | atmel_hlcdc_dc_unload(ddev); | |
842 | ||
843 | err_unref: | |
844 | drm_dev_unref(ddev); | |
845 | ||
846 | return ret; | |
847 | } | |
848 | ||
849 | static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev) | |
850 | { | |
851 | struct drm_device *ddev = platform_get_drvdata(pdev); | |
852 | ||
853 | atmel_hlcdc_dc_connector_unplug_all(ddev); | |
854 | drm_dev_unregister(ddev); | |
855 | atmel_hlcdc_dc_unload(ddev); | |
856 | drm_dev_unref(ddev); | |
857 | ||
858 | return 0; | |
859 | } | |
860 | ||
dbb3df2d | 861 | #ifdef CONFIG_PM_SLEEP |
58486982 SR |
862 | static int atmel_hlcdc_dc_drm_suspend(struct device *dev) |
863 | { | |
864 | struct drm_device *drm_dev = dev_get_drvdata(dev); | |
865 | struct drm_crtc *crtc; | |
866 | ||
867 | if (pm_runtime_suspended(dev)) | |
868 | return 0; | |
869 | ||
870 | drm_modeset_lock_all(drm_dev); | |
f026eb6e SR |
871 | list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) |
872 | atmel_hlcdc_crtc_suspend(crtc); | |
58486982 SR |
873 | drm_modeset_unlock_all(drm_dev); |
874 | return 0; | |
875 | } | |
876 | ||
877 | static int atmel_hlcdc_dc_drm_resume(struct device *dev) | |
878 | { | |
879 | struct drm_device *drm_dev = dev_get_drvdata(dev); | |
880 | struct drm_crtc *crtc; | |
881 | ||
882 | if (pm_runtime_suspended(dev)) | |
883 | return 0; | |
884 | ||
885 | drm_modeset_lock_all(drm_dev); | |
f026eb6e SR |
886 | list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) |
887 | atmel_hlcdc_crtc_resume(crtc); | |
58486982 SR |
888 | drm_modeset_unlock_all(drm_dev); |
889 | return 0; | |
890 | } | |
891 | #endif | |
892 | ||
893 | static SIMPLE_DEV_PM_OPS(atmel_hlcdc_dc_drm_pm_ops, | |
894 | atmel_hlcdc_dc_drm_suspend, atmel_hlcdc_dc_drm_resume); | |
895 | ||
1a396789 BB |
896 | static const struct of_device_id atmel_hlcdc_dc_of_match[] = { |
897 | { .compatible = "atmel,hlcdc-display-controller" }, | |
898 | { }, | |
899 | }; | |
900 | ||
901 | static struct platform_driver atmel_hlcdc_dc_platform_driver = { | |
902 | .probe = atmel_hlcdc_dc_drm_probe, | |
903 | .remove = atmel_hlcdc_dc_drm_remove, | |
904 | .driver = { | |
905 | .name = "atmel-hlcdc-display-controller", | |
58486982 | 906 | .pm = &atmel_hlcdc_dc_drm_pm_ops, |
1a396789 BB |
907 | .of_match_table = atmel_hlcdc_dc_of_match, |
908 | }, | |
909 | }; | |
910 | module_platform_driver(atmel_hlcdc_dc_platform_driver); | |
911 | ||
912 | MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>"); | |
913 | MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); | |
914 | MODULE_DESCRIPTION("Atmel HLCDC Display Controller DRM Driver"); | |
915 | MODULE_LICENSE("GPL"); | |
916 | MODULE_ALIAS("platform:atmel-hlcdc-dc"); |