--[[
  Copyright (c) 2021-2025 by Plexim GmbH
  All rights reserved.

  A free license is granted to anyone to use this software for any legal
  non safety-critical purpose, including commercial applications, provided
  that:
  1) IT IS NOT USED TO DIRECTLY OR INDIRECTLY COMPETE WITH PLEXIM, and
  2) THIS COPYRIGHT NOTICE IS PRESERVED in its entirety.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
--]]--
local T = require('targets.TI28')
local U = require('common.utils')
local P = require('resources.TI29H85x_pin_map')
local PLL = require('utils.pll_config')

local TripInputsAllocated = {}
local GpioAllocated = {}

function T.getFamilyPrefix()
  return '29H85x'
end

function T.getC2pConfig(c2pAdapter)
  return '29H850TU-CPU1_JTAG %s' % {c2pAdapter}
end

function T.getOptions()
  -- these are the values corresponding to drop down menu selections 
  -- in Coder Options. The order must match info.xml file.
  return {
    ChipCombo = {'F29H850TU9'},
    boards = {'custom', 'launchpad', 'som_evm'},
    uniFlashConfigs = {
      launchpad = 'Launchpad_F29H850TU9.ccxml',
      som_evm = 'SOM_EVM_F29H850TU9.ccxml'
    },
    c2pConfigs = {
      launchpad = T.getC2pConfig('xds110'),
      som_evm = T.getC2pConfig('xds110'),
    },
    linkerFiles = {
      {'f29h85x_flash_lnk.cmd', 'f29h85x_ram_lnk.cmd'},
    },
  }
end

function T.configure(resources)
  resources:add('ADC A')
  resources:add('ADC B')
  resources:add('ADC C')
  resources:add('ADC D')
  resources:add('ADC E')
  resources:add('ADCA-SOC', 0, 31)
  resources:add('ADCB-SOC', 0, 31)
  resources:add('ADCC-SOC', 0, 31)
  resources:add('ADCD-SOC', 0, 31)
  resources:add('ADCE-SOC', 0, 31)
  resources:add('Base Task Load')
  resources:add('Base Task Load CPU2')
  resources:add('CAN A')
  resources:add('CAP', 1, 6)
  resources:add('CMPSS', 1, 12)
  resources:add('CPU1TIMER', 0, 1)
  resources:add('CPU2TIMER', 0, 1)
  resources:add('DAC A')
  resources:add('DAC B')
  resources:add('EXTSYNC', 1, 2)
  resources:add('GPIO', 0, 242)
  resources:add('HRPWM', 1, 18)
  resources:add('IPC_FLAG', 4, 30)  -- only flags without interrupt capability, flag 31 used for start-up sync
  resources:add('MCAN A')
  resources:add('MCAN B')
  resources:add('MCAN C')
  resources:add('MCAN D')
  resources:add('MCAN E')
  resources:add('MCAN F')
  resources:add('Powerstage Control')
  resources:add('Powerstage Control CPU2')
  resources:add('PWM', 1, 18)
  resources:add('QEP', 1, 6)
  resources:add('SCI A')
  resources:add('SCI B')
  resources:add('SDFM 1', 1, 4)
  resources:add('SDFM 2', 1, 4)
  resources:add('SDFM 3', 1, 4)
  resources:add('SDFM 4', 1, 4)
  resources:add('SPI A')
  resources:add('SPI B')
  resources:add('SPI C')
  resources:add('SPI D')
  resources:add('SPI E')
  resources:add('UART A')
  resources:add('UART B')
  resources:add('UART C')
  resources:add('UART D')
  resources:add('UART E')
  resources:add('UART F')
  resources:add('XBAR_INPUT', 1, 16)
end

function T.validateAlternateFunction(fun)
  local settings = P.getPinSettings(fun)
  return settings ~= nil
end

