#include "ibm.h"
#include "mem.h"
#include "video.h"
#include "vid_svga.h"
#include "vid_tvp3026_ramdac.h"

#define REG_DCC 0x09
#define REG_CURSOR_DATA 0x0b
#define REG_CURSOR_XL 0x0c
#define REG_CURSOR_XH 0x0d
#define REG_CURSOR_YL 0x0e
#define REG_CURSOR_YH 0x0f

#define IREG_ICC 0x06
#define REG_MSC 0x1e

#define DCC_MODE_SEL_MASK (0x03)
#define DCC_MODE_SEL_3_COLOUR (0x01)
#define DCC_MODE_SEL_XGA (0x02)
#define DCC_MODE_SEL_XWINDOWS (0x03)
#define CCR_ADDR_SHIFT (2)
#define CCR_ADDR_MASK (3 << CCR_ADDR_SHIFT)

#define MSC_PAL_DEPTH (1 << 3)

/*Update SVGA hwcursor variables with RAMDAC cursor. Should only be called when
  ramdac->cursor_enabled is set, otherwise the host SVGA board controls the cursor*/
static void update_cursor(tvp3026_ramdac_t *ramdac, svga_t *svga) {
        //        pclog("update_cursor: %02x\n", ramdac->regs[REG_DCC]);
        if (ramdac->cursor_control & DCC_MODE_SEL_MASK) {
                svga->hwcursor.ena = 1;
                if (ramdac->cursor_x >= 64) {
                        svga->hwcursor.xoff = 0;
                        svga->hwcursor.x = ramdac->cursor_x - 64;
                } else {
                        svga->hwcursor.xoff = 64 - ramdac->cursor_x;
                        svga->hwcursor.x = 0;
                }
                if (ramdac->cursor_y >= 64) {
                        svga->hwcursor.yoff = 0;
                        svga->hwcursor.addr = 0;
                        svga->hwcursor.y = ramdac->cursor_y - 64;
                } else {
                        svga->hwcursor.yoff = 64 - ramdac->cursor_y;
                        svga->hwcursor.addr = svga->hwcursor.yoff * 8;
                        svga->hwcursor.y = 0;
                }
                ramdac->cursor_mode = ramdac->cursor_control & DCC_MODE_SEL_MASK;
                //                pclog("     x=%i y=%i\n", svga->hwcursor.x, svga->hwcursor.y);
        } else
                svga->hwcursor.ena = 0;
}

