/********************************** (C) COPYRIGHT *****************************
 * File Name          : CH390_Interface.c
 * Author             : WCH (Modified for STM32 HAL)
 * Version            : V1.1
 * Date               : 2024/08/20
 * Description        : CH390 interface for STM32 HAL Library (SPI mode)
 ******************************************************************************
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * Attention: This software (modified or not) and binary are used for
 * microcontroller manufactured by Nanjing Qinheng Microelectronics.
 * 
 * Modified for STM32F103 HAL Library with FreeRTOS support.
 ******************************************************************************/
#include "stm32f1xx_hal.h"
#include "main.h"
#include "CH390.h"
#include "CH390_Interface.h"
#include "SEGGER_RTT.h"

/* FreeRTOS includes */
#ifdef USE_FREERTOS
#include "FreeRTOS.h"
#include "task.h"
#endif

/*
 * This file defines CH390 operation interface using STM32 HAL Library.
 * Only SPI mode is implemented for this project.
 *
 * Hardware connections:
 * - PA4: SPI1_NSS (directly controlled as GPIO for CS)
 * - PA5: SPI1_SCK
 * - PA6: SPI1_MISO
 * - PA7: SPI1_MOSI
 * - PB0: CH390 INT (EXTI0)
 * - PB1: CH390 RST
 */

#ifdef CH390_INTERFACE_SPI

/* GPIO Pin Definitions - matching CubeMX configuration */
#define CH390_CS_PORT       GPIOA
#define CH390_CS_PIN        GPIO_PIN_4

#define CH390_RST_PORT      GPIOB
#define CH390_RST_PIN       GPIO_PIN_1

#define CH390_INT_PORT      GPIOB
#define CH390_INT_PIN       GPIO_PIN_0

#define CH390_SCK_PORT      GPIOA
#define CH390_SCK_PIN       GPIO_PIN_5
#define CH390_MISO_PORT     GPIOA
#define CH390_MISO_PIN      GPIO_PIN_6
#define CH390_MOSI_PORT     GPIOA
#define CH390_MOSI_PIN      GPIO_PIN_7

/* External SPI handle from spi.c */
extern SPI_HandleTypeDef hspi1;

/* Timeout for SPI operations (ms) */
#define SPI_TIMEOUT         100
#define CH390_SPI_CHUNK_SIZE 64u

/*----------------------------------------------------------------------------
 * Low-level GPIO operations
 *---------------------------------------------------------------------------*/

/**
 * @brief Set CS pin state
 * @param state 0=low (select), 1=high (deselect)
 */
static inline void ch390_cs(uint8_t state)
{
    HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN, 
                      state ? GPIO_PIN_SET : GPIO_PIN_RESET);
    ch390_delay_us(2);
}

/**
 * @brief Set RST pin state
 * @param state 0=low (reset), 1=high (normal)
 */
static inline void ch390_rst(uint8_t state)
{
    HAL_GPIO_WritePin(CH390_RST_PORT, CH390_RST_PIN, 
                      state ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

/*----------------------------------------------------------------------------
 * SPI Communication
 *---------------------------------------------------------------------------*/

/**
 * @brief Exchange one byte over SPI
 * @param byte Byte to send
 * @return Received byte
 */
static uint8_t ch390_spi_exchange_byte(uint8_t byte)
{
    uint8_t rx_data = 0;
    if (HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, SPI_TIMEOUT) != HAL_OK)
    {
        return 0;
    }
    return rx_data;
}

static int ch390_spi_read_bytes(uint8_t *data, uint16_t length)
{
    static const uint8_t dummy_tx[CH390_SPI_CHUNK_SIZE] = {0};

    while (length > 0u)
    {
        uint16_t chunk = (length > CH390_SPI_CHUNK_SIZE) ? CH390_SPI_CHUNK_SIZE : length;
        if (HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)dummy_tx, data, chunk, SPI_TIMEOUT) != HAL_OK)
        {
            return -1;
        }
        data += chunk;
        length = (uint16_t)(length - chunk);
    }

    return 0;
}

