/*
 * Sega Model 3 Emulator
 * Copyright (C) 2003 Bart Trzynadlowski, Ville Linde, Stefano Teso
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License Version 2 as published
 * by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program (license.txt); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * r3d.c
 *
 * Real3D Model 3 graphics system emulation. The 3D hardware in Model 3 is
 * supposedly based on the Pro-1000.
 *
 * To-Do List:
 * -----------
 * - In VS2_98, in the attract mode, when the camera zooms in on the score
 *   board for South Africa vs. Nigeria, the "Nigeria" text texture is
 *   garbage.
 * - RAM texture uploading is too weird to be true.
 */

/*
 * RAM Size:
 * ---------
 *
 * It appears that there is 2MB total of "culling RAM." 1MB appears at
 * 0x8C000000 and the other at 0x8E000000. Step 1.0 and 1.5 appear to have
 * 1MB of polygon RAM, but Step 2.0 (and probably 2.1) clearly uses 2MB.
 */

#include "model3.h"

extern UINT8 *ram;

/******************************************************************/
/* Privates                                                       */
/******************************************************************/

static UINT8    *culling_ram_8e;    // culling RAM at 0x8E000000
static UINT8    *culling_ram_8c;    // culling RAM at 0x8C000000
static UINT8    *polygon_ram;       // polygon RAM at 0x98000000
static UINT8    *texture_ram;       // texture RAM
static UINT8	*vrom;

static UINT8    texture_buffer_ram[1*1024*1024];

static UINT32   vrom_texture_address;
static UINT32   vrom_texture_header;

static UINT32   texture_last_addr = 0;
static UINT32	texture_header = 0;

/******************************************************************/
/* Interface                                                      */
/******************************************************************/

/*
 * void r3d_init(UINT8 *culling_ram_8e_ptr, UINT8 *culling_ram_8c_ptr,
 *               UINT8 *polygon_ram_ptr, UINT8 *texture_ram, UINT8 *vrom_ptr);
 *
 * Initializes the Real3D graphics emulation.
 *
 * Parameters:
 *      culling_ram_8e_ptr = Pointer to 0x8E000000 culling RAM.
 *      culling_ram_8c_ptr = Pointer to 0x8C000000 culling RAM.
 *      polygon_ram_ptr    = Pointer to polygon RAM.
 *      texture_ram        = Pointer to texture RAM.
 *      vrom_ptr           = Pointer to VROM.
 */

void r3d_init(UINT8 *culling_ram_8e_ptr, UINT8 *culling_ram_8c_ptr,
              UINT8 *polygon_ram_ptr, UINT8 *texture_ram_ptr, UINT8 *vrom_ptr)
{
    culling_ram_8e = culling_ram_8e_ptr;
    culling_ram_8c = culling_ram_8c_ptr;
    polygon_ram = polygon_ram_ptr;
    texture_ram = texture_ram_ptr;
	vrom = vrom_ptr;

    LOG_INIT("texture.log");
}

/*
 * void r3d_shutdown(void);
 *
 * Shuts down the Real3D emulation.
 */

void r3d_shutdown(void)
{
}

/*
 * void r3d_reset(void);
 *
 * Resets the Real3D graphics hardware. RAM is cleared in order to prevent
 * the renderer from drawing garbage and possibly locking up as a result.
 */

void r3d_reset(void)
{
    memset(culling_ram_8e, 0, 1*1024*1024);
    memset(culling_ram_8c, 0, 4*1024*1024);
    memset(polygon_ram, 0, 2*1024*1024);
    memset(texture_ram, 0, 2048*2048*2);
	tap_reset();
}

/*
 * void r3d_save_state(FILE *fp);
 *
 * Saves the state of the Real3D graphics hardware to a file.
 *
 * Parameters:
 *      fp = File to save to.
 */

void r3d_save_state(FILE *fp)
{
    fwrite(culling_ram_8e, sizeof(UINT8), 1*1024*1024, fp);
    fwrite(culling_ram_8c, sizeof(UINT8), 4*1024*1024, fp);
    fwrite(polygon_ram, sizeof(UINT8), 2*1024*1024, fp);
    fwrite(texture_ram, sizeof(UINT8), 2048*2048*2, fp);
}

