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

--[[
  Instances of this module are created implicitly in Coder.lua.

  For most targets, a single instance is created to generate initialization code for the
  primary CPU.

  For some targets, a second instance is created to generate initialization code for the
  secondary CPU, e.g. for configuring the direction of GPIO.
--]]

local U = require('common.utils')

local static = {}

local Module = {}

function Module.getBlock(globals, cpu)
  local SysCfg = require('common.block').getBlock(globals, cpu)
  if not static[cpu] then
    static[cpu] = {
      numInstances = 0,
      finalized = false,
    }
  end
  SysCfg.instance = static[cpu].numInstances
  static[cpu].numInstances = static[cpu].numInstances + 1

  function SysCfg:p_getDirectFeedthroughCode()
    U.error(
      'Explicit use of SysCfg:getDirectFeedthroughCode via target block not supported.')
  end

  function SysCfg:finalize(f)
    if static[self.cpu].finalized then
      return
    end

    if static[self.cpu].numInstances ~= 1 then
      U.error(
        'There should be only one (implicit) instance of the SysCfg block.')
    end

    if self:targetUsesPinsets() then
      -- Configuration for older targets is done in configure functions called from block.finalize()
      return
    end

    -- helper function for code clarity
    function finalizingPrimaryCpu()
      return (self.cpu == 0)
    end

    function finalizePeripheral(periphType, entries)
      if     periphType == 'System'
      or     periphType == 'csv'
      or     periphType == 'stackMon' then
        -- do nothing here for these blocks
      elseif periphType == 'gpio' then
        for _, gpio in ipairs(entries) do
          if self.cpu == 0 then
            if (globals.target.pinRequiresGpioConfig(gpio.unit)) then
              -- if pin isn't considered GPIO by default, make it so
              f.PostInitCode:append(
                'GPIO_setPinConfig(GPIO_%(pin)d_GPIO%(pin)d);'
                % {pin = gpio.unit})
            end
            -- configure hardware
            local pinType = 'GPIO_PIN_TYPE_STD'
            if gpio.cbx_direction.equals('OUTPUT') then
              if gpio.cbx_outputType.equals('OPEN_DRAIN') then
                pinType = 'GPIO_PIN_TYPE_OD'
              end
              f.PostInitCode:append(
                'GPIO_setPadConfig(%d, %s);' % {gpio.unit, pinType})
            elseif gpio.cbx_direction.equals('INPUT') then
              if gpio.cbx_pullType.equals('PULL_UP') then
                pinType = '%s | GPIO_PIN_TYPE_PULLUP' % {pinType}
              end
              f.PreInitCode:append(
                'GPIO_setPadConfig(%d, %s);' % {gpio.unit, pinType})
            else
              error('invalid cbx_direction')
            end
            if globals.target.isSecondaryCpu(gpio.core) then
              local controllerName = self:targetMatches({'28P65x', '29H85x'})
                 and 'Controller'
                 or 'Master'  -- older targets still say Master.
              f.PreInitCode:append(
                'GPIO_set%(cName)sCore(%(unit)d, GPIO_CORE_CPU%(core)d);'
                % {
                  cName = controllerName,
                  unit = gpio.unit,
                  core = gpio.core
                })
            end
            if (globals.target.pinHasAnalogMode(gpio.unit)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio.unit})
            end
          end  -- primary CPU configurations

          -- GPIO direction may need to be configured by primary or secondary CPU
          if (gpio.core == globals.target.getGpioDirConfigCpuNumber(self.cpu)) then
            if gpio.cbx_direction.equals('OUTPUT') then
              f.PostInitCode:append(
                'GPIO_setDirectionMode(%d, GPIO_DIR_MODE_OUT);' % {gpio.unit})
            elseif gpio.cbx_direction.equals('INPUT') then
              f.PreInitCode:append(
                'GPIO_setDirectionMode(%d, GPIO_DIR_MODE_IN);' % {gpio.unit})
            else
              error('invalid cbx_direction')
            end
          end
        end
      elseif periphType == 'sci' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, sci in ipairs(entries) do
          f.PostInitCode:append([[
            GPIO_setPinConfig(%(rxgpio)s);
            GPIO_setPinConfig(%(txgpio)s);]]
            % {rxgpio = sci.pinconf[1], txgpio = sci.pinconf[2]})
          if (globals.target.pinHasAnalogMode(sci.pins[1])) then
            f.PreInitCode:append(
              'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {sci.pins[1]})
          end
          if (globals.target.pinHasAnalogMode(sci.pins[2])) then
            f.PreInitCode:append(
              'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {sci.pins[2]})
          end
          if globals.target.isSecondaryCpu(sci.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_SCI%s, SYSCTL_CPUSEL_CPU2);'
                % {sci.unitAsChar})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL5_SCI, %(unit_n)d, SYSCTL_CPUSEL_CPU2);'
                % {unit_n = U.charToOneIndexedInt(sci.unitAsChar)})
            elseif self:targetMatches({'29H85x'}) then
              error('F29 does not have SCI peripherals.')
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'uart' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, uart in ipairs(entries) do
          f.PostInitCode:append([[
            GPIO_setPinConfig(%(rxgpio)s);
            GPIO_setPinConfig(%(txgpio)s);]] %
            {rxgpio = uart.pinconf[1], txgpio = uart.pinconf[2]})
          if (globals.target.pinHasAnalogMode(uart.pins[1])) then
            f.PreInitCode:append(
              'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {uart.pins[1]})
          end
          if (globals.target.pinHasAnalogMode(uart.pins[2])) then
            f.PreInitCode:append(
              'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {uart.pins[2]})
          end
          if globals.target.isSecondaryCpu(uart.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_UART%s, SYSCTL_CPUSEL_CPU2);'
                % {uart.unitAsChar})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_UART%(unit)s, SYSCTL_CPUSEL_CPU%(cpu)d);' %
                {unit = uart.unitAsChar, cpu = uart.core})
            else
              U.throwUnhandledTargetError('UART not supported for this target.')
            end
          end
        end
      elseif periphType == 'sdfm' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, sdfm in pairs(entries) do
          if sdfm.pinconf then
            for _, config in ipairs(sdfm.pinconf) do
              f.PostInitCode:append(
                'GPIO_setPinConfig(%s);' % {config})
            end
          end
          if sdfm.invertClock then
            f.PostInitCode:append(
              'GPIO_setPadConfig(%d, GPIO_PIN_TYPE_INVERT);' % {sdfm.pins.clk})
          end
          -- If pins have analog mode, disable it
          for _, gpio in pairs(sdfm.pins) do
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
          end
        end
      elseif periphType == 'epwm' then
        if finalizingPrimaryCpu() or self:targetMatches({'29H85x'}) then
          -- always generate sync code on primary CPU,
          -- and for select targets also on secondary CPU
          f.TimerSyncCode:append(globals.target.getEpwmTimersSyncCode())
        end

        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, epwm in ipairs(entries) do
          if epwm.pinconf ~= nil then
            for _, config in ipairs(epwm.pinconf) do
              f.PostInitCode:append(
                'GPIO_setPinConfig(%s);' % {config})
            end
          end
          if globals.target.isSecondaryCpu(epwm.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_EPWM%d, SYSCTL_CPUSEL_CPU%d);'
                % {epwm.unitAsInt, epwm.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL0_EPWM, %d, SYSCTL_CPUSEL_CPU%d);'
                % {epwm.unitAsInt, epwm.core})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_EPWM%d, SYSCTL_CPUSEL_CPU%d);'
                % {epwm.unitAsInt, epwm.core})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end

          for _, gpio in ipairs(epwm.gpios) do
            -- If pins have analog mode, disable it
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
            if self:targetMatches({'29H85x'}) then
              f.PostInitCode:append([[
                GPIO_setPadConfig(%(gpio)d, GPIO_PIN_TYPE_STD);
                GPIO_setQualificationMode(%(gpio)d, GPIO_QUAL_SYNC);]]
                % {gpio = gpio})
            end
          end
          -- On dual CPU targets the SyncSocRegs register is only available on CPU1
          -- Only the 28379D target implements getSyncRegsCode()
          if globals.target.getSyncSocRegsCode ~= nil then
            for _, b in ipairs(globals.instances) do
              if b:blockMatches('epwm') then
                if b:getParameter('epwm') == epwm.unitAsInt then
                  local opt_sync = b:getOptSync()
                  if opt_sync and opt_sync.synci_sel then
                    f.PostInitCode:append(
                      globals.target.getSyncSocRegsCode(epwm.unitAsInt, opt_sync))
                  end
                end
              end
            end
          end
        end
      elseif periphType == 'can' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, can in ipairs(entries) do
          f.PostInitCode:append([[
            GPIO_setPadConfig(%(rxgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setPadConfig(%(txgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setQualificationMode(%(rxgpio_num)d, GPIO_QUAL_ASYNC);]]
            % {rxgpio_num = can.pins[1], txgpio_num = can.pins[2]})
          -- If pins have analog mode, disable it
          for _, gpio in ipairs(can.pins) do
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
          end
          if can.pinconf ~= nil then
            for _, config in ipairs(can.pinconf) do
              f.PostInitCode:append(
                'GPIO_setPinConfig(%s);' % {config})
            end
          end
          if globals.target.isSecondaryCpu(can.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_CAN%s, SYSCTL_CPUSEL_CPU%d);'
                % {can.unitAsChar, can.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL8_CAN, %(unit_n)d, SYSCTL_CPUSEL_CPU%(cpu)d);'
                % {unit_n = U.charToOneIndexedInt(can.unitAsChar), cpu = can.core})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'mcan' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, mcan in ipairs(entries) do
          f.PostInitCode:append([[
            GPIO_setPadConfig(%(rxgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setPadConfig(%(txgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setQualificationMode(%(rxgpio_num)d, GPIO_QUAL_ASYNC);
            ]] 
            % {
              rxgpio_num = mcan.pins[1],
              txgpio_num = mcan.pins[2],
            })
          -- If pins have analog mode, disable it
          for _, gpio in ipairs(mcan.pins) do
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
          end
          if mcan.pinconf ~= nil then
            for _, config in ipairs(mcan.pinconf) do
              f.PostInitCode:append(
                'GPIO_setPinConfig(%s);' % {config})
            end
          end
          if globals.target.isSecondaryCpu(mcan.core) then
            if self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_MCAN%(unit)s, SYSCTL_CPUSEL_CPU%(cpu)d);'
                % {
                  unit = mcan.unitAsChar,
                  cpu = mcan.core,
                })
            else
              U.error('MCAN not supported on CPU%d.' % {mcan.core})
            end
          end
        end
      elseif periphType == 'spi' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, spi in ipairs(entries) do
          for i = 1, #spi.pins do
            f.PostInitCode:append([[
              GPIO_setPadConfig(%(pin)d, GPIO_PIN_TYPE_PULLUP);
              GPIO_setQualificationMode(%(pin)d, GPIO_QUAL_SYNC);
              GPIO_setPinConfig(%(conf)s);]]
              % {pin = spi.pins[i], conf = spi.pinconf[i]})
            if (globals.target.pinHasAnalogMode(spi.pins[i])) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);'
                % {spi.pins[i]})
            end
          end
          if globals.target.isSecondaryCpu(spi.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_SPI%s, SYSCTL_CPUSEL_CPU%d);'
                % {spi.unitAsChar, spi.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL6_SPI, %(unit_n)d, SYSCTL_CPUSEL_CPU%(cpu)d);'
                % {unit_n = U.charToOneIndexedInt(spi.unitAsChar), cpu = spi.core})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_SPI%(unit)s, SYSCTL_CPUSEL_CPU%(cpu)d);'
                % {unit = spi.unitAsChar, cpu = spi.core})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'qep' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, qep in ipairs(entries) do
          for _, pin in U.pairsSorted(qep.pins) do
            f.PostInitCode:append([[
              GPIO_setPadConfig(%(pin)d, GPIO_PIN_TYPE_PULLUP);
              GPIO_setQualificationMode(%(pin)d, GPIO_QUAL_SYNC);]]
              % {pin = pin})
            -- If pins have analog mode, disable it
            if (globals.target.pinHasAnalogMode(pin)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {pin})
            end
          end
          if qep.pinconf ~= nil then
            for _, config in ipairs(qep.pinconf) do
              f.PostInitCode:append(
                'GPIO_setPinConfig(%s);' % {config})
            end
          end
          if globals.target.isSecondaryCpu(qep.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_EQEP%d, SYSCTL_CPUSEL_CPU%d);'
                % {qep.unit, qep.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL2_EQEP, %d, SYSCTL_CPUSEL_CPU%d);'
                % {qep.unit, qep.core})
            else
              -- note C29 uses XBAR to configure the QEP
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'cmpss' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, cmpss in ipairs(entries) do
          if self:targetMatches({'29H85x'}) then
            if cmpss.opt_lpmux then
              f.PreInitCode:append(
                'ASysCtl_configCMPMux(ASYSCTL_CMP%(unit)d_LP_%(mux)d);'
                % {unit = cmpss.unit, mux = cmpss.opt_lpmux})
            end
            if cmpss.opt_hpmux then
              f.PreInitCode:append(
                'ASysCtl_configCMPMux(ASYSCTL_CMP%(unit)d_HP_%(mux)d);'
                % {unit = cmpss.unit, mux = cmpss.opt_hpmux})
            end
          elseif self:targetMatches({'28P55x', '28P65x', '2838x', '28003x', '28004x', '2837x', '280013x'}) then
            if cmpss.opt_lpmux then
              f.PreInitCode:append(
                'ASysCtl_selectCMPLPMux(ASYSCTL_CMPLPMUX_SELECT_%(unit)d, %(mux)d);'
                % {unit = cmpss.unit, mux = cmpss.opt_lpmux})
            end
            if cmpss.opt_hpmux then
              f.PreInitCode:append(
                'ASysCtl_selectCMPHPMux(ASYSCTL_CMPHPMUX_SELECT_%(unit)d, %(mux)d);'
                % {unit = cmpss.unit, mux = cmpss.opt_hpmux})
            end
          else
            error('Unhandled target')
          end
          if globals.target.isSecondaryCpu(cmpss.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_CMPSS%d, SYSCTL_CPUSEL_CPU%d);'
                % {cmpss.unit, cmpss.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL12_CMPSS, %d, SYSCTL_CPUSEL_CPU%d);'
                % {cmpss.unit, cmpss.core})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_CMPSS%d, SYSCTL_CPUSEL_CPU%d);'
                % {cmpss.unit, cmpss.core})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'epwm_xbar' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, epwm_xbar in ipairs(entries) do
          for iter, muxconf in ipairs(epwm_xbar.muxconf) do
            if self:targetMatches({'29H85x'}) then
              f.PostInitCode:append([[
                XBAR_selectEpwmXbarInputSource(XBAR_TRIP%(trip)d, %(muxconf)s);]]
                % {trip = epwm_xbar.trip, muxconf = muxconf})
            elseif self:targetMatches({'2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
              f.PostInitCode:append([[
                  XBAR_setEPWMMuxConfig(XBAR_TRIP%(trip)d, %(muxconf)s);
                  XBAR_enableEPWMMux(XBAR_TRIP%(trip)d, XBAR_MUX%(mux)02d);]]
                % {
                  trip = epwm_xbar.trip,
                  mux = epwm_xbar.mux[iter],
                  muxconf = muxconf,
                })
            else
              U.throwUnhandledTargetError()
            end
          end
        end
      elseif periphType == 'input_xbar' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, input_xbar in ipairs(entries) do
          local xbarConfig
          if self:targetMatches({'28004x', '2837x'}) then
            xbarConfig = 'XBAR_setInputPin(XBAR_INPUT%(input)d, %(pin)d);'
          elseif self:targetMatches({'2838x', '28003x', '280013x', '28P55x', '28P65x', '29H85x'}) then
            xbarConfig =
            'XBAR_setInputPin(INPUTXBAR_BASE, XBAR_INPUT%(input)d, %(pin)d);'
          else
            U.throwUnhandledTargetError()
          end
          f.PreInitCode:append(
            xbarConfig % {pin = input_xbar.gpio, input = input_xbar.input})
        end
      elseif periphType == 'ecap' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, ecap in ipairs(entries) do
          if (globals.target.pinHasAnalogMode(ecap.gpio)) then
            f.PreInitCode:append(
              'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {ecap.gpio})
          end
          if globals.target.isSecondaryCpu(ecap.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_ECAP%d, SYSCTL_CPUSEL_CPU%d);'
                % {ecap.unit, ecap.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL1_ECAP, %d, SYSCTL_CPUSEL_CPU%d);'
                % {ecap.unit, ecap.core})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_ECAP%d, SYSCTL_CPUSEL_CPU%d);'
                % {ecap.unit, ecap.core})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'adc' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, adc in ipairs(entries) do
          if globals.target.isSecondaryCpu(adc.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_ADC%s, SYSCTL_CPUSEL_CPU%d);'
                % {adc.unitAsChar, adc.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL11_ADC, %d, SYSCTL_CPUSEL_CPU%d);'
                % {U.charToOneIndexedInt(adc.unitAsChar), adc.core})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_ADC%s, SYSCTL_CPUSEL_CPU%d);'
                % {adc.unitAsChar, adc.core})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'analog_port' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        local initializedPorts = {}
        for _, analogPort in ipairs(entries) do
          if analogPort.pins then
            for _, pin in ipairs(analogPort.pins) do
              if not initializedPorts[pin] then
                f.PostInitCode:append(
                  'GPIO_setAnalogMode(%d, GPIO_ANALOG_ENABLED);' % {pin})
                initializedPorts[pin] = true
              end
            end
          end
        end
      elseif periphType == 'dac' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, dac in ipairs(entries) do
          if globals.target.isSecondaryCpu(dac.core) then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_DAC%s, SYSCTL_CPUSEL_CPU%d);'
                % {dac.unitAsChar, dac.core})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL14_DAC, %d, SYSCTL_CPUSEL_CPU%d);'
                % {U.charToOneIndexedInt(dac.unitAsChar), dac.core})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_PERIPH_CONFIG_DAC%s, SYSCTL_CPUSEL_CPU%d);'
                % {dac.unitAsChar, dac.core})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      elseif periphType == 'ipc_receive' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, ipc_rx in ipairs(entries) do
          local match = {
            flag = false,
            datatype = false,
            datalength = false,
          }
          local remote = {
            datatype = nil,  -- these initializations are meaningless...
            datalength = nil,
          }

          if sysCfg['ipc_transmit'] ~= nil then
            for _, ipc_tx in ipairs(sysCfg['ipc_transmit']) do
              if ipc_tx.flag == ipc_rx.flag then
                match.flag = true
                if ipc_tx.datatype == ipc_rx.datatype then
                  match.datatype = true
                else
                  remote.datatype = ipc_tx.datatype
                end
                if ipc_tx.datalength == ipc_rx.datalength then
                  match.datalength = true
                else
                  remote.datalength = ipc_tx.datalength
                end
                break
              end
            end
          end
          if not match.flag then
            U.error(
              'No matching IPC transmit block with flag %d on CPU%d found.'
              % {ipc_rx.flag, ipc_rx.remote_cpu})
          end
          if not match.datatype then
            U.error([[
              Data type for IPC blocks with flag %(rxFlag)d does not match.
              • CPU%(rxRemoteCpu)d has data type: %(remoteDataType)s
              • CPU%(rxLocalCpu)d has data type: %(rxDataType)s
            ]] % {
              rxFlag = ipc_rx.flag,
              rxRemoteCpu = ipc_rx.remote_cpu,
              remoteDataType = U.dataTypeAsString(remote.datatype),
              rxLocalCpu = ipc_rx.local_cpu,
              rxDataType = U.dataTypeAsString(ipc_rx.datatype),
            })
          end
          if not match.datalength then
            U.error([[
              Data length (number of signals) for IPC blocks with flag %(rxFlag)d does not match.
              • CPU%(remoteCpu)d transmits %(remoteDataLen)d signal(s)
              • CPU%(localCpu)d is configured to receive %(rxDataLen)d signal(s)
            ]] % {
              rxFlag = ipc_rx.flag,
              remoteCpu = ipc_rx.remote_cpu,
              remoteDataLen = remote.datalength,
              localCpu = ipc_rx.local_cpu,
              rxDataLen = ipc_rx.datalength,
            })
          end
        end
      elseif periphType == 'ipc_transmit' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        for _, ipc_tx in ipairs(entries) do
          local flag = ipc_tx.flag
          local match = {}
          match.flag = false
          if sysCfg['ipc_receive'] ~= nil then
            for _, ipc_rx in ipairs(sysCfg['ipc_receive']) do
              if ipc_rx.flag == flag then
                match.flag = true
                break
              end
            end
          end
          if not match.flag then
            U.error('No matching IPC receive block with flag %d on CPU%d found.' %
              {ipc_tx.flag, ipc_tx.remote_cpu})
          end
        end
      elseif periphType == 'f29x_epwm_xlink' then
        if not finalizingPrimaryCpu() then
          return -- Nothing to do for Secondary CPU
        end
        local linkChain = {}
        for _, epwm in ipairs(entries) do
          table.insert(linkChain, "SYSCTL_EPWM%d_XLINK" % {epwm.unit})
        end
        f.PreInitCode:append([[
            SysCtl_enableEPWMXLINK(%(linkChain)s);
          ]] % {linkChain = table.concat(linkChain, " | "),
        })        
      else
        error(
          'Undefined peripheral type for syscfg.lua: %(pType)s' %
          {pType = periphType})
      end
    end

    f.Include:append('pin_map.h')
    f.Include:append('gpio.h')
    f.Include:append('xbar.h')
    f.Include:append('asysctl.h')
    f.Include:append('sysctl.h')
    f.Include:append('plx_dio.h')

    f.PreInitCode:append('{ // early system configuration')
    f.PostInitCode:append('{ // late system configuration')

    f.PreInitCode:append('PLX_DIO_sinit();')

    local sysCfg = globals.syscfg:getAllEntries()
    for periphType, entries in U.pairsSorted(sysCfg) do
      finalizePeripheral(periphType, entries)
    end

    f.PreInitCode:append('}')
    f.PostInitCode:append('}')

    static[self.cpu].finalized = true
  end

  return SysCfg
end

return Module
