/*
 * Copyright 2018-2019 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		B Krishnan Iyer, krishnaniyer97@gmail.com
 */
#include <new>
#include <stdio.h>
#include <string.h>

#include <bus/PCI.h>
#include <PCI_x86.h>

#include <KernelExport.h>

#include "sdhci_pci.h"


#define TRACE_SDHCI
#ifdef TRACE_SDHCI
#	define TRACE(x...) dprintf("\33[33msdhci_pci:\33[0m " x)
#else
#	define TRACE(x...) ;
#endif
#define TRACE_ALWAYS(x...)	dprintf("\33[33msdhci_pci:\33[0m " x)
#define ERROR(x...)			dprintf("\33[33msdhci_pci:\33[0m " x)
#define CALLED(x...)		TRACE("CALLED %s\n", __PRETTY_FUNCTION__)


#define SDHCI_PCI_DEVICE_MODULE_NAME "busses/mmc/sdhci_pci/driver_v1"
#define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci_pci/device/v1"

#define SLOTS_COUNT				"device/slots_count"
#define SLOT_NUMBER				"device/slot"
#define BAR_INDEX				"device/bar"

typedef struct {
	struct registers* fRegisters;
	uint8_t fIrq;
} sdhci_pci_mmc_bus_info;


device_manager_info* gDeviceManager;
device_module_info* gSDHCIDeviceController;
static pci_x86_module_info* sPCIx86Module;


static void
sdhci_register_dump(uint8_t slot, struct registers* regs)
{
#ifdef TRACE_SDHCI
	TRACE("Register values for slot %d:\n", slot);
	TRACE("system_address: %d\n", regs->system_address);
	TRACE("%d blocks of size %d\n", regs->block_count, regs->block_size);
	TRACE("argument: %d\n", regs->argument);
	TRACE("transfer_mode: %d\n", regs->transfer_mode);
	TRACE("command: %d\n", regs->command.Bits());
	TRACE("response:");
	for (int i = 0; i < 8; i++)
		dprintf(" %d", regs->response[i]);
	dprintf("\n");
	TRACE("buffer_data_port: %d\n", regs->buffer_data_port);
	TRACE("present_state: %x\n", regs->present_state.Bits());
	TRACE("power_control: %d\n", regs->power_control.Bits());
	TRACE("host_control: %d\n", regs->host_control);
	TRACE("wakeup_control: %d\n", regs->wakeup_control);
	TRACE("block_gap_control: %d\n", regs->block_gap_control);
	TRACE("clock_control: %x\n", regs->clock_control.Bits());
	TRACE("software_reset: %d\n", regs->software_reset.Bits());
	TRACE("timeout_control: %d\n", regs->timeout_control);
	TRACE("interrupt_status: %x enable: %x signal: %x\n",
		regs->interrupt_status, regs->interrupt_status_enable,
		regs->interrupt_signal_enable);
	TRACE("auto_cmd12_error_status: %d\n", regs->auto_cmd12_error_status);
	TRACE("capabilities: %lld\n", regs->capabilities.Bits());
	TRACE("max_current_capabilities: %lld\n",
		regs->max_current_capabilities);
	TRACE("slot_interrupt_status: %d\n", regs->slot_interrupt_status);
	TRACE("host_controller_version spec %x vendor %x\n",
		regs->host_controller_version.specVersion,
		regs->host_controller_version.vendorVersion);
#endif
}


static void
sdhci_reset(struct registers* regs)
{
	// if card is not present then no point of reseting the registers
	if (!regs->present_state.IsCardInserted())
		return;

	// enabling software reset all
	regs->software_reset.ResetAll();
}


static void
sdhci_set_clock(struct registers* regs)
{
	int base_clock = regs->capabilities.BaseClockFrequency();
	// Try to get as close to 400kHz as possible, but not faster
	int divider = base_clock * 1000 / 400;

	if (regs->host_controller_version.specVersion <= 1) {
		// Old controller only support power of two dividers up to 256,
		// round to next power of two up to 256
		if (divider > 256)
			divider = 256;

		divider--;
		divider |= divider >> 1;
		divider |= divider >> 2;
		divider |= divider >> 4;
		divider++;
	}

	divider = regs->clock_control.SetDivider(divider);

	// Log the value after possible rounding by SetDivider (only even values
	// are allowed).
	TRACE("SDCLK frequency: %dMHz / %d = %dkHz\n", base_clock, divider,
		base_clock * 1000 / divider);

	// We have set the divider, now we can enable the internal clock.
	regs->clock_control.EnableInternal();

	// wait until internal clock is stabilized
	while (!(regs->clock_control.InternalStable()));

	regs->clock_control.EnablePLL();
	while (!(regs->clock_control.InternalStable()));

	// Finally, route the clock to the SD card
	regs->clock_control.EnableSD();
}


