--[[
  Copyright (c) 2021, 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 static = {}

local Module = {}

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

  function Qep:checkMaskParameters()

    self.qepModuleAsInt = U.passThrough(Block.Mask.QepModule)
    U.enforceMask(U.is3Pins, 'Gpios')
    self.gpios = {
      A = Block.Mask.Gpios[1],
      B = Block.Mask.Gpios[2],
      I = Block.Mask.Gpios[3],
    }

    self.resetOnIndex = U.enforceMask_newComboBox(
      'CounterResetMode', {'Free running', 'Index pulse'}).equals('Index pulse')

    self.prd = U.passThrough(Block.Mask.CounterMax) -- checks in MaskInit
  end

  function Qep:p_getDirectFeedthroughCode()
    local Require = ResourceList:new()
    local InitCode = U.CodeLines:new()

    table.insert(static[self.cpu].instances, self.bid)

    Require:add('QEP', self.qepModuleAsInt)

    -- pinSetString specific formatting required for pinset targets, but also used in comments.
    self.pinSetString = '[A=GPIO%(A)d, B=GPIO%(B)d, I=GPIO%(I)d]' % self.gpios

    if self:targetUsesPinsets() then
      -- older targets require hard-coded pin-sets
      -- check the user config is valid for this target
      local possiblePins = globals.target.getTargetParameters().qeps.pin_set_groups

      if not possiblePins then
        error('Qep support not configured for this "pinset" target.')
      end

      for _, gpio in U.pairsSorted(self.gpios) do
        globals.target.allocateGpio(gpio, {}, Require)
      end

      local unit_pins = possiblePins['_%d' % {self.qepModuleAsInt}]

      -- return error if desired a, b and i are not valid options
      if not U.sequenceContains(unit_pins.a, self.gpios.A)
      or not U.sequenceContains(unit_pins.b, self.gpios.B)
      or not U.sequenceContains(unit_pins.i, self.gpios.I) then
        U.error('Invalid @param:Gpios:. Pinset %s not supported for QEP%d.' %
          {self.pinSetString, self.qepModuleAsInt})
      end
    else
      -- newer targets have driverlib
      if self:targetMatches({'2806x', '2833x', '2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then 
        for _, gpio in U.pairsSorted(self.gpios) do
          globals.target.allocateGpio(gpio, {}, Require)
        end
        
        local chagpio, chbgpio, chigpio
        if self:targetMatches({'2837x'}) then
          chagpio = 'GPIO_%d_EQEP%dA' % {self.gpios.A, self.qepModuleAsInt}
          chbgpio = 'GPIO_%d_EQEP%dB' % {self.gpios.B, self.qepModuleAsInt}
          chigpio = 'GPIO_%d_EQEP%dI' % {self.gpios.I, self.qepModuleAsInt}
        else
          chagpio = 'GPIO_%d_EQEP%d_A' % {self.gpios.A, self.qepModuleAsInt}
          chbgpio = 'GPIO_%d_EQEP%d_B' % {self.gpios.B, self.qepModuleAsInt}
          chigpio = 'GPIO_%d_EQEP%d_INDEX' % {self.gpios.I, self.qepModuleAsInt}
        end
        if not globals.target.validateAlternateFunction(chagpio)
        or not globals.target.validateAlternateFunction(chbgpio)
        or not globals.target.validateAlternateFunction(chigpio) then
          U.error('Invalid @param:Gpios: configured for QEP block.')
        end
        globals.syscfg:addPeripheralEntry('qep', {
          unit = self.qepModuleAsInt,
          pins = self.gpios,
          pinconf = {chagpio, chbgpio, chigpio},
          core = globals.target.getTiCpuNumber(self.cpu),
        })
      elseif self:targetMatches({'29H85x'}) then
        for _, gpio in U.pairsSorted(self.gpios) do
          local din_obj = self:makeBlock('din', self.cpu)
          din_obj:createImplicitDin(gpio, {pullup = 'PULL_UP'}, Require)
        end

        globals.syscfg:addPeripheralEntry('input_xbar', {
          gpio = self.gpios.A,
          input = 33 + 4 * (self.qepModuleAsInt - 1),
          core = globals.target.getTiCpuNumber(self.cpu),
        })
        globals.syscfg:addPeripheralEntry('input_xbar', {
          gpio = self.gpios.B,
          input = 34 + 4 * (self.qepModuleAsInt - 1),
          core =globals.target.getTiCpuNumber(self.cpu),
        })
        globals.syscfg:addPeripheralEntry('input_xbar', {
          gpio = self.gpios.I,
          input = 35 + 4 * (self.qepModuleAsInt - 1),
          core = globals.target.getTiCpuNumber(self.cpu),
        })
        globals.syscfg:addPeripheralEntry('qep', {
          unit = self.qepModuleAsInt,
          core = globals.target.getTiCpuNumber(self.cpu),
        })
      else
        U.throwUnhandledTargetError()
      end
    end

    return {
      InitCode = InitCode,
      OutputSignal = {
        [1] = {'PLXHAL_QEP_getCounter(%d)' % {self.instance}},            -- c
        [2] = {'PLXHAL_QEP_getIndexLatchCounter(%d)' % {self.instance}},  -- ic
        [3] = {'PLXHAL_QEP_getAndCearIndexFlag(%d)' % {self.instance}},   -- i
      },
      Require = Require,
      UserData = {bid = self:getId()}
    }
  end

  function Qep:finalizeThis(c)

    local configFunctionCallCode
    if self:targetUsesDriverLib() then
      -- for driver lib, pins are configured in gpio.c
      configFunctionCallCode =
         'PLX_QEP_configure(QepHandles[%(instance)d], %(unit)d, &params);'
         % {
           instance = self.instance,
           unit = self.qepModuleAsInt,
         }
    else  -- pinsets
      configFunctionCallCode =
         'PLX_QEP_configureWithPins(QepHandles[%(instance)d], %(unit)d, %(pinA)d, %(pinB)d, %(pinI)d, &params);'
         % {
           instance = self.instance,
           unit = self.qepModuleAsInt,
           pinA = self.gpios.A,
           pinB = self.gpios.B,
           pinI = self.gpios.I,
         }
    end

    c.PreInitCode:append([[
      // configure QEP%(qep)d for pinset %(pinSetString)s
      {
        PLX_QEP_Params_t params;
        PLX_QEP_setDefaultParams(&params);
        params.QPOSMAX = %(prd)d;
        params.QEPCTL.bit.PCRM = %(resetFlag)d; // reset on %(resetComment)s
        %(configFunctionCallCode)s
      }
    ]] % {
      qep = self.qepModuleAsInt,
      pinSetString = self.pinSetString,
      prd = self.prd,
      resetFlag = self.resetOnIndex and 0 or 1,
      resetComment = self.resetOnIndex and 'index event' or 'max counter mode',
      configFunctionCallCode = configFunctionCallCode,
    })
  end

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

    c.Include:append('plx_qep.h')

    c.Declarations:append([[
      PLX_QEP_Handle_t QepHandles[%(numInst)d];
      PLX_QEP_Obj_t QepObj[%(numInst)d];

      uint32_t PLXHAL_QEP_getCounter(uint16_t aChannel){
        return PLX_QEP_getPosCnt(QepHandles[aChannel]);
      }

      bool PLXHAL_QEP_getAndCearIndexFlag(uint16_t aChannel){
        return PLX_QEP_getAndClearIndexFlag(QepHandles[aChannel]);
      }

      uint32_t PLXHAL_QEP_getIndexLatchCounter(uint16_t aChannel){
        return PLX_QEP_getPosILatchCnt(QepHandles[aChannel]);
      }
    ]] % {numInst = static[self.cpu].numInstances})

    c.PreInitCode:append([[
      PLX_QEP_sinit();
      for (int i = 0; i < %d; i++) {
        QepHandles[i] = PLX_QEP_init(&QepObj[i], sizeof(QepObj[i]));
      }
    ]] % {static[self.cpu].numInstances})

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

    static[self.cpu].finalized = true
  end

  return Qep
end

return Module
