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

#include "internal/macro.h"
#include "internal/types.h"

#include "atari/bus.h"
#include "atari/mfp.h"
#include "atari/machine.h"
#include "atari/mmu.h"
#include "atari/mmu-trace.h"
#include "atari/ram.h"
#include "atari/rom.h"

#include "m68k/m68k.h"
#include "m68k/m68kcpu.h"

static void mmu_bus_wait(struct machine *machine, const struct device *dev)
{
	if (!dev->main_bus)
		return;

	/*
	 * The MMU interleaves processor accesses to the main bus
	 * every fourth cycle.
	 */

	const uint64_t mc = machine_cycle(machine);
	const unsigned wait_cycles = ALIGN(mc, 4) - mc;

	if (wait_cycles) {
		struct m68k_module *module = &machine->cpu.m68k;

		USE_CYCLES(wait_cycles);
	}
}

#define DMA_DEVICE(bus_address, dev)					\
	valid_device_bus_address(bus_address, dev) ? dev
#define DMA_DEVICES(bus_address)					\
	DMA_DEVICE(bus_address, &ram_device) :				\
	DMA_DEVICE(bus_address, &rom_device) : NULL

uint8_t dma_read_memory_8(struct machine *machine, uint32_t bus_address)
{
	const struct device *dev = DMA_DEVICES(bus_address);

	if (!dev)
		return 0;

	const uint32_t dev_address = bus_address - dev->bus.address;
	const uint8_t value = dev->rd_u8(machine, dev, dev_address);

	mmu_trace_rd_u8(machine, dev_address, value, dev);

	return value;
}

uint16_t dma_read_memory_16(struct machine *machine, uint32_t bus_address)
{
	const struct device *dev = DMA_DEVICES(bus_address);

	if (!dev)
		return 0;

	const uint32_t dev_address = bus_address - dev->bus.address;
	const uint16_t value = dev->rd_u16(machine, dev, dev_address);

	mmu_trace_rd_u16(machine, dev_address, value, dev);

	return value;
}

uint8_t probe_read_memory_8(struct machine *machine, uint32_t bus_address)
{
	const struct device *dev = DMA_DEVICES(bus_address);

	return dev ? dev->rd_u8(machine, dev, bus_address - dev->bus.address) : 0;
}

uint16_t probe_read_memory_16(struct machine *machine, uint32_t bus_address)
{
	const struct device *dev = DMA_DEVICES(bus_address);

	return dev ? dev->rd_u16(machine, dev, bus_address - dev->bus.address) : 0;
}

void probe_copy_memory_8(struct machine *machine,
	void *buffer, uint32_t bus_address, size_t byte_count)
{
	uint8_t *b = buffer;

	for (size_t i = 0; i < byte_count; i++)
		b[i] = probe_read_memory_8(machine, bus_address + i);
}

void probe_copy_memory_16(struct machine *machine,
	void *buffer, uint32_t bus_address, size_t word_count)
{
	uint8_t *b = buffer;

	for (size_t i = 0; i < word_count; i++) {
		const uint16_t v = probe_read_memory_16(machine,
			bus_address + sizeof(uint16_t[i]));

		b[sizeof(uint16_t[i]) + 0] = v >> 8;
		b[sizeof(uint16_t[i]) + 1] = v & 0xff;
	}
}

uint32_t m68k_read_memory_8(struct m68k_module *module, uint32_t bus_address)
{
	struct machine *machine = machine_from_m68k_module(module);
	const struct device *dev = device_for_bus_address(machine, bus_address);
	const uint32_t dev_address = bus_address - dev->bus.address;

	mmu_bus_wait(machine, dev);

	const uint8_t value = dev->rd_u8(machine, dev, dev_address);

	mmu_trace_rd_u8(machine, dev_address, value, dev);

	return value;
}

uint32_t m68k_read_memory_16(struct m68k_module *module, uint32_t bus_address)
{
	struct machine *machine = machine_from_m68k_module(module);
	const struct device *dev = device_for_bus_address(machine, bus_address);
	const uint32_t dev_address = bus_address - dev->bus.address;
	const uint16_t value = dev->rd_u16(machine, dev, dev_address);

	mmu_bus_wait(machine, dev);

	mmu_trace_rd_u16(machine, dev_address, value, dev);

	return value;
}

uint32_t m68k_read_memory_32(struct m68k_module *module, uint32_t bus_address)
{
	const uint32_t hi = m68k_read_memory_16(module, bus_address);
	const uint16_t lo = m68k_read_memory_16(module, bus_address + 2);

	return (hi << 16) | lo;
}

void m68k_write_memory_8(struct m68k_module *module, uint32_t bus_address, uint32_t value)
{
	struct machine *machine = machine_from_m68k_module(module);
	const struct device *dev = device_for_bus_address(machine, bus_address);
	const uint32_t dev_address = bus_address - dev->bus.address;

	mmu_bus_wait(machine, dev);

	mmu_trace_wr_u8(machine, dev_address, value, dev);

	dev->wr_u8(machine, dev, dev_address, value & 0xff);
}

void m68k_write_memory_16(struct m68k_module *module, uint32_t bus_address, uint32_t value)
{
	struct machine *machine = machine_from_m68k_module(module);
	const struct device *dev = device_for_bus_address(machine, bus_address);
	const uint32_t dev_address = bus_address - dev->bus.address;

	mmu_bus_wait(machine, dev);

	mmu_trace_wr_u16(machine, dev_address, value, dev);

	dev->wr_u16(machine, dev, dev_address, value & 0xffff);
}

void m68k_write_memory_32(struct m68k_module *module, uint32_t bus_address, uint32_t value)
{
	m68k_write_memory_16(module, bus_address, value >> 16);
	m68k_write_memory_16(module, bus_address + 2, value & 0xffff);
}

uint32_t m68k_read_disassembler_16(struct m68k_module *module, uint32_t bus_address)
{
	struct machine *machine = machine_from_m68k_module(module);
	const struct device *dev = device_for_bus_address(machine, bus_address);
	const uint32_t dev_address = bus_address - dev->bus.address;

	return dev->rd_u16(machine, dev, dev_address);
}

uint32_t m68k_read_disassembler_32(struct m68k_module *module, uint32_t bus_address)
{
	const uint32_t hi = m68k_read_disassembler_16(module, bus_address);
	const uint16_t lo = m68k_read_disassembler_16(module, bus_address + 2);

	return (hi << 16) | lo;
}
