--[[
  Copyright (c) 2021 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 GpioAllocated = {}

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

function T.getC2pConfig(c2pAdapter)
  return '28069,67,66_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 = {'28069'},
    boards = {'custom', 'launchpad', 'controlcard'},
    uniFlashConfigs = {
      launchpad = 'Launchpad_TMS320F28069.ccxml',
      controlcard = 'ControlCard_TMS320F28069.ccxml',
    },
    c2pConfigs = {
      launchpad = T.getC2pConfig('xds100v2'),
      controlcard = T.getC2pConfig('xds100v2'),
    },
    linkerFiles = {
      {'F28069.cmd', 'F28069-RAM.cmd'},
    },
  }
end

function T.configure(resources)
  resources:add('ADC A')
  resources:add('ADCA-SOC', 0, 15)
  resources:add('Base Task Load')
  resources:add('CAN A')
  resources:add('CAN B')
  resources:add('CAP', 1, 3)
  resources:add('CPU1TIMER', 0, 1)
  resources:add('EXTSYNC', 1, 1)
  resources:add('GPIO', 0, 58)
  resources:add('Powerstage Control')
  resources:add('PWM', 1, 8)
  resources:add('QEP', 1, 2)
  resources:add('SCI A')
  resources:add('SCI B')
  resources:add('SPI A')
  resources:add('SPI B')
end

function T.getTargetParameters()
  local params = {
    adcs = {
      peripheralType = 3, 
      vrefConfigInterfaceType = 3,
      defaultVref = 3.3,
      num_channels = 16,
      vrefMap = {A = 'A'},
    },
    cans = {pin_sets = {A_GPIO30_GPIO31 = 0}},
    caps = {
      pins = {
        GPIO5 = 1,
        GPIO11 = 1,
        GPIO19 = 1,
        GPIO24 = 1,
        GPIO7 = 2,
        GPIO15 = 2,
        GPIO25 = 2,
        GPIO9 = 3,
        GPIO26 = 3,
      },
    },
    clas = {
      type = 0,
    },
    cpu_timers = {0},
    epwms = {
      type = 1,
      max_event_period = 3,
      gpio = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {40, 41}, {42, 43}},
    },
    gpios = {},
    qeps = {
      pin_set_groups = {
        _1 = {
          a = {20, 50},
          b = {21, 51},
          i = {23, 53},
        },
        _2 = {
          a = {24, 54},
          b = {25, 55},
          i = {26, 30, 56},
        },
      },
    },
    scis = {
      pin_sets = {
        GPIO28_GPIO29 = 0,
        GPIO15_GPIO58 = 1,
      }
    },
    spis = {
      fifo_depth = 4,
      pin_sets = {
        A_GPIO16_GPIO17_GPIO18_GPIO19 = 10,
        A_GPIO16_GPIO17_GPIO18 = 10,
        B_GPIO24_GPIO25_GPIO26_GPIO27 = 20,
        B_GPIO24_GPIO25_GPIO26 = 20,
        B_GPIO24_GPIO25_GPIO14_GPIO27 = 21,
        B_GPIO24_GPIO25_GPIO14 = 21,
      },
    },
  }
  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.getComparatorsForPin(pin)
  U.error('This target does not have the CMPSS peripheral.')
end

function T.checkGpioIsValidPwmSync(gpio)
  if gpio ~= 6 and gpio ~= 32 then
    U.error('Only @param:Gpio: 6 and 32 can be selected as external synchronization source for this target.')
  end
end

function T.getPwmSyncInSel(params)
  --[[
    The synchronization input is not selectable for this device.
    See also EpwmVar:configurePwmSynchronizationChain()
  ]]--
end

function T.getIntOscClock()
  return 10000000 -- INTOSC2
end

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

function T.getPwmClock()
  return T.getCleanSysClkHz()
end

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

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

function T.getAdcClock()
  return T.getCleanSysClkHz() / 2
end

function T.getCanClkAndMaxBrp()
  return T.getCleanSysClkHz() / 2, 0x40
end

