/****************************************************************************
 * arch/mips/src/pic32mz/pic32mz-gpio.c
 *
 *   Copyright (C) 2015 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 <assert.h>

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

#include "up_arch.h"
#include "up_internal.h"
#include "chip/pic32mz-ioport.h"
#include "pic32mz-gpio.h"

#ifdef CONFIG_PIC32MZ_GPIOIRQ

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

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static inline unsigned int pic32mz_ioport(pinset_t pinset);
static inline unsigned int pic32mz_pin(pinset_t pinset);
static inline bool pic32mz_input(pinset_t pinset);
static inline bool pic32mz_interrupt(pinset_t pinset);
static inline bool pic32mz_pullup(pinset_t pinset);
static inline bool pic32mz_pulldown(pinset_t pinset);
static int pic32mz_cninterrupt(int irq, FAR void *context);

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

struct ioport_level2_s
{
  xcpt_t handler[16];
};

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

/* Arrays of second level interrupt handlers for each pin of each enabled
 * I/O port.
 */

#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTA
static struct ioport_level2_s g_ioporta_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTB
static struct ioport_level2_s g_ioportb_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTC
static struct ioport_level2_s g_ioportc_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTD
static struct ioport_level2_s g_ioportd_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTE
static struct ioport_level2_s g_ioporte_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTF
static struct ioport_level2_s g_ioportf_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTG
static struct ioport_level2_s g_ioportg_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTH
static struct ioport_level2_s g_ioporth_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTJ
static struct ioport_level2_s g_ioportj_cnisrs;
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTK
static struct ioport_level2_s g_ioportk_cnisrs;
#endif

/* Look-up of port to interrupt handler array */

static struct ioport_level2_s * const g_level2_handlers[CHIP_NPORTS] =
{
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTA
  [PIC32MZ_IOPORTA] = &g_ioporta_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTB
  [PIC32MZ_IOPORTB] = &g_ioportb_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTC
  [PIC32MZ_IOPORTC] = &g_ioportc_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTD
  [PIC32MZ_IOPORTD] = &g_ioportd_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTE
  [PIC32MZ_IOPORTE] = &g_ioporte_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTF
  [PIC32MZ_IOPORTF] = &g_ioportf_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTG
  [PIC32MZ_IOPORTG] = &g_ioportg_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTH
  [PIC32MZ_IOPORTH] = &g_ioporth_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTJ
  [PIC32MZ_IOPORTJ] = &g_ioportj_cnisrs,
#endif
#ifdef CONFIG_PIC32MZ_GPIOIRQ_PORTK
  [PIC32MZ_IOPORTK] = &g_ioportk_cnisrs,
#endif
};

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

/****************************************************************************
 * Name: Inline PIN set field extractors
 ****************************************************************************/

static inline bool pic32mz_input(pinset_t pinset)
{
  return ((pinset & GPIO_MODE_MASK) != GPIO_INPUT);
}

static inline bool pic32mz_interrupt(pinset_t pinset)
{
  return ((pinset & GPIO_INTERRUPT) != 0);
}

static inline bool pic32mz_pullup(pinset_t pinset)
{
  return ((pinset & GPIO_PULLUP) != 0);
}

static inline bool pic32mz_pulldown(pinset_t pinset)
{
  return ((pinset & GPIO_PULLDOWN) != 0);
}

static inline unsigned int pic32mz_ioport(pinset_t pinset)
{
  return ((pinset & GPIO_PORT_MASK) >> GPIO_PORT_SHIFT);
}

static inline unsigned int pic32mz_pin(pinset_t pinset)
{
  return ((pinset & GPIO_PIN_MASK) >> GPIO_PIN_SHIFT);
}

/****************************************************************************
 * Name: pic32mz_cninterrupt
 *
 * Description:
 *  Common change notification interrupt handler.
 *
 ****************************************************************************/

