drivers: interrupt_controller: Add SweRV PIC support

Add support for the built-in Programmable Interrupt Controller
found in the SweRV EH1 RISC-V CPU

Signed-off-by: Olof Kindgren <olof.kindgren@gmail.com>
diff --git a/drivers/interrupt_controller/CMakeLists.txt b/drivers/interrupt_controller/CMakeLists.txt
index 96fab70..510b0c2 100644
--- a/drivers/interrupt_controller/CMakeLists.txt
+++ b/drivers/interrupt_controller/CMakeLists.txt
@@ -13,3 +13,4 @@
 zephyr_sources_ifdef(CONFIG_RV32M1_INTMUX           rv32m1_intmux.c)
 zephyr_sources_ifdef(CONFIG_SAM0_EIC                sam0_eic.c)
 zephyr_sources_ifdef(CONFIG_VEXRISCV_LITEX_IRQ      vexriscv_litex.c)
+zephyr_sources_ifdef(CONFIG_SWERV_PIC               swerv_pic.c)
diff --git a/drivers/interrupt_controller/Kconfig b/drivers/interrupt_controller/Kconfig
index acbe1ad..4cb3fd4 100644
--- a/drivers/interrupt_controller/Kconfig
+++ b/drivers/interrupt_controller/Kconfig
@@ -98,6 +98,12 @@
 	  Platform Level Interrupt Controller provides support
 	  for external interrupt lines defined by the RISC-V SoC;
 
+config SWERV_PIC
+	bool "SweRV EH1 Programmable Interrupt Controller (PIC)"
+	default n
+	help
+	  Programmable Interrupt Controller for the SweRV EH1 RISC-V CPU;
+
 config VEXRISCV_LITEX_IRQ
 	bool "VexRiscv LiteX Interrupt controller"
 	depends on SOC_RISCV32_LITEX_VEXRISCV