void tvp3026_ramdac_out(uint16_t addr, uint8_t val, tvp3026_ramdac_t *ramdac, svga_t *svga) {
        //        pclog("tvp3026_ramdac_out: addr=%04x val=%02x\n", addr, val);
        switch (addr) {
        case 0x0:
                ramdac->reg_idx = val;
        case 0x1:
                svga_out(addr + 0x3c8, val, svga);
                break;
        case 0x2:
        case 0x3:
                svga_out(addr + 0x3c4, val, svga);
                break;

        case 0x4:
                ramdac->cursor_pal_write = val;
                ramdac->cursor_pal_pos = 0;
                break;
        case 0x5:
                svga->fullchange = changeframecount;
                switch (ramdac->cursor_pal_pos) {
                case 0:
                        ramdac->cursor_pal_r = val;
                        ramdac->cursor_pal_pos++;
                        break;
                case 1:
                        ramdac->cursor_pal_g = val;
                        ramdac->cursor_pal_pos++;
                        break;
                case 2:
                        ramdac->cursor_pal[ramdac->cursor_pal_write].r = ramdac->cursor_pal_r;
                        ramdac->cursor_pal[ramdac->cursor_pal_write].g = ramdac->cursor_pal_g;
                        ramdac->cursor_pal[ramdac->cursor_pal_write].b = val;
                        ramdac->cursor_pal_look[ramdac->cursor_pal_write] = makecol32(
                                ramdac->cursor_pal[ramdac->cursor_pal_write].r, ramdac->cursor_pal[ramdac->cursor_pal_write].g,
                                ramdac->cursor_pal[ramdac->cursor_pal_write].b);
                        ramdac->cursor_pal_pos = 0;
                        ramdac->cursor_pal_write = (ramdac->cursor_pal_write + 1) & 3;
                        break;
                }
                break;
        case 0x7:
                ramdac->cursor_pal_read = val;
                ramdac->cursor_pal_pos = 0;
                break;

        case REG_DCC:
                ramdac->cursor_control = (ramdac->cursor_control & ~DCC_MODE_SEL_MASK) | (val & DCC_MODE_SEL_MASK);
                update_cursor(ramdac, svga);
                break;

        case REG_CURSOR_DATA:
                ramdac->cursor_data[svga->dac_write + (((ramdac->cursor_control & CCR_ADDR_MASK) >> CCR_ADDR_SHIFT) << 8)] = val;
                svga->dac_write = (svga->dac_write + 1) & 0xff;
                if (!svga->dac_write)
                        ramdac->cursor_control = (ramdac->cursor_control & ~CCR_ADDR_MASK) |
                                                 ((ramdac->cursor_control + (1 << CCR_ADDR_SHIFT)) & CCR_ADDR_MASK);
                break;

        case REG_CURSOR_XL:
                ramdac->cursor_x = val | (ramdac->cursor_x & 0xf00);
                update_cursor(ramdac, svga);
                break;
        case REG_CURSOR_XH:
                ramdac->cursor_x = ((val & 0xf) << 8) | (ramdac->cursor_x & 0xff);
                update_cursor(ramdac, svga);
                break;
        case REG_CURSOR_YL:
                ramdac->cursor_y = val | (ramdac->cursor_y & 0xf00);
                update_cursor(ramdac, svga);
                break;
        case REG_CURSOR_YH:
                ramdac->cursor_y = ((val & 0xf) << 8) | (ramdac->cursor_y & 0xff);
                update_cursor(ramdac, svga);
                break;

        case 0xa:
                switch (ramdac->reg_idx) {
                case IREG_ICC:
                        ramdac->cursor_control = val;
                        update_cursor(ramdac, svga);
                        break;

                case 0x18: /*True color control*/
                        /*bit 7 = pseudo-color mode
                          bit 6 = true-color mode
                          bit 4 = 24-bit colour mode
                          bit 3 = alternate packing on 24-bit formats*/
                        if (val & 0x80)
                                svga->bpp = 8;
                        else
                                switch (val & 0x1f) {
                                case 0x04: /*ORGB 1555*/
                                        svga->bpp = 15;
                                        break;
                                case 0x05: /*RGB 565*/
                                        svga->bpp = 16;
                                        break;
                                case 0x06: /*ORGB 8888*/
                                        svga->bpp = 32;
                                        break;
                                case 0x16:
                                case 0x1e: /*RGB 888*/
                                        svga->bpp = 24;
                                        break;

                                case 0x01: /*RGBO 4444 - not implemented*/
                                case 0x03: /*RGB 664 - not implemented*/
                                case 0x07: /*BGRO 8888 - not implemented*/
                                case 0x17: /*BGR 888 - not implemented*/
                                case 0x1f: /*BGR 888 - not implemented*/
                                        break;
                                }
                        svga_recalctimings(svga);
                        //                        pclog("bpp=%i\n", svga->bpp);
                        break;

                case REG_MSC:
                        svga_set_ramdac_type(svga, (val & MSC_PAL_DEPTH) ? RAMDAC_8BIT : RAMDAC_6BIT);
                        break;

                case 0x2d: /*Pixel clock PLL data*/
                        switch (ramdac->regs[0x2c] & 3) {
                        case 0:
                                ramdac->pix.n = val;
                                break;
                        case 1:
                                ramdac->pix.m = val;
                                break;
                        case 2:
                                ramdac->pix.p = val;
                                break;
                        }
                        ramdac->regs[0x2c] = ((ramdac->regs[0x2c] + 1) & 3) | (ramdac->regs[0x2c] & 0xfc);
                        break;

                case 0x2e: /*Memory clock PLL data*/
                        switch ((ramdac->regs[0x2c] >> 2) & 3) {
                        case 0:
                                ramdac->mem.n = val;
                                break;
                        case 1:
                                ramdac->mem.m = val;
                                break;
                        case 2:
                                ramdac->mem.p = val;
                                break;
                        }
                        ramdac->regs[0x2c] = ((ramdac->regs[0x2c] + 4) & 0x0c) | (ramdac->regs[0x2c] & 0xf3);
                        break;

                case 0x2f: /*Loop clock PLL data*/
                        switch ((ramdac->regs[0x2c] >> 4) & 3) {
                        case 0:
                                ramdac->loop.n = val;
                                break;
                        case 1:
                                ramdac->loop.m = val;
                                break;
                        case 2:
                                ramdac->loop.p = val;
                                break;
                        }
                        ramdac->regs[0x2c] = ((ramdac->regs[0x2c] + 0x10) & 0x30) | (ramdac->regs[0x2c] & 0xcf);
                        break;
                }

                switch (ramdac->reg_idx) {
                case 0x06:
                case 0x0f:
                case 0x18:
                case 0x19:
                case 0x1a:
                case 0x1c:
                case 0x1d:
                case 0x1e:
                case 0x2a:
                case 0x2b:
                case 0x2c:
                case 0x2d:
                case 0x2e:
                case 0x2f:
                case 0x30:
                case 0x31:
                case 0x32:
                case 0x33:
                case 0x34:
                case 0x35:
                case 0x36:
                case 0x37:
                case 0x38:
                case 0x39:
                case 0x3a:
                case 0x3e:
                case 0xff:
                        ramdac->regs[ramdac->reg_idx] = val;
                        break;
                }
                //                pclog("RAMDAC[%02x]=%02x\n", ramdac->reg_idx, val);
                break;
        }
}