/*
 * void r3d_load_state(FILE *fp);
 *
 * Loads the state of the Real3D graphics hardware from a file.
 *
 * Parameters:
 *      fp = File to load from.
 */

void r3d_load_state(FILE *fp)
{
    fread(culling_ram_8e, sizeof(UINT8), 1*1024*1024, fp);
    fread(culling_ram_8c, sizeof(UINT8), 4*1024*1024, fp);
    fread(polygon_ram, sizeof(UINT8), 2*1024*1024, fp);
    fread(texture_ram, sizeof(UINT8), 2048*2048*2, fp);

    osd_renderer_invalidate_textures(0, 0, 0, 0, 2048, 2048, texture_ram, 0);
}

/******************************************************************/
/* Texture Memory Management                                      */
/******************************************************************/

static const INT    decode[64] =
{
	 0, 1, 4, 5, 8, 9,12,13,
	 2, 3, 6, 7,10,11,14,15,
	16,17,20,21,24,25,28,29,
	18,19,22,23,26,27,30,31,
	32,33,36,37,40,41,44,45,
	34,35,38,39,42,43,46,47,
	48,49,52,53,56,57,60,61,
	50,51,54,55,58,59,62,63
};

/*
 * store_texture_tile():
 *
 * Writes a single 8x8 texture tile into the appropriate part of the texture
 * sheet.
 */

static void store_texture_tile(UINT x, UINT y, UINT8 *src, UINT bpp, BOOL little_endian)
{
    UINT    xi, yi, pixel_offs;
    UINT16  rgb16;
    UINT8   gray8;

	for (yi = 0; yi < 8; yi++)
	{
		for (xi = 0; xi < 8; xi++)
		{
            /*
             * Grab the pixel offset from the decode[] array and fetch the
             * pixel word
             */

            if (little_endian)
            {            
                if (bpp == 2)
                {
                    /*
                     * XOR with 1 in little endian mode -- every word contains
                     * 2 16-bit pixels, thus they are swapped
                     */

                    pixel_offs = decode[(yi * 8 + xi) ^ 1] * 2;
                    rgb16 = *(UINT16 *) &src[pixel_offs];
                }
                else
                {
                    pixel_offs = decode[((yi ^ 1) * 8 + (xi ^ 1))];
                    gray8 = src[pixel_offs];
                }
            }
            else
            {
                if (bpp == 2)
                {
                    pixel_offs = decode[yi * 8 + xi] * 2;
                    rgb16 = (src[pixel_offs + 0] << 8) | src[pixel_offs + 1];
                }
                else
                {
                    pixel_offs = decode[yi * 8 + xi];
                    gray8 = src[pixel_offs + 0];
                }
            }

			/*
             * Store within the texture sheet
             */

            if (bpp == 2)
                *(UINT16 *) &texture_ram[((y + yi) * 2048 + (x + xi)) * 2] = rgb16;
            else
                texture_ram[(((y + yi) * 2048 + x) * 2) + xi] = gray8;
        }
    }
}

/*
 * store_texture():
 *
 * Writes a texture into the texture sheet. The pixel words are not decoded,
 * but the 16-bit pixels are converted into a common endianness (little.)
 * 8-bit pixels are not expanded into 16-bits.
 *
 * bpp (bytes per pixel) must be 1 or 2.
 */

static void store_texture(UINT x, UINT y, UINT w, UINT h, UINT8 *src, UINT bpp, BOOL little_endian)
{
    UINT    xi, yi;
	UINT bw;

	if(bpp == 2)
		bw = 1;
	else
		bw = 2;

    for (yi = 0; yi < h; yi += 8)
	{
        for (xi = 0; xi < w; xi += 8)
		{
            store_texture_tile(x + (xi / bw), y + yi, src, bpp, little_endian);
            src += 8 * 8 * bpp; // each texture tile is 8x8 and 16-bit color
		}
	}
}

/*
 * upload_texture():
 *
 * Uploads a texture to texture memory.
 */