static void
sdhci_stop_clock(struct registers* regs)
{
	regs->clock_control.DisableSD();
}


static void
sdhci_set_power(struct registers* _regs)
{
	uint8_t supportedVoltages = _regs->capabilities.SupportedVoltages();
	if ((supportedVoltages & Capabilities::k3v3) != 0)
		_regs->power_control.SetVoltage(PowerControl::k3v3);
	else if ((supportedVoltages & Capabilities::k3v0) != 0)
		_regs->power_control.SetVoltage(PowerControl::k3v0);
	else if ((supportedVoltages & Capabilities::k1v8) != 0)
		_regs->power_control.SetVoltage(PowerControl::k1v8);
	else {
		_regs->power_control.PowerOff();
		ERROR("No voltage is supported\n");
		return;
	}

	if (!_regs->present_state.IsCardInserted()) {
		TRACE("Card not inserted\n");
		return;
	}

	TRACE("Execute CMD0\n");
	_regs->command.SendCommand(0, false);

	DELAY(1000);
}


static status_t
init_bus(device_node* node, void** bus_cookie)
{
	CALLED();
	status_t status = B_OK;
	area_id	regs_area;
	volatile uint32_t* regs;
	uint8_t bar, slot;

	sdhci_pci_mmc_bus_info* bus = new(std::nothrow) sdhci_pci_mmc_bus_info;
	if (bus == NULL)
		return B_NO_MEMORY;

	pci_device_module_info* pci;
	pci_device* device;

	device_node* parent = gDeviceManager->get_parent_node(node);
	device_node* pciParent = gDeviceManager->get_parent_node(parent);
	gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
	        (void**)&device);
	gDeviceManager->put_node(pciParent);
	gDeviceManager->put_node(parent);

	if (get_module(B_PCI_X86_MODULE_NAME, (module_info**)&sPCIx86Module)
	    != B_OK) {
	    sPCIx86Module = NULL;
		TRACE("PCIx86Module not loaded\n");
	}

	if (gDeviceManager->get_attr_uint8(node, SLOT_NUMBER, &slot, false) < B_OK
		|| gDeviceManager->get_attr_uint8(node, BAR_INDEX, &bar, false) < B_OK)
		return -1;

	TRACE("Register SD bus at slot %d, using bar %d\n", slot + 1, bar);

	pci_info pciInfo;
	pci->get_pci_info(device, &pciInfo);
	int msiCount = sPCIx86Module->get_msi_count(pciInfo.bus,
		pciInfo.device, pciInfo.function);
	TRACE("interrupts count: %d\n",msiCount);

	// enable bus master and io
	uint16 pcicmd = pci->read_pci_config(device, PCI_command, 2);
	pcicmd &= ~(PCI_command_int_disable | PCI_command_io);
	pcicmd |= PCI_command_master | PCI_command_memory;
	pci->write_pci_config(device, PCI_command, 2, pcicmd);

	TRACE("init_bus() %p node %p pci %p device %p\n", bus, node,
		pci, device);

	// mapping the registers by MMUIO method
	int bar_size = pciInfo->u.h0.base_register_sizes[bar];

	regs_area = map_physical_memory("sdhc_regs_map",
		pciInfo.u.h0.base_registers[bar],
		pciInfo.u.h0.base_register_sizes[bar], B_ANY_KERNEL_BLOCK_ADDRESS,
		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&regs);

	if (regs_area < B_OK) {
		TRACE("mapping failed");
		return B_BAD_VALUE;
	}

	struct registers* _regs = (struct registers*)regs;
	bus->fRegisters = _regs;
	sdhci_reset(_regs);

	// the interrupt is shared between all busses in an SDHC controller, but
	// they each register an handler. Not a problem, we will just test the
	// interrupt registers for all busses one after the other and find no
	// interrupts on the idle busses.
	bus->fIrq = pciInfo.u.h0.interrupt_line;

	TRACE("irq interrupt line: %d\n", bus->fIrq);

	if (bus->fIrq == 0 || bus->fIrq == 0xff) {
		TRACE("PCI IRQ not assigned\n");
		if (sPCIx86Module != NULL) {
			put_module(B_PCI_X86_MODULE_NAME);
			sPCIx86Module = NULL;
		}
		delete_area(regs_area);
		delete bus;
		return B_ERROR;
	}

	status = install_io_interrupt_handler(bus->fIrq,
		sdhci_generic_interrupt, bus, 0);

	if (status != B_OK) {
		TRACE("can't install interrupt handler\n");
		if (sPCIx86Module != NULL) {
			put_module(B_PCI_X86_MODULE_NAME);
			sPCIx86Module = NULL;
		}
		delete_area(regs_area);
		delete bus;
		return status;
	}
	TRACE("interrupt handler installed\n");

	_regs->interrupt_status_enable = SDHCI_INT_CMD_CMP
		| SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM
		| SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX
		| SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT;
	_regs->interrupt_signal_enable =  SDHCI_INT_CMD_CMP
		| SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM
		| SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX
		| SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT;

	sdhci_register_dump(slot, _regs);
	sdhci_set_clock(_regs);
	sdhci_set_power(_regs);

	*bus_cookie = bus;
	return status;
}


