diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index cfd2a8bf60741c2614e7c67c1fb54f618e3ebecb..67a1ad792931a8457697a3242becb1eeaa8d60de 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -20,7 +20,7 @@ config CPU_CORTEX
 
 config ARM_CUSTOM_INTERRUPT_CONTROLLER
 	bool
-	depends on !CPU_CORTEX_M
+	depends on !CPU_CORTEX_M || SOC_BCM2837
 	help
 	  This option indicates that the ARM CPU is connected to a custom (i.e.
 	  non-GIC) interrupt controller.
diff --git a/arch/arm/core/aarch64/Kconfig b/arch/arm/core/aarch64/Kconfig
index 3127c5061aa557661bfbad33bc57895c4274834e..e5e03c495011c6186e637fb76d3cbc9d085a2f18 100644
--- a/arch/arm/core/aarch64/Kconfig
+++ b/arch/arm/core/aarch64/Kconfig
@@ -10,7 +10,6 @@ config CPU_CORTEX_A
 	select USE_SWITCH
 	select USE_SWITCH_SUPPORTED
 	select HAS_ARM_SMCCC
-	select SCHED_IPI_SUPPORTED if SMP
 	help
 	  This option signifies the use of a CPU of the Cortex-A family.
 
diff --git a/arch/arm/core/aarch64/smp.c b/arch/arm/core/aarch64/smp.c
index 14c4f5014dff9233f1fb7b4e276d2c9b4b476181..8f1e055be4a908bda7a5230db92f9a4919e2e53d 100644
--- a/arch/arm/core/aarch64/smp.c
+++ b/arch/arm/core/aarch64/smp.c
@@ -38,6 +38,18 @@ volatile struct {
  */
 volatile _cpu_t *_curr_cpu[CONFIG_MP_NUM_CPUS];
 
+#ifdef CONFIG_SOC_BCM2837
+static ALWAYS_INLINE void __SEV(void)
+{
+	__asm__ volatile ("sev" : : : "memory");
+}
+
+static void sys_write64(uint64_t val, uintptr_t addr) {
+	sys_write32(val, addr);
+	sys_write32(val >> 32, addr + sizeof(uint32_t));
+}
+#endif
+
 extern void __start(void);
 /* Called from Zephyr initialization */
 void arch_start_cpu(int cpu_num, k_thread_stack_t *stack, int sz,
@@ -55,9 +67,15 @@ void arch_start_cpu(int cpu_num, k_thread_stack_t *stack, int sz,
 	arch_dcache_range((void *)&arm64_cpu_init[cpu_num],
 			  sizeof(arm64_cpu_init[cpu_num]), K_CACHE_WB_INVD);
 
+#ifdef CONFIG_SOC_BCM2837
+	sys_write64((uint64_t) &__start, 0xd8 + 0x8 * cpu_num);
+	__DSB();
+	__SEV();
+#else
 	/* TODO: get mpidr from device tree, using cpu_num */
 	if (pm_cpu_on(cpu_num, (uint64_t)&__start))
 		printk("Failed to boot CPU%d\n", cpu_num);
+#endif
 
 	/* Wait secondary cores up, see z_arm64_secondary_start */
 	while (arm64_cpu_init[cpu_num].fn) {
@@ -74,9 +92,11 @@ void z_arm64_secondary_start(void)
 	z_arm64_mmu_init();
 
 #ifdef CONFIG_SMP
+#ifndef CONFIG_SOC_BCM2837
 	arm_gic_secondary_init();
 
 	irq_enable(SGI_SCHED_IPI);
+#endif
 #endif
 
 	fn = arm64_cpu_init[cpu_num].fn;
@@ -94,6 +114,7 @@ void z_arm64_secondary_start(void)
 }
 
 #ifdef CONFIG_SMP
+#ifndef CONFIG_SOC_BCM2837
 void sched_ipi_handler(const void *unused)
 {
 	ARG_UNUSED(unused);
@@ -131,3 +152,4 @@ static int arm64_smp_init(const struct device *dev)
 }
 SYS_INIT(arm64_smp_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
 #endif
+#endif
diff --git a/boards/arm/raspi3/Kconfig.board b/boards/arm/raspi3/Kconfig.board
new file mode 100644
index 0000000000000000000000000000000000000000..e15f9d03c8e6534a1fd40372045f56b014720f89
--- /dev/null
+++ b/boards/arm/raspi3/Kconfig.board
@@ -0,0 +1,8 @@
+# Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+config BOARD_RASPI3
+	bool "Raspberry Pi 3 Model B+ and QEMU Machine raspi3"
+	depends on SOC_BCM2837
+	select ARM64
+	select QEMU_TARGET
diff --git a/boards/arm/raspi3/Kconfig.defconfig b/boards/arm/raspi3/Kconfig.defconfig
new file mode 100644
index 0000000000000000000000000000000000000000..90c9ba689cc6b72b69d6fddd4d1e43c810730c8b
--- /dev/null
+++ b/boards/arm/raspi3/Kconfig.defconfig
@@ -0,0 +1,12 @@
+# Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+if BOARD_RASPI3
+
+config BUILD_OUTPUT_BIN
+	default y
+
+config BOARD
+	default "raspi3"
+
+endif # BOARD_RASPI3
diff --git a/boards/arm/raspi3/board.cmake b/boards/arm/raspi3/board.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..68c4452cab15ce0bf8655332bc91c5e0e5b8b434
--- /dev/null
+++ b/boards/arm/raspi3/board.cmake
@@ -0,0 +1,17 @@
+# Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+set(EMU_PLATFORM qemu)
+set(QEMU_ARCH aarch64)
+set(QEMU_KERNEL_OPTION "-kernel;${ZEPHYR_BINARY_DIR}/zephyr.bin")
+
+# https://github.com/s-matyukevich/raspberry-pi-os/issues/8#issuecomment-411024746
+set(QEMU_FLAGS_${ARCH}
+  -M raspi3
+  -smp 4
+  -nographic
+  -no-reboot
+  # To see Mini UART on qemu's stdout (default is PL011):
+  # -serial null -serial chardev:con -monitor none
+  )
+board_set_debugger_ifnset(qemu)
diff --git a/boards/arm/raspi3/raspi3.dts b/boards/arm/raspi3/raspi3.dts
new file mode 100644
index 0000000000000000000000000000000000000000..b812a1224b8f321604513bb4d2e372210bfef8c9
--- /dev/null
+++ b/boards/arm/raspi3/raspi3.dts
@@ -0,0 +1,106 @@
+/*
+* Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+*
+* SPDX-License-Identifier: Apache-2.0
+*
+*/
+
+/dts-v1/;
+
+#include <mem.h>
+#include <arm/armv8-a.dtsi>
+
+/ {
+	interrupt-parent = <&intc>;
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		cpu@0 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a53";
+			reg = <0>;
+		};
+
+		cpu@1 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a53";
+			reg = <1>;
+		};
+
+		cpu@2 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a53";
+			reg = <2>;
+		};
+
+		cpu@3 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a53";
+			reg = <3>;
+		};
+	};
+
+	timer {
+		compatible = "arm,armv8-timer";
+		interrupt-parent = <&local_intc>;
+		/* TODO: Is the intc nested into the local_intc? Maybe we can tell zephyr to generate the unique interrupt number for us? */
+		interrupts = <72 0 0>, <73 0 0>, <75 0 0>, <74 0 0>; /* CNTPSIRQ, CNTPNSIRQ, CNTVIRQ, CNTHPIRQ */
+		label = "arch_timer";
+	};
+
+	uartclk: apb-pclk {
+		compatible = "fixed-clock";
+		/* BUG: This is not used, instead the parameters are hardcored in uart_pl011. */
+		clock-frequency = <14000000>;
+		#clock-cells = <0>;
+	};
+
+	soc {
+		/* https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson03/rpi-os.md#configuring-interrupt-controller */
+		intc: interrupt-controller@3f00b200 {
+			compatible = "brcm,bcm2835-armctrl-ic";
+			reg = <0x3f00b200 0x200>;
+			interrupt-controller;
+			#interrupt-cells = <3>;
+			label = "intc";
+		};
+
+		local_intc: local_intc@40000000 {
+			compatible = "brcm,bcm2835-l1-intc";
+			reg = <0x40000000 0x100>;
+			interrupt-controller;
+			#interrupt-cells = <3>;
+			label = "l1-intc";
+		};
+
+		uart0: uart@3f201000 {
+			compatible = "arm,pl011";
+			/* #clock-cells = <1>; */
+			reg = <0x3f201000 0x1000>;
+			status = "disabled";
+			interrupts = <57 0 0>;
+			interrupt-names = "irq_0";
+			clocks = <&uartclk>;
+			label = "UART_0";
+		};
+	};
+};
+
+/ {
+	model = "Raspberry Pi 3 Model B+";
+	compatible = "qemu,arm-cortex-a53";
+
+	chosen {
+		zephyr,console = &uart0;
+		zephyr,shell-uart = &uart0;
+		/* TODO: why has this no effect? falls back to size 0xffffffffffff6000 */
+		/* zephyr,sram = &sram; */
+	};
+};
+
+&uart0 {
+	status = "okay";
+	current-speed = <115200>;
+};
diff --git a/boards/arm/raspi3/raspi3.yaml b/boards/arm/raspi3/raspi3.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..eda12d153090a5bb29d72649753339a81971f22c
--- /dev/null
+++ b/boards/arm/raspi3/raspi3.yaml
@@ -0,0 +1,14 @@
+identifier: raspi3
+name: Raspberry Pi 3
+type: qemu
+simulation: qemu
+arch: arm
+toolchain:
+  - zephyr
+  - cross-compile
+ram: 256
+testing:
+  default: true
+  ignore_tags:
+    - net
+    - bluetooth
diff --git a/boards/arm/raspi3/raspi3_defconfig b/boards/arm/raspi3/raspi3_defconfig
new file mode 100644
index 0000000000000000000000000000000000000000..1a265e0e90cd093967f593b2212eb3362ca830c7
--- /dev/null
+++ b/boards/arm/raspi3/raspi3_defconfig
@@ -0,0 +1,47 @@
+CONFIG_SOC_BCM2837=y
+CONFIG_BOARD_RASPI3=y
+CONFIG_XIP=n
+CONFIG_QEMU_ICOUNT=n
+
+# Enable UART driver
+CONFIG_SERIAL=y
+
+# Enable console
+CONFIG_CONSOLE=y
+CONFIG_UART_CONSOLE=y
+
+# Enable serial port
+CONFIG_UART_PL011=y
+CONFIG_UART_PL011_PORT0=y
+CONFIG_UART_INTERRUPT_DRIVEN=n
+
+CONFIG_SMP=y
+CONFIG_MP_NUM_CPUS=4
+CONFIG_CACHE_MANAGEMENT=y
+CONFIG_TIMEOUT_64BIT=y
+CONFIG_ARMV8_A_NS=y
+
+CONFIG_AARCH64_IMAGE_HEADER=y
+
+# Memory offset to which the kerenl image is loaded by the bootloader.
+# If this is wrong, references to static data in the kernel image are off.
+# https://wiki.osdev.org/Raspberry_Pi_Bare_Bones#Pi_3.2C_4
+CONFIG_SRAM_BASE_ADDRESS=0x200000
+CONFIG_KERNEL_VM_BASE=0x200000
+
+# In KiB.
+CONFIG_SRAM_SIZE=256
+
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+
+CONFIG_ARM_ARCH_TIMER=y
+
+CONFIG_DEBUG=y
+CONFIG_ASSERT=y
+CONFIG_ASSERT_VERBOSE=y
+CONFIG_STACK_SENTINEL=y
+CONFIG_INIT_STACKS=y
+
+CONFIG_TICKLESS_KERNEL=n
+CONFIG_LOG_PRINTK=n
diff --git a/drivers/serial/uart_pl011.c b/drivers/serial/uart_pl011.c
index a5eb3757cd86cedbc69f49335ad16d2d051210c2..11578b35b71e37d7e668737d8a39b4438b82974b 100644
--- a/drivers/serial/uart_pl011.c
+++ b/drivers/serial/uart_pl011.c
@@ -185,8 +185,13 @@ static int pl011_set_baudrate(const struct device *dev,
 		return -EINVAL;
 	}
 
