diff --git a/Makefile b/Makefile
index c79264bc9e9bdb804d90006983cd7b55da7b1a71..eed7c7a8559e8fc76d4b1e8d73c4c9a124fdd8da 100644
--- a/Makefile
+++ b/Makefile
@@ -214,6 +214,7 @@ include lib/libc/libc.mk
 BL_COMMON_SOURCES	+=	common/bl_common.c			\
 				common/tf_log.c				\
 				common/${ARCH}/debug.S			\
+				drivers/console/multi_console.c		\
 				lib/${ARCH}/cache_helpers.S		\
 				lib/${ARCH}/misc_helpers.S		\
 				plat/common/plat_bl_common.c		\
diff --git a/docs/porting-guide.rst b/docs/porting-guide.rst
index 4f1638a99fc7575300c2e718fc7ee4172769f18f..f1a26f42eda281ce4d194a71cc09c3f9b1655f34 100644
--- a/docs/porting-guide.rst
+++ b/docs/porting-guide.rst
@@ -2550,9 +2550,6 @@ as Group 0 secure interrupt, Group 1 secure interrupt or Group 1 NS interrupt.
 Crash Reporting mechanism (in BL31)
 -----------------------------------
 
-NOTE: This section assumes that your platform is enabling the MULTI_CONSOLE_API
-flag in its platform.mk. Not using this flag is deprecated for new platforms.
-
 BL31 implements a crash reporting mechanism which prints the various registers
 of the CPU to enable quick crash analysis and debugging. This mechanism relies
 on the platform implementating ``plat_crash_console_init``,
@@ -2564,15 +2561,18 @@ makefiles in order to benefit from them. By default, they will cause the crash
 output to be routed over the normal console infrastructure and get printed on
 consoles configured to output in crash state. ``console_set_scope()`` can be
 used to control whether a console is used for crash output.
+NOTE: Platforms are responsible for making sure that they only mark consoles for
+use in the crash scope that are able to support this, i.e. that are written in
+assembly and conform with the register clobber rules for putc() (x0-x2, x16-x17)
+and flush() (x0-x3, x16-x17) crash callbacks.
 
 In some cases (such as debugging very early crashes that happen before the
 normal boot console can be set up), platforms may want to control crash output
-more explicitly. For these, the following functions can be overridden by
-platform code. They are executed outside of a C environment and without a stack.
-
-If this behaviour is not desirable, the platform may implement functions that
-redirect the prints to the console driver (``console_xxx_core_init``, etc). Most
-platforms (including Arm platforms) do this and they can be used as an example.
+more explicitly. These platforms may instead provide custom implementations for
+these. They are executed outside of a C environment and without a stack. Many
+console drivers provide functions named ``console_xxx_core_init/putc/flush``
+that are designed to be used by these functions. See Arm platforms (like juno)
+for an example of this.
 
 Function : plat\_crash\_console\_init [mandatory]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2586,28 +2586,6 @@ This API is used by the crash reporting mechanism to initialize the crash
 console. It must only use the general purpose registers x0 through x7 to do the
 initialization and returns 1 on success.
 
-When using the sample implementation, if you are trying to debug crashes before
-the console driver would normally get registered, you can use this to register a
-driver from assembly with hardcoded parameters. For example, you could register
-the 16550 driver like this:
-
-::
-
-    .section .data.crash_console      /* Reserve space for console structure */
-    crash_console:
-    .zero 6 * 8                       /* console_16550_t has 6 8-byte words */
-    func plat_crash_console_init
-        ldr     x0, =YOUR_16550_BASE_ADDR
-        ldr     x1, =YOUR_16550_SRCCLK_IN_HZ
-        ldr     x2, =YOUR_16550_TARGET_BAUD_RATE
-        adrp    x3, crash_console
-        add     x3, x3, :lo12:crash_console
-        b       console_16550_register  /* tail call, returns 1 on success */
-    endfunc plat_crash_console_init
-
-If you're trying to debug crashes in BL1, you can call the
-``console_xxx_core_init`` function exported by some console drivers from here.
-
 Function : plat\_crash\_console\_putc [mandatory]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -2621,12 +2599,6 @@ designated crash console. It must only use general purpose registers x1 and
 x2 to do its work. The parameter and the return value are in general purpose
 register x0.
 
-If you have registered a normal console driver in ``plat_crash_console_init``,
-you can keep the sample implementation here (which calls ``console_putc()``).
-
-If you're trying to debug crashes in BL1, you can call the
-``console_xxx_core_putc`` function exported by some console drivers from here.
-
 Function : plat\_crash\_console\_flush [mandatory]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -2640,12 +2612,6 @@ data on the designated crash console. It should only use general purpose
 registers x0 through x5 to do its work. The return value is 0 on successful
 completion; otherwise the return value is -1.
 
-If you have registered a normal console driver in ``plat_crash_console_init``,
-you can keep the sample implementation here (which calls ``console_flush()``).
-
-If you're trying to debug crashes in BL1, you can call the console_xx_core_flush
-function exported by some console drivers from here.
-
 External Abort handling and RAS Support
 ---------------------------------------
 
diff --git a/drivers/arm/pl011/aarch32/pl011_console.S b/drivers/arm/pl011/aarch32/pl011_console.S
index 46ff225870c57d799f6b6e043c06e5c99295a7c7..5d6b95fe3ea5e4e9afd25e2a164762db8594158b 100644
--- a/drivers/arm/pl011/aarch32/pl011_console.S
+++ b/drivers/arm/pl011/aarch32/pl011_console.S
@@ -10,11 +10,13 @@
 #include <console_macros.S>
 #include <pl011.h>
 
+#if !MULTI_CONSOLE_API
 /*
  * Pull in generic functions to provide backwards compatibility for
  * platform makefiles
  */
 #include "../../../console/aarch32/console.S"
+#endif
 
 	/*
 	 * "core" functions are low-level implementations that don't require
diff --git a/drivers/arm/pl011/aarch64/pl011_console.S b/drivers/arm/pl011/aarch64/pl011_console.S
index 3886f3b77c72909435c8b6560a88360dd77b6dcf..7fec0904d9189c3a01c9682fe6ae9abcd3d088e2 100644
--- a/drivers/arm/pl011/aarch64/pl011_console.S
+++ b/drivers/arm/pl011/aarch64/pl011_console.S
@@ -10,11 +10,13 @@
 #include <console_macros.S>
 #include <pl011.h>
 
+#if !MULTI_CONSOLE_API
 /*
  * Pull in generic functions to provide backwards compatibility for
  * platform makefiles
  */
 #include "../../../console/aarch64/console.S"
+#endif
 
 	/*
 	 * "core" functions are low-level implementations that don't require
diff --git a/drivers/console/aarch32/console.S b/drivers/console/aarch32/console.S
index f909609469a849a0667ebf0d925b42b039a28e95..1c380944d47d2a52d73baa045427677e3dd5dadf 100644
--- a/drivers/console/aarch32/console.S
+++ b/drivers/console/aarch32/console.S
@@ -5,7 +5,9 @@
  */
 
  #if MULTI_CONSOLE_API
- #include "multi_console.S"
+ #if ERROR_DEPRECATED
+ #error "console.S is deprecated, platforms should no longer link it explicitly"
+ #endif
  #else
  #include "deprecated_console.S"
  #endif
