#define TRUE  1
#define FALSE 0

#include "stm32h7xx_hal.h"
#include <stdbool.h>
#include <string.h>

#include "ff.h"
#include "diskio.h"
#include "fatfs_sd.h"

static volatile DSTATUS Stat = STA_NOINIT;	/* Disk Status */
static uint8_t CardType;                    /* Type 0:MMC, 1:SDC, 2:Block addressing */
static uint8_t PowerFlag = 0;				/* Power flag */

/***************************************
 * SPI functions
 **************************************/

/* slave select */
static void SELECT(void)
{
	HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_RESET);
}

/* slave deselect */
static void DESELECT(void)
{
	HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_SET);
}

static void SPI_Start(SPI_TypeDef *spi, uint16_t len)
{
	spi->CR2 = len;

	uint32_t cr1 = spi->CR1 | SPI_CR1_SPE;

	spi->CR1 = cr1; // enable
	spi->CR1 = cr1 | SPI_CR1_CSTART; // start
}

static void SPI_End(SPI_TypeDef *spi)
{
	// clear EOT/TXTF
	spi->IFCR |= SPI_IFCR_EOTC | SPI_IFCR_TXTFC;

	spi->CR1 &= ~SPI_CR1_SPE; // disable
}

/* SPI transmit a byte */
static void SPI_TxByte(uint8_t data)
{
  SPI_TypeDef *spi = (HSPI_SDCARD)->Instance;

  SPI_Start(spi, 1);

  // wait for tx space
  while(!(spi->SR & SPI_FLAG_TXP));

  *((__IO uint8_t *)&spi->TXDR) = data;

  // wait for end
  while(!(spi->SR & SPI_FLAG_EOT));

  // end
  SPI_End(spi);
}

/* SPI receive a byte */
static uint8_t SPI_RxByte(void)
{
  uint8_t data;

  SPI_TypeDef *spi = (HSPI_SDCARD)->Instance;

  SPI_Start(spi, 1);

  while(!(spi->SR & SPI_FLAG_TXP));

  *((__IO uint8_t *)&spi->TXDR) = 0xFF;

  // wait for a byte
  while(!(spi->SR & (SPI_FLAG_RXWNE | SPI_FLAG_FRLVL)));

  data = *((__IO uint8_t *)&spi->RXDR);

  // wait for end
  while(!(spi->SR & SPI_FLAG_EOT));

  // end
  SPI_End(spi);

  return data;
}

/* SPI receive a byte via pointer */
static void SPI_RxBytePtr(uint8_t *buff)
{
	*buff = SPI_RxByte();
}

/***************************************
 * SD functions
 **************************************/

/* wait SD ready */
static uint8_t SD_ReadyWait(void)
{
	uint8_t res;

	/* timeout 500ms */
	uint32_t timeout = HAL_GetTick() + 500;

	/* if SD goes ready, receives 0xFF */
	do {
		res = SPI_RxByte();
	} while ((res != 0xFF) && HAL_GetTick() < timeout);

	return res;
}

/* power on */
static void SD_PowerOn(void)
{
	uint32_t cnt = 0x1FFF;

	/* transmit bytes to wake up */
	DESELECT();
	for(int i = 0; i < 10; i++)
	{
		SPI_TxByte(0xFF);
	}

	/* slave select */
	SELECT();

	/* make idle state */
  // SD_SendCmd?
  SPI_TxByte(CMD0);		/* CMD0:GO_IDLE_STATE */
	SPI_TxByte(0);
	SPI_TxByte(0);
	SPI_TxByte(0);
	SPI_TxByte(0);
  SPI_TxByte(0x95);		/* CRC */

	/* wait response */
	while ((SPI_RxByte() != 0x01) && cnt)
	{
		cnt--;
	}

	DESELECT();
	SPI_TxByte(0XFF);

	PowerFlag = 1;
}

/* power off */
static void SD_PowerOff(void)
{
	PowerFlag = 0;
}

/* check power flag */
static uint8_t SD_CheckPower(void)
{
	return PowerFlag;
}