static void
uninit_bus(void* bus_cookie)
{
	sdhci_pci_mmc_bus_info* bus = (sdhci_pci_mmc_bus_info*)bus_cookie;

	bus->fRegisters->interrupt_signal_enable = 0;
	bus->fRegisters->interrupt_status_enable = 0;

	remove_io_interrupt_handler(bus->fIrq, sdhci_generic_interrupt, bus);

	area_id regs_area = area_for(bus->fRegisters);
	delete_area(regs_area);

	// FIXME do we need to put() the PCI module here?

	delete bus;
}


void
sdhci_error_interrupt_recovery(struct registers* _regs)
{
	_regs->interrupt_signal_enable &= ~(SDHCI_INT_CMD_CMP
		| SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM);

	if (_regs->interrupt_status & 7)
		_regs->software_reset.ResetTransaction();

	int16_t erorr_status = _regs->interrupt_status;
	_regs->interrupt_status &= ~(erorr_status);
}


int32
sdhci_generic_interrupt(void* data)
{
	sdhci_pci_mmc_bus_info* bus = (sdhci_pci_mmc_bus_info*)data;
	uint32_t intmask = bus->fRegisters->slot_interrupt_status;

	if ((intmask == 0) || (intmask == 0xffffffff)) {
		return B_UNHANDLED_INTERRUPT;
	}

	TRACE("interrupt function called\n");

	// handling card presence interrupt
	if (intmask & (SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM)) {
		uint32_t card_present = ((intmask & SDHCI_INT_CARD_INS) != 0);
		bus->fRegisters->interrupt_status_enable &= ~(SDHCI_INT_CARD_INS
			| SDHCI_INT_CARD_REM);
		bus->fRegisters->interrupt_signal_enable &= ~(SDHCI_INT_CARD_INS
			| SDHCI_INT_CARD_REM);

		bus->fRegisters->interrupt_status_enable |= card_present
		 	? SDHCI_INT_CARD_REM : SDHCI_INT_CARD_INS;
		bus->fRegisters->interrupt_signal_enable |= card_present
			? SDHCI_INT_CARD_REM : SDHCI_INT_CARD_INS;

		bus->fRegisters->interrupt_status |= (intmask &
			(SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM));
		TRACE("Card presence interrupt handled\n");

		return B_HANDLED_INTERRUPT;
	}

	// handling command interrupt
	if (intmask & SDHCI_INT_CMD_MASK) {
		bus->fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK);
		// TODO do something with the interrupt
		TRACE("Command interrupt handled\n");

		return B_HANDLED_INTERRUPT;
	}

	// handling bus power interrupt
	if (intmask & SDHCI_INT_BUS_POWER) {
		bus->fRegisters->interrupt_status |= SDHCI_INT_BUS_POWER;
		TRACE("card is consuming too much power\n");

		return B_HANDLED_INTERRUPT;
	}

	intmask = bus->fRegisters->slot_interrupt_status;
	if (intmask != 0) {
		ERROR("Remaining interrupts at end of handler: %x\n", intmask);
		intmask &= ~(SDHCI_INT_BUS_POWER | SDHCI_INT_CARD_INS
			| SDHCI_INT_CARD_REM | SDHCI_INT_CMD_MASK);
	}

	return B_UNHANDLED_INTERRUPT;
}


static void
bus_removed(void* bus_cookie)
{
	return;
}


