/**
 *******************************************************************************
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * All rights reserved.
 *
 * This software component is licensed by WCH under BSD 3-Clause license,
 * the "License"; You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                        opensource.org/licenses/BSD-3-Clause
 *
 *******************************************************************************
 */

#include "pins_arduino.h"
#include "mik32_hal_adc.h"
#include "wiring_analog.h"
#include "wiring_LL.h" 

#define SPI0_SWITCH_PORT    GPIO_1
#define SPI0_SWITCH_PIN     GPIO_PIN_10
#define SPI0_NSS_IN_PORT    GPIO_0
#define SPI0_NSS_IN_PIN     GPIO_PIN_3
#define SPI0_NSS_OUT_PORT   GPIO_1
#define SPI0_NSS_OUT_PIN    GPIO_PIN_14

#define SPI1_SWITCH_PORT    GPIO_1
#define SPI1_SWITCH_PIN     GPIO_PIN_6
#define SPI1_NSS_IN_PORT    GPIO_1
#define SPI1_NSS_IN_PIN     GPIO_PIN_3
#define SPI1_NSS_OUT_PORT   GPIO_1
#define SPI1_NSS_OUT_PIN    GPIO_PIN_4

bool spi0NssPinIsBlocked = false;
bool spi1NssPinIsBlocked = false;

// list of pin numbers from both ports with pins inside the port
const HAL_PinsTypeDef digitalPinToGpioPinArray[] =
{
  GPIO_PIN_5, // D0
  GPIO_PIN_6, // D1
  GPIO_PIN_10,// D2
  GPIO_PIN_0, // D3
  GPIO_PIN_8, // D4
  GPIO_PIN_1, // D5
  GPIO_PIN_2, // D6
  GPIO_PIN_8, // D7
  GPIO_PIN_9, // D8
  GPIO_PIN_3, // D9
  GPIO_PIN_3, // D10
  GPIO_PIN_1, // D11
  GPIO_PIN_0, // D12
  GPIO_PIN_2, // D13
  GPIO_PIN_5, // D14/A0
  GPIO_PIN_7, // D15/A1
  GPIO_PIN_4, // D16/A2
  GPIO_PIN_7, // D17/A3
  GPIO_PIN_12,// D18
  GPIO_PIN_13,// D19
  GPIO_PIN_9, // A4 (pin 20)
  GPIO_PIN_9, // A5 (pin 21)
  GPIO_PIN_7, // LED(pin 22)  
  0,          // dummy pin for backward compatibility with ace-uno
  GPIO_PIN_9, // A6 (pin 24)
  GPIO_PIN_9, // A7 (pin 25)
};

// determines the address of the port by the board pin number to which this pin belongs on the MCU
__attribute__((noinline, section(".ram_text"))) GPIO_TypeDef* digitalPinToPort(uint32_t digPinNumber)
{
  if (digPinNumber >= pinCommonQty())
  {
    ErrorMsgHandler("digitalPinToPort(): pin number exceeds the total number of pins");
    return NULL;
  }

  // port 2 - led (22)
  if (digPinNumber == LED_BUILTIN)
    return GPIO_2;
  // port 1 - board pins 7, 8, 10...13, 14(А0), 15(А1), 18,19
  else if (digPinNumber == 7 || digPinNumber == 8 || (digPinNumber >= 10 && digPinNumber <= 15) 
          || digPinNumber == 18 || digPinNumber == 19 || (digPinNumber == 9 && spi0NssPinIsBlocked))
    return GPIO_1;
  // port 0 - board pins 0...6, 9, 16(A2), 17(A3), 20(A4), 21(A5), 24(A6), 25(A7)
  else
    return GPIO_0;   
}

