// SAE J2284-4 document specifies a bus-line network running at 2 Mbit/s
// SAE J2284-5 document specifies a point-to-point communication running at 5 Mbit/s

#define CAN_PCLK 80000U // KHz, sourced from PLL1Q
#define BITRATE_PRESCALER 2U // Valid from 250Kbps to 5Mbps with 80Mhz clock
#define CAN_SP_NOMINAL 80U // 80% for both SAE J2284-4 and SAE J2284-5
#define CAN_SP_DATA_2M 80U // 80% for SAE J2284-4
#define CAN_SP_DATA_5M 75U // 75% for SAE J2284-5
#define CAN_QUANTA(speed, prescaler) (CAN_PCLK / ((speed) / 10U * (prescaler)))
#define CAN_SEG1(tq, sp) (((tq) * (sp) / 100U)- 1U)
#define CAN_SEG2(tq, sp) ((tq) * (100U - (sp)) / 100U)

// FDCAN core settings
#define FDCAN_MESSAGE_RAM_SIZE 0x2800UL
#define FDCAN_START_ADDRESS 0x4000AC00UL
#define FDCAN_OFFSET 3384UL // bytes for each FDCAN module, equally
#define FDCAN_OFFSET_W 846UL // words for each FDCAN module, equally
#define FDCAN_END_ADDRESS 0x4000D3FCUL // Message RAM has a width of 4 bytes

// FDCAN_RX_FIFO_0_EL_CNT + FDCAN_TX_FIFO_EL_CNT can't exceed 47 elements (47 * 72 bytes = 3,384 bytes) per FDCAN module

// RX FIFO 0
#define FDCAN_RX_FIFO_0_EL_CNT 46UL
#define FDCAN_RX_FIFO_0_HEAD_SIZE 8UL // bytes
#define FDCAN_RX_FIFO_0_DATA_SIZE 64UL // bytes
#define FDCAN_RX_FIFO_0_EL_SIZE (FDCAN_RX_FIFO_0_HEAD_SIZE + FDCAN_RX_FIFO_0_DATA_SIZE)
#define FDCAN_RX_FIFO_0_EL_W_SIZE (FDCAN_RX_FIFO_0_EL_SIZE / 4UL)
#define FDCAN_RX_FIFO_0_OFFSET 0UL

// TX FIFO
#define FDCAN_TX_FIFO_EL_CNT 1UL
#define FDCAN_TX_FIFO_HEAD_SIZE 8UL // bytes
#define FDCAN_TX_FIFO_DATA_SIZE 64UL // bytes
#define FDCAN_TX_FIFO_EL_SIZE (FDCAN_TX_FIFO_HEAD_SIZE + FDCAN_TX_FIFO_DATA_SIZE)
#define FDCAN_TX_FIFO_EL_W_SIZE (FDCAN_TX_FIFO_EL_SIZE / 4UL)
#define FDCAN_TX_FIFO_OFFSET (FDCAN_RX_FIFO_0_OFFSET + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_W_SIZE))

#define CAN_NAME_FROM_CANIF(CAN_DEV) (((CAN_DEV)==FDCAN1) ? "FDCAN1" : (((CAN_DEV) == FDCAN2) ? "FDCAN2" : "FDCAN3"))
#define CAN_NUM_FROM_CANIF(CAN_DEV) (((CAN_DEV)==FDCAN1) ? 0UL : (((CAN_DEV) == FDCAN2) ? 1UL : 2UL))


void print(const char *a);

// kbps multiplied by 10
const uint32_t speeds[] = {100U, 200U, 500U, 1000U, 1250U, 2500U, 5000U, 10000U};
const uint32_t data_speeds[] = {100U, 200U, 500U, 1000U, 1250U, 2500U, 5000U, 10000U, 20000U, 50000U};


bool fdcan_request_init(FDCAN_GlobalTypeDef *FDCANx) {
  bool ret = true;
  // Exit from sleep mode
  FDCANx->CCCR &= ~(FDCAN_CCCR_CSR);
  while ((FDCANx->CCCR & FDCAN_CCCR_CSA) == FDCAN_CCCR_CSA);

  // Request init
  uint32_t timeout_counter = 0U;
  FDCANx->CCCR |= FDCAN_CCCR_INIT;
  while ((FDCANx->CCCR & FDCAN_CCCR_INIT) == 0) {
    // Delay for about 1ms
    delay(10000);
    timeout_counter++;

    if (timeout_counter >= CAN_INIT_TIMEOUT_MS){
      ret = false;
      break;
    }
  }
  return ret;
}

bool fdcan_exit_init(FDCAN_GlobalTypeDef *FDCANx) {
  bool ret = true;

  FDCANx->CCCR &= ~(FDCAN_CCCR_INIT);
  uint32_t timeout_counter = 0U;
  while ((FDCANx->CCCR & FDCAN_CCCR_INIT) != 0) {
    // Delay for about 1ms
    delay(10000);
    timeout_counter++;

    if (timeout_counter >= CAN_INIT_TIMEOUT_MS) {
      ret = false;
      break;
    }
  }
  return ret;
}