// Mipmap starting positions for each level
static const int mipmap_xpos[11] =
{ 1024, 1536, 1792, 1920, 1984, 2016, 2032, 2040, 2044, 2046, 2047 };
static const int mipmap_ypos[11] =
{ 512, 768, 896, 960, 992, 1008, 1016, 1020, 1022, 1023, 0 };

// Mipmap size dividers
static const int mipmap_size[9] =
{ 2, 4, 8, 16, 32, 64, 128, 256, 512 };

static void upload_texture(UINT32 header, UINT8 *src, BOOL little_endian)
{    
	UINT    size_x, size_y, xpos, ypos, bit_depth, mip_ypos, page;
	int mipmap_num = 0;
	int mxpos, mypos, msize_x, msize_y;

	PROFILE_SECT_ENTRY("real3d");

    /*
     * Model 3 texture RAM appears as 2 2048x1024 textures. When textures are
     * uploaded, their size and position within a sheet is given. I treat the
     * texture sheet selection bit as an additional bit to the Y coordinate.
     */

    size_x = (header >> 14) & 7;
    size_y = (header >> 17) & 7;
    size_x = (32 << size_x);    // width in pixels
    size_y = (32 << size_y);    // height

    ypos = (((header >> 7) & 0x1F) | ((header >> 15) & 0x20)) * 32;
    xpos = ((header >> 0) & 0x3F) * 32;

	mip_ypos = ((header >> 7) & 0x1F) * 32;
	page = (header >> 15 & 0x20) ? 1 : 0;

	if( header & 0x00800000 )
		bit_depth = 2;		// 16-bit texture
	else
		bit_depth = 1;		// 8-bit texture

    LOG("texture.log", "%08X %d,%d\t%dx%d\n", header, xpos, ypos, size_x, size_y);

    /*
     * Render the texture into the texture buffer
     */

	switch( (header >> 24) & 0xF )
	{
		case 0:		// Texture with mipmaps
			store_texture(xpos, ypos, size_x, size_y, src, bit_depth, little_endian);
			osd_renderer_invalidate_textures(xpos, ypos, xpos, ypos, size_x, size_y, texture_ram, 0);

			msize_x = size_x;
			msize_y = size_y;

			// Store mipmaps
			while( msize_y > 8 && msize_x > 8 ) {

				src += (msize_x * msize_y * bit_depth);
				msize_x /= 2;
				msize_y /= 2;

				mxpos = mipmap_xpos[mipmap_num] + (xpos / mipmap_size[mipmap_num]);
				mypos = mipmap_ypos[mipmap_num] + (mip_ypos / mipmap_size[mipmap_num]);
				if(page)
					mypos += 1024;
				mipmap_num++;

				store_texture(mxpos, mypos, msize_x, msize_y, src, bit_depth, little_endian);
				//osd_renderer_invalidate_textures(mxpos, mypos, xpos, ypos, size_x, size_y, texture_ram, mipmap_num);
			}
			break;

		case 1:		// Texture without mipmaps
			store_texture(xpos, ypos, size_x, size_y, src, bit_depth, little_endian);
			osd_renderer_invalidate_textures(xpos, ypos, xpos, ypos, size_x, size_y, texture_ram, 0);
			break;

		case 2:		// Only mipmaps
			msize_x = size_x;
			msize_y = size_y;
			while( msize_y > 8 && msize_x > 8 ) {

				msize_x /= 2;
				msize_y /= 2;

				mxpos = mipmap_xpos[mipmap_num] + (xpos / mipmap_size[mipmap_num]);
				mypos = mipmap_ypos[mipmap_num] + (mip_ypos / mipmap_size[mipmap_num]);
				if(page)
					mypos += 1024;
				mipmap_num++;

				store_texture(mxpos, mypos, msize_x, msize_y, src, bit_depth, little_endian);
				//osd_renderer_invalidate_textures(mxpos, mypos, xpos, ypos, size_x, size_y, texture_ram, mipmap_num);

				src += (msize_x * msize_y * bit_depth);
			}
			break;
	}

    /*
     * Remove any existing textures that may have been overwritten
     */
    
    //osd_renderer_invalidate_textures(xpos, ypos, size_x, size_y);

	PROFILE_SECT_EXIT("real3d");
}