function T.getTargetParameters()
  local params = {
    adcs = {
      peripheralType = 5,
      vrefConfigInterfaceType = 5,
      defaultVref = 3.0, -- default vref (double-check!)
      num_channels = 32,
      vref_min = 2.4, -- (double-check!)
      vref_max = 3.3, -- allowable range on external vref (double-check!)
      num_units = 5,  -- 5 ADCs with independent references
      -- The below ADC channels require configuration as an analog port
      analog_mapping = {
        A6 = 224,
        A7 = 225,
        A8 = 226,
        A9 = 227,
        A10 = 228,
        A11 = 229,
        A28 = 246,
        A29 = 247,
        A30 = 248,
        A31 = 249,

        B6 = 230,
        B7 = 231,
        B8 = 232,
        B9 = 233,
        B10 = 234,
        B11 = 235,
        B28 = 240,
        B29 = 241,
        B30 = 242,
        B31 = 243,

        C6 = 236,
        C7 = 237,
        C8 = 238,
        C9 = 239,
        C28 = 244,
        C29 = 245,

        D4 = 240,
        D5 = 241,
        D6 = 242,
        D7 = 243,
        D8 = 244,
        D9 = 245,

        E4 = 246,
        E5 = 247,
        E6 = 248,
        E7 = 249,
        E24 = 224,
        E25 = 225,
        E26 = 230,
        E27 = 231,
      },
      vrefMap = {A = 'A', B = 'A', C = 'C', D = 'C', E = 'C'},
    },
    caps = {
      type = 3,
    },
    -- FIXME: The following still needs to be verified
    cbc_trip_inputs = {8, 9, 10, 11, 12},
    comps = {
      -- Comparator entries are stored as pin = {high = {unit, mux}, low = {unit, mux}}
      positive = {
        A2 = {high = {1, 1}, low = {1, 1}},
        A3 = {high = {1, 2}, low = {1, 2}},
        A4 = {high = {1, 0}, low = {1, 0}},
        A6 = {high = {2, 0}, low = {2, 0}},
        A7 = {high = {9, 2}, low = {9, 2}},
        A8 = {low = {8, 3}},
        A9 = {high = {6, 4}},
        A10 = {high = {7, 4}},
        A11 = {high = {8, 4}},
        A12 = {high = {1, 5}, low = {1, 5}},
        A13 = {high = {2, 5}, low = {2, 5}},
        A26 = {high = {3, 4}, low = {3, 4}},
        A27 = {high = {4, 4}, low = {4, 4}},
        A28 = {high = {8, 1}, low = {8, 1}},
        A29 = {high = {8, 2}, low = {8, 2}},
        A30 = {high = {5, 1}, low = {5, 1}},
        A31 = {high = {5, 2}, low = {5, 2}},
        B1 = {high = {3, 2}, low = {3, 2}},
        B3 = {high = {1, 3}, low = {1, 3}},
        B4 = {high = {7, 1}, low = {7, 1}},
        B5 = {high = {7, 2}, low = {7, 2}},
        B6 = {high = {9, 4}},
        B7 = {high = {10, 4}},
        B10 = {low = {5, 4}},
        B11 = {low = {6, 4}},
        B12 = {low = {7, 4}},
        B13 = {low = {8, 4}},
        B16 = {high = {9, 5}},
        B17 = {high = {10, 5}},
        B26 = {high = {4, 3}, low = {4, 3}},
        B28 = {low = {5, 3}},
        B29 = {high = {4, 1}, low = {4, 1}},
        B30 = {high = {1, 4}, low = {1, 4}},
        B31 = {high = {2, 4}, low = {2, 4}},
        C2 = {high = {9, 1}, low = {9, 1}},
        C3 = {low = {9, 4}},
        C4 = {low = {10, 4}},
        C5 = {low = {11, 4}},
        C6 = {low = {12, 4}},
        C7 = {high = {5, 5}},
        C8 = {low = {12, 0}},
        C9 = {low = {9, 3}},
        C10 = {high = {8, 5}},
        C11 = {high = {11, 5}},
        C12 = {high = {12, 5}},
        C13 = {low = {5, 5}},
        C16 = {low = {6, 5}},
        C17 = {low = {7, 5}},
        C30 = {high = {2, 1}, low = {2, 1}},
        C31 = {high = {2, 2}, low = {2, 2}},
        D10 = {high = {10, 0}, low = {10, 0}},
        D11 = {high = {11, 0}, low = {11, 0}},
        D12 = {high = {5, 0}, low = {5, 0}},
        D13 = {high = {2, 3}, low = {2, 3}},
        D16 = {high = {10, 1}, low = {10, 1}},
        D17 = {high = {11, 1}, low = {11, 1}},
        E10 = {high = {10, 2}, low = {10, 2}},
        E11 = {high = {11, 2}, low = {11, 2}},
        E16 = {high = {6, 2}, low = {6, 2}},
        E17 = {high = {6, 1}, low = {6, 1}},
      },

      negative = {
        A1 = {high = {4, 0}, low = {4, 0}},
        A2 = {high = {9, 0}, low = {9, 0}},
        A3 = {high = {1, 1}, low = {1, 1}},
        A4 = {high = {2, 1}, low = {2, 1}},
        A5 = {high = {1, 0}, low = {1, 0}},
        A6 = {high = {12, 0}, low = {12, 0}},
        A7 = {high = {2, 0}, low = {2, 0}},
        B3 = {high = {3, 0}, low = {3, 0}},
        B4 = {high = {7, 1}, low = {7, 1}},
        B5 = {high = {3, 1}, low = {3, 1}},
        B6 = {high = {11, 0}, low = {11, 0}},
        B26 = {high = {7, 0}, low = {7, 0}},
        B28 = {high = {8, 0}, low = {8, 0}},
        B29 = {high = {4, 1}, low = {4, 1}},
        C2 = {high = {11, 1}, low = {11, 1}},
        C30 = {high = {10, 0}, low = {10, 0}},
        C31 = {high = {9, 1}, low = {9, 1}},
        D12 = {high = {10, 1}, low = {10, 1}},
        D13 = {high = {5, 0}, low = {5, 0}},
      }
    },
    cpu_timers = {0, 1},
    dacs = {
      type = 2,
      minOutputVoltage = 0.0,
      maxOutputVoltage = 3.3,
      vrefMap = {A = 'A', B = 'C'},
    },
    epwms = {
      type = 5,
      max_event_period = 15,
      num_units = 18,
      sync_group_size = 1,
    },
    gpios = {
      openDrainSupported = true,
    },
    hrpwms = {
      num_units = 18,
    },
    mcans = {
      type = 2,
      ports = {'A', 'B', 'C', 'D', 'E', 'F'},
    },
    qeps = {},
    scis = {
      type = 0,
      num_units = 2,
    },
    sdfms = {
      num_units = 4,
    },
    spis = {
      type = 2, fifo_depth = 16,
    },
    uarts = {
      num_units = 6,
    },
    trip_groups = {A = 4, B = 5, C = 7},
  }
  return params
