Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
c8b75bca EA |
2 | /* |
3 | * Copyright (C) 2015 Broadcom | |
c8b75bca EA |
4 | */ |
5 | ||
6 | /** | |
7 | * DOC: VC4 HVS module. | |
8 | * | |
f6c01530 EA |
9 | * The Hardware Video Scaler (HVS) is the piece of hardware that does |
10 | * translation, scaling, colorspace conversion, and compositing of | |
11 | * pixels stored in framebuffers into a FIFO of pixels going out to | |
12 | * the Pixel Valve (CRTC). It operates at the system clock rate (the | |
13 | * system audio clock gate, specifically), which is much higher than | |
14 | * the pixel clock rate. | |
c8b75bca EA |
15 | * |
16 | * There is a single global HVS, with multiple output FIFOs that can | |
17 | * be consumed by the PVs. This file just manages the resources for | |
18 | * the HVS, while the vc4_crtc.c code actually drives HVS setup for | |
19 | * each CRTC. | |
20 | */ | |
21 | ||
29bbb930 | 22 | #include <linux/bitfield.h> |
d7d96c00 | 23 | #include <linux/clk.h> |
b7e8e25b | 24 | #include <linux/component.h> |
fd6d6d80 SR |
25 | #include <linux/platform_device.h> |
26 | ||
27 | #include <drm/drm_atomic_helper.h> | |
969cfae1 | 28 | #include <drm/drm_drv.h> |
8175287b | 29 | #include <drm/drm_vblank.h> |
fd6d6d80 | 30 | |
2a001ca0 MR |
31 | #include <soc/bcm2835/raspberrypi-firmware.h> |
32 | ||
c8b75bca EA |
33 | #include "vc4_drv.h" |
34 | #include "vc4_regs.h" | |
35 | ||
3051719a EA |
36 | static const struct debugfs_reg32 hvs_regs[] = { |
37 | VC4_REG32(SCALER_DISPCTRL), | |
38 | VC4_REG32(SCALER_DISPSTAT), | |
39 | VC4_REG32(SCALER_DISPID), | |
40 | VC4_REG32(SCALER_DISPECTRL), | |
41 | VC4_REG32(SCALER_DISPPROF), | |
42 | VC4_REG32(SCALER_DISPDITHER), | |
43 | VC4_REG32(SCALER_DISPEOLN), | |
44 | VC4_REG32(SCALER_DISPLIST0), | |
45 | VC4_REG32(SCALER_DISPLIST1), | |
46 | VC4_REG32(SCALER_DISPLIST2), | |
47 | VC4_REG32(SCALER_DISPLSTAT), | |
48 | VC4_REG32(SCALER_DISPLACT0), | |
49 | VC4_REG32(SCALER_DISPLACT1), | |
50 | VC4_REG32(SCALER_DISPLACT2), | |
51 | VC4_REG32(SCALER_DISPCTRL0), | |
52 | VC4_REG32(SCALER_DISPBKGND0), | |
53 | VC4_REG32(SCALER_DISPSTAT0), | |
54 | VC4_REG32(SCALER_DISPBASE0), | |
55 | VC4_REG32(SCALER_DISPCTRL1), | |
56 | VC4_REG32(SCALER_DISPBKGND1), | |
57 | VC4_REG32(SCALER_DISPSTAT1), | |
58 | VC4_REG32(SCALER_DISPBASE1), | |
59 | VC4_REG32(SCALER_DISPCTRL2), | |
60 | VC4_REG32(SCALER_DISPBKGND2), | |
61 | VC4_REG32(SCALER_DISPSTAT2), | |
62 | VC4_REG32(SCALER_DISPBASE2), | |
63 | VC4_REG32(SCALER_DISPALPHA2), | |
64 | VC4_REG32(SCALER_OLEDOFFS), | |
65 | VC4_REG32(SCALER_OLEDCOEF0), | |
66 | VC4_REG32(SCALER_OLEDCOEF1), | |
67 | VC4_REG32(SCALER_OLEDCOEF2), | |
c8b75bca EA |
68 | }; |
69 | ||
3454f01a | 70 | void vc4_hvs_dump_state(struct vc4_hvs *hvs) |
c8b75bca | 71 | { |
969cfae1 | 72 | struct drm_device *drm = &hvs->vc4->base; |
3454f01a | 73 | struct drm_printer p = drm_info_printer(&hvs->pdev->dev); |
969cfae1 | 74 | int idx, i; |
c8b75bca | 75 | |
969cfae1 MR |
76 | if (!drm_dev_enter(drm, &idx)) |
77 | return; | |
78 | ||
b3be4520 DK |
79 | drm_print_regset32(&p, &hvs->regset); |
80 | ||
c8b75bca EA |
81 | DRM_INFO("HVS ctx:\n"); |
82 | for (i = 0; i < 64; i += 4) { | |
83 | DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n", | |
84 | i * 4, i < HVS_BOOTLOADER_DLIST_END ? "B" : "D", | |
3454f01a MR |
85 | readl((u32 __iomem *)hvs->dlist + i + 0), |
86 | readl((u32 __iomem *)hvs->dlist + i + 1), | |
87 | readl((u32 __iomem *)hvs->dlist + i + 2), | |
88 | readl((u32 __iomem *)hvs->dlist + i + 3)); | |
c8b75bca | 89 | } |
969cfae1 MR |
90 | |
91 | drm_dev_exit(idx); | |
c8b75bca EA |
92 | } |
93 | ||
c9be804c | 94 | static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data) |
531a1b62 | 95 | { |
f2ede40e MC |
96 | struct drm_debugfs_entry *entry = m->private; |
97 | struct drm_device *dev = entry->dev; | |
531a1b62 BB |
98 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
99 | struct drm_printer p = drm_seq_file_printer(m); | |
100 | ||
101 | drm_printf(&p, "%d\n", atomic_read(&vc4->underrun)); | |
102 | ||
103 | return 0; | |
104 | } | |
c8b75bca | 105 | |
c6dac003 DS |
106 | static int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data) |
107 | { | |
f2ede40e MC |
108 | struct drm_debugfs_entry *entry = m->private; |
109 | struct drm_device *dev = entry->dev; | |
c6dac003 DS |
110 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
111 | struct vc4_hvs *hvs = vc4->hvs; | |
112 | struct drm_printer p = drm_seq_file_printer(m); | |
113 | unsigned int next_entry_start = 0; | |
114 | unsigned int i, j; | |
115 | u32 dlist_word, dispstat; | |
116 | ||
117 | for (i = 0; i < SCALER_CHANNELS_COUNT; i++) { | |
118 | dispstat = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(i)), | |
119 | SCALER_DISPSTATX_MODE); | |
120 | if (dispstat == SCALER_DISPSTATX_MODE_DISABLED || | |
121 | dispstat == SCALER_DISPSTATX_MODE_EOF) { | |
122 | drm_printf(&p, "HVS chan %u disabled\n", i); | |
123 | continue; | |
124 | } | |
125 | ||
126 | drm_printf(&p, "HVS chan %u:\n", i); | |
127 | ||
128 | for (j = HVS_READ(SCALER_DISPLISTX(i)); j < 256; j++) { | |
129 | dlist_word = readl((u32 __iomem *)vc4->hvs->dlist + j); | |
130 | drm_printf(&p, "dlist: %02d: 0x%08x\n", j, | |
131 | dlist_word); | |
132 | if (!next_entry_start || | |
133 | next_entry_start == j) { | |
134 | if (dlist_word & SCALER_CTL0_END) | |
135 | break; | |
136 | next_entry_start = j + | |
137 | VC4_GET_FIELD(dlist_word, | |
138 | SCALER_CTL0_SIZE); | |
139 | } | |
140 | } | |
141 | } | |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
21af94cf EA |
146 | /* The filter kernel is composed of dwords each containing 3 9-bit |
147 | * signed integers packed next to each other. | |
148 | */ | |
149 | #define VC4_INT_TO_COEFF(coeff) (coeff & 0x1ff) | |
150 | #define VC4_PPF_FILTER_WORD(c0, c1, c2) \ | |
151 | ((((c0) & 0x1ff) << 0) | \ | |
152 | (((c1) & 0x1ff) << 9) | \ | |
153 | (((c2) & 0x1ff) << 18)) | |
154 | ||
155 | /* The whole filter kernel is arranged as the coefficients 0-16 going | |
156 | * up, then a pad, then 17-31 going down and reversed within the | |
157 | * dwords. This means that a linear phase kernel (where it's | |
158 | * symmetrical at the boundary between 15 and 16) has the last 5 | |
159 | * dwords matching the first 5, but reversed. | |
160 | */ | |
161 | #define VC4_LINEAR_PHASE_KERNEL(c0, c1, c2, c3, c4, c5, c6, c7, c8, \ | |
162 | c9, c10, c11, c12, c13, c14, c15) \ | |
163 | {VC4_PPF_FILTER_WORD(c0, c1, c2), \ | |
164 | VC4_PPF_FILTER_WORD(c3, c4, c5), \ | |
165 | VC4_PPF_FILTER_WORD(c6, c7, c8), \ | |
166 | VC4_PPF_FILTER_WORD(c9, c10, c11), \ | |
167 | VC4_PPF_FILTER_WORD(c12, c13, c14), \ | |
168 | VC4_PPF_FILTER_WORD(c15, c15, 0)} | |
169 | ||
170 | #define VC4_LINEAR_PHASE_KERNEL_DWORDS 6 | |
171 | #define VC4_KERNEL_DWORDS (VC4_LINEAR_PHASE_KERNEL_DWORDS * 2 - 1) | |
172 | ||
173 | /* Recommended B=1/3, C=1/3 filter choice from Mitchell/Netravali. | |
174 | * http://www.cs.utexas.edu/~fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf | |
175 | */ | |
176 | static const u32 mitchell_netravali_1_3_1_3_kernel[] = | |
177 | VC4_LINEAR_PHASE_KERNEL(0, -2, -6, -8, -10, -8, -3, 2, 18, | |
178 | 50, 82, 119, 155, 187, 213, 227); | |
179 | ||
180 | static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, | |
181 | struct drm_mm_node *space, | |
182 | const u32 *kernel) | |
183 | { | |
184 | int ret, i; | |
185 | u32 __iomem *dst_kernel; | |
186 | ||
969cfae1 MR |
187 | /* |
188 | * NOTE: We don't need a call to drm_dev_enter()/drm_dev_exit() | |
189 | * here since that function is only called from vc4_hvs_bind(). | |
190 | */ | |
191 | ||
4e64e553 | 192 | ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS); |
21af94cf | 193 | if (ret) { |
59ac702a SW |
194 | drm_err(&hvs->vc4->base, "Failed to allocate space for filter kernel: %d\n", |
195 | ret); | |
21af94cf EA |
196 | return ret; |
197 | } | |
198 | ||
199 | dst_kernel = hvs->dlist + space->start; | |
200 | ||
201 | for (i = 0; i < VC4_KERNEL_DWORDS; i++) { | |
202 | if (i < VC4_LINEAR_PHASE_KERNEL_DWORDS) | |
203 | writel(kernel[i], &dst_kernel[i]); | |
204 | else { | |
205 | writel(kernel[VC4_KERNEL_DWORDS - i - 1], | |
206 | &dst_kernel[i]); | |
207 | } | |
208 | } | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
3454f01a MR |
213 | static void vc4_hvs_lut_load(struct vc4_hvs *hvs, |
214 | struct vc4_crtc *vc4_crtc) | |
8175287b | 215 | { |
969cfae1 | 216 | struct drm_device *drm = &hvs->vc4->base; |
3454f01a | 217 | struct drm_crtc *crtc = &vc4_crtc->base; |
87ebcd42 | 218 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
969cfae1 | 219 | int idx; |
8175287b MR |
220 | u32 i; |
221 | ||
969cfae1 MR |
222 | if (!drm_dev_enter(drm, &idx)) |
223 | return; | |
224 | ||
8175287b MR |
225 | /* The LUT memory is laid out with each HVS channel in order, |
226 | * each of which takes 256 writes for R, 256 for G, then 256 | |
227 | * for B. | |
228 | */ | |
229 | HVS_WRITE(SCALER_GAMADDR, | |
230 | SCALER_GAMADDR_AUTOINC | | |
87ebcd42 | 231 | (vc4_state->assigned_channel * 3 * crtc->gamma_size)); |
8175287b MR |
232 | |
233 | for (i = 0; i < crtc->gamma_size; i++) | |
234 | HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_r[i]); | |
235 | for (i = 0; i < crtc->gamma_size; i++) | |
236 | HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]); | |
237 | for (i = 0; i < crtc->gamma_size; i++) | |
238 | HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]); | |
969cfae1 MR |
239 | |
240 | drm_dev_exit(idx); | |
8175287b MR |
241 | } |
242 | ||
3454f01a MR |
243 | static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs, |
244 | struct vc4_crtc *vc4_crtc) | |
8175287b | 245 | { |
3454f01a MR |
246 | struct drm_crtc_state *crtc_state = vc4_crtc->base.state; |
247 | struct drm_color_lut *lut = crtc_state->gamma_lut->data; | |
248 | u32 length = drm_color_lut_size(crtc_state->gamma_lut); | |
8175287b MR |
249 | u32 i; |
250 | ||
251 | for (i = 0; i < length; i++) { | |
252 | vc4_crtc->lut_r[i] = drm_color_lut_extract(lut[i].red, 8); | |
253 | vc4_crtc->lut_g[i] = drm_color_lut_extract(lut[i].green, 8); | |
254 | vc4_crtc->lut_b[i] = drm_color_lut_extract(lut[i].blue, 8); | |
255 | } | |
256 | ||
3454f01a | 257 | vc4_hvs_lut_load(hvs, vc4_crtc); |
8175287b MR |
258 | } |
259 | ||
3454f01a | 260 | u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) |
b51cd7ad | 261 | { |
969cfae1 | 262 | struct drm_device *drm = &hvs->vc4->base; |
b51cd7ad | 263 | u8 field = 0; |
969cfae1 MR |
264 | int idx; |
265 | ||
266 | if (!drm_dev_enter(drm, &idx)) | |
267 | return 0; | |
b51cd7ad MR |
268 | |
269 | switch (fifo) { | |
270 | case 0: | |
271 | field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), | |
272 | SCALER_DISPSTAT1_FRCNT0); | |
273 | break; | |
274 | case 1: | |
275 | field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), | |
276 | SCALER_DISPSTAT1_FRCNT1); | |
277 | break; | |
278 | case 2: | |
279 | field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2), | |
280 | SCALER_DISPSTAT2_FRCNT2); | |
281 | break; | |
282 | } | |
283 | ||
969cfae1 | 284 | drm_dev_exit(idx); |
b51cd7ad MR |
285 | return field; |
286 | } | |
287 | ||
3454f01a | 288 | int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) |
29bbb930 | 289 | { |
1cbc91eb | 290 | struct vc4_dev *vc4 = hvs->vc4; |
29bbb930 MR |
291 | u32 reg; |
292 | int ret; | |
293 | ||
1cbc91eb | 294 | if (!vc4->is_vc5) |
29bbb930 MR |
295 | return output; |
296 | ||
969cfae1 MR |
297 | /* |
298 | * NOTE: We should probably use drm_dev_enter()/drm_dev_exit() | |
299 | * here, but this function is only used during the DRM device | |
300 | * initialization, so we should be fine. | |
301 | */ | |
302 | ||
29bbb930 MR |
303 | switch (output) { |
304 | case 0: | |
305 | return 0; | |
306 | ||
307 | case 1: | |
308 | return 1; | |
309 | ||
310 | case 2: | |
311 | reg = HVS_READ(SCALER_DISPECTRL); | |
312 | ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg); | |
313 | if (ret == 0) | |
314 | return 2; | |
315 | ||
316 | return 0; | |
317 | ||
318 | case 3: | |
319 | reg = HVS_READ(SCALER_DISPCTRL); | |
320 | ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg); | |
321 | if (ret == 3) | |
322 | return -EPIPE; | |
323 | ||
324 | return ret; | |
325 | ||
326 | case 4: | |
327 | reg = HVS_READ(SCALER_DISPEOLN); | |
328 | ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg); | |
329 | if (ret == 3) | |
330 | return -EPIPE; | |
331 | ||
332 | return ret; | |
333 | ||
334 | case 5: | |
335 | reg = HVS_READ(SCALER_DISPDITHER); | |
336 | ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg); | |
337 | if (ret == 3) | |
338 | return -EPIPE; | |
339 | ||
340 | return ret; | |
341 | ||
342 | default: | |
343 | return -EPIPE; | |
344 | } | |
345 | } | |
346 | ||
3454f01a | 347 | static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, |
6d01a106 MR |
348 | struct drm_display_mode *mode, bool oneshot) |
349 | { | |
1cbc91eb | 350 | struct vc4_dev *vc4 = hvs->vc4; |
969cfae1 | 351 | struct drm_device *drm = &vc4->base; |
3454f01a | 352 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
6d01a106 MR |
353 | struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); |
354 | unsigned int chan = vc4_crtc_state->assigned_channel; | |
d2f06525 MR |
355 | bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; |
356 | u32 dispbkgndx; | |
6d01a106 | 357 | u32 dispctrl; |
969cfae1 MR |
358 | int idx; |
359 | ||
360 | if (!drm_dev_enter(drm, &idx)) | |
361 | return -ENODEV; | |
6d01a106 | 362 | |
f8b13d9b MR |
363 | HVS_WRITE(SCALER_DISPCTRLX(chan), 0); |
364 | HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET); | |
365 | HVS_WRITE(SCALER_DISPCTRLX(chan), 0); | |
366 | ||
6d01a106 MR |
367 | /* Turn on the scaler, which will wait for vstart to start |
368 | * compositing. | |
369 | * When feeding the transposer, we should operate in oneshot | |
370 | * mode. | |
371 | */ | |
372 | dispctrl = SCALER_DISPCTRLX_ENABLE; | |
982ee944 | 373 | dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(chan)); |
6d01a106 | 374 | |
982ee944 | 375 | if (!vc4->is_vc5) { |
6d01a106 MR |
376 | dispctrl |= VC4_SET_FIELD(mode->hdisplay, |
377 | SCALER_DISPCTRLX_WIDTH) | | |
378 | VC4_SET_FIELD(mode->vdisplay, | |
379 | SCALER_DISPCTRLX_HEIGHT) | | |
380 | (oneshot ? SCALER_DISPCTRLX_ONESHOT : 0); | |
982ee944 DS |
381 | dispbkgndx |= SCALER_DISPBKGND_AUTOHS; |
382 | } else { | |
6d01a106 MR |
383 | dispctrl |= VC4_SET_FIELD(mode->hdisplay, |
384 | SCALER5_DISPCTRLX_WIDTH) | | |
385 | VC4_SET_FIELD(mode->vdisplay, | |
386 | SCALER5_DISPCTRLX_HEIGHT) | | |
387 | (oneshot ? SCALER5_DISPCTRLX_ONESHOT : 0); | |
982ee944 DS |
388 | dispbkgndx &= ~SCALER5_DISPBKGND_BCK2BCK; |
389 | } | |
6d01a106 MR |
390 | |
391 | HVS_WRITE(SCALER_DISPCTRLX(chan), dispctrl); | |
392 | ||
d2f06525 MR |
393 | dispbkgndx &= ~SCALER_DISPBKGND_GAMMA; |
394 | dispbkgndx &= ~SCALER_DISPBKGND_INTERLACE; | |
395 | ||
396 | HVS_WRITE(SCALER_DISPBKGNDX(chan), dispbkgndx | | |
1cbc91eb | 397 | ((!vc4->is_vc5) ? SCALER_DISPBKGND_GAMMA : 0) | |
d2f06525 MR |
398 | (interlace ? SCALER_DISPBKGND_INTERLACE : 0)); |
399 | ||
400 | /* Reload the LUT, since the SRAMs would have been disabled if | |
401 | * all CRTCs had SCALER_DISPBKGND_GAMMA unset at once. | |
402 | */ | |
3454f01a | 403 | vc4_hvs_lut_load(hvs, vc4_crtc); |
d2f06525 | 404 | |
969cfae1 MR |
405 | drm_dev_exit(idx); |
406 | ||
6d01a106 MR |
407 | return 0; |
408 | } | |
409 | ||
3454f01a | 410 | void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) |
6d01a106 | 411 | { |
969cfae1 MR |
412 | struct drm_device *drm = &hvs->vc4->base; |
413 | int idx; | |
414 | ||
415 | if (!drm_dev_enter(drm, &idx)) | |
6d01a106 MR |
416 | return; |
417 | ||
969cfae1 MR |
418 | if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE) |
419 | goto out; | |
420 | ||
6d01a106 MR |
421 | HVS_WRITE(SCALER_DISPCTRLX(chan), |
422 | HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET); | |
423 | HVS_WRITE(SCALER_DISPCTRLX(chan), | |
424 | HVS_READ(SCALER_DISPCTRLX(chan)) & ~SCALER_DISPCTRLX_ENABLE); | |
425 | ||
426 | /* Once we leave, the scaler should be disabled and its fifo empty. */ | |
427 | WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET); | |
428 | ||
429 | WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)), | |
430 | SCALER_DISPSTATX_MODE) != | |
431 | SCALER_DISPSTATX_MODE_DISABLED); | |
432 | ||
433 | WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & | |
434 | (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != | |
435 | SCALER_DISPSTATX_EMPTY); | |
969cfae1 MR |
436 | |
437 | out: | |
438 | drm_dev_exit(idx); | |
6d01a106 MR |
439 | } |
440 | ||
ee6965c8 | 441 | int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) |
8175287b | 442 | { |
ee6965c8 MR |
443 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
444 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state); | |
8175287b MR |
445 | struct drm_device *dev = crtc->dev; |
446 | struct vc4_dev *vc4 = to_vc4_dev(dev); | |
447 | struct drm_plane *plane; | |
448 | unsigned long flags; | |
449 | const struct drm_plane_state *plane_state; | |
450 | u32 dlist_count = 0; | |
451 | int ret; | |
452 | ||
453 | /* The pixelvalve can only feed one encoder (and encoders are | |
454 | * 1:1 with connectors.) | |
455 | */ | |
ee6965c8 | 456 | if (hweight32(crtc_state->connector_mask) > 1) |
8175287b MR |
457 | return -EINVAL; |
458 | ||
ee6965c8 | 459 | drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) |
8175287b MR |
460 | dlist_count += vc4_plane_dlist_size(plane_state); |
461 | ||
462 | dlist_count++; /* Account for SCALER_CTL0_END. */ | |
463 | ||
464 | spin_lock_irqsave(&vc4->hvs->mm_lock, flags); | |
465 | ret = drm_mm_insert_node(&vc4->hvs->dlist_mm, &vc4_state->mm, | |
466 | dlist_count); | |
467 | spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags); | |
468 | if (ret) | |
469 | return ret; | |
470 | ||
471 | return 0; | |
472 | } | |
473 | ||
4dfcaa09 | 474 | static void vc4_hvs_install_dlist(struct drm_crtc *crtc) |
8175287b MR |
475 | { |
476 | struct drm_device *dev = crtc->dev; | |
477 | struct vc4_dev *vc4 = to_vc4_dev(dev); | |
3454f01a | 478 | struct vc4_hvs *hvs = vc4->hvs; |
4dfcaa09 | 479 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
969cfae1 MR |
480 | int idx; |
481 | ||
482 | if (!drm_dev_enter(dev, &idx)) | |
483 | return; | |
4dfcaa09 MR |
484 | |
485 | HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), | |
486 | vc4_state->mm.start); | |
969cfae1 MR |
487 | |
488 | drm_dev_exit(idx); | |
4dfcaa09 MR |
489 | } |
490 | ||
491 | static void vc4_hvs_update_dlist(struct drm_crtc *crtc) | |
492 | { | |
493 | struct drm_device *dev = crtc->dev; | |
8175287b MR |
494 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
495 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); | |
0c250c15 | 496 | unsigned long flags; |
8175287b MR |
497 | |
498 | if (crtc->state->event) { | |
8175287b MR |
499 | crtc->state->event->pipe = drm_crtc_index(crtc); |
500 | ||
501 | WARN_ON(drm_crtc_vblank_get(crtc) != 0); | |
502 | ||
503 | spin_lock_irqsave(&dev->event_lock, flags); | |
504 | ||
a16c6640 | 505 | if (!vc4_crtc->feeds_txp || vc4_state->txp_armed) { |
8175287b MR |
506 | vc4_crtc->event = crtc->state->event; |
507 | crtc->state->event = NULL; | |
508 | } | |
509 | ||
8175287b | 510 | spin_unlock_irqrestore(&dev->event_lock, flags); |
8175287b | 511 | } |
0c250c15 MR |
512 | |
513 | spin_lock_irqsave(&vc4_crtc->irq_lock, flags); | |
514 | vc4_crtc->current_dlist = vc4_state->mm.start; | |
515 | spin_unlock_irqrestore(&vc4_crtc->irq_lock, flags); | |
8175287b MR |
516 | } |
517 | ||
eeb6ab46 MR |
518 | void vc4_hvs_atomic_begin(struct drm_crtc *crtc, |
519 | struct drm_atomic_state *state) | |
520 | { | |
521 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); | |
522 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); | |
523 | unsigned long flags; | |
524 | ||
525 | spin_lock_irqsave(&vc4_crtc->irq_lock, flags); | |
526 | vc4_crtc->current_hvs_channel = vc4_state->assigned_channel; | |
527 | spin_unlock_irqrestore(&vc4_crtc->irq_lock, flags); | |
528 | } | |
529 | ||
8175287b | 530 | void vc4_hvs_atomic_enable(struct drm_crtc *crtc, |
ee6965c8 | 531 | struct drm_atomic_state *state) |
8175287b MR |
532 | { |
533 | struct drm_device *dev = crtc->dev; | |
534 | struct vc4_dev *vc4 = to_vc4_dev(dev); | |
8175287b | 535 | struct drm_display_mode *mode = &crtc->state->adjusted_mode; |
a16c6640 MR |
536 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
537 | bool oneshot = vc4_crtc->feeds_txp; | |
8175287b | 538 | |
4dfcaa09 | 539 | vc4_hvs_install_dlist(crtc); |
8175287b | 540 | vc4_hvs_update_dlist(crtc); |
3454f01a | 541 | vc4_hvs_init_channel(vc4->hvs, crtc, mode, oneshot); |
8175287b MR |
542 | } |
543 | ||
544 | void vc4_hvs_atomic_disable(struct drm_crtc *crtc, | |
ee6965c8 | 545 | struct drm_atomic_state *state) |
8175287b MR |
546 | { |
547 | struct drm_device *dev = crtc->dev; | |
3454f01a | 548 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
ee6965c8 | 549 | struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc); |
87ebcd42 MR |
550 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(old_state); |
551 | unsigned int chan = vc4_state->assigned_channel; | |
8175287b | 552 | |
3454f01a | 553 | vc4_hvs_stop_channel(vc4->hvs, chan); |
8175287b MR |
554 | } |
555 | ||
556 | void vc4_hvs_atomic_flush(struct drm_crtc *crtc, | |
f6ebe9f9 | 557 | struct drm_atomic_state *state) |
8175287b | 558 | { |
f6ebe9f9 MR |
559 | struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, |
560 | crtc); | |
8175287b MR |
561 | struct drm_device *dev = crtc->dev; |
562 | struct vc4_dev *vc4 = to_vc4_dev(dev); | |
3454f01a MR |
563 | struct vc4_hvs *hvs = vc4->hvs; |
564 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); | |
8175287b | 565 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
e418639d | 566 | unsigned int channel = vc4_state->assigned_channel; |
8175287b MR |
567 | struct drm_plane *plane; |
568 | struct vc4_plane_state *vc4_plane_state; | |
569 | bool debug_dump_regs = false; | |
570 | bool enable_bg_fill = false; | |
571 | u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start; | |
572 | u32 __iomem *dlist_next = dlist_start; | |
868bc999 DS |
573 | unsigned int zpos = 0; |
574 | bool found = false; | |
969cfae1 MR |
575 | int idx; |
576 | ||
577 | if (!drm_dev_enter(dev, &idx)) { | |
578 | vc4_crtc_send_vblank(crtc); | |
579 | return; | |
580 | } | |
8175287b | 581 | |
92c17d16 MR |
582 | if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED) |
583 | return; | |
584 | ||
8175287b MR |
585 | if (debug_dump_regs) { |
586 | DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc)); | |
3454f01a | 587 | vc4_hvs_dump_state(hvs); |
8175287b MR |
588 | } |
589 | ||
590 | /* Copy all the active planes' dlist contents to the hardware dlist. */ | |
868bc999 DS |
591 | do { |
592 | found = false; | |
593 | ||
594 | drm_atomic_crtc_for_each_plane(plane, crtc) { | |
595 | if (plane->state->normalized_zpos != zpos) | |
596 | continue; | |
597 | ||
598 | /* Is this the first active plane? */ | |
599 | if (dlist_next == dlist_start) { | |
600 | /* We need to enable background fill when a plane | |
601 | * could be alpha blending from the background, i.e. | |
602 | * where no other plane is underneath. It suffices to | |
603 | * consider the first active plane here since we set | |
604 | * needs_bg_fill such that either the first plane | |
605 | * already needs it or all planes on top blend from | |
606 | * the first or a lower plane. | |
607 | */ | |
608 | vc4_plane_state = to_vc4_plane_state(plane->state); | |
609 | enable_bg_fill = vc4_plane_state->needs_bg_fill; | |
610 | } | |
611 | ||
612 | dlist_next += vc4_plane_write_dlist(plane, dlist_next); | |
613 | ||
614 | found = true; | |
8175287b MR |
615 | } |
616 | ||
868bc999 DS |
617 | zpos++; |
618 | } while (found); | |
8175287b MR |
619 | |
620 | writel(SCALER_CTL0_END, dlist_next); | |
621 | dlist_next++; | |
622 | ||
623 | WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm.size); | |
624 | ||
625 | if (enable_bg_fill) | |
626 | /* This sets a black background color fill, as is the case | |
627 | * with other DRM drivers. | |
628 | */ | |
e418639d MR |
629 | HVS_WRITE(SCALER_DISPBKGNDX(channel), |
630 | HVS_READ(SCALER_DISPBKGNDX(channel)) | | |
8175287b MR |
631 | SCALER_DISPBKGND_FILL); |
632 | ||
633 | /* Only update DISPLIST if the CRTC was already running and is not | |
634 | * being disabled. | |
635 | * vc4_crtc_enable() takes care of updating the dlist just after | |
636 | * re-enabling VBLANK interrupts and before enabling the engine. | |
637 | * If the CRTC is being disabled, there's no point in updating this | |
638 | * information. | |
639 | */ | |
4dfcaa09 MR |
640 | if (crtc->state->active && old_state->active) { |
641 | vc4_hvs_install_dlist(crtc); | |
8175287b | 642 | vc4_hvs_update_dlist(crtc); |
4dfcaa09 | 643 | } |
8175287b MR |
644 | |
645 | if (crtc->state->color_mgmt_changed) { | |
e418639d | 646 | u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel)); |
8175287b MR |
647 | |
648 | if (crtc->state->gamma_lut) { | |
3454f01a | 649 | vc4_hvs_update_gamma_lut(hvs, vc4_crtc); |
8175287b MR |
650 | dispbkgndx |= SCALER_DISPBKGND_GAMMA; |
651 | } else { | |
652 | /* Unsetting DISPBKGND_GAMMA skips the gamma lut step | |
653 | * in hardware, which is the same as a linear lut that | |
654 | * DRM expects us to use in absence of a user lut. | |
655 | */ | |
656 | dispbkgndx &= ~SCALER_DISPBKGND_GAMMA; | |
657 | } | |
e418639d | 658 | HVS_WRITE(SCALER_DISPBKGNDX(channel), dispbkgndx); |
8175287b MR |
659 | } |
660 | ||
661 | if (debug_dump_regs) { | |
662 | DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc)); | |
3454f01a | 663 | vc4_hvs_dump_state(hvs); |
8175287b | 664 | } |
969cfae1 MR |
665 | |
666 | drm_dev_exit(idx); | |
8175287b MR |
667 | } |
668 | ||
3454f01a | 669 | void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel) |
531a1b62 | 670 | { |
969cfae1 MR |
671 | struct drm_device *drm = &hvs->vc4->base; |
672 | u32 dispctrl; | |
673 | int idx; | |
674 | ||
675 | if (!drm_dev_enter(drm, &idx)) | |
676 | return; | |
531a1b62 | 677 | |
969cfae1 | 678 | dispctrl = HVS_READ(SCALER_DISPCTRL); |
87551ec6 DS |
679 | dispctrl &= ~(hvs->vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) : |
680 | SCALER_DISPCTRL_DSPEISLUR(channel)); | |
531a1b62 BB |
681 | |
682 | HVS_WRITE(SCALER_DISPCTRL, dispctrl); | |
969cfae1 MR |
683 | |
684 | drm_dev_exit(idx); | |
531a1b62 BB |
685 | } |
686 | ||
3454f01a | 687 | void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel) |
531a1b62 | 688 | { |
969cfae1 MR |
689 | struct drm_device *drm = &hvs->vc4->base; |
690 | u32 dispctrl; | |
691 | int idx; | |
692 | ||
693 | if (!drm_dev_enter(drm, &idx)) | |
694 | return; | |
531a1b62 | 695 | |
969cfae1 | 696 | dispctrl = HVS_READ(SCALER_DISPCTRL); |
87551ec6 DS |
697 | dispctrl |= (hvs->vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) : |
698 | SCALER_DISPCTRL_DSPEISLUR(channel)); | |
531a1b62 BB |
699 | |
700 | HVS_WRITE(SCALER_DISPSTAT, | |
701 | SCALER_DISPSTAT_EUFLOW(channel)); | |
702 | HVS_WRITE(SCALER_DISPCTRL, dispctrl); | |
969cfae1 MR |
703 | |
704 | drm_dev_exit(idx); | |
531a1b62 BB |
705 | } |
706 | ||
707 | static void vc4_hvs_report_underrun(struct drm_device *dev) | |
708 | { | |
709 | struct vc4_dev *vc4 = to_vc4_dev(dev); | |
710 | ||
711 | atomic_inc(&vc4->underrun); | |
712 | DRM_DEV_ERROR(dev->dev, "HVS underrun\n"); | |
713 | } | |
714 | ||
715 | static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) | |
716 | { | |
717 | struct drm_device *dev = data; | |
718 | struct vc4_dev *vc4 = to_vc4_dev(dev); | |
3454f01a | 719 | struct vc4_hvs *hvs = vc4->hvs; |
531a1b62 BB |
720 | irqreturn_t irqret = IRQ_NONE; |
721 | int channel; | |
722 | u32 control; | |
723 | u32 status; | |
87551ec6 | 724 | u32 dspeislur; |
531a1b62 | 725 | |
969cfae1 MR |
726 | /* |
727 | * NOTE: We don't need to protect the register access using | |
728 | * drm_dev_enter() there because the interrupt handler lifetime | |
729 | * is tied to the device itself, and not to the DRM device. | |
730 | * | |
731 | * So when the device will be gone, one of the first thing we | |
732 | * will be doing will be to unregister the interrupt handler, | |
733 | * and then unregister the DRM device. drm_dev_enter() would | |
734 | * thus always succeed if we are here. | |
735 | */ | |
736 | ||
531a1b62 BB |
737 | status = HVS_READ(SCALER_DISPSTAT); |
738 | control = HVS_READ(SCALER_DISPCTRL); | |
739 | ||
740 | for (channel = 0; channel < SCALER_CHANNELS_COUNT; channel++) { | |
87551ec6 DS |
741 | dspeislur = vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) : |
742 | SCALER_DISPCTRL_DSPEISLUR(channel); | |
531a1b62 BB |
743 | /* Interrupt masking is not always honored, so check it here. */ |
744 | if (status & SCALER_DISPSTAT_EUFLOW(channel) && | |
87551ec6 | 745 | control & dspeislur) { |
3454f01a | 746 | vc4_hvs_mask_underrun(hvs, channel); |
531a1b62 BB |
747 | vc4_hvs_report_underrun(dev); |
748 | ||
749 | irqret = IRQ_HANDLED; | |
750 | } | |
751 | } | |
752 | ||
753 | /* Clear every per-channel interrupt flag. */ | |
754 | HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_IRQMASK(0) | | |
755 | SCALER_DISPSTAT_IRQMASK(1) | | |
756 | SCALER_DISPSTAT_IRQMASK(2)); | |
757 | ||
758 | return irqret; | |
759 | } | |
760 | ||
445b287e MR |
761 | int vc4_hvs_debugfs_init(struct drm_minor *minor) |
762 | { | |
763 | struct drm_device *drm = minor->dev; | |
764 | struct vc4_dev *vc4 = to_vc4_dev(drm); | |
765 | struct vc4_hvs *hvs = vc4->hvs; | |
445b287e MR |
766 | |
767 | if (!vc4->hvs) | |
768 | return -ENODEV; | |
769 | ||
770 | if (!vc4->is_vc5) | |
771 | debugfs_create_bool("hvs_load_tracker", S_IRUGO | S_IWUSR, | |
772 | minor->debugfs_root, | |
773 | &vc4->load_tracker_enabled); | |
774 | ||
f2ede40e | 775 | drm_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, NULL); |
445b287e | 776 | |
f2ede40e | 777 | drm_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun, NULL); |
445b287e | 778 | |
f2ede40e | 779 | vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset); |
445b287e MR |
780 | |
781 | return 0; | |
782 | } | |
783 | ||
640dbcc9 MR |
784 | struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, struct platform_device *pdev) |
785 | { | |
786 | struct drm_device *drm = &vc4->base; | |
787 | struct vc4_hvs *hvs; | |
788 | ||
789 | hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL); | |
790 | if (!hvs) | |
791 | return ERR_PTR(-ENOMEM); | |
792 | ||
793 | hvs->vc4 = vc4; | |
794 | hvs->pdev = pdev; | |
795 | ||
796 | spin_lock_init(&hvs->mm_lock); | |
797 | ||
798 | /* Set up the HVS display list memory manager. We never | |
799 | * overwrite the setup from the bootloader (just 128b out of | |
800 | * our 16K), since we don't want to scramble the screen when | |
801 | * transitioning from the firmware's boot setup to runtime. | |
802 | */ | |
803 | drm_mm_init(&hvs->dlist_mm, | |
804 | HVS_BOOTLOADER_DLIST_END, | |
805 | (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END); | |
806 | ||
807 | /* Set up the HVS LBM memory manager. We could have some more | |
808 | * complicated data structure that allowed reuse of LBM areas | |
809 | * between planes when they don't overlap on the screen, but | |
810 | * for now we just allocate globally. | |
811 | */ | |
812 | if (!vc4->is_vc5) | |
813 | /* 48k words of 2x12-bit pixels */ | |
814 | drm_mm_init(&hvs->lbm_mm, 0, 48 * 1024); | |
815 | else | |
816 | /* 60k words of 4x12-bit pixels */ | |
817 | drm_mm_init(&hvs->lbm_mm, 0, 60 * 1024); | |
818 | ||
819 | vc4->hvs = hvs; | |
820 | ||
821 | return hvs; | |
822 | } | |
823 | ||
c8b75bca EA |
824 | static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) |
825 | { | |
826 | struct platform_device *pdev = to_platform_device(dev); | |
827 | struct drm_device *drm = dev_get_drvdata(master); | |
88e08589 | 828 | struct vc4_dev *vc4 = to_vc4_dev(drm); |
c8b75bca | 829 | struct vc4_hvs *hvs = NULL; |
21af94cf | 830 | int ret; |
d17a1bb9 | 831 | u32 dispctrl; |
1284365e | 832 | u32 reg, top; |
c8b75bca | 833 | |
640dbcc9 MR |
834 | hvs = __vc4_hvs_alloc(vc4, NULL); |
835 | if (IS_ERR(hvs)) | |
836 | return PTR_ERR(hvs); | |
c8b75bca EA |
837 | |
838 | hvs->regs = vc4_ioremap_regs(pdev, 0); | |
839 | if (IS_ERR(hvs->regs)) | |
840 | return PTR_ERR(hvs->regs); | |
841 | ||
3051719a EA |
842 | hvs->regset.base = hvs->regs; |
843 | hvs->regset.regs = hvs_regs; | |
844 | hvs->regset.nregs = ARRAY_SIZE(hvs_regs); | |
845 | ||
1cbc91eb | 846 | if (vc4->is_vc5) { |
2a001ca0 MR |
847 | struct rpi_firmware *firmware; |
848 | struct device_node *node; | |
849 | unsigned int max_rate; | |
850 | ||
851 | node = rpi_firmware_find_node(); | |
852 | if (!node) | |
853 | return -EINVAL; | |
854 | ||
855 | firmware = rpi_firmware_get(node); | |
856 | of_node_put(node); | |
857 | if (!firmware) | |
858 | return -EPROBE_DEFER; | |
859 | ||
d7d96c00 MR |
860 | hvs->core_clk = devm_clk_get(&pdev->dev, NULL); |
861 | if (IS_ERR(hvs->core_clk)) { | |
862 | dev_err(&pdev->dev, "Couldn't get core clock\n"); | |
863 | return PTR_ERR(hvs->core_clk); | |
864 | } | |
865 | ||
2a001ca0 MR |
866 | max_rate = rpi_firmware_clk_get_max_rate(firmware, |
867 | RPI_FIRMWARE_CORE_CLK_ID); | |
868 | rpi_firmware_put(firmware); | |
869 | if (max_rate >= 550000000) | |
870 | hvs->vc5_hdmi_enable_hdmi_20 = true; | |
871 | ||
f09e172d DC |
872 | if (max_rate >= 600000000) |
873 | hvs->vc5_hdmi_enable_4096by2160 = true; | |
874 | ||
2a001ca0 MR |
875 | hvs->max_core_rate = max_rate; |
876 | ||
d7d96c00 MR |
877 | ret = clk_prepare_enable(hvs->core_clk); |
878 | if (ret) { | |
879 | dev_err(&pdev->dev, "Couldn't enable the core clock\n"); | |
880 | return ret; | |
881 | } | |
882 | } | |
883 | ||
1cbc91eb | 884 | if (!vc4->is_vc5) |
c54619b0 DS |
885 | hvs->dlist = hvs->regs + SCALER_DLIST_START; |
886 | else | |
887 | hvs->dlist = hvs->regs + SCALER5_DLIST_START; | |
c8b75bca | 888 | |
21af94cf EA |
889 | /* Upload filter kernels. We only have the one for now, so we |
890 | * keep it around for the lifetime of the driver. | |
891 | */ | |
892 | ret = vc4_hvs_upload_linear_kernel(hvs, | |
893 | &hvs->mitchell_netravali_filter, | |
894 | mitchell_netravali_1_3_1_3_kernel); | |
895 | if (ret) | |
896 | return ret; | |
897 | ||
8514e6b1 MR |
898 | reg = HVS_READ(SCALER_DISPECTRL); |
899 | reg &= ~SCALER_DISPECTRL_DSP2_MUX_MASK; | |
900 | HVS_WRITE(SCALER_DISPECTRL, | |
901 | reg | VC4_SET_FIELD(0, SCALER_DISPECTRL_DSP2_MUX)); | |
902 | ||
903 | reg = HVS_READ(SCALER_DISPCTRL); | |
904 | reg &= ~SCALER_DISPCTRL_DSP3_MUX_MASK; | |
905 | HVS_WRITE(SCALER_DISPCTRL, | |
906 | reg | VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX)); | |
907 | ||
908 | reg = HVS_READ(SCALER_DISPEOLN); | |
909 | reg &= ~SCALER_DISPEOLN_DSP4_MUX_MASK; | |
910 | HVS_WRITE(SCALER_DISPEOLN, | |
911 | reg | VC4_SET_FIELD(3, SCALER_DISPEOLN_DSP4_MUX)); | |
912 | ||
913 | reg = HVS_READ(SCALER_DISPDITHER); | |
914 | reg &= ~SCALER_DISPDITHER_DSP5_MUX_MASK; | |
915 | HVS_WRITE(SCALER_DISPDITHER, | |
916 | reg | VC4_SET_FIELD(3, SCALER_DISPDITHER_DSP5_MUX)); | |
917 | ||
d17a1bb9 EA |
918 | dispctrl = HVS_READ(SCALER_DISPCTRL); |
919 | ||
920 | dispctrl |= SCALER_DISPCTRL_ENABLE; | |
531a1b62 BB |
921 | dispctrl |= SCALER_DISPCTRL_DISPEIRQ(0) | |
922 | SCALER_DISPCTRL_DISPEIRQ(1) | | |
923 | SCALER_DISPCTRL_DISPEIRQ(2); | |
d17a1bb9 | 924 | |
87551ec6 DS |
925 | if (!vc4->is_vc5) |
926 | dispctrl &= ~(SCALER_DISPCTRL_DMAEIRQ | | |
927 | SCALER_DISPCTRL_SLVWREIRQ | | |
928 | SCALER_DISPCTRL_SLVRDEIRQ | | |
929 | SCALER_DISPCTRL_DSPEIEOF(0) | | |
930 | SCALER_DISPCTRL_DSPEIEOF(1) | | |
931 | SCALER_DISPCTRL_DSPEIEOF(2) | | |
932 | SCALER_DISPCTRL_DSPEIEOLN(0) | | |
933 | SCALER_DISPCTRL_DSPEIEOLN(1) | | |
934 | SCALER_DISPCTRL_DSPEIEOLN(2) | | |
935 | SCALER_DISPCTRL_DSPEISLUR(0) | | |
936 | SCALER_DISPCTRL_DSPEISLUR(1) | | |
937 | SCALER_DISPCTRL_DSPEISLUR(2) | | |
938 | SCALER_DISPCTRL_SCLEIRQ); | |
939 | else | |
940 | dispctrl &= ~(SCALER_DISPCTRL_DMAEIRQ | | |
941 | SCALER5_DISPCTRL_SLVEIRQ | | |
942 | SCALER5_DISPCTRL_DSPEIEOF(0) | | |
943 | SCALER5_DISPCTRL_DSPEIEOF(1) | | |
944 | SCALER5_DISPCTRL_DSPEIEOF(2) | | |
945 | SCALER5_DISPCTRL_DSPEIEOLN(0) | | |
946 | SCALER5_DISPCTRL_DSPEIEOLN(1) | | |
947 | SCALER5_DISPCTRL_DSPEIEOLN(2) | | |
948 | SCALER5_DISPCTRL_DSPEISLUR(0) | | |
949 | SCALER5_DISPCTRL_DSPEISLUR(1) | | |
950 | SCALER5_DISPCTRL_DSPEISLUR(2) | | |
951 | SCALER_DISPCTRL_SCLEIRQ); | |
952 | ||
d17a1bb9 | 953 | |
df993fce DS |
954 | /* Set AXI panic mode. |
955 | * VC4 panics when < 2 lines in FIFO. | |
956 | * VC5 panics when less than 1 line in the FIFO. | |
957 | */ | |
958 | dispctrl &= ~(SCALER_DISPCTRL_PANIC0_MASK | | |
959 | SCALER_DISPCTRL_PANIC1_MASK | | |
960 | SCALER_DISPCTRL_PANIC2_MASK); | |
961 | dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC0); | |
962 | dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC1); | |
963 | dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC2); | |
964 | ||
d17a1bb9 EA |
965 | HVS_WRITE(SCALER_DISPCTRL, dispctrl); |
966 | ||
1284365e DS |
967 | /* Recompute Composite Output Buffer (COB) allocations for the displays |
968 | */ | |
969 | if (!vc4->is_vc5) { | |
970 | /* The COB is 20736 pixels, or just over 10 lines at 2048 wide. | |
971 | * The bottom 2048 pixels are full 32bpp RGBA (intended for the | |
972 | * TXP composing RGBA to memory), whilst the remainder are only | |
973 | * 24bpp RGB. | |
974 | * | |
975 | * Assign 3 lines to channels 1 & 2, and just over 4 lines to | |
976 | * channel 0. | |
977 | */ | |
978 | #define VC4_COB_SIZE 20736 | |
979 | #define VC4_COB_LINE_WIDTH 2048 | |
980 | #define VC4_COB_NUM_LINES 3 | |
981 | reg = 0; | |
982 | top = VC4_COB_LINE_WIDTH * VC4_COB_NUM_LINES; | |
983 | reg |= (top - 1) << 16; | |
984 | HVS_WRITE(SCALER_DISPBASE2, reg); | |
985 | reg = top; | |
986 | top += VC4_COB_LINE_WIDTH * VC4_COB_NUM_LINES; | |
987 | reg |= (top - 1) << 16; | |
988 | HVS_WRITE(SCALER_DISPBASE1, reg); | |
989 | reg = top; | |
990 | top = VC4_COB_SIZE; | |
991 | reg |= (top - 1) << 16; | |
992 | HVS_WRITE(SCALER_DISPBASE0, reg); | |
993 | } else { | |
994 | /* The COB is 44416 pixels, or 10.8 lines at 4096 wide. | |
995 | * The bottom 4096 pixels are full RGBA (intended for the TXP | |
996 | * composing RGBA to memory), whilst the remainder are only | |
997 | * RGB. Addressing is always pixel wide. | |
998 | * | |
999 | * Assign 3 lines of 4096 to channels 1 & 2, and just over 4 | |
1000 | * lines. to channel 0. | |
1001 | */ | |
1002 | #define VC5_COB_SIZE 44416 | |
1003 | #define VC5_COB_LINE_WIDTH 4096 | |
1004 | #define VC5_COB_NUM_LINES 3 | |
1005 | reg = 0; | |
1006 | top = VC5_COB_LINE_WIDTH * VC5_COB_NUM_LINES; | |
1007 | reg |= top << 16; | |
1008 | HVS_WRITE(SCALER_DISPBASE2, reg); | |
1009 | top += 16; | |
1010 | reg = top; | |
1011 | top += VC5_COB_LINE_WIDTH * VC5_COB_NUM_LINES; | |
1012 | reg |= top << 16; | |
1013 | HVS_WRITE(SCALER_DISPBASE1, reg); | |
1014 | top += 16; | |
1015 | reg = top; | |
1016 | top = VC5_COB_SIZE; | |
1017 | reg |= top << 16; | |
1018 | HVS_WRITE(SCALER_DISPBASE0, reg); | |
1019 | } | |
1020 | ||
531a1b62 BB |
1021 | ret = devm_request_irq(dev, platform_get_irq(pdev, 0), |
1022 | vc4_hvs_irq_handler, 0, "vc4 hvs", drm); | |
1023 | if (ret) | |
1024 | return ret; | |
1025 | ||
c8b75bca EA |
1026 | return 0; |
1027 | } | |
1028 | ||
1029 | static void vc4_hvs_unbind(struct device *dev, struct device *master, | |
1030 | void *data) | |
1031 | { | |
1032 | struct drm_device *drm = dev_get_drvdata(master); | |
88e08589 | 1033 | struct vc4_dev *vc4 = to_vc4_dev(drm); |
d7d96c00 | 1034 | struct vc4_hvs *hvs = vc4->hvs; |
398e7cea | 1035 | struct drm_mm_node *node, *next; |
c8b75bca | 1036 | |
71724f70 | 1037 | if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter)) |
21af94cf EA |
1038 | drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter); |
1039 | ||
398e7cea MR |
1040 | drm_mm_for_each_node_safe(node, next, &vc4->hvs->dlist_mm) |
1041 | drm_mm_remove_node(node); | |
1042 | ||
d8dbf44f | 1043 | drm_mm_takedown(&vc4->hvs->dlist_mm); |
398e7cea MR |
1044 | |
1045 | drm_mm_for_each_node_safe(node, next, &vc4->hvs->lbm_mm) | |
1046 | drm_mm_remove_node(node); | |
21af94cf | 1047 | drm_mm_takedown(&vc4->hvs->lbm_mm); |
d8dbf44f | 1048 | |
d7d96c00 MR |
1049 | clk_disable_unprepare(hvs->core_clk); |
1050 | ||
c8b75bca EA |
1051 | vc4->hvs = NULL; |
1052 | } | |
1053 | ||
1054 | static const struct component_ops vc4_hvs_ops = { | |
1055 | .bind = vc4_hvs_bind, | |
1056 | .unbind = vc4_hvs_unbind, | |
1057 | }; | |
1058 | ||
1059 | static int vc4_hvs_dev_probe(struct platform_device *pdev) | |
1060 | { | |
1061 | return component_add(&pdev->dev, &vc4_hvs_ops); | |
1062 | } | |
1063 | ||
1ed54a19 | 1064 | static void vc4_hvs_dev_remove(struct platform_device *pdev) |
c8b75bca EA |
1065 | { |
1066 | component_del(&pdev->dev, &vc4_hvs_ops); | |
c8b75bca EA |
1067 | } |
1068 | ||
1069 | static const struct of_device_id vc4_hvs_dt_match[] = { | |
c54619b0 | 1070 | { .compatible = "brcm,bcm2711-hvs" }, |
c8b75bca EA |
1071 | { .compatible = "brcm,bcm2835-hvs" }, |
1072 | {} | |
1073 | }; | |
1074 | ||
1075 | struct platform_driver vc4_hvs_driver = { | |
1076 | .probe = vc4_hvs_dev_probe, | |
1ed54a19 | 1077 | .remove_new = vc4_hvs_dev_remove, |
c8b75bca EA |
1078 | .driver = { |
1079 | .name = "vc4_hvs", | |
1080 | .of_match_table = vc4_hvs_dt_match, | |
1081 | }, | |
1082 | }; |