1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright 2010 Matt Turner. |
4 | * Copyright 2012 Red Hat |
5 | * |
6 | * Authors: Matthew Garrett |
7 | * Matt Turner |
8 | * Dave Airlie |
9 | */ |
10 | |
11 | #include <linux/delay.h> |
12 | #include <linux/iosys-map.h> |
13 | |
14 | #include <drm/drm_atomic.h> |
15 | #include <drm/drm_atomic_helper.h> |
16 | #include <drm/drm_cache.h> |
17 | #include <drm/drm_damage_helper.h> |
18 | #include <drm/drm_edid.h> |
19 | #include <drm/drm_format_helper.h> |
20 | #include <drm/drm_fourcc.h> |
21 | #include <drm/drm_framebuffer.h> |
22 | #include <drm/drm_gem_atomic_helper.h> |
23 | #include <drm/drm_gem_framebuffer_helper.h> |
24 | #include <drm/drm_print.h> |
25 | |
26 | #include "mgag200_drv.h" |
27 | |
28 | /* |
29 | * This file contains setup code for the CRTC. |
30 | */ |
31 | |
32 | void mgag200_crtc_set_gamma_linear(struct mga_device *mdev, |
33 | const struct drm_format_info *format) |
34 | { |
35 | int i; |
36 | |
37 | WREG8(DAC_INDEX + MGA1064_INDEX, 0); |
38 | |
39 | switch (format->format) { |
40 | case DRM_FORMAT_RGB565: |
41 | /* Use better interpolation, to take 32 values from 0 to 255 */ |
42 | for (i = 0; i < MGAG200_LUT_SIZE / 8; i++) { |
43 | WREG8(DAC_INDEX + MGA1064_COL_PAL, i * 8 + i / 4); |
44 | WREG8(DAC_INDEX + MGA1064_COL_PAL, i * 4 + i / 16); |
45 | WREG8(DAC_INDEX + MGA1064_COL_PAL, i * 8 + i / 4); |
46 | } |
47 | /* Green has one more bit, so add padding with 0 for red and blue. */ |
48 | for (i = MGAG200_LUT_SIZE / 8; i < MGAG200_LUT_SIZE / 4; i++) { |
49 | WREG8(DAC_INDEX + MGA1064_COL_PAL, 0); |
50 | WREG8(DAC_INDEX + MGA1064_COL_PAL, i * 4 + i / 16); |
51 | WREG8(DAC_INDEX + MGA1064_COL_PAL, 0); |
52 | } |
53 | break; |
54 | case DRM_FORMAT_RGB888: |
55 | case DRM_FORMAT_XRGB8888: |
56 | for (i = 0; i < MGAG200_LUT_SIZE; i++) { |
57 | WREG8(DAC_INDEX + MGA1064_COL_PAL, i); |
58 | WREG8(DAC_INDEX + MGA1064_COL_PAL, i); |
59 | WREG8(DAC_INDEX + MGA1064_COL_PAL, i); |
60 | } |
61 | break; |
62 | default: |
63 | drm_warn_once(&mdev->base, "Unsupported format %p4cc for gamma correction\n" , |
64 | &format->format); |
65 | break; |
66 | } |
67 | } |
68 | |
69 | void mgag200_crtc_set_gamma(struct mga_device *mdev, |
70 | const struct drm_format_info *format, |
71 | struct drm_color_lut *lut) |
72 | { |
73 | int i; |
74 | |
75 | WREG8(DAC_INDEX + MGA1064_INDEX, 0); |
76 | |
77 | switch (format->format) { |
78 | case DRM_FORMAT_RGB565: |
79 | /* Use better interpolation, to take 32 values from lut[0] to lut[255] */ |
80 | for (i = 0; i < MGAG200_LUT_SIZE / 8; i++) { |
81 | WREG8(DAC_INDEX + MGA1064_COL_PAL, lut[i * 8 + i / 4].red >> 8); |
82 | WREG8(DAC_INDEX + MGA1064_COL_PAL, lut[i * 4 + i / 16].green >> 8); |
83 | WREG8(DAC_INDEX + MGA1064_COL_PAL, lut[i * 8 + i / 4].blue >> 8); |
84 | } |
85 | /* Green has one more bit, so add padding with 0 for red and blue. */ |
86 | for (i = MGAG200_LUT_SIZE / 8; i < MGAG200_LUT_SIZE / 4; i++) { |
87 | WREG8(DAC_INDEX + MGA1064_COL_PAL, 0); |
88 | WREG8(DAC_INDEX + MGA1064_COL_PAL, lut[i * 4 + i / 16].green >> 8); |
89 | WREG8(DAC_INDEX + MGA1064_COL_PAL, 0); |
90 | } |
91 | break; |
92 | case DRM_FORMAT_RGB888: |
93 | case DRM_FORMAT_XRGB8888: |
94 | for (i = 0; i < MGAG200_LUT_SIZE; i++) { |
95 | WREG8(DAC_INDEX + MGA1064_COL_PAL, lut[i].red >> 8); |
96 | WREG8(DAC_INDEX + MGA1064_COL_PAL, lut[i].green >> 8); |
97 | WREG8(DAC_INDEX + MGA1064_COL_PAL, lut[i].blue >> 8); |
98 | } |
99 | break; |
100 | default: |
101 | drm_warn_once(&mdev->base, "Unsupported format %p4cc for gamma correction\n" , |
102 | &format->format); |
103 | break; |
104 | } |
105 | } |
106 | |
107 | static inline void mga_wait_vsync(struct mga_device *mdev) |
108 | { |
109 | unsigned long timeout = jiffies + HZ/10; |
110 | unsigned int status = 0; |
111 | |
112 | do { |
113 | status = RREG32(MGAREG_Status); |
114 | } while ((status & 0x08) && time_before(jiffies, timeout)); |
115 | timeout = jiffies + HZ/10; |
116 | status = 0; |
117 | do { |
118 | status = RREG32(MGAREG_Status); |
119 | } while (!(status & 0x08) && time_before(jiffies, timeout)); |
120 | } |
121 | |
122 | static inline void mga_wait_busy(struct mga_device *mdev) |
123 | { |
124 | unsigned long timeout = jiffies + HZ; |
125 | unsigned int status = 0; |
126 | do { |
127 | status = RREG8(MGAREG_Status + 2); |
128 | } while ((status & 0x01) && time_before(jiffies, timeout)); |
129 | } |
130 | |
131 | /* |
132 | * This is how the framebuffer base address is stored in g200 cards: |
133 | * * Assume @offset is the gpu_addr variable of the framebuffer object |
134 | * * Then addr is the number of _pixels_ (not bytes) from the start of |
135 | * VRAM to the first pixel we want to display. (divided by 2 for 32bit |
136 | * framebuffers) |
137 | * * addr is stored in the CRTCEXT0, CRTCC and CRTCD registers |
138 | * addr<20> -> CRTCEXT0<6> |
139 | * addr<19-16> -> CRTCEXT0<3-0> |
140 | * addr<15-8> -> CRTCC<7-0> |
141 | * addr<7-0> -> CRTCD<7-0> |
142 | * |
143 | * CRTCEXT0 has to be programmed last to trigger an update and make the |
144 | * new addr variable take effect. |
145 | */ |
146 | static void mgag200_set_startadd(struct mga_device *mdev, |
147 | unsigned long offset) |
148 | { |
149 | struct drm_device *dev = &mdev->base; |
150 | u32 startadd; |
151 | u8 crtcc, crtcd, crtcext0; |
152 | |
153 | startadd = offset / 8; |
154 | |
155 | if (startadd > 0) |
156 | drm_WARN_ON_ONCE(dev, mdev->info->bug_no_startadd); |
157 | |
158 | /* |
159 | * Can't store addresses any higher than that, but we also |
160 | * don't have more than 16 MiB of memory, so it should be fine. |
161 | */ |
162 | drm_WARN_ON(dev, startadd > 0x1fffff); |
163 | |
164 | RREG_ECRT(0x00, crtcext0); |
165 | |
166 | crtcc = (startadd >> 8) & 0xff; |
167 | crtcd = startadd & 0xff; |
168 | crtcext0 &= 0xb0; |
169 | crtcext0 |= ((startadd >> 14) & BIT(6)) | |
170 | ((startadd >> 16) & 0x0f); |
171 | |
172 | WREG_CRT(0x0c, crtcc); |
173 | WREG_CRT(0x0d, crtcd); |
174 | WREG_ECRT(0x00, crtcext0); |
175 | } |
176 | |
177 | void mgag200_init_registers(struct mga_device *mdev) |
178 | { |
179 | u8 crtc11, misc; |
180 | |
181 | WREG_SEQ(2, 0x0f); |
182 | WREG_SEQ(3, 0x00); |
183 | WREG_SEQ(4, 0x0e); |
184 | |
185 | WREG_CRT(10, 0); |
186 | WREG_CRT(11, 0); |
187 | WREG_CRT(12, 0); |
188 | WREG_CRT(13, 0); |
189 | WREG_CRT(14, 0); |
190 | WREG_CRT(15, 0); |
191 | |
192 | RREG_CRT(0x11, crtc11); |
193 | crtc11 &= ~(MGAREG_CRTC11_CRTCPROTECT | |
194 | MGAREG_CRTC11_VINTEN | |
195 | MGAREG_CRTC11_VINTCLR); |
196 | WREG_CRT(0x11, crtc11); |
197 | |
198 | misc = RREG8(MGA_MISC_IN); |
199 | misc |= MGAREG_MISC_IOADSEL; |
200 | WREG8(MGA_MISC_OUT, misc); |
201 | } |
202 | |
203 | void mgag200_set_mode_regs(struct mga_device *mdev, const struct drm_display_mode *mode) |
204 | { |
205 | const struct mgag200_device_info *info = mdev->info; |
206 | unsigned int hdisplay, hsyncstart, hsyncend, htotal; |
207 | unsigned int vdisplay, vsyncstart, vsyncend, vtotal; |
208 | u8 misc, crtcext1, crtcext2, crtcext5; |
209 | |
210 | hdisplay = mode->hdisplay / 8 - 1; |
211 | hsyncstart = mode->hsync_start / 8 - 1; |
212 | hsyncend = mode->hsync_end / 8 - 1; |
213 | htotal = mode->htotal / 8 - 1; |
214 | |
215 | /* Work around hardware quirk */ |
216 | if ((htotal & 0x07) == 0x06 || (htotal & 0x07) == 0x04) |
217 | htotal++; |
218 | |
219 | vdisplay = mode->vdisplay - 1; |
220 | vsyncstart = mode->vsync_start - 1; |
221 | vsyncend = mode->vsync_end - 1; |
222 | vtotal = mode->vtotal - 2; |
223 | |
224 | misc = RREG8(MGA_MISC_IN); |
225 | |
226 | if (mode->flags & DRM_MODE_FLAG_NHSYNC) |
227 | misc |= MGAREG_MISC_HSYNCPOL; |
228 | else |
229 | misc &= ~MGAREG_MISC_HSYNCPOL; |
230 | |
231 | if (mode->flags & DRM_MODE_FLAG_NVSYNC) |
232 | misc |= MGAREG_MISC_VSYNCPOL; |
233 | else |
234 | misc &= ~MGAREG_MISC_VSYNCPOL; |
235 | |
236 | crtcext1 = (((htotal - 4) & 0x100) >> 8) | |
237 | ((hdisplay & 0x100) >> 7) | |
238 | ((hsyncstart & 0x100) >> 6) | |
239 | (htotal & 0x40); |
240 | if (info->has_vidrst) |
241 | crtcext1 |= MGAREG_CRTCEXT1_VRSTEN | |
242 | MGAREG_CRTCEXT1_HRSTEN; |
243 | |
244 | crtcext2 = ((vtotal & 0xc00) >> 10) | |
245 | ((vdisplay & 0x400) >> 8) | |
246 | ((vdisplay & 0xc00) >> 7) | |
247 | ((vsyncstart & 0xc00) >> 5) | |
248 | ((vdisplay & 0x400) >> 3); |
249 | crtcext5 = 0x00; |
250 | |
251 | WREG_CRT(0, htotal - 4); |
252 | WREG_CRT(1, hdisplay); |
253 | WREG_CRT(2, hdisplay); |
254 | WREG_CRT(3, (htotal & 0x1F) | 0x80); |
255 | WREG_CRT(4, hsyncstart); |
256 | WREG_CRT(5, ((htotal & 0x20) << 2) | (hsyncend & 0x1F)); |
257 | WREG_CRT(6, vtotal & 0xFF); |
258 | WREG_CRT(7, ((vtotal & 0x100) >> 8) | |
259 | ((vdisplay & 0x100) >> 7) | |
260 | ((vsyncstart & 0x100) >> 6) | |
261 | ((vdisplay & 0x100) >> 5) | |
262 | ((vdisplay & 0x100) >> 4) | /* linecomp */ |
263 | ((vtotal & 0x200) >> 4) | |
264 | ((vdisplay & 0x200) >> 3) | |
265 | ((vsyncstart & 0x200) >> 2)); |
266 | WREG_CRT(9, ((vdisplay & 0x200) >> 4) | |
267 | ((vdisplay & 0x200) >> 3)); |
268 | WREG_CRT(16, vsyncstart & 0xFF); |
269 | WREG_CRT(17, (vsyncend & 0x0F) | 0x20); |
270 | WREG_CRT(18, vdisplay & 0xFF); |
271 | WREG_CRT(20, 0); |
272 | WREG_CRT(21, vdisplay & 0xFF); |
273 | WREG_CRT(22, (vtotal + 1) & 0xFF); |
274 | WREG_CRT(23, 0xc3); |
275 | WREG_CRT(24, vdisplay & 0xFF); |
276 | |
277 | WREG_ECRT(0x01, crtcext1); |
278 | WREG_ECRT(0x02, crtcext2); |
279 | WREG_ECRT(0x05, crtcext5); |
280 | |
281 | WREG8(MGA_MISC_OUT, misc); |
282 | } |
283 | |
284 | static u8 mgag200_get_bpp_shift(const struct drm_format_info *format) |
285 | { |
286 | static const u8 bpp_shift[] = {0, 1, 0, 2}; |
287 | |
288 | return bpp_shift[format->cpp[0] - 1]; |
289 | } |
290 | |
291 | /* |
292 | * Calculates the HW offset value from the framebuffer's pitch. The |
293 | * offset is a multiple of the pixel size and depends on the display |
294 | * format. |
295 | */ |
296 | static u32 mgag200_calculate_offset(struct mga_device *mdev, |
297 | const struct drm_framebuffer *fb) |
298 | { |
299 | u32 offset = fb->pitches[0] / fb->format->cpp[0]; |
300 | u8 bppshift = mgag200_get_bpp_shift(format: fb->format); |
301 | |
302 | if (fb->format->cpp[0] * 8 == 24) |
303 | offset = (offset * 3) >> (4 - bppshift); |
304 | else |
305 | offset = offset >> (4 - bppshift); |
306 | |
307 | return offset; |
308 | } |
309 | |
310 | static void mgag200_set_offset(struct mga_device *mdev, |
311 | const struct drm_framebuffer *fb) |
312 | { |
313 | u8 crtc13, crtcext0; |
314 | u32 offset = mgag200_calculate_offset(mdev, fb); |
315 | |
316 | RREG_ECRT(0, crtcext0); |
317 | |
318 | crtc13 = offset & 0xff; |
319 | |
320 | crtcext0 &= ~MGAREG_CRTCEXT0_OFFSET_MASK; |
321 | crtcext0 |= (offset >> 4) & MGAREG_CRTCEXT0_OFFSET_MASK; |
322 | |
323 | WREG_CRT(0x13, crtc13); |
324 | WREG_ECRT(0x00, crtcext0); |
325 | } |
326 | |
327 | void mgag200_set_format_regs(struct mga_device *mdev, const struct drm_format_info *format) |
328 | { |
329 | struct drm_device *dev = &mdev->base; |
330 | unsigned int bpp, bppshift, scale; |
331 | u8 crtcext3, xmulctrl; |
332 | |
333 | bpp = format->cpp[0] * 8; |
334 | |
335 | bppshift = mgag200_get_bpp_shift(format); |
336 | switch (bpp) { |
337 | case 24: |
338 | scale = ((1 << bppshift) * 3) - 1; |
339 | break; |
340 | default: |
341 | scale = (1 << bppshift) - 1; |
342 | break; |
343 | } |
344 | |
345 | RREG_ECRT(3, crtcext3); |
346 | |
347 | switch (bpp) { |
348 | case 8: |
349 | xmulctrl = MGA1064_MUL_CTL_8bits; |
350 | break; |
351 | case 16: |
352 | if (format->depth == 15) |
353 | xmulctrl = MGA1064_MUL_CTL_15bits; |
354 | else |
355 | xmulctrl = MGA1064_MUL_CTL_16bits; |
356 | break; |
357 | case 24: |
358 | xmulctrl = MGA1064_MUL_CTL_24bits; |
359 | break; |
360 | case 32: |
361 | xmulctrl = MGA1064_MUL_CTL_32_24bits; |
362 | break; |
363 | default: |
364 | /* BUG: We should have caught this problem already. */ |
365 | drm_WARN_ON(dev, "invalid format depth\n" ); |
366 | return; |
367 | } |
368 | |
369 | crtcext3 &= ~GENMASK(2, 0); |
370 | crtcext3 |= scale; |
371 | |
372 | WREG_DAC(MGA1064_MUL_CTL, xmulctrl); |
373 | |
374 | WREG_GFX(0, 0x00); |
375 | WREG_GFX(1, 0x00); |
376 | WREG_GFX(2, 0x00); |
377 | WREG_GFX(3, 0x00); |
378 | WREG_GFX(4, 0x00); |
379 | WREG_GFX(5, 0x40); |
380 | /* GCTL6 should be 0x05, but we configure memmapsl to 0xb8000 (text mode), |
381 | * so that it doesn't hang when running kexec/kdump on G200_SE rev42. |
382 | */ |
383 | WREG_GFX(6, 0x0d); |
384 | WREG_GFX(7, 0x0f); |
385 | WREG_GFX(8, 0x0f); |
386 | |
387 | WREG_ECRT(3, crtcext3); |
388 | } |
389 | |
390 | void mgag200_enable_display(struct mga_device *mdev) |
391 | { |
392 | u8 seq0, crtcext1; |
393 | |
394 | RREG_SEQ(0x00, seq0); |
395 | seq0 |= MGAREG_SEQ0_SYNCRST | |
396 | MGAREG_SEQ0_ASYNCRST; |
397 | WREG_SEQ(0x00, seq0); |
398 | |
399 | /* |
400 | * TODO: replace busy waiting with vblank IRQ; put |
401 | * msleep(50) before changing SCROFF |
402 | */ |
403 | mga_wait_vsync(mdev); |
404 | mga_wait_busy(mdev); |
405 | |
406 | RREG_ECRT(0x01, crtcext1); |
407 | crtcext1 &= ~MGAREG_CRTCEXT1_VSYNCOFF; |
408 | crtcext1 &= ~MGAREG_CRTCEXT1_HSYNCOFF; |
409 | WREG_ECRT(0x01, crtcext1); |
410 | } |
411 | |
412 | static void mgag200_disable_display(struct mga_device *mdev) |
413 | { |
414 | u8 seq0, crtcext1; |
415 | |
416 | RREG_SEQ(0x00, seq0); |
417 | seq0 &= ~MGAREG_SEQ0_SYNCRST; |
418 | WREG_SEQ(0x00, seq0); |
419 | |
420 | /* |
421 | * TODO: replace busy waiting with vblank IRQ; put |
422 | * msleep(50) before changing SCROFF |
423 | */ |
424 | mga_wait_vsync(mdev); |
425 | mga_wait_busy(mdev); |
426 | |
427 | RREG_ECRT(0x01, crtcext1); |
428 | crtcext1 |= MGAREG_CRTCEXT1_VSYNCOFF | |
429 | MGAREG_CRTCEXT1_HSYNCOFF; |
430 | WREG_ECRT(0x01, crtcext1); |
431 | } |
432 | |
433 | static void mgag200_handle_damage(struct mga_device *mdev, const struct iosys_map *vmap, |
434 | struct drm_framebuffer *fb, struct drm_rect *clip) |
435 | { |
436 | struct iosys_map dst = IOSYS_MAP_INIT_VADDR_IOMEM(mdev->vram); |
437 | |
438 | iosys_map_incr(map: &dst, incr: drm_fb_clip_offset(pitch: fb->pitches[0], format: fb->format, clip)); |
439 | drm_fb_memcpy(dst: &dst, dst_pitch: fb->pitches, src: vmap, fb, clip); |
440 | |
441 | /* Flushing the cache greatly improves latency on x86_64 */ |
442 | #if defined(CONFIG_DRM_MGAG200_IOBURST_WORKAROUND) |
443 | if (!vmap->is_iomem) |
444 | drm_clflush_virt_range(vmap->vaddr + clip->y1 * fb->pitches[0], |
445 | drm_rect_height(clip) * fb->pitches[0]); |
446 | #endif |
447 | } |
448 | |
449 | /* |
450 | * Primary plane |
451 | */ |
452 | |
453 | const uint32_t mgag200_primary_plane_formats[] = { |
454 | DRM_FORMAT_XRGB8888, |
455 | DRM_FORMAT_RGB565, |
456 | DRM_FORMAT_RGB888, |
457 | }; |
458 | |
459 | const size_t mgag200_primary_plane_formats_size = ARRAY_SIZE(mgag200_primary_plane_formats); |
460 | |
461 | const uint64_t mgag200_primary_plane_fmtmods[] = { |
462 | DRM_FORMAT_MOD_LINEAR, |
463 | DRM_FORMAT_MOD_INVALID |
464 | }; |
465 | |
466 | int mgag200_primary_plane_helper_atomic_check(struct drm_plane *plane, |
467 | struct drm_atomic_state *new_state) |
468 | { |
469 | struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state: new_state, plane); |
470 | struct drm_framebuffer *new_fb = new_plane_state->fb; |
471 | struct drm_framebuffer *fb = NULL; |
472 | struct drm_crtc *new_crtc = new_plane_state->crtc; |
473 | struct drm_crtc_state *new_crtc_state = NULL; |
474 | struct mgag200_crtc_state *new_mgag200_crtc_state; |
475 | int ret; |
476 | |
477 | if (new_crtc) |
478 | new_crtc_state = drm_atomic_get_new_crtc_state(state: new_state, crtc: new_crtc); |
479 | |
480 | ret = drm_atomic_helper_check_plane_state(plane_state: new_plane_state, crtc_state: new_crtc_state, |
481 | DRM_PLANE_NO_SCALING, |
482 | DRM_PLANE_NO_SCALING, |
483 | can_position: false, can_update_disabled: true); |
484 | if (ret) |
485 | return ret; |
486 | else if (!new_plane_state->visible) |
487 | return 0; |
488 | |
489 | if (plane->state) |
490 | fb = plane->state->fb; |
491 | |
492 | if (!fb || (fb->format != new_fb->format)) |
493 | new_crtc_state->mode_changed = true; /* update PLL settings */ |
494 | |
495 | new_mgag200_crtc_state = to_mgag200_crtc_state(base: new_crtc_state); |
496 | new_mgag200_crtc_state->format = new_fb->format; |
497 | |
498 | return 0; |
499 | } |
500 | |
501 | void mgag200_primary_plane_helper_atomic_update(struct drm_plane *plane, |
502 | struct drm_atomic_state *old_state) |
503 | { |
504 | struct drm_device *dev = plane->dev; |
505 | struct mga_device *mdev = to_mga_device(dev); |
506 | struct drm_plane_state *plane_state = plane->state; |
507 | struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state: old_state, plane); |
508 | struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state: plane_state); |
509 | struct drm_framebuffer *fb = plane_state->fb; |
510 | struct drm_atomic_helper_damage_iter iter; |
511 | struct drm_rect damage; |
512 | |
513 | drm_atomic_helper_damage_iter_init(iter: &iter, old_state: old_plane_state, new_state: plane_state); |
514 | drm_atomic_for_each_plane_damage(&iter, &damage) { |
515 | mgag200_handle_damage(mdev, vmap: shadow_plane_state->data, fb, clip: &damage); |
516 | } |
517 | |
518 | /* Always scanout image at VRAM offset 0 */ |
519 | mgag200_set_startadd(mdev, offset: (u32)0); |
520 | mgag200_set_offset(mdev, fb); |
521 | } |
522 | |
523 | void mgag200_primary_plane_helper_atomic_enable(struct drm_plane *plane, |
524 | struct drm_atomic_state *state) |
525 | { |
526 | struct drm_device *dev = plane->dev; |
527 | struct mga_device *mdev = to_mga_device(dev); |
528 | u8 seq1; |
529 | |
530 | RREG_SEQ(0x01, seq1); |
531 | seq1 &= ~MGAREG_SEQ1_SCROFF; |
532 | WREG_SEQ(0x01, seq1); |
533 | msleep(msecs: 20); |
534 | } |
535 | |
536 | void mgag200_primary_plane_helper_atomic_disable(struct drm_plane *plane, |
537 | struct drm_atomic_state *old_state) |
538 | { |
539 | struct drm_device *dev = plane->dev; |
540 | struct mga_device *mdev = to_mga_device(dev); |
541 | u8 seq1; |
542 | |
543 | RREG_SEQ(0x01, seq1); |
544 | seq1 |= MGAREG_SEQ1_SCROFF; |
545 | WREG_SEQ(0x01, seq1); |
546 | msleep(msecs: 20); |
547 | } |
548 | |
549 | /* |
550 | * CRTC |
551 | */ |
552 | |
553 | enum drm_mode_status mgag200_crtc_helper_mode_valid(struct drm_crtc *crtc, |
554 | const struct drm_display_mode *mode) |
555 | { |
556 | struct mga_device *mdev = to_mga_device(dev: crtc->dev); |
557 | const struct mgag200_device_info *info = mdev->info; |
558 | |
559 | /* |
560 | * Some devices have additional limits on the size of the |
561 | * display mode. |
562 | */ |
563 | if (mode->hdisplay > info->max_hdisplay) |
564 | return MODE_VIRTUAL_X; |
565 | if (mode->vdisplay > info->max_vdisplay) |
566 | return MODE_VIRTUAL_Y; |
567 | |
568 | if ((mode->hdisplay % 8) != 0 || (mode->hsync_start % 8) != 0 || |
569 | (mode->hsync_end % 8) != 0 || (mode->htotal % 8) != 0) { |
570 | return MODE_H_ILLEGAL; |
571 | } |
572 | |
573 | if (mode->crtc_hdisplay > 2048 || mode->crtc_hsync_start > 4096 || |
574 | mode->crtc_hsync_end > 4096 || mode->crtc_htotal > 4096 || |
575 | mode->crtc_vdisplay > 2048 || mode->crtc_vsync_start > 4096 || |
576 | mode->crtc_vsync_end > 4096 || mode->crtc_vtotal > 4096) { |
577 | return MODE_BAD; |
578 | } |
579 | |
580 | return MODE_OK; |
581 | } |
582 | |
583 | int mgag200_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *new_state) |
584 | { |
585 | struct drm_device *dev = crtc->dev; |
586 | struct mga_device *mdev = to_mga_device(dev); |
587 | const struct mgag200_device_funcs *funcs = mdev->funcs; |
588 | struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(state: new_state, crtc); |
589 | struct drm_property_blob *new_gamma_lut = new_crtc_state->gamma_lut; |
590 | int ret; |
591 | |
592 | if (!new_crtc_state->enable) |
593 | return 0; |
594 | |
595 | ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state: new_crtc_state); |
596 | if (ret) |
597 | return ret; |
598 | |
599 | if (new_crtc_state->mode_changed) { |
600 | if (funcs->pixpllc_atomic_check) { |
601 | ret = funcs->pixpllc_atomic_check(crtc, new_state); |
602 | if (ret) |
603 | return ret; |
604 | } |
605 | } |
606 | |
607 | if (new_crtc_state->color_mgmt_changed && new_gamma_lut) { |
608 | if (new_gamma_lut->length != MGAG200_LUT_SIZE * sizeof(struct drm_color_lut)) { |
609 | drm_dbg(dev, "Wrong size for gamma_lut %zu\n" , new_gamma_lut->length); |
610 | return -EINVAL; |
611 | } |
612 | } |
613 | |
614 | return 0; |
615 | } |
616 | |
617 | void mgag200_crtc_helper_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *old_state) |
618 | { |
619 | struct drm_crtc_state *crtc_state = crtc->state; |
620 | struct mgag200_crtc_state *mgag200_crtc_state = to_mgag200_crtc_state(base: crtc_state); |
621 | struct drm_device *dev = crtc->dev; |
622 | struct mga_device *mdev = to_mga_device(dev); |
623 | |
624 | if (crtc_state->enable && crtc_state->color_mgmt_changed) { |
625 | const struct drm_format_info *format = mgag200_crtc_state->format; |
626 | |
627 | if (crtc_state->gamma_lut) |
628 | mgag200_crtc_set_gamma(mdev, format, lut: crtc_state->gamma_lut->data); |
629 | else |
630 | mgag200_crtc_set_gamma_linear(mdev, format); |
631 | } |
632 | } |
633 | |
634 | void mgag200_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *old_state) |
635 | { |
636 | struct drm_device *dev = crtc->dev; |
637 | struct mga_device *mdev = to_mga_device(dev); |
638 | const struct mgag200_device_funcs *funcs = mdev->funcs; |
639 | struct drm_crtc_state *crtc_state = crtc->state; |
640 | struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; |
641 | struct mgag200_crtc_state *mgag200_crtc_state = to_mgag200_crtc_state(base: crtc_state); |
642 | const struct drm_format_info *format = mgag200_crtc_state->format; |
643 | |
644 | if (funcs->disable_vidrst) |
645 | funcs->disable_vidrst(mdev); |
646 | |
647 | mgag200_set_format_regs(mdev, format); |
648 | mgag200_set_mode_regs(mdev, mode: adjusted_mode); |
649 | |
650 | if (funcs->pixpllc_atomic_update) |
651 | funcs->pixpllc_atomic_update(crtc, old_state); |
652 | |
653 | if (crtc_state->gamma_lut) |
654 | mgag200_crtc_set_gamma(mdev, format, lut: crtc_state->gamma_lut->data); |
655 | else |
656 | mgag200_crtc_set_gamma_linear(mdev, format); |
657 | |
658 | mgag200_enable_display(mdev); |
659 | |
660 | if (funcs->enable_vidrst) |
661 | funcs->enable_vidrst(mdev); |
662 | } |
663 | |
664 | void mgag200_crtc_helper_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *old_state) |
665 | { |
666 | struct mga_device *mdev = to_mga_device(dev: crtc->dev); |
667 | const struct mgag200_device_funcs *funcs = mdev->funcs; |
668 | |
669 | if (funcs->disable_vidrst) |
670 | funcs->disable_vidrst(mdev); |
671 | |
672 | mgag200_disable_display(mdev); |
673 | |
674 | if (funcs->enable_vidrst) |
675 | funcs->enable_vidrst(mdev); |
676 | } |
677 | |
678 | void mgag200_crtc_reset(struct drm_crtc *crtc) |
679 | { |
680 | struct mgag200_crtc_state *mgag200_crtc_state; |
681 | |
682 | if (crtc->state) |
683 | crtc->funcs->atomic_destroy_state(crtc, crtc->state); |
684 | |
685 | mgag200_crtc_state = kzalloc(size: sizeof(*mgag200_crtc_state), GFP_KERNEL); |
686 | if (mgag200_crtc_state) |
687 | __drm_atomic_helper_crtc_reset(crtc, state: &mgag200_crtc_state->base); |
688 | else |
689 | __drm_atomic_helper_crtc_reset(crtc, NULL); |
690 | } |
691 | |
692 | struct drm_crtc_state *mgag200_crtc_atomic_duplicate_state(struct drm_crtc *crtc) |
693 | { |
694 | struct drm_crtc_state *crtc_state = crtc->state; |
695 | struct mgag200_crtc_state *mgag200_crtc_state = to_mgag200_crtc_state(base: crtc_state); |
696 | struct mgag200_crtc_state *new_mgag200_crtc_state; |
697 | |
698 | if (!crtc_state) |
699 | return NULL; |
700 | |
701 | new_mgag200_crtc_state = kzalloc(size: sizeof(*new_mgag200_crtc_state), GFP_KERNEL); |
702 | if (!new_mgag200_crtc_state) |
703 | return NULL; |
704 | __drm_atomic_helper_crtc_duplicate_state(crtc, state: &new_mgag200_crtc_state->base); |
705 | |
706 | new_mgag200_crtc_state->format = mgag200_crtc_state->format; |
707 | memcpy(&new_mgag200_crtc_state->pixpllc, &mgag200_crtc_state->pixpllc, |
708 | sizeof(new_mgag200_crtc_state->pixpllc)); |
709 | |
710 | return &new_mgag200_crtc_state->base; |
711 | } |
712 | |
713 | void mgag200_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state) |
714 | { |
715 | struct mgag200_crtc_state *mgag200_crtc_state = to_mgag200_crtc_state(base: crtc_state); |
716 | |
717 | __drm_atomic_helper_crtc_destroy_state(state: &mgag200_crtc_state->base); |
718 | kfree(objp: mgag200_crtc_state); |
719 | } |
720 | |
721 | /* |
722 | * Connector |
723 | */ |
724 | |
725 | int mgag200_vga_connector_helper_get_modes(struct drm_connector *connector) |
726 | { |
727 | struct mga_device *mdev = to_mga_device(dev: connector->dev); |
728 | const struct drm_edid *drm_edid; |
729 | int count; |
730 | |
731 | /* |
732 | * Protect access to I/O registers from concurrent modesetting |
733 | * by acquiring the I/O-register lock. |
734 | */ |
735 | mutex_lock(&mdev->rmmio_lock); |
736 | |
737 | drm_edid = drm_edid_read(connector); |
738 | drm_edid_connector_update(connector, edid: drm_edid); |
739 | count = drm_edid_connector_add_modes(connector); |
740 | drm_edid_free(drm_edid); |
741 | |
742 | mutex_unlock(lock: &mdev->rmmio_lock); |
743 | |
744 | return count; |
745 | } |
746 | |
747 | /* |
748 | * Mode config |
749 | */ |
750 | |
751 | static void mgag200_mode_config_helper_atomic_commit_tail(struct drm_atomic_state *state) |
752 | { |
753 | struct mga_device *mdev = to_mga_device(dev: state->dev); |
754 | |
755 | /* |
756 | * Concurrent operations could possibly trigger a call to |
757 | * drm_connector_helper_funcs.get_modes by trying to read the |
758 | * display modes. Protect access to I/O registers by acquiring |
759 | * the I/O-register lock. |
760 | */ |
761 | mutex_lock(&mdev->rmmio_lock); |
762 | drm_atomic_helper_commit_tail(state); |
763 | mutex_unlock(lock: &mdev->rmmio_lock); |
764 | } |
765 | |
766 | static const struct drm_mode_config_helper_funcs mgag200_mode_config_helper_funcs = { |
767 | .atomic_commit_tail = mgag200_mode_config_helper_atomic_commit_tail, |
768 | }; |
769 | |
770 | /* Calculates a mode's required memory bandwidth (in KiB/sec). */ |
771 | static uint32_t mgag200_calculate_mode_bandwidth(const struct drm_display_mode *mode, |
772 | unsigned int bits_per_pixel) |
773 | { |
774 | uint32_t total_area, divisor; |
775 | uint64_t active_area, pixels_per_second, bandwidth; |
776 | uint64_t bytes_per_pixel = (bits_per_pixel + 7) / 8; |
777 | |
778 | divisor = 1024; |
779 | |
780 | if (!mode->htotal || !mode->vtotal || !mode->clock) |
781 | return 0; |
782 | |
783 | active_area = mode->hdisplay * mode->vdisplay; |
784 | total_area = mode->htotal * mode->vtotal; |
785 | |
786 | pixels_per_second = active_area * mode->clock * 1000; |
787 | do_div(pixels_per_second, total_area); |
788 | |
789 | bandwidth = pixels_per_second * bytes_per_pixel * 100; |
790 | do_div(bandwidth, divisor); |
791 | |
792 | return (uint32_t)bandwidth; |
793 | } |
794 | |
795 | static enum drm_mode_status mgag200_mode_config_mode_valid(struct drm_device *dev, |
796 | const struct drm_display_mode *mode) |
797 | { |
798 | static const unsigned int max_bpp = 4; // DRM_FORMAT_XRGB8888 |
799 | struct mga_device *mdev = to_mga_device(dev); |
800 | unsigned long fbsize, fbpages, max_fbpages; |
801 | const struct mgag200_device_info *info = mdev->info; |
802 | |
803 | max_fbpages = mdev->vram_available >> PAGE_SHIFT; |
804 | |
805 | fbsize = mode->hdisplay * mode->vdisplay * max_bpp; |
806 | fbpages = DIV_ROUND_UP(fbsize, PAGE_SIZE); |
807 | |
808 | if (fbpages > max_fbpages) |
809 | return MODE_MEM; |
810 | |
811 | /* |
812 | * Test the mode's required memory bandwidth if the device |
813 | * specifies a maximum. Not all devices do though. |
814 | */ |
815 | if (info->max_mem_bandwidth) { |
816 | uint32_t mode_bandwidth = mgag200_calculate_mode_bandwidth(mode, bits_per_pixel: max_bpp * 8); |
817 | |
818 | if (mode_bandwidth > (info->max_mem_bandwidth * 1024)) |
819 | return MODE_BAD; |
820 | } |
821 | |
822 | return MODE_OK; |
823 | } |
824 | |
825 | static const struct drm_mode_config_funcs mgag200_mode_config_funcs = { |
826 | .fb_create = drm_gem_fb_create_with_dirty, |
827 | .mode_valid = mgag200_mode_config_mode_valid, |
828 | .atomic_check = drm_atomic_helper_check, |
829 | .atomic_commit = drm_atomic_helper_commit, |
830 | }; |
831 | |
832 | int mgag200_mode_config_init(struct mga_device *mdev, resource_size_t vram_available) |
833 | { |
834 | struct drm_device *dev = &mdev->base; |
835 | int ret; |
836 | |
837 | mdev->vram_available = vram_available; |
838 | |
839 | ret = drmm_mode_config_init(dev); |
840 | if (ret) { |
841 | drm_err(dev, "drmm_mode_config_init() failed: %d\n" , ret); |
842 | return ret; |
843 | } |
844 | |
845 | dev->mode_config.max_width = MGAG200_MAX_FB_WIDTH; |
846 | dev->mode_config.max_height = MGAG200_MAX_FB_HEIGHT; |
847 | dev->mode_config.preferred_depth = 24; |
848 | dev->mode_config.funcs = &mgag200_mode_config_funcs; |
849 | dev->mode_config.helper_private = &mgag200_mode_config_helper_funcs; |
850 | |
851 | return 0; |
852 | } |
853 | |