--[[
  Copyright (c) 2023 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.
--]]--

-- this file for code that is common to all TIC2000 tsps. 
local U = require('common.utils')
local CCXML = require('utils.ccxml')
local T = {}

-- Override function if chip has analog pins needing additional configuration
function T.pinHasAnalogMode(pin)
  return false
end

-- Return true for pins that require additional config on some targets
function T.pinRequiresGpioConfig(pin)
  return false
end

function T.getChipName()
  return T.getOptions().ChipCombo[Target.Variables.Chip]
end

function T.getBoardNameFromComboIndex(index)
  return T.getOptions().boards[index]
end

function T.getUniflashConfig(board)
  return T.getOptions().uniFlashConfigs[board]
end

function T.getC2pConfigFromCcxml(ccxmlFile)
  local c2pAdapter = CCXML.getC2pAdapterType(ccxmlFile)
  if not c2pAdapter then
    U.error('Unable to determine GDB server settings for custom board.')
  end
  return T.getC2pConfig(c2pAdapter)
end

function T.getLinkerFileName(cpu, index)
  return T.getOptions().linkerFiles[cpu][index]
end

function T.getCleanSysClkHz()
  return U.enforceCO(U.megaHzIsIntegerHertz, 'sysClkMHz') * 1e6
end

function T.getSysClkWidgetLink()
  return '@param:sysClkMHz:2:'
end

function T.getCleanExtClkHz()
  return U.enforceCO(U.megaHzIsIntegerHertz, 'extClkMHz') * 1e6
end

function T.getMaxSciBaudRate()
  -- assuming 8N1. 1.5 characters per poll
  local maxRate = 1 / Target.Variables.SAMPLE_TIME * 15
  return math.min(115200, maxRate)
end

function T.getSupportedSciBaudRates()
  return {
    115200, 57600, 38400, 19200, 14400, 9600, 4800
  }
end

function T.getPwmFrequencySettings(fsw, cbx_carrierType)
  assert(U.isReal(fsw))
  assert(U.isComboBox(cbx_carrierType))

  local prd, periodInSysTicks, achievableF, desiredPeriodAsFloat
  local prdHR, achievableFHR, desiredPeriodAsFloatHR

  if cbx_carrierType.equals('triangle') then
    desiredPeriodAsFloat = T.getPwmClock() / fsw / 2
    prd = math.floor(desiredPeriodAsFloat + 0.5)
    periodInSysTicks =
       prd * 2 * T.getCleanSysClkHz() / T.getTimerClock()
    achievableF = T.getPwmClock() / 2 / prd
    -- High resolution calculations
    desiredPeriodAsFloatHR = T.getPwmClock() * 256 / fsw / 2
    prdHR = math.floor(desiredPeriodAsFloatHR + 0.5)
    achievableFHR = T.getPwmClock() * 256 / 2 / prdHR
  else
    desiredPeriodAsFloat = T.getPwmClock() / fsw - 1
    prd = math.floor(desiredPeriodAsFloat + 0.5)
    periodInSysTicks = 
       (prd + 1) * T.getCleanSysClkHz() / T.getTimerClock()
    achievableF = T.getPwmClock() / (prd + 1)
    -- High resolution calculations
    desiredPeriodAsFloatHR = T.getPwmClock() * 256 / fsw - 1
    prdHR = math.floor(desiredPeriodAsFloatHR + 0.5)
    achievableFHR = T.getPwmClock() * 256 / (prdHR + 1)
  end

  return U.lockTable({
    freq = achievableF,
    freqHR = achievableFHR,
    period = prd,
    periodInSysTicks = periodInSysTicks,
    desiredPeriodAsFloat = desiredPeriodAsFloat,
  })
end