end

function T.allocateGpio(gpio, properties, req, label)
  GpioAllocated[gpio] = properties
  req:add('GPIO', gpio, label)
end

function T.isGpioAllocated(gpio)
  return (GpioAllocated[gpio] ~= nil)
end

function T.getGpioProperties(gpio)
  return GpioAllocated[gpio]
end

function T.getNextAvailableTripInput()
  local availableInputs = T.getTargetParameters().cbc_trip_inputs
  for _, tin in ipairs(availableInputs) do
    if TripInputsAllocated[tin] == nil then
      TripInputsAllocated[tin] = true
      return tin
    end
  end
end

function T.getComparatorsForPin(pin)
  local compHigh, compLow

  local comp = T.getTargetParameters().comps.positive[pin]
  if comp == nil then
    return nil
  end
  if comp.high then
    compHigh = {
      unit = comp.high[1],
      mux = comp.high[2],
    }
  end
  if comp.low then
    compLow = {
      unit = comp.low[1],
      mux = comp.low[2],
    }
  end
  return {
    low = compLow,
    high = compHigh,
  }
end

function T.checkGpioIsValidPwmSync(gpio)
  -- FIXME: Not fully verified (unable to find equivalent of XBAR_GPIO_MAX_CNT)
end

function T.pinHasAnalogMode(pin)
  -- must match GPIO_setAnalogMode
  return ((pin >= 160) and (pin <= 213)) or  ((pin >= 224) and (pin <= 249))
