/*
 * @history     2025_03_06  [SDK3.7] SSPサンプルにSPIモードの使用例を追加 (#5781)
 */
/*
 * This program was created by Axell Corporation.
 * Copyright (C) 2025 Axell Corporation, all rights reserved.
 */
#include <stdint.h>
#include <stdbool.h>
#include "sample_common.h"
#include "int/intmgr.h"
#include "ssp_sample.h"
#include "ssp/sspmgr.h"
#include "ssp/sspprm.h"

#define SSP_CH      0  /* MボードではSPIフラッシュはSSP0と接続 */
#define SSP_SMP_TX_DMACH		(3)		/* DMACチャンネル */
#define SSP_SMP_TX_DMACSEL		(4)		/* DMACハンドシェーク要求(DMACnCFG_REG)に割り当てるDMAインターフェース */
#define SSP_SMP_RX_DMACH		(4)		/* DMACチャンネル */
#define SSP_SMP_RX_DMACSEL		(5)		/* DMACハンドシェーク要求(DMACnCFG_REG)に割り当てるDMAインターフェース */
#define SSP_SMP_TX_BUF_SIZE (4+256) /* 最大送信長4 + 最大受信長256 */
#define SSP_SMP_RX_BUF_SIZE (4+256) /* 最大送信長4 + 最大受信長256 */

#define	PRINT(FMT, ...)			sys_print(0, FMT"\r\n",##__VA_ARGS__)
#define SSP_ERROR(str)			sys_print(1, " ERROR:%s[%d] *%s*\r\n",__FUNCTION__, __LINE__, str)

static void Ssp_CallbackSpi(AG903_SSPMgrHandle* handle, uint32_t event);
static void Ssp_SigSem(volatile uint32_t* sem);
static void Ssp_WaiSem(volatile uint32_t* sem);
static void Ssp_Dump(const uint8_t* dat, uint32_t len);

static volatile uint32_t	SspTxSem=0;
static volatile uint32_t	SspRxSem=0;
static uint8_t* tx_buf;
static uint8_t* rx_buf;
static uint8_t tx_buf_cacheable[SSP_SMP_TX_BUF_SIZE];
static uint8_t rx_buf_cacheable[SSP_SMP_RX_BUF_SIZE];