/******************************************************************/
/* Access                                                         */
/******************************************************************/

static BOOL     trigger = 0;
static UINT64   trigger_time;

/*
 * UINT32 r3d_read_32(UINT32 a);
 *
 * Reads a 32-bit word from the Real3D regions.
 *
 * Parameters:
 *      a = Address.
 *
 * Returns:
 *      Data read.
 */

UINT32 r3d_read_32(UINT32 a)
{
    static UINT32   _84000000 = 0;

//    message(0, "%08X (%08X): Real3D read32 to %08X", PPC_PC, PPC_LR, a);

    switch (a)
    {
    /*
     * In Lost World, routine at 0x1174E0 reads all the 0x840000XX status
     * registers and at 0x117A30, bit 0x02000000 is checked for.
     */

    case 0x84000000:
        return (_84000000 ^= 0xFFFFFFFF);
    case 0x84000004:    // unknown
    case 0x84000008:
    case 0x8400000C:
    case 0x84000010:
    case 0x84000014:
    case 0x84000018:
    case 0x8400001C:
    case 0x84000020:
        return 0xFFFFFFFF;
    }

    error("Unknown R3D read: %08X: %08X\n", ppc_get_pc(), a);
	return(0);
}

/*
 * void r3d_write_32(UINT32 a, UINT32 d);
 *
 * Writes a 32-bit word to the Real3D regions.
 *
 * Parameters:
 *      a = Address.
 *      d = Data to write.
 */

UINT32 _9C000000, _9C000004, _9C000008;
static int texture_start_pos = 8;
static int texture_ram_ptr = 0;

void r3d_write_32(UINT32 a, UINT32 d)
{
    static UINT32   last_addr;

    if (a >= 0x8E000000 && a <= 0x8E0FFFFF)         // culling RAM
    {
        *(UINT32 *) &culling_ram_8e[a & 0xFFFFF] = BSWAP32(d);
        return;
    }
    else if (a >= 0x8C000000 && a <= 0x8C3FFFFF)    // culling RAM
    {
        *(UINT32 *) &culling_ram_8c[a & 0x3FFFFF] = BSWAP32(d);
        return;
    }
    else if (a >= 0x98000000 && a <= 0x981FFFFF)    // polygon RAM
    {
//        if(a >= 0x98001000 && a < 0x98002000)
//            message(1, "color table: %08X = %08X", a, BSWAP32(d));

        *(UINT32 *) &polygon_ram[a & 0x1FFFFF] = BSWAP32(d);
        return;
    }
    else if (a >= 0x94000000 && a <= 0x940FFFFF)    // texture buffer
    {
        d = BSWAP32(d);
		*(UINT32 *)&texture_buffer_ram[texture_ram_ptr] = d;

		texture_ram_ptr += 4;
        return;
    }

    switch (a)
    {
    case 0x88000000:    // trigger?

        //message(0, "%08X (%08X): 88000000 = %08X", PPC_PC, PPC_LR, BSWAP32(d));

		if( texture_ram_ptr > 0 ) {
			int i=0;
			while( i < texture_ram_ptr ) {
				UINT32 length = (*(UINT32*)&texture_buffer_ram[i+0] / 2) + 2;
				UINT32 header = *(UINT32*)&texture_buffer_ram[i+4];
				upload_texture( header, &texture_buffer_ram[i+8], 1 );
				i += length;
			};
		}
		texture_ram_ptr = 0;

        return;
    case 0x90000000:    // VROM texture address
        vrom_texture_address = BSWAP32(d);
        LOG("model3.log", "VROM1 ADDR = %08X\n", BSWAP32(d));
        //message(0, "VROM texture address = %08X @ %08X (%08X)", BSWAP32(d), PPC_PC, PPC_LR);
        return;
    case 0x90000004:    
        vrom_texture_header = BSWAP32(d);
        LOG("model3.log", "VROM1 HEAD = %08X\n", BSWAP32(d));
        //message(0, "VROM texture header = %08X @ %08X (%08X)", BSWAP32(d), PPC_PC, PPC_LR);
        return;
    case 0x90000008:
        upload_texture(vrom_texture_header, &vrom[(vrom_texture_address & 0xFFFFFF) * 4], 1);
        LOG("model3.log", "VROM1 SIZE = %08X\n", BSWAP32(d));
        //message(0, "VROM texture length = %08X @ %08X (%08X)", BSWAP32(d), PPC_PC, PPC_LR);
        return;
    case 0x9000000C:    // ? Virtual On 2: These are almost certainly for VROM textures as well (I was too lazy to check :P)
        vrom_texture_address = BSWAP32(d);
        LOG("model3.log", "VROM2 ADDR = %08X\n", BSWAP32(d));
        //message(0, "90000000C = %08X", BSWAP32(d));
        return;
    case 0x90000010:    // ?
        vrom_texture_header = BSWAP32(d);
        LOG("model3.log", "VROM2 HEAD = %08X\n", BSWAP32(d));
        //message(0, "900000010 = %08X", BSWAP32(d));
        return;
    case 0x90000014:    // ?
        upload_texture(vrom_texture_header, &vrom[(vrom_texture_address & 0xFFFFFF) * 4], 1);
        LOG("model3.log", "VROM2 SIZE = %08X\n", BSWAP32(d));
        //message(0, "900000014 = %08X", BSWAP32(d));
        return;
    case 0x9C000000:    // ?
        //message(0, "9C000000 = %08X", BSWAP32(d));
        LOG("model3.log", "%08X = %08X\n", a, d);
		_9C000000 = BSWAP32(d);
        return;
    case 0x9C000004:    // ?
        //message(0, "9C000004 = %08X", BSWAP32(d));
        LOG("model3.log", "%08X = %08X\n", a, d);
		_9C000004 = BSWAP32(d);
        return;
    case 0x9C000008:    // ?
        //message(0, "9C000008 = %08X", BSWAP32(d));
        LOG("model3.log", "%08X = %08X\n", a, d);
		_9C000008 = BSWAP32(d);
        return;
    }

    error("Unknown R3D write: %08X: %08X = %08X\n", ppc_get_pc(), a, d);
}