/* receive data block */
static bool SD_RxDataBlock(BYTE *buff, uint16_t len)
{
	uint8_t token;

	/* timeout 200ms */
  uint32_t timeout = HAL_GetTick() + 200;

	/* loop until receive a response or timeout */
	do {
		token = SPI_RxByte();
	} while((token == 0xFF) && HAL_GetTick() < timeout);

	/* invalid response */
	if(token != 0xFE) return FALSE;

	/* receive data */
	if(len > 16)
	{
		// manual txrx
		SPI_Start((HSPI_SDCARD)->Instance, len + 2);

		// the only length that will be used here is 512
		uint32_t *buff32 = (uint32_t *)buff;
		const uint32_t *end = buff32 + len / 4;

		// fill the 16 byte fifo
		for(int i = 0; i < 4; i++)
			*((__IO uint32_t *)&(HSPI_SDCARD)->Instance->TXDR) = 0xFFFFFFFF;

		while(true)
		{
			if(((HSPI_SDCARD)->Instance->SR & SPI_FLAG_RXWNE))
			{
				*(buff32++) = *((__IO uint32_t *)&(HSPI_SDCARD)->Instance->RXDR);

				if(buff32 != end)
					*((__IO uint32_t *)&(HSPI_SDCARD)->Instance->TXDR) = 0xFFFFFFFF; // refill tx
				else
					break;
			}
		}

		// get CRC
		*((__IO uint16_t *)&(HSPI_SDCARD)->Instance->TXDR) = 0xFFFF;
		while(!((HSPI_SDCARD)->Instance->SR & SPI_SR_RXPLVL_1));
		uint16_t crc = *((__IO uint16_t *)&(HSPI_SDCARD)->Instance->RXDR);

		while(!((HSPI_SDCARD)->Instance->SR & SPI_FLAG_EOT)); // wait for end

		// end
		SPI_End((HSPI_SDCARD)->Instance);
	}
	else
	{
		while(len--)
			SPI_RxBytePtr(buff++);

		/* discard CRC */
		SPI_RxByte();
		SPI_RxByte();
	}
	return TRUE;
}

/* transmit data block */
#if FF_FS_READONLY == 0
static bool SD_TxDataBlock(const uint8_t *buff, BYTE token)
{
	uint8_t resp;
	uint8_t i = 0;

	/* wait SD ready */
	if (SD_ReadyWait() != 0xFF) return FALSE;

	/* transmit token */
	SPI_TxByte(token);

	/* if it's not STOP token, transmit data */
	if (token != 0xFD)
	{
    // manual tx
    const int len = 512;
		SPI_Start((HSPI_SDCARD)->Instance, len);

		// the only length that will be used here is 512
		uint32_t *buff32 = (uint32_t *)buff;
		const uint32_t *end = buff32 + len / 4;

		// fill the 16 byte fifo
		for(int i = 0; i < 4; i++)
			*((__IO uint32_t *)&(HSPI_SDCARD)->Instance->TXDR) = *buff32++;

		while(buff32 != end)
		{
			if(((HSPI_SDCARD)->Instance->SR & SPI_FLAG_TXP))
				*((__IO uint32_t *)&(HSPI_SDCARD)->Instance->TXDR) = *buff32++;
		}

		while(!((HSPI_SDCARD)->Instance->SR & SPI_FLAG_EOT)); // wait for end

		// end
		SPI_End((HSPI_SDCARD)->Instance);

		/* discard CRC */
		SPI_RxByte();
		SPI_RxByte();

		/* receive response */
		while (i <= 64)
		{
			resp = SPI_RxByte();

			/* transmit 0x05 accepted */
			if ((resp & 0x1F) == 0x05) break;
			i++;
		}

		/* recv buffer clear */
		while (SPI_RxByte() == 0);
	}
	else
		return TRUE;

	/* transmit 0x05 accepted */
	if ((resp & 0x1F) == 0x05) return TRUE;

	return FALSE;
}
#endif /*  FF_FS_READONLY */