bool llcan_set_speed(FDCAN_GlobalTypeDef *FDCANx, uint32_t speed, uint32_t data_speed, bool non_iso, bool loopback, bool silent) {
  UNUSED(speed);
  bool ret = fdcan_request_init(FDCANx);

  if (ret) {
    // Enable config change
    FDCANx->CCCR |= FDCAN_CCCR_CCE;

    //Reset operation mode to Normal
    FDCANx->CCCR &= ~(FDCAN_CCCR_TEST);
    FDCANx->TEST &= ~(FDCAN_TEST_LBCK);
    FDCANx->CCCR &= ~(FDCAN_CCCR_MON);
    FDCANx->CCCR &= ~(FDCAN_CCCR_ASM);
    FDCANx->CCCR &= ~(FDCAN_CCCR_NISO);

    // TODO: add as a separate safety mode
    // Enable ASM restricted operation(for debug or automatic bitrate switching)
    //FDCANx->CCCR |= FDCAN_CCCR_ASM;

    uint8_t prescaler = BITRATE_PRESCALER;
    if (speed < 2500U) {
      // The only way to support speeds lower than 250Kbit/s (down to 10Kbit/s)
      prescaler = BITRATE_PRESCALER * 16U;
    }

    // Set the nominal bit timing values
    uint16_t tq = CAN_QUANTA(speed, prescaler);
    uint8_t sp = CAN_SP_NOMINAL;
    uint8_t seg1 = CAN_SEG1(tq, sp);
    uint8_t seg2 = CAN_SEG2(tq, sp);
    uint8_t sjw = MIN(127U, seg2);

    FDCANx->NBTP = (((sjw & 0x7FU)-1U)<<FDCAN_NBTP_NSJW_Pos) | (((seg1 & 0xFFU)-1U)<<FDCAN_NBTP_NTSEG1_Pos) | (((seg2 & 0x7FU)-1U)<<FDCAN_NBTP_NTSEG2_Pos) | (((prescaler & 0x1FFU)-1U)<<FDCAN_NBTP_NBRP_Pos);

    // Set the data bit timing values
    if (data_speed == 50000U) {
      sp = CAN_SP_DATA_5M;
    } else {
      sp = CAN_SP_DATA_2M;
    }
    tq = CAN_QUANTA(data_speed, prescaler);
    seg1 = CAN_SEG1(tq, sp);
    seg2 = CAN_SEG2(tq, sp);
    sjw = MIN(15U, seg2);

    FDCANx->DBTP = (((sjw & 0xFU)-1U)<<FDCAN_DBTP_DSJW_Pos) | (((seg1 & 0x1FU)-1U)<<FDCAN_DBTP_DTSEG1_Pos) | (((seg2 & 0xFU)-1U)<<FDCAN_DBTP_DTSEG2_Pos) | (((prescaler & 0x1FU)-1U)<<FDCAN_DBTP_DBRP_Pos);

    if (non_iso) {
      // FD non-ISO mode
      FDCANx->CCCR |= FDCAN_CCCR_NISO;
    }

    // Silent loopback is known as internal loopback in the docs
    if (loopback) {
      FDCANx->CCCR |= FDCAN_CCCR_TEST;
      FDCANx->TEST |= FDCAN_TEST_LBCK;
      FDCANx->CCCR |= FDCAN_CCCR_MON;
    }
    // Silent is known as bus monitoring in the docs
    if (silent) {
      FDCANx->CCCR |= FDCAN_CCCR_MON;
    }
    ret = fdcan_exit_init(FDCANx);
    if (!ret) {
      print(CAN_NAME_FROM_CANIF(FDCANx)); print(" set_speed timed out! (2)\n");
    }
  } else {
    print(CAN_NAME_FROM_CANIF(FDCANx)); print(" set_speed timed out! (1)\n");
  }
  return ret;
}

void llcan_irq_disable(FDCAN_GlobalTypeDef *FDCANx) {
  if (FDCANx == FDCAN1) {
    NVIC_DisableIRQ(FDCAN1_IT0_IRQn);
    NVIC_DisableIRQ(FDCAN1_IT1_IRQn);
  } else if (FDCANx == FDCAN2) {
    NVIC_DisableIRQ(FDCAN2_IT0_IRQn);
    NVIC_DisableIRQ(FDCAN2_IT1_IRQn);
  } else if (FDCANx == FDCAN3) {
    NVIC_DisableIRQ(FDCAN3_IT0_IRQn);
    NVIC_DisableIRQ(FDCAN3_IT1_IRQn);
  } else {
  }
}

void llcan_irq_enable(FDCAN_GlobalTypeDef *FDCANx) {
  if (FDCANx == FDCAN1) {
    NVIC_EnableIRQ(FDCAN1_IT0_IRQn);
    NVIC_EnableIRQ(FDCAN1_IT1_IRQn);
  } else if (FDCANx == FDCAN2) {
    NVIC_EnableIRQ(FDCAN2_IT0_IRQn);
    NVIC_EnableIRQ(FDCAN2_IT1_IRQn);
  } else if (FDCANx == FDCAN3) {
    NVIC_EnableIRQ(FDCAN3_IT0_IRQn);
    NVIC_EnableIRQ(FDCAN3_IT1_IRQn);
  } else {
  }
}