// determines the pin address inside the port by the board pin number
__attribute__((noinline, section(".ram_text"))) HAL_PinsTypeDef digitalPinToBitMask(uint32_t digPinNumber)
{
  if (digPinNumber >= pinCommonQty())
  {
    ErrorMsgHandler("digitalPinToBitMask(): pin number exceeds the total number of pins");
    return NOT_A_PIN;
  }
  
  // if spi is on default pin NSS_IN is needed for spi, board pin is replaced to pin NSS_OUT
  if ((digPinNumber == 10) && spi1NssPinIsBlocked)
    return SPI1_NSS_OUT_PIN;
  else if ((digPinNumber == 9) && spi0NssPinIsBlocked)
    return SPI0_NSS_OUT_PIN;
  else 
    return digitalPinToGpioPinArray[digPinNumber];
}

// the function returns a reference to the OUTPUT address of the GPIO register
volatile uint32_t* portOutputRegister(GPIO_TypeDef* GPIO_x)
{
  return &GPIO_x->OUTPUT_;
}

// the function returns a reference to the STATE address of the GPIO register
volatile uint32_t* portInputRegister(GPIO_TypeDef* GPIO_x)
{
  return &GPIO_x->STATE;
}

// the function initializes additional MCU pins depending on the specified pin number
#define SELA_A_PORT   GPIO_1
#define SELA_A_PIN    GPIO_PIN_15
#define SELA_B_PORT   GPIO_1
#define SELA_B_PIN    GPIO_PIN_11
static bool selaSwitchesIsInited = false;

void additionalPinsInit(uint32_t PinNumber)
{
  // init switches
  if (!selaSwitchesIsInited)
  {
    // SELA_A
    HAL_GPIO_PinConfig(SELA_A_PORT, SELA_A_PIN, HAL_GPIO_MODE_GPIO_OUTPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);
    // SELA_B
    HAL_GPIO_PinConfig(SELA_B_PORT, SELA_B_PIN, HAL_GPIO_MODE_GPIO_OUTPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);

    // select A7 via switches selaA, selaB to allow using A4 and A5 as digital outputs
    HAL_GPIO_WritePin(SELA_A_PORT, SELA_A_PIN, GPIO_PIN_HIGH);
    HAL_GPIO_WritePin(SELA_B_PORT, SELA_B_PIN, GPIO_PIN_HIGH);
    
    selaSwitchesIsInited = true;
  }
  
  if (PinNumber == A4)   
  {
    HAL_GPIO_WritePin(SELA_A_PORT, SELA_A_PIN, GPIO_PIN_LOW);
    HAL_GPIO_WritePin(SELA_B_PORT, SELA_B_PIN, GPIO_PIN_LOW);
  }
  else if(PinNumber == A5)
  {
    HAL_GPIO_WritePin(SELA_A_PORT, SELA_A_PIN, GPIO_PIN_HIGH);
    HAL_GPIO_WritePin(SELA_B_PORT, SELA_B_PIN, GPIO_PIN_LOW);
  }
  else if(PinNumber == A6)
  {
    HAL_GPIO_WritePin(SELA_A_PORT, SELA_A_PIN, GPIO_PIN_LOW);
    HAL_GPIO_WritePin(SELA_B_PORT, SELA_B_PIN, GPIO_PIN_HIGH);
  }
  else if(PinNumber == A7)
  {
    HAL_GPIO_WritePin(SELA_A_PORT, SELA_A_PIN, GPIO_PIN_HIGH);
    HAL_GPIO_WritePin(SELA_B_PORT, SELA_B_PIN, GPIO_PIN_HIGH);
  }
}

void additionalPinsDeinit(uint32_t PinNumber)
{
  if (PinNumber == A4 || PinNumber == A5)
  {
    // select A7 via switches selaA, selaB to allow using A4 and A5 as digital outputs
    HAL_GPIO_WritePin(SELA_A_PORT, SELA_A_PIN, GPIO_PIN_HIGH);
    HAL_GPIO_WritePin(SELA_B_PORT, SELA_B_PIN, GPIO_PIN_HIGH);
  }
}

