--[[
  Copyright (c) 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 U = require('common.utils')

local static = {}
local Module = {}

local e_filterTypes = U.enum({
  'Sinc1',
  'Sinc2',
  'Sinc3',
  'SincFast',
})

-- for a given filter type and oversample rate, returns the appropriate shift register setting
local function getShiftValue(osr, filterTypeIndex)
  local shiftValues = {
    -- Sinc: 1  2  3  FAST
    [4]   = {0, 0, 0, 0},
    [8]   = {0, 0, 0, 0},
    [16]  = {0, 0, 0, 0},
    [32]  = {0, 0, 1, 0},
    [64]  = {0, 0, 4, 0},
    [128] = {0, 0, 7, 1},
    [256] = {0, 2, 10, 3},
  }
  if shiftValues[osr][filterTypeIndex] then
    return shiftValues[osr][filterTypeIndex]
  else
    U.error('Invalid arguments supplied to getShiftValue.')
  end
end

-- for a given filter type and oversample rate, returns the maximum possible value
local function getMaxFilterValue(osr, filterTypeIndex)
  if filterTypeIndex == e_filterTypes.Sinc1 then
    return osr
  elseif filterTypeIndex == e_filterTypes.Sinc2 then
    return osr ^ 2
  elseif filterTypeIndex == e_filterTypes.Sinc3 then
    return osr ^ 3
  elseif filterTypeIndex == e_filterTypes.SincFast then
    return 2 * osr ^ 2
  else
    error('SDFM: Invalid filterType: d%' % filterTypeIndex)
  end
end

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

  Sdfm.instance = static[cpu].numInstances
  static[cpu].numInstances = static[cpu].numInstances + 1

  function Sdfm:checkMaskParameters()
    -- Configuration Tab

    self.cbx_sdfmUnit = U.enforceMask_newComboBox(
      'SDUnit',
      {
        'SDFM1_BASE',
        'SDFM2_BASE',
        'SDFM3_BASE',
        'SDFM4_BASE',
      })

    self.sdUnitAsInt = self.cbx_sdfmUnit.asCx()

    self.cbx_dataFilterNumber = U.enforceMask_newComboBox(
      'ChannelNumber',
      {
        'SDFM_FILTER_1',
        'SDFM_FILTER_2',
        'SDFM_FILTER_3',
        'SDFM_FILTER_4',
      })

    self.filterNumberAsInt = self.cbx_dataFilterNumber.asCx()

    if not Sdfm:checkResourceExists(
      'SDFM %d' % {self.sdUnitAsInt}, tostring(self.filterNumberAsInt)) then
      U.error([[
        Target does not possess the requested SDFM resources:
        SDFM %d, Channel %d]]
        % {
          self.sdUnitAsInt,
          self.filterNumberAsInt
        })
    end

    self.isClockSharing = false
    if not self.cbx_dataFilterNumber.equals('SDFM_FILTER_1') then
      self.cbx_clkSelect = U.enforceMask_newComboBox(
        'ClkSelect',
        {
          'SDFM_CLK_SOURCE_CHANNEL_CLK',
          'SDFM_CLK_SOURCE_SD1_CLK'
        })

      self.isClockSharing = self.cbx_clkSelect.equals('SDFM_CLK_SOURCE_SD1_CLK')
    end

    self.pins = {}
    self.invertClock = false

    if not self.isClockSharing then
      local cbx_clkPolarity = U.enforceMask_newComboBox(
        'ClkPolarity', {'rising', 'falling'})
      self.invertClock = cbx_clkPolarity.equals('falling')

      local tempPins = U.enforceMask(U.isPinPair, 'Pins')
      self.pins.clk = tempPins[1]
      self.pins.data = tempPins[2]
    else
      self.pins.data = U.enforceMask(U.isNonNegativeIntScalar, 'DataPin')
    end

    -- Filtering Tab

    self.cbx_dataFilterType = U.enforceMask_newComboBox(
      'FilterType',
      {
        'SDFM_FILTER_SINC_1',
        'SDFM_FILTER_SINC_2',
        'SDFM_FILTER_SINC_3',
        'SDFM_FILTER_SINC_FAST',
      })

    -- OSR does not need to be checked because it is a ComboBox whose actual
    -- values map directly to its functional values. For OSR, this is
    -- 4, 8, 16, 32, 64, 128, and 256. If any other value is supplied to this
    -- variable, the block itself will produce an error.
    self.osr = U.passThrough(Block.Mask.OSR)
    self.scale = U.enforceMask(U.isScalar, 'Scale')
    self.offset = U.enforceMask(U.isScalar, 'Offset')

    -- Comparator Tab

    self.compEnabled = U.comboEnabled(Block.Mask.CompEnabled)

    if self.compEnabled then
      self.cbx_compFilterType = U.enforceMask_newComboBox(
        'CompFilterType',
        {
          'SDFM_FILTER_SINC_1',
          'SDFM_FILTER_SINC_2',
          'SDFM_FILTER_SINC_3',
          'SDFM_FILTER_SINC_FAST',
        })

      -- Same reasoning as data filter OSR settings, except for the
      -- comparator, the OSR values are limited to 4, 8, 16, and 32.
      self.compOSR = U.passThrough(Block.Mask.CompOSR)
      -- The OSR together with the filter type determines the maximum
      -- value that can be output by the comparator Sinc filter.
      self.compMaxOutputValue = getMaxFilterValue(self.compOSR,
                                      self.cbx_compFilterType.asCx())

      -- Unlike the main data filter OSR, the comparator OSR needs 
      -- to be stored in a 6-bit number. The driverlib functions thus
      -- expect a value from 0 to 31, and then later adds 1 to get the
      -- true OSR. So here, we store the comparator OSR as "(OSR - 1)"
      self.reg_compOSR = self.compOSR - 1

      self.cbx_tripSignal = U.enforceMask_newComboBox(
        'CompTripSignalSel',
        {
          'A',
          'B',
          'C',
        }
      )

      -- establish TRIPIN signal number
      -- 'Emit trip signal' aka group A, B, C
      local group = self.cbx_tripSignal.asString()
      self.tripInSignal =
         globals.target.getTargetParameters().trip_groups[group]

      -- check if global tripzone object exists
      self.tripzones_obj = self:getGlobalBlockInstance('tripzones')

      if self.tripzones_obj == nil then
        error(
          'TSP exception: TZs tripzones object not found, should have been created in Coder.Initialize().')
      end

      self.tripzones_obj:registerTripSignalGroup(
        group,
        {
          sourceBlockPath = self:getPath(),
          widgetName = 'CompTripSignalSel'
        })

      self.cbx_compThreshType = U.enforceMask_newComboBox(
        'CompThreshType',
        {
          'High',
          'Low',
          'Window',
        }
      )

      if self.cbx_compThreshType.equals('High')
      or self.cbx_compThreshType.equals('Window') then
        self.opt_compHigh = {}
        local compThreshH1Unscaled = U.enforceMask(U.isScalarInOpenInterval,
                                                   'CompThreshH1', 0, 1)
        self.opt_compHigh.threshold = math.floor(compThreshH1Unscaled *
          self.compMaxOutputValue)
      end

      if self.cbx_compThreshType.equals('Low')
      or self.cbx_compThreshType.equals('Window') then
        self.opt_compLow = {}
        local compThreshL1Unscaled = U.enforceMask(U.isScalarInOpenInterval,
                                                   'CompThreshL1', 0, 1)
        self.opt_compLow.threshold = math.floor(compThreshL1Unscaled *
          self.compMaxOutputValue)
      end
    end
  end

  function Sdfm:p_getDirectFeedthroughCode()
    local Require = ResourceList:new()
    local InitCode = U.CodeLines:new()
    local Declarations = U.CodeLines:new()
    
    table.insert(static[self.cpu].instances, self.bid)

    local clkGpioStr, dataGpioStr
    if not self.isClockSharing then
      clkGpioStr = 'GPIO_%d_SD%d_C%d' % {
        self.pins.clk,
        self.sdUnitAsInt,
        self.filterNumberAsInt,
      }
      dataGpioStr = 'GPIO_%d_SD%d_D%d' % {
        self.pins.data,
        self.sdUnitAsInt,
        self.filterNumberAsInt,
      }

      if not (globals.target.validateAlternateFunction(clkGpioStr)
        and globals.target.validateAlternateFunction(dataGpioStr)) then
        U.error('SDFM pin configuration invalid: [%s, %s]' %
          {clkGpioStr, dataGpioStr})
      end

      globals.syscfg:addPeripheralEntry('sdfm', {
        unit = self.sdUnitAsInt,
        filterNumber = self.filterNumberAsInt,
        pins = self.pins,
        pinconf = {clkGpioStr, dataGpioStr},
        invertClock = self.invertClock,
        core = globals.target.getTiCpuNumber(self.cpu)
      })
    else
      if self:targetMatches({'2837x', '28004x'}) then
        U.error([[
          Target chip does not support sharing the channel 1 SDFM clock.
          Please provide one clock pin per SDFM channel.'
        ]])
      end

      dataGpioStr = 'GPIO_%d_SD%d_D%d' % {
        self.pins.data,
        self.sdUnitAsInt,
        self.filterNumberAsInt,
      }
      if not globals.target.validateAlternateFunction(dataGpioStr) then
        U.error('SDFM data pin invalid: [%s]' % {dataGpioStr})
      end

      globals.syscfg:addPeripheralEntry('sdfm', {
        unit = self.sdUnitAsInt,
        filterNumber = self.filterNumberAsInt,
        pins = self.pins,
        pinconf = {dataGpioStr},
        invertClock = self.invertClock,
        core = globals.target.getTiCpuNumber(self.cpu)
      })
    end

    Require:add('SDFM %d' % {self.sdUnitAsInt}, self.filterNumberAsInt)
    for _, pin in pairs(self.pins) do
      Require:add('GPIO', pin)
    end

    local dataFilterMaxValue = getMaxFilterValue(self.osr, self.cbx_dataFilterType.asCx())
    self.normalCoeff = 1.0 / dataFilterMaxValue

    if (dataFilterMaxValue > 2 ^ 15) then
      -- if we are using 32-bit format, there's no need for a shift value
      self.m_dataFormat = 'SDFM_DATA_FORMAT_32_BIT'
      self.dataWidth = 32
      self.shiftValue = 0
    else
      self.m_dataFormat = 'SDFM_DATA_FORMAT_16_BIT'
      self.dataWidth = 16
      self.shiftValue = getShiftValue(self.osr, self.cbx_dataFilterType.asCx())
    end

    if self.compEnabled then
      -- configure SDFM and EPWM_XBAR multiplexer settings
      local xbarEpwmMuxConfigs = {}
      local xbarEpwmMuxUnits = {}
      if self:targetMatches({'28P65x'}) then
        -- 28P65x has 4 SDFM and a slightly different mux layout
        -- see the TRM or the 28P65x driverlib file 'xbar.h'
        local xbarEpwmMuxHigh
        local xbarEpwmMuxLow
        if self.sdUnitAsInt < 3 then
          xbarEpwmMuxHigh =
              16 + 8 * (self.sdUnitAsInt - 1) + 2 * (self.filterNumberAsInt - 1)
          xbarEpwmMuxLow = xbarEpwmMuxHigh + 1
        elseif self.sdUnitAsInt <= 4 then
          xbarEpwmMuxHigh =
              16 * (self.sdUnitAsInt - 3) + 4 * (self.filterNumberAsInt - 1)
          xbarEpwmMuxLow = 2 + xbarEpwmMuxHigh
        else
          error('SDFM: Unknown SDFM unit.')
        end

        -- For F28P65X target convention has changed to instead configuring mux for both events individually
        if self.cbx_compThreshType.equals('Window') or
           self.cbx_compThreshType.equals('High') then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02d_SD%dFLT%d_CEVT1'
            % {xbarEpwmMuxHigh, self.sdUnitAsInt, self.filterNumberAsInt})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxHigh)
        end

        if self.cbx_compThreshType.equals('Window') or
           self.cbx_compThreshType.equals('Low') then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02d_SD%dFLT%d_CEVT2'
            % {xbarEpwmMuxLow, self.sdUnitAsInt, self.filterNumberAsInt})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxLow)
        end
      elseif self:targetMatches({'29H85x'}) then
        -- 29H85x has created XBAR aliasas that map to SDFM filter/channel configs directly
        if self.cbx_compThreshType.equals('Window') or
           self.cbx_compThreshType.equals('High') then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_SD%dFLT%d_COMPH'
            % {self.sdUnitAsInt, self.filterNumberAsInt})
        end

        if self.cbx_compThreshType.equals('Window') or
           self.cbx_compThreshType.equals('Low') then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_SD%dFLT%d_COMPL'
            % {self.sdUnitAsInt, self.filterNumberAsInt})
        end

      elseif self:targetMatches({'2837x', '2838x', '28003x', '28004x', '280013x', '28P55x'}) then
        --[[
        The following is based on this assumption of register ordering,
        from the 28003x TRF:

        XBAR_OUT_MUX16_SD1FLT1_COMPH           = 0x2000,
        XBAR_OUT_MUX16_SD1FLT1_COMPH_OR_COMPL  = 0x2001,
        XBAR_OUT_MUX17_SD1FLT1_COMPL           = 0x2200,
        [...]
        XBAR_OUT_MUX18_SD1FLT2_COMPH           = 0x2400,
        XBAR_OUT_MUX18_SD1FLT2_COMPH_OR_COMPL  = 0x2401,
        XBAR_OUT_MUX19_SD1FLT2_COMPL           = 0x2600,
        [...]
        XBAR_OUT_MUX24_SD2FLT1_COMPH           = 0x3000,
        XBAR_OUT_MUX24_SD2FLT1_COMPH_OR_COMPL  = 0x3001,
        XBAR_OUT_MUX25_SD2FLT1_COMPL           = 0x3200,
        ]]  --

        local xbarEpwmMuxBase = 16
            + 8 * (self.sdUnitAsInt - 1)
            + 2 * (self.filterNumberAsInt - 1)

        if self.cbx_compThreshType.equals('Window') then
          table.insert(
            xbarEpwmMuxConfigs,
            'XBAR_EPWM_MUX%02d_SD%dFLT%d_COMPH_OR_COMPL' %
            {
              xbarEpwmMuxBase, self.sdUnitAsInt, self.filterNumberAsInt,
            })
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase)
        elseif self.cbx_compThreshType.equals('High') then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02d_SD%iFLT%d_COMPH'
            % {xbarEpwmMuxBase, self.sdUnitAsInt, self.filterNumberAsInt})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase)
        elseif self.cbx_compThreshType.equals('Low') then
          table.insert(xbarEpwmMuxConfigs, 'XBAR_EPWM_MUX%02d_SD%dFLT%d_COMPL'
            % {xbarEpwmMuxBase + 1, self.sdUnitAsInt, self.filterNumberAsInt})
          table.insert(xbarEpwmMuxUnits, xbarEpwmMuxBase + 1)
        end
      else
        U.throwUnhandledTargetError()
      end

      globals.syscfg:addPeripheralEntry('epwm_xbar', {
        trip = self.tripInSignal,
        mux = xbarEpwmMuxUnits,
        muxconf = xbarEpwmMuxConfigs,
        core = globals.target.getTiCpuNumber(self.cpu)
      })
    end

    -- Data Output

    -- By default, with scaling and offset factors at 1 and 0 respectively,
    -- the SDFM block will output a unitless value from -1 to 1. This is because
    -- unlike a conventional ADC, we can't be sure of the dynamic range of the
    -- part encoding the original signal.
    local dataFilterOutputSignal = 
       '(%(offset)f + (%(scaling)f * (%(normal_coeff)e * (float) PLXHAL_SDFM_getFilterData%(width)d(%(instance)d))))'
       % {
         offset = self.offset,
         scaling = self.scale,
         instance = self.instance,
         normal_coeff = self.normalCoeff,
         width = self.dataWidth,
       }

    return {
      InitCode = InitCode,
      Require = Require,
      UserData = {bid = self:getId()},
      OutputSignal = {
        [1] = {dataFilterOutputSignal},
      },
      Declarations = Declarations,
    }
  end

  function Sdfm:finalizeThis(c, activeUnits)
    --[[====== Finalize Data Filters ======]]
    c.PostInitCode:append([[
      PLX_SDFM_configFilter(SdfmHandles[%(instance)d], %(m_unit)s,
                            %(m_filterNumber)s,
                            %(m_filterTypeStr)s, %(m_dataFormat)s,
                            %(osr)d, %(shift)d);
    ]] % {
      instance = self.instance,
      m_unit = self.cbx_sdfmUnit.asString(),
      m_filterNumber = self.cbx_dataFilterNumber.asString(),
      m_filterTypeStr = self.cbx_dataFilterType.asString(),
      m_dataFormat = self.m_dataFormat,
      osr = self.osr,
      shift = self.shiftValue,
    })

    -- some targets do not have clock-sharing feature
    if self.isClockSharing then
      c.PostInitCode:append([[
        PLX_SDFM_configClockSource(SdfmHandles[%(instance)d], %(m_clkSource)s);
      ]] % {
        instance = self.instance,
        m_clkSource = self.cbx_clkSelect.asString(),
      })
    end

    -- [[====== Finalize Comparators ======]]

    local comparatorHighConfigCode = ''
    local comparatorLowConfigCode = ''
    local digitalFilterConfigCode = ''
    local comparatorEnableCode = ''

    if self.opt_compHigh then
      comparatorHighConfigCode = [[
        PLX_SDFM_configCompHighThreshold(SdfmHandles[%(instance)d], %(highThreshold)d);
      ]] % {
        instance = self.instance,
        highThreshold = self.opt_compHigh.threshold,
      }
    end

    if self.opt_compLow then
      comparatorLowConfigCode = [[
        PLX_SDFM_configCompLowThreshold(SdfmHandles[%(instance)d], %(lowThreshold)d);
      ]] % {
        instance = self.instance,
        lowThreshold = self.opt_compLow.threshold,
      }
    end

    if not self:targetMatches({'2837x', '28004x'}) then
      --[[
        Per TRM, we need to ensure that the trip pulse width is at least 3xTBCLK.
        Very similarly to the CMPSS subsystem (and the CSMPSS block code), we can
        use the digital filter path which can be applied to the CEVTs themselves
        to force this trip pulse width.

        This only works on more recent models -- for 2837x and 28004x, we cannot
        filter the comparator outputs, and thus the trip pulse width cannot
        be guaranteed. TODO: is this acceptable behavior?
      --]]
      local glitchLatencyInSysTick = math.ceil(
        3 * globals.target.getCleanSysClkHz() / globals.target.getPwmClock())
      local filterPrescale = 1
      -- filter latency = 1 + prescale(1+threshold)
      local filterThreshold =
         math.ceil((glitchLatencyInSysTick - 1) / filterPrescale) - 1
      -- According to TRM, filterWindow must be at least 2*filterThreshold
      local filterWindow = filterThreshold * 2
      digitalFilterConfigCode = [[
        {
          SDFM_CompEventFilterConfig compConfig;
          compConfig.clkPrescale = %(filterPrescale)d;
          compConfig.sampleWindow = %(filterWindow)d;
          compConfig.threshold = %(filterThreshold)d;

          PLX_SDFM_configCompDigitalFilter(
                                    SdfmHandles[%(instance)d],
                                    %(cb_highCompEnabled)d,
                                    %(cb_lowCompEnabled)d,
                                    &compConfig);
        }
      ]] % {
        filterPrescale = filterPrescale,
        filterWindow = filterWindow,
        filterThreshold = filterThreshold,
        cb_highCompEnabled = self.opt_compHigh and '1' or '0',
        cb_lowCompEnabled = self.opt_compLow and '1' or '0',
        instance = self.instance,
      }
    end

    if not self:targetMatches({'2837x'}) then
      comparatorEnableCode = [[
        PLX_SDFM_enableComparator(SdfmHandles[%(instance)d]);
      ]] % {
        instance = self.instance,
      }
    end

    if self.opt_compHigh or self.opt_compLow then
      c.PostInitCode:append([[
        PLX_SDFM_setCompFilterType(SdfmHandles[%(instance)d], %(m_filterTypeStr)s);
        PLX_SDFM_configCompOSR(SdfmHandles[%(instance)d], %(osr)d);

        %(comparatorHighConfigCode)s
        %(comparatorLowConfigCode)s

        %(digitalFilterConfigCode)s

        %(comparatorEnableCode)s
      ]] % {
        m_filterTypeStr = self.cbx_compFilterType.asString(),
        osr = self.reg_compOSR,
        comparatorHighConfigCode = comparatorHighConfigCode,
        comparatorLowConfigCode = comparatorLowConfigCode,
        digitalFilterConfigCode = digitalFilterConfigCode,
        comparatorEnableCode = comparatorEnableCode,
        instance = self.instance,
      })
    end

    -- need a list of active units to turn on at the end of the "finalize" step
    local unit = self.sdUnitAsInt
    if not activeUnits[unit] then
      activeUnits[unit] = {}
    end
    if self.clockSharing then
      activeUnits[unit].clockSharing = true
    end

    if self.cbx_dataFilterNumber.equals('SDFM_FILTER_1') then
      activeUnits[unit].channel1Active = true
    end
  end

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

    c.Include:append('plx_sdfm.h')
    c.Include:append('sdfm.h')
    c.Include:append('sysctl.h')

    c.Declarations:append([[
      PLX_SDFM_Handle_t SdfmHandles[%(numSdfmInstances)d];
      PLX_SDFM_Obj_t SdfmObj[%(numSdfmInstances)d];

      int16_t PLXHAL_SDFM_getFilterData16(uint16_t aHandle){
        return PLX_SDFM_getFilterData16(SdfmHandles[aHandle]);
      }

      int32_t PLXHAL_SDFM_getFilterData32(uint16_t aHandle){
        return PLX_SDFM_getFilterData32(SdfmHandles[aHandle]);
      }
    ]] % {numSdfmInstances = static[self.cpu].numInstances})

    c.PreInitCode:append([[
    for (int i = 0; i < %(numSdfmInstances)d; i++) {
      SdfmHandles[i] = PLX_SDFM_init(&SdfmObj[i], sizeof(SdfmObj[i]));
    }
    ]] % {numSdfmInstances = static[self.cpu].numInstances})

    -- used to keep track of shared SDFM resources across channels
    local activeUnits = {}

    for _, bid in ipairs(static[self.cpu].instances) do
      local sdfm = globals.instances[bid]
      if sdfm:getCpu() == self.cpu then
        sdfm:finalizeThis(c, activeUnits)
      end
    end

    for unit, entry in pairs(activeUnits) do
      if entry.clockSharing and not entry.channel1Active then
        U.error(
          'SDFM: Cannot share channel 1 clock without channel 1 present in the model.')
      end

      c.PreInitCode:append(
        'SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_SD%(unit)d);' %
        {unit = unit})

      if (self:targetMatches({'28004x', '2838x'})) then
        c.PostInitCode:append('SDFM_enableMasterFilter(SDFM%(unit)d_BASE);' %
          {unit = unit})
      else
        c.PostInitCode:append('SDFM_enableMainFilter(SDFM%(unit)d_BASE);' %
          {unit = unit})
      end
    end

    static[self.cpu].finalized = true
  end

  return Sdfm
end

return Module