static int pic32mz_cninterrupt(int irq, FAR void *context)
{
  struct ioport_level2_s *handlers;
  xcpt_t handler;
  uintptr_t base;
  uint32_t cnstat;
  uint32_t cnen;
  uint32_t pending;
  uint32_t regval;
  int ioport;
  int status;
  int ret = OK;
  int i;

  /* Get the IO port index from the IRQ number.  This, of course,
   * assumes that the irq numbers are consecutive beginning with
   * IOPORTA.
   */

  ioport   = irq - PIC32MZ_IRQ_PORTA;
  DEBUGASSERT(ioport >= 0 && ioport < CHIP_NPORTS);

  /* If we got this interrupt, then there must also be an array
   * of second level handlers (and a base address) for the IOPORT.
   */

  handlers = g_level2_handlers[ioport];
  base     = g_gpiobase[ioport];
  DEBUGASSERT(handlers && base);

  if (handlers && base)
    {
      /* Get the interrupt status associated with this interrupt */

      cnstat  = getreg32(base + PIC32MZ_IOPORT_CNSTAT_OFFSET);
      cnen    = getreg32(base + PIC32MZ_IOPORT_CNSTAT_OFFSET);
      pending = cnstat & cnen;

      /* Hmmm.. the data sheet implies that the status will pend
       * until the corresponding PORTx registers is read?  Clear
       * pending status.
       */

      regval = getreg32(base + PIC32MZ_IOPORT_PORT_OFFSET);
      UNUSED(regval);

      /* Call all attached handlers for each pending interrupt */

      for (i = 0; i < 16; i++)
        {
          /* Is this interrupt pending */

          if ((pending & (1 << IOPORT_CNSTAT(i))) != 0)
            {
              /* Yes.. Has the user attached a handler? */

              handler = handlers->handler[i];
              if (handler)
                {
                  /* Yes.. call the attached handler */

                  status = handler(irq, context);

                  /* Keep track of the status of the last handler that
                   * failed.
                   */

                  if (status < 0)
                    {
                      ret = status;
                    }
                }
            }
        }
    }

  /* Clear the pending interrupt */

  up_clrpend_irq(irq);
  return ret;
}

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

/****************************************************************************
 * Name: pic32mz_gpioirqinitialize
 *
 * Description:
 *   Initialize logic to support a GPIO change notification interrupts.
 *   This function is called internally by the system on power up and should
 *   not be called again.
 *
 ****************************************************************************/

void pic32mz_gpioirqinitialize(void)
{
  uintptr_t base;
  int ret;
  int i;

  /* Perform initialization for each IO port that has interrupt support
   * enabled.  We can tell this because there will be an array of second
   * level handlers for each enabled IO port.
   */

  for (i = 0; i < CHIP_NPORTS; i++)
    {
      /* Get the base address of this IO port peripheral */

      base = g_gpiobase[i];
      DEBUGASSERT(base);

      /* Reset all registers and disable the CN module */

      putreg32(IOPORT_CNEN_ALL, base + PIC32MZ_IOPORT_CNENCLR_OFFSET);
      putreg32(IOPORT_CNPU_ALL, base + PIC32MZ_IOPORT_CNPUCLR_OFFSET);
      putreg32(IOPORT_CNPD_ALL, base + PIC32MZ_IOPORT_CNPDCLR_OFFSET);
      putreg32(0,               base + PIC32MZ_IOPORT_CNCON_OFFSET);

      /* Is interrupt support selected for this IO port? */

      if (g_level2_handlers[i] != NULL)
        {
          /* Yes.. Attach the common change notice interrupt handler
           * to the IO port interrupt.  Notice that this assumes that
           * each IRQ number is consecutive beginning with IOPORTA.
           */

          ret = irq_attach(PIC32MZ_IRQ_PORTA + i, pic32mz_cninterrupt);
          DEBUGASSERT(ret == OK);
          UNUSED(ret);

          /* Enable the CN module.  NOTE that the CN module is active when
           * in sleep mode.
           */

          putreg32(IOPORT_CNCON_ON, base + PIC32MZ_IOPORT_CNCON_OFFSET);

          /* And enable the GPIO interrupt.  Same assumption as above. */

          up_enable_irq(PIC32MZ_IRQ_PORTA + i);
        }
    }
}

/****************************************************************************
 * Name: pic32mz_gpioattach
 *
 * Description:
 *   Attach an interrupt service routine to a GPIO interrupt.  This will
 *   also reconfigure the pin as an interrupting input.  The change
 *   notification number is associated with all interrupt-capabile GPIO pins.
 *   The association could, however, differ from part to part and must be
 *   provided by the caller.
 *
 *   When an interrupt occurs, it is due to a change on the GPIO input pin.
 *   In that case, all attached handlers will be called.  Each handler must
 *   maintain state and determine if the underlying GPIO input value changed.
 *
 * Parameters:
 *  - pinset:  GPIO pin configuration
 *  - pin:      The change notification number associated with the pin.
 *  - handler: Interrupt handler (may be NULL to detach)
 *
 * Returns:
 *  The previous value of the interrupt handler function pointer.  This
 *  value may, for example, be used to restore the previous handler when
 *  multiple handlers are used.
 *
 ****************************************************************************/