+#ifdef CONFIG_BOARD_RASPI3
+	PL011_REGS(dev)->ibrd = 26;
+	PL011_REGS(dev)->fbrd = 3;
+#else
 	PL011_REGS(dev)->ibrd = bauddiv >> PL011_FBRD_WIDTH;
 	PL011_REGS(dev)->fbrd = bauddiv & ((1u << PL011_FBRD_WIDTH) - 1u);
+#endif
 
 	__DMB();
 
@@ -356,11 +361,51 @@ static const struct uart_driver_api pl011_driver_api = {
 #endif /* CONFIG_UART_INTERRUPT_DRIVEN */
 };
 
+#ifdef CONFIG_BOARD_RASPI3
+#define PBASE 0x3F000000
+
+#define GPFSEL1 (PBASE + 0x00200004)
+#define GPSET0 (PBASE + 0x0020001C)
+#define GPCLR0 (PBASE + 0x00200028)
+#define GPPUD (PBASE + 0x00200094)
+#define GPPUDCLK0 (PBASE + 0x00200098)
+
+static void write32_sys(uintptr_t addr, uint32_t val) {
+	sys_write32(val, addr);
+}
+
+#define GPIO_ALT0 4 // UART0/PL011
+#define GPIO_ALT5 2 // UART1/Mini/AUX
+
+static void raspi3_gpio_enable_pl011(void)
+{
+	unsigned int selector;  // 32 bits
+
+	selector = sys_read32(GPFSEL1);
+	selector &= ~(7 << 12);
+	selector |= GPIO_ALT0 << 12; // Set GPIO14
+	selector &= ~(7 << 15);
+	selector |= GPIO_ALT0 << 15; // Set GPIO15
+	write32_sys(GPFSEL1, selector);
+
+	// Set GPIO pull-up/down. Common to Mini UART (AUX) and PL011.
+	write32_sys(GPPUD, 0);
+	k_busy_wait(150); 	/* 150 cycles ~= 150us */
+	write32_sys(GPPUDCLK0, (1 << 14) | (1 << 15));
+	k_busy_wait(150);
+	write32_sys(GPPUDCLK0, 0);
+}
+#endif
+
 static int pl011_init(const struct device *dev)
 {
 	int ret;
 	uint32_t lcrh;
 
+#ifdef CONFIG_BOARD_RASPI3
+	raspi3_gpio_enable_pl011();
+#endif
+
 	/*
 	 * If working in SBSA mode, we assume that UART is already configured,
 	 * or does not require configuration at all (if UART is emulated by
diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index 7e6c75bdde145703f95c317ce42fd5c1c6d82d20..7b9dade22c1fd9f832921424c0bf6ae7a4e1f1c9 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -81,7 +81,6 @@ config ARCV2_TIMER_IRQ_PRIORITY
 
 config ARM_ARCH_TIMER
 	bool "ARM architected timer"
-	depends on GIC
 	select ARCH_HAS_CUSTOM_BUSY_WAIT
 	select TICKLESS_CAPABLE
 	help
diff --git a/dts/bindings/interrupt-controller/brcm,bcm2835-armctrl-ic.yaml b/dts/bindings/interrupt-controller/brcm,bcm2835-armctrl-ic.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..93386a80ec19df90323fc7331560862041686e9f
--- /dev/null
+++ b/dts/bindings/interrupt-controller/brcm,bcm2835-armctrl-ic.yaml
@@ -0,0 +1,21 @@
+# Copyright (c) 2018 Marvell
+# SPDX-License-Identifier: Apache-2.0
+
+description: Interrupt Controller
+
+compatible: "brcm,bcm2835-armctrl-ic"
+
+include: base.yaml
+
+properties:
+    reg:
+      required: true
+
+    label:
+      required: true
+
+# GIC-compatible to allow reuse of arm,arm-timer. flags and priority are always 0.
+interrupt-cells:
+  - irq
+  - flags
+  - priority
diff --git a/dts/bindings/interrupt-controller/brcm,bcm2835-l1-intc.yaml b/dts/bindings/interrupt-controller/brcm,bcm2835-l1-intc.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..917b981413662d8f274342a93db585749a91da9c
--- /dev/null
+++ b/dts/bindings/interrupt-controller/brcm,bcm2835-l1-intc.yaml
@@ -0,0 +1,21 @@
+# Copyright (c) 2018 Marvell
+# SPDX-License-Identifier: Apache-2.0
+
+description: Interrupt Controller
+
+compatible: "brcm,bcm2835-l1-intc"
+
+include: base.yaml
+
+properties:
+    reg:
+      required: true
+
+    label:
+      required: true
+
+# GIC-compatible to allow reuse of arm,arm-timer. flags and priority are always 0.
+interrupt-cells:
+  - irq
+  - flags
+  - priority
diff --git a/dts/bindings/serial/arm,raspi3.yaml b/dts/bindings/serial/arm,raspi3.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f1731792e479dd1d6b3bd2ee85d4712bb947ee9a
--- /dev/null
+++ b/dts/bindings/serial/arm,raspi3.yaml
@@ -0,0 +1,9 @@
+description: raspi3 UART
+
+compatible: "arm,raspi3"
+
+include: uart-controller.yaml
+
+properties:
+    reg:
+      required: true
diff --git a/soc/arm/bcm2837/CMakeLists.txt b/soc/arm/bcm2837/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..584f43dab059861d6e234012098733b0ab5b5e0a
--- /dev/null
+++ b/soc/arm/bcm2837/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+zephyr_library_sources_ifdef(CONFIG_ARM_MMU mmu_regions.c)
+
+zephyr_sources_ifdef(CONFIG_SOC_BCM2837    plat_core.S)
+zephyr_sources_ifdef(CONFIG_SOC_BCM2837    intc_bcm2837.c)
diff --git a/soc/arm/bcm2837/Kconfig.defconfig b/soc/arm/bcm2837/Kconfig.defconfig
new file mode 100644
index 0000000000000000000000000000000000000000..801c488d498e2e6eceb0e170116e171dcc54017c
--- /dev/null
+++ b/soc/arm/bcm2837/Kconfig.defconfig
@@ -0,0 +1,30 @@
+# Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+if SOC_BCM2837
+
+config SOC
+	default "bcm2837"
+
+config NUM_IRQS
+	# must be >= the highest interrupt number used
+	# - include the UART interrupts
+    # via BCM2837-PM Page 113
+	default 128
+
+config SYS_CLOCK_HW_CYCLES_PER_SEC
+	default 62500000
+
+# Workaround for not being able to have commas in macro arguments
+DT_CHOSEN_Z_FLASH := zephyr,flash
+
+config FLASH_SIZE
+	default $(dt_chosen_reg_size_int,$(DT_CHOSEN_Z_FLASH),0,K)
+
+config FLASH_BASE_ADDRESS
+	default $(dt_chosen_reg_addr_hex,$(DT_CHOSEN_Z_FLASH))
+
+config ARM_CUSTOM_INTERRUPT_CONTROLLER
+    default y
+
+endif # SOC_BCM2837
diff --git a/soc/arm/bcm2837/Kconfig.soc b/soc/arm/bcm2837/Kconfig.soc
new file mode 100644
index 0000000000000000000000000000000000000000..a5a92e4766464bbca765441363e1613ae10f7789
--- /dev/null
+++ b/soc/arm/bcm2837/Kconfig.soc
@@ -0,0 +1,7 @@
+# Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+# SPDX-License-Identifier: Apache-2.0
+
+config SOC_BCM2837
+	bool "BCM2837 and BCM2837B0 SoC, QEMU virt platform (raspi3)"
+	select ARM
+	select CPU_CORTEX_A53
diff --git a/soc/arm/bcm2837/README.md b/soc/arm/bcm2837/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..135a4cdd1ef6f801d2d9d9deb94eef94787886b8
--- /dev/null
+++ b/soc/arm/bcm2837/README.md
@@ -0,0 +1,9 @@
+# BCM2837
+
+SoC of the Raspberry Pi
+- 2 Mod. B v1.2,
+- 3 Mod. A+ (assumed to be qemu's `-M raspi3`)
+- 3 Mod. B
+- 3 Mod. B+.
+
+Peripheral Manual (BCM2837-PM): https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
diff --git a/soc/arm/bcm2837/intc_bcm2837.c b/soc/arm/bcm2837/intc_bcm2837.c
new file mode 100644
index 0000000000000000000000000000000000000000..1746574b0de0fb8f86c4f48497d7cf755c49bc7f
--- /dev/null
+++ b/soc/arm/bcm2837/intc_bcm2837.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2020 Broadcom
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <sys/__assert.h>
+#include <arch/arm/aarch64/irq.h>
+
+/* TODO: Get these from device tree. */
+#define PBASE 0x3f000000
+#define BCM2836_ARMCTRL_BASE (PBASE+0x0000B200)
+
+/* Definitions copied from linux's irq-bcm2835.c: */
+
+/* Put the bank and irq (32 bits) into the hwirq */
+#define MAKE_HWIRQ(b, n)	((b << 5) | (n))
+#define HWIRQ_BANK(i)		(i >> 5)
+#define HWIRQ_BIT(i)		BIT(i & 0x1f)
+
+#define NR_IRQS_BANK0		8
+#define BANK0_HWIRQ_MASK	0xff
+/* Shortcuts can't be disabled so any unknown new ones need to be masked */
+#define SHORTCUT1_MASK		0x00007c00
+#define SHORTCUT2_MASK		0x001f8000
+#define SHORTCUT_SHIFT		10
+#define BANK1_HWIRQ		BIT(8)
+#define BANK2_HWIRQ		BIT(9)
+#define BANK0_VALID_MASK	(BANK0_HWIRQ_MASK | BANK1_HWIRQ | BANK2_HWIRQ \
+					| SHORTCUT1_MASK | SHORTCUT2_MASK)
+
+#define REG_FIQ_CONTROL		0x0c
+#define FIQ_CONTROL_ENABLE	BIT(7)
+
+#define NR_BANKS		3
+#define IRQS_PER_BANK		32
+
+static const int armctrl_reg_pending[] = { 0x04, 0x08, 0x00 };
+static const int armctrl_reg_enable[] = { 0x10, 0x14, 0x18 };
+static const int armctrl_reg_disable[] = { 0x1c, 0x20, 0x24 };
+static const int armctrl_bank_bits[] = { 32, 32, 8 };
+
+/* Copied from https://github.com/xinu-os/xinu/blob/master/system/platforms/arm-rpi/dispatch.c: */
+
+/* Number of IRQs shared between the GPU and ARM. These correspond to the IRQs
+ * that show up in the IRQ_pending_1 and IRQ_pending_2 registers.  */
+#define ARMCTRL_NUM_GPU_SHARED_IRQS     64
+
+/* Number of ARM-specific IRQs. These correspond to IRQs that show up in the
+ * first 8 bits of IRQ_basic_pending.  */
+#define ARMCTRL_NUM_ARM_SPECIFIC_IRQS   8
+
+#define ARMCTRL_NUM_IRQS (ARMCTRL_NUM_GPU_SHARED_IRQS + ARMCTRL_NUM_ARM_SPECIFIC_IRQS)
+#define L1_NUM_IRQS (32)
+#define NUM_IRQS (ARMCTRL_NUM_IRQS + L1_NUM_IRQS)
+
+static bool irq_enabled[NUM_IRQS];
+
+static void armctrl_irq_to_bank(unsigned int irq, int *bank, int *bank_bit) {
+	__ASSERT_NO_MSG(irq < ARMCTRL_NUM_IRQS);
+	*bank = 0;
+	*bank_bit = irq;
+	while (*bank_bit >= armctrl_bank_bits[*bank]) {
+		*bank_bit -= armctrl_bank_bits[*bank];
+		*bank += 1;
+
+		__ASSERT_NO_MSG(*bank <= 2);
+	}
+	__ASSERT_NO_MSG(0 <= *bank);
+	__ASSERT_NO_MSG(*bank <= 2);
+	__ASSERT_NO_MSG(0 <= *bank_bit);
+	__ASSERT_NO_MSG(*bank_bit < 32);
+}
+
+static void armctrl_irq_enable(unsigned int irq) {
+	int bank, bank_bit;
+	armctrl_irq_to_bank(irq, &bank, &bank_bit);
+	uintptr_t addr = BCM2836_ARMCTRL_BASE + armctrl_reg_enable[bank];
+
+	printk("armctrl_irq_enable irq=%d, bank=%d, bit=%d, addr=%p\n", irq, bank, bank_bit, (char *) addr);
+	sys_write32((1 << bank_bit), addr);
+}
+
+static void armctrl_irq_disable(unsigned int irq) {
+	int bank, bank_bit;
+	armctrl_irq_to_bank(irq, &bank, &bank_bit);
+	uintptr_t addr = BCM2836_ARMCTRL_BASE + armctrl_reg_disable[bank];
+
+	printk("armctrl_irq_enable irq=%d, bank=%d, bit=%d, addr=%p\n", irq, bank, bank_bit, (char *) addr);
+	sys_write32((1 << bank_bit), addr);
+}
+
+/* BCM2836 ARM-local peripherals manual, section 4.10 "Core interrupt sources" */
+/* QEMU Device Implementation: https://github.com/qemu/qemu/blob/918c81a53eb18ec4e9979876a39e86610cc565f4/hw/intc/bcm2836_control.c */
+
+static void l1_irq_enable(unsigned int l1_irq) {
+	__ASSERT_NO_MSG(l1_irq < L1_NUM_IRQS);
+
+	if (0 <= l1_irq && l1_irq <= 3) {
+		/* Core timer interrupts: CNT_PS_IRQ = 0, CNT_PNS_IRQ = 1, CNT_HP_IRQ = 2, CNT_V_IRQ = 3 */
+		int cpuid = arch_curr_cpu()->id;
+		intptr_t core_timer_interrupt_control_addr = 0x40000040 + cpuid * 4;
+		uint32_t val = sys_read32(core_timer_interrupt_control_addr);
+		val |= (1 << l1_irq);
+		sys_write32(val, core_timer_interrupt_control_addr);
+	} else {
+		printk("Warning: l1_irq_enable(%d) not implemented.\n", l1_irq);
+	}
+}
+
+static void l1_irq_disable(unsigned int l1_irq) {
+	__ASSERT_NO_MSG(l1_irq < L1_NUM_IRQS);
+	printk("Warning: l1_irq_disable(%d) not implemented.\n", l1_irq);
+}
+
+void z_soc_irq_init(void) {
+	/* Don't print anything here as C might not be initialized yet. */
+}
+
+int z_soc_irq_is_enabled(unsigned int irq) {
+	/* Manual does not explicitly state that reading from armctrl_reg_enable
+	 * indicates if an IRQ was enabled, also Xinu does not do it like
+	 * this. That's why we have irq_enabled. */
+	__ASSERT_NO_MSG(irq < ARMCTRL_NUM_IRQS);
+	return irq_enabled[irq];
+}
+
+void z_soc_irq_enable(unsigned int irq) {
+	if (irq < ARMCTRL_NUM_IRQS) {
+		armctrl_irq_enable(irq);
+		irq_enabled[irq] = true;
+	} else {
+		l1_irq_enable(irq - ARMCTRL_NUM_IRQS);
+	}
+}
+
+void z_soc_irq_disable(unsigned int irq) {
+	if (irq < ARMCTRL_NUM_IRQS) {
+		armctrl_irq_disable(irq);
+		irq_enabled[irq] = false;
+	} else {
+		l1_irq_disable(irq - ARMCTRL_NUM_IRQS);
+	}
+}
+
+void z_soc_irq_priority_set(unsigned int irq, unsigned int prio, unsigned int flags) {
+	/* Not supported. */
+
+	/* Peripheral Manual: There is no priority for any interrupt. If one
+	 * interrupt is much more important then all others it canbe routed to
+	 * the FIQ. */
+	return;
+}
+
+/* Called from HW IRQ context with interrupts disabled: Retrieve the currently
+ * active IRQ that should be handled.
+ *
+ * Resources:
+ * - https://embedded-xinu.readthedocs.io/en/latest/arm/rpi/BCM2835-Interrupt-Controller.html
+ */
+unsigned int z_soc_irq_get_active(void) {
+	int cpuid = arch_curr_cpu()->id;
+
+	/* printk("%s on cpu%d, stack at ~%p\n", __func__, cpuid, &cpuid); */
+
+	for (unsigned int irq = 0; irq < ARMCTRL_NUM_IRQS; irq++) {
+		int bank, bank_bit;
+		armctrl_irq_to_bank(irq, &bank, &bank_bit);
+
+		unsigned int bank_pending = sys_read32(BCM2836_ARMCTRL_BASE + armctrl_reg_pending[bank]);
+		bool is_pending = (bank_pending & (1 << bank_bit)) != 0;
+
+		if (is_pending) {
+			/* Clear pending to allow us to detect when this IRQ occurs again (nested irq). */
+			sys_write32(bank_pending & ~(1 << bank_bit), BCM2836_ARMCTRL_BASE + armctrl_reg_pending[bank]);
+			return irq;
+		}
+	}
+
+	intptr_t core_interrupt_sources_addr = 0x40000060 + cpuid * 4;
+	uint32_t pending = sys_read32(core_interrupt_sources_addr);
+	for (unsigned int l1_irq = 0; l1_irq < L1_NUM_IRQS; l1_irq++) {
+		unsigned int irq = ARMCTRL_NUM_IRQS + l1_irq;
+		if (pending & (1 << l1_irq)) {
+			/* https://developer.arm.com/architectures/learn-the-architecture/generic-timer/single-page */
+			if (l1_irq == 3) {
+				arm_arch_timer_set_irq_mask(true);
+			}
+			return irq;
+		}
+	}
+
+	__ASSERT_NO_MSG(false);
+	CODE_UNREACHABLE;
+}
+
+/* Mark irq as inactive (called in HW IRQ context after handler). */
+void z_soc_irq_eoi(unsigned int irq) {
+	if (irq < ARMCTRL_NUM_IRQS) {
+		printk("Warning: %s(%d)\n", __func__, irq);
+	} else {
+		unsigned int l1_irq = irq - ARMCTRL_NUM_IRQS;
+		if (l1_irq == 3) {
+			arm_arch_timer_set_irq_mask(false);
+		}
+	}
+}
diff --git a/soc/arm/bcm2837/linker.ld b/soc/arm/bcm2837/linker.ld
new file mode 100644
index 0000000000000000000000000000000000000000..fd1bad9e1d6d30ed1a4450dd67a4ce721c9ddd6a
--- /dev/null
+++ b/soc/arm/bcm2837/linker.ld
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <arch/arm/aarch64/scripts/linker.ld>
diff --git a/soc/arm/bcm2837/mmu_regions.c b/soc/arm/bcm2837/mmu_regions.c
new file mode 100644
index 0000000000000000000000000000000000000000..0cb8238581abc3af69aa2cb8fdf8ae6c10022961
--- /dev/null
+++ b/soc/arm/bcm2837/mmu_regions.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Broadcom
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <soc.h>
+#include <arch/arm/aarch64/arm_mmu.h>
+
+#define SZ_1K	1024
+
+static const struct arm_mmu_region mmu_regions[] = {
+#ifdef CONFIG_GIC_V3
+	MMU_REGION_FLAT_ENTRY("GIC",
+			      DT_REG_ADDR_BY_IDX(DT_INST(0, arm_gic), 0),
+			      DT_REG_SIZE_BY_IDX(DT_INST(0, arm_gic), 0),
+			      MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_DEFAULT_SECURE_STATE),
+
+	MMU_REGION_FLAT_ENTRY("GIC",
+			      DT_REG_ADDR_BY_IDX(DT_INST(0, arm_gic), 1),
+			      DT_REG_SIZE_BY_IDX(DT_INST(0, arm_gic), 1),
+			      MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_DEFAULT_SECURE_STATE),
+#else
+	MMU_REGION_FLAT_ENTRY("INTC_BCM2837_L1",
+			      0x40000000,
+			      0x1000,
+			      MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_DEFAULT_SECURE_STATE),
+	MMU_REGION_FLAT_ENTRY("INTC_BCM2837_ARMCTRK",
+			      0x3f00b000,
+			      0x1000,
+			      MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_DEFAULT_SECURE_STATE),
+#endif
+	MMU_REGION_FLAT_ENTRY("GP",
+			      0x3f200000,
+			      0x1000,
+			      MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_DEFAULT_SECURE_STATE),
+#ifdef CONFIG_UART_PL011
+	MMU_REGION_FLAT_ENTRY("UART",
+			      DT_REG_ADDR(DT_INST(0, arm_pl011)),
+			      DT_REG_SIZE(DT_INST(0, arm_pl011)),
+			      MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_DEFAULT_SECURE_STATE),
+#endif
+#if defined(CONFIG_SMP) || defined(CONFIG_ZTEST)
+	MMU_REGION_FLAT_ENTRY("SPINTABLE",
+			      0x00000000,
+			      0x1000,
+			      MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_DEFAULT_SECURE_STATE),
+#endif
+};
+
+const struct arm_mmu_config mmu_config = {
+	.num_regions = ARRAY_SIZE(mmu_regions),
+	.mmu_regions = mmu_regions,
+};
diff --git a/soc/arm/bcm2837/plat_core.S b/soc/arm/bcm2837/plat_core.S
new file mode 100644
index 0000000000000000000000000000000000000000..c1ee72db6c1caf7c0ebaca6ef767916fc5dbac23
--- /dev/null
+++ b/soc/arm/bcm2837/plat_core.S
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 Carlo Caione <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ *@file
+ *@brief plat/core specific init
+*/
+
+#include <toolchain.h>
+#include <linker/sections.h>
+#include <arch/cpu.h>
+
+_ASM_FILE_PROLOGUE
+
+GTEXT(z_arch_el3_plat_init)
+
+SECTION_FUNC(TEXT, z_arch_el3_plat_init)
+
+	mov	x20, x30
+
+#ifdef CONFIG_GIC_V3
+	/* Enable GIC v3 system interface */
+	mov_imm	x0, (ICC_SRE_ELx_DFB | ICC_SRE_ELx_DIB | \
+		     ICC_SRE_ELx_SRE | ICC_SRE_EL3_EN)
+	msr	ICC_SRE_EL3, x0
+#endif
+
+	mov	x30, x20
+	ret
diff --git a/soc/arm/bcm2837/soc.h b/soc/arm/bcm2837/soc.h
new file mode 100644
index 0000000000000000000000000000000000000000..889f634fe3e0997dc1e1afd4177eae669f915675
--- /dev/null
+++ b/soc/arm/bcm2837/soc.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#ifndef _SOC_H_
+#define _SOC_H_
+
+#include <sys/util.h>
+
+#ifndef _ASMLANGUAGE
+
+#include <device.h>
+
+#endif /* !_ASMLANGUAGE */
+
+#endif /* _SOC_H_ */