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