--[[
  Copyright (c) 2025 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 VREF = require('blocks.vref_config')

local static = {
  numInstances = 0,
  finalized = false,
}

local Module = {}

function Module.getBlock(globals, cpu)
  local AnalogRef = require('common.block').getBlock(globals, cpu)
  assert((cpu == 0), 'AnalogRef module should always be associated with CPU 0.')

  AnalogRef.instance = static.numInstances
  static.numInstances = static.numInstances + 1

  if static.numInstances > 1 then
    error(
      'There should be only one (implicit) instance of the AnalogRef block.')
  end

  function AnalogRef:createImplicit(req)
  end

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

  function AnalogRef:finalize(c)
    if static.finalized then
      return
    end

    if  not self:getBlockInstance('adc')
    and not self:getBlockInstance('dac') then
      -- no need to configure analog subsystem
      return
    end

    if self:targetMatches({'2833x'}) then
      -- reference configuration and trimming hard-coded in ain_2833x.c
    elseif self:targetMatches({'2806x'}) then
      -- reference configuration and trimming handled by ain_2806x.c
    elseif self:targetMatches({'2837x', '2838x'}) then
      -- this was formerly handled in ain_type4
      -- these devices do not have internal voltage references
      -- NOTE: AdcSetMode call in ain_type4 will set the trims
    else
      local vrefMap = globals.target.getTargetParameters().adcs.vrefMap
      local vrefMapForConfiguration = {}
      for _, adcUnitAsChar in pairs(vrefMap) do
        vrefMapForConfiguration[adcUnitAsChar] = VREF.getAdcVref(globals,
                                                                 adcUnitAsChar)
        if self:targetMatches({'28003x', '28P55x', '28P65x'}) then
          -- for these devices a single reference configuration is sufficient
          break
        elseif self:targetMatches({'28004x', '29H85x'}) then
          -- continue looping, these targets must set all Vrefs
        else
          U.throwUnhandledTargetError()
        end
      end

      c.PreInitCode:append([[
        // setup analog voltage references
        {
      ]])
      if self:targetMatches({'28P65x'}) then
        -- for this device, we call ADC_setVREF from the TI driverlib
        -- NOTE: The AdcSetMode calls in ain_type4 set the trims
        for refAsChar, config in pairs(vrefMapForConfiguration) do
          local m_base, m_source, m_voltage
          m_base = 'ADC%s_BASE' % {refAsChar}
          if config.useExternalVref then
            m_source = 'ADC_REFERENCE_EXTERNAL'
            m_voltage = '(ADC_ReferenceVoltage)0'
          else
            m_source = 'ADC_REFERENCE_INTERNAL'
            if config.vref == 2.5 then
              m_voltage = 'ADC_REFERENCE_2_5V'
            elseif config.vref == 3.3 then
              m_voltage = 'ADC_REFERENCE_3_3V'
            else
              error('Unsupported reference voltage.')
            end
          end
          c.PreInitCode:append(
            'ADC_setVREF(%s, %s, %s);' % {m_base, m_source, m_voltage}
          )
        end
      elseif self:targetMatches({'28003x', '28004x', '28P55x'}) then
        -- for these devices, we call SetVREF from the TI "f28xx_adc.c" implementation
        -- NOTE: SetVREF also loads ADC trims
        c.Include:append('plx_ain.h')
        for refAsChar, config in pairs(vrefMapForConfiguration) do
          local m_base, m_source, m_voltage
          m_base = 'ADC_ADC%s' % {refAsChar}
          if config.useExternalVref then
            m_source = 'ADC_EXTERNAL'
            m_voltage = '0' -- Dummy value, not really used in this case.
          else
            m_source = 'ADC_INTERNAL'
            if config.vref == 2.5 then
              m_voltage = 'ADC_VREF2P5'
            elseif config.vref == 3.3 then
              m_voltage = 'ADC_VREF3P3'
            else
              error('Unsupported reference voltage.')
            end
          end
          c.PreInitCode:append(
            'SetVREF(%s, %s, %s);' % {m_base, m_source, m_voltage})
        end
      elseif self:targetMatches({'29H85x'}) then
        for refAsChar, config in pairs(vrefMapForConfiguration) do
          local m_base, m_source, m_voltage
          if (refAsChar == 'A') or (refAsChar == 'B') then
            m_base = 'ASYSCTL_VREFHIAB'
          else
            m_base = 'ASYSCTL_VREFHICDE'
          end
          if config.useExternalVref then
            m_source = 'ASYSCTL_VREF_EXTERNAL'
          elseif config.vref == 2.5 then
            m_source = 'ASYSCTL_VREF_INTERNAL_2_5_V'
          elseif config.vref == 3.3 then
            m_source = 'ASYSCTL_VREF_INTERNAL_3_3_V'
          else
            error('Unsupported reference voltage.')
          end
          c.PreInitCode:append(
            'ASysCtl_setVREF(%s, %s);' % {m_base, m_source})
        end
      else
        U.throwUnhandledTargetError()
      end
      c.PreInitCode:append(
        '}')
    end
    static.finalized = true
  end

  return AnalogRef
end

return Module
