// (C) 2020-2026 Philip Endecott.
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENCE.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#include "cpu.hh"
#include "debug.hh"
#include "gpio.hh"
#include "modbus.hh"
#include "opentherm.hh"
#include "power.hh"
//#include "rtc.hh"
#include "timer.hh"
#include "vic.hh"
#include "wdt.hh"


// Interrupt Controller
// ====================

IRQ_HANDLER
void default_irq_handler()
{
  debug("default irq!!\n");
  // Error.
  // Perhaps do more here if we want to ignore the interrupt, hmm.
}

static void setup_vic()
{
  *VICIntSelect   = 0;  // No FIQs.

  int slot = 0;
  uint32_t enables = 0;

  *VICDefVectAddr = (uint32_t) &default_irq_handler;

  // I've not made this work using a function; I think the attributes on the 
  // handler functions cause trouble.
#define ADD_HANDLER(HANDLER, INTNUM)        \
  VICvectCntl[slot]  = INTNUM | (1<<5);     \
  VICVectAddrs[slot] = (uint32_t) &HANDLER; \
  enables |= 1<<INTNUM;                     \
  ++slot;

  // Put the highest priority interrupts first.
  // (Note that higher-priority interrupts don't interrupt lower priority ones, 
  // so the ordering here doesn't matter very much.(
  ADD_HANDLER(modbus_uart_irq_handler,      vic_uart1_num);
  ADD_HANDLER(modbus_timer_irq_handler,     vic_timer0_num);
  ADD_HANDLER(opentherm::timer_irq_handler, vic_timer1_num);

#undef ADD_HANDLER

  *VICIntEnable = enables;
  // Note that writing to this register apparently sets bits but 
  // doesn't clear them; to clear bits, write ones to IntEnClear.
}


// Misc setup
// ==========

static void setup_power()
{
  set_peripheral_power(debug_power_bits
                     | modbus_power_bits
                     | opentherm::power_bits
                       );
}

static void setup_pins()
{
  // Pin configuration.
  // Pins default to GPIOs.
  *PINSEL0 = (0b01 <<  0)  // P0.0 is UART0 TXD
           | (0b01 <<  2)  // P0.1 is UART0 RXD
           | (0b01 << 16)  // P0.8 is UART1 TXD
           | (0b01 << 18)  // P0.9 is UART1 RXD
           | (0b01 << 20)  // P0.10 is UART1 RTS
           ;
  *PINSEL1 = (0b01 <<  4)  // P0.18 is CAP1.3 - for OpenTherm receive.
         //| (0b01 <<  8)  // P0.20 is MAT1.3 - no, we're not using timer match for OpenTherm tx.
           | (0b01 << 22)  // P0.27 is AIN0
           | (0b01 << 24)  // P0.28 is AIN1
           | (0b01 << 26)  // P0.29 is AIN2
           | (0b01 << 28)  // P0.30 is AIN3
           ;
  *PINSEL2 = 0;

}


// Outputs
// =======

struct output_info { uint8_t block; uint8_t bit; };

constexpr int n_outputs = 1;
constexpr output_info output_infos[n_outputs] = { {0,16} };


#ifdef RAD_CONTROLLER

constexpr int outputs = 20;
constexpr output_info output_infos[outputs] = {
  {1,16}, {1,17}, {1,18}, {1,19}, {1,20}, {1,21}, {1,22}, {1,23},
  {1,24}, {1,25}, {1,26}, {1,27}, {1,28}, {1,29}, {1,30}, {1,31},
  // The next pins along the edge of the board after P1.16-31 are P0.0-7.
  // P0.0 and P0.1 are the programming UART, so we don't use them; P0.2 and
  // P0.3 are I2C pins that are open-drain only so we skip those; we use
  // P0.4-7 (note the order).
  {0,6},  {0,7},  {0,4},  {0,5}
};

#endif


static void set_output(int output)
{
  debug("Set output ",output,'\n');
  auto info = output_infos[output-1];
  if (info.block == 0) gpio0_set_bit(info.bit);
  else                 gpio1_set_bit(info.bit);
}

static void clear_output(int output)
{
  debug("Clear output ",output,'\n');
  auto info = output_infos[output-1];
  if (info.block == 0) gpio0_clear_bit(info.bit);
  else                 gpio1_clear_bit(info.bit);
}



// modbus callbacks
// ================

constexpr auto output_begin    = 0;
constexpr auto output_end      = output_begin+16;
constexpr auto opentherm_begin = output_end;
constexpr auto opentherm_end   = opentherm_begin+32;


get_modbus_register_return_t get_modbus_register(uint16_t address)
{
  if (opentherm_begin <= address && address < opentherm_end) {

    address -= opentherm_begin;
    return { true, opentherm::get_register(address) };

  } else {
    return { false, 0 };
  }
}

SetRegisterStatus set_modbus_register(uint16_t address, uint16_t value)
{
  if (output_begin <= address && address < output_end) {

    address -= output_begin;
    if (address < 1 || address > n_outputs) return SetRegisterStatus::invalid_address;
    switch (value) {
      case 0x0100:
        set_output(address);
        break;
      case 0x0200:
        clear_output(address);
        break;
      default:
        return SetRegisterStatus::invalid_value;
    }
    return SetRegisterStatus::ok;

  } else if (opentherm_begin <= address && address < opentherm_end) {

    address -= opentherm_begin;
    opentherm::set_register(address,value);
    return SetRegisterStatus::ok;

  } else {
    return SetRegisterStatus::invalid_address;
  }
}



// main()
// ======

constexpr int timeout_s = 60;   // If no MODBUS commands have been successfully received and 
                                // replied to for this period, the device resets itself.


__attribute__((target("arm")))    // Needs to be ARM because it inlines functions that must be ARM, 
                                  // i.e. enable_irq().
int main() //int /*argc*/, char* /*argv*/[])
{
  bool was_wdt_reset [[maybe_unused]] = wdt_timedout();  // Set an LED?

  setup_memmap();
  setup_wdt<timeout_s>();
  setup_mam();
  setup_power();
  setup_debug();
  setup_modbus(42);  // slave address for boiler controller is 42.
  setup_pins();
  setup_gpio();
  setup_timer();

  opentherm::setup();

  setup_vic();

  debug("Hello world\n");
  if (was_wdt_reset) debug("Reset was due to watchdog\n");

  enable_irq();
  debug("Interrupts enabled\n");

  opentherm::start_sync_process();

  modbus_loop();

  while (1) {}
}

