/*
 * 86Box	A hypervisor and IBM PC system emulator that specializes in
 *		running old operating systems and software designed for IBM
 *		PC systems and compatibles from 1981 through fairly recent
 *		system designs based on the PCI bus.
 *
 *		This file is part of the 86Box distribution.
 *
 *		Implementation of the OPTi 82C546/82C547(Python) & 82C596/82C597(Cobra) chipsets.

 * Authors:	plant/nerd73,
 *          Miran Grca, <mgrca8@gmail.com>
 *          Tiseno100
 *
 * Copyright 2020 plant/nerd73.
 * Copyright 2020 Miran Grca.
 * Copyright 2021 Tiseno100.
 */
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/timer.h>
#include <86box/io.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/port_92.h>
#include <86box/chipset.h>

typedef struct
{
    uint8_t idx, regs[16];
} opti5x7_t;

#ifdef ENABLE_OPTI5X7_LOG
int opti5x7_do_log = ENABLE_OPTI5X7_LOG;

static void
opti5x7_log(const char *fmt, ...)
{
    va_list ap;

    if (opti5x7_do_log)
    {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#define opti5x7_log(fmt, ...)
#endif

static void
opti5x7_shadow_map(int cur_reg, opti5x7_t *dev)
{

/*
Register 4h: Cxxxx Segment
Register 5h: Dxxxx Segment

Bits 7-6: xC000-xFFFF
Bits 5-4: x8000-xBFFF
Bits 3-2: x4000-x7FFF
Bits 0-1: x0000-x3FFF

     x-y
     0 0 Read/Write AT bus
     1 0 Read from AT - Write to DRAM
     1 1 Read from DRAM - Write to DRAM
     0 1 Read from DRAM (write protected)
*/
    if (cur_reg == 0x06)
    {
        mem_set_mem_state_both(0xe0000, 0x10000, ((dev->regs[6] & 1) ? MEM_READ_INTERNAL : MEM_READ_EXTANY) | ((dev->regs[6] & 2) ? MEM_WRITE_INTERNAL : MEM_WRITE_EXTANY));
        mem_set_mem_state_both(0xf0000, 0x10000, ((dev->regs[6] & 4) ? MEM_READ_INTERNAL : MEM_READ_EXTANY) | ((dev->regs[6] & 8) ? MEM_WRITE_INTERNAL : MEM_WRITE_EXTANY));
    }
    else
    {
        for (int i = 0; i < 4; i++)
            mem_set_mem_state_both(0xc0000 + ((cur_reg & 1) << 16) + (i << 14), 0x4000, ((dev->regs[cur_reg] & (1 << (2 * i))) ? MEM_READ_INTERNAL : MEM_READ_EXTANY) | ((dev->regs[cur_reg] & (2 << (2 * i))) ? MEM_WRITE_INTERNAL : MEM_WRITE_EXTANY));
    }

    flushmmucache_nopc();
}

static void
opti5x7_write(uint16_t addr, uint8_t val, void *priv)
{
    opti5x7_t *dev = (opti5x7_t *)priv;

    switch (addr)
    {
    case 0x22:
        dev->idx = val;
        break;
    case 0x24:
        switch (dev->idx)
        {
        case 0x00: /* DRAM Configuration Register #1 */
            dev->regs[dev->idx] = val & 0x7f;
            break;
        case 0x01: /* DRAM Control Register #1 */
            dev->regs[dev->idx] = val;
            break;
        case 0x02:  /* Cache Control Register #1 */
            dev->regs[dev->idx] = val;
            cpu_cache_ext_enabled = !!(dev->regs[0x02] & 0x0c);
            cpu_update_waitstates();
            break;
        case 0x03: /* Cache Control Register #2 */
            dev->regs[dev->idx] = val;
            break;
        case 0x04: /* Shadow RAM Control Register #1 */
        case 0x05: /* Shadow RAM Control Register #2 */
        case 0x06: /* Shadow RAM Control Register #3 */
            dev->regs[dev->idx] = val;
            opti5x7_shadow_map(dev->idx, dev);
            break;
        case 0x07: /* Tag Test Register */
        case 0x08: /* CPU Cache Control Register #1 */
        case 0x09: /* System Memory Function Register #1 */
        case 0x0a: /* System Memory Address Decode Register #1 */
        case 0x0b: /* System Memory Address Decode Register #2 */
            dev->regs[dev->idx] = val;
            break;
        case 0x0c: /* Extended DMA Register */
            dev->regs[dev->idx] = val & 0xcf;
            break;
        case 0x0d: /* ROMCS# Register */
        case 0x0e: /* Local Master Preemption Register */
        case 0x0f: /* Deturbo Control Register #1 */
        case 0x10: /* Cache Write-Hit Control Register */
        case 0x11: /* Master Cycle Control Register */
            dev->regs[dev->idx] = val;
            break;
        }
        opti5x7_log("OPTi 5x7: dev->regs[%02x] = %02x\n", dev->idx, dev->regs[dev->idx]);
        break;
    }
}

static uint8_t
opti5x7_read(uint16_t addr, void *priv)
{
    opti5x7_t *dev = (opti5x7_t *)priv;

    return (addr == 0x24) ? dev->regs[dev->idx] : 0xff;
}

static void
opti5x7_close(void *priv)
{
    opti5x7_t *dev = (opti5x7_t *)priv;

    free(dev);
}

static void *
opti5x7_init(const device_t *info)
{
    opti5x7_t *dev = (opti5x7_t *)malloc(sizeof(opti5x7_t));
    memset(dev, 0, sizeof(opti5x7_t));

    io_sethandler(0x0022, 0x0001, opti5x7_read, NULL, NULL, opti5x7_write, NULL, NULL, dev);
    io_sethandler(0x0024, 0x0001, opti5x7_read, NULL, NULL, opti5x7_write, NULL, NULL, dev);

    device_add(&port_92_device);

    return dev;
}

const device_t opti5x7_device = {
    .name = "OPTi 82C5x6/82C5x7",
    .internal_name = "opti5x7",
    .flags = 0,
    .local = 0,
    .init = opti5x7_init,
    .close = opti5x7_close,
    .reset = NULL,
    { .available = NULL },
    .speed_changed = NULL,
    .force_redraw = NULL,
    .config = NULL
};