end

function T.pinRequiresGpioConfig(pin)
  -- TDI and TDO pins are not GPIOs per default
  return (pin == 222) or (pin == 223)
end

function T.getPwmSyncInSel(params)
  local mux
  if params.type == 'external' then
    if params.source_unit == 1 then
      mux = 0x18 -- EPWM_SYNC_IN_PULSE_SRC_INPUTXBAR_OUT5
    elseif params.source_unit == 2 then
      mux = 0x19 -- EPWM_SYNC_IN_PULSE_SRC_INPUTXBAR_OUT6
    else
      U.error('Unsupported external sync unit (%d).' % {params.source_unit})
    end
  elseif params.type == 'epwm' then
    mux = params.source_unit
  else
    U.error('Unsupported sync type (%s).' % {params.type})
  end
  return mux
end

function T.getLowSpeedClock()
  U.error('This chip has no LowSpeed Clock!')
end

function T.getPwmClock()
  return T.getCleanSysClkHz() -- 200 MHz max
end

function T.getTimerClock()
  -- relevant clock for dispatcher
  return T.getPwmClock()
end

function T.getDeadTimeClock()
  -- TODO
  return T.getPwmClock()
end

function T.getAdcClock()
  -- TODO:  Set clock to a maximum of 60 MHz and update code below
  local sysClkHz = T.getCleanSysClkHz()
  if sysClkHz <= 50000000 then
    return sysClkHz
  elseif sysClkHz <= 75000000 then
    return sysClkHz / 1.5
  elseif sysClkHz <= 100000000 then
    return sysClkHz / 2
  elseif sysClkHz <= 120000000 then
    return sysClkHz / 2.5
  elseif sysClkHz <= 150000000 then
    return sysClkHz / 3.0
  elseif sysClkHz <= 175000000 then
    return sysClkHz / 3.5
  else
    return sysClkHz / 4
  end
end

function T.getCanClkAndMaxBrp()
  return T.getCleanSysClkHz(), 0x400
end

function T.calcACQPS(ts, sigmodeDifferential)
  local tsMin
  local acqpsMax = 511
  local acqpsMin = 0
  -- NOTE: This chip supports differential and single ended mode for both 12 bit and 16 bit.
  -- However, we are assuming 12 bit resolution for single ended and 16 bit for differential
  -- to match previous convention for type 4 ADCs.
  if sigmodeDifferential then
    tsMin = math.max(320e-9, 1 / T.getAdcClock()) -- per datasheet, differential (16 bit)
  else
    tsMin = math.max(75e-9, 1 / T.getAdcClock()) -- per datasheet, single ended (12 bit)
  end
  local sysClkHz = Target.Variables.sysClkMHz * 1e6
  if (not U.isScalar(ts)) or (ts < tsMin) then
    ts = tsMin
  end

  return U.clampNumber(math.ceil(ts * sysClkHz) - 1, {acqpsMin, acqpsMax})
end

function T.getCpuTimerSetupCode(unit, params)
  -- TODO: Implement clock divider settings - do we still want to match PWM clock?
  local code = [[
    CPUTimer_setPeriod(CPUTIMER%(unit)d_BASE, %(period)d - 1);
    CPUTimer_setPreScaler(CPUTIMER%(unit)d_BASE, 0);
    CPUTimer_stopTimer(CPUTIMER%(unit)d_BASE);
    CPUTimer_reloadTimerCounter(CPUTIMER%(unit)d_BASE);
  ]] % {
    unit = unit,
    period = params.period,
  }

  if params.isr then
    -- note, this is really hard-coded for CPUTimer0
    local isrConfigCode = [[
      Interrupt_Config cfg ={
            .enable        = true,
            .priority      = 40,
            .linkOwner     = SSU_LINK2,
            .contextID     = INTERRUPT_CONTEXT_AGNOSTIC,
            .apiLinkID     = SSU_API_DISABLE
        };

        cfg.handler = &%(isr)s;
        Interrupt_configChannel(INT_TIMER%(unit)d, cfg);
	  ]] % {
      unit = unit,
      isr = params.isr,
    }
    code = code..isrConfigCode
  else
    -- the following configuration is made so that the CPUTimer can issue ADC SOC events
    -- if the CPUTimer is the actual interrupt source, then its enabling is deferred to
    -- Controller_enableTasksInterrupt()
    code = code .. [[
      CPUTimer_enableInterrupt(CPUTIMER%(unit)d_BASE);
    ]] % {
      unit = unit,
    }
  end
  return code