diff --git a/drivers/interrupt_controller/swerv_pic.c b/drivers/interrupt_controller/swerv_pic.c
new file mode 100644
index 0000000..f35d023
--- /dev/null
+++ b/drivers/interrupt_controller/swerv_pic.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2019 Western Digital Corporation or its affiliates
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @brief SweRV EH1 PIC driver
+ */
+
+#include <kernel.h>
+#include <arch/cpu.h>
+#include <init.h>
+#include <sw_isr_table.h>
+
+#define SWERV_PIC_MAX_NUM	CONFIG_NUM_IRQS
+#define SWERV_PIC_MAX_ID	(SWERV_PIC_MAX_NUM + RISCV_MAX_GENERIC_IRQ)
+#define SWERV_PIC_MAX_PRIO		16
+
+#define SWERV_PIC_mpiccfg		0x3000
+#define SWERV_PIC_meipl(s)		(0x0 + (s)*4)
+#define SWERV_PIC_meip(x)		(0x1000 + (x)*4)
+#define SWERV_PIC_meie(s)		(0x2000 + (s)*4)
+#define SWERV_PIC_meigwctrl(s)		(0x4000 + (s)*4)
+#define SWERV_PIC_meigwclr(s)		(0x5000 + (s)*4)
+
+#define SWERV_PIC_meivt			"0xBC8"
+#define SWERV_PIC_meipt			"0xBC9"
+#define SWERV_PIC_meicpct		"0xBCA"
+#define SWERV_PIC_meicidpl		"0xBCB"
+#define SWERV_PIC_meicurpl		"0xBCC"
+#define SWERV_PIC_meihap		"0xFC8"
+
+#define swerv_piccsr(csr) SWERV_PIC_##csr
+
+#define swerv_pic_readcsr(csr, value) \
+	volatile("csrr %0, "swerv_piccsr(csr) : "=r" (value))
+#define swerv_pic_writecsr(csr, value) \
+	volatile("csrw "swerv_piccsr(csr)", %0" :: "rK" (value))
+
+static int save_irq;
+
+static u32_t swerv_pic_read(u32_t reg)
+{
+	return *(volatile u32_t *)(DT_INST_0_SWERV_PIC_BASE_ADDRESS + reg);
+}
+
+static void swerv_pic_write(u32_t reg, u32_t val)
+{
+	*(volatile u32_t *)(DT_INST_0_SWERV_PIC_BASE_ADDRESS + reg) = val;
+}
+
+void swerv_pic_irq_enable(u32_t irq)
+{
+	u32_t key;
+
+	if ((irq >= SWERV_PIC_MAX_ID) || (irq < RISCV_MAX_GENERIC_IRQ)) {
+		return;
+	}
+
+	key = irq_lock();
+	swerv_pic_write(SWERV_PIC_meie(irq - RISCV_MAX_GENERIC_IRQ), 1);
+	irq_unlock(key);
+}
+
+void swerv_pic_irq_disable(u32_t irq)
+{
+	u32_t key;
+
+	if ((irq >= SWERV_PIC_MAX_ID) || (irq < RISCV_MAX_GENERIC_IRQ)) {
+		return;
+	}
+
+	key = irq_lock();
+	swerv_pic_write(SWERV_PIC_meie(irq - RISCV_MAX_GENERIC_IRQ), 0);
+	irq_unlock(key);
+}
+
+int swerv_pic_irq_is_enabled(u32_t irq)
+{
+	if ((irq >= SWERV_PIC_MAX_ID) || (irq < RISCV_MAX_GENERIC_IRQ)) {
+		return -1;
+	}
+
+	return swerv_pic_read(SWERV_PIC_meie(irq - RISCV_MAX_GENERIC_IRQ))
+	  & 0x1;
+}
+
+void swerv_pic_set_priority(u32_t irq, u32_t priority)
+{
+	u32_t key;
+
+	if (irq <= RISCV_MAX_GENERIC_IRQ) {
+		return;
+	}
+
+	if ((irq >= SWERV_PIC_MAX_ID) || (irq < RISCV_MAX_GENERIC_IRQ)) {
+		return;
+	}
+
+	if (priority >= SWERV_PIC_MAX_PRIO) {
+		return;
+	}
+
+	key = irq_lock();
+	swerv_pic_write(SWERV_PIC_meipl(irq - RISCV_MAX_GENERIC_IRQ), priority);
+	irq_unlock(key);
+}
+
+int swerv_pic_get_irq(void)
+{
+	return save_irq;
+}
+
+static void swerv_pic_irq_handler(void *arg)
+{
+	u32_t tmp;
+	u32_t irq;
+	struct _isr_table_entry *ite;
+
+	/* trigger the capture of the interrupt source ID */
+	__asm__ swerv_pic_writecsr(meicpct, 0);
+
+	__asm__ swerv_pic_readcsr(meihap, tmp);
+	irq = (tmp >> 2) & 0xff;
+
+	save_irq = irq;
+
+	if (irq == 0U || irq >= 64) {
+		z_irq_spurious(NULL);
+	}
+	irq += RISCV_MAX_GENERIC_IRQ;
+
+	/* Call the corresponding IRQ handler in _sw_isr_table */
+	ite = (struct _isr_table_entry *)&_sw_isr_table[irq];
+	if (ite->isr)
+		ite->isr(ite->arg);
+
+	swerv_pic_write(SWERV_PIC_meigwclr(irq), 0);
+}
+
+static int swerv_pic_init(struct device *dev)
+{
+	ARG_UNUSED(dev);
+	int i;
+
+	/* Init priority order to 0, 0=lowest to 15=highest */
+	swerv_pic_write(SWERV_PIC_mpiccfg, 0);
+
+	/* Ensure that all interrupts are disabled initially */
+	for (i = 1; i < SWERV_PIC_MAX_ID; i++) {
+		swerv_pic_write(SWERV_PIC_meie(i), 0);
+	}
+
+	/* Set priority of each interrupt line to 0 initially */
+	for (i = 1; i < SWERV_PIC_MAX_ID; i++) {
+		swerv_pic_write(SWERV_PIC_meipl(i), 15);
+	}
+
+	/* Set property of each interrupt line to level-triggered/high */
+	for (i = 1; i < SWERV_PIC_MAX_ID; i++) {
+		swerv_pic_write(SWERV_PIC_meigwctrl(i), (0<<1)|(0<<0));
+	}
+
+	/* clear pending of each interrupt line */
+	for (i = 1; i < SWERV_PIC_MAX_ID; i++) {
+		swerv_pic_write(SWERV_PIC_meigwclr(i), 0);
+	}
+
+	/* No interrupts masked */
+	__asm__ swerv_pic_writecsr(meipt, 0);
+	__asm__ swerv_pic_writecsr(meicidpl, 0);
+	__asm__ swerv_pic_writecsr(meicurpl, 0);
+
+	/* Setup IRQ handler for SweRV PIC driver */
+	IRQ_CONNECT(RISCV_MACHINE_EXT_IRQ,
+		    0,
+		    swerv_pic_irq_handler,
+		    NULL,
+		    0);
+
+	/* Enable IRQ for SweRV PIC driver */
+	irq_enable(RISCV_MACHINE_EXT_IRQ);
+
+	return 0;
+}
+
+void arch_irq_enable(unsigned int irq)
+{
+	u32_t mie;
+
+	if (irq > RISCV_MAX_GENERIC_IRQ) {
+		swerv_pic_irq_enable(irq);
+		return;
+	}
+
+	/*
+	 * CSR mie register is updated using atomic instruction csrrs
+	 * (atomic read and set bits in CSR register)
+	 */
+	__asm__ volatile ("csrrs %0, mie, %1\n"
+			  : "=r" (mie)
+			  : "r" (1 << irq));
+}
+
+void arch_irq_disable(unsigned int irq)
+{
+	u32_t mie;
+
+	if (irq > RISCV_MAX_GENERIC_IRQ) {
+		swerv_pic_irq_disable(irq);
+		return;
+	}
+
+	/*
+	 * Use atomic instruction csrrc to disable device interrupt in mie CSR.
+	 * (atomic read and clear bits in CSR register)
+	 */
+	__asm__ volatile ("csrrc %0, mie, %1\n"
+			  : "=r" (mie)
+			  : "r" (1 << irq));
+};
+
+int arch_irq_is_enabled(unsigned int irq)
+{
+	u32_t mie;
+
+	if (irq > RISCV_MAX_GENERIC_IRQ)
+		return swerv_pic_irq_is_enabled(irq);
+
+	__asm__ volatile ("csrr %0, mie" : "=r" (mie));
+
+	return !!(mie & (1 << irq));
+}
+
+SYS_INIT(swerv_pic_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
diff --git a/dts/bindings/interrupt-controller/swerv,pic.yaml b/dts/bindings/interrupt-controller/swerv,pic.yaml
new file mode 100644
index 0000000..ae54afd
--- /dev/null
+++ b/dts/bindings/interrupt-controller/swerv,pic.yaml
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2019
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+---
+title: SweRV EH1 PIC
+
+description: >
+    This binding describes the SweRV EH1 Programmable Interrupt Controller
+
+compatible: "swerv,pic"
+
+include: [interrupt-controller.yaml, base.yaml]
+
+properties:
+  reg:
+      required: true
+
+  riscv,max-priority:
+      type: int
+      description: maximum interrupt priority
+      required: true
+
+interrupt-cells:
+  - irq
+  - priority