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