function T.calcACQPS(ts, sigmodeDifferential)
  -- allowed values are 7-64 with a bunch of exceptions noted below for the 2806x
  -- also note per the errata, 8 is the minimim if overlaping sampling is allowed (which we don't do)
  local minACQPS = 6
  local maxACQPS = 63

  if (not U.isScalar(ts)) then -- test for NAN
    return minACQPS
  end

  -- desired setting of the ACQPS register is one less than the number of cycles desired
  local desiredACQPS = U.clampNumber(math.ceil(ts * T.getAdcClock()) - 1, {minACQPS, maxACQPS})
  -- Check for other invalid selections: (and silently round up!)
  -- 10h, 11h, 12h, 13h, 14h, 1Dh, 1Eh, 1Fh, 20h, 21h, 2Ah, 2Bh, 2Ch, 2Dh, 2Eh, 37h, 38h, 39h, 3Ah, 3Bh
  if     (0x10 <= desiredACQPS) and (desiredACQPS <= 0x14) then
    return 0x15
  elseif (0x1D <= desiredACQPS) and (desiredACQPS <= 0x21) then
    return 0x22
  elseif (0x2A <= desiredACQPS) and (desiredACQPS <= 0x2E) then
    return 0x2F
  elseif (0x37 <= desiredACQPS) and (desiredACQPS <= 0x3B) then
    return 0x3C
  else
    return desiredACQPS
  end
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.TINT%(unit)d = &%(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

T.ts_epwm = {}

function T.ts_epwm.ts_getEpwmSetupCode(unit, params)
  local code = [[]]

  if params.soca_sel then
    local soccode = [[
      EPwm%(unit)dRegs.ETSEL.bit.SOCASEL = %(socasel)d;
      EPwm%(unit)dRegs.ETPS.bit.SOCAPRD = %(socaprd)d;
      EPwm%(unit)dRegs.ETSEL.bit.SOCAEN = 1;
    ]] % {
      unit = unit,
      socasel = params.soca_sel,
      socaprd = params.soca_prd or 1,
    }

    code = code..soccode
  end

  if params.int_sel then
    local intConfigCode = [[
      EPwm%(unit)dRegs.ETSEL.bit.INTSEL = %(intsel)d;
      EPwm%(unit)dRegs.ETPS.bit.INTPRD = %(intprd)d;
      EPwm%(unit)dRegs.ETSEL.bit.INTEN = 1;  // enable INT
    ]] % {
      unit = unit,
      intsel = params.int_sel,
      intprd = params.int_prd or 1,
    }

    code = code..intConfigCode
  end

  if params.sync then
    code = code..[[
      EPwm%(unit)dRegs.TBCTL.bit.PHSEN = %(phsen)d;
    ]] % {
      unit = unit,
      phsen = params.sync.phsen,
    }
    if params.sync.synco_sel then
      code = code..[[
        EPwm%(unit)dRegs.TBCTL.bit.SYNCOSEL = %(synco_sel)d;
      ]] % {
        unit = unit,
        synco_sel = params.sync.synco_sel,
      }
    end
  end

  return code
end

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

function T.getAdcSetupCode(unitAsChar, params)
  local code = [[
    EALLOW;
    AdcRegs.INTSEL1N2.bit.INT1CONT = 0; // disable ADCINT1 Continuous mode
    AdcRegs.INTSEL1N2.bit.INT1SEL = %(int1sel)d; // setup EOC%(int1sel)d to trigger ADCINT1
    AdcRegs.INTSEL1N2.bit.INT1E = 1; // enable ADCINT1
    AdcRegs.ADCCTL1.bit.INTPULSEPOS = 1; // ADCINT1 trips after AdcResults latch
    EDIS;
  ]] % {int1sel = params.INT1SEL}

  if params.isr then
    local isrConfigCode = [[
      EALLOW;
      PieVectTable.ADCINT1 = %(isr)s;
      PieCtrlRegs.PIEIER1.bit.INTx1 = 1;
      PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
      EDIS;
    ]] % {isr = params.isr}

    code = code..isrConfigCode
  end

  return code
end

function T.getAdcInterruptAcknCode(unitAsChar, params)
  local code = [[
    AdcRegs.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;
  ]]
  return code
end

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

  local sysClkHz = T.getCleanSysClkHz()

  if sysClkHz > 90000000 then
    U.error('Excessive system clock setting.')
  elseif sysClkHz < 2000000 then
    U.error('Insufficient system clock setting.')
  end

  local inputClkHz
  if Target.Variables.useIntOsc ~= 1 then
    inputClkHz = T.getCleanExtClkHz()
  else
    inputClkHz = T.getIntOscClock()
  end

  local pllDivSel = 2 -- hard-coded
  local pllDiv = math.min(math.floor(pllDivSel * sysClkHz / inputClkHz), 18)
  -- important: VCOCLK must be at least 50 MHz
  if (pllDiv * inputClkHz < 50000000) then
    U.error('PLL OSC frequency (%f MHz) outside of valid range (>= 50 MHz).' % {pllDiv * inputClkHz / 1000000})
  end

  if not (sysClkHz == pllDiv * inputClkHz / pllDivSel) then
    U.error('Unable to achieve the desired system clock frequency (with input clock = %f Hz).' % {inputClkHz})
  end
  return {
    pllConfig = {
      pllDiv = pllDiv,
      pllDivSel = pllDivSel,
    },
    inputClkHz = inputClkHz,
    sysClkHz = sysClkHz,
    useInternalOsc = useInternalOsc,
  }
end

function T.getClockConfigurationCode(clockConfig)
  local pllConfig = clockConfig.pllConfig
  
  local clksrc
  if not clockConfig.useInternalOsc then
    clksrc = 1
  else
    clksrc = 0
  end

  local cpuRate = 100.0 / clockConfig.sysClkHz * 10000000;

  local declarations = [[
  	void DeviceInit(Uint16 clock_source, Uint16 pllDiv);
    void InitFlash();
    void DSP28x_usDelay(long LoopCount);

// Clock configuration
#define SYSCLK_HZ %(sysClkHz)dL
#define LSPCLK_HZ (SYSCLK_HZ / 4l)
#define PLL_DIV %(pllDiv)d
#define PLL_DIVSEL %(pllDivSel)d
#define PLL_SRC %(clksrc)d
#define PLX_DELAY_US(A)  DSP28x_usDelay( \
        ((((long double) A * \
          1000.0L) / \
          %(cpuRate)fL) - 9.0L) / 5.0L)
  ]] % {
    sysClkHz = clockConfig.sysClkHz,
    pllDiv = pllConfig.pllDiv,
    pllDivSel = pllConfig.pllDivSel,
    clksrc = clksrc,
    cpuRate = cpuRate,
  }

  if T.getTimerClock() ~= clockConfig.sysClkHz then
    U.error('T.getTimerClockPeriodInSysTicks() configuration error.')
  end

  local code = [[
    DeviceInit(PLL_SRC, PLL_DIV);
    InitFlash();
    // set cpu timers to same clock as ePWM
    CpuTimer0Regs.TPR.all = 0;
    CpuTimer1Regs.TPR.all = 0;
    CpuTimer2Regs.TPR.all = 0;
    EALLOW;
    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; // stop all the TB clocks
    EDIS;
  ]]

  return {declarations = declarations, code = code}
end

return T