function T.checkSpiClockIsAchievable(clk)
  local lspClkHz = T.getLowSpeedClock()
  local maxClk = math.floor(lspClkHz / (0x03 + 1))
  local minClk = math.ceil(lspClkHz / (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

-- This target specific function should be overwritten by all targets
-- Needs access to which target we are, so pass in the T structure?
function TARGET_SPECIFIC_FUNCTION_MUST_BE_IMPLEMENTED()
  return error('All targets must implement a target specific version of this function.')
end

T.ts_epwm = {
  ts_getEpwmSetupCode = TARGET_SPECIFIC_FUNCTION_MUST_BE_IMPLEMENTED,
  ts_getCmpssLowComparatorEpwmTripSetupCode = TARGET_SPECIFIC_FUNCTION_MUST_BE_IMPLEMENTED,
  ts_getCmpssHighComparatorEpwmTripSetupCode = TARGET_SPECIFIC_FUNCTION_MUST_BE_IMPLEMENTED,
}

-- This is the common interface
function T.getEpwmSetupCode(unit, params)
  -- print(dump(params))
  -- U.enforceParamContract(
  --   params,
  --   {
  --     opt_soca_sel = U.isPositiveIntScalar,
  --     soca_prd = U.isPositiveIntScalar,
  --     opt_int_sel = U.isPositiveIntScalar,
  --     int_prd = U.isPositiveIntScalar,
  --     opt_sync = {
  --       synco_sel = U.isPositiveIntScalar,
  --       phsen = U.isBooleanInteger,
  --       -- Mux values?
  --       opt_synci_sel = U.isNonNegativeIntScalar,
  --       opt_synco_sel = U.isNonNegativeIntScalar,
  --       -- Trip input number
  --       opt_tripi_sel = U.isPositiveIntScalar,

  --     },
  --   })
  return T.ts_epwm.ts_getEpwmSetupCode(unit, params, T)
end

function T.getCmpssLowComparatorEpwmTripSetupCode(unit, params)
  return T.ts_epwm.ts_getCmpssLowComparatorEpwmTripSetupCode(unit, params, T)
end

function T.getCmpssHighComparatorEpwmTripSetupCode(unit, params)
  return T.ts_epwm.ts_getCmpssHighComparatorEpwmTripSetupCode(unit, params, T)
end

local function getEpwmPieGroupAndOffset(unit)
 -- For PWMs > 16 (for example on the F28P65x) a different PIEIER reg location must be used (9.7 or 9.8 instead of 3.x)
 if(unit <= 16) then
    return 3, 0
  else
    return 9, 10
  end
end

function T.getEpwmIsrConfigCode(unit, params)
  assert(U.isString(params.isr), 'isr arg is the function name of the isr')
  if T.targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
    local pieVect
    -- PIE vector table memory addresses for PWMs are in groups of 8
    if unit <= 8 then
      pieVect = '(PINT *)((uint32_t)(&PieVectTable.EPWM1_INT) + ((uint32_t)%d-1)*sizeof(PINT *))' % {unit}
    elseif unit <= 16 then
      pieVect = '(PINT *)((uint32_t)(&PieVectTable.EPWM9_INT) + ((uint32_t)%d-9)*sizeof(PINT *))' % {unit}
    else
      pieVect = '(PINT *)((uint32_t)(&PieVectTable.EPWM17_INT) + ((uint32_t)%d-17)*sizeof(PINT *))' % {unit}
    end

    local pieReg, offset = getEpwmPieGroupAndOffset(unit)

    return [[
      {
        EALLOW;
        PieCtrlRegs.PIEIER%(reg)i.all |= (1 << (%(unit)d-1));
        *%(pieVect)s = &%(isr)s;
        PieCtrlRegs.PIEACK.all = PIEACK_GROUP%(reg)i; // Acknowledge interrupt to PIE
        EDIS;
      }
    ]] % {
      reg = pieReg,
      unit = unit - offset,
      pieVect = pieVect,
      isr = params.isr,
    }
  elseif T.targetMatches({'29H85x'}) then
    return [[
      {
        Interrupt_register(INT_EPWM%(unit)i, &%(isr)s);
      }
    ]] % {
      unit = unit,
      isr = params.isr,
    }
  else
    U.throwUnhandledTargetError()
  end
end

function T.getEpwmIsrEnableCode(unit)
  if T.targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
    local pie_group = getEpwmPieGroupAndOffset(unit)
    return 'IER |= M_INT%(pie_group)i;' % {pie_group = pie_group}
  elseif T.targetMatches({'29H85x'}) then
    return [[
      Interrupt_enable(INT_EPWM%(unit)i);
      Interrupt_setPriority(INT_EPWM%(unit)i, 255);
      Interrupt_setContextID(INT_EPWM%(unit)i, INTERRUPT_CONTEXTID_0);
    ]] % {
      unit = unit,
    }
  else
    U.throwUnhandledTargetError()
  end
end

function T.getEpwmIsrCode(unit)
  if T.targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
    local pie_group = getEpwmPieGroupAndOffset(unit)
    return [[
      interrupt void %(isr)s_baseTaskInterrupt(void)
      {
        EPwm%(unit)iRegs.ETCLR.bit.INT = 1;  // clear INT flag for this timer
        PieCtrlRegs.PIEACK.all = PIEACK_GROUP%(pie_group)i; // acknowledge interrupt to PIE
        IER |= M_INT%(pie_group)i;
        DISPR_dispatch();
      }
    ]] % {
      isr = Target.Variables.BASE_NAME,
      unit = unit,
      pie_group = pie_group,
    }
  elseif T.targetMatches({'29H85x'}) then
    return [[
      __attribute__((interrupt("INT"))) void %(isr)s_baseTaskInterrupt(void);
      void %(isr)s_baseTaskInterrupt(void)
      {
        EPWM_clearEventTriggerInterruptFlag(EPWM%(unit)i_BASE);
        DISPR_dispatch();
      }
    ]] % {
      isr = Target.Variables.BASE_NAME,
      unit = unit,
    }
  else
    U.throwUnhandledTargetError()
  end
end

function T.getEpwmCheckOverrunCode(unit)
  if T.targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x', '28004x', '28P55x', '28P65x'}) then
    return [[
      #pragma CODE_SECTION(%(base_name)s_hasBaseTaskOverrunOccurred, "ramfuncs")
      static inline bool %(base_name)s_hasBaseTaskOverrunOccurred() {
        return EPwm%(unit)iRegs.ETFLG.bit.INT;
      }
    ]] % {
      base_name = Target.Variables.BASE_NAME,
      unit = unit,
    }
  elseif T.targetMatches({'29H85x'}) then
    return [[
      __attribute__((section(".ramfuncs"))) static inline bool %(base_name)s_hasBaseTaskOverrunOccurred() {
        return EPWM_getEventTriggerInterruptStatus(EPWM%(unit)i_BASE);
      }
    ]] % {
      base_name = Target.Variables.BASE_NAME,
      unit = unit,
    }
  else
    U.throwUnhandledTargetError()
  end
end

--[[
    Returns a list of possible values for 'targetMatches' checks for this TSP.
    The order displayed here is the order that should be used for targetMatches()
    checks as well, a convention just to keep things organized. The order is
    approximately alpha numberic, oldest -> newest.
--]]
function T.getAllPossibleFamilyPrefixes()
  return {
    '2806x', '2833x',
    '2837x', '2838x',
    '28003x', '28004x',
    '280013x',
    '28P55x', '28P65x',
    '29H85x',
  }
end
--[[
    Check if the current target matches one in the argument,
    where the arg 'targets' is optionally a string for one target
    or a list of strings for one or more targets.
--]]
function T.targetMatches(targets, should_be_nil)
  if should_be_nil ~= nil then
    error('Multiple targets must be listed in an array for targetMatches.')
  end
  return U.targetMatches(targets, T)
end

function T.is29Target()
  return string.find(T.getFamilyPrefix(), '^29')
end

function T.is28Target()
  return string.find(T.getFamilyPrefix(), '^28')
end

-- cpu input is zero indexed
function T.getTiCpuNumber(cpu)
  -- default is CPU1, CPU2, etc
  return cpu + 1
end

-- cpu input is zero indexed
function T.isSecondaryCpu(cpu)
  -- default primary is CPU1 
  return cpu and (cpu ~= 1)
end

function T.getGpioDirConfigCpu(_gpioCoreNum)
  -- default: CPU1 (self.cpu == 0) configures direction for all GPIO
  return 0
end

local targetParametersCache
function T.getTargetParameters()
  if not targetParametersCache then
    targetParametersCache = T.ts_getTargetParameters()
  end
  return targetParametersCache
end

return T