void r3d_dma_culling_ram_8c(UINT32 *src, UINT32 dst, int length, BOOL swap_words)
{
	int i;

	dst &= 0xffffff;

	if (swap_words)
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = BSWAP32(*src++);
			*(UINT32 *)&culling_ram_8c[dst] = d;
			dst += 4;
		}
	}
	else
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = (*src++);
			*(UINT32 *)&culling_ram_8c[dst] = d;
			dst += 4;
		}
	}
}

void r3d_dma_culling_ram_8e(UINT32 *src, UINT32 dst, int length, BOOL swap_words)
{
	int i;

	dst &= 0xffffff;

	if (swap_words)
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = BSWAP32(*src++);
			*(UINT32 *)&culling_ram_8e[dst] = d;
			dst += 4;
		}
	}
	else
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = (*src++);
			*(UINT32 *)&culling_ram_8e[dst] = d;
			dst += 4;
		}
	}
}

void r3d_dma_polygon_ram(UINT32 *src, UINT32 dst, int length, BOOL swap_words)
{
	int i;

	dst &= 0xffffff;

	if (swap_words)
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = BSWAP32(*src++);
			*(UINT32 *) &polygon_ram[dst] = d;
			dst += 4;
		}
	}
	else
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = (*src++);
			*(UINT32 *) &polygon_ram[dst] = d;
			dst += 4;
		}
	}
}

void r3d_dma_texture_ram(UINT32 *src, UINT32 dst, int length, BOOL swap_words)
{
	int i;

	dst &= 0xffffff;

	if (swap_words)
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = BSWAP32(*src++);
			*(UINT32 *)&texture_buffer_ram[texture_ram_ptr] = d;
			texture_ram_ptr += 4;
		}
	}
	else
	{
		for (i=0; i < length; i+=4)
		{
			UINT32 d = (*src++);
			*(UINT32 *)&texture_buffer_ram[texture_ram_ptr] = d;
			texture_ram_ptr += 4;
		}
	}
}

/******************************************************************/
/* Real3D TAP Port                                                */
/******************************************************************/

/*
 * State (corresponding to fsm[][] Y) and Instruction Names
 */