bool llcan_init(FDCAN_GlobalTypeDef *FDCANx) {
  uint32_t can_number = CAN_NUM_FROM_CANIF(FDCANx);
  bool ret = fdcan_request_init(FDCANx);

  if (ret) {
    // Enable config change
    FDCANx->CCCR |= FDCAN_CCCR_CCE;
    // Enable automatic retransmission
    FDCANx->CCCR &= ~(FDCAN_CCCR_DAR);
    // Enable transmission pause feature
    FDCANx->CCCR |= FDCAN_CCCR_TXP;
    // Disable protocol exception handling
    FDCANx->CCCR |= FDCAN_CCCR_PXHD;
    // FD with BRS
    FDCANx->CCCR |= (FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE);

    // Set TX mode to FIFO
    FDCANx->TXBC &= ~(FDCAN_TXBC_TFQM);
    // Configure TX element data size
    FDCANx->TXESC |= 0x7U << FDCAN_TXESC_TBDS_Pos; // 64 bytes
    //Configure RX FIFO0 element data size
    FDCANx->RXESC |= 0x7U << FDCAN_RXESC_F0DS_Pos;
    // Disable filtering, accept all valid frames received
    FDCANx->XIDFC &= ~(FDCAN_XIDFC_LSE); // No extended filters
    FDCANx->SIDFC &= ~(FDCAN_SIDFC_LSS); // No standard filters
    FDCANx->GFC &= ~(FDCAN_GFC_RRFE); // Accept extended remote frames
    FDCANx->GFC &= ~(FDCAN_GFC_RRFS); // Accept standard remote frames
    FDCANx->GFC &= ~(FDCAN_GFC_ANFE); // Accept extended frames to FIFO 0
    FDCANx->GFC &= ~(FDCAN_GFC_ANFS); // Accept standard frames to FIFO 0

    uint32_t RxFIFO0SA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET);
    uint32_t TxFIFOSA = RxFIFO0SA + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_SIZE);

    // RX FIFO 0
    FDCANx->RXF0C |= (FDCAN_RX_FIFO_0_OFFSET + (can_number * FDCAN_OFFSET_W)) << FDCAN_RXF0C_F0SA_Pos;
    FDCANx->RXF0C |= FDCAN_RX_FIFO_0_EL_CNT << FDCAN_RXF0C_F0S_Pos;
    // RX FIFO 0 switch to non-blocking (overwrite) mode
    FDCANx->RXF0C |= FDCAN_RXF0C_F0OM;

    // TX FIFO (mode set earlier)
    FDCANx->TXBC |= (FDCAN_TX_FIFO_OFFSET + (can_number * FDCAN_OFFSET_W)) << FDCAN_TXBC_TBSA_Pos;
    FDCANx->TXBC |= FDCAN_TX_FIFO_EL_CNT << FDCAN_TXBC_TFQS_Pos;

    // Flush allocated RAM
    uint32_t EndAddress = TxFIFOSA + (FDCAN_TX_FIFO_EL_CNT * FDCAN_TX_FIFO_EL_SIZE);
    for (uint32_t RAMcounter = RxFIFO0SA; RAMcounter < EndAddress; RAMcounter += 4U) {
        *(uint32_t *)(RAMcounter) = 0x00000000;
    }

    // Enable both interrupts for each module
    FDCANx->ILE = (FDCAN_ILE_EINT0 | FDCAN_ILE_EINT1);

    FDCANx->IE &= 0x0U; // Reset all interrupts
    // Messages for INT0
    FDCANx->IE |= FDCAN_IE_RF0NE; // Rx FIFO 0 new message
    FDCANx->IE |= FDCAN_IE_PEDE | FDCAN_IE_PEAE | FDCAN_IE_BOE | FDCAN_IE_EPE | FDCAN_IE_RF0LE;

    // Messages for INT1 (Only TFE works??)
    FDCANx->ILS |= FDCAN_ILS_TFEL;
    FDCANx->IE |= FDCAN_IE_TFEE; // Tx FIFO empty

    ret = fdcan_exit_init(FDCANx);
    if(!ret) {
      print(CAN_NAME_FROM_CANIF(FDCANx)); print(" llcan_init timed out (2)!\n");
    }

    llcan_irq_enable(FDCANx);

  } else {
    print(CAN_NAME_FROM_CANIF(FDCANx)); print(" llcan_init timed out (1)!\n");
  }
  return ret;
}

void llcan_clear_send(FDCAN_GlobalTypeDef *FDCANx) {
  // from datasheet: "Transmit cancellation is not intended for Tx FIFO operation."
  // so we need to clear pending transmission manually by resetting FDCAN core
  FDCANx->IR |= 0x3FCFFFFFU; // clear all interrupts
  bool ret = llcan_init(FDCANx);
  UNUSED(ret);
}
