/*Arculator 2.2 by Sarah Walker
  Podule subsystem*/
#include <stdlib.h>
#include <string.h>

#include "arc.h"
#include "colourcard.h"
#include "config.h"
#include "g16.h"
#include "ide_a3in.h"
#include "ide_idea.h"
#include "ide_riscdev.h"
#include "ide_zidefs.h"
#include "ide_zidefs_a3k.h"
#include "ioc.h"
#include "podules.h"
#include "riscdev_hdfc.h"
#include "st506_akd52.h"
#include "timer.h"

static uint8_t backplane_mask;
static void podule_run_timer(void *p);

typedef struct podule_list
{
	const podule_header_t *header;
	struct podule_list *next;
} podule_list;

static podule_list *podule_list_head = NULL;

static const podule_header_t *(*internal_podules[])(const podule_callbacks_t *callbacks, char *path) =
{
	/*16-bit podules*/
	akd52_probe,
	colourcard_probe,
	g16_probe,
	idea_ide_probe,
	riscdev_hdfc_probe,
	riscdev_ide_probe,
	zidefs_ide_probe,

	/*8-bit minipodules*/
	ics_a3inv5_ide_probe,
	zidefs_a3k_ide_probe
};

#define NR_INTERNAL_PODULES (sizeof(internal_podules) / sizeof(internal_podules[0]))
static int nr_podules = 0;

/*Podules -
  0 is reserved for extension ROMs
  1 is for additional IDE interface
  2-3 are free*/
static podule_internal_state_t podules[4];
static const podule_functions_t *podule_functions[4];
char podule_names[4][16];

void podule_add(const podule_header_t *header)
{
	podule_list *current = malloc(sizeof(podule_list));
	podule_list *last_entry = podule_list_head;
	podule_list *prev_entry = NULL;

	current->header = header;
	current->next = NULL;

	if (!last_entry)
		podule_list_head = current;
	else while (last_entry)
	{
		if (strcasecmp(header->name, last_entry->header->name) < 0)
		{
			current->next = last_entry;
			if (prev_entry)
			{
				/*Insert before last_entry*/
				prev_entry->next = current;
			}
			else
			{
				/*Insert as head of list*/
				podule_list_head = current;
			}
			break;
		}

		if (!last_entry->next)
		{
			/*Insert at end of list*/
			last_entry->next = current;
			break;
		}

		prev_entry = last_entry;
		last_entry = last_entry->next;
	}

	nr_podules++;
}

void podule_build_list(void)
{
	int c;

	for (c = 0; c < NR_INTERNAL_PODULES; c++)
	{
		const podule_header_t *header = internal_podules[c](&podule_callbacks_def, exname);

		if (header)
			podule_add(header);
	}
}

const char *podule_get_name(int c)
{
	podule_list *current = podule_list_head;

	while (c--)
	{
		current = current->next;
		if (!current)
			return NULL;
	}

	return current->header->name;
}

const char *podule_get_short_name(int c)
{
	podule_list *current = podule_list_head;

	while (c--)
	{
		current = current->next;
		if (!current)
			return NULL;
	}

	return current->header->short_name;
}

uint32_t podule_get_flags(int c)
{
	podule_list *current = podule_list_head;

	while (c--)
	{
		current = current->next;
		if (!current)
			return 0;
	}

	return current->header->flags;
}

const podule_header_t *podule_find(const char *short_name)
{
	podule_list *current = podule_list_head;

	while (current)
	{
		if (!strcmp(short_name, current->header->short_name))
			return current->header;
		current = current->next;
	}

	return NULL;
}

void podules_init(void)
{
	int c;

	podules_close();

	for (c = 0; c < 4; c++)
	{
		const podule_header_t *header = podule_find(podule_names[c]);

		memset(&podules[c], 0, sizeof(podule_internal_state_t));

		if (header)
		{
			podules[c].podule.header = header;
			podule_functions[c] = &header->functions;

			if (podule_functions[c]->init)
			{
				int ret = podule_functions[c]->init(&podules[c].podule);

				if (ret)
				{
					/*Podule init failed, clear structs*/
					rpclog("Failed to init podule %i : %s\n", c, header->short_name);
					arc_print_error("Failed to initialise podule %i :\n%s", c, header->name);
					podules[c].podule.header = NULL;
					podule_functions[c] = NULL;
				}
				else
				{
					timer_add(&podules[c].timer, podule_run_timer, (void *)(intptr_t)c, 1);
					podules[c].last_callback_tsc = tsc;
				}
			}
		}
	}
}

