--[[
  Copyright (c) 2021-2022 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.TI2837x_pin_map')
local PLL = require('utils.pll_config')

local TripInputsAllocated = {}
local GpioAllocated = {}

function T.getFamilyPrefix()
  return '2837x'
end

function T.getC2pConfig(c2pAdapter)
  return '28379,378,377,375S_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 = {'28379D'},
    boards = {'custom', 'launchpad', 'controlcard'},
    uniFlashConfigs = {
      launchpad = 'Launchpad_TMS320F28379D.ccxml',
      controlcard = 'ControlCard_TMS320F28379D.ccxml'
    },
    c2pConfigs = {
      launchpad = T.getC2pConfig('xds100v2'),
      controlcard = T.getC2pConfig('xds100v2'),
    },
    linkerFiles = {
      {'f2837xD_FLASH_lnk_cpu1.cmd', 'f2837xD_RAM_lnk_cpu1.cmd'},
      {'f2837xD_FLASH_lnk_cpu2.cmd'},
    },
  }
end

function T.configure(resources)
  resources:add('ADC A')
  resources:add('ADC B')
  resources:add('ADC C')
  resources:add('ADC D')
  resources:add('ADCA-SOC', 0, 15)
  resources:add('ADCB-SOC', 0, 15)
  resources:add('ADCC-SOC', 0, 15)
  resources:add('ADCD-SOC', 0, 15)
  resources:add('Base Task Load')
  resources:add('Base Task Load CPU2')
  resources:add('CAN A')
  resources:add('CAN B')
  resources:add('CAP', 1, 6)
  resources:add('CMPSS', 1, 8)
  resources:add('CPU1TIMER', 0, 1)
  resources:add('CPU2TIMER', 0, 1)
  resources:add('DAC A')
  resources:add('DAC B')
  resources:add('DAC C')
  resources:add('EXTSYNC', 1, 2)
  resources:add('GPIO', 0, 199)
  resources:add('HRPWM', 1, 8)
  resources:add('IPC_FLAG', 4, 30)  -- only flags without interrupt capability, flag 31 used for start-up sync
  resources:add('Powerstage Control')
  resources:add('Powerstage Control CPU2')
  resources:add('PWM', 1, 12)
  resources:add('QEP', 1, 3)
  resources:add('SCI A')
  resources:add('SCI B')
  resources:add('SCI C')
  resources:add('SCI D')
  resources:add('SDFM 1', 1, 4)
  resources:add('SDFM 2', 1, 4)
  resources:add('SPI A')
  resources:add('SPI B')
  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 = 4,
      vrefConfigInterfaceType = 4,
      defaultVref = 3.0,
      num_channels = 16,
      vref_min = 2.4,
      vref_max = 3.3,     -- allowable range on external vref
      num_units = 4,      -- 4 ADCs with independent references
    },
    caps = {},
    cbc_trip_inputs = {8, 9, 10, 11, 12},
    clas = {
      type = 1,
    },
    comps = {
      positive = {
        A14 = 4,
        B14 = 4,
        C14 = 4,
        A2 = 1,
        A4 = 2,
        B2 = 3,
        C2 = 6,
        C4 = 5,
        D0 = 7,
        D2 = 8,
      },
    },
    cpu_timers = {0},
    dacs = {
      minOutputVoltage = 0.0,
      maxOutputVoltage = 3.3, -- hardware upper voltage limit
      -- DAC A and B use vrefA
      -- DAC C uses vrefB
      vrefMap = {A = 'A', B = 'A', C = 'B'},
    },
    dcans = {
      type = 0,
    },
    epwms = {
      type = 4,
      max_event_period = 15,
      num_units = 12,
    },
    gpios = {
      openDrainSupported = true,
    },
    hrpwms = {
      num_units = 8,
    },
    qeps = {},
    scis = {
      num_units = 4,
    },
    sdfm = {
      num_units = 2,
    },
    spis = {
      type = 2, fifo_depth = 16,
    },
    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()
  -- find next available trip input
  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 comp = T.getTargetParameters().comps.positive[pin]
  if comp == nil then
    return nil
  end

  local compLowHigh = {
    unit = comp,
  }

  return {
    low = compLowHigh,
    high = compLowHigh,
  }
