/****************************************************************************
 * arch/arm/src/sama5/sam_irq.c
 *
 *   Copyright (C) 2013-2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <debug.h>

#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <arch/irq.h>

#include "up_arch.h"
#include "up_internal.h"

#ifdef CONFIG_SAMA5_PIO_IRQ
#  include "sam_pio.h"
#endif

#include "chip.h"
#include "mmu.h"
#include "cache.h"
#include "sctlr.h"
#include "chip/sam_aic.h"
#include "chip/sam_matrix.h"
#include "chip/sam_aximx.h"
#include "chip/sam_sfr.h"

#include "sam_irq.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private types
 ****************************************************************************/

typedef uint32_t *(*doirq_t)(int irq, uint32_t *regs);

/****************************************************************************
 * Public Data
 ****************************************************************************/

volatile uint32_t *current_regs;

/* Symbols defined via the linker script */

extern uint32_t _vector_start; /* Beginning of vector block */
extern uint32_t _vector_end;   /* End+1 of vector block */

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const uint8_t g_srctype[SCRTYPE_NTYPES] =
{
  0, 0, 1, 1, 2, 3
};

/* This is an arry of bit maps that can be used to quickly determine is the
 * peripheral identified by its PID is served by H64MX or H32MX.  Then the
 * appropriate MATRIX SPSELR register can be consulted to determine if the
 * peripheral interrupts are secured or not.
 */

#if defined(CONFIG_SAMA5_SAIC)
static const uint32_t g_h64mxpids[3] =
{
  H64MX_SPSELR0_PIDS, H64MX_SPSELR1_PIDS, H64MX_SPSELR2_PIDS
};
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: sam_dumpaic
 *
 * Description:
 *   Dump some interesting AIC registers
 *
 ****************************************************************************/

#if defined(CONFIG_DEBUG_IRQ)
static void sam_dumpaic(const char *msg, uintptr_t base, int irq)
{
  irqstate_t flags;

  flags = irqsave();
  lldbg("AIC (%s, base=%08x irq=%d):\n", msg, base, irq);

  /* Select the register set associated with this irq */

  putreg32(irq, base + SAM_AIC_SSR_OFFSET);

  /* Then dump all of the (readable) register contents */

  lldbg("  SSR: %08x  SMR: %08x  SVR: %08x  IVR: %08x\n",
        getreg32(base + SAM_AIC_SSR_OFFSET),
        getreg32(base + SAM_AIC_SMR_OFFSET),
        getreg32(base + SAM_AIC_SVR_OFFSET),
        getreg32(base + SAM_AIC_IVR_OFFSET));
  lldbg("  FVR: %08x  ISR: %08x\n",
        getreg32(base + SAM_AIC_FVR_OFFSET),
        getreg32(base + SAM_AIC_ISR_OFFSET));
  lldbg("  IPR: %08x       %08x       %08x       %08x\n",
        getreg32(base + SAM_AIC_IPR0_OFFSET),
        getreg32(base + SAM_AIC_IPR1_OFFSET),
        getreg32(base + SAM_AIC_IPR2_OFFSET),
        getreg32(base + SAM_AIC_IPR3_OFFSET));

  /* SAMA5D4 does not have the FFSR register */

#if defined(SAM_AIC_FFSR)
  lldbg("  IMR: %08x CISR: %08x  SPU: %08x FFSR: %08x\n",
        getreg32(base + SAM_AIC_IMR_OFFSET),
        getreg32(base + SAM_AIC_CISR_OFFSET),
        getreg32(base + SAM_AIC_SPU_OFFSET),
        getreg32(base + SAM_AIC_FFSR_OFFSET));
#else
  lldbg("  IMR: %08x CISR: %08x  SPU: %08x\n",
        getreg32(base + SAM_AIC_IMR_OFFSET),
        getreg32(base + SAM_AIC_CISR_OFFSET),
        getreg32(base + SAM_AIC_SPU_OFFSET));
#endif

  lldbg("  DCR: %08x WPMR: %08x WPSR: %08x\n",
        getreg32(base + SAM_AIC_DCR_OFFSET),
        getreg32(base + SAM_AIC_WPMR_OFFSET),
        getreg32(base + SAM_AIC_WPSR_OFFSET));

  irqrestore(flags);
}
#else
#  define sam_dumpaic(msg, base, irq)
#endif

