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

local TripInputsAllocated = {}
local GpioAllocated = {}

function T.getFamilyPrefix()
  return '28P55x'
end

function T.getC2pConfig(c2pAdapter)
  return '28P550SJ9_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 = {'28P550SJ9'},
    boards = {'custom', 'launchpad', 'controlcard'},
    uniFlashConfigs = {
      launchpad = 'Launchpad_TMS320F28P550SJ9.ccxml',
      controlcard = 'ControlCard_TMS320F28P550SJ9.ccxml',
    },
    c2pConfigs = {
      launchpad = T.getC2pConfig('xds110-2w'),
      controlcard = T.getC2pConfig('xds110'),
    },
    linkerFiles = {
      {'f28p55x_flash_lnk.cmd', 'f28p55x_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, 15)
  resources:add('ADCB-SOC', 0, 15)
  resources:add('ADCC-SOC', 0, 15)
  resources:add('ADCD-SOC', 0, 15)
  resources:add('ADCE-SOC', 0, 15)
  resources:add('Base Task Load')
  resources:add('CAP', 1, 2)
  resources:add('CMPSS', 1, 4)
  resources:add('CPU1TIMER', 0, 1)
  resources:add('DAC A')
  resources:add('EXTSYNC', 1, 2)
  resources:add('GPIO', 0, 253)
  resources:add('HRPWM', 1, 6)
  resources:add('MCAN A')
  resources:add('MCAN B')
  resources:add('Powerstage Control')
  resources:add('PWM', 1, 12)
  resources:add('QEP', 1, 3)
  resources:add('SCI A')
  resources:add('SCI B')
  resources:add('SCI C')
  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 = 6, 
      vrefConfigInterfaceType = 5,
      defaultVref = 3.3,
      num_channels = 32, 
      vref_min = 2.4,
      vref_max = 3.3, -- allowable range on external vref
      num_units = 5,  -- 5 ADCs (independent references not supported at this time)
      -- The below ADC channels require configuration as an analog port
      analog_mapping = {
        A2 = 224,
        A3 = 242,
        A6 = 228,
        A9 = 227,
        A10 = 230,
        A16 = 28,
        A17 = 20,
        A18 = 21,
        A19 = 13,
        A20 = 12,
        A24 = 11,
        A25 = 17,
        A27 = 212,
        A28 = 215,
        B0 = 253,
        B1 = 230,
        B2 = 226,
        B3 = 242,
        B4 = 236,
        B6 = 224,
        B16 = 28,
        B17 = 20,
        B18 = 21,
        B19 = 13,
        B20 = 12,
        B24 = 33,
        B25 = 24,
        B27 = 213,
        C5 = 242,
        C6 = 226,
        C8 = 236,
        C9 = 224,
        C10 = 230,
        C11 = 253,
        C14 = 247,
        C16 = 28,
        C17 = 20,
        C18 = 21,
        C19 = 13,
        C20 = 12,
        C24 = 16,
        C26 = 211,
        C27 = 214,
        D0 = 11,
        D1 = 33,
        D2 = 16,
        D3 = 17,
        D4 = 24,
        D8 = 211,
        D9 = 212,
        D10 = 213,
        D14 = 228,
        D18 = 214,
        D19 = 215,
        E0 = 11,
        E1 = 33,
        E2 = 16,
        E3 = 17,
        E4 = 24,
        E8 = 211,
        E9 = 212,
        E10 = 213,
        E12 = 226,
        E14 = 228,
        E18 = 214,
        E19 = 215,
      },
      vrefMap = {A = 'A', B = 'A', C = 'A', D = 'A', E = 'A'}, -- only one reference
    },
    caps = {
      type = 2,
    },
    -- Trips 4, 5, and 7-12 available for EPWM XBAR, allocate these for cbc trips
    cbc_trip_inputs = {8, 9, 10, 11, 12},
    clas = {
      type = 2,
    },
    comps = {
      -- Listed as ANALOGPIN = {COMP#, INPUT#}, according to listed order in data sheet
      positive = {
        A0 = {3, 2}, B15 = {3, 2}, C15 = {3, 2},
        A1 = {1, 4}, B7 = {1, 4}, D11 = {1, 4},
        A2 = {1, 0}, B6 = {1, 0}, C9 = {1, 0},
        A3 = {3, 5},
        A4 = {2, 0}, B8 = {2, 0},
        A5 = {2, 5},
        A6 = {1, 2}, D14 = {1, 2}, E14 = {1, 2},
        A7 = {4, 1}, B30 = {4, 1}, C3 = {4, 1}, D12 = {4, 1}, E30 = {4, 1},
        A8 = {4, 4}, B0 = {4, 4}, C11 = {4, 4},
        A9 = {2, 2},
        A10 = {2, 3}, B1 = {2, 3}, C10 = {2, 3},
        A11 = {1, 1}, B10 = {1, 1}, C0 = {1, 1},
        A12 = {2, 1},
        A14 = {3, 4}, B14 = {3, 4}, C4 = {3, 4},
        A15 = {1, 3},
        B2 = {3, 0}, C6 = {3, 0}, E12 = {3, 0},
        B3 = {3, 3},
        B4 = {4, 0}, C8 = {4, 0},
        B5 = {1, 5}, D15 = {1, 5}, E15 = {1, 5},
        B11 = {4, 5}, D16 = {4, 5}, E16 = {4, 5},
        B12 = {3, 1}, C2 = {3, 1},
        C1 = {4, 2}, E11 = {4, 2},
        C14 = {4, 3},
      },
    },
    cpu_timers = {0, 1},
    dacs = {
      minOutputVoltage = 0.0,
      maxOutputVoltage = 3.3,
      vrefMap = {A = 'A'},
    },
    epwms = {
      type = 4,
      max_event_period = 15,
      num_units = 12,
      sync_group_size = 1,
    },
    gpios = {
      openDrainSupported = true,
    },
    hrpwms = {
      num_units = 6,
    },
    mcans = {
      type = 2,
      ports = {'A', 'B'},
    },
    qeps = {},
    scis = {
      type = 0,
      num_units = 3,
    },
    spis = {
      type = 2, fifo_depth = 16,
    },
    -- Trips 4, 5, and 7-12 available for EPWM XBAR, allocate these for trip zones
    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 comp = T.getTargetParameters().comps.positive[pin]
  if comp == nil then
    return nil
  end

  local compLowHigh = {
    unit = comp[1],
    mux = comp[2],
  }
  return {
    low = compLowHigh,
    high = compLowHigh,
  }
end

function T.checkGpioIsValidPwmSync(gpio)
  local valid = (gpio <= 81)
     or ((208 <= gpio) and (gpio <= 215))
     or ((224 <= gpio) and (gpio <= 253))
  if not valid then
    U.error(
      'Only @param:Gpio: from 0-81, 208-215, and 224-253 can be selected as external synchronization source for this target.')
  end
end

function T.pinHasAnalogMode(pin)
  -- With this chip some pins have optional analog mode and need special configuration because of it
  return ((11 <= pin) and (pin <= 13)) or
     ((16 <= pin) and (pin <= 17)) or
     ((20 <= pin) and (pin <= 21)) or
     (pin == 24) or
     (pin == 28) or
     (pin == 33) or
     ((211 <= pin) and (pin <= 215)) or
     ((224 <= pin) and (pin <= 247))
end

function T.pinRequiresGpioConfig(pin)
  -- For some reason this chip has some pins whose default configuration isn't to be GPIO (in this case, JTAG)
  -- For this case, we need to check and configure these pins to be GPIO
  return (pin == 35) or (pin == 37)
end

function T.getPwmSyncInSel(params)
  local mux
  if params.type == 'external' then
    if params.source_unit == 1 then
      mux = 0x18
    elseif params.source_unit == 2 then
      mux = 0x19
    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()
  return T.getCleanSysClkHz() / 4
end

function T.getPwmClock()
  -- TBCLOCK - CLKDIV=/1, HSPCLKDIV=/1
  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()
  -- Clock is set to be maximum of 75 MHz
  local sysClkHz = T.getCleanSysClkHz()
  if sysClkHz <= 75000000 then
    return sysClkHz
  else
    return sysClkHz / 2
  end
end

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

function T.calcACQPS(ts, sigmodeDifferential)
  local tsMin = math.max(67e-9, 1 / T.getAdcClock()) -- per datasheet 67 ns minimum
  local acqpsMax = 511
  local acqpsMin = 0
  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)sRegs.TCR.bit.TSS = 1; // stop timer
    CpuTimer%(unit)sRegs.TPRH.all = 0;
    CpuTimer%(unit)sRegs.PRD.all = %(period)d - 1;
    CpuTimer%(unit)sRegs.TCR.bit.TRB = 1; // reload period
    CpuTimer%(unit)sRegs.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)s_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.getEpwmTimersSyncCode()
  return [[
    CpuSysRegs.PCLKCR0.bit.TBCLKSYNC = 1; // start all the timers synced
  ]]
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 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
    EDIS;
  ]] % {
    adc = string.lower(unitAsChar),
    int1sel = params.INT1SEL,
  }
  local zeroIndexedUnit = U.charToZeroIndexedInt(unitAsChar)
  local pieBitMask
  -- Units D and E have different PIE locations
  if zeroIndexedUnit > 2 then
    pieBitMask = (1 << (zeroIndexedUnit + 5))
  else
    pieBitMask = 1 << zeroIndexedUnit
  end

  local baseInt
  if zeroIndexedUnit > 2 then
    baseInt = '*(PINT *)((uint32_t)(&PieVectTable.ADCD1_INT) + ((uint32_t)%(unit)d - 3)*sizeof(PINT *)) = &%(isr)s' % {unit = zeroIndexedUnit, isr = params.isr}
  else
    baseInt = '*(PINT *)((uint32_t)(&PieVectTable.ADCA1_INT) + ((uint32_t)%(unit)d)*sizeof(PINT *)) = &%(isr)s' % {unit = zeroIndexedUnit, isr = params.isr}
  end

  if params.isr then
    local isrConfigCode = [[
      EALLOW;
      %(baseInt)s;
      PieCtrlRegs.PIEIER1.all |= %(pieBitMask)s;
      PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
      EDIS;
    ]] % {
      baseInt = baseInt,
      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

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 = 2e6, max = 20e6})
  limits:set('VCOCLK', {min = 220e6, max = 600e6})
  limits:set('PLLRAWCLK', {min = 6e6, max = 300e6})
  limits:set('SYSCLK', {min = 2e6, max = 150e6})

  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 = 0

  local cpuRate = 100.0 / clockConfig.sysClkHz * 10000000

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

