interupt_controller/stm32_exti: driver for STM32 EXTI controller

Introcuce a driver for External Interrupt/Event Controller (EXTI) found
on STM32 MCUs.

Change-Id: Ib206521fcc51b5dfaaf5dea9d436f8304f3a36be
Origin: Original
Signed-off-by: Maciej Borzecki <maciek.borzecki@gmail.com>
diff --git a/arch/arm/soc/st_stm32/stm32f1/soc.h b/arch/arm/soc/st_stm32/stm32f1/soc.h
index 3a3e6c6..91ccfa7 100644
--- a/arch/arm/soc/st_stm32/stm32f1/soc.h
+++ b/arch/arm/soc/st_stm32/stm32f1/soc.h
@@ -55,6 +55,9 @@
 /* base address for where GPIO registers start */
 #define GPIO_PORTS_BASE       (GPIOA_BASE)
 
+/* EXTI */
+#define EXTI_BASE            (APB2PERIPH_BASE + 0x0400)
+
 #ifndef _ASMLANGUAGE
 
 #include <device.h>
diff --git a/drivers/interrupt_controller/Kconfig b/drivers/interrupt_controller/Kconfig
index 82d2fe1..2da2c4e 100644
--- a/drivers/interrupt_controller/Kconfig
+++ b/drivers/interrupt_controller/Kconfig
@@ -112,4 +112,7 @@
 	The interrupt unit is optional in the ARCv2-based processors. When
 	building a processor, you can configure the processor to include an
 	interrupt unit. The ARCv2 interrupt unit is highly programmable.
+
+source "drivers/interrupt_controller/Kconfig.stm32"
+
 endmenu
diff --git a/drivers/interrupt_controller/Kconfig.stm32 b/drivers/interrupt_controller/Kconfig.stm32
new file mode 100644
index 0000000..d0a6efd
--- /dev/null
+++ b/drivers/interrupt_controller/Kconfig.stm32
@@ -0,0 +1,75 @@
+# Kconfig - STM32 EXTI configuration
+#
+# Copyright (c) 2016 Open-RnD Sp. z o.o.
+#
+# Licensed 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.
+#
+
+if SOC_STM32
+
+config EXTI_STM32
+	bool "External Interrupt/Event Controller (EXTI) Driver for STM32 family of MCUs"
+	default y if SOC_STM32
+	help
+	 Enable EXTI driver for STM32 line of MCUs
+
+config EXTI_STM32_EXTI0_IRQ_PRI
+	int "EXTI0 IRQ priority"
+	depends on EXTI_STM32
+	default 0
+	help
+	 IRQ priority of EXTI0 interrupt
+
+config EXTI_STM32_EXTI1_IRQ_PRI
+	int "EXTI1 IRQ priority"
+	depends on EXTI_STM32
+	default 0
+	help
+	 IRQ priority of EXTI1 interrupt
+
+config EXTI_STM32_EXTI2_IRQ_PRI
+	int "EXTI2 IRQ priority"
+	depends on EXTI_STM32
+	default 0
+	help
+	 IRQ priority of EXTI2 interrupt
+
+config EXTI_STM32_EXTI3_IRQ_PRI
+	int "EXTI3 IRQ priority"
+	depends on EXTI_STM32
+	default 0
+	help
+	 IRQ priority of EXTI3 interrupt
+
+config EXTI_STM32_EXTI4_IRQ_PRI
+	int "EXTI4 IRQ priority"
+	depends on EXTI_STM32
+	default 0
+	help
+	 IRQ priority of EXTI4 interrupt
+
+config EXTI_STM32_EXTI9_5_IRQ_PRI
+	int "EXTI9:5 IRQ priority"
+	depends on EXTI_STM32
+	default 0
+	help
+	 IRQ priority of EXTI9:5 interrupt
+
+config EXTI_STM32_EXTI15_10_IRQ_PRI
+	int "EXTI15:10 IRQ priority"
+	depends on EXTI_STM32
+	default 0
+	help
+	 IRQ priority of EXTI15:10 interrupt
+
+endif # SOC_STM32
diff --git a/drivers/interrupt_controller/Makefile b/drivers/interrupt_controller/Makefile
index e667738..1f44d4d 100644
--- a/drivers/interrupt_controller/Makefile
+++ b/drivers/interrupt_controller/Makefile
@@ -7,3 +7,5 @@
 obj-$(CONFIG_LOAPIC_SPURIOUS_VECTOR) += loapic_spurious.o
 
 obj-$(CONFIG_ARCV2_INTERRUPT_UNIT) += arcv2_irq_unit.o