/****************************************************************************
 * Name: sam_vectorsize
 *
 * Description:
 *   Return the size of the vector data
 *
 ****************************************************************************/

static inline size_t sam_vectorsize(void)
{
  uintptr_t src;
  uintptr_t end;

  src  = (uintptr_t)&_vector_start;
  end  = (uintptr_t)&_vector_end;

  return (size_t)(end - src);
}

/****************************************************************************
 * Name: sam_spurious
 *
 * Description:
 *   Spurious interrupt handler.
 *
 *   Paragraph 17.8.6, Spurious Interrupt: "The Advanced Interrupt Controller
 *   features protection against spurious interrupts. A spurious interrupt is
 *   defined as being the assertion of an interrupt source long enough for the
 *   AIC to assert the nIRQ, but no longer present when AIC_IVR is read. This
 *   is most prone to occur when:
 *
 *     o An external interrupt source is programmed in level-sensitive mode
 *       and an active level occurs for only a short time.
 *     o An internal interrupt source is programmed in level sensitive and
 *       the output signal of the corresponding embedded peripheral is
 *       activated for a short time. (As in the case for the Watchdog.)
 *     o An interrupt occurs just a few cycles before the software begins to
 *       mask it, thus resulting in a pulse on the interrupt source.
 *
 *   "The AIC detects a spurious interrupt at the time the AIC_IVR is read
 *   while no enabled interrupt source is pending. When this happens, the AIC
 *   returns the value stored by the programmer in AIC_SPU (Spurious Vector
 *   Register). The programmer must store the address of a spurious interrupt
 *   handler in AIC_SPU as part of the application, to enable an as fast as
 *   possible return to the normal execution flow. This handler writes in
 *   AIC_EOICR and performs a return from interrupt."
 *
 ****************************************************************************/

static uint32_t *sam_spurious(int irq, uint32_t *regs)
{
  /* This is probably irrelevant since true vectored interrupts are not used
   * in this implementation.  The value of AIC_IVR is ignored.
   */

#if defined(CONFIG_DEBUG_IRQ)
  lldbg("Spurious interrupt: IRQ %d\n", irq);
#endif
  return regs;
}

/****************************************************************************
 * Name: sam_fiqhandler
 *
 * Description:
 *   Default FIQ interrupt handler.
 *
 ****************************************************************************/

static uint32_t *sam_fiqhandler(int irq, uint32_t *regs)
{
  /* Dispatch the FIQ */

  return arm_doirq(SAM_IRQ_FIQ, regs);
}

/****************************************************************************
 * Name: sam_aic_issecure
 *
 * Description:
 *   Return true if the peripheral secure.
 *
 * Input Parameter:
 *   PID = IRQ number
 *
 ****************************************************************************/

#if defined(CONFIG_SAMA5_SAIC)
static bool sam_aic_issecure(uint32_t irq)
{
  uintptr_t regaddr;
  uint32_t bit;
  unsigned int regndx;

  /* Get the register index and bit mask */

  regndx = (irq >> 5);
  bit    = ((uint32_t)1 << (irq & 0x1f));

  /* Get the SPSELR register address */

  DEBUGASSERT(regndx < 3);
  if ((g_h64mxpids[regndx] & bit) != 0)
    {
      /* H64MX.  Use Matrix 0 */

      regaddr = SAM_MATRIX0_SPSELR(regndx);
    }
  else
    {
      /* H32MX.  Use Matrix 1 */

      regaddr = SAM_MATRIX1_SPSELR(regndx);
    }

  /* Return true if the bit corresponding to this IRQ is zero */

  return (getreg32(regaddr) & bit) == 0;
}
#endif

/****************************************************************************
 * Name: sam_aic_redirection
 *
 * Description:
 *   Redirect all interrupts to the AIC.  This function is only compiled if
 *   (1) the architecture supports an SAIC (CONFIG_SAMA5_HAVE_SAIC), but (2)
 *   Use of the SAIC has not been selected (!CONFIG_SAMA5_SAIC).
 *
 ****************************************************************************/

