blob: 57987fb363e7c4341fabf9459c85f897848c7a91 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/cxd56xx/cxd56_gpioint.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <arch/board/board.h>
#include <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/spinlock.h>
#include "arm_internal.h"
#include "chip.h"
#include "cxd56_pinconfig.h"
#include "cxd56_gpio.h"
#include "cxd56_gpioint.h"
#include "hardware/cxd5602_topreg.h"
#ifdef CONFIG_CXD56_GPIO_IRQ
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* GPIO Interrupt Polarity Definitions */
#define GPIOINT_POLARITY_SHIFT (0)
#define GPIOINT_POLARITY_MASK (7)
#define GPIOINT_GET_POLARITY(v) (((v) & GPIOINT_POLARITY_MASK) >> GPIOINT_POLARITY_SHIFT)
#define GPIOINT_SET_POLARITY(v) (((v) << GPIOINT_POLARITY_SHIFT) & GPIOINT_POLARITY_MASK)
#define GPIOINT_IS_LEVEL(v) (GPIOINT_GET_POLARITY(v) <= GPIOINT_LEVEL_LOW)
#define GPIOINT_IS_EDGE(v) (GPIOINT_EDGE_RISE <= GPIOINT_GET_POLARITY(v))
#define GPIOINT_IS_HIGH(v) ((GPIOINT_LEVEL_HIGH == GPIOINT_GET_POLARITY(v)) || \
(GPIOINT_EDGE_RISE == GPIOINT_GET_POLARITY(v)))
#define GPIOINT_IS_LOW(v) ((GPIOINT_LEVEL_LOW == GPIOINT_GET_POLARITY(v)) || \
(GPIOINT_EDGE_FALL == GPIOINT_GET_POLARITY(v)))
/* GPIO Interrupt Noise Filter Definitions */
#define GPIOINT_NOISE_FILTER_SHIFT (3)
#define GPIOINT_NOISE_FILTER_MASK (1u << GPIOINT_NOISE_FILTER_SHIFT)
#define GPIOINT_NOISE_FILTER_ENABLED(v) (((v) & GPIOINT_NOISE_FILTER_MASK) \
== GPIOINT_NOISE_FILTER_ENABLE)
/* Use Pseudo Edge Interrupt */
#define GPIOINT_TOGGLE_MODE_SHIFT (16)
/* GPIO Interrupt Index Number Definitions */
#define MAX_SLOT (12)
#define MAX_SYS_SLOT (6)
#define INTSEL_DEFAULT_VAL (63)
#define GET_SLOT2IRQ(slot) (CXD56_IRQ_EXDEVICE_0 + (slot))
#define GET_IRQ2SLOT(irq) ((irq) - CXD56_IRQ_EXDEVICE_0)
/* PMU_WAKE_TRIG_CPUINTSELx */
#define INT_ROUTE_THROUGH (0)
#define INT_ROUTE_INVERTER (1)
#define INT_ROUTE_PMU (2)
#define INT_ROUTE_PMU_LATCH (3)
#define CXD56_INTC_ENABLE (CXD56_INTC_BASE + 0x10)
#define CXD56_INTC_INVERT (CXD56_INTC_BASE + 0x20)
/****************************************************************************
* Private Data
****************************************************************************/
static xcpt_t g_isr[MAX_SLOT];
static uint32_t g_bothedge = 0;
/****************************************************************************
* Private Functions
****************************************************************************/
/* allocate/deallocate/get slot number (SYS: 0~5, APP: 6~11) */
static int alloc_slot(int pin, bool isalloc)
{
irqstate_t flags;
int alloc = -1;
int slot;
uint8_t val;
uint32_t base = (pin < PIN_IS_CLK) ? CXD56_TOPREG_IOCSYS_INTSEL0
: CXD56_TOPREG_IOCAPP_INTSEL0;
int offset = (pin < PIN_IS_CLK) ? 1 : 56;
flags = spin_lock_irqsave(NULL);
for (slot = 0; slot < MAX_SYS_SLOT; slot++)
{
val = getreg8(base + slot);
if ((pin - offset) == val)
{
if (isalloc == false)
{
putreg8(INTSEL_DEFAULT_VAL, base + slot);
}
break; /* already used */
}
if ((-1 == alloc) && (INTSEL_DEFAULT_VAL == val))
{
alloc = slot;
}
}
if (slot == MAX_SYS_SLOT)
{
if (isalloc && (-1 != alloc))
{
slot = alloc;
putreg8(pin - offset, base + slot);
}
else
{
spin_unlock_irqrestore(NULL, flags);
return -ENXIO; /* no space */
}
}
spin_unlock_irqrestore(NULL, flags);
if (PIN_IS_CLK <= pin)
{
slot += MAX_SYS_SLOT;
}
return slot;
}
/* convert from slot to pin */
static int get_slot2pin(int slot)
{
uint32_t base = (slot < MAX_SYS_SLOT) ? CXD56_TOPREG_IOCSYS_INTSEL0
: CXD56_TOPREG_IOCAPP_INTSEL0;
int offset = 1;
if (MAX_SYS_SLOT <= slot)
{
slot -= MAX_SYS_SLOT;
offset = 56;
}
return (int)getreg8(base + slot) + offset;
}
/* convert from pin to slot number (SYS: 0~5, APP: 6~11) */
static int get_pin2slot(int pin)
{
int slot;
uint32_t base = (pin < PIN_IS_CLK) ? CXD56_TOPREG_IOCSYS_INTSEL0
: CXD56_TOPREG_IOCAPP_INTSEL0;
int offset = (pin < PIN_IS_CLK) ? 1 : 56;
for (slot = 0; slot < MAX_SYS_SLOT; slot++)
{
if ((pin - offset) == getreg8(base + slot)) /* byte access */
{
break;
}
}
if (slot == MAX_SYS_SLOT)
{
return -1;
}
if (PIN_IS_CLK <= pin)
{
slot += MAX_SYS_SLOT;
}
return slot;
}
/* convert from pin to irq number */
static int get_pin2irq(int pin)
{
int slot = get_pin2slot(pin);
if ((0 <= slot) && (slot < MAX_SLOT))
{
return GET_SLOT2IRQ(slot);
}
else
{
return -1;
}
}
/* set GPIO interrupt configuration registers */
static int set_gpioint_config(int slot, uint32_t gpiocfg)
{
uint32_t val;
uint32_t shift;
uint32_t polreg = CXD56_TOPREG_PMU_WAKE_TRIG_INTDET0;
uint32_t selreg = CXD56_TOPREG_PMU_WAKE_TRIG_CPUINTSEL0;
/* Configure the noise filter */
val = getreg32(CXD56_TOPREG_PMU_WAKE_TRIG_NOISECUTEN0);
if (GPIOINT_NOISE_FILTER_ENABLED(gpiocfg))
{
val |= (1 << (slot + 16));
}
else
{
val &= ~(1 << (slot + 16));
}
putreg32(val, CXD56_TOPREG_PMU_WAKE_TRIG_NOISECUTEN0);
/* Configure the polarity */
shift = 16 + (slot * 4);
if (32 <= shift)
{
polreg = CXD56_TOPREG_PMU_WAKE_TRIG_INTDET1;
selreg = CXD56_TOPREG_PMU_WAKE_TRIG_CPUINTSEL1;
shift -= 32;
}
val = getreg32(polreg);
val &= ~(0x7 << shift);
val |= (GPIOINT_GET_POLARITY(gpiocfg) << shift);
putreg32(val, polreg);
/* Configure the interrupt route */
val = getreg32(selreg);
val &= ~(0x7 << shift);
switch (GPIOINT_GET_POLARITY(gpiocfg))
{
case GPIOINT_LEVEL_HIGH:
if (GPIOINT_NOISE_FILTER_ENABLED(gpiocfg))
{
val |= (INT_ROUTE_PMU << shift);
}
else
{
val |= (INT_ROUTE_THROUGH << shift);
}
break;
case GPIOINT_LEVEL_LOW:
if (GPIOINT_NOISE_FILTER_ENABLED(gpiocfg))
{
val |= (INT_ROUTE_PMU << shift);
}
else
{
val |= (INT_ROUTE_INVERTER << shift);
}
break;
case GPIOINT_EDGE_RISE:
case GPIOINT_EDGE_FALL:
case GPIOINT_EDGE_BOTH:
val |= (INT_ROUTE_PMU_LATCH << shift);
break;
default:
DEBUGPANIC();
break;
}
putreg32(val, selreg);
return 0;
}
/* Invert interrupt polarity in INTC */
static void invert_irq(int irq)
{
irqstate_t flags;
uint32_t val;
flags = spin_lock_irqsave(NULL);
val = getreg32(CXD56_INTC_INVERT);
val ^= (1 << (irq - CXD56_IRQ_EXTINT));
putreg32(val, CXD56_INTC_INVERT);
spin_unlock_irqrestore(NULL, flags);
}
static bool inverted_irq(int irq)
{
uint32_t val;
val = getreg32(CXD56_INTC_INVERT);
return ((val & (1 << (irq - CXD56_IRQ_EXTINT))) != 0);
}
static bool enabled_irq(int irq)
{
uint32_t val;
val = getreg32(CXD56_INTC_ENABLE);
return ((val & (1 << (irq - CXD56_IRQ_EXTINT))) != 0);
}
static int gpioint_handler(int irq, void *context, void *arg)
{
uint32_t val;
uint32_t shift;
uint32_t polreg = CXD56_TOPREG_PMU_WAKE_TRIG_INTDET0;
int slot = GET_IRQ2SLOT(irq);
/* Invert mask of interrupt to be disable temporarily */
invert_irq(irq);
if (g_bothedge & (1 << slot))
{
g_isr[slot](irq, context, arg);
return 0;
}
/* Get the polarity */
shift = 16 + (slot * 4);
if (32 <= shift)
{
polreg = CXD56_TOPREG_PMU_WAKE_TRIG_INTDET1;
shift -= 32;
}
val = getreg32(polreg);
val = (val >> shift) & 0x7;
if (inverted_irq(irq))
{
/* Clear edge interrupt */
if (GPIOINT_IS_EDGE(val))
{
/* TBD: ignore access protection */
putreg32(1 << (slot + 16), CXD56_TOPREG_PMU_WAKE_TRIG0_CLR);
}
g_isr[slot](irq, context, arg);
}
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: cxd56_gpioint_config
*
* Description:
* Configure a GPIO pin as an GPIO pin interrupt source
*
* Input Parameters:
* pin - Pin number defined in cxd56_pinconfig.h
* gpiocfg - GPIO Interrupt Polarity and Noise Filter Configuration Value
* isr - Interrupt handler. If isr is NULL, then free an allocated handler.
* arg - Argument for the interrupt handler
*
* Returned Value:
* IRQ number on success; a negated errno value on failure.
*
* Assumptions:
* The interrupt are disabled so that read-modify-write operations
* are safe.
*
****************************************************************************/
int cxd56_gpioint_config(uint32_t pin, uint32_t gpiocfg, xcpt_t isr,
void *arg)
{
int slot;
int irq;
irqstate_t flags;
slot = alloc_slot(pin, (isr != NULL));
if (slot < 0)
{
return -ENXIO;
}
irq = GET_SLOT2IRQ(slot);
if (isr == NULL)
{
/* disable GPIO input */
cxd56_gpio_config(pin, false);
/* disable interrupt */
irq_attach(irq, NULL, NULL);
g_isr[slot] = NULL;
flags = spin_lock_irqsave(NULL);
g_bothedge &= ~(1 << slot);
spin_unlock_irqrestore(NULL, flags);
return irq;
}
/* enable GPIO input */
cxd56_gpio_config(pin, true);
/* set GPIO interrupt configuration */
if (gpiocfg & GPIOINT_TOGGLE_BOTH_MASK)
{
/* set GPIO pseudo both edge interrupt */
flags = spin_lock_irqsave(NULL);
g_bothedge |= (1 << slot);
spin_unlock_irqrestore(NULL, flags);
/* detect the change from the current signal */
if (true == cxd56_gpio_read(pin))
{
gpiocfg |= GPIOINT_SET_POLARITY(GPIOINT_LEVEL_LOW);
}
else
{
gpiocfg |= GPIOINT_SET_POLARITY(GPIOINT_LEVEL_HIGH);
}
}
set_gpioint_config(slot, gpiocfg);
if ((gpiocfg & GPIOINT_TOGGLE_MODE_MASK) || GPIOINT_IS_EDGE(gpiocfg))
{
irq_attach(irq, gpioint_handler, arg); /* call intermediate handler */
g_isr[slot] = isr;
}
else
{
irq_attach(irq, isr, arg); /* call user handler directly */
g_isr[slot] = NULL;
}
return irq;
}
/****************************************************************************
* Name: cxd56_gpioint_irq
*
* Description:
* Get a GPIO interrupt number for specified pin number
*
* Returned Value:
* IRQ number on success; a negated errno value on failure.
*
****************************************************************************/
int cxd56_gpioint_irq(uint32_t pin)
{
return get_pin2irq(pin);
}
/****************************************************************************
* Name: cxd56_gpioint_pin
*
* Description:
* Get a pin number for specified IRQ number
*
* Returned Value:
* Pin number on success; a negated errno value on failure.
*
****************************************************************************/
int cxd56_gpioint_pin(int irq)
{
int slot;
if ((irq < CXD56_IRQ_EXDEVICE_0) || (CXD56_IRQ_EXDEVICE_11 < irq))
{
return -1;
}
slot = GET_IRQ2SLOT(irq);
return get_slot2pin(slot);
}
/****************************************************************************
* Name: cxd56_gpioint_enable
*
* Description:
* Enable a GPIO interrupt for specified pin number
*
* Returned Value:
* None
*
****************************************************************************/
void cxd56_gpioint_enable(uint32_t pin)
{
int irq = get_pin2irq(pin);
if (irq > 0)
{
up_enable_irq(irq);
}
}
/****************************************************************************
* Name: cxd56_gpioint_disable
*
* Description:
* Disable a GPIO interrupt for specified pin number
*
* Returned Value:
* None
*
****************************************************************************/
void cxd56_gpioint_disable(uint32_t pin)
{
int irq = get_pin2irq(pin);
if (irq > 0)
{
up_disable_irq(irq);
}
}
/****************************************************************************
* Name: cxd56_gpioint_invert
*
* Description:
* Invert polarity of a GPIO interrupt for specified pin number
*
* Returned Value:
* None
*
****************************************************************************/
void cxd56_gpioint_invert(uint32_t pin)
{
int irq = get_pin2irq(pin);
if (irq > 0)
{
invert_irq(irq);
}
}
/****************************************************************************
* Name: cxd56_gpioint_status
*
* Description:
* Get a gpio interrupt status
*
* Returned Value:
* OK on success; A negated errno value on failure.
*
****************************************************************************/
int cxd56_gpioint_status(uint32_t pin, cxd56_gpioint_status_t *stat)
{
uint32_t val;
uint32_t shift;
uint32_t polreg = CXD56_TOPREG_PMU_WAKE_TRIG_INTDET0;
int slot;
DEBUGASSERT(stat);
/* Get IRQ number */
stat->irq = cxd56_gpioint_irq(pin);
if (stat->irq < 0)
{
return -EINVAL;
}
/* Get polarity */
slot = GET_IRQ2SLOT(stat->irq);
shift = 16 + (slot * 4);
if (32 <= shift)
{
polreg = CXD56_TOPREG_PMU_WAKE_TRIG_INTDET1;
shift -= 32;
}
val = getreg32(polreg);
stat->polarity = GPIOINT_GET_POLARITY(val >> shift);
/* Replace for pseudo edge */
if ((g_isr[slot]) && (stat->polarity == GPIOINT_LEVEL_HIGH))
{
stat->polarity = GPIOINT_EDGE_RISE;
}
if ((g_isr[slot]) && (stat->polarity == GPIOINT_LEVEL_LOW))
{
stat->polarity = GPIOINT_EDGE_FALL;
}
if ((g_isr[slot]) && (g_bothedge & (1 << slot)))
{
stat->polarity = GPIOINT_EDGE_BOTH;
}
/* Get noise filter enabled or not */
val = getreg32(CXD56_TOPREG_PMU_WAKE_TRIG_NOISECUTEN0);
stat->filter = ((val >> (slot + 16)) & 1) ? true : false;
/* Get interrupt enabled or not */
stat->enable = enabled_irq(stat->irq);
return OK;
}
#endif /* CONFIG_CXD56_GPIO_IRQ */