end

function T.getEpwmTimersSyncCode()
  return 'SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);'
end

function T.getAdcSetupCode(unitAsChar, params)
  -- TODO: Verify ADC clock configuration!
  local code = [[
    Adc%(adc)sRegs.ADCINTSEL1N2.bit.INT1CONT = 0; // disable ADCINT1 Continuous mode
    Adc%(adc)sRegs.ADCINTSEL1N2.bit.INT1SEL = %(int1sel)d; // setup EOC%(int1sel)d or OSINT(%(int1sel)d - 15) to trigger ADCINT1
    Adc%(adc)sRegs.ADCINTSEL1N2.bit.INT1E = 1; // enable ADCINT1
    Adc%(adc)sRegs.ADCCTL1.bit.INTPULSEPOS = 1; // ADCINT1 trips after AdcResults latch
  ]] % {
    adc = string.lower(unitAsChar),
    int1sel = params.INT1SEL,
  }

  if params.isr then
    local isrConfigCode = [[
      Interrupt_register(INT_ADC%(adc)s1, &%(isr)s);
    ]] % {
      adc = unitAsChar,
      isr = params.isr,
    }
    code = code..isrConfigCode
  end

  return code
end

function T.getAdcInterruptAcknCode(unitAsChar, params)
  return [[
    ADC_clearInterruptStatus(ADC%(unit)s_BASE, ADC_INT_NUMBER1);
  ]] % {
    unit = unitAsChar,
  }
end

-- Configure Clock settings and limits related to the PLL
local function setAndGetPllLimits(useInternalOsc, externalClockIsSingleEnded)
  local limits = PLL.new(useInternalOsc)

  limits:set('INTERNAL_OSCILLATOR_HZ', {value = 10e6})  -- INTOSC2

  if externalClockIsSingleEnded then  
    limits:set('OSCCLK', {min = 10e6, max = 25e6})
  else
    limits:set('OSCCLK', {min = 10e6, max = 20e6})
  end
  limits:set('INTCLK', {min = 10e6, max = 25e6})
  limits:set('VCOCLK', {min = 220e6, max = 600e6})
  limits:set('PLLRAWCLK', {min = 6e6, max = 400e6})
  limits:set('SYSCLK', {min = 2e6, max = 200e6})

  limits:set('REFDIV', {max = 32})
  limits:set('ODIV', {max = 32})
  limits:set('IMULT', {max = 127})
  limits:set('SYSDIV', {max = 126})

  return limits:getLimits()  -- executes validation and returns limits
end

function T.getClockConfiguration()
  local useInternalOsc = (Target.Variables.useIntOsc == 1) -- convert gui value to boolean

  local externalClockIsSingleEnded = (Target.Variables.ExternalClockIsSingleEnded == 1)
  local limits = setAndGetPllLimits(useInternalOsc, externalClockIsSingleEnded)


  local sysClkHz = T.getCleanSysClkHz()

  local inputClkHz
  if useInternalOsc then
    inputClkHz = limits.INTERNAL_OSCILLATOR_HZ.value
  else
    inputClkHz = T.getCleanExtClkHz()
  end

  -- establish PLL settings
  return {
    pllConfig = PLL.getClockConfiguration(limits, inputClkHz, sysClkHz),
    inputClkHz = inputClkHz,
    sysClkHz = sysClkHz,
    useInternalOsc = useInternalOsc,
    externalClockIsSingleEnded = externalClockIsSingleEnded,
  }
end