diff --git a/drivers/console/aarch32/multi_console.S b/drivers/console/aarch32/multi_console.S
deleted file mode 100644
index e23b20e56ae7083e0b2ca4b7317df01b0da64f04..0000000000000000000000000000000000000000
--- a/drivers/console/aarch32/multi_console.S
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (c) 2015-2018, ARM Limited and Contributors. All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-
-#include <asm_macros.S>
-#include <assert_macros.S>
-#include <console.h>
-
-	.globl	console_register
-	.globl	console_unregister
-	.globl	console_is_registered
-	.globl	console_set_scope
-	.globl	console_switch_state
-	.globl	console_putc
-	.globl	console_getc
-	.globl	console_flush
-
-	/*
-	 *  The console list pointer is in the data section and not in
-	 *  .bss even though it is zero-init. In particular, this allows
-	 *  the console functions to start using this variable before
-	 *  the runtime memory is initialized for images which do not
-	 *  need to copy the .data section from ROM to RAM.
-	 */
-.section .data.console_list ; .align 2
-	console_list: .word 0x0
-.section .data.console_state ; .align 0
-	console_state: .byte CONSOLE_FLAG_BOOT
-
-	/* -----------------------------------------------
-	 * int console_register(console_t *console)
-	 * Function to insert a new console structure into
-	 * the console list. Should usually be called by
-	 * console_<driver>_register implementations. The
-	 * data structure passed will be taken over by the
-	 * console framework and *MUST* be allocated in
-	 * persistent memory (e.g. the data section).
-	 * In : r0 - address of console_t structure
-	 * Out: r0 - Always 1 (for easier tail calling)
-	 * Clobber list: r0, r1
-	 * -----------------------------------------------
-	 */
-func console_register
-	push	{r6,  lr}
-#if ENABLE_ASSERTIONS
-	/* Assert that r0 isn't a NULL pointer */
-	cmp	r0, #0
-	ASM_ASSERT(ne)
-	/* Assert that the struct isn't in the stack */
-	ldr	r1, =__STACKS_START__
-	cmp	r0, r1
-	blo	not_on_stack
-	ldr	r1, =__STACKS_END__
-	cmp	r0, r1
-	ASM_ASSERT(hs)
-not_on_stack:
-	/* Assert that this struct isn't in the list */
-	mov	r1, r0 /* Preserve r0 and lr */
-	bl	console_is_registered
-	cmp	r0, #0
-	ASM_ASSERT(eq)
-	mov	r0, r1
-#endif /* ENABLE_ASSERTIONS */
-	ldr	r6, =console_list
-	ldr	r1, [r6]	/* R1 = first struct in list */
-	str	r0, [r6]	/* list head = new console */
-	str	r1, [r0, #CONSOLE_T_NEXT]	/* new console next ptr = R1 */
-	mov	r0, #1
-	pop	{r6, pc}
-endfunc console_register
-
-	/* -----------------------------------------------
-	 * int console_unregister(console_t *console)
-	 * Function to find a specific console in the list
-	 * of currently active consoles and remove it.
-	 * In: r0 - address of console_t struct to remove
-	 * Out: r0 - removed address, or NULL if not found
-	 * Clobber list: r0, r1
-	 * -----------------------------------------------
-	 */
-func console_unregister
-#if ENABLE_ASSERTIONS
-	/* Assert that r0 isn't a NULL pointer */
-	cmp	r0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	push	{r6}
-	ldr	r6, =console_list		/* R6 = ptr to first struct */
-	ldr	r1, [r6]			/* R1 = first struct */
-
-unregister_loop:
-	cmp	r1, #0
-	beq	unregister_not_found
-	cmp	r0, r1
-	beq	unregister_found
-	ldr	r6, [r6]			/* R6 = next ptr of struct */
-	ldr	r1, [r6]			/* R1 = next struct */
-	b	unregister_loop
-
-unregister_found:
-	ldr	r1, [r1]			/* R1 = next struct */
-	str	r1, [r6]			/* prev->next = cur->next */
-	pop	{r6}
-	bx	lr
-
-unregister_not_found:
-	mov	r0, #0				/* return NULL if not found */
-	pop	{r6}
-	bx	lr
-endfunc console_unregister
-
-	/* -----------------------------------------------
-	 * int console_is_registered(console_t *console)
-	 * Function to detect if a specific console is
-	 * registered or not.
-	 * In: r0 - address of console_t struct to remove
-	 * Out: r0 - 1 if it is registered, 0 if not.
-	 * Clobber list: r0
-	 * -----------------------------------------------
-	 */
-func console_is_registered
-#if ENABLE_ASSERTIONS
-	/* Assert that r0 isn't a NULL pointer */
-	cmp	r0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	push	{r6}
-	ldr	r6, =console_list
-	ldr	r6, [r6]	/* R6 = first console struct */
-check_registered_loop:
-	cmp	r6, #0			/* Check if end of list */
-	beq	console_not_registered
-	cmp	r0, r6		/* Check if the pointers are different */
-	beq	console_registered
-	ldr	r6, [r6, #CONSOLE_T_NEXT]	/* Get pointer to next struct */
-	b	check_registered_loop
-console_not_registered:
-	mov	r0, #0
-	pop	{r6}
-	bx	lr
-console_registered:
-	mov	r0, #1
-	pop	{r6}
-	bx	lr
-endfunc console_is_registered
-
-	/* -----------------------------------------------
-	 * void console_switch_state(unsigned int new_state)
-	 * Function to switch the current console state.
-	 * The console state determines which of the
-	 * registered consoles are actually used at a time.
-	 * In : r0 - global console state to move to
-	 * Clobber list: r0, r1
-	 * -----------------------------------------------
-	 */
-func console_switch_state
-	ldr	r1, =console_state
-	strb	r0, [r1]
-	bx	lr
-endfunc console_switch_state
-
-	/* -----------------------------------------------
-	 * void console_set_scope(console_t *console,
-	 *                       unsigned int scope)
-	 * Function to update the states that a given console
-	 * may be active in.
-	 * In : r0 - pointer to console_t struct
-	 *    : r1 - new active state mask
-	 * Clobber list: r0, r1, r2
-	 * -----------------------------------------------
-	 */
-func console_set_scope
-#if ENABLE_ASSERTIONS
-	ands	r2, r1, #~CONSOLE_FLAG_SCOPE_MASK
-	ASM_ASSERT(eq)
-#endif /* ENABLE_ASSERTIONS */
-	ldr	r2, [r0, #CONSOLE_T_FLAGS]
-	and	r2, r2, #~CONSOLE_FLAG_SCOPE_MASK
-	orr	r2, r2, r1
-	str	r2, [r0, #CONSOLE_T_FLAGS]
-	bx	lr
-endfunc console_set_scope
-
-	/* ---------------------------------------------
-	 * int console_putc(int c)
-	 * Function to output a character. Calls all
-	 * active console's putc() handlers in succession.
-	 * In : r0 - character to be printed
-	 * Out: r0 - printed character on success, or < 0
-	             if at least one console had an error
-	 * Clobber list : r0, r1, r2
-	 * ---------------------------------------------
-	 */
-func console_putc
-	push	{r4-r6, lr}
-	mov	r5, #ERROR_NO_VALID_CONSOLE	/* R5 = current return value */
-	mov	r4, r0				/* R4 = character to print */
-	ldr	r6, =console_list
-	ldr	r6, [r6]	/* R6 = first console struct */
-
-putc_loop:
-	cmp	r6, #0
-	beq	putc_done
-	ldr	r1, =console_state
-	ldrb	r1, [r1]
-	ldr	r2, [r6, #CONSOLE_T_FLAGS]
-	tst	r1, r2
-	beq	putc_continue
-	ldr	r2, [r6, #CONSOLE_T_PUTC]
-	cmp	r2, #0
-	beq	putc_continue
-	mov	r0, r4
-	mov	r1, r6
-	blx	r2
-	cmp	r5, #ERROR_NO_VALID_CONSOLE	/* update R5 if it's NOVALID */
-	cmpne	r0, #0				/* else update it if R0 < 0 */
-	movlt	r5, r0
-putc_continue:
-	ldr	r6, [r6]			/* R6 = next struct */
-	b	putc_loop
-
-putc_done:
-	mov	r0, r5
-	pop	{r4-r6, pc}
-endfunc console_putc
-
-	/* ---------------------------------------------
-	 * int console_getc(void)
-	 * Function to get a character from any console.
-	 * Keeps looping through all consoles' getc()
-	 * handlers until one of them returns a
-	 * character, then stops iterating and returns
-	 * that character to the caller. Will stop looping
-	 * if all active consoles report real errors
-	 * (other than just not having a char available).
-	 * Out : r0 - read character, or < 0 on error
-	 * Clobber list : r0, r1
-	 * ---------------------------------------------
-	 */
-func console_getc
-	push	{r5-r6, lr}
-getc_try_again:
-	mov	r5, #ERROR_NO_VALID_CONSOLE	/* R5 = current return value */
-	ldr	r6, =console_list
-	ldr	r6, [r6]			/* R6 = first console struct */
-	cmp	r6, #0
-	bne	getc_loop
-	mov	r0, r5				/* If no consoles registered */
-	pop	{r5-r6, pc}			/* return immediately. */
-
-getc_loop:
-	ldr	r0, =console_state
-	ldrb	r0, [r0]
-	ldr	r1, [r6, #CONSOLE_T_FLAGS]
-	tst	r0, r1
-	beq	getc_continue
-	ldr	r1, [r6, #CONSOLE_T_GETC]
-	cmp	r1, #0
-	beq	getc_continue
-	mov	r0, r6
-	blx	r1
-	cmp	r0, #0				/* if R0 >= 0: return */
-	bge	getc_found
-	cmp	r5, #ERROR_NO_PENDING_CHAR	/* may update R5 (NOCHAR has */
-	movne	r5, r0				/* precedence vs real errors) */
-getc_continue:
-	ldr	r6, [r6]			/* R6 = next struct */
-	cmp	r6, #0
-	bne	getc_loop
-	cmp	r5, #ERROR_NO_PENDING_CHAR	/* Keep scanning if at least */
-	beq	getc_try_again			/* one console returns NOCHAR */
-	mov	r0, r5
-
-getc_found:
-	pop	{r5-r6, pc}
-endfunc console_getc
-
-	/* ---------------------------------------------
-	 * int console_flush(void)
-	 * Function to force a write of all buffered
-	 * data that hasn't been output. Calls all
-	 * console's flush() handlers in succession.
-	 * Out: r0 - 0 on success, < 0 if at least one error
-	 * Clobber list : r0, r1, r2
-	 * ---------------------------------------------
-	 */
-func console_flush
-	push	{r5-r6, lr}
-	mov	r5, #ERROR_NO_VALID_CONSOLE	/* R5 = current return value */
-	ldr	r6, =console_list
-	ldr	r6, [r6]			/* R6 = first console struct */
-
-flush_loop:
-	cmp	r6, #0
-	beq	flush_done
-	ldr	r1, =console_state
-	ldrb	r1, [r1]
-	ldr	r2, [r6, #CONSOLE_T_FLAGS]
-	tst	r1, r2
-	beq	flush_continue
-	ldr	r1, [r6, #CONSOLE_T_FLUSH]
-	cmp	r1, #0
-	beq	flush_continue
-	mov	r0, r6
-	blx	r1
-	cmp	r5, #ERROR_NO_VALID_CONSOLE	/* update R5 if it's NOVALID */
-	cmpne	r0, #0				/* else update it if R0 < 0 */
-	movlt	r5, r0
-flush_continue:
-	ldr	r6, [r6]			/* R6 = next struct */
-	b	flush_loop
-
-flush_done:
-	mov	r0, r5
-	pop	{r5-r6, pc}
-endfunc console_flush
diff --git a/drivers/console/aarch64/console.S b/drivers/console/aarch64/console.S
index f847ed597811ba338b93c56476849b5727fd2e9c..669b31a6672dd68522ea1ebc7b7b00fd6708daab 100644
--- a/drivers/console/aarch64/console.S
+++ b/drivers/console/aarch64/console.S
@@ -5,7 +5,9 @@
  */
 
 #if MULTI_CONSOLE_API
-#include "multi_console.S"
+#if ERROR_DEPRECATED
+#error "console.S is deprecated, platforms should no longer link it explicitly"
+#endif
 #else
 #include "deprecated_console.S"
 #endif
diff --git a/drivers/console/aarch64/multi_console.S b/drivers/console/aarch64/multi_console.S
deleted file mode 100644
index 7f076c6233c7a5e79dbf3adec4011578a3afb0f4..0000000000000000000000000000000000000000
--- a/drivers/console/aarch64/multi_console.S
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (c) 2015-2018, ARM Limited and Contributors. All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-
-#include <asm_macros.S>
-#include <assert_macros.S>
-#include <console.h>
-
-	.globl	console_register
-	.globl	console_unregister
-	.globl	console_is_registered
-	.globl	console_set_scope
-	.globl	console_switch_state
-	.globl	console_putc
-	.globl	console_getc
-	.globl	console_flush
-
-	/*
-	 *  The console list pointer is in the data section and not in
-	 *  .bss even though it is zero-init. In particular, this allows
-	 *  the console functions to start using this variable before
-	 *  the runtime memory is initialized for images which do not
-	 *  need to copy the .data section from ROM to RAM.
-	 */
-.section .data.console_list ; .align 3
-	console_list: .quad 0x0
-.section .data.console_state ; .align 0
-	console_state: .byte CONSOLE_FLAG_BOOT
-
-	/* -----------------------------------------------
-	 * int console_register(console_t *console)
-	 * Function to insert a new console structure into
-	 * the console list. Should usually be called by
-	 * console_<driver>_register implementations. The
-	 * data structure passed will be taken over by the
-	 * console framework and *MUST* be allocated in
-	 * persistent memory (e.g. the data section).
-	 * In : x0 - address of console_t structure
-	 * Out: x0 - Always 1 (for easier tail calling)
-	 * Clobber list: x0, x1
-	 * -----------------------------------------------
-	 */
-func console_register
-	stp	x21, x30, [sp, #-16]!
-#if ENABLE_ASSERTIONS
-	/* Assert that x0 isn't a NULL pointer */
-	cmp	x0, #0
-	ASM_ASSERT(ne)
-	/* Assert that the struct isn't in the stack */
-	adrp	x1, __STACKS_START__
-	add	x1, x1, :lo12:__STACKS_START__
-	cmp	x0, x1
-	b.lo	not_on_stack
-	adrp	x1, __STACKS_END__
-	add	x1, x1, :lo12:__STACKS_END__
-	cmp	x0, x1
-	ASM_ASSERT(hs)
-not_on_stack:
-	/* Assert that this struct isn't in the list */
-	mov	x1, x0 /* Preserve x0 and x30 */
-	bl	console_is_registered
-	cmp	x0, #0
-	ASM_ASSERT(eq)
-	mov	x0, x1
-#endif /* ENABLE_ASSERTIONS */
-	adrp	x21, console_list
-	ldr	x1, [x21, :lo12:console_list]	/* X1 = first struct in list */
-	str	x0, [x21, :lo12:console_list]	/* list head = new console */
-	str	x1, [x0, #CONSOLE_T_NEXT]	/* new console next ptr = X1 */
-	mov	x0, #1
-	ldp	x21, x30, [sp], #16
-	ret
-endfunc console_register
-
-	/* -----------------------------------------------
-	 * int console_unregister(console_t *console)
-	 * Function to find a specific console in the list
-	 * of currently active consoles and remove it.
-	 * In: x0 - address of console_t struct to remove
-	 * Out: x0 - removed address, or NULL if not found
-	 * Clobber list: x0, x1
-	 * -----------------------------------------------
-	 */
-func console_unregister
-#if ENABLE_ASSERTIONS
-	/* Assert that x0 isn't a NULL pointer */
-	cmp	x0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	stp	x21, xzr, [sp, #-16]!
-	adrp	x21, console_list
-	add	x21, x21, :lo12:console_list	/* X21 = ptr to first struct */
-	ldr	x1, [x21]			/* X1 = first struct */
-
-unregister_loop:
-	cbz	x1, unregister_not_found
-	cmp	x0, x1
-	b.eq	unregister_found
-	ldr	x21, [x21]			/* X21 = next ptr of struct */
-	ldr	x1, [x21]			/* X1 = next struct */
-	b	unregister_loop
-
-unregister_found:
-	ldr	x1, [x1]			/* X1 = next struct */
-	str	x1, [x21]			/* prev->next = cur->next */
-	ldp	x21, xzr, [sp], #16
-	ret
-
-unregister_not_found:
-	mov	x0, #0				/* return NULL if not found */
-	ldp	x21, xzr, [sp], #16
-	ret
-endfunc console_unregister
-
-	/* -----------------------------------------------
-	 * int console_is_registered(console_t *console)
-	 * Function to detect if a specific console is
-	 * registered or not.
-	 * In: x0 - address of console_t struct to remove
-	 * Out: x0 - 1 if it is registered, 0 if not.
-	 * Clobber list: x0
-	 * -----------------------------------------------
-	 */
-func console_is_registered
-#if ENABLE_ASSERTIONS
-	/* Assert that x0 isn't a NULL pointer */
-	cmp	x0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	stp	x21, xzr, [sp, #-16]!
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-check_registered_loop:
-	cbz	x21, console_not_registered /* Check if end of list */
-	cmp	x0, x21		/* Check if the pointers are different */
-	b.eq	console_registered
-	ldr	x21, [x21, #CONSOLE_T_NEXT]	/* Get pointer to next struct */
-	b	check_registered_loop
-console_not_registered:
-	mov	x0, #0
-	ldp	x21, xzr, [sp], #16
-	ret
-console_registered:
-	mov	x0, #1
-	ldp	x21, xzr, [sp], #16
-	ret
-endfunc console_is_registered
-
-	/* -----------------------------------------------
-	 * void console_switch_state(unsigned int new_state)
-	 * Function to switch the current console state.
-	 * The console state determines which of the
-	 * registered consoles are actually used at a time.
-	 * In : w0 - global console state to move to
-	 * Clobber list: x0, x1
-	 * -----------------------------------------------
-	 */
-func console_switch_state
-	adrp	x1, console_state
-	strb	w0, [x1, :lo12:console_state]
-	ret
-endfunc console_switch_state
-
-	/* -----------------------------------------------
-	 * void console_set_scope(console_t *console,
-	 *                       unsigned int scope)
-	 * Function to update the states that a given console
-	 * may be active in.
-	 * In : x0 - pointer to console_t struct
-	 *    : w1 - new active state mask
-	 * Clobber list: x0, x1, x2
-	 * -----------------------------------------------
-	 */
-func console_set_scope
-#if ENABLE_ASSERTIONS
-	tst	w1, #~CONSOLE_FLAG_SCOPE_MASK
-	ASM_ASSERT(eq)
-#endif /* ENABLE_ASSERTIONS */
-	ldr	w2, [x0, #CONSOLE_T_FLAGS]
-	and	w2, w2, #~CONSOLE_FLAG_SCOPE_MASK
-	orr	w2, w2, w1
-	str	w2, [x0, #CONSOLE_T_FLAGS]
-	ret
-endfunc console_set_scope
-
-	/* ---------------------------------------------
-	 * int console_putc(int c)
-	 * Function to output a character. Calls all
-	 * active console's putc() handlers in succession.
-	 * In : x0 - character to be printed
-	 * Out: x0 - printed character on success, or < 0
-	             if at least one console had an error
-	 * Clobber list : x0, x1, x2
-	 * ---------------------------------------------
-	 */
-func console_putc
-	stp	x21, x30, [sp, #-16]!
-	stp	x19, x20, [sp, #-16]!
-	mov	w20, #ERROR_NO_VALID_CONSOLE	/* W20 = current return value */
-	mov	w19, w0				/* W19 = character to print */
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-
-putc_loop:
-	cbz	x21, putc_done
-	adrp	x1, console_state
-	ldrb	w1, [x1, :lo12:console_state]
-	ldr	w2, [x21, #CONSOLE_T_FLAGS]
-	tst	w1, w2
-	b.eq	putc_continue
-	ldr	x2, [x21, #CONSOLE_T_PUTC]
-	cbz	x2, putc_continue
-	mov	w0, w19
-	mov	x1, x21
-	blr	x2
-	cmp	w20, #ERROR_NO_VALID_CONSOLE	/* update W20 if it's NOVALID */
-	ccmp	w0, #0, #0x8, ne		/* else update it if W0 < 0 */
-	csel	w20, w0, w20, lt
-putc_continue:
-	ldr	x21, [x21]			/* X21 = next struct */
-	b	putc_loop
-
-putc_done:
-	mov	w0, w20
-	ldp	x19, x20, [sp], #16
-	ldp	x21, x30, [sp], #16
-	ret
-endfunc console_putc
-
-	/* ---------------------------------------------
-	 * int console_getc(void)
-	 * Function to get a character from any console.
-	 * Keeps looping through all consoles' getc()
-	 * handlers until one of them returns a
-	 * character, then stops iterating and returns
-	 * that character to the caller. Will stop looping
-	 * if all active consoles report real errors
-	 * (other than just not having a char available).
-	 * Out : x0 - read character, or < 0 on error
-	 * Clobber list : x0, x1
-	 * ---------------------------------------------
-	 */
-func console_getc
-	stp	x30, xzr, [sp, #-16]!
-	stp	x20, x21, [sp, #-16]!
-getc_try_again:
-	mov	w20, #ERROR_NO_VALID_CONSOLE	/* W20 = current return value */
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-	cbnz	x21, getc_loop
-	mov	w0, w20				/* If no consoles registered */
-	ldp	x20, x21, [sp], #16
-	ldp	x30, xzr, [sp], #16
-	ret					/* return immediately. */
-
-getc_loop:
-	adrp	x0, console_state
-	ldrb	w0, [x0, :lo12:console_state]
-	ldr	w1, [x21, #CONSOLE_T_FLAGS]
-	tst	w0, w1
-	b.eq	getc_continue
-	ldr	x1, [x21, #CONSOLE_T_GETC]
-	cbz	x1, getc_continue
-	mov	x0, x21
-	blr	x1
-	cmp	w0, #0				/* if X0 >= 0: return */
-	b.ge	getc_found
-	cmp	w20, #ERROR_NO_PENDING_CHAR	/* may update W20 (NOCHAR has */
-	csel	w20, w20, w0, eq		/* precedence vs real errors) */
-getc_continue:
-	ldr	x21, [x21]			/* X21 = next struct */
-	cbnz	x21, getc_loop
-	cmp	w20, #ERROR_NO_PENDING_CHAR	/* Keep scanning if at least */
-	b.eq	getc_try_again			/* one console returns NOCHAR */
-	mov	w0, w20
-
-getc_found:
-	ldp	x20, x21, [sp], #16
-	ldp	x30, xzr, [sp], #16
-	ret
-endfunc console_getc
-
-	/* ---------------------------------------------
-	 * int console_flush(void)
-	 * Function to force a write of all buffered
-	 * data that hasn't been output. Calls all
-	 * console's flush() handlers in succession.
-	 * Out: x0 - 0 on success, < 0 if at least one error
-	 * Clobber list : x0, x1, x2
-	 * ---------------------------------------------
-	 */
-func console_flush
-	stp	x30, xzr, [sp, #-16]!
-	stp	x20, x21, [sp, #-16]!
-	mov	w20, #ERROR_NO_VALID_CONSOLE	/* W20 = current return value */
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-
-flush_loop:
-	cbz	x21, flush_done
-	adrp	x1, console_state
-	ldrb	w1, [x1, :lo12:console_state]
-	ldr	w2, [x21, #CONSOLE_T_FLAGS]
-	tst	w1, w2
-	b.eq	flush_continue
-	ldr	x1, [x21, #CONSOLE_T_FLUSH]
-	cbz	x1, flush_continue
-	mov	x0, x21
-	blr	x1
-	cmp	w20, #ERROR_NO_VALID_CONSOLE	/* update W20 if it's NOVALID */
-	ccmp	w0, #0, #0x8, ne		/* else update it if W0 < 0 */
-	csel	w20, w0, w20, lt
-flush_continue:
-	ldr	x21, [x21]			/* X21 = next struct */
-	b	flush_loop
-
-flush_done:
-	mov	w0, w20
-	ldp	x20, x21, [sp], #16
-	ldp	x30, xzr, [sp], #16
-	ret
-endfunc console_flush
diff --git a/drivers/console/multi_console.c b/drivers/console/multi_console.c
new file mode 100644
index 0000000000000000000000000000000000000000..c678de09895feb5ab731ab8de41f49742e106b93
--- /dev/null
+++ b/drivers/console/multi_console.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#if MULTI_CONSOLE_API
+
+#include <assert.h>
+#include <drivers/console.h>
+
+console_t *console_list;
+uint8_t console_state = CONSOLE_FLAG_BOOT;
+
+int console_register(console_t *console)
+{
+	IMPORT_SYM(console_t *, __STACKS_START__, stacks_start)
+	IMPORT_SYM(console_t *, __STACKS_END__, stacks_end)
+
+	/* Assert that the struct is not on the stack (common mistake). */
+	assert((console < stacks_start) || (console >= stacks_end));
+	/* Assert that we won't make a circle in the list. */
+	assert(!console_is_registered(console));
+
+	console->next = console_list;
+	console_list = console;
+
+	/* Return 1 for convenient tail-calling from console_xxx_register(). */
+	return 1;
+}
+
+console_t *console_unregister(console_t *to_be_deleted)
+{
+	console_t **ptr;
+
+	assert(to_be_deleted != NULL);
+
+	for (ptr = &console_list; *ptr != NULL; ptr = &(*ptr)->next)
+		if (*ptr == to_be_deleted) {
+			*ptr = (*ptr)->next;
+			return to_be_deleted;
+		}
+
+	return NULL;
+}
+
+int console_is_registered(console_t *to_find)
+{
+	console_t *console;
+
+	assert(to_find != NULL);
+
+	for (console = console_list; console != NULL; console = console->next)
+		if (console == to_find)
+			return 1;
+
+	return 0;
+}
+
+void console_switch_state(unsigned int new_state)
+{
+	console_state = new_state;
+}
+
+void console_set_scope(console_t *console, unsigned int scope)
+{
+	assert(console != NULL);
+
+	console->flags = (console->flags & ~CONSOLE_FLAG_SCOPE_MASK) | scope;
+}
+
+int console_putc(int c)
+{
+	int err = ERROR_NO_VALID_CONSOLE;
+	console_t *console;
+
+	for (console = console_list; console != NULL; console = console->next)
+		if (console->flags & console_state) {
+			int ret = console->putc(c, console);
+			if ((err == ERROR_NO_VALID_CONSOLE) || (ret < err))
+				err = ret;
+		}
+
+	return err;
+}
+
+int console_getc(void)
+{
+	int err = ERROR_NO_VALID_CONSOLE;
+	console_t *console;
+
+	do {	/* Keep polling while at least one console works correctly. */
+		for (console = console_list; console != NULL;
+		     console = console->next)
+			if (console->flags & console_state) {
+				int ret = console->getc(console);
+				if (ret >= 0)
+					return ret;
+				if (err != ERROR_NO_PENDING_CHAR)
+					err = ret;
+			}
+	} while (err == ERROR_NO_PENDING_CHAR);
+
+	return err;
+}
+
+int console_flush(void)
+{
+	int err = ERROR_NO_VALID_CONSOLE;
+	console_t *console;
+
+	for (console = console_list; console != NULL; console = console->next)
+		if (console->flags & console_state) {
+			int ret = console->flush(console);
+			if ((err == ERROR_NO_VALID_CONSOLE) || (ret < err))
+				err = ret;
+		}
+
+	return err;
+}
+
+#endif	/* MULTI_CONSOLE_API */
diff --git a/include/drivers/console.h b/include/drivers/console.h
index 23754665cca9044160fa3f6ef83b47a006c04516..02f2f8a2ddf6dee2544f37be9dc53feea7273308 100644
--- a/include/drivers/console.h
+++ b/include/drivers/console.h
@@ -52,8 +52,9 @@ typedef struct console {
  * implementation, e.g. console_16550_register() from <uart_16550.h>. Consoles
  * registered that way can be unregistered/reconfigured with below functions.
  */
-/* Remove a single console_t instance from the console list. */
-int console_unregister(console_t *console);
+/* Remove a single console_t instance from the console list. Return a pointer to
+ * the console that was removed if it was found, or NULL if not. */
+console_t *console_unregister(console_t *console);
 /* Returns 1 if this console is already registered, 0 if not */
 int console_is_registered(console_t *console);
 /*
diff --git a/maintainers.rst b/maintainers.rst
index 3122ecd333e55b96212a20ff85af6f783b0d1261..e807f18518ddc83d9bf0d7e4243a1f52f1a729bc 100644
--- a/maintainers.rst
+++ b/maintainers.rst
@@ -48,6 +48,23 @@ Arm System Guidance for Infrastructure / Mobile FVP platforms
 :F: plat/arm/board/sgi575/
 :F: plat/arm/board/sgm775/
 
+Console API framework
+---------------------
+:M: Julius Werner <jwerner@chromium.org>
+:G: `jwerner-chromium`_
+:F: drivers/console/
+:F: include/drivers/console.h
+:F: plat/common/aarch64/crash_console_helpers.S
+
+coreboot support libraries
+--------------------------
+:M: Julius Werner <jwerner@chromium.org>
+:G: `jwerner-chromium`_
+:F: drivers/coreboot/
+:F: include/drivers/coreboot/
+:F: include/lib/coreboot.h
+:F: lib/coreboot/
+
 eMMC/UFS drivers
 ----------------
 :M: Haojian Zhuang <haojian.zhuang@linaro.org>
diff --git a/plat/allwinner/common/allwinner-common.mk b/plat/allwinner/common/allwinner-common.mk
index 2dc058f5422ffd76268ce455b72be071a9231133..f20f5157e96267f42e3d490175b27fa4830c5232 100644
--- a/plat/allwinner/common/allwinner-common.mk
+++ b/plat/allwinner/common/allwinner-common.mk
@@ -15,8 +15,7 @@ PLAT_INCLUDES		:=	-Iinclude/plat/arm/common		\
 
 include lib/libfdt/libfdt.mk
 
-PLAT_BL_COMMON_SOURCES	:=	drivers/console/${ARCH}/console.S	\
-				drivers/ti/uart/${ARCH}/16550_console.S	\
+PLAT_BL_COMMON_SOURCES	:=	drivers/ti/uart/${ARCH}/16550_console.S	\
 				${XLAT_TABLES_LIB_SRCS}			\
 				${AW_PLAT}/common/plat_helpers.S	\
 				${AW_PLAT}/common/sunxi_common.c
diff --git a/plat/common/aarch64/crash_console_helpers.S b/plat/common/aarch64/crash_console_helpers.S
index 5af8db252775c5aafe23a88bf2a643bf88135e2c..8f8ca11245827615aad5f925b3302bd716906577 100644
--- a/plat/common/aarch64/crash_console_helpers.S
+++ b/plat/common/aarch64/crash_console_helpers.S
@@ -16,77 +16,167 @@
 	.globl	plat_crash_console_putc
 	.globl	plat_crash_console_flush
 
-#if MULTI_CONSOLE_API
+#if !MULTI_CONSOLE_API
+#error "This crash console implementation only works with the MULTI_CONSOLE_API!"
+#endif
 
-	/* -----------------------------------------------------
-	 * int plat_crash_console_init(void)
-	 * Use normal console by default. Switch it to crash
-	 * mode so serial consoles become active again.
-	 * NOTE: This default implementation will only work for
-	 * crashes that occur after a normal console (marked
-	 * valid for the crash state) has been registered with
-	 * the console framework. To debug crashes that occur
-	 * earlier, the platform has to override these functions
-	 * with an implementation that initializes a console
-	 * driver with hardcoded parameters. See
-	 * docs/porting-guide.rst for more information.
-	 * -----------------------------------------------------
-	 */
-func plat_crash_console_init
-#if defined(IMAGE_BL1)
 	/*
-	 * BL1 code can possibly crash so early that the data segment is not yet
-	 * accessible. Don't risk undefined behavior by trying to run the normal
-	 * console framework. Platforms that want to debug BL1 will need to
-	 * override this with custom functions that can run from registers only.
+	 * Spinlock to syncronize access to crash_console_triggered. We cannot
+	 * acquire spinlocks when the cache is disabled, so in some cases (like
+	 * late during CPU suspend) some risk remains.
 	 */
-	mov	x0, #0
-	ret
-#else	/* IMAGE_BL1 */
-	mov	x3, x30
-	mov	x0, #CONSOLE_FLAG_CRASH
-	bl	console_switch_state
-	mov	x0, #1
-	ret	x3
-#endif
-endfunc plat_crash_console_init
+.section .data.crash_console_spinlock
+	define_asm_spinlock crash_console_spinlock
 
-	/* -----------------------------------------------------
-	 * void plat_crash_console_putc(int character)
-	 * Output through the normal console by default.
-	 * -----------------------------------------------------
+	/*
+	 * Flag to make sure that only one CPU can write a crash dump even if
+	 * multiple crash at the same time. Interleaving crash dumps on the same
+	 * console would just make the output unreadable, so it's better to only
+	 * get a single but uncorrupted dump. This also means that we don't have
+	 * to duplicate the reg_stash below for each CPU.
 	 */
-func plat_crash_console_putc
-	b	console_putc
-endfunc plat_crash_console_putc
+.section .data.crash_console_triggered
+	crash_console_triggered: .byte 0
 
-	/* -----------------------------------------------------
-	 * void plat_crash_console_flush(void)
-	 * Flush normal console by default.
-	 * -----------------------------------------------------
+	/*
+	 * Space to stash away some register values while we're calling into
+	 * console drivers and don't have a real stack available. We need x14,
+	 * x15 and x30 for bookkeeping within the plat_crash_console functions
+	 * themselves, and some console drivers use x16 and x17 as additional
+	 * scratch space that is not preserved by the main crash reporting
+	 * framework. (Note that x16 and x17 should really never be expected to
+	 * retain their values across any function call, even between carefully
+	 * designed assembly functions, since the linker is always free to
+	 * insert a function call veneer that uses these registers as scratch
+	 * space at any time. The current crash reporting framework doesn't
+	 * really respect that, but since TF is usually linked as a single
+	 * contiguous binary of less than 128MB, it seems to work in practice.)
 	 */
-func plat_crash_console_flush
-	b	console_flush
-endfunc plat_crash_console_flush
-
-#else	/* MULTI_CONSOLE_API */
+.section .data.crash_console_reg_stash
+	.align 3
+	crash_console_reg_stash: .quad 0, 0, 0, 0, 0
 
-	/* -----------------------------------------------------
-	 * In the old API these are all no-op stubs that need to
-	 * be overridden by the platform to be useful.
-	 * -----------------------------------------------------
+	/* --------------------------------------------------------------------
+	 * int plat_crash_console_init(void)
+	 * Takes the crash console spinlock (if possible) and checks the trigger
+	 * flag to make sure we're the first CPU to dump. If not, return an
+	 * error (so crash dumping will fail but the CPU will still call
+	 * plat_panic_handler() which may do important platform-specific tasks
+	 * that may be needed on all crashing CPUs). In either case, the lock
+	 * will be released so other CPUs can make forward progress on this.
+	 * Clobbers: x0 - x4, x30
+	 * --------------------------------------------------------------------
 	 */
 func plat_crash_console_init
-	mov	x0, #0
+#if defined(IMAGE_BL31)
+	mov	x4, x30		/* x3 and x4 are not clobbered by spin_lock() */
+	mov	x3, #0		/* return value */
+
+	mrs	x1, sctlr_el3
+	tst	x1, #SCTLR_C_BIT
+	beq	skip_spinlock	/* can't synchronize when cache disabled */
+
+	adrp	x0, crash_console_spinlock
+	add	x0, x0, :lo12:crash_console_spinlock
+	bl	spin_lock
+
+skip_spinlock:
+	adrp	x1, crash_console_triggered
+	add	x1, x1, :lo12:crash_console_triggered
+	ldarb	w2, [x1]
+	cmp	w2, #0
+	bne	init_error
+
+	mov	x3, #1		/* set return value to success */
+	stlrb	w3, [x1]
+
+init_error:
+	bl	spin_unlock	/* harmless if we didn't acquire the lock */
+	mov	x0, x3
+	ret	x4
+#else	/* Only one CPU in BL1/BL2, no need to synchronize anything */
+	mov	x0, #1
 	ret
+#endif
 endfunc plat_crash_console_init
 
+	/* --------------------------------------------------------------------
+	 * int plat_crash_console_putc(char c)
+	 * Prints the character on all consoles registered with the console
+	 * framework that have CONSOLE_FLAG_CRASH set. Note that this is only
+	 * helpful for crashes that occur after the platform intialization code
+	 * has registered a console. Platforms using this implementation need to
+	 * ensure that all console drivers they use that have the CRASH flag set
+	 * support this (i.e. are written in assembly and comply to the register
+	 * clobber requirements of plat_crash_console_putc().
+	 * --------------------------------------------------------------------
+	 */
 func plat_crash_console_putc
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	stp	x14, x15, [x1]
+	stp	x16, x17, [x1, #16]
+	str	x30, [x1, #32]
+
+	mov	w14, w0				/* W14 = character to print */
+	adrp	x15, console_list
+	ldr	x15, [x15, :lo12:console_list]	/* X15 = first console struct */
+
+putc_loop:
+	cbz	x15, putc_done
+	ldr	w1, [x15, #CONSOLE_T_FLAGS]
+	tst	w1, #CONSOLE_FLAG_CRASH
+	b.eq	putc_continue
+	ldr	x2, [x15, #CONSOLE_T_PUTC]
+	cbz	x2, putc_continue
+	mov	x1, x15
+	blr	x2
+	mov	w0, w14
+putc_continue:
+	ldr	x15, [x15]			/* X15 = next struct */
+	b	putc_loop
+
+putc_done:
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	ldp	x14, x15, [x1]
+	ldp	x16, x17, [x1, #16]
+	ldr	x30, [x1, #32]
 	ret
 endfunc plat_crash_console_putc
 
+	/* --------------------------------------------------------------------
+	 * int plat_crash_console_flush(char c)
+	 * Flushes all consoles registered with the console framework that have
+	 * CONSOLE_FLAG_CRASH set. Same requirements as putc().
+	 * --------------------------------------------------------------------
+	 */
 func plat_crash_console_flush
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	stp	x30, x15, [x1]
+	stp	x16, x17, [x1, #16]
+
+	adrp	x15, console_list
+	ldr	x15, [x15, :lo12:console_list]	/* X15 = first console struct */
+
+flush_loop:
+	cbz	x15, flush_done
+	ldr	w1, [x15, #CONSOLE_T_FLAGS]
+	tst	w1, #CONSOLE_FLAG_CRASH
+	b.eq	flush_continue
+	ldr	x2, [x15, #CONSOLE_T_FLUSH]
+	cbz	x2, flush_continue
+	mov	x0, x15
+	blr	x2
+flush_continue:
+	ldr	x15, [x15]			/* X15 = next struct */
+	b	flush_loop
+
+flush_done:
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	ldp	x30, x15, [x1]
+	ldp	x16, x17, [x1, #16]
 	ret
 endfunc plat_crash_console_flush
-
-#endif
diff --git a/plat/common/aarch64/platform_helpers.S b/plat/common/aarch64/platform_helpers.S
index d3ffcaf1923d0e1ef8151aa71a7b0083b8c42994..89523194d226b2f646a38a3a312fa723080cc88d 100644
--- a/plat/common/aarch64/platform_helpers.S
+++ b/plat/common/aarch64/platform_helpers.S
@@ -40,65 +40,6 @@ func plat_report_exception
 endfunc plat_report_exception
 
 #if !ERROR_DEPRECATED
-#if MULTI_CONSOLE_API
-	/* -----------------------------------------------------
-	 * int plat_crash_console_init(void)
-	 * Use normal console by default. Switch it to crash
-	 * mode so serial consoles become active again.
-	 * NOTE: This default implementation will only work for
-	 * crashes that occur after a normal console (marked
-	 * valid for the crash state) has been registered with
-	 * the console framework. To debug crashes that occur
-	 * earlier, the platform has to override these functions
-	 * with an implementation that initializes a console
-	 * driver with hardcoded parameters. See
-	 * docs/porting-guide.rst for more information.
-	 * -----------------------------------------------------
-	 */
-func plat_crash_console_init
-#if defined(IMAGE_BL1)
-	/*
-	 * BL1 code can possibly crash so early that the data segment is not yet
-	 * accessible. Don't risk undefined behavior by trying to run the normal
-	 * console framework. Platforms that want to debug BL1 will need to
-	 * override this with custom functions that can run from registers only.
-	 */
-	mov	x0, #0
-	ret
-#else	/* IMAGE_BL1 */
-	mov	x3, x30
-	mov	x0, #CONSOLE_FLAG_CRASH
-	bl	console_switch_state
-	mov	x0, #1
-	ret	x3
-#endif
-endfunc plat_crash_console_init
-
-	/* -----------------------------------------------------
-	 * void plat_crash_console_putc(int character)
-	 * Output through the normal console by default.
-	 * -----------------------------------------------------
-	 */
-func plat_crash_console_putc
-	b	console_putc
-endfunc plat_crash_console_putc
-
-	/* -----------------------------------------------------
-	 * void plat_crash_console_flush(void)
-	 * Flush normal console by default.
-	 * -----------------------------------------------------
-	 */
-func plat_crash_console_flush
-	b	console_flush
-endfunc plat_crash_console_flush
-
-#else	/* MULTI_CONSOLE_API */
-
-	/* -----------------------------------------------------
-	 * In the old API these are all no-op stubs that need to
-	 * be overridden by the platform to be useful.
-	 * -----------------------------------------------------
-	 */
 func plat_crash_console_init
 	mov	x0, #0
 	ret
@@ -111,7 +52,6 @@ endfunc plat_crash_console_putc
 func plat_crash_console_flush
 	ret
 endfunc plat_crash_console_flush
-#endif
 #endif /* ERROR_DEPRECATED */
 
 	/* -----------------------------------------------------
diff --git a/plat/imx/imx8qm/platform.mk b/plat/imx/imx8qm/platform.mk
index ce84e2baa2b49503b4bf0d8682a8cf67004a39d2..dc45e901d885950e8deb5c35b1dbcc9cdf0b0f58 100644
--- a/plat/imx/imx8qm/platform.mk
+++ b/plat/imx/imx8qm/platform.mk
@@ -26,7 +26,6 @@ BL31_SOURCES		+=	plat/imx/common/lpuart_console.S	\
 				lib/xlat_tables/xlat_tables_common.c		\
 				lib/cpus/aarch64/cortex_a53.S			\
 				lib/cpus/aarch64/cortex_a72.S			\
-				drivers/console/aarch64/console.S		\
 				drivers/arm/cci/cci.c				\
 				${IMX_GIC_SOURCES}				\
 
diff --git a/plat/imx/imx8qx/platform.mk b/plat/imx/imx8qx/platform.mk
index 02559b46256274718a6a730a8fb67fe7da7536ca..a831bf2f05f1181fc4b3b8ee63a38ca686917020 100644
--- a/plat/imx/imx8qx/platform.mk
+++ b/plat/imx/imx8qx/platform.mk
@@ -25,7 +25,6 @@ BL31_SOURCES		+=	plat/imx/common/lpuart_console.S	\
 				lib/xlat_tables/xlat_tables_common.c	\
 				lib/xlat_tables/aarch64/xlat_tables.c	\
 				lib/cpus/aarch64/cortex_a35.S		\
-				drivers/console/aarch64/console.S	\
 				${IMX_GIC_SOURCES}			\
 
 include plat/imx/common/sci/sci_api.mk
diff --git a/plat/layerscape/board/ls1043/platform.mk b/plat/layerscape/board/ls1043/platform.mk
index 678205cd32309035fa658c41470367475fd0e44f..795d924828b77ee35290964b4847ba3e7f043910 100644
--- a/plat/layerscape/board/ls1043/platform.mk
+++ b/plat/layerscape/board/ls1043/platform.mk
@@ -28,8 +28,7 @@ PLAT_INCLUDES			:=	-Iplat/layerscape/board/ls1043/include   \
 					-Iinclude/drivers/io
 
 
-PLAT_BL_COMMON_SOURCES		:=	drivers/console/aarch64/console.S	\
-					plat/layerscape/common/aarch64/ls_console.S
+PLAT_BL_COMMON_SOURCES		:=	plat/layerscape/common/aarch64/ls_console.S
 
 LS1043_CPU_LIBS			:=	lib/cpus/${ARCH}/aem_generic.S
 
diff --git a/plat/marvell/a3700/common/a3700_common.mk b/plat/marvell/a3700/common/a3700_common.mk
index 3983c707c0c0cfae3e34f7aa2bb853be9e695292..e2ac97ffc9b9eebf63ea9308f34cb41a6417f354 100644
--- a/plat/marvell/a3700/common/a3700_common.mk
+++ b/plat/marvell/a3700/common/a3700_common.mk
@@ -96,7 +96,6 @@ PLAT_INCLUDES		:=	-I$(PLAT_FAMILY_BASE)/$(PLAT)		\
 				$(ATF_INCLUDES)
 
 PLAT_BL_COMMON_SOURCES	:=	$(PLAT_COMMON_BASE)/aarch64/a3700_common.c \
-				drivers/console/aarch64/console.S	   \
 				$(MARVELL_COMMON_BASE)/marvell_cci.c	   \
 				$(MARVELL_DRV_BASE)/uart/a3700_console.S
 
diff --git a/plat/marvell/a8k/common/a8k_common.mk b/plat/marvell/a8k/common/a8k_common.mk
index 88d9311156d7179fd1077b98d3db5bff8dcb2317..e350d6ac96751912d27a814a041a97c229468c67 100644
--- a/plat/marvell/a8k/common/a8k_common.mk
+++ b/plat/marvell/a8k/common/a8k_common.mk
@@ -55,7 +55,6 @@ PLAT_INCLUDES		:=	-I$(PLAT_FAMILY_BASE)/$(PLAT)		\
 				$(ATF_INCLUDES)
 
 PLAT_BL_COMMON_SOURCES	:=	$(PLAT_COMMON_BASE)/aarch64/a8k_common.c \
-				drivers/console/aarch64/console.S	 \
 				drivers/ti/uart/aarch64/16550_console.S
 
 BLE_PORTING_SOURCES	:=	$(PLAT_FAMILY_BASE)/$(PLAT)/board/dram_port.c \
diff --git a/plat/meson/gxbb/platform.mk b/plat/meson/gxbb/platform.mk
index e6f5ae4894f5b08f86430a29cb552471f381d402..68ff4007519119b70719cbdc9e4709ba0b70aac5 100644
--- a/plat/meson/gxbb/platform.mk
+++ b/plat/meson/gxbb/platform.mk
@@ -14,8 +14,7 @@ GXBB_GIC_SOURCES	:=	drivers/arm/gic/common/gic_common.c	\
 				drivers/arm/gic/v2/gicv2_helpers.c	\
 				plat/common/plat_gicv2.c
 
-PLAT_BL_COMMON_SOURCES	:=	drivers/console/aarch64/multi_console.S	\
-				drivers/meson/console/aarch64/meson_console.S \
+PLAT_BL_COMMON_SOURCES	:=	drivers/meson/console/aarch64/meson_console.S \
 				plat/meson/gxbb/gxbb_common.c		\
 				plat/meson/gxbb/gxbb_topology.c		\
 				${XLAT_TABLES_LIB_SRCS}
diff --git a/plat/rockchip/rk3328/platform.mk b/plat/rockchip/rk3328/platform.mk
index 785f640367a15d27c1faa653e546841be4fbf2a1..2b2ac51c47e9a9478015c00bca5ca76841e0098d 100644
--- a/plat/rockchip/rk3328/platform.mk
+++ b/plat/rockchip/rk3328/platform.mk
@@ -34,7 +34,6 @@ PLAT_BL_COMMON_SOURCES	:=	lib/xlat_tables/aarch64/xlat_tables.c		\
 
 BL31_SOURCES		+=	${RK_GIC_SOURCES}				\
 				drivers/arm/cci/cci.c				\
-				drivers/console/aarch64/console.S		\
 				drivers/ti/uart/aarch64/16550_console.S		\
 				drivers/delay_timer/delay_timer.c		\
 				drivers/delay_timer/generic_delay_timer.c	\
diff --git a/plat/rockchip/rk3368/platform.mk b/plat/rockchip/rk3368/platform.mk
index a3e593e60553ad0d1826107b6c53f5f73bd00ac1..c0164c1797dae8cafdaf0171424079a1133176f7 100644
--- a/plat/rockchip/rk3368/platform.mk
+++ b/plat/rockchip/rk3368/platform.mk
@@ -31,7 +31,6 @@ PLAT_BL_COMMON_SOURCES	:=	lib/xlat_tables/xlat_tables_common.c		\
 
 BL31_SOURCES		+=	${RK_GIC_SOURCES}				\
 				drivers/arm/cci/cci.c				\
-				drivers/console/aarch64/console.S		\
 				drivers/ti/uart/aarch64/16550_console.S		\
 				drivers/delay_timer/delay_timer.c		\
 				drivers/delay_timer/generic_delay_timer.c	\
diff --git a/plat/rockchip/rk3399/platform.mk b/plat/rockchip/rk3399/platform.mk
index eccf1cc850e164d8a681403ba505f9e5a85abca1..b624717dda7ff64137db4a3fb181fd3df3a0e581 100644
--- a/plat/rockchip/rk3399/platform.mk
+++ b/plat/rockchip/rk3399/platform.mk
@@ -37,7 +37,6 @@ PLAT_BL_COMMON_SOURCES	:=	lib/xlat_tables/xlat_tables_common.c	\
 
 BL31_SOURCES	+=	${RK_GIC_SOURCES}				\
 			drivers/arm/cci/cci.c				\
-			drivers/console/aarch64/console.S		\
 			drivers/ti/uart/aarch64/16550_console.S		\
 			drivers/delay_timer/delay_timer.c		\
 			drivers/delay_timer/generic_delay_timer.c	\
diff --git a/plat/rpi3/platform.mk b/plat/rpi3/platform.mk
index db96de821173ca513a33ad5cccf9e0d0db0f2403..46c139eac43f1ea7f725605b523e236fdc2de3dc 100644
--- a/plat/rpi3/platform.mk
+++ b/plat/rpi3/platform.mk
@@ -10,8 +10,7 @@ include lib/xlat_tables_v2/xlat_tables.mk
 PLAT_INCLUDES		:=	-Iinclude/common/tbbr			\
 				-Iplat/rpi3/include
 
-PLAT_BL_COMMON_SOURCES	:=	drivers/console/aarch64/console.S	\
-				drivers/ti/uart/aarch64/16550_console.S	\
+PLAT_BL_COMMON_SOURCES	:=	drivers/ti/uart/aarch64/16550_console.S	\
 				plat/rpi3/rpi3_common.c			\
 				${XLAT_TABLES_LIB_SRCS}
 
diff --git a/plat/st/stm32mp1/platform.mk b/plat/st/stm32mp1/platform.mk
index f4a0ca4a7a234cfdf17846637083203e3c3d1954..545b140d8828d672a7231617b8b8bb7662b18093 100644
--- a/plat/st/stm32mp1/platform.mk
+++ b/plat/st/stm32mp1/platform.mk
@@ -35,8 +35,7 @@ include lib/libfdt/libfdt.mk
 
 PLAT_BL_COMMON_SOURCES	:=	plat/st/stm32mp1/stm32mp1_common.c
 
-PLAT_BL_COMMON_SOURCES	+=	drivers/console/aarch32/console.S			\
-				drivers/st/uart/aarch32/stm32_console.S
+PLAT_BL_COMMON_SOURCES	+=	drivers/st/uart/aarch32/stm32_console.S
 
 ifneq (${ENABLE_STACK_PROTECTOR},0)
 PLAT_BL_COMMON_SOURCES	+=	plat/st/stm32mp1/stm32mp1_stack_protector.c
diff --git a/plat/ti/k3/common/plat_common.mk b/plat/ti/k3/common/plat_common.mk
index fbd79b4ad9b98d17972a27d0a7fba231537f7856..9b3e7d84f51a263f2f58b4d5845a30ba1ac68415 100644
--- a/plat/ti/k3/common/plat_common.mk
+++ b/plat/ti/k3/common/plat_common.mk
@@ -39,7 +39,6 @@ PLAT_INCLUDES		+=	\
 				-I${PLAT_PATH}/common/drivers/ti_sci	\
 
 K3_CONSOLE_SOURCES	+=	\
-				drivers/console/aarch64/console.S	\
 				drivers/ti/uart/aarch64/16550_console.S	\
 				${PLAT_PATH}/common/k3_console.c	\