// ---------------------- ADC ---------------------- //
// determines the ADC channel number by the board pin number
uint32_t analogInputToChannelNumber(uint32_t PinNumber)
{
  uint32_t adcChannel = 0;

 // if get a value 0...5 instead of A0...A7
  if      (PinNumber < 4) PinNumber += 14;  // A0...A3
  else if (PinNumber < 6) PinNumber += 16;  // A4, A5
  else if (PinNumber < 8) PinNumber += 18;  // A6, A7

  switch (PinNumber)
  {
    case PIN_A0:
      adcChannel = ADC_CHANNEL0;
      break;
    case PIN_A1:
      adcChannel = ADC_CHANNEL1;
      break;
    case PIN_A2:
      adcChannel = ADC_CHANNEL3;
      break;
    case PIN_A3:
      adcChannel = ADC_CHANNEL4;
      break;
    case PIN_A4:
    case PIN_A5:
    case PIN_A6:
    case PIN_A7:
      adcChannel = ADC_CHANNEL5;
      break;
    default:
      adcChannel = NC;
  }
  return adcChannel;
}

// ---------------------- PWM ---------------------- //
// return 1 if digitalPin configured as pwm and enabled, 
// 0 if pwm on pin is disabled, 
// -1 if pin doesn't support pwm
__attribute__((noinline, section(".ram_text"))) int8_t digitalPinPwmIsOn(uint8_t digitalPin)
{
  if (digitalPinHasPWM(digitalPin))
  {
    uint8_t config = 0;
    uint8_t pinShift = PIN_MASK_TO_PIN_NUMBER(digitalPinToBitMask(digitalPin));

    if ((digitalPin==10)||(digitalPin==11)||(digitalPin==12)||(digitalPin==13))
      config = PIN_GET_PAD_CONFIG(PORT_1_CFG, pinShift);
    else 
      config = PIN_GET_PAD_CONFIG(PORT_0_CFG, pinShift);

    if (config == 2)
      return 1; // pwm is on
    else
      return 0; // pwm is off
  }
  return -1;    // pin doesn't support pwm
}

// function is used only if digitalPinHasPWM() is true
TIMER32_TypeDef* pwmPinToTimer(uint32_t digPinNumber)
{
  TIMER32_TypeDef* timerNum = NULL;

  // timer 1
  if (digPinNumber == 3 || digPinNumber == 5 || digPinNumber == 6 || digPinNumber == 9)
    timerNum = TIMER32_1;
  // timer 2
  else if ((digPinNumber >= 10) && (digPinNumber <= 13))
    timerNum = TIMER32_2;
  
  return timerNum;
}

// function is used only if digitalPinHasPWM() is true
HAL_TIMER32_CHANNEL_IndexTypeDef pwmPinToTimerChannel(uint32_t digPinNumber)
{
  HAL_TIMER32_CHANNEL_IndexTypeDef channel = 0;
  if      (digPinNumber == 3 || digPinNumber == 12)   channel = TIMER32_CHANNEL_0;
  else if (digPinNumber == 5 || digPinNumber == 11)   channel = TIMER32_CHANNEL_1;
  else if (digPinNumber == 6 || digPinNumber == 13)   channel = TIMER32_CHANNEL_2;
  else if (digPinNumber == 9 || digPinNumber == 10)   channel = TIMER32_CHANNEL_3;
  return channel;
}

// ---------------------- interrupts ---------------------- //
// interrupt table is stored in ram to improve performance
// index = interrupt number. In each row {digitalPinNumber, IntLineValue, IntMuxValue}
uint8_t interruptInfo[EXTERNAL_INTERRUPTS_QTY][3] = 
{
  { 2,      GPIO_LINE_2,  GPIO_MUX_LINE_2_PORT0_10},  // INT0
  { 3,      GPIO_LINE_0,  GPIO_MUX_LINE_0_PORT0_0},   // INT1
  { 4,      GPIO_LINE_4,  GPIO_MUX_LINE_4_PORT0_8},   // INT2
  { 5,      GPIO_LINE_1,  GPIO_MUX_LINE_1_PORT0_1},   // INT3
  { 6,      GPIO_LINE_6,  GPIO_MUX_LINE_6_PORT0_2},   // INT4
  { 8,      GPIO_LINE_5,  GPIO_MUX_LINE_5_PORT1_9},   // INT5
  { 9,      GPIO_LINE_3,  GPIO_MUX_LINE_3_PORT0_3},   // INT6
  { A1,     GPIO_LINE_7,  GPIO_MUX_LINE_7_PORT1_7},   // INT7
};