void Ssp_SpiFlashCommand(_Bool dma)
{
	AG903_SSPMgrHandle*     handle;
	AG903_SSPMgrSpiParam    spi;
	int32_t		result;
	int			tx_len;
	int			rx_len;
	int			xfer_len;

	static const uint8_t cmd_read_id[1]         = {0x9E};
	static const uint8_t cmd_read_000000h[4]    = {0x03,0x00,0x00,0x00};
	static struct {
		const char *title;
		int cmd_len;
		int dat_len;
		const uint8_t* cmd;
	} spiflash_demo[] = {
		{
			"READ ID (1 Byte)",
			1,   1, cmd_read_id,
		},
		{
			"READ ID (20 Bytes)",
			1,  20, cmd_read_id,
		},
		{
			"READ 000000h (256 Bytes)",
			4, 256, cmd_read_000000h,
		},
	};

	Board_SetSpiFlashInterface();

	if (dma) {
		/* DMAのときはキャッシュ不可メモリをバッファとして確保 */
		tx_buf = (uint8_t*)sys_memalign(SSP_SMP_TX_BUF_SIZE, sizeof(int64_t));
		rx_buf = (uint8_t*)sys_memalign(SSP_SMP_RX_BUF_SIZE, sizeof(int64_t));
		if (NULL == tx_buf || NULL== rx_buf) {
			SSP_ERROR("sys_memalign");
			goto error_sys_memalign;
		}
	} else {
		tx_buf = tx_buf_cacheable;
		rx_buf = rx_buf_cacheable;
	}

	result = AG903_SSPMgrGetHandle(SSP_CH, &handle);			/* SSPハンドル取得 */
	if (AG903_ENONE != result) {
		SSP_ERROR("SSP GetHandle");
		goto error_get_handle;
	}

	result = AG903_SSPMgrReset(handle);						/* SSPリセット */
	if(AG903_ENONE != result) {
		SSP_ERROR("SSP Reset");
		goto error_reset;
	}

	/*
	 * SPIフラッシュはコマンド送信からデータ受信までSSP_FS信号をアサートし続ける必要があります。
	 * 通常のSPIモードは送信FIFOが空になるとSSP_FS信号がネゲートするためクロックを速くする場合は
	 * 制御の時間制約が厳しくなります。
	 * その場合必要に応じてSSP_FS端子をGPIOとして制御するようにしてください。
	 * .flash=1とする方法もありますが副作用があり制御が難しくなります。
	 */
	spi.clkdiv   = 240;  /* MCLK 100kHz = XOUT(24MHz)/240 */
	spi.wordlen  = 8;
	spi.polarity = AG903_SSP_POL_NEGATIVE;
	spi.firstbit = AG903_SSP_FIRSTBIT_MSB;
	spi.sclktype = AG903_SSP_SPICLK_MODE0; /* Rise edge, Idle low */
	spi.slave    = false;
	spi.flash    = 0;
	result = AG903_SSPMgrSetSpiMode(handle, &spi);
	if (AG903_ENONE != result) {
		SSP_ERROR("SSP SetSpiMode");
		goto error_setspimode;
	}

	result = AG903_SSPMgrSetTxFifoThreshold(handle, 15);
	if(AG903_ENONE != result) {
		SSP_ERROR("SetTxFifoThreshold");
	}

	if (dma) {
		result = AG903_SSPMgrEnableDmaMode(handle, (1<<1|1<<0));		/* DMA有効設定 */
		if(AG903_ENONE != result) {
			SSP_ERROR("EnableDmaMode");
			goto error_enable_dma_mode;
		}

		result = AG903_SSPMgrAttachTxDma(handle, SSP_SMP_TX_DMACH, SSP_SMP_TX_DMACSEL);	/* 送信DMA取り付け */
		if(AG903_ENONE != result) {
			SSP_ERROR("AttachTxDma");
			goto error_attach_tx_dma;
		}
		result = AG903_SSPMgrAttachRxDma(handle, SSP_SMP_RX_DMACH, SSP_SMP_RX_DMACSEL);	/* 受信DMA取り付け */
		if(AG903_ENONE != result) {
			SSP_ERROR("AttachRxDma");
			goto error_attach_rx_dma;
		}
		result = AG903_SSPMgrSetTxDmaBeat(handle, 1);
		if(AG903_ENONE != result) {
			SSP_ERROR("SetTxDmaBeat");
			goto error_set_tx_dma_beat;
		}
		result = AG903_SSPMgrSetRxDmaBeat(handle, 1);
		if(AG903_ENONE != result) {
			SSP_ERROR("SetRxDmaBeat");
			goto error_set_rx_dma_beat;
		}

		AG903_INTMgrEnableInt(AG903_IRQ26_DMAC);

		PRINT("Enabld internal DMA");
	}

	result = AG903_SSPMgrSetCallback(handle, (AG903_SSPMgrClbk)Ssp_CallbackSpi);
	if(AG903_ENONE != result) {
		SSP_ERROR("SSP SetCallback");
		goto error_setcallback;
	}

	SspTxSem = 0;
	SspRxSem = 0;

	for (int ii = 0; ii < (int)(sizeof(spiflash_demo)/sizeof(spiflash_demo[0])); ii++) {
		PRINT(" ---- %s", spiflash_demo[ii].title);
		tx_len = spiflash_demo[ii].cmd_len;
		rx_len = spiflash_demo[ii].dat_len;
		xfer_len = tx_len + rx_len;
		sys_memset(tx_buf, 0xff, xfer_len);
		sys_memcpy(tx_buf, spiflash_demo[ii].cmd, tx_len);
		sys_memset(rx_buf, 0xAA, xfer_len);

		result = AG903_SSPMgrSpiEnableTransfer(handle);
		if (AG903_ENONE != result) {
			SSP_ERROR("SSP SpiEnableTransfer");
		}

		if (dma) {
			result = AG903_SSPMgrSpiSendReceiveDma(handle, tx_buf, xfer_len, rx_buf, xfer_len);
			if (AG903_ENONE != result) {
				SSP_ERROR("SSP SpiSendReceiveDma");
			}
		} else {
			result = AG903_SSPMgrSpiSendReceive(handle, tx_buf, xfer_len, rx_buf, xfer_len);
			if (AG903_ENONE != result) {
				SSP_ERROR("SSP SpiSendReceive");
			}
		}
		Ssp_WaiSem(&SspTxSem);
		Ssp_WaiSem(&SspRxSem);

		result = AG903_SSPMgrSpiDisableTransfer(handle);
		if (AG903_ENONE != result) {
			SSP_ERROR("SSP SpiDisableTransfer");
		}

		PRINT("Command:");
		Ssp_Dump(&tx_buf[0], tx_len);
		PRINT("Data:");
		Ssp_Dump(&rx_buf[tx_len], rx_len); /* コマンド送信中に受信したデータはダミー */
	}

 error_setcallback:
 error_set_rx_dma_beat:
 error_set_tx_dma_beat:
	if (dma) {
		result = AG903_SSPMgrDetachRxDma(handle);
		if(AG903_ENONE != result) {
			SSP_ERROR("DetachRxDma");
		}
	}
 error_attach_rx_dma:
	if (dma) {
		result = AG903_SSPMgrDetachTxDma(handle);
		if(AG903_ENONE != result) {
			SSP_ERROR("DetachTxDma");
		}
	}
 error_attach_tx_dma:
 error_enable_dma_mode:
 error_setspimode:
 error_reset:
	AG903_SSPMgrReleaseHandle(handle);
 error_get_handle:
	if (dma) {
		if (NULL != tx_buf) {
			sys_free(tx_buf);
		}
		if (NULL != rx_buf) {
			sys_free(rx_buf);
		}
	}
 error_sys_memalign:

	Board_UnsetSpiFlashInterface();

	return;
}

static void Ssp_CallbackSpi(AG903_SSPMgrHandle* handle, uint32_t event)
{
	((void)handle);

	if(AG903_SSP_EVENT_SNDEND & event) {
		Ssp_SigSem(&SspTxSem);
	}

	if(AG903_SSP_EVENT_RCVEND & event) {
		Ssp_SigSem(&SspRxSem);
	}

	if((AG903_SSP_EVENT_OVERRUN|AG903_SSP_EVENT_UNDERRUN) & event) {
		/* 処理なし */
	}

	return;
}

static void Ssp_SigSem(volatile uint32_t* sem)
{
	if(1 > *sem) {
		*sem += 1;
	}

	return;
}

static void Ssp_WaiSem(volatile uint32_t* sem)
{
	while (0 == *sem);
	*sem -= 1;

	return;
}

static void Ssp_Dump(const uint8_t* dat, uint32_t len)
{
	uint32_t output;
	uint32_t remain;
	uint32_t current;

	output = 0;
	remain = len;
	while (remain > 0) {
		current = (16 <= remain) ? 16 : (remain % 16);
		for (uint32_t i = 0; i < current; i++) {
			sys_print(0, "%02X ", dat[output]);
			output += 1;
			remain -= 1;
		}
		PRINT("");
	}

	return;
}