// Clock configuration
#define PLX_DEVICE_SETCLOCK_CFG       (%(clkSrcMacro)s | SYSCTL_IMULT(%(imult)d) | \
                                      SYSCTL_REFDIV(%(refdiv)d) | SYSCTL_ODIV(%(odiv)d) | \
                                      SYSCTL_SYSDIV(%(sysdiv)d) | SYSCTL_PLL_ENABLE | \
                                      SYSCTL_DCC_BASE_%(dccBase)d)

#define SYSCLK_HZ %(sysClkHz)dL
#define LSPCLK_HZ (%(sysClkHz)dL / 4l)

#define PLX_DELAY_US(A)  F28x_usDelay(((((long double) A * 1000.0L) / (long double)%(cpuRate)fL) - 9.0L) / 5.0L)
  ]] % {
    clkSrcMacro = clkSrcMacro,
    imult = pllConfig.imult,
    refdiv = pllConfig.refdiv,
    odiv = pllConfig.odiv,
    sysdiv = pllConfig.sysdiv,
    dccBase = dccBase,
    sysClkHz = clockConfig.sysClkHz,
    cpuRate = cpuRate,
  }

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

      SysCtl_setLowSpeedClock(SYSCTL_LSPCLK_PRESCALE_4);
    }

    InitFlashHz(SYSCLK_HZ);

    // set cpu timers to same clock as ePWM
    CpuTimer0Regs.TPR.all = 0;
    CpuTimer1Regs.TPR.all = 0;
    CpuTimer2Regs.TPR.all = 0;
    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
    EDIS;
  ]]

  return {declarations = declarations, code = code}
end

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

return T