static void ch390_spi_apply_mode(uint32_t polarity, uint32_t phase)
{
    hspi1.Init.CLKPolarity = polarity;
    hspi1.Init.CLKPhase = phase;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    if (HAL_SPI_Init(&hspi1) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
 * @brief Read a dummy byte (send 0x00)
 * @return Received byte
 */
#define ch390_spi_dummy_read() ch390_spi_exchange_byte(0x00)

/*----------------------------------------------------------------------------
 * Public Interface Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Initialize CH390 GPIO pins
 * @note CS and RST pins are configured here. SPI pins are handled by CubeMX.
 *       INT pin (PB0) is configured as EXTI in CubeMX.
 */
void ch390_gpio_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    /* Enable GPIO clocks */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    
    /* Configure CS pin (PA4) as GPIO output - we control it manually */
    /* Note: CubeMX may configure PA4 as SPI1_NSS, we need to reconfigure it */
    HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN, GPIO_PIN_SET); /* Deselect */
    GPIO_InitStruct.Pin = CH390_CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(CH390_CS_PORT, &GPIO_InitStruct);
    
    /* Configure RST pin (PB1) as output */
    HAL_GPIO_WritePin(CH390_RST_PORT, CH390_RST_PIN, GPIO_PIN_SET); /* Not reset */
    GPIO_InitStruct.Pin = CH390_RST_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(CH390_RST_PORT, &GPIO_InitStruct);
    
    /* INT pin (PB0) is configured as EXTI input by CubeMX */
}

/**
 * @brief Initialize CH390 interrupt (EXTI0 on PB0)
 * @note EXTI and NVIC are configured in CubeMX. This function can enable/disable.
 */
void ch390_interrupt_init(void)
{
    /* EXTI0 is configured in CubeMX for PB0 */
    /* NVIC priority should be >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY */
    /* for FreeRTOS compatibility */
    HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

/**
 * @brief Initialize SPI for CH390
 * @note SPI1 is already initialized by CubeMX. This function reconfigures
 *       for CH390 requirements (Mode 3: CPOL=High, CPHA=2Edge).
 */
void ch390_spi_init(void)
{
    /* SPI1 is initialized by MX_SPI1_Init() in main.c */
    /* Reference CH390 SPI path uses mode 3. */
    ch390_spi_apply_mode(SPI_POLARITY_HIGH, SPI_PHASE_2EDGE);
    SEGGER_RTT_WriteString(0, "CH390 SPI mode=3 (CPOL=1 CPHA=1)\r\n");
}

/**
 * @brief Get CH390 interrupt pin state
 * @return Non-zero if INT pin is high (active low interrupt)
 */
uint16_t ch390_get_int_pin(void)
{
    return HAL_GPIO_ReadPin(CH390_INT_PORT, CH390_INT_PIN);
}

/**
 * @brief Delay in microseconds
 * @param time Delay time in microseconds
 * @note Uses DWT cycle counter for accurate timing if available,
 *       otherwise falls back to simple loop delay.
 */
void ch390_delay_us(uint32_t time)
{
#ifdef USE_FREERTOS
    /* For FreeRTOS, if delay is long enough, use vTaskDelay */
    if (time >= 1000)
    {
        /* Convert to milliseconds and use FreeRTOS delay if in task context */
        if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)
        {
            vTaskDelay(pdMS_TO_TICKS(time / 1000));
            time = time % 1000;
        }
    }
#endif
    
    /* Short delay using DWT or simple loop */
    if (time > 0)
    {
        /* Simple delay loop - approximately calibrated for 72MHz */
        /* Each iteration is roughly 1/9 us at 72MHz */
        volatile uint32_t count = time * 9;
        while (count--)
        {
            __NOP();
        }
    }
}

/**
 * @brief Hardware reset CH390 by pulling RST pin low
 */
