Hardware/AV Encoder

From WiiBrew
Jump to navigation Jump to search

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

AVE-RVL.jpg

Chip Pinout

AVE pinout for component[1]
AVE pinout for VGA[2]
AVE pinout for i2s[3]
AVE tested pinouts[4]
Diagram of SDA and SCL (SDW and SCW) used by WiiHUD to connect to arbitrary peripherals.[5]
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

Raw timing diagram

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.