xcpt_t pic32mz_gpioattach(pinset_t pinset, xcpt_t handler)
{
  struct ioport_level2_s *handlers;
  xcpt_t oldhandler = NULL;
  irqstate_t flags;
  uintptr_t base;
  int ioport;
  int pin;

  DEBUGASSERT(pin < IOPORT_NUMCN);

  /* First verify that the pinset is configured as an interrupting input */

  if (pic32mz_input(pinset) && pic32mz_interrupt(pinset))
    {
      /* Get the ioport index and pin number from the pinset */

      ioport = pic32mz_ioport(pinset);
      pin    = pic32mz_pin(pinset);
      DEBUGASSERT(ioport >= 0 && ioport < CHIP_NPORTS);

      /* Get the register base address for this port */

      base = g_gpiobase[ioport];
      DEBUGASSERT(base);

      /* If this IO port has been properly configured for interrupts, then
       * there should be an array of second level interrupt handlers
       * allocated for it.
       */

      handlers = g_level2_handlers[ioport];
      DEBUGASSERT(handlers);
      if (handlers)
        {
          /* Get the previously attached handler as the return value */

          flags = irqsave();
          oldhandler = handlers->handler[pin];

          /* Are we attaching or detaching? */

          if (handler != NULL)
            {
              /* Attaching... Make sure that the GPIO is properly configured
               * as an input
               */

              pic32mz_configgpio(pinset);

              /* Pull-up requested? */

              if (pic32mz_pullup(pinset))
                {
                  putreg32(1 << pin, base + PIC32MZ_IOPORT_CNPUSET_OFFSET);
                }
              else
                {
                  putreg32(1 << pin, base + PIC32MZ_IOPORT_CNPUCLR_OFFSET);
                }

              /* Pull-down requested? */

              if (pic32mz_pulldown(pinset))
                {
                  putreg32(1 << pin, base + PIC32MZ_IOPORT_CNPDSET_OFFSET);
                }
              else
                {
                  putreg32(1 << pin, base + PIC32MZ_IOPORT_CNPDCLR_OFFSET);
                }
            }
          else
            {
              /* Disable the pull-up/downs (just so things look better in
               * the debugger).
               */

               putreg32(1 << pin, base + PIC32MZ_IOPORT_CNPUCLR_OFFSET);
               putreg32(1 << pin, base + PIC32MZ_IOPORT_CNPDCLR_OFFSET);
            }

          /* Whether attaching or detaching, the next state of the interrupt
           * should be disabled.
           */

          putreg32(1 << pin, base + PIC32MZ_IOPORT_CNENCLR_OFFSET);

          /* Set the new handler (perhaps NULLifying the current handler) */

          handlers->handler[pin] = handler;
          irqrestore(flags);
        }
    }

  return oldhandler;
}

/****************************************************************************
 * Name: pic32mz_gpioirqenable
 *
 * Description:
 *   Enable the interrupt for specified GPIO IRQ
 *
 ****************************************************************************/

void pic32mz_gpioirqenable(pinset_t pinset)
{
  uintptr_t base;
  int ioport;
  int pin;

  /* Get the ioport index and pin number from the pinset */

  ioport = pic32mz_ioport(pinset);
  pin    = pic32mz_pin(pinset);
  DEBUGASSERT(ioport >= 0 && ioport < CHIP_NPORTS);

  /* Get the register base address for this port */

  base = g_gpiobase[ioport];
  DEBUGASSERT(base);
  if (base)
    {
      /* And enable the interrupt.  NOTE that we don't actually check if
       * interrupts are configured for this IO port.  If not, this operation
       * should do nothing.
       */

      putreg32(1 << pin, base + PIC32MZ_IOPORT_CNENSET_OFFSET);
    }
}

/****************************************************************************
 * Name: pic32mz_gpioirqdisable
 *
 * Description:
 *   Disable the interrupt for specified GPIO IRQ
 *
 ****************************************************************************/

void pic32mz_gpioirqdisable(pinset_t pinset)
{
  uintptr_t base;
  int ioport;
  int pin;

  /* Get the ioport index and pin number from the pinset */

  ioport = pic32mz_ioport(pinset);
  pin    = pic32mz_pin(pinset);
  DEBUGASSERT(ioport >= 0 && ioport < CHIP_NPORTS);

  /* Get the register base address for this port */

  base = g_gpiobase[ioport];
  DEBUGASSERT(base);
  if (base)
    {
      /* And disable the interrupt.  NOTE that we don't actually check if
       * interrupts are configured for this IO port.  If not, this operation
       * should do nothing.
       */

      putreg32(1 << pin, base + PIC32MZ_IOPORT_CNENCLR_OFFSET);
    }
}

#endif /* CONFIG_PIC32MZ_GPIOIRQ */