void ch390_hardware_reset(void)
{
    ch390_delay_us(10000);      /* Short delay before reset */
    ch390_rst(0);               /* Assert reset (low) */
    ch390_delay_us(50000);      /* Recover chips stuck after repeated MCU resets */
    ch390_rst(1);               /* Release reset (high) */
    ch390_delay_us(500000);     /* Allow EEPROM load, PHY, and SPI state to settle */
}

/*----------------------------------------------------------------------------
 * CH390 Register/Memory Access Functions (SPI Mode)
 *---------------------------------------------------------------------------*/

/**
 * @brief Read a CH390 register
 * @param reg Register address
 * @return Register value
 */
uint8_t ch390_read_reg(uint8_t reg)
{
    uint8_t value;
    
    ch390_cs(0);                                /* CS low - select */
    ch390_spi_exchange_byte(reg | OPC_REG_R);   /* Send read command */
    value = ch390_spi_dummy_read();             /* Read register value */
    ch390_cs(1);                                /* CS high - deselect */
    
    return value;
}

/**
 * @brief Write a CH390 register
 * @param reg Register address
 * @param value Value to write
 */
void ch390_write_reg(uint8_t reg, uint8_t value)
{
    ch390_cs(0);                                /* CS low - select */
    (void)ch390_spi_exchange_byte(reg | OPC_REG_W);
    (void)ch390_spi_exchange_byte(value);
    ch390_cs(1);                                /* CS high - deselect */
}

/**
 * @brief Read data from CH390 RX SRAM
 * @param data Buffer to store received data
 * @param length Number of bytes to read
 */
void ch390_read_mem(uint8_t *data, int length)
{
    if (data == NULL || length <= 0)
    {
        return;
    }

    ch390_cs(0);                                /* CS low - select */
    ch390_spi_exchange_byte(OPC_MEM_READ);      /* Send memory read command */

    (void)ch390_spi_read_bytes(data, (uint16_t)length);
    
    ch390_cs(1);                                /* CS high - deselect */
}

/**
 * @brief Read data from CH390 RX SRAM using DMA (for larger transfers)
 * @param data Buffer to store received data
 * @param length Number of bytes to read
 * @note Falls back to polling mode for small transfers
 */
void ch390_read_mem_dma(uint8_t *data, int length)
{
    /* For small transfers, use polling mode */
    if (length < 64)
    {
        ch390_read_mem(data, length);
        return;
    }
    
    /* For larger transfers, could use DMA - currently using polling */
    /* TODO: Implement DMA transfer if needed for performance */
    ch390_read_mem(data, length);
}

/**
 * @brief Write data to CH390 TX SRAM
 * @param data Data buffer to send
 * @param length Number of bytes to write
 */
void ch390_write_mem(uint8_t *data, int length)
{
    if (data == NULL || length <= 0)
    {
        return;
    }

    ch390_cs(0);                                /* CS low - select */
    ch390_spi_exchange_byte(OPC_MEM_WRITE);     /* Send memory write command */

    (void)HAL_SPI_Transmit(&hspi1, data, (uint16_t)length, SPI_TIMEOUT);
    
    ch390_cs(1);                                /* CS high - deselect */
}

/**
 * @brief Write data to CH390 TX SRAM using DMA (for larger transfers)
 * @param data Data buffer to send
 * @param length Number of bytes to write
 * @note Falls back to polling mode for small transfers
 */
void ch390_write_mem_dma(uint8_t *data, int length)
{
    /* For small transfers, use polling mode */
    if (length < 64)
    {
        ch390_write_mem(data, length);
        return;
    }
    
    /* For larger transfers, could use DMA - currently using polling */
    /* TODO: Implement DMA transfer if needed for performance */
    ch390_write_mem(data, length);
}

#else /* CH390_INTERFACE_SPI */

/* 
 * Non-SPI modes (8-bit/16-bit parallel) are not implemented for this project.
 * This project uses SPI interface only.
 */
#error "This project only supports CH390 SPI interface. Please define CH390_INTERFACE_SPI in CH390.h"

#endif /* CH390_INTERFACE_SPI */
