--[[
  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 CCXML = require('utils.ccxml')

local GpioAllocated = {}

function T.getFamilyPrefix()
  return '2833x'
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 = {'28335'},
    boards = {'custom'},
    uniFlashConfigs = {},
    c2pConfigs = {},
    linkerFiles = {
      {'F28335.cmd', 'F28335-RAM.cmd'},
    },
  }
end

-- overrides default implementation in TI28.lua
function T.getC2pConfigFromCcxml(ccxmlFile)
  -- only select clock frequencies are supported
  if Target.Variables.extClkMHz == 20 or Target.Variables.extClkMHz == 24
     or Target.Variables.extClkMHz == 25 or Target.Variables.extClkMHz == 30
     or Target.Variables.extClkMHz == 40 then

    local c2pAdapter = CCXML.getC2pAdapterType(ccxmlFile)
    if not c2pAdapter then
      U.error('Unable to determine GDB server settings for this board.')
    end
    return '28235,335_%iMHz-JTAG %s' % {Target.Variables.extClkMHz, c2pAdapter}
  else
    U.error('GDB server does not support external clock frequency of this board.')
  end
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, 6)
  resources:add('CPU1TIMER', 0, 1)
  resources:add('EXTSYNC', 1, 1)
  resources:add('GPIO', 0, 63)
  resources:add('Powerstage Control')
  resources:add('PWM', 1, 6)
  resources:add('QEP', 1, 2)
  resources:add('SCI A')
  resources:add('SCI B')
  resources:add('SCI C')
  resources:add('SPI A')
end

function T.getTargetParameters()
  local params = {
    adcs = {
      peripheralType = 2, 
      vrefConfigInterfaceType = 2,
      num_channels = 16,
    },
    cans = {pin_sets = {A_GPIO30_GPIO31 = 0, B_GPIO21_GPIO20 = 10}},
    caps = {
      pins = {
        GPIO1 = 6,
        GPIO3 = 5,
        GPIO5 = 1,
        GPIO7 = 2,
        GPIO9 = 3,
        GPIO11 = 4,
        GPIO24 = 1,
        GPIO25 = 2,
        GPIO26 = 3,
        GPIO27 = 4,
        GPIO34 = 1,
        GPIO37 = 2,
        GPIO48 = 5,
        GPIO49 = 6,
      },
    },
    cpu_timers = {0},
    epwms = {
      type = 0,
      max_event_period = 3,
      gpio = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}},
    },
    gpios = {},
    qeps = {
      pin_set_groups = {
        _1 = {
          a = {20, 50},
          b = {21, 51},
          i = {23, 53},
        },
        _2 = {
          a = {24},
          b = {25},
          i = {26},
        },
      },
    },
    scis = {
      pin_sets = {
        GPIO28_GPIO29 = 0,
        GPIO35_GPIO36 = 1,
        GPIO62_GPIO63 = 2,
        }
      },
    spis = {
      fifo_depth = 16,
      pin_sets = {
        A_GPIO16_GPIO17_GPIO18_GPIO19 = 10,
        A_GPIO16_GPIO17_GPIO18 = 10,
      },
    },
  }
  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 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() / 6
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()
  -- per datasheet: 25 MHz max.
  -- hard-coded: HISPCP = 3, CPS = 1
  return T.getCleanSysClkHz() / 6 / 2
end

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

function T.calcACQPS(ts, sigmodeDifferential)
  -- allowed values are 0-15 for ACQ_PS on the 28335
  local minACQPS = 0
  local maxACQPS = 15
  -- desired setting of the ACQPS register is one less than the number of cycles desired
  if (not U.isScalar(ts)) then
    return minACQPS
  end

  return U.clampNumber(math.ceil(ts * T.getAdcClock()) - 1, {minACQPS, maxACQPS})
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(unit, params)
  local code = [[]]

  if params.isr then
    local isrConfigCode
    if params.trig_is_timer == true then
      isrConfigCode = [[
        EALLOW;
        AdcRegs.ADCTRL2.bit.INT_ENA_SEQ1 = 1;  // enable SEQ1 interrupt (every EOS)
        PieVectTable.ADCINT = %(isr)s;
        PieVectTable.TINT0 = %(isr)s; // interrupt for calling ADC s/w trigger
        EDIS;
        PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
        PieCtrlRegs.PIEIER1.bit.INTx6 = 1;
        PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
      ]] % {isr = params.isr}
    else
      isrConfigCode = [[
        EALLOW;
        AdcRegs.ADCTRL2.bit.INT_ENA_SEQ1 = 1;  // enable SEQ1 interrupt (every EOS)
        PieVectTable.ADCINT = %(isr)s;
        PieCtrlRegs.PIEIER1.bit.INTx6 = 1;
        PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
        EDIS;
      ]] % {isr = params.isr}
    end

    code = code..isrConfigCode
  end

  return code
end

function T.getAdcInterruptAcknCode(unit, params)
  local code
  if params.trig_is_timer == true then
    code = [[
      PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
      IER |= M_INT1;
      if (CpuTimer0Regs.TCR.bit.TIF == 1)
      {
        CpuTimer0Regs.TCR.bit.TIF = 1;  // clear interrupt flag
        AdcRegs.ADCTRL2.bit.SOC_SEQ1 = 1; // trigger ADC by software
        // don't dispatch yet - wait for ADC interrupt
        return;
      }
      else
      {
         AdcRegs.ADCST.bit.INT_SEQ1_CLR = 1; // clear interrupt flag
         AdcRegs.ADCTRL2.bit.RST_SEQ1 = 1; // reset sequencer
      }
   ]]
  else
    code = [[
      AdcRegs.ADCTRL2.bit.RST_SEQ1 = 1;     // reset SEQ1
      AdcRegs.ADCST.bit.INT_SEQ1_CLR = 1;   // clear INT SEQ1 bit
      PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // acknowledge interrupt to PIE (All ADCS in group 1)
      IER |= M_INT1;
    ]]
  end

  return code
end

function T.getClockConfiguration()

  local sysClkHz = T.getCleanSysClkHz()

  if sysClkHz > 150000000 then
    U.error("Excessive system clock setting.")
  end

  inputClkHz = T.getCleanExtClkHz()
  local pllDiv = math.min(math.floor(2 * sysClkHz / inputClkHz), 127)
  if not (sysClkHz == pllDiv * inputClkHz / 2) then
    U.error('Unable to achieve the desired system clock frequency (with input clock = %f Hz).' % {inputClkHz})
  end
  return {
    pllConfig = {
      pllDiv = pllDiv,
    },
    inputClkHz = inputClkHz,
    sysClkHz = sysClkHz,
  }
end

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

  local cpuRate = 100.0 / clockConfig.sysClkHz * 10000000;

  --[[
  Note: HISPCP and LOSPCP hard-coded to SYSCLK/6
  --]]

  local declarations = [[
    void DeviceInit(Uint16 pllDiv);
    void InitFlash();
    void __attribute__((unused)) DSP28x_usDelay(long LoopCount);

// Clock configuration
#define SYSCLK_HZ %(sysClkHz)dL
#define LSPCLK_HZ (SYSCLK_HZ / 6l)
#define PLL_DIV %(pllDiv)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,
    cpuRate = cpuRate,
  }

  local code = [[
    DeviceInit(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