+
+obj-$(CONFIG_SOC_STM32) += exti_stm32.o
diff --git a/drivers/interrupt_controller/exti_stm32.c b/drivers/interrupt_controller/exti_stm32.c
new file mode 100644
index 0000000..3242c8e
--- /dev/null
+++ b/drivers/interrupt_controller/exti_stm32.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2016 Open-RnD Sp. z o.o.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * @brief Driver for External interrupt/event controller in STM32 MCUs
+ *
+ * Based on reference manual:
+ *   STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx
+ *   advanced ARM ® -based 32-bit MCUs
+ *
+ * Chapter 10.2: External interrupt/event controller (EXTI)
+ *
+ */
+#include <device.h>
+#include <soc.h>
+#include <misc/__assert.h>
+#include "exti_stm32.h"
+
+
+/* 10.3.7 EXTI register map */
+struct stm32_exti {
+	/* EXTI_IMR */
+	uint32_t imr;
+	/* EXTI_EMR */
+	uint32_t emr;
+	/* EXTI_RTSR */
+	uint32_t rtsr;
+	/* EXTI_FTSR */
+	uint32_t ftsr;
+	/* EXTI_SWIER */
+	uint32_t swier;
+	/* EXTI_PR */
+	uint32_t pr;
+};
+
+/* wrapper for user callback */
+struct __exti_cb {
+	stm32_exti_callback_t cb;
+	void *data;
+};
+
+#ifdef CONFIG_SOC_STM32F1X
+#define EXTI_LINES 19
+#endif
+
+/* driver data */
+struct stm32_exti_data {
+	/* per-line callbacks */
+	struct __exti_cb cb[EXTI_LINES];
+};
+
+
+#define AS_EXTI(__base_addr)			\
+	((struct stm32_exti *)(__base_addr))
+
+void stm32_exti_enable(struct device *dev, int line)
+{
+	volatile struct stm32_exti *exti = AS_EXTI(EXTI_BASE);
+	int irqnum;
+
+	ARG_UNUSED(dev);
+
+	exti->imr |= 1 << line;
+
+#ifdef CONFIG_SOC_STM32F1X
+	if (line >= 5 && line <= 9) {
+		irqnum = STM32F1_IRQ_EXTI9_5;
+	} else if (line >= 10 && line <= 15) {
+		irqnum = STM32F1_IRQ_EXTI15_10;
+	} else {
+		/* pins 0..4 are mapped to EXTI0.. EXTI4 */
+		irqnum = STM32F1_IRQ_EXTI0 + line;
+	}
+#endif
+
+	irq_enable(irqnum);
+}
+
+void stm32_exti_disable(struct device *dev, int line)
+{
+	volatile struct stm32_exti *exti = AS_EXTI(EXTI_BASE);
+
+	ARG_UNUSED(dev);
+
+	exti->imr &= ~(1 << line);
+}
+
+/**
+ * @brief check if interrupt is pending
+ *
+ * @param line line number
+ */
+static inline int stm32_exti_is_pending(int line)
+{
+	volatile struct stm32_exti *exti = AS_EXTI(EXTI_BASE);
+
+	return (exti->pr & (1 << line)) ? 1 : 0;
+}
+
+/**
+ * @brief clear pending interrupt bit
+ *
+ * @param line line number
+ */
+static inline void stm32_exti_clear_pending(int line)
+{
+	volatile struct stm32_exti *exti = AS_EXTI(EXTI_BASE);
+
+	exti->pr |= 1 << line;
+}
+
+void stm32_exti_trigger(struct device *dev, int line, int trigger)
+{
+	volatile struct stm32_exti *exti = AS_EXTI(EXTI_BASE);
+
+	ARG_UNUSED(dev);
+
+	if (trigger & STM32_EXTI_TRIG_RISING) {
+		exti->rtsr |= 1 << line;
+	}
+
+	if (trigger & STM32_EXTI_TRIG_FALLING) {
+		exti->ftsr |= 1 << line;
+	}
+}
+
+void stm32_exti_set_callback(struct device *dev, int line,
+			stm32_exti_callback_t cb, void *arg)
+{
+	struct stm32_exti_data *data = dev->driver_data;
+
+	__ASSERT(data->cb[line].cb == NULL,
+		"EXTI %d callback already registered", line);
+
+	data->cb[line].cb = cb;
+	data->cb[line].data = arg;
+}
+
+void stm32_exti_unset_callback(struct device *dev, int line)
+{
+	struct stm32_exti_data *data = dev->driver_data;
+
+	data->cb[line].cb = NULL;
+	data->cb[line].data = NULL;
+}
+
+/**
+ * @brief EXTI ISR handler
+ *
+ * Check EXTI lines in range @min @max for pending interrupts
+ *
+ * @param arg isr argument
+ * @parram min low end of EXTI# range
+ * @parram max low end of EXTI# range
+ */
+static void __stm32_exti_isr(int min, int max, void *arg)
+{
+	struct device *dev = arg;
+	struct stm32_exti_data *data = dev->driver_data;
+	int line;
+
+	/* see which bits are set */
+	for (line = min; line < max; line++) {
+		/* check if interrupt is pending */
+		if (stm32_exti_is_pending(line)) {
+			/* clear pending interrupt */
+			stm32_exti_clear_pending(line);
+
+			/* run callback only if one is registered */
+			if (!data->cb[line].cb) {
+				continue;
+			}
+
+			data->cb[line].cb(line, data->cb[line].data);
+		}
+	}
+}
+
+static inline void __stm32_exti_isr_0(void *arg)
+{
+	__stm32_exti_isr(0, 1, arg);
+}
+
+static inline void __stm32_exti_isr_1(void *arg)
+{
+	__stm32_exti_isr(1, 2, arg);
+}
+
+static inline void __stm32_exti_isr_2(void *arg)
+{
+	__stm32_exti_isr(2, 3, arg);
+}
+
+static inline void __stm32_exti_isr_3(void *arg)
+{
+	__stm32_exti_isr(3, 4, arg);
+}
+
+static inline void __stm32_exti_isr_4(void *arg)
+{
+	__stm32_exti_isr(4, 5, arg);
+}
+
+static inline void __stm32_exti_isr_9_5(void *arg)
+{
+	__stm32_exti_isr(5, 10, arg);
+}
+
+static inline void __stm32_exti_isr_15_10(void *arg)
+{
+	__stm32_exti_isr(10, 16, arg);
+}
+
+static void __stm32_exti_connect_irqs(struct device *dev);
+
+/**
+ * @brief initialize EXTI device driver
+ */
+static int stm32_exti_init(struct device *dev)
+{
+	__stm32_exti_connect_irqs(dev);
+
+	return 0;
+}
+
+static struct stm32_exti_data exti_data;
+DEVICE_INIT(exti_stm32, STM32_EXTI_NAME, stm32_exti_init,
+	    &exti_data, NULL,
+	    PRIMARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
+
+/**
+ * @brief connect all interrupts
+ */
+static void __stm32_exti_connect_irqs(struct device *dev)
+{
+#ifdef CONFIG_SOC_STM32F1X
+	IRQ_CONNECT(STM32F1_IRQ_EXTI0,
+		CONFIG_EXTI_STM32_EXTI0_IRQ_PRI,
+		__stm32_exti_isr_0, DEVICE_GET(exti_stm32),
+		0);
+	IRQ_CONNECT(STM32F1_IRQ_EXTI1,
+		CONFIG_EXTI_STM32_EXTI1_IRQ_PRI,
+		__stm32_exti_isr_1, DEVICE_GET(exti_stm32),
+		0);
+	IRQ_CONNECT(STM32F1_IRQ_EXTI2,
+		CONFIG_EXTI_STM32_EXTI2_IRQ_PRI,
+		__stm32_exti_isr_2, DEVICE_GET(exti_stm32),
+		0);
+	IRQ_CONNECT(STM32F1_IRQ_EXTI3,
+		CONFIG_EXTI_STM32_EXTI3_IRQ_PRI,
+		__stm32_exti_isr_3, DEVICE_GET(exti_stm32),
+		0);
+	IRQ_CONNECT(STM32F1_IRQ_EXTI4,
+		CONFIG_EXTI_STM32_EXTI4_IRQ_PRI,
+		__stm32_exti_isr_4, DEVICE_GET(exti_stm32),
+		0);
+	IRQ_CONNECT(STM32F1_IRQ_EXTI9_5,
+		CONFIG_EXTI_STM32_EXTI9_5_IRQ_PRI,
+		__stm32_exti_isr_9_5, DEVICE_GET(exti_stm32),
+		0);
+	IRQ_CONNECT(STM32F1_IRQ_EXTI15_10,
+		CONFIG_EXTI_STM32_EXTI15_10_IRQ_PRI,
+		__stm32_exti_isr_15_10, DEVICE_GET(exti_stm32),
+		0);
+#endif
+}
+
+
diff --git a/drivers/interrupt_controller/exti_stm32.h b/drivers/interrupt_controller/exti_stm32.h
new file mode 100644
index 0000000..a04dea8
--- /dev/null
+++ b/drivers/interrupt_controller/exti_stm32.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2016 Open-RnD Sp. z o.o.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * @brief Driver for External interrupt/event controller in STM32 MCUs
+ *
+ * Based on reference manual:
+ *   STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx
+ *   advanced ARM ® -based 32-bit MCUs
+ *
+ * Chapter 8.2: External interrupt/event controller (EXTI)
+ *
+ */
+
+#ifndef _STM32_EXTI_H_
+#define _STM32_EXTI_H_
+
+#include <stdint.h>
+
+/* device name */
+#define STM32_EXTI_NAME "stm32-exti"
+
+/**
+ * @brief enable EXTI interrupt for specific line
+ *
+ * @param line EXTI# line
+ */
+void stm32_exti_enable(struct device *dev, int line);
+
+/**
+ * @brief disable EXTI interrupt for specific line
+ *
+ * @param line EXTI# line
+ */
+void stm32_exti_disable(struct device *dev, int line);
+
+/**
+ * @brief EXTI trigger flags
+ */
+enum stm32_exti_trigger {
+	/* trigger on rising edge */
+	STM32_EXTI_TRIG_RISING  = 0x1,
+	/* trigger on falling endge */
+	STM32_EXTI_TRIG_FALLING = 0x2,
+};
+
+/**
+ * @brief set EXTI interrupt line triggers
+ *
+ * @param line EXTI# line
+ * @param trg  OR'ed stm32_exti_trigger flags
+ */
+void stm32_exti_trigger(struct device *dev, int line, int trg);
+
+/* callback for exti interrupt */
+typedef void (*stm32_exti_callback_t) (int line, void *user);
+
+/**
+ * @brief set EXTI interrupt callback
+ *
+ * @param line EXI# line
+ * @param cb   user callback
+ * @param arg  user arg
+ */
+void stm32_exti_set_callback(struct device *dev, int line,
+			stm32_exti_callback_t cb, void *data);
+
+/**
+ * @brief unset EXTI interrupt callback
+ *
+ * @param line EXI# line
+ */
+void stm32_exti_unset_callback(struct device *dev, int line);
+
+#endif /* _STM32_EXTI_H_ */