1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) STMicroelectronics SA 2014 |
4 | * Authors: Benjamin Gaignard <benjamin.gaignard@st.com> |
5 | * Fabien Dessenne <fabien.dessenne@st.com> |
6 | * for STMicroelectronics. |
7 | */ |
8 | |
9 | #include <linux/dma-mapping.h> |
10 | #include <linux/of.h> |
11 | #include <linux/seq_file.h> |
12 | |
13 | #include <drm/drm_atomic.h> |
14 | #include <drm/drm_device.h> |
15 | #include <drm/drm_fb_dma_helper.h> |
16 | #include <drm/drm_fourcc.h> |
17 | #include <drm/drm_framebuffer.h> |
18 | #include <drm/drm_gem_dma_helper.h> |
19 | |
20 | #include "sti_compositor.h" |
21 | #include "sti_gdp.h" |
22 | #include "sti_plane.h" |
23 | #include "sti_vtg.h" |
24 | |
25 | #define ALPHASWITCH BIT(6) |
26 | #define ENA_COLOR_FILL BIT(8) |
27 | #define BIGNOTLITTLE BIT(23) |
28 | #define WAIT_NEXT_VSYNC BIT(31) |
29 | |
30 | /* GDP color formats */ |
31 | #define GDP_RGB565 0x00 |
32 | #define GDP_RGB888 0x01 |
33 | #define GDP_RGB888_32 0x02 |
34 | #define GDP_XBGR8888 (GDP_RGB888_32 | BIGNOTLITTLE | ALPHASWITCH) |
35 | #define GDP_ARGB8565 0x04 |
36 | #define GDP_ARGB8888 0x05 |
37 | #define GDP_ABGR8888 (GDP_ARGB8888 | BIGNOTLITTLE | ALPHASWITCH) |
38 | #define GDP_ARGB1555 0x06 |
39 | #define GDP_ARGB4444 0x07 |
40 | |
41 | #define GDP2STR(fmt) { GDP_ ## fmt, #fmt } |
42 | |
43 | static struct gdp_format_to_str { |
44 | int format; |
45 | char name[20]; |
46 | } gdp_format_to_str[] = { |
47 | GDP2STR(RGB565), |
48 | GDP2STR(RGB888), |
49 | GDP2STR(RGB888_32), |
50 | GDP2STR(XBGR8888), |
51 | GDP2STR(ARGB8565), |
52 | GDP2STR(ARGB8888), |
53 | GDP2STR(ABGR8888), |
54 | GDP2STR(ARGB1555), |
55 | GDP2STR(ARGB4444) |
56 | }; |
57 | |
58 | #define GAM_GDP_CTL_OFFSET 0x00 |
59 | #define GAM_GDP_AGC_OFFSET 0x04 |
60 | #define GAM_GDP_VPO_OFFSET 0x0C |
61 | #define GAM_GDP_VPS_OFFSET 0x10 |
62 | #define GAM_GDP_PML_OFFSET 0x14 |
63 | #define GAM_GDP_PMP_OFFSET 0x18 |
64 | #define GAM_GDP_SIZE_OFFSET 0x1C |
65 | #define GAM_GDP_NVN_OFFSET 0x24 |
66 | #define GAM_GDP_KEY1_OFFSET 0x28 |
67 | #define GAM_GDP_KEY2_OFFSET 0x2C |
68 | #define GAM_GDP_PPT_OFFSET 0x34 |
69 | #define GAM_GDP_CML_OFFSET 0x3C |
70 | #define GAM_GDP_MST_OFFSET 0x68 |
71 | |
72 | #define GAM_GDP_ALPHARANGE_255 BIT(5) |
73 | #define GAM_GDP_AGC_FULL_RANGE 0x00808080 |
74 | #define GAM_GDP_PPT_IGNORE (BIT(1) | BIT(0)) |
75 | |
76 | #define GAM_GDP_SIZE_MAX_WIDTH 3840 |
77 | #define GAM_GDP_SIZE_MAX_HEIGHT 2160 |
78 | |
79 | #define GDP_NODE_NB_BANK 2 |
80 | #define GDP_NODE_PER_FIELD 2 |
81 | |
82 | struct sti_gdp_node { |
83 | u32 gam_gdp_ctl; |
84 | u32 gam_gdp_agc; |
85 | u32 reserved1; |
86 | u32 gam_gdp_vpo; |
87 | u32 gam_gdp_vps; |
88 | u32 gam_gdp_pml; |
89 | u32 gam_gdp_pmp; |
90 | u32 gam_gdp_size; |
91 | u32 reserved2; |
92 | u32 gam_gdp_nvn; |
93 | u32 gam_gdp_key1; |
94 | u32 gam_gdp_key2; |
95 | u32 reserved3; |
96 | u32 gam_gdp_ppt; |
97 | u32 reserved4; |
98 | u32 gam_gdp_cml; |
99 | }; |
100 | |
101 | struct sti_gdp_node_list { |
102 | struct sti_gdp_node *top_field; |
103 | dma_addr_t top_field_paddr; |
104 | struct sti_gdp_node *btm_field; |
105 | dma_addr_t btm_field_paddr; |
106 | }; |
107 | |
108 | /* |
109 | * STI GDP structure |
110 | * |
111 | * @sti_plane: sti_plane structure |
112 | * @dev: driver device |
113 | * @regs: gdp registers |
114 | * @clk_pix: pixel clock for the current gdp |
115 | * @clk_main_parent: gdp parent clock if main path used |
116 | * @clk_aux_parent: gdp parent clock if aux path used |
117 | * @vtg_field_nb: callback for VTG FIELD (top or bottom) notification |
118 | * @is_curr_top: true if the current node processed is the top field |
119 | * @node_list: array of node list |
120 | * @vtg: registered vtg |
121 | */ |
122 | struct sti_gdp { |
123 | struct sti_plane plane; |
124 | struct device *dev; |
125 | void __iomem *regs; |
126 | struct clk *clk_pix; |
127 | struct clk *clk_main_parent; |
128 | struct clk *clk_aux_parent; |
129 | struct notifier_block vtg_field_nb; |
130 | bool is_curr_top; |
131 | struct sti_gdp_node_list node_list[GDP_NODE_NB_BANK]; |
132 | struct sti_vtg *vtg; |
133 | }; |
134 | |
135 | #define to_sti_gdp(x) container_of(x, struct sti_gdp, plane) |
136 | |
137 | static const uint32_t gdp_supported_formats[] = { |
138 | DRM_FORMAT_XRGB8888, |
139 | DRM_FORMAT_XBGR8888, |
140 | DRM_FORMAT_ARGB8888, |
141 | DRM_FORMAT_ABGR8888, |
142 | DRM_FORMAT_ARGB4444, |
143 | DRM_FORMAT_ARGB1555, |
144 | DRM_FORMAT_RGB565, |
145 | DRM_FORMAT_RGB888, |
146 | }; |
147 | |
148 | #define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \ |
149 | readl(gdp->regs + reg ## _OFFSET)) |
150 | |
151 | static void gdp_dbg_ctl(struct seq_file *s, int val) |
152 | { |
153 | int i; |
154 | |
155 | seq_puts(m: s, s: "\tColor:" ); |
156 | for (i = 0; i < ARRAY_SIZE(gdp_format_to_str); i++) { |
157 | if (gdp_format_to_str[i].format == (val & 0x1F)) { |
158 | seq_puts(m: s, s: gdp_format_to_str[i].name); |
159 | break; |
160 | } |
161 | } |
162 | if (i == ARRAY_SIZE(gdp_format_to_str)) |
163 | seq_puts(m: s, s: "<UNKNOWN>" ); |
164 | |
165 | seq_printf(m: s, fmt: "\tWaitNextVsync:%d" , val & WAIT_NEXT_VSYNC ? 1 : 0); |
166 | } |
167 | |
168 | static void gdp_dbg_vpo(struct seq_file *s, int val) |
169 | { |
170 | seq_printf(m: s, fmt: "\txdo:%4d\tydo:%4d" , val & 0xFFFF, (val >> 16) & 0xFFFF); |
171 | } |
172 | |
173 | static void gdp_dbg_vps(struct seq_file *s, int val) |
174 | { |
175 | seq_printf(m: s, fmt: "\txds:%4d\tyds:%4d" , val & 0xFFFF, (val >> 16) & 0xFFFF); |
176 | } |
177 | |
178 | static void gdp_dbg_size(struct seq_file *s, int val) |
179 | { |
180 | seq_printf(m: s, fmt: "\t%d x %d" , val & 0xFFFF, (val >> 16) & 0xFFFF); |
181 | } |
182 | |
183 | static void gdp_dbg_nvn(struct seq_file *s, struct sti_gdp *gdp, int val) |
184 | { |
185 | void *base = NULL; |
186 | unsigned int i; |
187 | |
188 | for (i = 0; i < GDP_NODE_NB_BANK; i++) { |
189 | if (gdp->node_list[i].top_field_paddr == val) { |
190 | base = gdp->node_list[i].top_field; |
191 | break; |
192 | } |
193 | if (gdp->node_list[i].btm_field_paddr == val) { |
194 | base = gdp->node_list[i].btm_field; |
195 | break; |
196 | } |
197 | } |
198 | |
199 | if (base) |
200 | seq_printf(m: s, fmt: "\tVirt @: %p" , base); |
201 | } |
202 | |
203 | static void gdp_dbg_ppt(struct seq_file *s, int val) |
204 | { |
205 | if (val & GAM_GDP_PPT_IGNORE) |
206 | seq_puts(m: s, s: "\tNot displayed on mixer!" ); |
207 | } |
208 | |
209 | static void gdp_dbg_mst(struct seq_file *s, int val) |
210 | { |
211 | if (val & 1) |
212 | seq_puts(m: s, s: "\tBUFFER UNDERFLOW!" ); |
213 | } |
214 | |
215 | static int gdp_dbg_show(struct seq_file *s, void *data) |
216 | { |
217 | struct drm_info_node *node = s->private; |
218 | struct sti_gdp *gdp = (struct sti_gdp *)node->info_ent->data; |
219 | struct drm_plane *drm_plane = &gdp->plane.drm_plane; |
220 | struct drm_crtc *crtc; |
221 | |
222 | drm_modeset_lock(lock: &drm_plane->mutex, NULL); |
223 | crtc = drm_plane->state->crtc; |
224 | drm_modeset_unlock(lock: &drm_plane->mutex); |
225 | |
226 | seq_printf(m: s, fmt: "%s: (vaddr = 0x%p)" , |
227 | sti_plane_to_str(plane: &gdp->plane), gdp->regs); |
228 | |
229 | DBGFS_DUMP(GAM_GDP_CTL); |
230 | gdp_dbg_ctl(s, readl(addr: gdp->regs + GAM_GDP_CTL_OFFSET)); |
231 | DBGFS_DUMP(GAM_GDP_AGC); |
232 | DBGFS_DUMP(GAM_GDP_VPO); |
233 | gdp_dbg_vpo(s, readl(addr: gdp->regs + GAM_GDP_VPO_OFFSET)); |
234 | DBGFS_DUMP(GAM_GDP_VPS); |
235 | gdp_dbg_vps(s, readl(addr: gdp->regs + GAM_GDP_VPS_OFFSET)); |
236 | DBGFS_DUMP(GAM_GDP_PML); |
237 | DBGFS_DUMP(GAM_GDP_PMP); |
238 | DBGFS_DUMP(GAM_GDP_SIZE); |
239 | gdp_dbg_size(s, readl(addr: gdp->regs + GAM_GDP_SIZE_OFFSET)); |
240 | DBGFS_DUMP(GAM_GDP_NVN); |
241 | gdp_dbg_nvn(s, gdp, readl(addr: gdp->regs + GAM_GDP_NVN_OFFSET)); |
242 | DBGFS_DUMP(GAM_GDP_KEY1); |
243 | DBGFS_DUMP(GAM_GDP_KEY2); |
244 | DBGFS_DUMP(GAM_GDP_PPT); |
245 | gdp_dbg_ppt(s, readl(addr: gdp->regs + GAM_GDP_PPT_OFFSET)); |
246 | DBGFS_DUMP(GAM_GDP_CML); |
247 | DBGFS_DUMP(GAM_GDP_MST); |
248 | gdp_dbg_mst(s, readl(addr: gdp->regs + GAM_GDP_MST_OFFSET)); |
249 | |
250 | seq_puts(m: s, s: "\n\n" ); |
251 | if (!crtc) |
252 | seq_puts(m: s, s: " Not connected to any DRM CRTC\n" ); |
253 | else |
254 | seq_printf(m: s, fmt: " Connected to DRM CRTC #%d (%s)\n" , |
255 | crtc->base.id, sti_mixer_to_str(to_sti_mixer(crtc))); |
256 | |
257 | return 0; |
258 | } |
259 | |
260 | static void gdp_node_dump_node(struct seq_file *s, struct sti_gdp_node *node) |
261 | { |
262 | seq_printf(m: s, fmt: "\t@:0x%p" , node); |
263 | seq_printf(m: s, fmt: "\n\tCTL 0x%08X" , node->gam_gdp_ctl); |
264 | gdp_dbg_ctl(s, val: node->gam_gdp_ctl); |
265 | seq_printf(m: s, fmt: "\n\tAGC 0x%08X" , node->gam_gdp_agc); |
266 | seq_printf(m: s, fmt: "\n\tVPO 0x%08X" , node->gam_gdp_vpo); |
267 | gdp_dbg_vpo(s, val: node->gam_gdp_vpo); |
268 | seq_printf(m: s, fmt: "\n\tVPS 0x%08X" , node->gam_gdp_vps); |
269 | gdp_dbg_vps(s, val: node->gam_gdp_vps); |
270 | seq_printf(m: s, fmt: "\n\tPML 0x%08X" , node->gam_gdp_pml); |
271 | seq_printf(m: s, fmt: "\n\tPMP 0x%08X" , node->gam_gdp_pmp); |
272 | seq_printf(m: s, fmt: "\n\tSIZE 0x%08X" , node->gam_gdp_size); |
273 | gdp_dbg_size(s, val: node->gam_gdp_size); |
274 | seq_printf(m: s, fmt: "\n\tNVN 0x%08X" , node->gam_gdp_nvn); |
275 | seq_printf(m: s, fmt: "\n\tKEY1 0x%08X" , node->gam_gdp_key1); |
276 | seq_printf(m: s, fmt: "\n\tKEY2 0x%08X" , node->gam_gdp_key2); |
277 | seq_printf(m: s, fmt: "\n\tPPT 0x%08X" , node->gam_gdp_ppt); |
278 | gdp_dbg_ppt(s, val: node->gam_gdp_ppt); |
279 | seq_printf(m: s, fmt: "\n\tCML 0x%08X\n" , node->gam_gdp_cml); |
280 | } |
281 | |
282 | static int gdp_node_dbg_show(struct seq_file *s, void *arg) |
283 | { |
284 | struct drm_info_node *node = s->private; |
285 | struct sti_gdp *gdp = (struct sti_gdp *)node->info_ent->data; |
286 | unsigned int b; |
287 | |
288 | for (b = 0; b < GDP_NODE_NB_BANK; b++) { |
289 | seq_printf(m: s, fmt: "\n%s[%d].top" , sti_plane_to_str(plane: &gdp->plane), b); |
290 | gdp_node_dump_node(s, node: gdp->node_list[b].top_field); |
291 | seq_printf(m: s, fmt: "\n%s[%d].btm" , sti_plane_to_str(plane: &gdp->plane), b); |
292 | gdp_node_dump_node(s, node: gdp->node_list[b].btm_field); |
293 | } |
294 | |
295 | return 0; |
296 | } |
297 | |
298 | static struct drm_info_list gdp0_debugfs_files[] = { |
299 | { "gdp0" , gdp_dbg_show, 0, NULL }, |
300 | { "gdp0_node" , gdp_node_dbg_show, 0, NULL }, |
301 | }; |
302 | |
303 | static struct drm_info_list gdp1_debugfs_files[] = { |
304 | { "gdp1" , gdp_dbg_show, 0, NULL }, |
305 | { "gdp1_node" , gdp_node_dbg_show, 0, NULL }, |
306 | }; |
307 | |
308 | static struct drm_info_list gdp2_debugfs_files[] = { |
309 | { "gdp2" , gdp_dbg_show, 0, NULL }, |
310 | { "gdp2_node" , gdp_node_dbg_show, 0, NULL }, |
311 | }; |
312 | |
313 | static struct drm_info_list gdp3_debugfs_files[] = { |
314 | { "gdp3" , gdp_dbg_show, 0, NULL }, |
315 | { "gdp3_node" , gdp_node_dbg_show, 0, NULL }, |
316 | }; |
317 | |
318 | static int gdp_debugfs_init(struct sti_gdp *gdp, struct drm_minor *minor) |
319 | { |
320 | unsigned int i; |
321 | struct drm_info_list *gdp_debugfs_files; |
322 | int nb_files; |
323 | |
324 | switch (gdp->plane.desc) { |
325 | case STI_GDP_0: |
326 | gdp_debugfs_files = gdp0_debugfs_files; |
327 | nb_files = ARRAY_SIZE(gdp0_debugfs_files); |
328 | break; |
329 | case STI_GDP_1: |
330 | gdp_debugfs_files = gdp1_debugfs_files; |
331 | nb_files = ARRAY_SIZE(gdp1_debugfs_files); |
332 | break; |
333 | case STI_GDP_2: |
334 | gdp_debugfs_files = gdp2_debugfs_files; |
335 | nb_files = ARRAY_SIZE(gdp2_debugfs_files); |
336 | break; |
337 | case STI_GDP_3: |
338 | gdp_debugfs_files = gdp3_debugfs_files; |
339 | nb_files = ARRAY_SIZE(gdp3_debugfs_files); |
340 | break; |
341 | default: |
342 | return -EINVAL; |
343 | } |
344 | |
345 | for (i = 0; i < nb_files; i++) |
346 | gdp_debugfs_files[i].data = gdp; |
347 | |
348 | drm_debugfs_create_files(files: gdp_debugfs_files, |
349 | count: nb_files, |
350 | root: minor->debugfs_root, minor); |
351 | return 0; |
352 | } |
353 | |
354 | static int sti_gdp_fourcc2format(int fourcc) |
355 | { |
356 | switch (fourcc) { |
357 | case DRM_FORMAT_XRGB8888: |
358 | return GDP_RGB888_32; |
359 | case DRM_FORMAT_XBGR8888: |
360 | return GDP_XBGR8888; |
361 | case DRM_FORMAT_ARGB8888: |
362 | return GDP_ARGB8888; |
363 | case DRM_FORMAT_ABGR8888: |
364 | return GDP_ABGR8888; |
365 | case DRM_FORMAT_ARGB4444: |
366 | return GDP_ARGB4444; |
367 | case DRM_FORMAT_ARGB1555: |
368 | return GDP_ARGB1555; |
369 | case DRM_FORMAT_RGB565: |
370 | return GDP_RGB565; |
371 | case DRM_FORMAT_RGB888: |
372 | return GDP_RGB888; |
373 | } |
374 | return -1; |
375 | } |
376 | |
377 | static int sti_gdp_get_alpharange(int format) |
378 | { |
379 | switch (format) { |
380 | case GDP_ARGB8565: |
381 | case GDP_ARGB8888: |
382 | case GDP_ABGR8888: |
383 | return GAM_GDP_ALPHARANGE_255; |
384 | } |
385 | return 0; |
386 | } |
387 | |
388 | /** |
389 | * sti_gdp_get_free_nodes |
390 | * @gdp: gdp pointer |
391 | * |
392 | * Look for a GDP node list that is not currently read by the HW. |
393 | * |
394 | * RETURNS: |
395 | * Pointer to the free GDP node list |
396 | */ |
397 | static struct sti_gdp_node_list *sti_gdp_get_free_nodes(struct sti_gdp *gdp) |
398 | { |
399 | int hw_nvn; |
400 | unsigned int i; |
401 | |
402 | hw_nvn = readl(addr: gdp->regs + GAM_GDP_NVN_OFFSET); |
403 | if (!hw_nvn) |
404 | goto end; |
405 | |
406 | for (i = 0; i < GDP_NODE_NB_BANK; i++) |
407 | if ((hw_nvn != gdp->node_list[i].btm_field_paddr) && |
408 | (hw_nvn != gdp->node_list[i].top_field_paddr)) |
409 | return &gdp->node_list[i]; |
410 | |
411 | /* in hazardous cases restart with the first node */ |
412 | DRM_ERROR("inconsistent NVN for %s: 0x%08X\n" , |
413 | sti_plane_to_str(&gdp->plane), hw_nvn); |
414 | |
415 | end: |
416 | return &gdp->node_list[0]; |
417 | } |
418 | |
419 | /** |
420 | * sti_gdp_get_current_nodes |
421 | * @gdp: gdp pointer |
422 | * |
423 | * Look for GDP nodes that are currently read by the HW. |
424 | * |
425 | * RETURNS: |
426 | * Pointer to the current GDP node list |
427 | */ |
428 | static |
429 | struct sti_gdp_node_list *sti_gdp_get_current_nodes(struct sti_gdp *gdp) |
430 | { |
431 | int hw_nvn; |
432 | unsigned int i; |
433 | |
434 | hw_nvn = readl(addr: gdp->regs + GAM_GDP_NVN_OFFSET); |
435 | if (!hw_nvn) |
436 | goto end; |
437 | |
438 | for (i = 0; i < GDP_NODE_NB_BANK; i++) |
439 | if ((hw_nvn == gdp->node_list[i].btm_field_paddr) || |
440 | (hw_nvn == gdp->node_list[i].top_field_paddr)) |
441 | return &gdp->node_list[i]; |
442 | |
443 | end: |
444 | DRM_DEBUG_DRIVER("Warning, NVN 0x%08X for %s does not match any node\n" , |
445 | hw_nvn, sti_plane_to_str(&gdp->plane)); |
446 | |
447 | return NULL; |
448 | } |
449 | |
450 | /** |
451 | * sti_gdp_disable |
452 | * @gdp: gdp pointer |
453 | * |
454 | * Disable a GDP. |
455 | */ |
456 | static void sti_gdp_disable(struct sti_gdp *gdp) |
457 | { |
458 | unsigned int i; |
459 | |
460 | DRM_DEBUG_DRIVER("%s\n" , sti_plane_to_str(&gdp->plane)); |
461 | |
462 | /* Set the nodes as 'to be ignored on mixer' */ |
463 | for (i = 0; i < GDP_NODE_NB_BANK; i++) { |
464 | gdp->node_list[i].top_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; |
465 | gdp->node_list[i].btm_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; |
466 | } |
467 | |
468 | if (sti_vtg_unregister_client(vtg: gdp->vtg, nb: &gdp->vtg_field_nb)) |
469 | DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n" ); |
470 | |
471 | if (gdp->clk_pix) |
472 | clk_disable_unprepare(clk: gdp->clk_pix); |
473 | |
474 | gdp->plane.status = STI_PLANE_DISABLED; |
475 | gdp->vtg = NULL; |
476 | } |
477 | |
478 | /** |
479 | * sti_gdp_field_cb |
480 | * @nb: notifier block |
481 | * @event: event message |
482 | * @data: private data |
483 | * |
484 | * Handle VTG top field and bottom field event. |
485 | * |
486 | * RETURNS: |
487 | * 0 on success. |
488 | */ |
489 | static int sti_gdp_field_cb(struct notifier_block *nb, |
490 | unsigned long event, void *data) |
491 | { |
492 | struct sti_gdp *gdp = container_of(nb, struct sti_gdp, vtg_field_nb); |
493 | |
494 | if (gdp->plane.status == STI_PLANE_FLUSHING) { |
495 | /* disable need to be synchronize on vsync event */ |
496 | DRM_DEBUG_DRIVER("Vsync event received => disable %s\n" , |
497 | sti_plane_to_str(&gdp->plane)); |
498 | |
499 | sti_gdp_disable(gdp); |
500 | } |
501 | |
502 | switch (event) { |
503 | case VTG_TOP_FIELD_EVENT: |
504 | gdp->is_curr_top = true; |
505 | break; |
506 | case VTG_BOTTOM_FIELD_EVENT: |
507 | gdp->is_curr_top = false; |
508 | break; |
509 | default: |
510 | DRM_ERROR("unsupported event: %lu\n" , event); |
511 | break; |
512 | } |
513 | |
514 | return 0; |
515 | } |
516 | |
517 | static void sti_gdp_init(struct sti_gdp *gdp) |
518 | { |
519 | struct device_node *np = gdp->dev->of_node; |
520 | dma_addr_t dma_addr; |
521 | void *base; |
522 | unsigned int i, size; |
523 | |
524 | /* Allocate all the nodes within a single memory page */ |
525 | size = sizeof(struct sti_gdp_node) * |
526 | GDP_NODE_PER_FIELD * GDP_NODE_NB_BANK; |
527 | base = dma_alloc_wc(dev: gdp->dev, size, dma_addr: &dma_addr, GFP_KERNEL); |
528 | |
529 | if (!base) { |
530 | DRM_ERROR("Failed to allocate memory for GDP node\n" ); |
531 | return; |
532 | } |
533 | memset(base, 0, size); |
534 | |
535 | for (i = 0; i < GDP_NODE_NB_BANK; i++) { |
536 | if (dma_addr & 0xF) { |
537 | DRM_ERROR("Mem alignment failed\n" ); |
538 | return; |
539 | } |
540 | gdp->node_list[i].top_field = base; |
541 | gdp->node_list[i].top_field_paddr = dma_addr; |
542 | |
543 | DRM_DEBUG_DRIVER("node[%d].top_field=%p\n" , i, base); |
544 | base += sizeof(struct sti_gdp_node); |
545 | dma_addr += sizeof(struct sti_gdp_node); |
546 | |
547 | if (dma_addr & 0xF) { |
548 | DRM_ERROR("Mem alignment failed\n" ); |
549 | return; |
550 | } |
551 | gdp->node_list[i].btm_field = base; |
552 | gdp->node_list[i].btm_field_paddr = dma_addr; |
553 | DRM_DEBUG_DRIVER("node[%d].btm_field=%p\n" , i, base); |
554 | base += sizeof(struct sti_gdp_node); |
555 | dma_addr += sizeof(struct sti_gdp_node); |
556 | } |
557 | |
558 | if (of_device_is_compatible(device: np, "st,stih407-compositor" )) { |
559 | /* GDP of STiH407 chip have its own pixel clock */ |
560 | char *clk_name; |
561 | |
562 | switch (gdp->plane.desc) { |
563 | case STI_GDP_0: |
564 | clk_name = "pix_gdp1" ; |
565 | break; |
566 | case STI_GDP_1: |
567 | clk_name = "pix_gdp2" ; |
568 | break; |
569 | case STI_GDP_2: |
570 | clk_name = "pix_gdp3" ; |
571 | break; |
572 | case STI_GDP_3: |
573 | clk_name = "pix_gdp4" ; |
574 | break; |
575 | default: |
576 | DRM_ERROR("GDP id not recognized\n" ); |
577 | return; |
578 | } |
579 | |
580 | gdp->clk_pix = devm_clk_get(dev: gdp->dev, id: clk_name); |
581 | if (IS_ERR(ptr: gdp->clk_pix)) |
582 | DRM_ERROR("Cannot get %s clock\n" , clk_name); |
583 | |
584 | gdp->clk_main_parent = devm_clk_get(dev: gdp->dev, id: "main_parent" ); |
585 | if (IS_ERR(ptr: gdp->clk_main_parent)) |
586 | DRM_ERROR("Cannot get main_parent clock\n" ); |
587 | |
588 | gdp->clk_aux_parent = devm_clk_get(dev: gdp->dev, id: "aux_parent" ); |
589 | if (IS_ERR(ptr: gdp->clk_aux_parent)) |
590 | DRM_ERROR("Cannot get aux_parent clock\n" ); |
591 | } |
592 | } |
593 | |
594 | /** |
595 | * sti_gdp_get_dst |
596 | * @dev: device |
597 | * @dst: requested destination size |
598 | * @src: source size |
599 | * |
600 | * Return the cropped / clamped destination size |
601 | * |
602 | * RETURNS: |
603 | * cropped / clamped destination size |
604 | */ |
605 | static int sti_gdp_get_dst(struct device *dev, int dst, int src) |
606 | { |
607 | if (dst == src) |
608 | return dst; |
609 | |
610 | if (dst < src) { |
611 | dev_dbg(dev, "WARNING: GDP scale not supported, will crop\n" ); |
612 | return dst; |
613 | } |
614 | |
615 | dev_dbg(dev, "WARNING: GDP scale not supported, will clamp\n" ); |
616 | return src; |
617 | } |
618 | |
619 | static int sti_gdp_atomic_check(struct drm_plane *drm_plane, |
620 | struct drm_atomic_state *state) |
621 | { |
622 | struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, |
623 | plane: drm_plane); |
624 | struct sti_plane *plane = to_sti_plane(drm_plane); |
625 | struct sti_gdp *gdp = to_sti_gdp(plane); |
626 | struct drm_crtc *crtc = new_plane_state->crtc; |
627 | struct drm_framebuffer *fb = new_plane_state->fb; |
628 | struct drm_crtc_state *crtc_state; |
629 | struct sti_mixer *mixer; |
630 | struct drm_display_mode *mode; |
631 | int dst_x, dst_y, dst_w, dst_h; |
632 | int src_x, src_y, src_w, src_h; |
633 | int format; |
634 | |
635 | /* no need for further checks if the plane is being disabled */ |
636 | if (!crtc || !fb) |
637 | return 0; |
638 | |
639 | mixer = to_sti_mixer(crtc); |
640 | crtc_state = drm_atomic_get_crtc_state(state, crtc); |
641 | mode = &crtc_state->mode; |
642 | dst_x = new_plane_state->crtc_x; |
643 | dst_y = new_plane_state->crtc_y; |
644 | dst_w = clamp_val(new_plane_state->crtc_w, 0, mode->hdisplay - dst_x); |
645 | dst_h = clamp_val(new_plane_state->crtc_h, 0, mode->vdisplay - dst_y); |
646 | /* src_x are in 16.16 format */ |
647 | src_x = new_plane_state->src_x >> 16; |
648 | src_y = new_plane_state->src_y >> 16; |
649 | src_w = clamp_val(new_plane_state->src_w >> 16, 0, |
650 | GAM_GDP_SIZE_MAX_WIDTH); |
651 | src_h = clamp_val(new_plane_state->src_h >> 16, 0, |
652 | GAM_GDP_SIZE_MAX_HEIGHT); |
653 | |
654 | format = sti_gdp_fourcc2format(fourcc: fb->format->format); |
655 | if (format == -1) { |
656 | DRM_ERROR("Format not supported by GDP %.4s\n" , |
657 | (char *)&fb->format->format); |
658 | return -EINVAL; |
659 | } |
660 | |
661 | if (!drm_fb_dma_get_gem_obj(fb, plane: 0)) { |
662 | DRM_ERROR("Can't get DMA GEM object for fb\n" ); |
663 | return -EINVAL; |
664 | } |
665 | |
666 | /* Set gdp clock */ |
667 | if (mode->clock && gdp->clk_pix) { |
668 | struct clk *clkp; |
669 | int rate = mode->clock * 1000; |
670 | int res; |
671 | |
672 | /* |
673 | * According to the mixer used, the gdp pixel clock |
674 | * should have a different parent clock. |
675 | */ |
676 | if (mixer->id == STI_MIXER_MAIN) |
677 | clkp = gdp->clk_main_parent; |
678 | else |
679 | clkp = gdp->clk_aux_parent; |
680 | |
681 | if (clkp) |
682 | clk_set_parent(clk: gdp->clk_pix, parent: clkp); |
683 | |
684 | res = clk_set_rate(clk: gdp->clk_pix, rate); |
685 | if (res < 0) { |
686 | DRM_ERROR("Cannot set rate (%dHz) for gdp\n" , |
687 | rate); |
688 | return -EINVAL; |
689 | } |
690 | } |
691 | |
692 | DRM_DEBUG_KMS("CRTC:%d (%s) drm plane:%d (%s)\n" , |
693 | crtc->base.id, sti_mixer_to_str(mixer), |
694 | drm_plane->base.id, sti_plane_to_str(plane)); |
695 | DRM_DEBUG_KMS("%s dst=(%dx%d)@(%d,%d) - src=(%dx%d)@(%d,%d)\n" , |
696 | sti_plane_to_str(plane), |
697 | dst_w, dst_h, dst_x, dst_y, |
698 | src_w, src_h, src_x, src_y); |
699 | |
700 | return 0; |
701 | } |
702 | |
703 | static void sti_gdp_atomic_update(struct drm_plane *drm_plane, |
704 | struct drm_atomic_state *state) |
705 | { |
706 | struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state, |
707 | plane: drm_plane); |
708 | struct drm_plane_state *newstate = drm_atomic_get_new_plane_state(state, |
709 | plane: drm_plane); |
710 | struct sti_plane *plane = to_sti_plane(drm_plane); |
711 | struct sti_gdp *gdp = to_sti_gdp(plane); |
712 | struct drm_crtc *crtc = newstate->crtc; |
713 | struct drm_framebuffer *fb = newstate->fb; |
714 | struct drm_display_mode *mode; |
715 | int dst_x, dst_y, dst_w, dst_h; |
716 | int src_x, src_y, src_w, src_h; |
717 | struct drm_gem_dma_object *dma_obj; |
718 | struct sti_gdp_node_list *list; |
719 | struct sti_gdp_node_list *curr_list; |
720 | struct sti_gdp_node *top_field, *btm_field; |
721 | u32 dma_updated_top; |
722 | u32 dma_updated_btm; |
723 | int format; |
724 | unsigned int bpp; |
725 | u32 ydo, xdo, yds, xds; |
726 | |
727 | if (!crtc || !fb) |
728 | return; |
729 | |
730 | if ((oldstate->fb == newstate->fb) && |
731 | (oldstate->crtc_x == newstate->crtc_x) && |
732 | (oldstate->crtc_y == newstate->crtc_y) && |
733 | (oldstate->crtc_w == newstate->crtc_w) && |
734 | (oldstate->crtc_h == newstate->crtc_h) && |
735 | (oldstate->src_x == newstate->src_x) && |
736 | (oldstate->src_y == newstate->src_y) && |
737 | (oldstate->src_w == newstate->src_w) && |
738 | (oldstate->src_h == newstate->src_h)) { |
739 | /* No change since last update, do not post cmd */ |
740 | DRM_DEBUG_DRIVER("No change, not posting cmd\n" ); |
741 | plane->status = STI_PLANE_UPDATED; |
742 | return; |
743 | } |
744 | |
745 | if (!gdp->vtg) { |
746 | struct sti_compositor *compo = dev_get_drvdata(dev: gdp->dev); |
747 | struct sti_mixer *mixer = to_sti_mixer(crtc); |
748 | |
749 | /* Register gdp callback */ |
750 | gdp->vtg = compo->vtg[mixer->id]; |
751 | sti_vtg_register_client(vtg: gdp->vtg, nb: &gdp->vtg_field_nb, crtc); |
752 | clk_prepare_enable(clk: gdp->clk_pix); |
753 | } |
754 | |
755 | mode = &crtc->mode; |
756 | dst_x = newstate->crtc_x; |
757 | dst_y = newstate->crtc_y; |
758 | dst_w = clamp_val(newstate->crtc_w, 0, mode->hdisplay - dst_x); |
759 | dst_h = clamp_val(newstate->crtc_h, 0, mode->vdisplay - dst_y); |
760 | /* src_x are in 16.16 format */ |
761 | src_x = newstate->src_x >> 16; |
762 | src_y = newstate->src_y >> 16; |
763 | src_w = clamp_val(newstate->src_w >> 16, 0, GAM_GDP_SIZE_MAX_WIDTH); |
764 | src_h = clamp_val(newstate->src_h >> 16, 0, GAM_GDP_SIZE_MAX_HEIGHT); |
765 | |
766 | list = sti_gdp_get_free_nodes(gdp); |
767 | top_field = list->top_field; |
768 | btm_field = list->btm_field; |
769 | |
770 | dev_dbg(gdp->dev, "%s %s top_node:0x%p btm_node:0x%p\n" , __func__, |
771 | sti_plane_to_str(plane), top_field, btm_field); |
772 | |
773 | /* build the top field */ |
774 | top_field->gam_gdp_agc = GAM_GDP_AGC_FULL_RANGE; |
775 | top_field->gam_gdp_ctl = WAIT_NEXT_VSYNC; |
776 | format = sti_gdp_fourcc2format(fourcc: fb->format->format); |
777 | top_field->gam_gdp_ctl |= format; |
778 | top_field->gam_gdp_ctl |= sti_gdp_get_alpharange(format); |
779 | top_field->gam_gdp_ppt &= ~GAM_GDP_PPT_IGNORE; |
780 | |
781 | dma_obj = drm_fb_dma_get_gem_obj(fb, plane: 0); |
782 | |
783 | DRM_DEBUG_DRIVER("drm FB:%d format:%.4s phys@:0x%lx\n" , fb->base.id, |
784 | (char *)&fb->format->format, |
785 | (unsigned long) dma_obj->dma_addr); |
786 | |
787 | /* pixel memory location */ |
788 | bpp = fb->format->cpp[0]; |
789 | top_field->gam_gdp_pml = (u32) dma_obj->dma_addr + fb->offsets[0]; |
790 | top_field->gam_gdp_pml += src_x * bpp; |
791 | top_field->gam_gdp_pml += src_y * fb->pitches[0]; |
792 | |
793 | /* output parameters (clamped / cropped) */ |
794 | dst_w = sti_gdp_get_dst(dev: gdp->dev, dst: dst_w, src: src_w); |
795 | dst_h = sti_gdp_get_dst(dev: gdp->dev, dst: dst_h, src: src_h); |
796 | ydo = sti_vtg_get_line_number(mode: *mode, y: dst_y); |
797 | yds = sti_vtg_get_line_number(mode: *mode, y: dst_y + dst_h - 1); |
798 | xdo = sti_vtg_get_pixel_number(mode: *mode, x: dst_x); |
799 | xds = sti_vtg_get_pixel_number(mode: *mode, x: dst_x + dst_w - 1); |
800 | top_field->gam_gdp_vpo = (ydo << 16) | xdo; |
801 | top_field->gam_gdp_vps = (yds << 16) | xds; |
802 | |
803 | /* input parameters */ |
804 | src_w = dst_w; |
805 | top_field->gam_gdp_pmp = fb->pitches[0]; |
806 | top_field->gam_gdp_size = src_h << 16 | src_w; |
807 | |
808 | /* Same content and chained together */ |
809 | memcpy(btm_field, top_field, sizeof(*btm_field)); |
810 | top_field->gam_gdp_nvn = list->btm_field_paddr; |
811 | btm_field->gam_gdp_nvn = list->top_field_paddr; |
812 | |
813 | /* Interlaced mode */ |
814 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
815 | btm_field->gam_gdp_pml = top_field->gam_gdp_pml + |
816 | fb->pitches[0]; |
817 | |
818 | /* Update the NVN field of the 'right' field of the current GDP node |
819 | * (being used by the HW) with the address of the updated ('free') top |
820 | * field GDP node. |
821 | * - In interlaced mode the 'right' field is the bottom field as we |
822 | * update frames starting from their top field |
823 | * - In progressive mode, we update both bottom and top fields which |
824 | * are equal nodes. |
825 | * At the next VSYNC, the updated node list will be used by the HW. |
826 | */ |
827 | curr_list = sti_gdp_get_current_nodes(gdp); |
828 | dma_updated_top = list->top_field_paddr; |
829 | dma_updated_btm = list->btm_field_paddr; |
830 | |
831 | dev_dbg(gdp->dev, "Current NVN:0x%X\n" , |
832 | readl(gdp->regs + GAM_GDP_NVN_OFFSET)); |
833 | dev_dbg(gdp->dev, "Posted buff: %lx current buff: %x\n" , |
834 | (unsigned long) dma_obj->dma_addr, |
835 | readl(gdp->regs + GAM_GDP_PML_OFFSET)); |
836 | |
837 | if (!curr_list) { |
838 | /* First update or invalid node should directly write in the |
839 | * hw register */ |
840 | DRM_DEBUG_DRIVER("%s first update (or invalid node)\n" , |
841 | sti_plane_to_str(plane)); |
842 | |
843 | writel(val: gdp->is_curr_top ? |
844 | dma_updated_btm : dma_updated_top, |
845 | addr: gdp->regs + GAM_GDP_NVN_OFFSET); |
846 | goto end; |
847 | } |
848 | |
849 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) { |
850 | if (gdp->is_curr_top) { |
851 | /* Do not update in the middle of the frame, but |
852 | * postpone the update after the bottom field has |
853 | * been displayed */ |
854 | curr_list->btm_field->gam_gdp_nvn = dma_updated_top; |
855 | } else { |
856 | /* Direct update to avoid one frame delay */ |
857 | writel(val: dma_updated_top, |
858 | addr: gdp->regs + GAM_GDP_NVN_OFFSET); |
859 | } |
860 | } else { |
861 | /* Direct update for progressive to avoid one frame delay */ |
862 | writel(val: dma_updated_top, addr: gdp->regs + GAM_GDP_NVN_OFFSET); |
863 | } |
864 | |
865 | end: |
866 | sti_plane_update_fps(plane, new_frame: true, new_field: false); |
867 | |
868 | plane->status = STI_PLANE_UPDATED; |
869 | } |
870 | |
871 | static void sti_gdp_atomic_disable(struct drm_plane *drm_plane, |
872 | struct drm_atomic_state *state) |
873 | { |
874 | struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state, |
875 | plane: drm_plane); |
876 | struct sti_plane *plane = to_sti_plane(drm_plane); |
877 | |
878 | if (!oldstate->crtc) { |
879 | DRM_DEBUG_DRIVER("drm plane:%d not enabled\n" , |
880 | drm_plane->base.id); |
881 | return; |
882 | } |
883 | |
884 | DRM_DEBUG_DRIVER("CRTC:%d (%s) drm plane:%d (%s)\n" , |
885 | oldstate->crtc->base.id, |
886 | sti_mixer_to_str(to_sti_mixer(oldstate->crtc)), |
887 | drm_plane->base.id, sti_plane_to_str(plane)); |
888 | |
889 | plane->status = STI_PLANE_DISABLING; |
890 | } |
891 | |
892 | static const struct drm_plane_helper_funcs sti_gdp_helpers_funcs = { |
893 | .atomic_check = sti_gdp_atomic_check, |
894 | .atomic_update = sti_gdp_atomic_update, |
895 | .atomic_disable = sti_gdp_atomic_disable, |
896 | }; |
897 | |
898 | static int sti_gdp_late_register(struct drm_plane *drm_plane) |
899 | { |
900 | struct sti_plane *plane = to_sti_plane(drm_plane); |
901 | struct sti_gdp *gdp = to_sti_gdp(plane); |
902 | |
903 | return gdp_debugfs_init(gdp, minor: drm_plane->dev->primary); |
904 | } |
905 | |
906 | static const struct drm_plane_funcs sti_gdp_plane_helpers_funcs = { |
907 | .update_plane = drm_atomic_helper_update_plane, |
908 | .disable_plane = drm_atomic_helper_disable_plane, |
909 | .destroy = drm_plane_cleanup, |
910 | .reset = drm_atomic_helper_plane_reset, |
911 | .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, |
912 | .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, |
913 | .late_register = sti_gdp_late_register, |
914 | }; |
915 | |
916 | struct drm_plane *sti_gdp_create(struct drm_device *drm_dev, |
917 | struct device *dev, int desc, |
918 | void __iomem *baseaddr, |
919 | unsigned int possible_crtcs, |
920 | enum drm_plane_type type) |
921 | { |
922 | struct sti_gdp *gdp; |
923 | int res; |
924 | |
925 | gdp = devm_kzalloc(dev, size: sizeof(*gdp), GFP_KERNEL); |
926 | if (!gdp) { |
927 | DRM_ERROR("Failed to allocate memory for GDP\n" ); |
928 | return NULL; |
929 | } |
930 | |
931 | gdp->dev = dev; |
932 | gdp->regs = baseaddr; |
933 | gdp->plane.desc = desc; |
934 | gdp->plane.status = STI_PLANE_DISABLED; |
935 | |
936 | gdp->vtg_field_nb.notifier_call = sti_gdp_field_cb; |
937 | |
938 | sti_gdp_init(gdp); |
939 | |
940 | res = drm_universal_plane_init(dev: drm_dev, plane: &gdp->plane.drm_plane, |
941 | possible_crtcs, |
942 | funcs: &sti_gdp_plane_helpers_funcs, |
943 | formats: gdp_supported_formats, |
944 | ARRAY_SIZE(gdp_supported_formats), |
945 | NULL, type, NULL); |
946 | if (res) { |
947 | DRM_ERROR("Failed to initialize universal plane\n" ); |
948 | goto err; |
949 | } |
950 | |
951 | drm_plane_helper_add(plane: &gdp->plane.drm_plane, funcs: &sti_gdp_helpers_funcs); |
952 | |
953 | sti_plane_init_property(plane: &gdp->plane, type); |
954 | |
955 | return &gdp->plane.drm_plane; |
956 | |
957 | err: |
958 | devm_kfree(dev, p: gdp); |
959 | return NULL; |
960 | } |
961 | |