uint8_t tvp3026_ramdac_in(uint16_t addr, tvp3026_ramdac_t *ramdac, svga_t *svga) {
        uint8_t ret = 0xff;

        switch (addr) {
        case 0x0:
        case 0x1:
                ret = svga_in(addr + 0x3c8, svga);
                break;
        case 0x2:
                ret = 0xff;
                break;
        case 0x3:
                ret = svga_in(addr + 0x3c4, svga);
                break;

        case 0x4:
                ret = ramdac->cursor_pal_write;
                break;
        case 0x5:
                switch (ramdac->cursor_pal_pos) {
                case 0:
                        ramdac->cursor_pal_pos++;
                        return ramdac->cursor_pal[ramdac->cursor_pal_read].r;
                case 1:
                        ramdac->cursor_pal_pos++;
                        return ramdac->cursor_pal[ramdac->cursor_pal_read].g;
                case 2:
                        ramdac->cursor_pal_pos = 0;
                        ramdac->cursor_pal_read = (ramdac->cursor_pal_read + 1) & 3;
                        return ramdac->cursor_pal[(ramdac->cursor_pal_read - 1) & 3].b;
                }
                break;
        case 0x7:
                ret = ramdac->cursor_pal_read;
                break;

        case 0xa:
                switch (ramdac->reg_idx) {
                case 0x2d: /*Pixel clock PLL data*/
                        switch (ramdac->regs[0x2c] & 3) {
                        case 0:
                                ret = ramdac->pix.n;
                                break;
                        case 1:
                                ret = ramdac->pix.m;
                                break;
                        case 2:
                                ret = ramdac->pix.p;
                                break;
                        case 3:
                                ret = 0x40; /*PLL locked to frequency*/
                                break;
                        }
                        break;

                case 0x2e: /*Memory clock PLL data*/
                        switch ((ramdac->regs[0x2c] >> 2) & 3) {
                        case 0:
                                ret = ramdac->mem.n;
                                break;
                        case 1:
                                ret = ramdac->mem.m;
                                break;
                        case 2:
                                ret = ramdac->mem.p;
                                break;
                        case 3:
                                ret = 0x40; /*PLL locked to frequency*/
                                break;
                        }
                        break;

                case 0x2f: /*Loop clock PLL data*/
                        switch ((ramdac->regs[0x2c] >> 4) & 3) {
                        case 0:
                                ret = ramdac->loop.n;
                                break;
                        case 1:
                                ret = ramdac->loop.m;
                                break;
                        case 2:
                                ret = ramdac->loop.p;
                                break;
                        case 3:
                                ret = 0x40; /*PLL locked to frequency*/
                                break;
                        }
                        break;

                default:
                        ret = ramdac->regs[ramdac->reg_idx];
                        break;
                }
                //                pclog("Read RAMDAC[%02x]=%02x\n", ramdac->reg_idx, ret);
                break;
        }
        //        pclog("tvp3026_ramdac_in: addr=%04x ret=%02x\n", addr, ret);
        return ret;
}