end

function T.checkGpioIsValidPwmSync(gpio)
  if gpio > 168 then
    U.error('Only @param:Gpio: from 0-168 can be selected as external synchronization source for this target.')
  end
end

function T.getPwmSyncInSel(params)
  if (params.epwm == 1) then
    if (params.type ~= 'external') or (params.source_unit ~= 1) then
      U.error('EPWM1 can only be synchronized from EXTSYNC 1')
    else
      return nil -- no configuration is necessary
    end
  end
  if (params.epwm ~= 4) and (params.epwm ~= 7) and (params.epwm ~= 10) then
    -- not configurable - source must be base of chain
    local syncBase = math.floor((params.epwm - 1) / 3) * 3 + 1
    if params.source_unit ~= syncBase then
      U.error('EPWM%d does not have a configurable synchronization source.' % {params.epwm})
    end
    return nil
  end
  local mux
  if params.type == 'external' then
    if params.source_unit == 1 then
      mux = 5
    elseif params.source_unit == 2 then
      mux = 6
    else
      U.error('Unsupported external sync unit (%d).' % {params.source_unit})
    end
  elseif params.type == 'epwm' then
    if params.source_unit >= params.epwm then
      U.error('Invalid synchronization order (EPWM%d cannot be synchronized from EPWM%d).' %
        {params.epwm, params.source_unit})
    end
    if params.source_unit == 1 then
      mux = 0
    elseif params.source_unit == 4 then
      mux = 1
    elseif params.source_unit == 7 then
      mux = 2
    elseif params.source_unit == 10 then
      mux = 3
    else
      U.error('Invalid synchronization order (EPWM%d cannot be synchronized from EPWM%d).' %
        {params.epwm, params.source_unit})
    end
  else
    U.error('Unsupported sync type (%s).' % {params.type})
  end
  return mux
end

function T.getLowSpeedClock()
  return T.getCleanSysClkHz() / 4
end

function T.getPwmClock()
  -- TBCLOCK - CLKDIV=/1, HSPCLKDIV=/1, EPWMCLK = SYSCLK (or SYSCLK/2 if SYSCLK > 100 MHz)
  local sysClkHz = T.getCleanSysClkHz()
  if sysClkHz <= 100000000 then
    return sysClkHz
  else
    return sysClkHz / 2
  end
end

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

function T.getDeadTimeClock()
  -- TBCLOCK - CLKDIV=/1, HSPCLKDIV=/1
  return T.getPwmClock()
end

function T.getAdcClock()
  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
  if sigmodeDifferential then
    tsMin = math.max(320e-9, 1 / T.getAdcClock())    -- per datasheet, differential
  else
    tsMin = math.max(75e-9, 1 / T.getAdcClock())     -- per datasheet, single ended
  end

  if (not U.isScalar(ts)) or (ts < tsMin) then
    ts = tsMin
  end
  local sysClkHz = T.getCleanSysClkHz()

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

function T.getCpuTimerSetupCode(unit, params)
  local code = [[
    CpuTimer%(unit)dRegs.TCR.bit.TSS = 1; // stop timer
    CpuTimer%(unit)dRegs.TPRH.all = 0;
    CpuTimer%(unit)dRegs.PRD.all = %(period)d - 1;
    CpuTimer%(unit)dRegs.TCR.bit.TRB = 1; // reload period
    CpuTimer%(unit)dRegs.TCR.bit.TIE = 1; // enable trigger to SOC/interrupt
  ]] % {
    unit = unit,
    period = params.period,
  }

  if params.isr then
    -- note, this is really hard-coded for CPUTimer0
    local isrConfigCode = [[
      PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
      EALLOW;
      PieVectTable.TIMER%(unit)d_INT = &%(isr)s;
      EDIS;
      PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
      PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // acknowledge interrupt to PIE
	  ]] % {
      unit = unit,
      isr = params.isr,
    }

    code = code..isrConfigCode
  end

  return code
end