int8_t digitalPinToGpioIntMux(uint8_t digPinNumber)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][0] == digPinNumber)
      return interruptInfo[i][2];
  }
  return NOT_AN_INTERRUPT;
}
int8_t digitalPinToGpioIntLine(uint8_t digPinNumber)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][0] == digPinNumber)
      return interruptInfo[i][1];
  }
  return NOT_AN_INTERRUPT;
}

int8_t gpioIntLineToInterrupt(uint32_t gpioIntLine)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][1] == gpioIntLine)
      return i;
  }
  return NOT_AN_INTERRUPT;
}

int8_t digitalPinToInterrupt(uint32_t digPinNumber)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][0] == digPinNumber)
      return i;
  }
  return NOT_AN_INTERRUPT;
}

// ---------------------- SPI ---------------------- //
void spi_onBegin(uint8_t spiNum)
{
  // On Elbear Nano spi0 needs pin 0.3, spi1 needs pin 1.3 for correct work (NSS_IN).
  // This pins are connected to board digital pins D9 and D10. There is a seller on each pin which 
  // replace board digital pin from NSS_IN pin to different free pin (NSS_OUT)

  // replace config from NSS_IN to NSS_OUT 
  uint8_t config;
  if (spiNum == 1)
    config = PIN_GET_PAD_CONFIG(PORT_1_CFG, PIN_MASK_TO_PIN_NUMBER(SPI1_NSS_IN_PIN));
  else
    config = PIN_GET_PAD_CONFIG(PORT_0_CFG, PIN_MASK_TO_PIN_NUMBER(SPI0_NSS_IN_PIN));

  if (config == 0)    // common gpio
  {
    if (spiNum == 1)
    {
      // get info from pin NSS_IN and set config to NSS_OUT
      HAL_GPIO_PinConfig(SPI1_NSS_OUT_PORT, SPI1_NSS_OUT_PIN, HAL_GPIO_GetPinDirection(SPI1_NSS_IN_PORT, SPI1_NSS_IN_PIN), 
                        (HAL_GPIO_PullTypeDef)PIN_GET_PAD_CONFIG(PORT_1_PUPD, PIN_MASK_TO_PIN_NUMBER(SPI1_NSS_IN_PIN)), 
                        HAL_GPIO_DS_2MA);
      HAL_GPIO_WritePin(SPI1_NSS_OUT_PORT, SPI1_NSS_OUT_PIN, (GPIO_PinState)GPIO_GET_PIN_STATE(SPI1_NSS_IN_PORT, PIN_MASK_TO_PIN_NUMBER(SPI1_NSS_IN_PIN)));
      
      // pin D10 was switched to different gpio and can be used further
    }
    else
    {
      // get info from pin NSS_IN and set config to NSS_OUT
      HAL_GPIO_PinConfig(SPI0_NSS_OUT_PORT, SPI0_NSS_OUT_PIN, HAL_GPIO_GetPinDirection(SPI0_NSS_IN_PORT, SPI0_NSS_IN_PIN), 
                        (HAL_GPIO_PullTypeDef)PIN_GET_PAD_CONFIG(PORT_0_PUPD, PIN_MASK_TO_PIN_NUMBER(SPI0_NSS_IN_PIN)), 
                        HAL_GPIO_DS_2MA);
      HAL_GPIO_WritePin(SPI0_NSS_OUT_PORT, SPI0_NSS_OUT_PIN, (GPIO_PinState)GPIO_GET_PIN_STATE(SPI0_NSS_IN_PORT, PIN_MASK_TO_PIN_NUMBER(SPI0_NSS_IN_PIN)));
      // pin D9 was switched to different gpio and can be used further
    }
  }
  else if(config == 2)  // timer for pwm
  {
    // if spi NSS_IN pin was used as pwm, we need to stop timer, because another pins don't support it
    if (spiNum == 1)
    {
      analogWriteStop(10);
      ErrorMsgHandler("analogWrite(): D10 cannot be used as PWM pin while SPI is running");
    }
    else
    {
      analogWriteStop(9);
      ErrorMsgHandler("analogWrite(): D9 cannot be used as PWM pin while SPI1 is running");
    }
  }
  
  // switch seller to pin NSS_OUT
  if (spiNum == 1)
  {
    HAL_GPIO_PinConfig(SPI1_SWITCH_PORT, SPI1_SWITCH_PIN, HAL_GPIO_MODE_GPIO_OUTPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);
    HAL_GPIO_WritePin (SPI1_SWITCH_PORT, SPI1_SWITCH_PIN, GPIO_PIN_HIGH);
    spi1NssPinIsBlocked = true; // block spi pin
  }
  else
  {
    HAL_GPIO_PinConfig(SPI0_SWITCH_PORT, SPI0_SWITCH_PIN, HAL_GPIO_MODE_GPIO_OUTPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);
    HAL_GPIO_WritePin (SPI0_SWITCH_PORT, SPI0_SWITCH_PIN, GPIO_PIN_HIGH);
    spi0NssPinIsBlocked = true; // block spi pin
  }
}