uint32_t podule_validate_and_get_valid_flags(podule_header_t *header)
{
	switch (header->version)
	{
		case MAKE_PODULE_API_VERSION(1, 1):
		return PODULE_FLAGS_VALID;

		case MAKE_PODULE_API_VERSION(1, 0):
		return PODULE_FLAGS_VALID_VERSION_1_0;

		default:
		rpclog("Failed to validate podule %s - unsupported API version %i.%i\n", header->short_name,
			PODULE_API_VERSION_GET_MAJOR(header->version),
			PODULE_API_VERSION_GET_MINOR(header->version));
		return 0;
	}

}

void podules_reset(void)
{
	int c;

	for (c = 0; c < 4; c++)
	{
		if (podule_functions[c] && podule_functions[c]->reset)
			podule_functions[c]->reset(&podules[c].podule);
	}
	backplane_mask = 0xf; /*All IRQs enabled*/
}

/**
 * Reset and empty all the podule slots
 *
 * Safe to call on program startup and user instigated virtual machine
 * reset.
 */
void podules_close(void)
{
	int c;

	/* Call any reset functions that an open podule may have to allow
	   then to tidy open files etc */
	for (c = 0; c < 4; c++)
	{
		if (podule_functions[c] && podule_functions[c]->close)
			podule_functions[c]->close(&podules[c].podule);

		podules[c].podule.header = NULL;
		podule_functions[c] = NULL;
	}
}

void rethinkpoduleints(void)
{
	int c;
	ioc.irqb &= ~(0x21);
	ioc.fiq  &= ~0x40;
	for (c=0;c<4;c++)
	{
		if (podules[c].irq && (backplane_mask & (1 << c)))
		{
//                        rpclog("Podule IRQ! %02X %i\n", ioc.mskb, c);
			ioc.irqb |= 0x20;
		}
		if (podules[c].fiq)
		{
			ioc.irqb |= 0x01;
			ioc.fiq  |= 0x40;
		}
	}
	ioc_updateirqs();
	if (ioc.fiq & 0x40)
		ioc_fiq(0x40);
	else
		ioc_fiqc(0x40);
}

void podule_set_irq(podule_t *podule, int state)
{
	podule_internal_state_t *internal = container_of(podule, podule_internal_state_t, podule);

	internal->irq = state;
	rethinkpoduleints();
}
void podule_set_fiq(podule_t *podule, int state)
{
	podule_internal_state_t *internal = container_of(podule, podule_internal_state_t, podule);

	internal->fiq = state;
	rethinkpoduleints();
}

void podule_write_b(int num, uint32_t addr, uint8_t val)
{
	if (podule_functions[num] && podule_functions[num]->write_b)
		podule_functions[num]->write_b(&podules[num].podule, PODULE_IO_TYPE_IOC, addr, val);
}

void podule_write_w(int num, uint32_t addr, uint32_t val)
{
//        rpclog("podule_write_w: addr=%08x val=%08x\n", addr, val);
	if (podule_functions[num] && podule_functions[num]->write_w)
		podule_functions[num]->write_w(&podules[num].podule, PODULE_IO_TYPE_IOC, addr, val >> 16);
	else if (podule_functions[num] && podule_functions[num]->write_b)
		podule_functions[num]->write_b(&podules[num].podule, PODULE_IO_TYPE_IOC, addr, (val >> 16) & 0xff);
}

void podule_memc_write_b(int num, uint32_t addr, uint8_t val)
{
	if (podule_functions[num] && podule_functions[num]->write_b)
		podule_functions[num]->write_b(&podules[num].podule, PODULE_IO_TYPE_MEMC, addr, val);
}

void podule_memc_write_w(int num, uint32_t addr, uint32_t val)
{
//        rpclog("podule_memc_write_w: addr=%08x val=%08x\n", addr, val);
	if (podule_functions[num] && podule_functions[num]->write_w)
		podule_functions[num]->write_w(&podules[num].podule, PODULE_IO_TYPE_MEMC, addr, val >> 16);
}


uint8_t podule_read_b(int num, uint32_t addr)
{
	uint8_t temp = 0xff;

	if (podule_functions[num] && podule_functions[num]->read_b)
		temp = podule_functions[num]->read_b(&podules[num].podule, PODULE_IO_TYPE_IOC, addr);

	return temp;
}

uint32_t podule_read_w(int num, uint32_t addr)
{
	uint16_t temp = 0xffff;

	if (podule_functions[num] && podule_functions[num]->read_w)
		temp = podule_functions[num]->read_w(&podules[num].podule, PODULE_IO_TYPE_IOC, addr);
	else if (podule_functions[num] && podule_functions[num]->read_b)
		temp = podule_functions[num]->read_b(&podules[num].podule, PODULE_IO_TYPE_IOC, addr) | 0xff00;

	return temp;
}

