Hardware/AV Encoder
The A/V encoder is located on the underside of the Wii main board; it is communicated with via I2C.
See http://www.gc-linux.org/wiki/AVE-RVL (archived) for additional information.
Registers description
Here is a more complete description of the registers that can be modified by the software. This is heavily based on the disassembly and analysis of some original code with help of left symbols, there is therefore still unknown features and the whole description should be handled with caution.
Register 00h
address | size | read/write | description |
---|---|---|---|
0x00 | 1 | W | A/V timings ? |
bit(s) | description |
---|---|
0 | 0 = default value, 1 for D-Terminal (?) |
Register 01h
address | size | read/write | description |
---|---|---|---|
0x01 | 1 | W | Video Output configuration |
bit(s) | description |
---|---|
5 | YUV output (0: disabled 1: enabled) |
0-1 | color encoding (0: NTSC, 1:MPAL, 2:PAL, 3:DEBUG ?) |
YUV output is typically enabled only when component video cable is detected.
Register 02h
address | size | read/write | description |
---|---|---|---|
0x02 | 1 | W | Vertical blanking interval (VBI) control ? |
bit(s) | description |
---|---|
2 | unknown (1: default value, 0 for D-Terminal ?) |
1 | unknown (1: default value, 0 for D-Terminal ?) |
0 | unknown (1: default value, 0 for D-Terminal ?) |
Register 03h
address | size | read/write | description |
---|---|---|---|
0x03 | 1 | W | Composite Video Trap Filter control |
bit(s) | description |
---|---|
1 | 1: enabled, 0: disabled |
A Trap filter is generally used to improve Luma/Chroma separation in the composite video signal. When disabled, the video signal is unfiltered, which sometimes produces visual artefacts such as color bleeding. This register does not seem to affect RGB, S-Video or YUV output.
Register 04h
address | size | read/write | description |
---|---|---|---|
0x04 | 1 | R/W | A/V output control |
bit(s) | description |
---|---|
1 | 1: enabled, 0: disabled |
A/V output is typically disabled during register initialization.
Registers 05h & 06h
address | size | read/write | description |
---|---|---|---|
0x05 | 2 | W | CGMS protection ? |
bit(s) | description |
---|---|
8-15 | unknown (0: default value) |
2-5 | unknown (0: default value) |
0-1 | unknown (0: default value) |
Registers 08h & 09h
address | size | read/write | description |
---|---|---|---|
0x08 | 2 | W | WSS (Widescreen signaling) ? |
bit(s) | description |
---|---|
11-13 | unknown (0: default value) |
8-10 | unknown (0: default value) |
4-7 | unknown (0: default value) |
0-3 | unknown (0: default value) |
Register 0Ah
address | size | read/write | description |
---|---|---|---|
0x0A | 1 | W | RGB color output control (overdrive ?) |
bit(s) | description |
---|---|
1-7 | unknown (1: default value) |
0 | 0: disabled, 1: enabled |
This is apparently only enabled in DEBUG mode (Reg $1 = 3).
Registers 10h - 30h
address | size | read/write | description |
---|---|---|---|
0x10-0x30 | 33 | r/W | Gamma coefficients |
byte(s) | description |
---|---|
0-32 | See coefficients table below (default is gamma 1.0) |
/* Wii A/V Encoder gamma coefficients */
const u8 gamma_coeffs[][33] =
{
/* GM_0_0 */
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
},
/* GM_0_1 */
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x97, 0x3B, 0x49,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x80, 0x1B, 0x80, 0xEB, 0x00
},
/* GM_0_2 */
{
0x00, 0x00, 0x00, 0x28, 0x00, 0x5A, 0x02, 0xDB, 0x0D, 0x8D, 0x30, 0x49,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x10, 0x00, 0x10, 0x40, 0x11, 0x00, 0x18, 0x80, 0x42, 0x00, 0xEB, 0x00
},
/* GM_0_3 */
{
0x00, 0x00, 0x00, 0x7A, 0x02, 0x3C, 0x07, 0x6D, 0x12, 0x9C, 0x27, 0x24,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x10, 0x00, 0x10, 0xC0, 0x15, 0x80, 0x29, 0x00, 0x62, 0x00, 0xEB, 0x00
},
/* GM_0_4 */
{
0x00, 0x4E, 0x01, 0x99, 0x05, 0x2D, 0x0B, 0x24, 0x14, 0x29, 0x20, 0xA4,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x00, 0x10, 0x10, 0x40, 0x12, 0xC0, 0x1D, 0xC0, 0x3B, 0x00, 0x78, 0xC0, 0xEB, 0x00
},
/* GM_0_5 */
{
0x00, 0xEC, 0x03, 0xD7, 0x08, 0x00, 0x0D, 0x9E, 0x14, 0x3E, 0x1B, 0xDB,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x10, 0xC0, 0x16, 0xC0, 0x27, 0xC0, 0x4B, 0x80, 0x89, 0x80, 0xEB, 0x00
},
/* GM_0_6 */
{
0x02, 0x76, 0x06, 0x66, 0x0A, 0x96, 0x0E, 0xF3, 0x13, 0xAC, 0x18, 0x49,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x12, 0x00, 0x1C, 0x00, 0x32, 0x80, 0x59, 0xC0, 0x96, 0x00, 0xEB, 0x00
},
/* GM_0_7 */
{
0x04, 0xEC, 0x08, 0xF5, 0x0C, 0x96, 0x0F, 0xCF, 0x12, 0xC6, 0x15, 0x80,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x14, 0x00, 0x22, 0x00, 0x3C, 0xC0, 0x66, 0x40, 0x9F, 0xC0, 0xEB, 0x00
},
/* GM_0_8 */
{
0x08, 0x00, 0x0B, 0xAE, 0x0E, 0x00, 0x10, 0x30, 0x11, 0xCB, 0x13, 0x49,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x16, 0x80, 0x28, 0xC0, 0x46, 0x80, 0x71, 0x00, 0xA7, 0x80, 0xEB, 0x00
},
/* GM_0_9 */
{
0x0B, 0xB1, 0x0E, 0x14, 0x0F, 0x2D, 0x10, 0x18, 0x10, 0xE5, 0x11, 0x80,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x19, 0x80, 0x2F, 0x80, 0x4F, 0xC0, 0x7A, 0x00, 0xAD, 0xC0, 0xEB, 0x00
},
/* GM_1_0 */
{
0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,
0x10, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xEB,
0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x60, 0x00, 0x80, 0x00, 0xA0, 0x00, 0xEB, 0x00
},
/* GM_1_1 */
{
0x14, 0xEC, 0x11, 0xC2, 0x10, 0x78, 0x0F, 0xB6, 0x0F, 0x2F, 0x0E, 0xB6,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x21, 0x00, 0x3C, 0xC0, 0x5F, 0xC0, 0x89, 0x00, 0xB7, 0x80, 0xEB, 0x00
},
/* GM_1_2 */
{
0x19, 0xD8, 0x13, 0x33, 0x10, 0xD2, 0x0F, 0x6D, 0x0E, 0x5E, 0x0D, 0xA4,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x25, 0x00, 0x43, 0x00, 0x66, 0xC0, 0x8F, 0x40, 0xBB, 0x40, 0xEB, 0x00
},
/* GM_1_3 */
{
0x1E, 0xC4, 0x14, 0x7A, 0x11, 0x0F, 0xF, 0x0C, 0x0D, 0xA1, 0x0C, 0xB6,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x29, 0x00, 0x49, 0x00, 0x6D, 0x40, 0x94, 0xC0, 0xBE, 0x80, 0xEB, 0x00
},
/* GM_1_4 */
{
0x24, 0x00, 0x15, 0x70, 0x11, 0x0F, 0x0E, 0xAA, 0x0D, 0x0F, 0x0B, 0xDB,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x2D, 0x40, 0x4E, 0xC0, 0x73, 0x00, 0x99, 0x80, 0xC1, 0x80, 0xEB, 0x00
},
/* GM_1_5 */
{
0x29, 0x3B, 0x16, 0x3D, 0x11, 0x0F, 0x0E, 0x30, 0x0C, 0x7D, 0x0B, 0x24,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x31, 0x80, 0x54, 0x40, 0x78, 0x80, 0x9D, 0xC0, 0xC4, 0x00, 0xEB, 0x00
},
/* GM_1_6 */
{
0x2E, 0x27, 0x17, 0x0A, 0x10, 0xD2, 0x0D, 0xE7, 0x0B, 0xEB, 0x0A, 0x80,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x35, 0x80, 0x59, 0x80, 0x7D, 0x40, 0xA1, 0xC0, 0xC6, 0x40, 0xEB, 0x00
},
/* GM_1_7 */
{
0x33, 0x62, 0x17, 0x5C, 0x10, 0xD2, 0x0D, 0x6D, 0x0B, 0x6D, 0x09, 0xED,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x39, 0xC0, 0x5E, 0x40, 0x82, 0x00, 0xA5, 0x40, 0xC8, 0x40, 0xEB, 0x00
},
/* GM_1_8 */
{
0x38, 0x4E, 0x17, 0xAE, 0x10, 0xB4, 0x0D, 0x0C, 0x0A, 0xF0, 0x09, 0x6D,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x3D, 0xC0, 0x62, 0xC0, 0x86, 0x40, 0xA8, 0x80, 0xCA, 0x00, 0xEB, 0x00
},
/* GM_1_9 */
{
0x3D, 0x3B, 0x18, 0x00, 0x10, 0x5A, 0x0C, 0xC3, 0x0A, 0x72, 0x09, 0x00,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x41, 0xC0, 0x67, 0x40, 0x8A, 0x00, 0xAB, 0x80, 0xCB, 0x80, 0xEB, 0x00
},
/* GM_2_0 */
{
0x41, 0xD8, 0x18, 0x28, 0x10, 0x3C, 0x0C, 0x49, 0x0A, 0x1F, 0x08, 0x92,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x45, 0x80, 0x6B, 0x40, 0x8D, 0xC0, 0xAE, 0x00, 0xCD, 0x00, 0xEB, 0x00
},
/* GM_2_1 */
{
0x46, 0x76, 0x18, 0x51, 0x0F, 0xE1, 0x0C, 0x00, 0x09, 0xB6, 0x08, 0x36,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x49, 0x40, 0x6F, 0x40, 0x91, 0x00, 0xB0, 0x80, 0xCE, 0x40, 0xEB, 0x00
},
/* GM_2_2 */
{
0x4A, 0xC4, 0x18, 0x7A, 0x0F, 0xA5, 0x0B, 0x9E, 0x09, 0x63, 0x07, 0xDB,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x4C, 0xC0, 0x73, 0x00, 0x94, 0x40, 0xB2, 0xC0, 0xCF, 0x80, 0xEB, 0x00
},
/* GM_2_3 */
{
0x4F, 0x13, 0x18, 0x51, 0x0F, 0x69, 0x0B, 0x6D, 0x09, 0x0F, 0x07, 0x80,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x50, 0x40, 0x76, 0x40, 0x97, 0x00, 0xB5, 0x00, 0xD0, 0xC0, 0xEB, 0x00
},
/* GM_2_4 */
{
0x53, 0x13, 0x18, 0x7A, 0x0F, 0x0F, 0x0B, 0x24, 0x08, 0xBC, 0x07, 0x36,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x53, 0x80, 0x79, 0xC0, 0x99, 0xC0, 0xB7, 0x00, 0xD1, 0xC0, 0xEB, 0x00
},
/* GM_2_5 */
{
0x57, 0x13, 0x18, 0x51, 0x0E, 0xF0, 0x0A, 0xC3, 0x08, 0x7D, 0x06, 0xED,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x56, 0xC0, 0x7C, 0xC0, 0x9C, 0x80, 0xB8, 0xC0, 0xD2, 0xC0, 0xEB, 0x00
},
/* GM_2_6 */
{
0x5B, 0x13, 0x18, 0x28, 0x0E, 0x96, 0x0A, 0x92, 0x08, 0x29, 0x06, 0xB6,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x5A, 0x00, 0x7F, 0xC0, 0x9E, 0xC0, 0xBA, 0x80, 0xD3, 0x80, 0xEB, 0x00
},
/* GM_2_7 */
{
0x5E, 0xC4, 0x18, 0x00, 0x0E, 0x78, 0x0A, 0x30, 0x08, 0x00, 0x06, 0x6D,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x5D, 0x00, 0x82, 0x80, 0xA1, 0x40, 0xBC, 0x00, 0xD4, 0x80, 0xEB, 0x00
},
/* GM_2_8 */
{
0x62, 0x76, 0x17, 0xD7, 0x0E, 0x1E, 0x0A, 0x00, 0x07, 0xC1, 0x06, 0x36,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x60, 0x00, 0x85, 0x40, 0xA3, 0x40, 0xBD, 0x80, 0xD5, 0x40, 0xEB, 0x00
},
/* GM_2_9 */
{
0x65, 0xD8, 0x17, 0xAE, 0x0D, 0xE1, 0x09, 0xCF, 0x07, 0x82, 0x06, 0x00,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x62, 0xC0, 0x87, 0xC0, 0xA5, 0x40, 0xBF, 0x00, 0xD6, 0x00, 0xEB, 0x00
},
/* GM_3_0 */
{
0x69, 0x3B, 0x17, 0x85, 0x0D, 0xA5, 0x09, 0x86, 0x07, 0x43, 0x05, 0xDB,
0x10, 0x1D, 0x36, 0x58, 0x82, 0xB3, 0xEB,
0x10, 0x00, 0x65, 0x80, 0x8A, 0x40, 0xA7, 0x40, 0xC0, 0x40, 0xD6, 0x80, 0xEB, 0x00
}
}
Registers 40h - 59h
address | size | read/write | description |
---|---|---|---|
0x40-0x59 | 26 | r/W | Macrovision code |
bytes(s) | description |
---|---|
0-25 | unknown, default is 0xFF |
Register 62h
address | size | read/write | description |
---|---|---|---|
0x62 | 1 | W | RGB switch control |
bit(s) | description |
---|---|
0 | 1:swap blue & red signals, 0: normal |
Register 63h
address | size | read/write | description |
---|---|---|---|
0x63 | 1 | ? | Unknown |
bit(s) | description |
---|---|
0 | unknown (0: initial value) |
Register 64h
address | size | read/write | description |
---|---|---|---|
0x64 | 1 | ? | Unknown |
bit(s) | description |
---|---|
0 | unknown (0: initial value) |
Register 65h
address | size | read/write | description |
---|---|---|---|
0x65 | 1 | W | color DAC control (oversampling ?) |
bit(s) | description |
---|---|
0 | unknown (1: default value) |
Modifying the default value does seem to have any effect on retail Wii.
Register 67h
address | size | read/write | description |
---|---|---|---|
0x67 | 1 | W | Color Test |
bit(s) | description |
---|---|
0 | 1:display color test pattern, 0: normal |
Register 68h
address | size | read/write | description |
---|---|---|---|
0x68 | 1 | ? | Unknown |
bit(s) | description |
---|---|
0 | unknown (0: initial value) |
Register 6Ah
address | size | read/write | description |
---|---|---|---|
0x6A | 1 | W | Unknown (CCSEL ?) |
bit(s) | description |
---|---|
0 | unknown (1: default value) |
Register 6Bh
address | size | read/write | description |
---|---|---|---|
0x6B | 1 | ? | Unknown |
bit(s) | description |
---|---|
0 | unknown (0: initial value) |
Register 6Ch
address | size | read/write | description |
---|---|---|---|
0x6C | 1 | ? | Unknown |
bit(s) | description |
---|---|
0 | unknown (0: initial value) |
Register 6Dh
address | size | read/write | description |
---|---|---|---|
0x6D | 1 | W | Audio mute control |
bit(s) | description |
---|---|
0 | 1:enabled, 0: disabled |
Register 6Eh
address | size | read/write | description |
---|---|---|---|
0x6E | 1 | W | RGB output filter |
bit(s) | description |
---|---|
0 | 1:enabled, 0: disabled |
This is typically enabled when the video mode is set to EURGB60 (5).
Setting this to zero results in red saturated image when using RGB video cable.
Still need confirmation if this happens in 576i (PAL 50Hz) mode as well.
Maybe this is used to select between S-Video (NTSC) & RGB (PAL60) output when the video output is 60hz.
Register 70h
address | size | read/write | description |
---|---|---|---|
0x70 | 1 | ? | Unknown |
bit(s) | description |
---|---|
0 | unknown (0: initial value) |
Registers 71h & 72h
address | size | read/write | description |
---|---|---|---|
0x71 | 2 | W | Audio stereo output control |
bit(s) | description |
---|---|
8-15 | right volume ? (default value is 0x8e, 0x71 for d-Terminal ?) |
0-7 | left volume ? (default value is 0x8e, 0x71 for d-Terminal ?) |
Registers 7Ah - 7Dh
address | size | read/write | description |
---|---|---|---|
0x7A | 4 | W | Closed Captioning control ? |
bit(s) | description |
---|---|
24-30 | unknown (0: default value) |
16-22 | unknown (0: default value) |
8-14 | unknown (0: default value) |
0-6 | unknown (0: default value) |
Sample Code
Here is some sample code to initialize the A/V encoder :)
/*
BootMii - a Free Software replacement for the Nintendo/BroadOn bootloader.
low-level video support for the BootMii UI
Copyright (C) 2008, 2009 Hector Martin "marcan" <marcan@marcansoft.com>
Copyright (C) 2009 Haxx Enterprises <bushing@gmail.com>
Copyright (c) 2009 Sven Peter <svenpeter@gmail.com>
# This code is licensed to you under the terms of the GNU GPL, version 2;
# see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
Some routines and initialization constants originally came from the
"GAMECUBE LOW LEVEL INFO" document and sourcecode released by Titanik
of Crazy Nation and the GC Linux project.
*/
#include "bootmii_ppc.h"
#include "video_low.h"
#include "string.h"
#include "hollywood.h"
#ifdef VI_DEBUG
#define VI_debug(f, arg...) printf("VI: " f, ##arg);
#else
#define VI_debug(f, arg...) while(0)
#endif
// hardcoded VI init states -- these were obtained by dumping the register space after it was configured by
a game in each mode
static const u16 VIDEO_Mode640X480NtsciYUV16[64] = {
0x0F06, 0x0001, 0x4769, 0x01AD, 0x02EA, 0x5140, 0x0003, 0x0018,
0x0002, 0x0019, 0x410C, 0x410C, 0x40ED, 0x40ED, 0x0043, 0x5A4E,
0x0000, 0x0000, 0x0043, 0x5A4E, 0x0000, 0x0000, 0x0000, 0x0000,
0x1107, 0x01AE, 0x1001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0000, 0x0000, 0x0000, 0x0000, 0x2850, 0x0100, 0x1AE7, 0x71F0,
0x0DB4, 0xA574, 0x00C1, 0x188E, 0xC4C0, 0xCBE2, 0xFCEC, 0xDECF,
0x1313, 0x0F08, 0x0008, 0x0C0F, 0x00FF, 0x0000, 0x0000, 0x0000,
0x0280, 0x0000, 0x0000, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF};
static const u16 VIDEO_Mode640X480Pal50YUV16[64] = {
0x11F5, 0x0101, 0x4B6A, 0x01B0, 0x02F8, 0x5640, 0x0001, 0x0023,
0x0000, 0x0024, 0x4D2B, 0x4D6D, 0x4D8A, 0x4D4C, 0x0043, 0x5A4E,
0x0000, 0x0000, 0x0043, 0x5A4E, 0x0000, 0x0000, 0x013C, 0x0144,
0x1139, 0x01B1, 0x1001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0000, 0x0000, 0x0000, 0x0000, 0x2850, 0x0100, 0x1AE7, 0x71F0,
0x0DB4, 0xA574, 0x00C1, 0x188E, 0xC4C0, 0xCBE2, 0xFCEC, 0xDECF,
0x1313, 0x0F08, 0x0008, 0x0C0F, 0x00FF, 0x0000, 0x0000, 0x0000,
0x0280, 0x0000, 0x0000, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF};
static const u16 VIDEO_Mode640X480Pal60YUV16[64] = {
0x0F06, 0x0001, 0x4769, 0x01AD, 0x02EA, 0x5140, 0x0003, 0x0018,
0x0002, 0x0019, 0x410C, 0x410C, 0x40ED, 0x40ED, 0x0043, 0x5A4E,
0x0000, 0x0000, 0x0043, 0x5A4E, 0x0000, 0x0000, 0x0005, 0x0176,
0x1107, 0x01AE, 0x1001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0000, 0x0000, 0x0000, 0x0000, 0x2850, 0x0100, 0x1AE7, 0x71F0,
0x0DB4, 0xA574, 0x00C1, 0x188E, 0xC4C0, 0xCBE2, 0xFCEC, 0xDECF,
0x1313, 0x0F08, 0x0008, 0x0C0F, 0x00FF, 0x0000, 0x0000, 0x0000,
0x0280, 0x0000, 0x0000, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF};
static const u16 VIDEO_Mode640X480NtscpYUV16[64] = {
0x1E0C, 0x0005, 0x4769, 0x01AD, 0x02EA, 0x5140, 0x0006, 0x0030,
0x0006, 0x0030, 0x81D8, 0x81D8, 0x81D8, 0x81D8, 0x0015, 0x77A0,
0x0000, 0x0000, 0x0015, 0x77A0, 0x0000, 0x0000, 0x022A, 0x01D6,
0x120E, 0x0001, 0x1001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
0x0000, 0x0000, 0x0000, 0x0000, 0x2828, 0x0100, 0x1AE7, 0x71F0,
0x0DB4, 0xA574, 0x00C1, 0x188E, 0xC4C0, 0xCBE2, 0xFCEC, 0xDECF,
0x1313, 0x0F08, 0x0008, 0x0C0F, 0x00FF, 0x0000, 0x0001, 0x0001,
0x0280, 0x807A, 0x019C, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF};
static int video_mode;
void VIDEO_Init(int VideoMode)
{
u32 Counter=0;
const u16 *video_initstate=NULL;
VI_debug("Resetting VI...\n");
write16(R_VIDEO_STATUS1, 2);
udelay(2);
write16(R_VIDEO_STATUS1, 0);
VI_debug("VI reset...\n");
switch(VideoMode)
{
case VIDEO_640X480_NTSCi_YUV16:
video_initstate = VIDEO_Mode640X480NtsciYUV16;
break;
case VIDEO_640X480_PAL50_YUV16:
video_initstate = VIDEO_Mode640X480Pal50YUV16;
break;
case VIDEO_640X480_PAL60_YUV16:
video_initstate = VIDEO_Mode640X480Pal60YUV16;
break;
case VIDEO_640X480_NTSCp_YUV16:
video_initstate = VIDEO_Mode640X480NtscpYUV16;
break;
/* Use NTSC as default */
default:
VideoMode = VIDEO_640X480_NTSCi_YUV16;
video_initstate = VIDEO_Mode640X480NtsciYUV16;
break;
}
VI_debug("Configuring VI...\n");
for(Counter=0; Counter<64; Counter++)
{
if(Counter==1)
write16(MEM_VIDEO_BASE + 2*Counter, video_initstate[Counter] & 0xFFFE);
else
write16(MEM_VIDEO_BASE + 2*Counter, video_initstate[Counter]);
}
video_mode = VideoMode;
write16(R_VIDEO_STATUS1, video_initstate[1]);
#ifdef VI_DEBUG
VI_debug("VI dump:\n");
for(Counter=0; Counter<32; Counter++)
printf("%02x: %04x %04x,\n", Counter*4, read16(MEM_VIDEO_BASE + Counter*4),
read16(MEM_VIDEO_BASE + Counter*4+2));
printf("---\n");
#endif
}
void VIDEO_SetFrameBuffer(void *FrameBufferAddr)
{
u32 fb = virt_to_phys(FrameBufferAddr);
write32(R_VIDEO_FRAMEBUFFER_1, (fb >> 5) | 0x10000000);
if(video_mode != VIDEO_640X480_NTSCp_YUV16)
fb += 2 * 640; // 640 pixels == 1 line
write32(R_VIDEO_FRAMEBUFFER_2, (fb >> 5) | 0x10000000);
}
void VIDEO_WaitVSync(void)
{
while(read16(R_VIDEO_HALFLINE_1) >= 200);
while(read16(R_VIDEO_HALFLINE_1) < 200);
}
/* black out video (not reversible!) */
void VIDEO_BlackOut(void)
{
VIDEO_WaitVSync();
int active = read32(R_VIDEO_VTIMING) >> 4;
write32(R_VIDEO_PRB_ODD, read32(R_VIDEO_PRB_ODD) + ((active<<1)-2));
write32(R_VIDEO_PRB_EVEN, read32(R_VIDEO_PRB_EVEN) + ((active<<1)-2));
write32(R_VIDEO_PSB_ODD, read32(R_VIDEO_PSB_ODD) + 2);
write32(R_VIDEO_PSB_EVEN, read32(R_VIDEO_PSB_EVEN) + 2);
mask32(R_VIDEO_VTIMING, 0xfffffff0, 0);
}
//static vu16* const _viReg = (u16*)0xCC002000;
void VIDEO_Shutdown(void)
{
VIDEO_BlackOut();
write16(R_VIDEO_STATUS1, 0);
}
#define SLAVE_AVE 0xe0
static inline void aveSetDirection(u32 dir)
{
u32 val = (read32(HW_GPIO1BDIR)&~0x8000)|0x4000;
if(dir) val |= 0x8000;
write32(HW_GPIO1BDIR, val);
}
static inline void aveSetSCL(u32 scl)
{
u32 val = read32(HW_GPIO1BOUT)&~0x4000;
if(scl) val |= 0x4000;
write32(HW_GPIO1BOUT, val);
}
static inline void aveSetSDA(u32 sda)
{
u32 val = read32(HW_GPIO1BOUT)&~0x8000;
if(sda) val |= 0x8000;
write32(HW_GPIO1BOUT, val);
}
static inline u32 aveGetSDA()
{
if(read32(HW_GPIO1BIN)&0x8000)
return 1;
else
return 0;
}
static u32 __sendSlaveAddress(u8 addr)
{
u32 i;
aveSetSDA(0);
udelay(2);
aveSetSCL(0);
for(i=0;i<8;i++) {
if(addr&0x80) aveSetSDA(1);
else aveSetSDA(0);
udelay(2);
aveSetSCL(1);
udelay(2);
aveSetSCL(0);
addr <<= 1;
}
aveSetDirection(0);
udelay(2);
aveSetSCL(1);
udelay(2);
if(aveGetSDA()!=0) {
VI_debug("No ACK\n");
return 0;
}
aveSetSDA(0);
aveSetDirection(1);
aveSetSCL(0);
return 1;
}
static u32 __VISendI2CData(u8 addr,void *val,u32 len)
{
u8 c;
u32 i,j;
u32 ret;
VI_debug("I2C[%02x]:",addr);
for(i=0;i<len;i++)
VI_debug(" %02x", ((u8*)val)[i]);
VI_debug("\n");
aveSetDirection(1);
aveSetSCL(1);
aveSetSDA(1);
udelay(4);
ret = __sendSlaveAddress(addr);
if(ret==0) {
return 0;
}
aveSetDirection(1);
for(i=0;i<len;i++) {
c = ((u8*)val)[i];
for(j=0;j<8;j++) {
if(c&0x80) aveSetSDA(1);
else aveSetSDA(0);
udelay(2);
aveSetSCL(1);
udelay(2);
aveSetSCL(0);
c <<= 1;
}
aveSetDirection(0);
udelay(2);
aveSetSCL(1);
udelay(2);
if(aveGetSDA()!=0) {
VI_debug("No ACK\n");
return 0;
}
aveSetSDA(0);
aveSetDirection(1);
aveSetSCL(0);
}
aveSetDirection(1);
aveSetSDA(0);
udelay(2);
aveSetSDA(1);
return 1;
}
static void __VIWriteI2CRegister8(u8 reg, u8 data)
{
u8 buf[2];
buf[0] = reg;
buf[1] = data;
__VISendI2CData(SLAVE_AVE,buf,2);
udelay(2);
}
static void __VIWriteI2CRegister16(u8 reg, u16 data)
{
u8 buf[3];
buf[0] = reg;
buf[1] = data >> 8;
buf[2] = data & 0xFF;
__VISendI2CData(SLAVE_AVE,buf,3);
udelay(2);
}
static void __VIWriteI2CRegister32(u8 reg, u32 data)
{
u8 buf[5];
buf[0] = reg;
buf[1] = data >> 24;
buf[2] = (data >> 16) & 0xFF;
buf[3] = (data >> 8) & 0xFF;
buf[4] = data & 0xFF;
__VISendI2CData(SLAVE_AVE,buf,5);
udelay(2);
}
static void __VIWriteI2CRegisterBuf(u8 reg, int size, u8 *data)
{
u8 buf[0x100];
buf[0] = reg;
memcpy(&buf[1], data, size);
__VISendI2CData(SLAVE_AVE,buf,size+1);
udelay(2);
}
static void __VISetYUVSEL(u8 dtvstatus)
{
int vdacFlagRegion;
switch(video_mode) {
case VIDEO_640X480_NTSCi_YUV16:
case VIDEO_640X480_NTSCp_YUV16:
default:
vdacFlagRegion = 0;
break;
case VIDEO_640X480_PAL50_YUV16:
case VIDEO_640X480_PAL60_YUV16:
vdacFlagRegion = 2;
break;
}
__VIWriteI2CRegister8(0x01, (dtvstatus<<5) | (vdacFlagRegion&0x1f));
}
static void __VISetFilterEURGB60(u8 enable)
{
__VIWriteI2CRegister8(0x6e, enable);
}
void VISetupEncoder(void)
{
u8 macrobuf[0x1a];
u8 gamma[0x21] = {
0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,
0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x40, 0x60,
0x80, 0xa0, 0xeb, 0x10, 0x00, 0x20, 0x00, 0x40,
0x00, 0x60, 0x00, 0x80, 0x00, 0xa0, 0x00, 0xeb,
0x00
};
u8 dtv;
//tv = VIDEO_GetCurrentTvMode();
dtv = read16(R_VIDEO_VISEL) & 1;
//oldDtvStatus = dtv;
// SetRevolutionModeSimple
VI_debug("DTV status: %d\n", dtv);
memset(macrobuf, 0, 0x1a);
__VIWriteI2CRegister8(0x6a, 1);
__VIWriteI2CRegister8(0x65, 1);
__VISetYUVSEL(dtv);
__VIWriteI2CRegister8(0x00, 0);
__VIWriteI2CRegister16(0x71, 0x8e8e);
__VIWriteI2CRegister8(0x02, 7);
__VIWriteI2CRegister16(0x05, 0x0000);
__VIWriteI2CRegister16(0x08, 0x0000);
__VIWriteI2CRegister32(0x7A, 0x00000000);
// Macrovision crap
__VIWriteI2CRegisterBuf(0x40, sizeof(macrobuf), macrobuf);
// Sometimes 1 in RGB mode? (reg 1 == 3)
__VIWriteI2CRegister8(0x0A, 0);
__VIWriteI2CRegister8(0x03, 1);
__VIWriteI2CRegisterBuf(0x10, sizeof(gamma), gamma);
__VIWriteI2CRegister8(0x04, 1);
__VIWriteI2CRegister32(0x7A, 0x00000000);
__VIWriteI2CRegister16(0x08, 0x0000);
__VIWriteI2CRegister8(0x03, 1);
//if(tv==VI_EURGB60) __VISetFilterEURGB60(1);
//else
__VISetFilterEURGB60(0);
//oldTvStatus = tv;
}
Chip Picture
Chip Pinout
Pin | Name | |
---|---|---|
1 | VData 1 | |
2 | VData 2 | |
3 | VData 3 | |
4 | 1.8V | |
5 | GND | |
6 | VData 4 | |
7 | VData 5 | |
8 | 54 MHz clock | |
9 | 1.8V | |
10 | GND | |
11 | CSel (clock/color select)[6] | |
12 | GND | |
13 | VData 6 | |
14 | VData 7 | |
15 | GND | |
16 | GND | |
17 | GND | |
18 | GND | |
19 | SDA | |
20 | SCL | |
21 | GND | |
22 | Capacitor to GND | |
23 | 1.8V | |
24 | GND | |
25 | i2s MC | |
26 | 1.8V | |
27 | i2s WS | |
28 | GND | |
29 | i2s D | |
30 | GND | |
31 | i2s C | |
32 | GND | |
33 | H-Sync | |
34 | V-Sync | |
35 | GND | |
36 | GND | |
37 | 3.3V | |
38 | GND | |
39 | Audio R | |
40 | Capacitor to GND | |
41 | 3.3V | |
42 | GND | |
43 | Audio L | |
44 | GND | |
45 | GND | |
46 | 3.3V | |
47 | 1.2kΩ GND | |
48 | GND | |
49 | GND | |
50 | 3.3V | |
51 | Composite | |
52 | GND | |
53 | 3.3V | |
54 | Red/Y/Luma | |
55 | GND | |
56 | 3.3V | |
57 | Green/Pb/Chroma | |
58 | GND | |
59 | Blue/Pr | |
60 | 3.3V | |
61 | GND | |
62 | Mode select, connected to data 3 of multi-AV. 0V 4:3, 2.2V 4:3 letterbox, 5V 16:9[7] | |
63 | GND | |
64 | VData 0 |
VData Encoding
The VData pins are an 8-bit parallel bus with a 54 MHz rising edge clock and "clock select" (CSel) line. The bus sends one byte per clock tick, with VData0 being the LSB and VData7 being the MSB. The bus interleaves Y, CbCr, and flags clocked to CSel edge. When CSel changes (both rising and falling), the first byte is Y. A Y value of 0 indicates blanking and is followed by a flag byte. Otherwise, the next byte is CbCr.[8]
The flag byte is divided as follows (big endian):
Bit | Name |
---|---|
7 | CSync (active low) |
6 | IsEvenField |
5 | VSync (active low) |
4 | HSync (active low) |
3 | (unused) |
2 | (unused) |
1 | IsPAL |
0 | IsProgressive |
This matches 8-bit parallel ITU-R BT.656, a 4:2:2 YCbCr digital encoding for PAL/NTSC meant for interfacing with a DAC to drive the analog signals (the AVE). It's driven at 27 Mword/s, and a word is split between the two Y and CbCr bytes transmitted separately, so half the VData clock.
References
↑ 1. "Shank's Wii Super Thread". https://bitbuilt.net/forums/index.php?threads/shanks-wii-super-thread.66/.
↑ 2. "Wii native VGA - version 1.5". https://bitbuilt.net/forums/index.php?threads/wii-native-vga-version-1-5.2176/page-10.
↑ 3. "Wiring the U-Amp". https://bitbuilt.net/forums/index.php?threads/a-few-questions-on-wiring-the-u-amp.3219/.
↑ 4. "Analyzing the Wii's Video Encoder". https://bitbuilt.net/forums/index.php?threads/analyzing-the-wiis-video-encoder.206/page-2.
↑ 5. "RVL-PMS-LITE". https://4layertech.com/products/pms2-lite?pr_prod_strat=copurchase&pr_rec_pid=6894101070022&pr_ref_pid=6545900503238&pr_seq=uniform.
↑ 6. "GCVideo DVI for Wii". https://github.com/ikorb/gcvideo/blob/d5989ead04c088575e3bbd91c426084331bca71d/HDL/gcvideo_dvi/README-Wii.md.
↑ 7. "Wii Multi AV Pinout". https://gamesx.com/wiki/doku.php?id=av:wii_multi_av_pinout.
↑ 8. "gcdv_decoder.vhd". https://github.com/ikorb/gcvideo/blob/main/HDL/gcvideo_dvi/src/gcdv_decoder.vhd.