void spi_onEnd(uint8_t spiNum)
{
  // get info from pin NSS_OUT and set config to NSS_IN
  if (spiNum == 1)
  {
    HAL_GPIO_PinConfig(SPI1_NSS_IN_PORT, SPI1_NSS_IN_PIN, HAL_GPIO_GetPinDirection(SPI1_NSS_OUT_PORT, SPI1_NSS_OUT_PIN), 
                      (HAL_GPIO_PullTypeDef)PIN_GET_PAD_CONFIG(PORT_1_PUPD, PIN_MASK_TO_PIN_NUMBER(SPI1_NSS_OUT_PIN)), HAL_GPIO_DS_2MA);
    HAL_GPIO_WritePin (SPI1_NSS_IN_PORT, SPI1_NSS_IN_PIN,  
                      (GPIO_PinState)GPIO_GET_PIN_STATE(SPI1_NSS_OUT_PORT, PIN_MASK_TO_PIN_NUMBER(SPI1_NSS_OUT_PIN)));

    // switch seller back to NSS_IN
    HAL_GPIO_WritePin(SPI1_SWITCH_PORT, SPI1_SWITCH_PIN, GPIO_PIN_LOW);
    spi1NssPinIsBlocked = false; // unblock spi pin
  }
  else
  {
    HAL_GPIO_PinConfig(SPI0_NSS_IN_PORT, SPI0_NSS_IN_PIN, HAL_GPIO_GetPinDirection(SPI0_NSS_OUT_PORT, SPI0_NSS_OUT_PIN), 
                      (HAL_GPIO_PullTypeDef)PIN_GET_PAD_CONFIG(PORT_1_PUPD, PIN_MASK_TO_PIN_NUMBER(SPI0_NSS_OUT_PIN)), HAL_GPIO_DS_2MA);
    HAL_GPIO_WritePin (SPI0_NSS_IN_PORT, SPI0_NSS_IN_PIN,  
                      (GPIO_PinState)GPIO_GET_PIN_STATE(SPI0_NSS_OUT_PORT, PIN_MASK_TO_PIN_NUMBER(SPI0_NSS_OUT_PIN)));

    // switch seller back to NSS_IN
    HAL_GPIO_WritePin(SPI0_SWITCH_PORT, SPI0_SWITCH_PIN, GPIO_PIN_LOW);
    spi0NssPinIsBlocked = false; // unblock spi pin
  }
}