static status_t
register_child_devices(void* cookie)
{
	CALLED();
	device_node* node = (device_node*)cookie;
	device_node* parent = gDeviceManager->get_parent_node(node);
	pci_device_module_info* pci;
	pci_device* device;
	uint8 slots_count, bar, slotsInfo;

	gDeviceManager->get_driver(parent, (driver_module_info**)&pci,
		(void**)&device);
	uint16 pciSubDeviceId = pci->read_pci_config(device, PCI_subsystem_id, 2);
	slotsInfo = pci->read_pci_config(device, SDHCI_PCI_SLOT_INFO, 1);
	bar = SDHCI_PCI_SLOT_INFO_FIRST_BASE_INDEX(slotsInfo);
	slots_count = SDHCI_PCI_SLOTS(slotsInfo);

	char prettyName[25];

	if (slots_count > 6 || bar > 5) {
		TRACE("Invalid slots count: %d or BAR count: %d \n", slots_count, bar);
		return B_BAD_VALUE;
	}

	for (uint8_t slot = 0; slot <= slots_count; slot++) {

		bar = bar + slot;
		sprintf(prettyName, "SDHC bus %" B_PRIu16 " slot %"
			B_PRIu8, pciSubDeviceId, slot);
		device_attr attrs[] = {
			// properties of this controller for SDHCI bus manager
			{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
				{ string: prettyName }},
			{ B_DEVICE_FIXED_CHILD, B_STRING_TYPE,
				{string: SDHCI_BUS_CONTROLLER_MODULE_NAME}},
			{SDHCI_DEVICE_TYPE_ITEM, B_UINT16_TYPE,
				{ ui16: pciSubDeviceId}},
			{B_DEVICE_BUS, B_STRING_TYPE,{string: "mmc"}},
			{SLOT_NUMBER, B_UINT8_TYPE,
				{ ui8: slot}},
			{BAR_INDEX, B_UINT8_TYPE,
				{ ui8: bar}},
			{ NULL }
		};
		if (gDeviceManager->register_node(node, SDHCI_PCI_MMC_BUS_MODULE_NAME,
				attrs, NULL, &node) != B_OK)
			return B_BAD_VALUE;
	}
	return B_OK;
}


static status_t
init_device(device_node* node, void** device_cookie)
{
	CALLED();
	*device_cookie = node;
	return B_OK;
}


static status_t
register_device(device_node* parent)
{
	device_attr attrs[] = {
		{B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {string: "SD Host Controller"}},
		{}
	};

	return gDeviceManager->register_node(parent, SDHCI_PCI_DEVICE_MODULE_NAME,
		attrs, NULL, NULL);
}


static float
supports_device(device_node* parent)
{
	CALLED();
	const char* bus;
	uint16 type, subType;
	uint8 pciSubDeviceId;

	// make sure parent is a PCI SDHCI device node
	if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)
		!= B_OK || gDeviceManager->get_attr_uint16(parent, B_DEVICE_SUB_TYPE,
		&subType, false) < B_OK || gDeviceManager->get_attr_uint16(parent,
		B_DEVICE_TYPE, &type, false) < B_OK)
		return -1;

	if (strcmp(bus, "pci") != 0)
		return 0.0f;

	if (type == PCI_base_peripheral) {
		if (subType != PCI_sd_host)
			return 0.0f;

		pci_device_module_info* pci;
		pci_device* device;
		gDeviceManager->get_driver(parent, (driver_module_info**)&pci,
			(void**)&device);
		pciSubDeviceId = pci->read_pci_config(device, PCI_revision, 1);
		TRACE("SDHCI Device found! Subtype: 0x%04x, type: 0x%04x\n",
			subType, type);
		return 0.8f;
	}

	return 0.0f;
}


module_dependency module_dependencies[] = {
	{ SDHCI_BUS_CONTROLLER_MODULE_NAME, (module_info**)&gSDHCIDeviceController},
	{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
	{}
};


static sdhci_mmc_bus_interface gSDHCIPCIDeviceModule = {
	{
		{
			SDHCI_PCI_MMC_BUS_MODULE_NAME,
			0,
			NULL
		},
		NULL,	// supports device
		NULL,	// register device
		init_bus,
		uninit_bus,
		NULL,	// register child devices
		NULL,	// rescan
		bus_removed,
	}
};


static driver_module_info sSDHCIDevice = {
	{
		SDHCI_PCI_DEVICE_MODULE_NAME,
		0,
		NULL
	},
	supports_device,
	register_device,
	init_device,
	NULL,	// uninit
	register_child_devices,
	NULL,	// rescan
	NULL,	// device removed
};


module_info* modules[] = {
	(module_info* )&sSDHCIDevice,
	(module_info* )&gSDHCIPCIDeviceModule,
	NULL
};