// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2019 Fredrik Noring
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "internal/macro.h"

#include "atari/bus.h"
#include "atari/device.h"
#include "atari/irq.h"
#include "atari/glue.h"
#include "atari/machine.h"
#include "atari/psg.h"

#define PSG_EVENT_FREQUENCY 100		/* 10 ms */
#define PSG_EVENT_CYCLES (PSG_FREQUENCY / PSG_EVENT_FREQUENCY)

static char *psg_register_name(uint32_t reg)
{
	switch (reg) {
#define PSG_REG_NAME(register_, symbol_, label_, description_)		\
	case register_: return #symbol_;
CF2149_REGISTERS(PSG_REG_NAME)
	default:
		return "";
	}
}

static inline struct cf2149_cycle cf2149_cycle_from_device(
	struct device_cycle cycle)
{
	return cf2149_cycle_cd(cycle.c, 1 /* FIXME */);
}

static void psg_emit(struct machine *machine,
	const struct device_cycle psg_cycle)
{
	struct cf2149_module *cf2149 = &machine->psg.cf2149;
	const struct cf2149_cycle cycle = cf2149_cycle_from_device(psg_cycle);
	struct cf2149_ac buffer[256];

	for (;;) {
		const size_t n = cf2149->port.rd_ac(cf2149,
			cycle, &buffer[0], ARRAY_SIZE(buffer));

		if (!n)
			return;

		if (machine->psg.output.sample)
			machine->psg.output.sample(buffer, n,
					machine->psg.output.sample_arg);
	}
}

static void psg_event(struct machine *machine, const struct device *device,
	const struct device_cycle psg_cycle)
{
	psg_emit(machine, psg_cycle);

	request_device_event(machine, device,
		(struct device_cycle) { .c = psg_cycle.c + PSG_EVENT_CYCLES });
}

static uint8_t psg_rd_u8(struct machine *machine, const struct device *device,
	uint32_t dev_address)
{
	struct cf2149_module *cf2149 = &machine->psg.cf2149;
	const struct device_cycle psg_cycle = device_cycle(machine, device);
	const struct cf2149_cycle cycle = cf2149_cycle_from_device(psg_cycle);

	switch (dev_address) {
	case 0:
	case 1:
		cf2149->port.bdc(cf2149, cycle,
			(struct cf2149_bdc) { .u8 = CF2149_BDC_DTB });
		return cf2149->port.rd_da(cf2149, cycle);
	case 2:
	case 3: return 0xff;
	default:
#if 0  /* FIXME: Dependency on pr_bug */
		BUG();
#endif
		return 0xff;
	}
}

static uint16_t psg_rd_u16(struct machine *machine, const struct device *device,
	uint32_t dev_address)
{
	return psg_rd_u8(machine, device, dev_address) << 8;
}

static void psg_wr_u8(struct machine *machine, const struct device *device,
	uint32_t dev_address, uint8_t data)
{
	struct cf2149_module *cf2149 = &machine->psg.cf2149;
	const struct device_cycle psg_cycle = device_cycle(machine, device);
	const struct cf2149_cycle cycle = cf2149_cycle_from_device(psg_cycle);

	psg_emit(machine, psg_cycle);

	switch (dev_address % 4) {
	case 0:
	case 1:
		cf2149->port.bdc(cf2149, cycle,
			(struct cf2149_bdc) { .u8 = CF2149_BDC_BAR });
		cf2149->port.wr_da(cf2149, cycle, data);
		break;
	case 2:
	case 3:
		cf2149->port.bdc(cf2149, cycle,
			(struct cf2149_bdc) { .u8 = CF2149_BDC_DWS });
		cf2149->port.wr_da(cf2149, cycle, data);
		break;
#if 0  /* FIXME: Dependency on pr_bug */
	default:
		BUG();
#endif
	}
}

static void psg_wr_u16(struct machine *machine, const struct device *device,
	uint32_t dev_address, uint16_t data)
{
	psg_wr_u8(machine, device, dev_address, data >> 8);
}

static size_t psg_id_u8(struct machine *machine,
	const struct device *device, uint32_t dev_address, char *buf, size_t size)
{
	struct cf2149_module *cf2149 = &machine->psg.cf2149;

	switch (dev_address % 4) {
	case 0:
	case 1:
		snprintf(buf, size, "rd/select");
		break;
	case 2:
	case 3:
		snprintf(buf, size, "wr %s",
			psg_register_name(cf2149->state.reg));
		break;
	default:
		snprintf(buf, size, "%2u", dev_address);
#if 0  /* FIXME: Dependency on pr_bug */
		BUG();
#endif
	}

	return strlen(buf);
}

static size_t psg_id_u16(struct machine *machine,
	const struct device *device, uint32_t dev_address, char *buf, size_t size)
{
	return psg_id_u8(machine, device, dev_address, buf, size);
}

static void psg_reset(struct machine *machine, const struct device *device)
{
	struct cf2149_module *cf2149 = &machine->psg.cf2149;
	const struct device_cycle psg_cycle = device_cycle(machine, device);
	const struct cf2149_cycle cycle = cf2149_cycle_from_device(psg_cycle);

	*cf2149 = cf2149_init();

	cf2149->port.select_l(cf2149, cycle, CF2149_SELECT_MODE_CLKDIV8);

	psg_event(machine, device, device_cycle(machine, device));
}

void psg_sample(struct machine *machine,
	psg_sample_f sample, void *sample_arg)
{
	machine->psg.output.sample = sample;
	machine->psg.output.sample_arg = sample_arg;
}

const struct device psg_device = {
	.name = "psg",
	.clk = {
		.frequency = PSG_FREQUENCY,
		.divisor = 1
	},
	.bus = {
		.address = 0xff8800,
		.size = 256,
	},
	.reset = psg_reset,
	.event = psg_event,
	.rd_u8  = psg_rd_u8,
	.rd_u16 = psg_rd_u16,
	.wr_u8  = psg_wr_u8,
	.wr_u16 = psg_wr_u16,
	.id_u8  = psg_id_u8,
	.id_u16 = psg_id_u16,
};