function T.getSyncSocRegsCode(unit, params)
  local code = [[
    EALLOW;
    SyncSocRegs.SYNCSELECT.bit.EPWM%(unit)dSYNCIN = %(synci_sel)d;
    EDIS;
  ]] % {
    unit = unit,
    synci_sel = params.synci_sel,
  }
  return code
end

function T.getEpwmTimersSyncCode()
  return [[
    CpuSysRegs.PCLKCR0.bit.GTBCLKSYNC = 1; // start all the timers synced (global)
  ]]
end

function T.getAdcSetupCode(unitAsChar, params)
  local code = [[
    EALLOW;
    Adc%(adc)sRegs.ADCINTSEL1N2.bit.INT1CONT = 0; // disable ADCINT1 Continuous mode
    Adc%(adc)sRegs.ADCINTSEL1N2.bit.INT1SEL = %(int1sel)d; // setup EOC%(int1sel)d to trigger ADCINT1
    Adc%(adc)sRegs.ADCINTSEL1N2.bit.INT1E = 1; // enable ADCINT1
    Adc%(adc)sRegs.ADCCTL1.bit.INTPULSEPOS = 1; // ADCINT1 trips after AdcResults latch
    EDIS;
  ]] % {
    adc = string.lower(unitAsChar),
    int1sel = params.INT1SEL,
  }
  local zeroIndexedUnit = U.charToZeroIndexedInt(unitAsChar)
  if params.isr then
    local pieVect, pieBitMask
    if zeroIndexedUnit <= 2 then
      pieVect = '(PINT *)((uint32_t)(&PieVectTable.ADCA1_INT) + ((uint32_t)%d)*sizeof(PINT *))' % {zeroIndexedUnit}
      pieBitMask = (1 << zeroIndexedUnit)
    else
      -- ADC D is special case
      pieVect = '(PINT *)&PieVectTable.ADCD1_INT'
      pieBitMask = (1 << 5)
    end

    local isrConfigCode = [[
      EALLOW;
      *%(pieVect)s = &%(isr)s;
      PieCtrlRegs.PIEIER1.all |= %(pieBitMask)s;
      PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
      EDIS;
    ]] % {
      pieVect = pieVect,
      isr = params.isr,
      pieBitMask = pieBitMask,
    }

    code = code..isrConfigCode
  end

  return code
end

function T.getAdcInterruptAcknCode(unitAsChar, params)
  local code = [[
    Adc%sRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; // clear ADCINT1 flag reinitialize for next SOC
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // acknowledge interrupt to PIE (All ADCS in group 1)
    IER |= M_INT1;
  ]] % {string.lower(unitAsChar)}

  return code
end

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

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

  limits:set('OSCCLK', {min = 2e6, max = 25e6})
  limits:set('PLLRAWCLK', {min = 120e6, max = 400e6})
  limits:set('SYSCLK', {min = 2e6, max = 200e6})
  limits:set('INTERNAL_OSCILLATOR_SYSCLK', {max = 194e6}) -- limit to flash with internal clock

  limits:set('IMULT', {max = 127})
  limits:set('FMULT', {divisor = 4})
  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 limits = setAndGetPllLimits(useInternalOsc)

  local sysClkHz = T.getCleanSysClkHz()
  
  local inputClkHz
  local sysClkWcHi
  if useInternalOsc then 
    -- using internal oscillator with 3% error
    inputClkHz = limits.INTERNAL_OSCILLATOR_HZ.value
    sysClkWcHi = 1000000 * math.floor((sysClkHz * 1.03 + 500000) / 1000000)

  else
    inputClkHz = T.getCleanExtClkHz()
    sysClkWcHi = sysClkHz
  end

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