/* transmit command */
static BYTE SD_SendCmd(BYTE cmd, uint32_t arg)
{
	uint8_t crc, res;

	/* wait SD ready */
	if (SD_ReadyWait() != 0xFF) return 0xFF;

	/* transmit command */
	SPI_TxByte(cmd); 					/* Command */
	SPI_TxByte((uint8_t)(arg >> 24)); 	/* Argument[31..24] */
	SPI_TxByte((uint8_t)(arg >> 16)); 	/* Argument[23..16] */
	SPI_TxByte((uint8_t)(arg >> 8)); 	/* Argument[15..8] */
	SPI_TxByte((uint8_t)arg); 			/* Argument[7..0] */

	/* prepare CRC */
	if(cmd == CMD0) crc = 0x95;	/* CRC for CMD0(0) */
	else if(cmd == CMD8) crc = 0x87;	/* CRC for CMD8(0x1AA) */
	else crc = 1;

	/* transmit CRC */
	SPI_TxByte(crc);

	/* Skip a stuff byte when STOP_TRANSMISSION */
	if (cmd == CMD12) SPI_RxByte();

	/* receive response */
	uint8_t n = 10;
	do {
		res = SPI_RxByte();
	} while ((res & 0x80) && --n);

	return res;
}

/***************************************
 * user_diskio.c functions
 **************************************/

/* initialize SD */
DSTATUS SD_disk_initialize(BYTE drv)
{
	uint8_t n, type, ocr[4];

	/* single drive, drv should be 0 */
	if(drv) return STA_NOINIT;

	/* no disk */
	if(Stat & STA_NODISK) return Stat;

	/* power on */
	SD_PowerOn();

	/* slave select */
	SELECT();

	/* check disk type */
	type = 0;

	/* send GO_IDLE_STATE command */
	if (SD_SendCmd(CMD0, 0) == 1)
	{
		/* timeout 1 sec */
		uint32_t timeout = HAL_GetTick() + 1000;

		/* SDC V2+ accept CMD8 command, http://elm-chan.org/docs/mmc/mmc_e.html */
		if (SD_SendCmd(CMD8, 0x1AA) == 1)
		{
			/* operation condition register */
			for (n = 0; n < 4; n++)
			{
				ocr[n] = SPI_RxByte();
			}

			/* voltage range 2.7-3.6V */
			if (ocr[2] == 0x01 && ocr[3] == 0xAA)
			{
				/* ACMD41 with HCS bit */
				do {
					if (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 1UL << 30) == 0) break;
				} while (HAL_GetTick() < timeout);

				/* READ_OCR */
				if (HAL_GetTick() < timeout && SD_SendCmd(CMD58, 0) == 0)
				{
					/* Check CCS bit */
					for (n = 0; n < 4; n++)
					{
						ocr[n] = SPI_RxByte();
					}

					/* SDv2 (HC or SC) */
					type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
				}
			}
		}
		else
		{
			/* SDC V1 or MMC */
			type = (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) <= 1) ? CT_SD1 : CT_MMC;

			do
			{
				if (type == CT_SD1)
				{
					if (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) == 0) break; /* ACMD41 */
				}
				else
				{
					if (SD_SendCmd(CMD1, 0) == 0) break; /* CMD1 */
				}

			} while (HAL_GetTick() < timeout);

			/* SET_BLOCKLEN */
			if (!(HAL_GetTick() >= timeout) || SD_SendCmd(CMD16, 512) != 0) type = 0;
		}
	}

	CardType = type;

	/* Idle */
	DESELECT();
	SPI_RxByte();

	/* Clear STA_NOINIT */
	if (type)
	{
		Stat &= ~STA_NOINIT;
	}
	else
	{
		/* Initialization failed */
		SD_PowerOff();
	}

	return Stat;
}

/* return disk status */
DSTATUS SD_disk_status(BYTE drv)
{
	if (drv) return STA_NOINIT;
	return Stat;
}

