513 lines
14 KiB
C
513 lines
14 KiB
C
/*
|
|
* Copyright (c) 2021 Microchip Technology Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @brief Driver for External interrupt controller in Microchip XEC devices
|
|
*
|
|
* Driver is currently implemented to support MEC172x ECIA GIRQs
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT microchip_xec_ecia
|
|
|
|
#include <arch/cpu.h>
|
|
#include <arch/arm/aarch32/cortex_m/cmsis.h>
|
|
#include <device.h>
|
|
#include <soc.h>
|
|
#include <sys/__assert.h>
|
|
#include <drivers/clock_control/mchp_xec_clock_control.h>
|
|
#include <drivers/interrupt_controller/intc_mchp_xec_ecia.h>
|
|
|
|
/* defined at the SoC layer */
|
|
#define MCHP_FIRST_GIRQ MCHP_FIRST_GIRQ_NOS
|
|
#define MCHP_LAST_GIRQ MCHP_LAST_GIRQ_NOS
|
|
#define MCHP_XEC_DIRECT_CAPABLE MCHP_ECIA_DIRECT_BITMAP
|
|
|
|
#define GIRQ_ID_TO_BITPOS(id) ((id) + 8)
|
|
|
|
/*
|
|
* MEC SoC's have one and only one instance of ECIA. GIRQ8 register are located
|
|
* at the beginning of the ECIA block.
|
|
*/
|
|
#define ECIA_XEC_REG_BASE \
|
|
((struct ecia_regs *)(DT_REG_ADDR(DT_NODELABEL(ecia))))
|
|
|
|
#define ECS_XEC_REG_BASE \
|
|
((struct ecs_regs *)(DT_REG_ADDR(DT_NODELABEL(ecs))))
|
|
|
|
#define PCR_XEC_REG_BASE \
|
|
((struct pcr_regs *)(DT_REG_ADDR(DT_NODELABEL(pcr))))
|
|
|
|
#define ECIA_XEC_PCR_REG_IDX DT_INST_CLOCKS_CELL(0, regidx)
|
|
#define ECIA_XEC_PCR_BITPOS DT_INST_CLOCKS_CELL(0, bitpos)
|
|
|
|
#define ECIA_XEC_PCR_INFO \
|
|
MCHP_XEC_PCR_SCR_ENCODE(DT_INST_CLOCKS_CELL(0, regidx), \
|
|
DT_INST_CLOCKS_CELL(0, bitpos))
|
|
|
|
struct xec_girq_config {
|
|
uintptr_t base;
|
|
uint8_t girq_id;
|
|
uint8_t num_srcs;
|
|
uint8_t sources[32];
|
|
};
|
|
|
|
struct xec_ecia_config {
|
|
uintptr_t ecia_base;
|
|
struct mchp_xec_pcr_clk_ctrl clk_ctrl;
|
|
const struct device *girq_node_handles[32];
|
|
};
|
|
|
|
struct xec_girq_src_data {
|
|
mchp_xec_ecia_callback_t cb;
|
|
void *data;
|
|
};
|
|
|
|
#define DEV_ECIA_CFG(ecia_dev) \
|
|
((const struct xec_ecia_config *const)(ecia_dev)->config)
|
|
|
|
#define DEV_GIRQ_CFG(girq_dev) \
|
|
((const struct xec_girq_config *const)(girq_dev)->config)
|
|
|
|
#define DEV_GIRQ_DATA(girq_dev) \
|
|
((struct xec_girq_src_data *const)(girq_dev)->data)
|
|
|
|
/*
|
|
* Enable/disable specified GIRQ's aggregated output. Aggrated output is the
|
|
* bit-wise or of all the GIRQ's result bits.
|
|
*/
|
|
void mchp_xec_ecia_girq_aggr_en(uint8_t girq_num, uint8_t enable)
|
|
{
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
if (enable) {
|
|
regs->BLK_EN_SET = BIT(girq_num);
|
|
} else {
|
|
regs->BLK_EN_CLR = BIT(girq_num);
|
|
}
|
|
}
|
|
|
|
void mchp_xec_ecia_girq_src_clr(uint8_t girq_num, uint8_t src_bit_pos)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to clear */
|
|
regs->GIRQ[girq_num - MCHP_FIRST_GIRQ].SRC = BIT(src_bit_pos);
|
|
}
|
|
|
|
void mchp_xec_ecia_girq_src_en(uint8_t girq_num, uint8_t src_bit_pos)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to set */
|
|
regs->GIRQ[girq_num - MCHP_FIRST_GIRQ].EN_SET = BIT(src_bit_pos);
|
|
}
|
|
|
|
void mchp_xec_ecia_girq_src_dis(uint8_t girq_num, uint8_t src_bit_pos)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to clear */
|
|
regs->GIRQ[girq_num - MCHP_FIRST_GIRQ].EN_CLR = BIT(src_bit_pos);
|
|
}
|
|
|
|
void mchp_xec_ecia_girq_src_clr_bitmap(uint8_t girq_num, uint32_t bitmap)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to clear */
|
|
regs->GIRQ[girq_num - MCHP_FIRST_GIRQ].SRC = bitmap;
|
|
}
|
|
|
|
void mchp_xec_ecia_girq_src_en_bitmap(uint8_t girq_num, uint32_t bitmap)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to clear */
|
|
regs->GIRQ[girq_num - MCHP_FIRST_GIRQ].EN_SET = bitmap;
|
|
}
|
|
|
|
void mchp_xec_ecia_girq_src_dis_bitmap(uint8_t girq_num, uint32_t bitmap)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to clear */
|
|
regs->GIRQ[girq_num - MCHP_FIRST_GIRQ].EN_CLR = bitmap;
|
|
}
|
|
|
|
/*
|
|
* Return read-only GIRQ result register. Result is bit-wise and of source
|
|
* and enable registers.
|
|
*/
|
|
uint32_t mchp_xec_ecia_girq_result(uint8_t girq_num)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return 0U;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
return regs->GIRQ[girq_num - MCHP_FIRST_GIRQ].RESULT;
|
|
}
|
|
|
|
/* Clear NVIC pending given the external NVIC input number (zero based) */
|
|
void mchp_xec_ecia_nvic_clr_pend(uint32_t nvic_num)
|
|
{
|
|
if (nvic_num >= ((SCnSCB->ICTR + 1) * 32)) {
|
|
return;
|
|
}
|
|
|
|
NVIC_ClearPendingIRQ(nvic_num);
|
|
}
|
|
|
|
/**
|
|
* @brief enable GIRQn interrupt for specific source
|
|
*
|
|
* @param girq is the GIRQ number (8 - 26)
|
|
* @param src is the interrupt source in the GIRQ (0 - 31)
|
|
*/
|
|
int mchp_xec_ecia_enable(int girq, int src)
|
|
{
|
|
if ((girq < MCHP_FIRST_GIRQ) || (girq > MCHP_LAST_GIRQ) ||
|
|
(src < 0) || (src > 31)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to set */
|
|
regs->GIRQ[girq - MCHP_FIRST_GIRQ].EN_SET = BIT(src);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief disable EXTI interrupt for specific line
|
|
*
|
|
* @param girq is the GIRQ number (8 - 26)
|
|
* @param src is the interrupt source in the GIRQ (0 - 31)
|
|
*/
|
|
int mchp_xec_ecia_disable(int girq, int src)
|
|
{
|
|
if ((girq < MCHP_FIRST_GIRQ) || (girq > MCHP_LAST_GIRQ) ||
|
|
(src < 0) || (src > 31)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
struct ecia_regs *regs = ECIA_XEC_REG_BASE;
|
|
|
|
/* write 1 to clear */
|
|
regs->GIRQ[girq - MCHP_FIRST_GIRQ].EN_CLR = BIT(src);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* forward reference */
|
|
static const struct device *get_girq_dev(int girq_num);
|
|
|
|
/**
|
|
* @brief set GIRQn interrupt source callback
|
|
*
|
|
* @param dev_girq is the GIRQn device handle
|
|
* @param src is the interrupt source in the GIRQ (0 - 31)
|
|
* @param cb user callback
|
|
* @param data user data
|
|
*/
|
|
int mchp_xec_ecia_set_callback_by_dev(const struct device *dev_girq, int src,
|
|
mchp_xec_ecia_callback_t cb, void *data)
|
|
{
|
|
if ((dev_girq == NULL) || (src < 0) || (src > 31)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
const struct xec_girq_config *const cfg = DEV_GIRQ_CFG(dev_girq);
|
|
struct xec_girq_src_data *girq_data = DEV_GIRQ_DATA(dev_girq);
|
|
|
|
/* source exists in this GIRQ? */
|
|
if (!(cfg->sources[src] & BIT(7))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* obtain the callback array index for the source */
|
|
int idx = (int)(cfg->sources[src] & ~BIT(7));
|
|
|
|
girq_data[idx].cb = cb;
|
|
girq_data[idx].data = data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief set GIRQn interrupt source callback
|
|
*
|
|
* @param girq is the GIRQ number (8 - 26)
|
|
* @param src is the interrupt source in the GIRQ (0 - 31)
|
|
* @param cb user callback
|
|
* @param data user data
|
|
*/
|
|
int mchp_xec_ecia_set_callback(int girq_num, int src,
|
|
mchp_xec_ecia_callback_t cb, void *data)
|
|
{
|
|
const struct device *dev = get_girq_dev(girq_num);
|
|
|
|
return mchp_xec_ecia_set_callback_by_dev(dev, src, cb, data);
|
|
}
|
|
|
|
/**
|
|
* @brief unset GIRQn interrupt source callback by device handle
|
|
*
|
|
* @param dev_girq is the GIRQn device handle
|
|
* @param src is the interrupt source in the GIRQ (0 - 31)
|
|
*/
|
|
int mchp_ecia_unset_callback_by_dev(const struct device *dev_girq, int src)
|
|
{
|
|
if ((dev_girq == NULL) || (src < 0) || (src > 31)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
const struct xec_girq_config *const cfg = DEV_GIRQ_CFG(dev_girq);
|
|
struct xec_girq_src_data *girq_data = DEV_GIRQ_DATA(dev_girq);
|
|
|
|
/* source exists in this GIRQ? */
|
|
if (!(cfg->sources[src] & BIT(7))) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* obtain the callback array index for the source */
|
|
int idx = (int)(cfg->sources[src] & ~BIT(7));
|
|
|
|
girq_data[idx].cb = NULL;
|
|
girq_data[idx].data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief unset GIRQn interrupt source callback
|
|
*
|
|
* @param girq is the GIRQ number (8 - 26)
|
|
* @param src is the interrupt source in the GIRQ (0 - 31)
|
|
*/
|
|
int mchp_ecia_unset_callback(int girq_num, int src)
|
|
{
|
|
const struct device *dev = get_girq_dev(girq_num);
|
|
|
|
return mchp_ecia_unset_callback_by_dev(dev, src);
|
|
}
|
|
|
|
/*
|
|
* Create a build time flag to know if any aggregated GIRQ has been enabled.
|
|
* We make use of DT FOREACH macro to check GIRQ node status.
|
|
* Enabling a GIRQ node (status = "okay") implies you want it used in
|
|
* aggregated mode. Note, GIRQ 8-12, 24-26 are aggregated only by HW design.
|
|
* If a GIRQ node is disabled(status = "disabled") and is direct capable the
|
|
* other driver/application may use IRQ_CONNECT, irq_enable, and the helper
|
|
* functions in this driver to set/clear GIRQ enable bits and status.
|
|
* Leaving a node disabled also allows another driver/application to take over
|
|
* aggregation by managing the GIRQ itself.
|
|
*/
|
|
#define XEC_CHK_REQ_AGGR(n) DT_NODE_HAS_STATUS(n, okay) |
|
|
|
|
#define XEC_ECIA_REQUIRE_AGGR_ISR \
|
|
( \
|
|
DT_FOREACH_CHILD(DT_NODELABEL(ecia), XEC_CHK_REQ_AGGR) \
|
|
0)
|
|
|
|
/* static const uint32_t xec_chk_req = (XEC_ECIA_REQUIRE_AGGR_ISR); */
|
|
|
|
#if XEC_ECIA_REQUIRE_AGGR_ISR
|
|
/*
|
|
* Generic ISR for aggregated GIRQ's.
|
|
* GIRQ source(status) bits are latched (R/W1C). The peripheral status
|
|
* connected to the GIRQ source bit must be cleared first by the callback
|
|
* and this routine will clear the GIRQ source bit. If a callback was not
|
|
* registered for a source the enable will also be cleared to prevent
|
|
* interrupt storms.
|
|
* NOTE: dev_girq is a pointer to a GIRQ child device instance.
|
|
*/
|
|
static void xec_girq_isr(const struct device *dev_girq)
|
|
{
|
|
const struct xec_girq_config *const cfg = DEV_GIRQ_CFG(dev_girq);
|
|
struct xec_girq_src_data *data = DEV_GIRQ_DATA(dev_girq);
|
|
struct girq_regs *girq = (struct girq_regs *)cfg->base;
|
|
int girq_id = GIRQ_ID_TO_BITPOS(cfg->girq_id);
|
|
uint32_t idx = 0;
|
|
uint32_t result = girq->RESULT;
|
|
|
|
for (int i = 0; result && i < 32; i++) {
|
|
uint8_t bitpos = 31 - (__builtin_clz(result) & 0x1f);
|
|
|
|
/* is it an implemented source? */
|
|
if (cfg->sources[bitpos] & BIT(7)) {
|
|
/* yes, get the index by removing bit[7] flag */
|
|
idx = (uint32_t)cfg->sources[bitpos] & ~BIT(7);
|
|
/* callback registered? */
|
|
if (data[idx].cb) {
|
|
data[idx].cb(girq_id, bitpos, data[idx].data);
|
|
} else { /* no callback, clear the enable */
|
|
girq->EN_CLR = BIT(bitpos);
|
|
}
|
|
} else { /* paranoia, we should not get here... */
|
|
girq->EN_CLR = BIT(bitpos);
|
|
}
|
|
|
|
/* clear GIRQ latched status */
|
|
girq->SRC = BIT(bitpos);
|
|
result &= ~BIT(bitpos);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief initialize XEC ECIA driver
|
|
* NOTE: GIRQ22 is special used for waking the PLL from deep sleep when a
|
|
* peripheral receives data from an external entity (eSPI, I2C, etc). Once
|
|
* the data transfer is complete the system re-enters deep sleep unless the
|
|
* peripheral was configured to wake CPU after reception of data or event.
|
|
* GIRQ22 aggregated output and sources are not connected to the NVIC.
|
|
* We enable GIRQ22 aggregated output to ensure clock asynchronous wake
|
|
* functionality is operational.
|
|
*/
|
|
static int xec_ecia_init(const struct device *dev)
|
|
{
|
|
const struct xec_ecia_config *cfg =
|
|
(const struct xec_ecia_config *const) (dev->config);
|
|
const struct device *const clk_dev = DEVICE_DT_GET(DT_NODELABEL(pcr));
|
|
struct ecs_regs *const ecs = ECS_XEC_REG_BASE;
|
|
struct ecia_regs *const ecia = (struct ecia_regs *)cfg->ecia_base;
|
|
uint32_t n = 0, nr = 0;
|
|
int ret;
|
|
|
|
ret = clock_control_on(clk_dev,
|
|
(clock_control_subsys_t *)&cfg->clk_ctrl);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Enable all direct NVIC connections */
|
|
ecs->INTR_CTRL |= BIT(0);
|
|
|
|
/* gate off all aggregated outputs */
|
|
ecia->BLK_EN_CLR = UINT32_MAX;
|
|
|
|
/* connect aggregated only GIRQs to NVIC */
|
|
ecia->BLK_EN_SET = MCHP_ECIA_AGGR_BITMAP;
|
|
|
|
/* Clear all GIRQn source enables */
|
|
for (n = 0; n < MCHP_GIRQS; n++) {
|
|
ecia->GIRQ[n].EN_CLR = UINT32_MAX;
|
|
}
|
|
|
|
/* Clear all external NVIC enables and pending status */
|
|
nr = SCnSCB->ICTR;
|
|
for (n = 0u; n <= nr; n++) {
|
|
NVIC->ICER[n] = UINT32_MAX;
|
|
NVIC->ICPR[n] = UINT32_MAX;
|
|
}
|
|
|
|
/* ecia->BLK_ACTIVE = xec_chk_req; */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* xec_config_girq_xxx.sources[] entries from GIRQ node */
|
|
#define XEC_GIRQ_SOURCES2(node_id, prop, idx) \
|
|
.sources[DT_PROP_BY_IDX(node_id, prop, idx)] = \
|
|
((uint8_t)(idx) | BIT(7)),
|
|
|
|
/* Parameter n is a child node-id */
|
|
#define GIRQ_XEC_DEVICE(n) \
|
|
static int xec_girq_init_##n(const struct device *dev); \
|
|
\
|
|
static struct xec_girq_src_data \
|
|
xec_data_girq_##n[DT_PROP_LEN(n, sources)]; \
|
|
\
|
|
static const struct xec_girq_config xec_config_girq_##n = { \
|
|
.base = DT_REG_ADDR(n), \
|
|
.girq_id = DT_PROP(n, girq_id), \
|
|
.num_srcs = DT_PROP_LEN(n, sources), \
|
|
DT_FOREACH_PROP_ELEM(n, sources, XEC_GIRQ_SOURCES2) \
|
|
}; \
|
|
\
|
|
DEVICE_DT_DEFINE(n, xec_girq_init_##n, \
|
|
NULL, &xec_data_girq_##n, &xec_config_girq_##n, \
|
|
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
NULL); \
|
|
\
|
|
static int xec_girq_init_##n(const struct device *dev) \
|
|
{ \
|
|
mchp_xec_ecia_girq_aggr_en( \
|
|
GIRQ_ID_TO_BITPOS(DT_PROP(n, girq_id)), 1); \
|
|
\
|
|
IRQ_CONNECT(DT_IRQN(n), \
|
|
DT_IRQ(n, priority), \
|
|
xec_girq_isr, \
|
|
DEVICE_DT_GET(n), 0); \
|
|
\
|
|
irq_enable(DT_IRQN(n)); \
|
|
\
|
|
return 0; \
|
|
}
|
|
|
|
/*
|
|
* iterate over each enabled child node of ECIA
|
|
* Enable means property status = "okay"
|
|
*/
|
|
DT_FOREACH_CHILD_STATUS_OKAY(DT_NODELABEL(ecia), GIRQ_XEC_DEVICE)
|
|
|
|
/* n = GIRQ node id */
|
|
#define XEC_GIRQ_HANDLE(n) \
|
|
.girq_node_handles[DT_PROP(n, girq_id)] = (DEVICE_DT_GET(n)),
|
|
|
|
static const struct xec_ecia_config xec_config_ecia = {
|
|
.ecia_base = DT_REG_ADDR(DT_NODELABEL(ecia)),
|
|
.clk_ctrl = {
|
|
.pcr_info = ECIA_XEC_PCR_INFO,
|
|
},
|
|
DT_FOREACH_CHILD_STATUS_OKAY(DT_NODELABEL(ecia), XEC_GIRQ_HANDLE)
|
|
};
|
|
|
|
DEVICE_DT_DEFINE(DT_NODELABEL(ecia), xec_ecia_init,
|
|
NULL, NULL, &xec_config_ecia,
|
|
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
|
|
NULL);
|
|
|
|
/* look up GIRQ node handle from ECIA configuration */
|
|
static const struct device *get_girq_dev(int girq_num)
|
|
{
|
|
if ((girq_num < MCHP_FIRST_GIRQ) || (girq_num > MCHP_LAST_GIRQ)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* safe to convert to zero based index */
|
|
girq_num -= MCHP_FIRST_GIRQ;
|
|
|
|
return xec_config_ecia.girq_node_handles[girq_num];
|
|
}
|