uint8_t podule_memc_read_b(int num, uint32_t addr)
{
	uint8_t temp = 0xff;

	if (podule_functions[num] && podule_functions[num]->read_b)
		temp = podule_functions[num]->read_b(&podules[num].podule, PODULE_IO_TYPE_MEMC, addr);

	return temp;
}

uint32_t podule_memc_read_w(int num, uint32_t addr)
{
	uint16_t temp = 0xffff;

	if (podule_functions[num] && podule_functions[num]->read_w)
		temp = podule_functions[num]->read_w(&podules[num].podule, PODULE_IO_TYPE_MEMC, addr);

	return temp;
}

static void podule_run_timer(void *p)
{
	int num = (intptr_t)p;
	podule_t *podule = &podules[num].podule;
	uint64_t timeslice = tsc - podules[num].last_callback_tsc;
	int ret = 0;

	podules[num].last_callback_tsc = tsc;
	if (podule_functions[num]->run)
		ret = podule_functions[num]->run(podule, timeslice / TIMER_USEC);

	if (ret)
		timer_advance_u64(&podules[num].timer, ret * TIMER_USEC);
}

uint8_t podule_irq_state()
{
	uint8_t state = 0;

	if (podules[0].irq)
		state |= 0x01;
	if (podules[1].irq)
		state |= 0x02;
	if (podules[2].irq)
		state |= 0x04;
	if (podules[3].irq)
		state |= 0x08;
	state &= backplane_mask;

	return state;
}

void podule_write_backplane_mask(uint8_t val)
{
	backplane_mask = val;
	rethinkpoduleints();
}

uint8_t podule_read_backplane_mask(void)
{
	return backplane_mask;
}

static int podule_get_nr(podule_t *podule)
{
	podule_internal_state_t *internal = container_of(podule, podule_internal_state_t, podule);

	return ((uintptr_t)internal - (uintptr_t)&podules[0]) / sizeof(podule_internal_state_t);
}

static int podule_config_get_int(podule_t *podule, const char *name, int def)
{
	char section_name[20];
	int slot_nr = podule_get_nr(podule);

	snprintf(section_name, 20, "%s.%i", podule->header->short_name, slot_nr);

	return config_get_int(CFG_MACHINE, section_name, name, def);
}

static const char *podule_config_get_string(podule_t *podule, const char *name, const char *def)
{
	char section_name[20];
	int slot_nr = podule_get_nr(podule);

	snprintf(section_name, 20, "%s.%i", podule->header->short_name, slot_nr);

	return config_get_string(CFG_MACHINE, section_name, name, def);
}

static void podule_config_set_int(podule_t *podule, const char *name, int val)
{
	char section_name[20];
	int slot_nr = podule_get_nr(podule);

	snprintf(section_name, 20, "%s.%i", podule->header->short_name, slot_nr);

	config_set_int(CFG_MACHINE, section_name, name, val);
}

static void podule_config_set_string(podule_t *podule, const char *name, char *val)
{
	char section_name[20];
	int slot_nr = podule_get_nr(podule);

	snprintf(section_name, 20, "%s.%i", podule->header->short_name, slot_nr);

	config_set_string(CFG_MACHINE, section_name, name, val);
}

static void podule_set_timer_delay_us(podule_t *podule, int delay_us)
{
	podule_internal_state_t *internal = container_of(podule, podule_internal_state_t, podule);

	timer_set_delay_u64(&internal->timer, delay_us * TIMER_USEC);
}

static int podule_get_timer_remaining_us(podule_t *podule)
{
	podule_internal_state_t *internal = container_of(podule, podule_internal_state_t, podule);

	return timer_get_remaining_us(&internal->timer);
}

static void podule_stop_timer(podule_t *podule)
{
	podule_internal_state_t *internal = container_of(podule, podule_internal_state_t, podule);

	timer_disable(&internal->timer);
}

const podule_callbacks_t podule_callbacks_def =
{
	.set_irq = podule_set_irq,
	.set_fiq = podule_set_fiq,
	.set_timer_delay_us = podule_set_timer_delay_us,
	.get_timer_remaining_us = podule_get_timer_remaining_us,
	.stop_timer = podule_stop_timer,
	.config_get_int = podule_config_get_int,
	.config_get_string = podule_config_get_string,
	.config_set_int = podule_config_set_int,
	.config_set_string = podule_config_set_string,
	.config_get_current = podule_config_get_current,
	.config_set_current = podule_config_set_current,
	.config_file_selector = podule_config_file_selector,
	.config_open = podule_config_open
};
