Hardware/AV Encoder

From WiiBrew
< Hardware
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 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 analysis and disassemby of some original code, there is therefore still unknown features and the whole description should be subject to 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 a composite video cable is connected.

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 Video Trap Filter control (composite video only ?)


bit(s) description
1 1: default value, 0: disabled ?


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.

Registers 10h - 30h

address size read/write description
0x10-0x30 33 r/W Gamma correction coefficients


bit(s) description
0-263 unknown


Registers 40h - 59h

address size read/write description
0x40-0x59 26 r/W Macrovision code


bit(s) description
0-207 unknown


Register 65h

address size read/write description
0x65 1 W color DAC control (oversampling ?)


bit(s) description
0 unknown (1: default value)


Register 6Ah

address size read/write description
0x6A 1 W Unknown (CCSEL ?)


bit(s) description
0 unknown (1: default value)


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.

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