; Gives a DCE4 display (Evergreen, Northern Islands, the PALM/SUMO IGPs) a ; desktop the size of the panel's own active area instead of the smaller one the ; firmware scales up to it. ; ; The CRTC keeps running exactly as the firmware left it. Timings, PLL, encoder ; and panel power are never touched - which is the whole point, because that is ; the part that leaves the panel dark when a full modeset goes wrong. Only the ; scanout surface, the viewport and the scaler's horizontal ratio change, and ; the kernel is told afterwards through SetScreen. ; ; With no command line the target is the panel's own active size, read back from ; the CRTC blanking registers. "width height" overrides it; the height must stay ; the panel's, because the vertical half of the scaler is not touched. Asking ; for the size the firmware used therefore puts everything back, so this undoes ; itself without a reboot. ; ; Offsets are from drivers/video/drm/radeon/evergreen_reg.h, except for two that ; the tree does not declare - DCE4 drives its scaler through the ATOM EnableScaler ; table rather than named registers: ; ; 0x6D80 the horizontal ratio, source over destination. A Wrestler running a ; 1024 wide surface on a 1366 wide panel reads 02FFA000 here, and ; round(1024 * 2^19 / 1366) shl 13 is 02FFA000 exactly. 1:1 is ; 04000000. ; 0x6D88 a second copy of the scaler's source size, in the same shape as ; VIEWPORT_SIZE. Read as 04000300 next to a 1024x768 viewport. ; ; The blanking layout is read the same way: H_BLANK holds the blank start in the ; low half and the blank end in the high half, so the active width is their ; difference. 005405AA on that machine gives 1450 - 84 = 1366, against an ; H_TOTAL of 1465, and the vertical pair gives 786 - 18 = 768. format PE DLL native 0.05 entry START DEBUG = 1 __DEBUG__ = 1 __DEBUG_LEVEL__ = 1 include '../struct.inc' GRPH_CONTROL = 0x6804 GRPH_DEPTH_32BPP = 2 ; bits 1:0 GRPH_ARRAY_LINEAR_ALIGNED = 1 ; bits 22:20, anything above is tiled GRPH_SURF_ADDR = 0x6810 GRPH_PITCH = 0x6818 ; in pixels, not bytes GRPH_X_START = 0x682C GRPH_Y_START = 0x6830 GRPH_X_END = 0x6834 GRPH_Y_END = 0x6838 GRPH_UPDATE = 0x6844 GRPH_UPDATE_LOCK = 1 shl 16 DESKTOP_HEIGHT = 0x6B04 VIEWPORT_START = 0x6D70 VIEWPORT_SIZE = 0x6D74 SCL_H_RATIO = 0x6D80 SCL_SRC_SIZE = 0x6D88 CRTC_H_BLANK = 0x6E04 CRTC_V_BLANK = 0x6E34 CRTC_CONTROL = 0x6E70 CRTC_UPDATE_LOCK = 0x6ED4 CRTC_MASTER_EN = 1 CRTC_STEP = 0xC00 BLANK_MASK = 0x1FFF ; the ratio keeps nineteen fractional bits and reads back with the rest clear SCL_RATIO_MASK = 0x1FFF ; the register aperture is 64K or 256K depending on D0F0x64_x1C[RegApSize] ; (BKDG 43170 rev 3.13, D1F0x18), and everything here lives below 0x8000 RMMIO_SIZE = 0x10000 ; the kernel maps four large pages of framebuffer at LFB_BASE, see ; kernel/trunk/video/framebuffer.inc LFB_LIMIT = 0x1000000 ; display_t, kernel/trunk/const.inc DISPLAY_WIDTH = 0x08 DISPLAY_HEIGHT = 0x0C DISPLAY_BPP = 0x10 DISPLAY_PITCH = 0x1C section '.flat' code readable writable executable include '../proc32.inc' include '../peimport.inc' include '../fdo.inc' include '../macros.inc' proc START c uses ebx esi edi, reason:dword, cmdline:dword cmp [reason], DRV_ENTRY jnz .fail invoke GetDisplay mov ecx, [eax + DISPLAY_BPP] cmp ecx, 32 jnz .not_32bpp shr ecx, 3 mov [bytes_pp], ecx mov esi, [cmdline] test esi, esi jz .no_cmdline call parse_dec test eax, eax jz .no_cmdline mov [target_w], eax call parse_dec test eax, eax jz .bad_cmdline mov [target_h], eax .no_cmdline: ; an integrated display controller sits on bus 0 as function 0 of its device xor ebx, ebx .scan: push 0 push ebx push 0 invoke PciRead32 cmp ax, 0x1002 jnz .next_dev push 8 push ebx push 0 invoke PciRead32 shr eax, 16 cmp ax, 0x0300 jz .found .next_dev: add ebx, 8 cmp ebx, 0x100 jb .scan DEBUGF 1, "radmode: no AMD display controller on bus 0\n" jmp .fail ; D1F0x18 keeps that offset whether the BARs are 32 or 64 bit; only its high ; half moves to D1F0x1C .found: push 0x18 push ebx push 0 invoke PciRead32 mov edi, eax and edi, not 0xF test eax, 4 jz .have_bar push 0x1C push ebx push 0 invoke PciRead32 test eax, eax jnz .above_4g .have_bar: push 0x1B push RMMIO_SIZE push edi invoke MapIoMem test eax, eax jz .no_map mov esi, eax xor ebx, ebx .find_crtc: mov edx, ebx imul edx, CRTC_STEP mov eax, [esi + edx + CRTC_CONTROL] cmp eax, -1 jz .next_crtc test eax, CRTC_MASTER_EN jnz .got_crtc .next_crtc: inc ebx cmp ebx, 2 jb .find_crtc DEBUGF 1, "radmode: no CRTC is running\n" jmp .unmap .got_crtc: mov [crtc_off], edx ; a pitch in pixels and a widened surface mean nothing on a tiled one mov eax, [esi + edx + GRPH_CONTROL] mov ecx, eax and ecx, 3 cmp ecx, GRPH_DEPTH_32BPP jnz .not_linear32 shr eax, 20 and eax, 7 cmp eax, GRPH_ARRAY_LINEAR_ALIGNED ja .not_linear32 mov eax, [esi + edx + CRTC_H_BLANK] mov ecx, eax and ecx, BLANK_MASK ; blank start shr eax, 16 and eax, BLANK_MASK ; blank end sub ecx, eax mov [panel_w], ecx mov eax, [esi + edx + CRTC_V_BLANK] mov ecx, eax and ecx, BLANK_MASK shr eax, 16 and eax, BLANK_MASK sub ecx, eax mov [panel_h], ecx DEBUGF 1, "radmode: CRTC %u panel %ux%u\n", ebx, [panel_w], [panel_h] ; the blanking fields are thirteen bits wide, so a difference outside that range ; means the pair was not read as a blank start and a blank end at all mov eax, [panel_w] dec eax cmp eax, BLANK_MASK jae .no_timing mov eax, [panel_h] dec eax cmp eax, BLANK_MASK jae .no_timing mov eax, [panel_w] mov ecx, [panel_h] ; no command line means the panel's own size cmp [target_w], 0 jnz .have_target mov [target_w], eax mov [target_h], ecx .have_target: DEBUGF 1, "radmode: target %ux%u\n", [target_w], [target_h] mov eax, [target_w] cmp eax, [panel_w] ja .too_wide ; only the horizontal ratio is understood, so the height has to stay the ; panel's own mov eax, [target_h] cmp eax, [panel_h] jnz .bad_height ; 256 byte alignment is what the surface wants, and the pitch register counts ; pixels mov eax, [target_w] mul dword [bytes_pp] add eax, 255 and eax, not 255 mov [pitch_bytes], eax xor edx, edx div dword [bytes_pp] mov [pitch_px], eax mov eax, [pitch_bytes] mul dword [target_h] test edx, edx jnz .no_room cmp eax, LFB_LIMIT ja .no_room ; source over destination, and the hardware keeps only the top nineteen bits of ; the fraction mov eax, [target_w] mov edx, eax shr edx, 6 shl eax, 26 div dword [panel_w] and eax, not SCL_RATIO_MASK mov [scl_ratio], eax DEBUGF 1, "radmode: pitch %u px, scaler ratio %x\n", [pitch_px], [scl_ratio] mov edx, [crtc_off] mov dword [esi + edx + CRTC_UPDATE_LOCK], 1 mov eax, [esi + edx + GRPH_UPDATE] or eax, GRPH_UPDATE_LOCK mov [esi + edx + GRPH_UPDATE], eax mov dword [esi + edx + GRPH_X_START], 0 mov dword [esi + edx + GRPH_Y_START], 0 mov eax, [target_w] mov [esi + edx + GRPH_X_END], eax mov eax, [target_h] mov [esi + edx + GRPH_Y_END], eax mov [esi + edx + DESKTOP_HEIGHT], eax mov eax, [pitch_px] mov [esi + edx + GRPH_PITCH], eax mov eax, [target_w] shl eax, 16 or eax, [target_h] mov dword [esi + edx + VIEWPORT_START], 0 mov [esi + edx + VIEWPORT_SIZE], eax mov [esi + edx + SCL_SRC_SIZE], eax mov eax, [scl_ratio] mov [esi + edx + SCL_H_RATIO], eax ; the surface registers latch on a write to the surface address; write back the ; one that is already there mov eax, [esi + edx + GRPH_SURF_ADDR] mov [esi + edx + GRPH_SURF_ADDR], eax mov eax, [esi + edx + GRPH_UPDATE] and eax, not GRPH_UPDATE_LOCK mov [esi + edx + GRPH_UPDATE], eax mov dword [esi + edx + CRTC_UPDATE_LOCK], 0 DEBUGF 1, "radmode: armed with SURF %x\n", [esi + edx + GRPH_SURF_ADDR] invoke FreeKernelSpace, esi ; and only now the kernel, so that its redraw lands on a display engine that is ; already showing the new size invoke GetDisplay mov ecx, [target_w] mov [eax + DISPLAY_WIDTH], ecx mov ecx, [target_h] mov [eax + DISPLAY_HEIGHT], ecx mov eax, [target_w] dec eax mov edx, [target_h] dec edx mov ecx, [pitch_bytes] invoke SetScreen DEBUGF 1, "radmode: kernel told, pitch %u bytes\n", [pitch_bytes] jmp .done .not_linear32: DEBUGF 1, "radmode: the surface is not linear 32 bit, GRPH_CONTROL %x\n", \ [esi + edx + GRPH_CONTROL] jmp .unmap .no_timing: DEBUGF 1, "radmode: the CRTC reports no active area\n" jmp .unmap .too_wide: DEBUGF 1, "radmode: %u is wider than the panel %u\n", \ [target_w], [panel_w] jmp .unmap .bad_height: DEBUGF 1, "radmode: height must stay %u, the scaler is not touched\n", \ [panel_h] jmp .unmap .no_room: DEBUGF 1, "radmode: %u bytes a line does not fit the mapped framebuffer\n", \ [pitch_bytes] .unmap: invoke FreeKernelSpace, esi jmp .done .not_32bpp: DEBUGF 1, "radmode: the screen is not 32 bits per pixel\n" jmp .done .bad_cmdline: DEBUGF 1, "radmode: command line needs a width and a height\n" jmp .done .above_4g: DEBUGF 1, "radmode: registers are above 4G, out of reach\n" jmp .done .no_map: DEBUGF 1, "radmode: cannot map the register aperture\n" ; never register a service: this is a one shot, so report failure and let the ; kernel unload us again .done: .fail: xor eax, eax ret endp ; esi - the text, left past the number; eax - the value, zero if there is none parse_dec: xor eax, eax .spaces: cmp byte [esi], ' ' jnz .digits inc esi jmp .spaces .digits: movzx ecx, byte [esi] sub ecx, '0' cmp ecx, 9 ja .done lea eax, [eax*4 + eax] lea eax, [eax*2 + ecx] inc esi jmp .digits .done: ret align 4 target_w dd 0 target_h dd 0 panel_w dd ? panel_h dd ? pitch_px dd ? pitch_bytes dd ? scl_ratio dd ? crtc_off dd ? bytes_pp dd ? ; without a relocation table every absolute address stays at the link time base ; and the kernel loads us somewhere else entirely data fixups end data include_debug_strings