static char *state_name[] = { "Test-Logic/Reset", "Run-Test/Idle", "Select-DR-Scan",
                              "Capture-DR", "Shift-DR", "Exit1-DR", "Pause-DR",
                              "Exit2-DR", "Update-DR", "Select-IR-Scan",
                              "Capture-IR", "Shift-IR", "Exit1-IR", "Pause-IR",
                              "Exit2-IR", "Update-IR"
                            };

/*
 * TAP Finite State Machine
 *
 * Y are states and X are outgoing paths. Constructed from information on page
 * 167 of the 3D-RAM manual.
 */

#define NEXT(new_state) fsm[state][new_state]

static INT  state;  // current state
static INT  fsm[][2] =  {
                            {  1,  0 },  // 0  Test-Logic/Reset
                            {  1,  2 },  // 1  Run-Test/Idle
                            {  3,  9 },  // 2  Select-DR-Scan
                            {  4,  5 },  // 3  Capture-DR
                            {  4,  5 },  // 4  Shift-DR
                            {  6,  8 },  // 5  Exit1-DR
                            {  6,  7 },  // 6  Pause-DR
                            {  4,  8 },  // 7  Exit2-DR
                            {  1,  2 },  // 8  Update-DR
                            { 10,  0 },  // 9  Select-IR-Scan
                            { 11, 12 },  // 10 Capture-IR
                            { 11, 12 },  // 11 Shift-IR
                            { 13, 15 },  // 12 Exit1-IR
                            { 13, 14 },  // 13 Pause-IR
                            { 11, 15 },  // 14 Exit2-IR
                            {  1,  2 }   // 15 Update-IR
                        };

/*
 * TAP Registers
 */

static UINT64   current_instruction;    // latched IR (not always equal to IR)
static UINT64   ir;                     // instruction register (46 bits)

static UINT8    id_data[32];            // ASIC ID code data buffer
static INT      id_size;                // size of ID data in bits
static INT      ptr;                    // current bit ptr for data

static BOOL     tdo;                    // bit shifted out to TDO

/*
 * insert_bit():
 *
 * Inserts a bit into an arbitrarily long bit field. Bit 0 is assumed to be
 * the MSB of the first byte in the buffer.
 */

static void insert_bit(UINT8 *buf, INT bit_num, INT bit)
{
    INT bit_in_byte;

    bit_in_byte = 7 - (bit_num & 7);

    buf[bit_num / 8] &= ~(1 << bit_in_byte);
    buf[bit_num / 8] |= (bit << bit_in_byte);
}

/*
 * insert_id():
 *
 * Inserts a 32-bit ID code into the ID bit field.
 */

static void insert_id(UINT32 id, INT start_bit)
{
    INT i;

    for (i = 31; i >= 0; i--)
        insert_bit(id_data, start_bit++, (id >> i) & 1);
}

/*
 * shift():
 *
 * Shifts the data buffer right (towards LSB at byte 0) by 1 bit. The size of
 * the number of bits must be specified. The bit shifted out of the LSB is
 * returned.
 */

static BOOL shift(UINT8 *data, INT num_bits)
{
    INT     i;
    BOOL    shift_out, shift_in;

    /*
     * This loop takes care of all the fully-filled bytes
     */

    shift_in = 0;
    for (i = 0; i < num_bits / 8; i++)
    {
        shift_out = data[i] & 1;
        data[i] >>= 1;
        data[i] |= (shift_in << 7);
        shift_in = shift_out;   // carry over to next element's MSB
    }

    /*
     * Take care of the last partial byte (if there is one)
     */

    if ((num_bits & 7) != 0)
    {
        shift_out = (data[i] >> (8 - (num_bits & 7))) & 1;
        data[i] >>= 1;
        data[i] |= (shift_in << 7);
    }

    return shift_out;
}

/*
 * BOOL tap_read(void);
 *
 * Reads TDO.
 *
 * Returns:
 *      TDO.
 */

BOOL tap_read(void)
{
    return tdo;
}