float tvp3026_getclock(tvp3026_ramdac_t *ramdac) {
        /*Fvco = 8 x Fref x (65 - M) / (65 - N)*/
        /*Fpll = Fvco / 2^P*/
        int n = ramdac->pix.n & 0x3f;
        int m = ramdac->pix.m & 0x3f;
        int p = ramdac->pix.p & 0x03;
        double f_vco = 8.0 * 14318184 * (double)(65 - m) / (double)(65 - n);
        double f_pll = f_vco / (double)(1 << p);

        //        pclog("f_pll=%g %i\n", f_pll, (int)f_pll);
        return f_pll;
}

void tvp3026_set_cursor_enable(tvp3026_ramdac_t *ramdac, svga_t *svga, int ena) {
        ramdac->cursor_ena = ena;
        if (ena)
                update_cursor(ramdac, svga);
}

void tvp3026_hwcursor_draw(tvp3026_ramdac_t *ramdac, svga_t *svga, int displine) {
        int offset = (svga->hwcursor_latch.x + 32) - svga->hwcursor.xoff;
        int x;
        uint8_t *cursor_data = &ramdac->cursor_data[svga->hwcursor_latch.addr];

        for (x = 0; x < 64; x++) {
                if ((offset + x) >= 32) {
                        int p0 = cursor_data[x >> 3] & (0x80 >> (x & 7));
                        int p1 = cursor_data[(x >> 3) + 512] & (0x80 >> (x & 7));

                        switch (ramdac->cursor_mode) {
                        case DCC_MODE_SEL_3_COLOUR:
                                if (p0 || p1) {
                                        int col = (p0 ? 1 : 0) | (p1 ? 2 : 0);
                                        ((uint32_t *)buffer32->line[displine])[x + offset] = ramdac->cursor_pal_look[col];
                                }
                                break;

                        case DCC_MODE_SEL_XGA:
                                if (!p1)
                                        ((uint32_t *)buffer32->line[displine])[x + offset] = ramdac->cursor_pal_look[p0 ? 2 : 1];
                                else if (p0)
                                        ((uint32_t *)buffer32->line[displine])[x + offset] ^= 0xffffff;
                                break;

                        case DCC_MODE_SEL_XWINDOWS:
                                if (p1)
                                        ((uint32_t *)buffer32->line[displine])[x + offset] = ramdac->cursor_pal_look[p0 ? 2 : 1];
                                break;
                        }
                }
        }
        svga->hwcursor_latch.addr += 8;
}

void tvp3026_init(tvp3026_ramdac_t *ramdac) {
        ramdac->regs[0x0f] = 0x06; /*Latch control*/
        ramdac->regs[0x18] = 0x80; /*True color control*/
        ramdac->regs[0x19] = 0x80; /*Multiplex control*/
        ramdac->regs[0x1a] = 0x80; /*Clock selection*/
        ramdac->regs[0x39] = 0x18; /*MCLK / Loop clock control*/
        ramdac->regs[0x3f] = 0x26; /*ID*/
}