function T.getClockConfigurationCode(clockConfig)
  local pllConfig = clockConfig.pllConfig
  local clkSrcMacro

  if clockConfig.useInternalOsc then
    clkSrcMacro = 'SYSCTL_OSCSRC_OSC2'
  else
    clkSrcMacro = 'SYSCTL_OSCSRC_XTAL'
  end

  local fmultMacro
  local fmult = pllConfig.fmult
  if(fmult == 0) then
    fmultMacro = 'SYSCTL_FMULT_0'
  elseif (fmult == 1) then
    fmultMacro = 'SYSCTL_FMULT_1_4'
  elseif (fmult == 2) then
    fmultMacro = 'SYSCTL_FMULT_1_2'
  elseif (fmult == 3) then
    fmultMacro = 'SYSCTL_FMULT_3_4'
  end

  local cpuRate = 100.0 / clockConfig.sysClkHz * 10000000 -- in nanoSeconds

  local declarations = [[
  	void DevInit(uint32_t aDeviceClkConf);
	  void InitFlashHz(Uint32 clkHz);
	  void DSP28x_usDelay(long LoopCount);

// Clock configuration
#define PLX_DEVICE_SETCLOCK_CFG  (%(clkSrcMacro)s | SYSCTL_IMULT(%(imult)d) |  \
                                  %(fmultMacro)s | SYSCTL_SYSDIV(%(sysdiv)d) |   \
                                  SYSCTL_PLL_ENABLE)

#define SYSCLK_HZ %(sysClkHz)dL
#define SYSCLK_WC_HI_HZ %(sysClkWcHi)dL
#define LSPCLK_HZ (SYSCLK_HZ / 4l)
#define PLX_DELAY_US(A)  DSP28x_usDelay( \
        ((((long double) A * 1000.0L) / %(cpuRate)fL) - 9.0L) / 5.0L)
  ]] % {
    clkSrcMacro = clkSrcMacro,
    imult = pllConfig.imult,
    fmultMacro = fmultMacro,
    sysdiv = pllConfig.sysdiv,
    sysClkHz = clockConfig.sysClkHz,
    sysClkWcHi = clockConfig.sysClkWcHi,
    cpuRate = cpuRate,
  }

  local code = [[
  	{
      uint32_t sysPllConfig = PLX_DEVICE_SETCLOCK_CFG;
      DevInit(sysPllConfig);
      SysCtl_setLowSpeedClock(SYSCTL_LSPCLK_PRESCALE_4);

      PLX_ASSERT(SysCtl_getClock(%(clkin)d) == SYSCLK_HZ);
      PLX_ASSERT(SysCtl_getLowSpeedClock(%(clkin)d) == LSPCLK_HZ);
    }
    InitFlashHz(SYSCLK_WC_HI_HZ);
    // set cpu timers to same clock as ePWM
    CpuTimer0Regs.TPR.all = %(cpuTimerDiv)d;
    CpuTimer1Regs.TPR.all = %(cpuTimerDiv)d;
    CpuTimer2Regs.TPR.all = %(cpuTimerDiv)d;
    EALLOW;
    CpuSysRegs.PCLKCR0.bit.CPUTIMER0 = 1;
    CpuSysRegs.PCLKCR0.bit.CPUTIMER1 = 1;
    CpuSysRegs.PCLKCR0.bit.CPUTIMER2 = 1;
    CpuSysRegs.PCLKCR0.bit.TBCLKSYNC = 0; // stop all the TB clocks on the local cpu
    CpuSysRegs.PCLKCR0.bit.GTBCLKSYNC = 0; // stop all the TB clocks
    ClkCfgRegs.PERCLKDIVSEL.bit.EPWMCLKDIV = %(epwmClkDiv)d;
    EDIS;
  ]] % {
    clkin = clockConfig.inputClkHz,
    cpuTimerDiv = (clockConfig.sysClkHz <= 100e6) and 0 or 1,
    epwmClkDiv = (clockConfig.sysClkHz <= 100e6) and 0 or 1,
  }

  return {declarations = declarations, code = code}
end

-- FIX ME: Revisit CPU2 clock configuration after the dual core integration work.
local CONST = {
  -- System Clock Limits per datasheet
  PLL_SYS_CLK_MIN = 2e6,
  PLL_SYS_CLK_MAX = 200e6,
}