#if defined(CONFIG_SAMA5_HAVE_SAIC) && !defined(CONFIG_SAMA5_SAIC)
static void sam_aic_redirection(void)
{
  unsigned int regval;

  /* Check if interrupts are already redirected to the AIC */

  regval = getreg32(SAM_SFR_AICREDIR);
  if ((regval & SFR_AICREDIR_ENABLE) == 0)
    {
      /* Enable redirection of all interrupts to the AIC */

      regval  = getreg32(SAM_SFR_SN1);
      regval ^= SFR_AICREDIR_KEY;
      regval |= SFR_AICREDIR_ENABLE;
      putreg32(regval, SAM_SFR_AICREDIR);

#if defined(CONFIG_DEBUG_IRQ)
      /* Check if redirection was successfully enabled */

      regval = getreg32(SAM_SFR_AICREDIR);
      lldbg("Interrupts %s redirected to the AIC\n",
           (regval & SFR_AICREDIR_ENABLE) != 0 ? "ARE" : "NOT");
#endif
    }
}
#else
#  define sam_aic_redirection()
#endif

/****************************************************************************
 * Name: sam_aic_initialize
 *
 * Description:
 *   Initialize the AIC or the SAIC.
 *
 ****************************************************************************/

static void sam_aic_initialize(uintptr_t base)
{
  int i;

  /* Unprotect SMR, SVR, SPU and DCR register */

  putreg32(AIC_WPMR_WPKEY, base + SAM_AIC_WPMR_OFFSET);

  /* Configure the FIQ and the IRQs. */

  for (i = 0; i < SAM_IRQ_NINT; i++)
    {
      /* Select the interrupt registers */

      putreg32(i, base + SAM_AIC_SSR_OFFSET);

      /* Disable the interrupt */

      putreg32(AIC_IDCR_INTD, base + SAM_AIC_IDCR_OFFSET);

      /* Set the (unused) FIQ/IRQ handler */

      if (i == SAM_PID_FIQ)
        {
          putreg32((uint32_t)sam_fiqhandler, base + SAM_AIC_SVR_OFFSET);
        }
      else
        {
          putreg32((uint32_t)arm_doirq, base + SAM_AIC_SVR_OFFSET);
        }

      /* Set the default interrupt priority */

      putreg32(SAM_DEFAULT_PRIOR, base + SAM_AIC_SMR_OFFSET);

      /* Clear any pending interrupt */

      putreg32(AIC_ICCR_INTCLR, base + SAM_AIC_ICCR_OFFSET);
    }

  /* Set the (unused) spurious interrupt handler */

  putreg32((uint32_t)sam_spurious, base + SAM_AIC_SPU_OFFSET);

  /* Perform 8 interrupt acknowledgements by writing any value to the
   * EOICR register.
   */

  for (i = 0; i < 8; i++)
    {
      putreg32(AIC_EOICR_ENDIT, base + SAM_AIC_EOICR_OFFSET);
    }

  /* Restore protection and the interrupt state */

  putreg32(AIC_WPMR_WPKEY | AIC_WPMR_WPEN, base + SAM_AIC_WPMR_OFFSET);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: up_irqinitialize
 *
 * Description:
 *   This function is called by up_initialize() during the bring-up of the
 *   system.  It is the responsibility of this function to but the interrupt
 *   subsystem into the working and ready state.
 *
 ****************************************************************************/

void up_irqinitialize(void)
{
#if defined(CONFIG_SAMA5_BOOT_ISRAM) || defined(CONFIG_SAMA5_BOOT_CS0FLASH)
  size_t vectorsize;
#endif

  /* The following operations need to be atomic, but since this function is
   * called early in the initialization sequence, we expect to have exclusive
   * access to the AIC.
   */

  /* Colorize the interrupt stack for debug purposes */

#if defined(CONFIG_STACK_COLORATION) && CONFIG_ARCH_INTERRUPTSTACK > 3
  {
    size_t intstack_size = (CONFIG_ARCH_INTERRUPTSTACK & ~3);
    up_stack_color((FAR void *)((uintptr_t)&g_intstackbase - intstack_size),
                   intstack_size);
  }
#endif

  /* Redirect all interrupts to the AIC if so configured */

  sam_aic_redirection();

  /* Initialize the Advanced Interrupt Controller (AIC) */

  sam_aic_initialize(SAM_AIC_VBASE);

#if defined(CONFIG_SAMA5_SAIC)
  /* Initialize the Secure Advanced Interrupt Controller (SAIC) */

  sam_aic_initialize(SAM_SAIC_VBASE);

#endif

#if defined(CONFIG_ARCH_LOWVECTORS)
  /* If CONFIG_ARCH_LOWVECTORS is defined, then the vectors located at the
   * beginning of the .text region must appear at address at the address
   * specified in the VBAR.  There are three ways to accomplish this:
   *
   *   1. By explicitly mapping the beginning of .text region with a page
   *      table entry so that the virtual address zero maps to the beginning
   *      of the .text region.  VBAR == 0x0000:0000.
   *
   *   2. A second way is to map the use the AXI MATRIX remap register to
   *      map physical address zero to the beginning of the text region,
   *      either internal SRAM or EBI CS 0.  Then we can set an identity
   *      mapping to map the boot region at 0x0000:0000 to virtual address
   *      0x0000:00000.   VBAR == 0x0000:0000.
   *
   *      This method is used when booting from ISRAM or NOR FLASH.  In
   *      that case, vectors must lie at the beginning of NOFR FLASH.
   *
   *   3. Set the Cortex-A5 VBAR register so that the vector table address
   *      is moved to a location other than 0x0000:0000.
   *
   *      This is the method used when booting from SDRAM.
   *
   * - When executing from NOR FLASH, the first level bootloader is supposed
   *   to provide the AXI MATRIX mapping for us at boot time base on the state
   *   of the BMS pin.  However, I have found that in the test environments
   *   that I use, I cannot always be assured of that physical address mapping.
   *
   * - If we are executing out of ISRAM, then the SAMA5 primary bootloader
   *   probably copied us into ISRAM and set the AXI REMAP bit for us.
   *
   * - If we are executing from external SDRAM, then a secondary bootloader
   *   must have loaded us into SDRAM.  In this case, simply set the VBAR
   *   register to the address of the vector table (not necessary at the
   *   beginning or SDRAM).
   */

#if defined(CONFIG_SAMA5_BOOT_ISRAM) || defined(CONFIG_SAMA5_BOOT_CS0FLASH)
  /* Set the vector base address register to 0x0000:0000 */

  cp15_wrvbar(0);

#if 0 /* Disabled on reset */
  /* Disable MATRIX write protection */

  putreg32(MATRIX_WPMR_WPKEY, SAM_MATRIX_WPMR);
#endif

  /* Set remap state 0 if we are running from internal SRAM or from SDRAM.
   * If we booted into NOR FLASH, then the first level bootloader should
   * have already provided this mapping for us.
   *
   * This is done late in the boot sequence.  Any exceptions taken before
   * this point in time will be handled by the ROM code, not by the NuttX
   * interrupt since which was, up to this point, uninitialized.
   *
   *   Boot state:    ROM is seen at address 0x00000000
   *   Remap State 0: SRAM is seen at address 0x00000000 (through AHB slave
   *                  interface) instead of ROM.
   *   Remap State 1: HEBI is seen at address 0x00000000 (through AHB slave
   *                  interface) instead of ROM for external boot.
   *
   * Here we are assuming that vectors reside in the lower end of ISRAM.
   * Hmmm... this probably does not matter since we will map a page to
   * address 0x0000:0000 in that case anyway.
   */

#ifdef ATSAMA5D3
  putreg32(MATRIX_MRCR_RCB0, SAM_MATRIX_MRCR);   /* Enable Cortex-A5 remap */
#endif

#if defined(CONFIG_SAMA5_BOOT_ISRAM)
  putreg32(AXIMX_REMAP_REMAP0, SAM_AXIMX_REMAP); /* Remap SRAM */
#elif defined(ATSAMA5D3) /* && defined(CONFIG_SAMA5_BOOT_CS0FLASH) */
  putreg32(AXIMX_REMAP_REMAP1, SAM_AXIMX_REMAP); /* Remap NOR FLASH on CS0 */
#endif

  /* Make sure that there is no trace of any previous mapping (here we
   * that the L2 cache has not yet been enabled.
   */

  vectorsize = sam_vectorsize();
  cp15_invalidate_icache();
  cp15_invalidate_dcache(0, vectorsize);
  mmu_invalidate_region(0, vectorsize);

#if 0 /* Disabled on reset */
  /* Restore MATRIX write protection */

  putreg32(MATRIX_WPMR_WPKEY | MATRIX_WPMR_WPEN, SAM_MATRIX_WPMR);
#endif

#elif defined(CONFIG_SAMA5_BOOT_SDRAM)
  /* Set the VBAR register to the address of the vector table in SDRAM */

  DEBUGASSERT((((uintptr_t)&_vector_start) & ~VBAR_MASK) == 0);
  cp15_wrvbar((uint32_t)&_vector_start);

#endif /* CONFIG_SAMA5_BOOT_ISRAM || CONFIG_SAMA5_BOOT_CS0FLASH */
#endif /* CONFIG_ARCH_LOWVECTORS */

  /* currents_regs is non-NULL only while processing an interrupt */

  current_regs = NULL;

#ifndef CONFIG_SUPPRESS_INTERRUPTS
  /* Initialize logic to support a second level of interrupt decoding for
   * PIO pins.
   */

#ifdef CONFIG_SAMA5_PIO_IRQ
  sam_pioirqinitialize();
#endif

  /* And finally, enable interrupts */

  (void)irqenable();
#endif
}

/****************************************************************************
 * Name: arm_decodeirq, arm_decodefiq (and sam_decodeirq helper).
 *
 * Description:
 *   This function is called from the IRQ vector handler in arm_vectors.S.
 *   At this point, the interrupt has been taken and the registers have
 *   been saved on the stack.  This function simply needs to determine the
 *   the irq number of the interrupt and then to call arm_doirq to dispatch
 *   the interrupt.
 *
 *  Input parameters:
 *   regs - A pointer to the register save area on the stack.
 *
 ****************************************************************************/

static uint32_t *sam_decodeirq(uintptr_t base, uint32_t *regs)
{
  uint32_t irqid;
  uint32_t ivr;

  /* Paragraph 17.8.5 Protect Mode: "The Protect Mode permits reading the
   *   Interrupt Vector Register without performing the associated automatic
   *   operations. ... Writing PROT in AIC_DCR (Debug Control Register) at 0x1
   *   enables the Protect Mode.
   *
   *  "When the Protect Mode is enabled, the AIC performs interrupt stacking
   *    only when a write access is performed on the AIC_IVR. Therefore, the
   *    Interrupt Service Routines must write (arbitrary data) to the AIC_IVR
   *    just after reading it. The new context of the AIC, including the value
   *    of the Interrupt Status Register (AIC_ISR), is updated with the current
   *    interrupt only when AIC_IVR is written. ..."
   *
   *  "To summarize, in normal operating mode, the read of AIC_IVR performs the
   *   following operations within the AIC:
   *
   *   1. Calculates active interrupt (higher than current or spurious).
   *   2. Determines and returns the vector of the active interrupt.
   *   3. Memorizes the interrupt.
   *   4. Pushes the current priority level onto the internal stack.
   *   5. Acknowledges the interrupt.
   *
   * "However, while the Protect Mode is activated, only operations 1 to 3 are
   *  performed when AIC_IVR is read.  Operations 4 and 5 are only performed by
   *  the AIC when AIC_IVR is written.
   *
   * "Software that has been written and debugged using the Protect Mode runs
   *  correctly in Normal Mode without modification. However, in Normal Mode the
   *  AIC_IVR write has no effect and can be removed to optimize the code.
   */

  /* Write in the IVR to support Protect Mode */

  ivr = getreg32(base + SAM_AIC_IVR_OFFSET);
  putreg32(ivr, base + SAM_AIC_IVR_OFFSET);

  /* Get the IRQ number from the interrupt status register.  NOTE that the
   * IRQ number is the same is the peripheral ID (PID).
   */

  irqid = getreg32(base + SAM_AIC_ISR_OFFSET) & AIC_ISR_MASK;

  /* Dispatch the interrupt */

  regs = ((doirq_t)ivr)((int)irqid, regs);

  /* Acknowledge interrupt */

  putreg32(AIC_EOICR_ENDIT, base + SAM_AIC_EOICR_OFFSET);
  return regs;
}

/* This is the entry point from the ARM IRQ vector handler */

uint32_t *arm_decodeirq(uint32_t *regs)
{
  return sam_decodeirq(SAM_AIC_VBASE, regs);
}

#if defined(CONFIG_SAMA5_SAIC)
/* This is the entry point from the ARM FIQ vector handler */

uint32_t *arm_decodefiq(FAR uint32_t *regs)
{
  uint32_t *ret;

  /* In order to distinguish a FIQ from a true secure interrupt we need to
   * check the state of the FIQ line in the SAIC_CISR register.
   */

  if ((getreg32(SAM_SAIC_CISR) & AIC_CISR_NFIQ) != 0)
    {
      /* Handle the FIQ */

      ret = arm_doirq(SAM_IRQ_FIQ, regs);

      /* Acknowledge interrupt */

      putreg32(AIC_EOICR_ENDIT, SAM_SAIC_EOICR);
    }
  else
    {
      /* Handle the IRQ */

      ret = sam_decodeirq(SAM_SAIC_VBASE, regs);
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: up_disable_irq (and sam_disable_irq helper)
 *
 * Description:
 *   Disable the IRQ specified by 'irq'
 *
 ****************************************************************************/

static void sam_disable_irq(uintptr_t base, int irq)
{
  irqstate_t flags;

  if (irq < SAM_IRQ_NINT)
    {
      /* These operations must be atomic */

      flags = irqsave();

      /* Select the register set associated with this irq */

      putreg32(irq, base + SAM_AIC_SSR_OFFSET);

      /* Disable the interrupt */

      putreg32(AIC_IDCR_INTD, base + SAM_AIC_IDCR_OFFSET);
      sam_dumpaic("disable", base, irq);
      irqrestore(flags);
    }
#ifdef CONFIG_SAMA5_PIO_IRQ
  else
    {
      /* Maybe it is a (derived) PIO IRQ */

      sam_pioirqdisable(irq);
    }
#endif
}

void up_disable_irq(int irq)
{
#if defined(CONFIG_SAMA5_SAIC)
  if (sam_aic_issecure(irq))
    {
      sam_disable_irq(SAM_SAIC_VBASE, irq);
    }
  else
#endif
    {
      sam_disable_irq(SAM_AIC_VBASE, irq);
    }
}

/****************************************************************************
 * Name: up_enable_irq (and sam_enable_irq helper)
 *
 * Description:
 *   Enable the IRQ specified by 'irq'
 *
 ****************************************************************************/

static void sam_enable_irq(uintptr_t base, int irq)
{
  irqstate_t flags;

  if (irq < SAM_IRQ_NINT)
    {
      /* These operations must be atomic */

      flags = irqsave();

      /* Select the register set associated with this irq */

      putreg32(irq, base + SAM_AIC_SSR_OFFSET);

      /* Enable the interrupt */

      putreg32(AIC_IECR_INTEN, base + SAM_AIC_IECR_OFFSET);
      sam_dumpaic("enable", base, irq);
      irqrestore(flags);
    }
#ifdef CONFIG_SAMA5_PIO_IRQ
  else
    {
      /* Maybe it is a (derived) PIO IRQ */

      sam_pioirqenable(irq);
    }
#endif
}

void up_enable_irq(int irq)
{
#if defined(CONFIG_SAMA5_SAIC)
  if (sam_aic_issecure(irq))
    {
      sam_enable_irq(SAM_SAIC_VBASE, irq);
    }
  else
#endif
    {
      sam_enable_irq(SAM_AIC_VBASE, irq);
    }
}

/****************************************************************************
 * Name: up_prioritize_irq (and sam_prioritize_irq helper)
 *
 * Description:
 *   Set the priority of an IRQ.
 *
 *   Since this API is not supported on all architectures, it should be
 *   avoided in common implementations where possible.
 *
 ****************************************************************************/

#ifdef CONFIG_ARCH_IRQPRIO
static int sam_prioritize_irq(uint32_t base, int irq, int priority)
{
  irqstate_t flags;
  uint32_t regval;

  DEBUGASSERT(irq < SAM_IRQ_NINT && (unsigned)priority <= AIC_SMR_PRIOR_MASK);
  if (irq < SAM_IRQ_NINT)
    {
      /* These operations must be atomic */

      flags = irqsave();

      /* Select the register set associated with this irq */

      putreg32(irq, base + SAM_AIC_SSR_OFFSET);

      /* Unprotect and write the SMR register */

      putreg32(AIC_WPMR_WPKEY, base + SAM_AIC_WPMR_OFFSET);

      /* Set the new priority, preserving the current srctype */

      regval  = getreg32(base + SAM_AIC_SMR_OFFSET);
      regval &= ~AIC_SMR_PRIOR_MASK;
      regval |= (uint32_t)priority << AIC_SMR_PRIOR_SHIFT;
      putreg32(regval, base + SAM_AIC_SMR_OFFSET);

      /* Restore protection and the interrupt state */

      putreg32(AIC_WPMR_WPKEY | AIC_WPMR_WPEN, base + SAM_AIC_WPMR_OFFSET);
      sam_dumpaic("prioritize", base, irq);
      irqrestore(flags);
    }

  return OK;
}

int up_prioritize_irq(int irq, int priority)
{
#if defined(CONFIG_SAMA5_SAIC)
  if (sam_aic_issecure(irq))
    {
      sam_prioritize_irq(SAM_SAIC_VBASE, irq, priority);
    }
  else
#endif
    {
      sam_prioritize_irq(SAM_AIC_VBASE, irq, priority);
    }
}
#endif

/****************************************************************************
 * Name: sam_irq_srctype (and _sam_irq_srctype helper)
 *
 * Description:
 *   irq     - Identifies the IRQ source to be configured
 *   srctype - IRQ source configuration
 *
 ****************************************************************************/

static void _sam_irq_srctype(uintptr_t base, int irq,
                             enum sam_srctype_e srctype)
{
  irqstate_t flags;
  uint32_t regval;

  DEBUGASSERT(irq < SAM_IRQ_NINT && (unsigned)srctype < SCRTYPE_NTYPES);

  /* These operations must be atomic */

  flags = irqsave();

  /* Select the register set associated with this irq */

  putreg32(irq, base + SAM_AIC_SSR_OFFSET);

  /* Unprotect and write the SMR register */

  putreg32(AIC_WPMR_WPKEY, base + SAM_AIC_WPMR_OFFSET);

  /* Set the new srctype, preserving the current priority */

  regval  = getreg32(base + SAM_AIC_SMR_OFFSET);
  regval &= ~AIC_SMR_SRCTYPE_MASK;
  regval |= (uint32_t)g_srctype[srctype] << AIC_SMR_SRCTYPE_SHIFT;
  putreg32(regval, base + SAM_AIC_SMR_OFFSET);

  /* Restore protection and the interrupt state */

  putreg32(AIC_WPMR_WPKEY | AIC_WPMR_WPEN, base + SAM_AIC_WPMR_OFFSET);
  sam_dumpaic("srctype", base, irq);
  irqrestore(flags);
}

void sam_irq_srctype(int irq, enum sam_srctype_e srctype)
{
#if defined(CONFIG_SAMA5_SAIC)
  if (sam_aic_issecure(irq))
    {
      _sam_irq_srctype(SAM_SAIC_VBASE, irq, srctype);
    }
  else
#endif
    {
      _sam_irq_srctype(SAM_AIC_VBASE, irq, srctype);
    }
}
