#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pic_mpc5200.c,v 1.1 2026/06/27 13:28:35 rkujawa Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <dev/ofw/openfirm.h>
#include <machine/intr.h>
#include <powerpc/pic/picvar.h>
#include <powerpc/mpc5200/obiovar.h>
#include <powerpc/mpc5200/pic_mpc5200var.h>
#define SIU_PER_MASK 0x00
#define SIU_PER_PRI1 0x04
#define SIU_PER_PRI2 0x08
#define SIU_PER_PRI3 0x0c
#define SIU_CTRL 0x10
#define SIU_MAIN_MASK 0x14
#define SIU_MAIN_PRI1 0x18
#define SIU_MAIN_PRI2 0x1c
#define SIU_ENC_STATUS 0x24
#define SIU_CRIT_STATUS 0x28
#define SIU_MAIN_STATUS 0x2c
#define SIU_PER_STATUS 0x30
#define SIU_REG_SIZE 0x40
#define SIU_ENC_CRIT_VALID 0x00000400
#define SIU_ENC_CRIT_MASK 0x00000300
#define SIU_ENC_CRIT_SHIFT 8
#define SIU_ENC_MAIN_VALID 0x00200000
#define SIU_ENC_MAIN_MASK 0x001f0000
#define SIU_ENC_MAIN_SHIFT 16
#define SIU_ENC_PER_VALID 0x20000000
#define SIU_ENC_PER_MASK 0x1f000000
#define SIU_ENC_PER_SHIFT 24
#define SIU_ENC_CRIT_IS_CASCADE 2
#define SIU_ENC_MAIN_IS_CASCADE 4
#define SIU_MAIN_MASK_INIT 0x0001efff
#define SIU_PER_MASK_INIT 0xffffff00
#define SIU_CTRL_INIT 0x0fc01001
#define MPC5200_NIRQ 96
#define MPC5200_IRQ_MAIN_BIT 0x20
#define MPC5200_IRQ_PER_BIT 0x40
#define MPC5200_IRQ_SOURCE(irq) ((irq) & 0x1f)
static int mpc5200pic_match(device_t, cfdata_t, void *);
static void mpc5200pic_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(mpc5200pic, sizeof(struct mpc5200pic_softc),
mpc5200pic_match, mpc5200pic_attach, NULL, NULL);
static void mpc5200_enable_irq(struct pic_ops *, int, int);
static void mpc5200_disable_irq(struct pic_ops *, int);
static int mpc5200_get_irq(struct pic_ops *, int);
static void mpc5200_ack_irq(struct pic_ops *, int);
static void mpc5200_establish_irq(struct pic_ops *, int, int, int);
static inline uint32_t
siu_read(struct mpc5200_ops *m, bus_size_t off)
{
return bus_space_read_4(m->bst, m->bsh, off);
}
static inline void
siu_write(struct mpc5200_ops *m, bus_size_t off, uint32_t val)
{
bus_space_write_4(m->bst, m->bsh, off, val);
}
static bool
mpc5200pic_is_pic(int node, const char *name)
{
char compat[32];
if (OF_getprop(node, "compatible", compat, sizeof(compat)) > 0 &&
(strcmp(compat, "mpc5200-pic") == 0 ||
strcmp(compat, "mpc5200b-pic") == 0))
return true;
return strcmp(name, "pic") == 0;
}
static int
mpc5200pic_match(device_t parent, cfdata_t cf, void *aux)
{
struct obio_attach_args *oba = aux;
return mpc5200pic_is_pic(oba->obio_node, oba->obio_name) ? 1 : 0;
}
static void
mpc5200pic_attach(device_t parent, device_t self, void *aux)
{
struct mpc5200pic_softc *sc = device_private(self);
struct obio_attach_args *oba = aux;
struct mpc5200_ops *m = &sc->sc_pic;
struct pic_ops *pic = &m->pic;
bus_size_t size;
sc->sc_dev = self;
size = oba->obio_size != 0 ? oba->obio_size : SIU_REG_SIZE;
m->bst = oba->obio_bst;
if (bus_space_map(m->bst, oba->obio_addr, size, 0, &m->bsh) != 0) {
aprint_error(": can't map registers\n");
return;
}
pic->pic_cookie = m;
pic->pic_numintrs = MPC5200_NIRQ;
pic->pic_enable_irq = mpc5200_enable_irq;
pic->pic_reenable_irq = mpc5200_enable_irq;
pic->pic_disable_irq = mpc5200_disable_irq;
pic->pic_get_irq = mpc5200_get_irq;
pic->pic_ack_irq = mpc5200_ack_irq;
pic->pic_establish_irq = mpc5200_establish_irq;
pic->pic_finish_setup = NULL;
strlcpy(pic->pic_name, device_xname(self), sizeof(pic->pic_name));
m->main_mask = SIU_MAIN_MASK_INIT;
m->per_mask = SIU_PER_MASK_INIT;
m->ctrl = SIU_CTRL_INIT;
pic_add(pic);
siu_write(m, SIU_MAIN_MASK, m->main_mask);
siu_write(m, SIU_PER_MASK, m->per_mask);
siu_write(m, SIU_CTRL, m->ctrl);
siu_write(m, SIU_PER_PRI1, 0);
siu_write(m, SIU_PER_PRI2, 0);
siu_write(m, SIU_PER_PRI3, 0);
siu_write(m, SIU_MAIN_PRI1, 0);
siu_write(m, SIU_MAIN_PRI2, 0);
aprint_normal(": MPC5200 SIU interrupt controller, %d sources\n",
MPC5200_NIRQ);
aprint_verbose_dev(self, "main_mask=0x%08x per_mask=0x%08x\n",
siu_read(m, SIU_MAIN_MASK), siu_read(m, SIU_PER_MASK));
}
static void
mpc5200_enable_irq(struct pic_ops *pic, int irq, int type)
{
struct mpc5200_ops *m = pic->pic_cookie;
uint32_t bit;
if (irq & MPC5200_IRQ_MAIN_BIT) {
if (MPC5200_IRQ_SOURCE(irq) > 16)
return;
bit = 1u << (16 - MPC5200_IRQ_SOURCE(irq));
m->main_mask &= ~bit;
siu_write(m, SIU_MAIN_MASK, m->main_mask);
} else if (irq & MPC5200_IRQ_PER_BIT) {
if (MPC5200_IRQ_SOURCE(irq) > 23)
return;
bit = 1u << (31 - MPC5200_IRQ_SOURCE(irq));
m->per_mask &= ~bit;
siu_write(m, SIU_PER_MASK, m->per_mask);
} else {
if (MPC5200_IRQ_SOURCE(irq) > 3)
return;
bit = (1u << (11 - irq)) | ((uint32_t)type << (22 - irq * 2));
m->ctrl |= bit;
siu_write(m, SIU_CTRL, m->ctrl);
}
}
static void
mpc5200_disable_irq(struct pic_ops *pic, int irq)
{
struct mpc5200_ops *m = pic->pic_cookie;
uint32_t bit;
if (irq == (32 + 4))
return;
if (irq & MPC5200_IRQ_MAIN_BIT) {
if (MPC5200_IRQ_SOURCE(irq) > 16)
return;
bit = 1u << (16 - MPC5200_IRQ_SOURCE(irq));
m->main_mask |= bit;
siu_write(m, SIU_MAIN_MASK, m->main_mask);
} else if (irq & MPC5200_IRQ_PER_BIT) {
if (MPC5200_IRQ_SOURCE(irq) > 23)
return;
bit = 1u << (31 - MPC5200_IRQ_SOURCE(irq));
m->per_mask |= bit;
siu_write(m, SIU_PER_MASK, m->per_mask);
} else {
if (MPC5200_IRQ_SOURCE(irq) > 3)
return;
bit = 1u << (11 - MPC5200_IRQ_SOURCE(irq));
m->ctrl &= ~bit;
siu_write(m, SIU_CTRL, m->ctrl);
}
}
static int
mpc5200_get_irq(struct pic_ops *pic, int mode)
{
struct mpc5200_ops *m = pic->pic_cookie;
uint32_t enc;
int source;
enc = siu_read(m, SIU_ENC_STATUS);
if (enc & SIU_ENC_CRIT_VALID) {
source = (enc & SIU_ENC_CRIT_MASK) >> SIU_ENC_CRIT_SHIFT;
if (source != SIU_ENC_CRIT_IS_CASCADE)
return source;
goto peripheral;
}
if (enc & SIU_ENC_MAIN_VALID) {
source = (enc & SIU_ENC_MAIN_MASK) >> SIU_ENC_MAIN_SHIFT;
if (source != SIU_ENC_MAIN_IS_CASCADE)
return source + 32;
goto peripheral;
}
if (enc & SIU_ENC_PER_VALID) {
peripheral:
source = (enc & SIU_ENC_PER_MASK) >> SIU_ENC_PER_SHIFT;
return source + 64;
}
return 255;
}
static void
mpc5200_ack_irq(struct pic_ops *pic, int irq)
{
}
static void
mpc5200_establish_irq(struct pic_ops *pic, int irq, int type, int maxlevel)
{
}
static int
mpc5200_sense_to_ist(uint32_t sense)
{
switch (sense) {
case 1:
return IST_EDGE_RISING;
case 2:
return IST_EDGE_FALLING;
default:
return IST_LEVEL;
}
}
int
obio_decode_interrupt(int node, int index, int *irqp, int *typep)
{
uint32_t intr[3 * 16];
uint32_t tier, source;
int len, count, base;
len = OF_getprop(node, "interrupts", intr, sizeof(intr));
if (len < (int)(3 * sizeof(uint32_t)))
return 0;
count = len / (int)(3 * sizeof(uint32_t));
if (index < 0 || index >= count)
return 0;
tier = intr[index * 3 + 0];
source = intr[index * 3 + 1];
switch (tier) {
case 0:
base = 0;
break;
case 1:
base = 32;
break;
case 2:
base = 64;
break;
case 3:
base = 96;
break;
default:
return 0;
}
*irqp = base + source;
*typep = mpc5200_sense_to_ist(intr[index * 3 + 2]);
return 1;
}