function T.getClockConfigurationCode(clockConfig)
  local pllConfig = clockConfig.pllConfig
  local clkSrcMacro
  if clockConfig.useInternalOsc then
    clkSrcMacro = 'SYSCTL_OSCSRC_OSC2'

  elseif clockConfig.externalClockIsSingleEnded then
    clkSrcMacro = 'SYSCTL_OSCSRC_XTAL_SE'

  else
    clkSrcMacro = 'SYSCTL_OSCSRC_XTAL'
  end

  local dccBase = 1

  local declarations = [[
#include "device.h"
#define SYSCLK_HZ %(sysClkHz)dL
#define CLKIN_HZ %(inputClkHz)dL

#define PLX_DELAY_US(x) SysCtl_delay(((((long double)(x)) / (1000000.0L / (long double)SYSCLK_HZ)) - 11.0L) / 4.0L)
  ]] % {
    sysClkHz = clockConfig.sysClkHz,
    inputClkHz = clockConfig.inputClkHz
  }
  if(T.getCleanSysClkHz() > 150e6) then
    waitStates = 3
  elseif (T.getCleanSysClkHz() > 100e6) then
    waitStates = 2
  else
    waitStates = 1
  end

  local code = [[
  {
    SysCtl_disableWatchdog();
    Device_errataWorkaroundNMIVectorFetch();
    {
        SysCtl_setClock(%(clkSrcMacro)s, SYSCTL_PLL_ENABLE, SYSCTL_REFDIV_%(refdiv)d, 
        SYSCTL_IMULT_%(imult)d, SYSCTL_ODIV_%(odiv)d, SYSCTL_SYSDIV_%(sysdiv)d, DCC%(dccBase)d_BASE);
        //SysCtl_setHSMClock(SYSCTL_HSMCLK_DIV_2);
        ASSERT(SysCtl_getClock(CLKIN_HZ) == SYSCLK_HZ);
    }
    Flash_setWaitstates(%(waitStates)i);
    
    // don't know why this is necessary, but otherwise we cannot access PWM registers!
    Device_enableAllPeripherals();

    Device_clearXbarSourceSelection();

    Interrupt_initModule();
    Interrupt_initVectorTable();

    // set epwm and timer clock to sysclk
    SysCtl_setCputimer2Clock(SYSCTL_TIMER2CLK_SOURCE_SYSCLK, SYSCTL_TIMER2CLK_DIV_1);
    SysCtl_setEPWMClock(SYSCTL_EPWMCLK_DIV_1);
    // stop all epwm timers so that they can be started synchronously
    SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);
    SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_GTBCLKSYNC);
  }
  SysCtl_delay(100);
  ]] % {
    clkSrcMacro = clkSrcMacro,
    imult = pllConfig.imult,
    refdiv = pllConfig.refdiv,
    odiv = pllConfig.odiv,
    sysdiv = pllConfig.sysdiv,
    dccBase = dccBase,
    sysClkHz = clockConfig.sysClkHz,
    waitStates = waitStates,
  }
  return {declarations = declarations, code = code}
end

function T.getClockConfigurationCodeCpu2(clockConfig)
  local declarations = ''
  local code = ''
  return {declarations = declarations, code = code}
end

function T.getCpu2BootCode()
  local declarations = ''
  local code = ''
  local includes = ''
  return {declarations = declarations, code = code, includes = includes}
end

function T.getCpu2BootCodeCpu2()
  local code = ''
  return {declarations = nil, code = code}
end

-- overwrites function in TI28.lua
function T.checkSpiClockIsAchievable(clk)
  local sysClk = T.getCleanSysClkHz()
  local maxClk = math.floor(sysClk / (0x03 + 1))
  local minClk = math.ceil(sysClk / (0x7F + 1))

  if clk > maxClk then
    U.error('SPI clock rate must not exceed %d Hz.' % {maxClk})
  elseif clk < minClk then
    U.error('SPI clock rate must not be below %d Hz.' % {minClk})
  end
end

T.ts_epwm = require('peripherals.epwm_type4').getFunctions(T)

return T
