diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 6571fbfdb2a1..0d173289c67e 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -914,9 +914,9 @@
 
 	earlycon=	[KNL] Output early console device and options.
 
-			When used with no options, the early console is
-			determined by the stdout-path property in device
-			tree's chosen node.
+			[ARM64] The early console is determined by the
+			stdout-path property in device tree's chosen node,
+			or determined by the ACPI SPCR table.
 
 		cdns,<addr>[,options]
 			Start an early, polled-mode console on a Cadence
diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c
index b3162715ed78..b3e33bbdf3b7 100644
--- a/arch/arm64/kernel/acpi.c
+++ b/arch/arm64/kernel/acpi.c
@@ -25,7 +25,6 @@
 #include <linux/memblock.h>
 #include <linux/of_fdt.h>
 #include <linux/smp.h>
-#include <linux/serial_core.h>
 
 #include <asm/cputype.h>
 #include <asm/cpu_ops.h>
@@ -177,6 +176,128 @@ static int __init acpi_fadt_sanity_check(void)
 	return ret;
 }
 
+/*
+ * Erratum 44 for QDF2432v1 and QDF2400v1 SoCs describes the BUSY bit as
+ * occasionally getting stuck as 1. To avoid the potential for a hang, check
+ * TXFE == 0 instead of BUSY == 1. This may not be suitable for all UART
+ * implementations, so only do so if an affected platform is detected in
+ * acpi_parse_spcr().
+ */
+bool qdf2400_e44_present;
+EXPORT_SYMBOL(qdf2400_e44_present);
+
+/*
+ * Some Qualcomm Datacenter Technologies SoCs have a defective UART BUSY bit.
+ * Detect them by examining the OEM fields in the SPCR header, similar to PCI
+ * quirk detection in pci_mcfg.c.
+ */
+static bool qdf2400_erratum_44_present(struct acpi_table_header *h)
+{
+	if (memcmp(h->oem_id, "QCOM  ", ACPI_OEM_ID_SIZE))
+		return false;
+
+	if (!memcmp(h->oem_table_id, "QDF2432 ", ACPI_OEM_TABLE_ID_SIZE))
+		return true;
+
+	if (!memcmp(h->oem_table_id, "QDF2400 ", ACPI_OEM_TABLE_ID_SIZE) &&
+			h->oem_revision == 1)
+		return true;
+
+	return false;
+}
+
+/*
+ * APM X-Gene v1 and v2 UART hardware is an 16550 like device but has its
+ * register aligned to 32-bit. In addition, the BIOS also encoded the
+ * access width to be 8 bits. This function detects this errata condition.
+ */
+static bool xgene_8250_erratum_present(struct acpi_table_spcr *tb)
+{
+	bool xgene_8250 = false;
+
+	if (tb->interface_type != ACPI_DBG2_16550_COMPATIBLE)
+		return false;
+
+	if (memcmp(tb->header.oem_id, "APMC0D", ACPI_OEM_ID_SIZE) &&
+	    memcmp(tb->header.oem_id, "HPE   ", ACPI_OEM_ID_SIZE))
+		return false;
+
+	if (!memcmp(tb->header.oem_table_id, "XGENESPC",
+	    ACPI_OEM_TABLE_ID_SIZE) && tb->header.oem_revision == 0)
+		xgene_8250 = true;
+
+	if (!memcmp(tb->header.oem_table_id, "ProLiant",
+	    ACPI_OEM_TABLE_ID_SIZE) && tb->header.oem_revision == 1)
+		xgene_8250 = true;
+
+	return xgene_8250;
+}
+
+int acpi_arch_setup_console(struct acpi_table_spcr *table,
+			    char *opts, char *uart, char *iotype,
+			    int baud_rate, bool earlycon)
+{
+	if (table->header.revision < 2) {
+		pr_err("wrong table version\n");
+		return -ENOENT;
+	}
+
+	switch (table->interface_type) {
+	case ACPI_DBG2_ARM_SBSA_32BIT:
+		snprintf(iotype, ACPI_SPCR_BUF_SIZE, "mmio32");
+		/* fall through */
+	case ACPI_DBG2_ARM_PL011:
+	case ACPI_DBG2_ARM_SBSA_GENERIC:
+	case ACPI_DBG2_BCM2835:
+		snprintf(uart, ACPI_SPCR_BUF_SIZE, "pl011");
+		break;
+	default:
+		if (strlen(uart) == 0)
+			return -ENOENT;
+	}
+
+	/*
+	 * If the E44 erratum is required, then we need to tell the pl011
+	 * driver to implement the work-around.
+	 *
+	 * The global variable is used by the probe function when it
+	 * creates the UARTs, whether or not they're used as a console.
+	 *
+	 * If the user specifies "traditional" earlycon, the qdf2400_e44
+	 * console name matches the EARLYCON_DECLARE() statement, and
+	 * SPCR is not used.  Parameter "earlycon" is false.
+	 *
+	 * If the user specifies "SPCR" earlycon, then we need to update
+	 * the console name so that it also says "qdf2400_e44".  Parameter
+	 * "earlycon" is true.
+	 *
+	 * For consistency, if we change the console name, then we do it
+	 * for everyone, not just earlycon.
+	 */
+	if (qdf2400_erratum_44_present(&table->header)) {
+		qdf2400_e44_present = true;
+		if (earlycon)
+			snprintf(uart, ACPI_SPCR_BUF_SIZE, "qdf2400_e44");
+	}
+
+	if (xgene_8250_erratum_present(table)) {
+		snprintf(iotype, ACPI_SPCR_BUF_SIZE, "mmio32");
+
+		/* for xgene v1 and v2 we don't know the clock rate of the
+		 * UART so don't attempt to change to the baud rate state
+		 * in the table because driver cannot calculate the dividers
+		 */
+		snprintf(opts, ACPI_SPCR_OPTS_SIZE, "%s,%s,0x%llx", uart,
+			 iotype, table->serial_port.address);
+	} else {
+		snprintf(opts, ACPI_SPCR_OPTS_SIZE, "%s,%s,0x%llx,%d", uart,
+			 iotype, table->serial_port.address, baud_rate);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(acpi_arch_setup_console);
+
 /*
  * acpi_boot_table_init() called from setup_arch(), always.
  *	1. find RSDP and get its address, and then find XSDT
@@ -230,10 +351,11 @@ void __init acpi_boot_table_init(void)
 
 done:
 	if (acpi_disabled) {
-		if (earlycon_init_is_deferred)
+		if (console_acpi_spcr_enable)
 			early_init_dt_scan_chosen_stdout();
 	} else {
-		parse_spcr(earlycon_init_is_deferred);
+		/* Always enable the ACPI SPCR console */
+		acpi_parse_spcr(console_acpi_spcr_enable);
 		if (IS_ENABLED(CONFIG_ACPI_BGRT))
 			acpi_table_parse(ACPI_SIG_BGRT, acpi_parse_bgrt);
 	}
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 46505396869e..9ae98eeada76 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -79,7 +79,12 @@ config ACPI_DEBUGGER_USER
 endif
 
 config ACPI_SPCR_TABLE
-	bool
+	bool "ACPI Serial Port Console Redirection Support"
+	default y if ARM64
+	help
+	  Enable support for Serial Port Console Redirection (SPCR) Table.
+	  This table provides information about the configuration of the
+	  earlycon console.
 
 config ACPI_LPIT
 	bool
diff --git a/drivers/acpi/spcr.c b/drivers/acpi/spcr.c
index 324b35bfe781..f4bb8110e404 100644
--- a/drivers/acpi/spcr.c
+++ b/drivers/acpi/spcr.c
@@ -16,65 +16,18 @@
 #include <linux/kernel.h>
 #include <linux/serial_core.h>
 
-/*
- * Erratum 44 for QDF2432v1 and QDF2400v1 SoCs describes the BUSY bit as
- * occasionally getting stuck as 1. To avoid the potential for a hang, check
- * TXFE == 0 instead of BUSY == 1. This may not be suitable for all UART
- * implementations, so only do so if an affected platform is detected in
- * parse_spcr().
- */
-bool qdf2400_e44_present;
-EXPORT_SYMBOL(qdf2400_e44_present);
-
-/*
- * Some Qualcomm Datacenter Technologies SoCs have a defective UART BUSY bit.
- * Detect them by examining the OEM fields in the SPCR header, similiar to PCI
- * quirk detection in pci_mcfg.c.
- */
-static bool qdf2400_erratum_44_present(struct acpi_table_header *h)
-{
-	if (memcmp(h->oem_id, "QCOM  ", ACPI_OEM_ID_SIZE))
-		return false;
-
-	if (!memcmp(h->oem_table_id, "QDF2432 ", ACPI_OEM_TABLE_ID_SIZE))
-		return true;
-
-	if (!memcmp(h->oem_table_id, "QDF2400 ", ACPI_OEM_TABLE_ID_SIZE) &&
-			h->oem_revision == 1)
-		return true;
-
-	return false;
-}
-
-/*
- * APM X-Gene v1 and v2 UART hardware is an 16550 like device but has its
- * register aligned to 32-bit. In addition, the BIOS also encoded the
- * access width to be 8 bits. This function detects this errata condition.
- */
-static bool xgene_8250_erratum_present(struct acpi_table_spcr *tb)
+int __weak acpi_arch_setup_console(struct acpi_table_spcr *table,
+				   char *opts, char *uart, char *iotype,
+				   int baud_rate, bool earlycon)
 {
-	bool xgene_8250 = false;
-
-	if (tb->interface_type != ACPI_DBG2_16550_COMPATIBLE)
-		return false;
-
-	if (memcmp(tb->header.oem_id, "APMC0D", ACPI_OEM_ID_SIZE) &&
-	    memcmp(tb->header.oem_id, "HPE   ", ACPI_OEM_ID_SIZE))
-		return false;
-
-	if (!memcmp(tb->header.oem_table_id, "XGENESPC",
-	    ACPI_OEM_TABLE_ID_SIZE) && tb->header.oem_revision == 0)
-		xgene_8250 = true;
-
-	if (!memcmp(tb->header.oem_table_id, "ProLiant",
-	    ACPI_OEM_TABLE_ID_SIZE) && tb->header.oem_revision == 1)
-		xgene_8250 = true;
-
-	return xgene_8250;
+	snprintf(opts, ACPI_SPCR_OPTS_SIZE, "%s,%s,0x%llx,%d", uart, iotype,
+		 table->serial_port.address, baud_rate);
+	return 0;
 }
 
+bool console_acpi_spcr_enable __initdata;
 /**
- * parse_spcr() - parse ACPI SPCR table and add preferred console
+ * acpi_parse_spcr() - parse ACPI SPCR table and add preferred console
  *
  * @earlycon: set up earlycon for the console specified by the table
  *
@@ -86,13 +39,13 @@ static bool xgene_8250_erratum_present(struct acpi_table_spcr *tb)
  * from arch initialization code as soon as the DT/ACPI decision is made.
  *
  */
-int __init parse_spcr(bool earlycon)
+int __init acpi_parse_spcr(bool earlycon)
 {
-	static char opts[64];
+	static char opts[ACPI_SPCR_OPTS_SIZE];
+	static char uart[ACPI_SPCR_BUF_SIZE];
+	static char iotype[ACPI_SPCR_BUF_SIZE];
 	struct acpi_table_spcr *table;
 	acpi_status status;
-	char *uart;
-	char *iotype;
 	int baud_rate;
 	int err;
 
@@ -105,48 +58,6 @@ int __init parse_spcr(bool earlycon)
 	if (ACPI_FAILURE(status))
 		return -ENOENT;
 
-	if (table->header.revision < 2) {
-		err = -ENOENT;
-		pr_err("wrong table version\n");
-		goto done;
-	}
-
-	if (table->serial_port.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
-		switch (ACPI_ACCESS_BIT_WIDTH((
-			table->serial_port.access_width))) {
-		default:
-			pr_err("Unexpected SPCR Access Width.  Defaulting to byte size\n");
-		case 8:
-			iotype = "mmio";
-			break;
-		case 16:
-			iotype = "mmio16";
-			break;
-		case 32:
-			iotype = "mmio32";
-			break;
-		}
-	} else
-		iotype = "io";
-
-	switch (table->interface_type) {
-	case ACPI_DBG2_ARM_SBSA_32BIT:
-		iotype = "mmio32";
-		/* fall through */
-	case ACPI_DBG2_ARM_PL011:
-	case ACPI_DBG2_ARM_SBSA_GENERIC:
-	case ACPI_DBG2_BCM2835:
-		uart = "pl011";
-		break;
-	case ACPI_DBG2_16550_COMPATIBLE:
-	case ACPI_DBG2_16550_SUBSET:
-		uart = "uart";
-		break;
-	default:
-		err = -ENOENT;
-		goto done;
-	}
-
 	switch (table->baud_rate) {
 	case 3:
 		baud_rate = 9600;
@@ -165,43 +76,36 @@ int __init parse_spcr(bool earlycon)
 		goto done;
 	}
 
-	/*
-	 * If the E44 erratum is required, then we need to tell the pl011
-	 * driver to implement the work-around.
-	 *
-	 * The global variable is used by the probe function when it
-	 * creates the UARTs, whether or not they're used as a console.
-	 *
-	 * If the user specifies "traditional" earlycon, the qdf2400_e44
-	 * console name matches the EARLYCON_DECLARE() statement, and
-	 * SPCR is not used.  Parameter "earlycon" is false.
-	 *
-	 * If the user specifies "SPCR" earlycon, then we need to update
-	 * the console name so that it also says "qdf2400_e44".  Parameter
-	 * "earlycon" is true.
-	 *
-	 * For consistency, if we change the console name, then we do it
-	 * for everyone, not just earlycon.
-	 */
-	if (qdf2400_erratum_44_present(&table->header)) {
-		qdf2400_e44_present = true;
-		if (earlycon)
-			uart = "qdf2400_e44";
+	switch (table->interface_type) {
+	case ACPI_DBG2_16550_COMPATIBLE:
+	case ACPI_DBG2_16550_SUBSET:
+		snprintf(uart, ACPI_SPCR_BUF_SIZE, "uart");
+		break;
+	default:
+		break;
 	}
 
-	if (xgene_8250_erratum_present(table)) {
-		iotype = "mmio32";
+	if (table->serial_port.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+		u8 width = ACPI_ACCESS_BIT_WIDTH((
+					table->serial_port.access_width));
+		switch (width) {
+		default:
+			pr_err("Unexpected SPCR Access Width.  Defaulting to byte size\n");
+		case 8:
+			snprintf(iotype, ACPI_SPCR_BUF_SIZE, "mmio");
+			break;
+		case 16:
+		case 32:
+			snprintf(iotype, ACPI_SPCR_BUF_SIZE, "mmio%d", width);
+			break;
+		}
+	} else
+		snprintf(iotype, ACPI_SPCR_BUF_SIZE, "io");
 
-		/* for xgene v1 and v2 we don't know the clock rate of the
-		 * UART so don't attempt to change to the baud rate state
-		 * in the table because driver cannot calculate the dividers
-		 */
-		snprintf(opts, sizeof(opts), "%s,%s,0x%llx", uart, iotype,
-			 table->serial_port.address);
-	} else {
-		snprintf(opts, sizeof(opts), "%s,%s,0x%llx,%d", uart, iotype,
-			 table->serial_port.address, baud_rate);
-	}
+	err = acpi_arch_setup_console(table, opts, uart, iotype, baud_rate,
+				      earlycon);
+	if (err)
+		goto done;
 
 	pr_info("console: %s\n", opts);
 
@@ -209,7 +113,6 @@ int __init parse_spcr(bool earlycon)
 		setup_earlycon(opts);
 
 	err = add_preferred_console(uart, 0, opts + strlen(uart) + 1);
-
 done:
 	acpi_put_table((struct acpi_table_header *)table);
 	return err;
diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
index 4c8b80f1c688..b22afb62c7a3 100644
--- a/drivers/tty/serial/earlycon.c
+++ b/drivers/tty/serial/earlycon.c
@@ -196,26 +196,15 @@ int __init setup_earlycon(char *buf)
 	return -ENOENT;
 }
 
-/*
- * When CONFIG_ACPI_SPCR_TABLE is defined, "earlycon" without parameters in
- * command line does not start DT earlycon immediately, instead it defers
- * starting it until DT/ACPI decision is made.  At that time if ACPI is enabled
- * call parse_spcr(), else call early_init_dt_scan_chosen_stdout()
- */
-bool earlycon_init_is_deferred __initdata;
-
 /* early_param wrapper for setup_earlycon() */
 static int __init param_setup_earlycon(char *buf)
 {
 	int err;
 
-	/*
-	 * Just 'earlycon' is a valid param for devicetree earlycons;
-	 * don't generate a warning from parse_early_params() in that case
-	 */
+	/* Just 'earlycon' is a valid param for devicetree and ACPI SPCR. */
 	if (!buf || !buf[0]) {
 		if (IS_ENABLED(CONFIG_ACPI_SPCR_TABLE)) {
-			earlycon_init_is_deferred = true;
+			console_acpi_spcr_enable = true;
 			return 0;
 		} else if (!buf) {
 			return early_init_dt_scan_chosen_stdout();
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index dc1ebfeeb5ec..875d7327d91c 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -1241,10 +1241,17 @@ static inline bool acpi_has_watchdog(void) { return false; }
 #endif
 
 #ifdef CONFIG_ACPI_SPCR_TABLE
+#define ACPI_SPCR_OPTS_SIZE 64
+#define ACPI_SPCR_BUF_SIZE 32
 extern bool qdf2400_e44_present;
-int parse_spcr(bool earlycon);
+extern bool console_acpi_spcr_enable __initdata;
+extern int acpi_arch_setup_console(struct acpi_table_spcr *table,
+				   char *opts, char *uart, char *iotype,
+				   int baud_rate, bool earlycon);
+int acpi_parse_spcr(bool earlycon);
 #else
-static inline int parse_spcr(bool earlycon) { return 0; }
+static const bool console_acpi_spcr_enable;
+static inline int acpi_parse_spcr(bool earlycon) { return 0; }
 #endif
 
 #if IS_ENABLED(CONFIG_ACPI_GENERIC_GSI)
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 37b044e78333..abfffb4b1c37 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -376,10 +376,8 @@ extern int of_setup_earlycon(const struct earlycon_id *match,
 			     const char *options);
 
 #ifdef CONFIG_SERIAL_EARLYCON
-extern bool earlycon_init_is_deferred __initdata;
 int setup_earlycon(char *buf);
 #else
-static const bool earlycon_init_is_deferred;
 static inline int setup_earlycon(char *buf) { return 0; }
 #endif
 
