/* Reflow Oven Controller
*
* Copyright (C) 2021  Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/

#include <stm-periph/spi.h>
#include <helper-macros/helper-macros.h>
#include <stm-periph/rcc-manager.h>
#include <string.h>

#define STM_SPI_DEV_MAGIC 0x5A678F5CUL

struct spi_rcc_lookup_pair {
	SPI_TypeDef *spi_regs;
	volatile uint32_t *rcc_reg;
	uint32_t bit_no;
};

const struct spi_rcc_lookup_pair rcc_lookup[3] = {
	{SPI1, &RCC->APB2ENR, BITMASK_TO_BITNO(RCC_APB2ENR_SPI1EN)},
	{SPI2, &RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB1ENR_SPI2EN)},
	{SPI3, &RCC->APB1ENR, BITMASK_TO_BITNO(RCC_APB1ENR_SPI3EN)},
};

static const struct spi_rcc_lookup_pair *spi_find_rcc_reg_and_bit(SPI_TypeDef *spi_regs)
{
	uint32_t i;
	const struct spi_rcc_lookup_pair *ret = NULL;

	for (i = 0; i < COUNT_OF(rcc_lookup); i++) {
		if (rcc_lookup[i].spi_regs == spi_regs) {
			ret = &rcc_lookup[i];
			break;
		}
	}

	return ret;
}

static struct stm_spi_dev *spi_handle_to_struct(stm_spi_handle handle)
{
	struct stm_spi_dev *dev;

	dev = (struct stm_spi_dev *)handle;
	if (dev == NULL)
		return NULL;
	if (dev->magic != STM_SPI_DEV_MAGIC)
		return NULL;

	return dev;
}

stm_spi_handle spi_init(struct stm_spi_dev *spi_dev_struct, SPI_TypeDef *spi_regs, const struct stm_spi_settings *settings)
{
	stm_spi_handle ret_handle = NULL;
	uint32_t reg_val;
	const struct spi_rcc_lookup_pair *rcc_pair;

	if (!spi_dev_struct || !spi_regs || !settings)
		goto exit;
	if (!settings->cs_activate || !settings->cs_deactivate)
		goto exit;

	rcc_pair = spi_find_rcc_reg_and_bit(spi_regs);
	if (!rcc_pair)
		goto exit;

	memcpy(&spi_dev_struct->settings, settings, sizeof(struct stm_spi_settings));
	spi_dev_struct->spi_regs = spi_regs;

	rcc_manager_enable_clock(rcc_pair->rcc_reg, rcc_pair->bit_no);

	reg_val = 0;
	if (settings->cpha)
		reg_val |= SPI_CR1_CPHA;
	if (settings->cpol)
		reg_val |= SPI_CR1_CPOL;
	if (settings->master)
		reg_val |= SPI_CR1_MSTR;
	reg_val |= (settings->prescaler & 0x7) << 3;
	reg_val |= SPI_CR1_SPE | SPI_CR1_SSI | SPI_CR1_SSM;

	if (!settings->msb_first)
		reg_val |= SPI_CR1_LSBFIRST;

	spi_regs->CR2 = 0UL;
	spi_regs->CR1 = reg_val;

	ret_handle = spi_dev_struct;

	spi_dev_struct->settings.cs_deactivate();
	spi_dev_struct->magic = STM_SPI_DEV_MAGIC;

exit:
	return ret_handle;
}

void spi_deinit(stm_spi_handle handle)
{
	const struct spi_rcc_lookup_pair *rcc_pair;
	struct stm_spi_dev *dev;

	dev = spi_handle_to_struct(handle);
	if (!dev)
		return;

	dev->magic = 0UL;

	dev->spi_regs->CR1 = 0;
	dev->spi_regs->CR2 = 0;

	rcc_pair = spi_find_rcc_reg_and_bit(dev->spi_regs);
	if (!rcc_pair)
		rcc_manager_disable_clock(rcc_pair->rcc_reg, rcc_pair->bit_no);
}

static uint8_t transfer_byte(uint8_t byte, struct stm_spi_dev *dev)
{
	while (dev->spi_regs->SR & SPI_SR_BSY);
	dev->spi_regs->DR = (uint16_t)byte;
	__DSB();
	while((dev->spi_regs->SR & SPI_SR_BSY) || !(dev->spi_regs->SR & SPI_SR_TXE));

	return (uint8_t)dev->spi_regs->DR;
}

int spi_transfer(stm_spi_handle handle, const uint8_t *tx, uint8_t *rx, uint32_t count, bool handle_cs)
{
	struct stm_spi_dev *dev;
	uint8_t tx_byte;
	uint8_t rx_byte;
	uint32_t idx;

	dev = spi_handle_to_struct(handle);
	if (!dev)
		return -1001;

	if (handle_cs)
		dev->settings.cs_activate();

	for (idx = 0; idx < count; idx++) {
		tx_byte = (tx ? tx[idx] : 0U);
		rx_byte = transfer_byte(tx_byte, dev);
		if (rx)
			rx[idx] = rx_byte;
	}

	if (handle_cs)
		dev->settings.cs_deactivate();

	return 0;
}