/* read sector */
DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
	/* pdrv should be 0 */
	if (pdrv || !count) return RES_PARERR;

	/* no disk */
	if (Stat & STA_NOINIT) return RES_NOTRDY;

	/* convert to byte address */
	if (!(CardType & CT_BLOCK)) sector *= 512;

	SELECT();

	if (count == 1)
	{
		/* READ_SINGLE_BLOCK */
		if ((SD_SendCmd(CMD17, sector) == 0) && SD_RxDataBlock(buff, 512)) count = 0;
	}
	else
	{
		/* READ_MULTIPLE_BLOCK */
		if (SD_SendCmd(CMD18, sector) == 0)
		{
			do {
				if (!SD_RxDataBlock(buff, 512)) break;
				buff += 512;
			} while (--count);

			/* STOP_TRANSMISSION */
			SD_SendCmd(CMD12, 0);
		}
	}

	/* Idle */
	DESELECT();
	SPI_RxByte();

	return count ? RES_ERROR : RES_OK;
}

/* write sector */
#if FF_FS_READONLY == 0
DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
	/* pdrv should be 0 */
	if (pdrv || !count) return RES_PARERR;

	/* no disk */
	if (Stat & STA_NOINIT) return RES_NOTRDY;

	/* write protection */
	if (Stat & STA_PROTECT) return RES_WRPRT;

	/* convert to byte address */
	if (!(CardType & CT_BLOCK)) sector *= 512;

	SELECT();

	if (count == 1)
	{
		/* WRITE_BLOCK */
		if ((SD_SendCmd(CMD24, sector) == 0) && SD_TxDataBlock(buff, 0xFE))
			count = 0;
	}
	else
	{
		/* WRITE_MULTIPLE_BLOCK */
		if (CardType & CT_SD1)
		{
			SD_SendCmd(CMD55, 0);
			SD_SendCmd(CMD23, count); /* ACMD23 */
		}

		if (SD_SendCmd(CMD25, sector) == 0)
		{
			do {
				if(!SD_TxDataBlock(buff, 0xFC)) break;
				buff += 512;
			} while (--count);

			/* STOP_TRAN token */
			if(!SD_TxDataBlock(0, 0xFD))
			{
				count = 1;
			}
		}
	}

	/* Idle */
	DESELECT();
	SPI_RxByte();

	return count ? RES_ERROR : RES_OK;
}
#endif /* FF_FS_READONLY */

/* ioctl */
DRESULT SD_disk_ioctl(BYTE drv, BYTE ctrl, void *buff)
{
	DRESULT res;
	uint8_t n, csd[16], *ptr = (uint8_t*)buff;
	DWORD csize;

	/* pdrv should be 0 */
	if (drv) return RES_PARERR;
	res = RES_ERROR;

	if (ctrl == CTRL_POWER)
	{
		switch (*ptr)
		{
		case 0:
			SD_PowerOff();		/* Power Off */
			res = RES_OK;
			break;
		case 1:
			SD_PowerOn();		/* Power On */
			res = RES_OK;
			break;
		case 2:
			*(ptr + 1) = SD_CheckPower();
			res = RES_OK;		/* Power Check */
			break;
		default:
			res = RES_PARERR;
		}
	}
	else
	{
		/* no disk */
		if (Stat & STA_NOINIT) return RES_NOTRDY;

		SELECT();

		switch (ctrl)
		{
		case GET_SECTOR_COUNT:
			/* SEND_CSD */
			if ((SD_SendCmd(CMD9, 0) == 0) && SD_RxDataBlock(csd, 16))
			{
				if ((csd[0] >> 6) == 1)
				{
					/* SDC V2 */
					csize = csd[9] + ((WORD) csd[8] << 8) + ((DWORD) (csd[7] & 0x3F) << 16) + 1;
					*(DWORD*) buff = (DWORD) csize << 10;
				}
				else
				{
					/* MMC or SDC V1 */
					n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
					csize = (csd[8] >> 6) + ((WORD) csd[7] << 2) + ((WORD) (csd[6] & 3) << 10) + 1;
					*(DWORD*) buff = (DWORD) csize << (n - 9);
				}
				res = RES_OK;
			}
			break;
		case GET_SECTOR_SIZE:
			*(WORD*) buff = 512;
			res = RES_OK;
			break;
		case CTRL_SYNC:
			if (SD_ReadyWait() == 0xFF) res = RES_OK;
			break;
		default:
			res = RES_PARERR;
		}

		DESELECT();
		SPI_RxByte();
	}

	return res;
}