/*
 * void tap_write(BOOL tck, BOOL tms, BOOL tdi, BOOL trst);
 *
 * Writes to the TAP. State changes only occur on the rising edge of the clock
 * (tck = 1.)
 *
 * Parameters:
 *      tck  = Clock.
 *      tms  = Test mode select.
 *      tdi  = Serial data input. Must be 0 or 1 only!
 *      trst = Reset.
 */

void tap_write(BOOL tck, BOOL tms, BOOL tdi, BOOL trst)
{
    if (!tck)
        return;

    state = NEXT(tms);

    switch (state)
    {
    case 3:     // Capture-DR

        /*
         * Read ASIC IDs.
         *
         * The ID Sequence is:
         *  - Jupiter
         *  - Mercury
         *  - Venus
         *  - Earth
         *  - Mars
         *  - Mars (again)
         *
         * Note that different Model 3 steps have different chip
         * revisions, hence the different IDs returned below.
         *
         * On Step 1.5 and 1.0, instruction 0x0C631F8C7FFE is used to retrieve
         * the ID codes but Step 2.0 is a little weirder. It seems to use this
         * and either the state of the TAP after reset or other instructions
         * to read the IDs as well. This can be emulated in one of 2 ways:
         * Ignore the instruction and always load up the data or load the
         * data on TAP reset and when the instruction is issued.
         */

        if (m3_config.step == 0x10)
        {
            insert_id(0x116C7057, 1 + 0 * 32);
            insert_id(0x216C3057, 1 + 1 * 32);
            insert_id(0x116C4057, 1 + 2 * 32);
            insert_id(0x216C5057, 1 + 3 * 32);
            insert_id(0x116C6057, 1 + 4 * 32 + 1);
            insert_id(0x116C6057, 1 + 5 * 32 + 1);
        }
        else if (m3_config.step == 0x15)
        {
            insert_id(0x316C7057, 1 + 0 * 32);
            insert_id(0x316C3057, 1 + 1 * 32);
            insert_id(0x216C4057, 1 + 2 * 32);      // Lost World may to use 0x016C4057
            insert_id(0x316C5057, 1 + 3 * 32);
            insert_id(0x216C6057, 1 + 4 * 32 + 1);
            insert_id(0x216C6057, 1 + 5 * 32 + 1);
        }
        else if (m3_config.step >= 0x20)
        {
            insert_id(0x416C7057, 1 + 0 * 32);
            insert_id(0x416C3057, 1 + 1 * 32);
            insert_id(0x316C4057, 1 + 2 * 32);
            insert_id(0x416C5057, 1 + 3 * 32);
            insert_id(0x316C6057, 1 + 4 * 32 + 1);
            insert_id(0x316C6057, 1 + 5 * 32 + 1);
        }

        break;

    case 4:     // Shift-DR

        tdo = shift(id_data, id_size);
        break;

    case 10:    // Capture-IR

        /*
         * Load lower 2 bits with 01 as per IEEE 1149.1-1990
         */

        ir = 1;
        break;

    case 11:    // Shift-IR

        /*
         * Shift IR towards output and load in new data from TDI
         */

        tdo = ir & 1;   // shift LSB to output
        ir >>= 1;
        ir |= ((UINT64) tdi << 45);
        break;

    case 15:    // Update-IR

        /*
         * Latch IR (technically, this should occur on the falling edge of
         * TCK)
         */

        ir &= 0x3fffffffffff;
        current_instruction = ir;

#if 0
        {
            UINT8   *i = (UINT8 *) &ir;
            LOG("tap.log", "current instruction set: %02X%02X%02X%02X%02X%02X\n", i[5], i[4], i[3], i[2], i[1], i[0]);
        }
#endif

        break;

    default:
        break;
    }

#if 0
    if (state == 4)
        LOG("tap.log", "state: Shift-DR %d\n", tdi);
    else if (state == 11)
        LOG("tap.log", "state: Shift-IR %d\n", tdi);
    else
        LOG("tap.log", "state: %s\n", state_name[state]);
#endif
}


/*
 * void tap_reset(void);
 *
 * Resets the TAP (simulating a power up or SCAN_RST signal.)
 */

void tap_reset(void)
{
    id_size = 197;  // 197 bits

    state = 0;  // test-logic/reset
}