function T.getClockConfigurationCodeCpu2(clockConfig)
  local cpuRate = 100.0 / clockConfig.sysClkHz * 10000000

  local declarations = [[
    void DevInit();
    void InitFlashHz(Uint32 clkHz);
    void DSP28x_usDelay(long LoopCount);

    // Clock configurations
#define SYSCLK_HZ %(sysClkHz)dL
#define SYSCLK_WC_HI_HZ %(sysClkWcHi)dL
#define LSPCLK_HZ (%(sysClkHz)d / 4l)
#define PLX_DELAY_US(A)  DSP28x_usDelay( \
        ((((long double) A * \
          1000.0L) / \
          %(cpuRate)fL) - 9.0L) / 5.0L)
  ]] % {
    sysClkHz = clockConfig.sysClkHz,
    sysClkWcHi = clockConfig.sysClkWcHi,
    cpuRate = cpuRate,
  }

  local code = [[
    DevInit();
    InitFlashHz(SYSCLK_WC_HI_HZ);
    // set cpu timers to same clock as ePWM
    CpuTimer0Regs.TPR.all = %(cpuTimerDiv)d;
    CpuTimer1Regs.TPR.all = %(cpuTimerDiv)d;
    CpuTimer2Regs.TPR.all = %(cpuTimerDiv)d;
    EALLOW;
    CpuSysRegs.PCLKCR0.bit.CPUTIMER0 = 1;
    CpuSysRegs.PCLKCR0.bit.CPUTIMER1 = 1;
    CpuSysRegs.PCLKCR0.bit.CPUTIMER2 = 1;
    CpuSysRegs.PCLKCR0.bit.TBCLKSYNC = 0; // stop all the TB clocks
    ClkCfgRegs.PERCLKDIVSEL.bit.EPWMCLKDIV = %(epwmClkDiv)d;
    EDIS;
  ]] % {
    cpuTimerDiv = (clockConfig.sysClkHz <= 100e6) and 0 or 1,
    epwmClkDiv = (clockConfig.sysClkHz <= 100e6) and 0 or 1,
  }

  return {declarations = declarations, code = code}
end

function T.getCpu2BootCode()
  local declarations = [[
#define C2_BOOTROM_BOOTSTS_SYSTEM_READY 0x00000002
#define C1C2_BROM_BOOTMODE_BOOT_FROM_FLASH 0x0000000B
#define BROM_IPC_EXECUTE_BOOTMODE_CMD 0x00000013

    static void BootCPU2ToFlashSector0()
    {
      volatile int32_t bootStatus;
      do
      {
        bootStatus = IpcRegs.IPCBOOTSTS & 0x0000000F;
      }
      while ((bootStatus != C2_BOOTROM_BOOTSTS_SYSTEM_READY));


      // loop until CPU02 control system IPC flags 0 and 31 are available
      while (IPC_isFlagBusyRtoL(IPC_CPU1_L_CPU2_R, IPC_FLAG0) |
            IPC_isFlagBusyRtoL(IPC_CPU1_L_CPU2_R, IPC_FLAG31))
      {
        continue;
      }

      //  send boot command to allow the CPU2 application to begin execution
      IpcRegs.IPCBOOTMODE = C1C2_BROM_BOOTMODE_BOOT_FROM_FLASH;
      IpcRegs.IPCSENDCOM  = BROM_IPC_EXECUTE_BOOTMODE_CMD;
      IpcRegs.IPCSET.all = 0x80000001;
    }
  ]]

  local code = [[
    // reset CPU2
    EALLOW;
    DevCfgRegs.CPU2RESCTL.all = 0xa5a50001L;
    DevCfgRegs.CPU2RESCTL.all = 0xa5a50000L;
    EDIS;

    BootCPU2ToFlashSector0();

    // wait for CPU2 to signal that has completed its initialization
    while(!(HWREG(IPC_BASE + IPC_O_STS) & IPC_FLAG31)){
        continue;
    }

    // acknowledge flag which will allow CPU2 to proceed
    HWREG(IPC_BASE + IPC_O_ACK) = IPC_FLAG31;
  ]]

  return {declarations = declarations, code = code}
end

function T.getCpu2BootCodeCpu2()
  local code = [[
    // signal to CPU1 that we are configured
    HWREG(IPC_BASE + IPC_O_SET) = IPC_FLAG31;
    // wait for acknowledgment and permission to go
    while(HWREG(IPC_BASE + IPC_O_FLG) & IPC_FLAG31){
        continue;
    }
  ]]

  return {declarations = nil, code = code}
end

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

return T
