1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2015 Broadcom |
4 | */ |
5 | |
6 | /** |
7 | * DOC: VC4 HVS module. |
8 | * |
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. |
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 | |
22 | #include <linux/bitfield.h> |
23 | #include <linux/clk.h> |
24 | #include <linux/component.h> |
25 | #include <linux/platform_device.h> |
26 | |
27 | #include <drm/drm_atomic_helper.h> |
28 | #include <drm/drm_drv.h> |
29 | #include <drm/drm_vblank.h> |
30 | |
31 | #include <soc/bcm2835/raspberrypi-firmware.h> |
32 | |
33 | #include "vc4_drv.h" |
34 | #include "vc4_regs.h" |
35 | |
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), |
68 | }; |
69 | |
70 | void vc4_hvs_dump_state(struct vc4_hvs *hvs) |
71 | { |
72 | struct drm_device *drm = &hvs->vc4->base; |
73 | struct drm_printer p = drm_info_printer(dev: &hvs->pdev->dev); |
74 | int idx, i; |
75 | |
76 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
77 | return; |
78 | |
79 | drm_print_regset32(p: &p, regset: &hvs->regset); |
80 | |
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" , |
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)); |
89 | } |
90 | |
91 | drm_dev_exit(idx); |
92 | } |
93 | |
94 | static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data) |
95 | { |
96 | struct drm_debugfs_entry *entry = m->private; |
97 | struct drm_device *dev = entry->dev; |
98 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
99 | struct drm_printer p = drm_seq_file_printer(f: m); |
100 | |
101 | drm_printf(p: &p, f: "%d\n" , atomic_read(v: &vc4->underrun)); |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data) |
107 | { |
108 | struct drm_debugfs_entry *entry = m->private; |
109 | struct drm_device *dev = entry->dev; |
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(f: 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: &p, f: "HVS chan %u disabled\n" , i); |
123 | continue; |
124 | } |
125 | |
126 | drm_printf(p: &p, f: "HVS chan %u:\n" , i); |
127 | |
128 | for (j = HVS_READ(SCALER_DISPLISTX(i)); j < 256; j++) { |
129 | dlist_word = readl(addr: (u32 __iomem *)vc4->hvs->dlist + j); |
130 | drm_printf(p: &p, f: "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 | |
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 | |
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 | |
192 | ret = drm_mm_insert_node(mm: &hvs->dlist_mm, node: space, VC4_KERNEL_DWORDS); |
193 | if (ret) { |
194 | DRM_ERROR("Failed to allocate space for filter kernel: %d\n" , |
195 | ret); |
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(val: kernel[i], addr: &dst_kernel[i]); |
204 | else { |
205 | writel(val: kernel[VC4_KERNEL_DWORDS - i - 1], |
206 | addr: &dst_kernel[i]); |
207 | } |
208 | } |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static void vc4_hvs_lut_load(struct vc4_hvs *hvs, |
214 | struct vc4_crtc *vc4_crtc) |
215 | { |
216 | struct drm_device *drm = &hvs->vc4->base; |
217 | struct drm_crtc *crtc = &vc4_crtc->base; |
218 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
219 | int idx; |
220 | u32 i; |
221 | |
222 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
223 | return; |
224 | |
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 | |
231 | (vc4_state->assigned_channel * 3 * crtc->gamma_size)); |
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]); |
239 | |
240 | drm_dev_exit(idx); |
241 | } |
242 | |
243 | static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs, |
244 | struct vc4_crtc *vc4_crtc) |
245 | { |
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(blob: crtc_state->gamma_lut); |
249 | u32 i; |
250 | |
251 | for (i = 0; i < length; i++) { |
252 | vc4_crtc->lut_r[i] = drm_color_lut_extract(user_input: lut[i].red, bit_precision: 8); |
253 | vc4_crtc->lut_g[i] = drm_color_lut_extract(user_input: lut[i].green, bit_precision: 8); |
254 | vc4_crtc->lut_b[i] = drm_color_lut_extract(user_input: lut[i].blue, bit_precision: 8); |
255 | } |
256 | |
257 | vc4_hvs_lut_load(hvs, vc4_crtc); |
258 | } |
259 | |
260 | u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) |
261 | { |
262 | struct drm_device *drm = &hvs->vc4->base; |
263 | u8 field = 0; |
264 | int idx; |
265 | |
266 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
267 | return 0; |
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 | |
284 | drm_dev_exit(idx); |
285 | return field; |
286 | } |
287 | |
288 | int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) |
289 | { |
290 | struct vc4_dev *vc4 = hvs->vc4; |
291 | u32 reg; |
292 | int ret; |
293 | |
294 | if (!vc4->is_vc5) |
295 | return output; |
296 | |
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 | |
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 | |
347 | static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, |
348 | struct drm_display_mode *mode, bool oneshot) |
349 | { |
350 | struct vc4_dev *vc4 = hvs->vc4; |
351 | struct drm_device *drm = &vc4->base; |
352 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
353 | struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); |
354 | unsigned int chan = vc4_crtc_state->assigned_channel; |
355 | bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; |
356 | u32 dispbkgndx; |
357 | u32 dispctrl; |
358 | int idx; |
359 | |
360 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
361 | return -ENODEV; |
362 | |
363 | HVS_WRITE(SCALER_DISPCTRLX(chan), 0); |
364 | HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET); |
365 | HVS_WRITE(SCALER_DISPCTRLX(chan), 0); |
366 | |
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; |
373 | dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(chan)); |
374 | |
375 | if (!vc4->is_vc5) { |
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); |
381 | dispbkgndx |= SCALER_DISPBKGND_AUTOHS; |
382 | } else { |
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); |
388 | dispbkgndx &= ~SCALER5_DISPBKGND_BCK2BCK; |
389 | } |
390 | |
391 | HVS_WRITE(SCALER_DISPCTRLX(chan), dispctrl); |
392 | |
393 | dispbkgndx &= ~SCALER_DISPBKGND_GAMMA; |
394 | dispbkgndx &= ~SCALER_DISPBKGND_INTERLACE; |
395 | |
396 | HVS_WRITE(SCALER_DISPBKGNDX(chan), dispbkgndx | |
397 | ((!vc4->is_vc5) ? SCALER_DISPBKGND_GAMMA : 0) | |
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 | */ |
403 | vc4_hvs_lut_load(hvs, vc4_crtc); |
404 | |
405 | drm_dev_exit(idx); |
406 | |
407 | return 0; |
408 | } |
409 | |
410 | void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) |
411 | { |
412 | struct drm_device *drm = &hvs->vc4->base; |
413 | int idx; |
414 | |
415 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
416 | return; |
417 | |
418 | if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE) |
419 | goto out; |
420 | |
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); |
436 | |
437 | out: |
438 | drm_dev_exit(idx); |
439 | } |
440 | |
441 | int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) |
442 | { |
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); |
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 | */ |
456 | if (hweight32(crtc_state->connector_mask) > 1) |
457 | return -EINVAL; |
458 | |
459 | drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) |
460 | dlist_count += vc4_plane_dlist_size(state: 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(mm: &vc4->hvs->dlist_mm, node: &vc4_state->mm, |
466 | size: dlist_count); |
467 | spin_unlock_irqrestore(lock: &vc4->hvs->mm_lock, flags); |
468 | if (ret) |
469 | return ret; |
470 | |
471 | return 0; |
472 | } |
473 | |
474 | static void vc4_hvs_install_dlist(struct drm_crtc *crtc) |
475 | { |
476 | struct drm_device *dev = crtc->dev; |
477 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
478 | struct vc4_hvs *hvs = vc4->hvs; |
479 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
480 | int idx; |
481 | |
482 | if (!drm_dev_enter(dev, idx: &idx)) |
483 | return; |
484 | |
485 | HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), |
486 | vc4_state->mm.start); |
487 | |
488 | drm_dev_exit(idx); |
489 | } |
490 | |
491 | static void vc4_hvs_update_dlist(struct drm_crtc *crtc) |
492 | { |
493 | struct drm_device *dev = crtc->dev; |
494 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
495 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
496 | unsigned long flags; |
497 | |
498 | if (crtc->state->event) { |
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 | |
505 | if (!vc4_crtc->feeds_txp || vc4_state->txp_armed) { |
506 | vc4_crtc->event = crtc->state->event; |
507 | crtc->state->event = NULL; |
508 | } |
509 | |
510 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
511 | } |
512 | |
513 | spin_lock_irqsave(&vc4_crtc->irq_lock, flags); |
514 | vc4_crtc->current_dlist = vc4_state->mm.start; |
515 | spin_unlock_irqrestore(lock: &vc4_crtc->irq_lock, flags); |
516 | } |
517 | |
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(lock: &vc4_crtc->irq_lock, flags); |
528 | } |
529 | |
530 | void vc4_hvs_atomic_enable(struct drm_crtc *crtc, |
531 | struct drm_atomic_state *state) |
532 | { |
533 | struct drm_device *dev = crtc->dev; |
534 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
535 | struct drm_display_mode *mode = &crtc->state->adjusted_mode; |
536 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
537 | bool oneshot = vc4_crtc->feeds_txp; |
538 | |
539 | vc4_hvs_install_dlist(crtc); |
540 | vc4_hvs_update_dlist(crtc); |
541 | vc4_hvs_init_channel(hvs: vc4->hvs, crtc, mode, oneshot); |
542 | } |
543 | |
544 | void vc4_hvs_atomic_disable(struct drm_crtc *crtc, |
545 | struct drm_atomic_state *state) |
546 | { |
547 | struct drm_device *dev = crtc->dev; |
548 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
549 | struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc); |
550 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(old_state); |
551 | unsigned int chan = vc4_state->assigned_channel; |
552 | |
553 | vc4_hvs_stop_channel(hvs: vc4->hvs, chan); |
554 | } |
555 | |
556 | void vc4_hvs_atomic_flush(struct drm_crtc *crtc, |
557 | struct drm_atomic_state *state) |
558 | { |
559 | struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, |
560 | crtc); |
561 | struct drm_device *dev = crtc->dev; |
562 | struct vc4_dev *vc4 = to_vc4_dev(dev); |
563 | struct vc4_hvs *hvs = vc4->hvs; |
564 | struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
565 | struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
566 | unsigned int channel = vc4_state->assigned_channel; |
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; |
573 | unsigned int zpos = 0; |
574 | bool found = false; |
575 | int idx; |
576 | |
577 | if (!drm_dev_enter(dev, idx: &idx)) { |
578 | vc4_crtc_send_vblank(crtc); |
579 | return; |
580 | } |
581 | |
582 | if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED) |
583 | return; |
584 | |
585 | if (debug_dump_regs) { |
586 | DRM_INFO("CRTC %d HVS before:\n" , drm_crtc_index(crtc)); |
587 | vc4_hvs_dump_state(hvs); |
588 | } |
589 | |
590 | /* Copy all the active planes' dlist contents to the hardware dlist. */ |
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: dlist_next); |
613 | |
614 | found = true; |
615 | } |
616 | |
617 | zpos++; |
618 | } while (found); |
619 | |
620 | writel(SCALER_CTL0_END, addr: 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 | */ |
629 | HVS_WRITE(SCALER_DISPBKGNDX(channel), |
630 | HVS_READ(SCALER_DISPBKGNDX(channel)) | |
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 | */ |
640 | if (crtc->state->active && old_state->active) { |
641 | vc4_hvs_install_dlist(crtc); |
642 | vc4_hvs_update_dlist(crtc); |
643 | } |
644 | |
645 | if (crtc->state->color_mgmt_changed) { |
646 | u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel)); |
647 | |
648 | if (crtc->state->gamma_lut) { |
649 | vc4_hvs_update_gamma_lut(hvs, vc4_crtc); |
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 | } |
658 | HVS_WRITE(SCALER_DISPBKGNDX(channel), dispbkgndx); |
659 | } |
660 | |
661 | if (debug_dump_regs) { |
662 | DRM_INFO("CRTC %d HVS after:\n" , drm_crtc_index(crtc)); |
663 | vc4_hvs_dump_state(hvs); |
664 | } |
665 | |
666 | drm_dev_exit(idx); |
667 | } |
668 | |
669 | void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel) |
670 | { |
671 | struct drm_device *drm = &hvs->vc4->base; |
672 | u32 dispctrl; |
673 | int idx; |
674 | |
675 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
676 | return; |
677 | |
678 | dispctrl = HVS_READ(SCALER_DISPCTRL); |
679 | dispctrl &= ~(hvs->vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) : |
680 | SCALER_DISPCTRL_DSPEISLUR(channel)); |
681 | |
682 | HVS_WRITE(SCALER_DISPCTRL, dispctrl); |
683 | |
684 | drm_dev_exit(idx); |
685 | } |
686 | |
687 | void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel) |
688 | { |
689 | struct drm_device *drm = &hvs->vc4->base; |
690 | u32 dispctrl; |
691 | int idx; |
692 | |
693 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
694 | return; |
695 | |
696 | dispctrl = HVS_READ(SCALER_DISPCTRL); |
697 | dispctrl |= (hvs->vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) : |
698 | SCALER_DISPCTRL_DSPEISLUR(channel)); |
699 | |
700 | HVS_WRITE(SCALER_DISPSTAT, |
701 | SCALER_DISPSTAT_EUFLOW(channel)); |
702 | HVS_WRITE(SCALER_DISPCTRL, dispctrl); |
703 | |
704 | drm_dev_exit(idx); |
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(v: &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); |
719 | struct vc4_hvs *hvs = vc4->hvs; |
720 | irqreturn_t irqret = IRQ_NONE; |
721 | int channel; |
722 | u32 control; |
723 | u32 status; |
724 | u32 dspeislur; |
725 | |
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 | |
737 | status = HVS_READ(SCALER_DISPSTAT); |
738 | control = HVS_READ(SCALER_DISPCTRL); |
739 | |
740 | for (channel = 0; channel < SCALER_CHANNELS_COUNT; channel++) { |
741 | dspeislur = vc4->is_vc5 ? SCALER5_DISPCTRL_DSPEISLUR(channel) : |
742 | SCALER_DISPCTRL_DSPEISLUR(channel); |
743 | /* Interrupt masking is not always honored, so check it here. */ |
744 | if (status & SCALER_DISPSTAT_EUFLOW(channel) && |
745 | control & dspeislur) { |
746 | vc4_hvs_mask_underrun(hvs, channel); |
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 | |
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; |
766 | |
767 | if (!vc4->hvs) |
768 | return -ENODEV; |
769 | |
770 | if (!vc4->is_vc5) |
771 | debugfs_create_bool(name: "hvs_load_tracker" , S_IRUGO | S_IWUSR, |
772 | parent: minor->debugfs_root, |
773 | value: &vc4->load_tracker_enabled); |
774 | |
775 | drm_debugfs_add_file(dev: drm, name: "hvs_dlists" , show: vc4_hvs_debugfs_dlist, NULL); |
776 | |
777 | drm_debugfs_add_file(dev: drm, name: "hvs_underrun" , show: vc4_hvs_debugfs_underrun, NULL); |
778 | |
779 | vc4_debugfs_add_regset32(drm, filename: "hvs_regs" , regset: &hvs->regset); |
780 | |
781 | return 0; |
782 | } |
783 | |
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(dev: drm, size: sizeof(*hvs), GFP_KERNEL); |
790 | if (!hvs) |
791 | return ERR_PTR(error: -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(mm: &hvs->dlist_mm, |
804 | HVS_BOOTLOADER_DLIST_END, |
805 | size: (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(mm: &hvs->lbm_mm, start: 0, size: 48 * 1024); |
815 | else |
816 | /* 60k words of 4x12-bit pixels */ |
817 | drm_mm_init(mm: &hvs->lbm_mm, start: 0, size: 60 * 1024); |
818 | |
819 | vc4->hvs = hvs; |
820 | |
821 | return hvs; |
822 | } |
823 | |
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(dev: master); |
828 | struct vc4_dev *vc4 = to_vc4_dev(drm); |
829 | struct vc4_hvs *hvs = NULL; |
830 | int ret; |
831 | u32 dispctrl; |
832 | u32 reg, top; |
833 | |
834 | hvs = __vc4_hvs_alloc(vc4, NULL); |
835 | if (IS_ERR(ptr: hvs)) |
836 | return PTR_ERR(ptr: hvs); |
837 | |
838 | hvs->regs = vc4_ioremap_regs(dev: pdev, index: 0); |
839 | if (IS_ERR(ptr: hvs->regs)) |
840 | return PTR_ERR(ptr: hvs->regs); |
841 | |
842 | hvs->regset.base = hvs->regs; |
843 | hvs->regset.regs = hvs_regs; |
844 | hvs->regset.nregs = ARRAY_SIZE(hvs_regs); |
845 | |
846 | if (vc4->is_vc5) { |
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(firmware_node: node); |
856 | of_node_put(node); |
857 | if (!firmware) |
858 | return -EPROBE_DEFER; |
859 | |
860 | hvs->core_clk = devm_clk_get(dev: &pdev->dev, NULL); |
861 | if (IS_ERR(ptr: hvs->core_clk)) { |
862 | dev_err(&pdev->dev, "Couldn't get core clock\n" ); |
863 | return PTR_ERR(ptr: hvs->core_clk); |
864 | } |
865 | |
866 | max_rate = rpi_firmware_clk_get_max_rate(fw: firmware, |
867 | id: RPI_FIRMWARE_CORE_CLK_ID); |
868 | rpi_firmware_put(fw: firmware); |
869 | if (max_rate >= 550000000) |
870 | hvs->vc5_hdmi_enable_hdmi_20 = true; |
871 | |
872 | if (max_rate >= 600000000) |
873 | hvs->vc5_hdmi_enable_4096by2160 = true; |
874 | |
875 | hvs->max_core_rate = max_rate; |
876 | |
877 | ret = clk_prepare_enable(clk: hvs->core_clk); |
878 | if (ret) { |
879 | dev_err(&pdev->dev, "Couldn't enable the core clock\n" ); |
880 | return ret; |
881 | } |
882 | } |
883 | |
884 | if (!vc4->is_vc5) |
885 | hvs->dlist = hvs->regs + SCALER_DLIST_START; |
886 | else |
887 | hvs->dlist = hvs->regs + SCALER5_DLIST_START; |
888 | |
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 | space: &hvs->mitchell_netravali_filter, |
894 | kernel: mitchell_netravali_1_3_1_3_kernel); |
895 | if (ret) |
896 | return ret; |
897 | |
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 | |
918 | dispctrl = HVS_READ(SCALER_DISPCTRL); |
919 | |
920 | dispctrl |= SCALER_DISPCTRL_ENABLE; |
921 | dispctrl |= SCALER_DISPCTRL_DISPEIRQ(0) | |
922 | SCALER_DISPCTRL_DISPEIRQ(1) | |
923 | SCALER_DISPCTRL_DISPEIRQ(2); |
924 | |
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 | |
953 | |
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 | |
965 | HVS_WRITE(SCALER_DISPCTRL, dispctrl); |
966 | |
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 | |
1021 | ret = devm_request_irq(dev, irq: platform_get_irq(pdev, 0), |
1022 | handler: vc4_hvs_irq_handler, irqflags: 0, devname: "vc4 hvs" , dev_id: drm); |
1023 | if (ret) |
1024 | return ret; |
1025 | |
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(dev: master); |
1033 | struct vc4_dev *vc4 = to_vc4_dev(drm); |
1034 | struct vc4_hvs *hvs = vc4->hvs; |
1035 | struct drm_mm_node *node, *next; |
1036 | |
1037 | if (drm_mm_node_allocated(node: &vc4->hvs->mitchell_netravali_filter)) |
1038 | drm_mm_remove_node(node: &vc4->hvs->mitchell_netravali_filter); |
1039 | |
1040 | drm_mm_for_each_node_safe(node, next, &vc4->hvs->dlist_mm) |
1041 | drm_mm_remove_node(node); |
1042 | |
1043 | drm_mm_takedown(mm: &vc4->hvs->dlist_mm); |
1044 | |
1045 | drm_mm_for_each_node_safe(node, next, &vc4->hvs->lbm_mm) |
1046 | drm_mm_remove_node(node); |
1047 | drm_mm_takedown(mm: &vc4->hvs->lbm_mm); |
1048 | |
1049 | clk_disable_unprepare(clk: hvs->core_clk); |
1050 | |
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 | |
1064 | static void vc4_hvs_dev_remove(struct platform_device *pdev) |
1065 | { |
1066 | component_del(&pdev->dev, &vc4_hvs_ops); |
1067 | } |
1068 | |
1069 | static const struct of_device_id vc4_hvs_dt_match[] = { |
1070 | { .compatible = "brcm,bcm2711-hvs" }, |
1071 | { .compatible = "brcm,bcm2835-hvs" }, |
1072 | {} |
1073 | }; |
1074 | |
1075 | struct platform_driver vc4_hvs_driver = { |
1076 | .probe = vc4_hvs_dev_probe, |
1077 | .remove_new = vc4_hvs_dev_remove, |
1078 | .driver = { |
1079 | .name = "vc4_hvs" , |
1080 | .of_match_table = vc4_hvs_dt_match, |
1081 | }, |
1082 | }; |
1083 | |