summaryrefslogtreecommitdiff
path: root/src/cpu
diff options
context:
space:
mode:
authorIgor Bagnucki <igor.bagnucki@3mdeb.com>2020-12-14 14:52:50 +0100
committerFelix Held <felix-coreboot@felixheld.de>2022-02-11 13:53:29 +0000
commit252fc29d1afc4c1eb42122ed302a0d25a3331e7c (patch)
treea4f9f1ca0aead8182b52b7ab53ac4fc547c25fc1 /src/cpu
parent3c00c7ec6b1a26699f7aab39c6cb702aa16a3d42 (diff)
src/cpu/power9: add file structure for power9, implement SCOM access
Change-Id: Ib555ce51294c94b22d9a7c0db84d38d7928f7015 Signed-off-by: Igor Bagnucki <igor.bagnucki@3mdeb.com> Signed-off-by: Krystian Hebel <krystian.hebel@3mdeb.com> Signed-off-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/57078 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Michał Żygowski <michal.zygowski@3mdeb.com>
Diffstat (limited to 'src/cpu')
-rw-r--r--src/cpu/Makefile.inc1
-rw-r--r--src/cpu/power9/Kconfig8
-rw-r--r--src/cpu/power9/Makefile.inc6
-rw-r--r--src/cpu/power9/power9.c20
-rw-r--r--src/cpu/power9/scom.c135
5 files changed, 170 insertions, 0 deletions
diff --git a/src/cpu/Makefile.inc b/src/cpu/Makefile.inc
index 9f1e6c4bf5..2d90638bd5 100644
--- a/src/cpu/Makefile.inc
+++ b/src/cpu/Makefile.inc
@@ -7,6 +7,7 @@ subdirs-y += intel
subdirs-y += ti
subdirs-$(CONFIG_ARCH_X86) += x86
subdirs-$(CONFIG_CPU_QEMU_X86) += qemu-x86
+subdirs-$(CONFIG_CPU_POWER9) += power9
$(eval $(call create_class_compiler,cpu_microcode,x86_32))
################################################################################
diff --git a/src/cpu/power9/Kconfig b/src/cpu/power9/Kconfig
new file mode 100644
index 0000000000..c3a628cc34
--- /dev/null
+++ b/src/cpu/power9/Kconfig
@@ -0,0 +1,8 @@
+## SPDX-License-Identifier: GPL-2.0-only
+
+config CPU_POWER9
+ bool
+ select ARCH_BOOTBLOCK_PPC64
+ select ARCH_VERSTAGE_PPC64
+ select ARCH_ROMSTAGE_PPC64
+ select ARCH_RAMSTAGE_PPC64
diff --git a/src/cpu/power9/Makefile.inc b/src/cpu/power9/Makefile.inc
new file mode 100644
index 0000000000..2fe9e57a96
--- /dev/null
+++ b/src/cpu/power9/Makefile.inc
@@ -0,0 +1,6 @@
+## SPDX-License-Identifier: GPL-2.0-or-later
+
+ramstage-y += power9.c
+
+bootblock-y += scom.c
+romstage-y += scom.c
diff --git a/src/cpu/power9/power9.c b/src/cpu/power9/power9.c
new file mode 100644
index 0000000000..fd33ff219b
--- /dev/null
+++ b/src/cpu/power9/power9.c
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <cpu/cpu.h>
+#include <device/device.h>
+
+static void power9_cpu_init(struct device *dev)
+{
+}
+
+static struct device_operations cpu_dev_ops = {
+ .init = power9_cpu_init,
+};
+
+static const struct cpu_driver driver __cpu_driver = {
+ .ops = &cpu_dev_ops,
+};
+
+struct chip_operations cpu_power8_qemu_ops = {
+ CHIP_NAME("POWER9 CPU")
+};
diff --git a/src/cpu/power9/scom.c b/src/cpu/power9/scom.c
new file mode 100644
index 0000000000..e55d149bff
--- /dev/null
+++ b/src/cpu/power9/scom.c
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <cpu/power/scom.h>
+#include <cpu/power/spr.h> // HMER
+#include <console/console.h>
+
+#define XSCOM_DATA_IND_READ PPC_BIT(0)
+#define XSCOM_DATA_IND_COMPLETE PPC_BIT(32)
+#define XSCOM_DATA_IND_ERR PPC_BITMASK(33, 35)
+#define XSCOM_DATA_IND_DATA PPC_BITMASK(48, 63)
+#define XSCOM_DATA_IND_FORM1_DATA PPC_BITMASK(12, 63)
+#define XSCOM_IND_MAX_RETRIES 10
+
+#define XSCOM_RCVED_STAT_REG 0x00090018
+#define XSCOM_LOG_REG 0x00090012
+#define XSCOM_ERR_REG 0x00090013
+
+uint64_t read_scom_direct(uint64_t reg_address)
+{
+ uint64_t val;
+ uint64_t hmer = 0;
+ do {
+ /*
+ * Clearing HMER on every SCOM access seems to slow down CCS up
+ * to a point where it starts hitting timeout on "less ideal"
+ * DIMMs for write centering. Clear it only if this do...while
+ * executes more than once.
+ */
+ if ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED)
+ clear_hmer();
+
+ eieio();
+ asm volatile(
+ "ldcix %0, %1, %2" :
+ "=r"(val) :
+ "b"(MMIO_GROUP0_CHIP0_SCOM_BASE_ADDR),
+ "r"(reg_address << 3));
+ eieio();
+ hmer = read_hmer();
+ } while ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED);
+
+ if (hmer & SPR_HMER_XSCOM_STATUS) {
+ reset_scom_engine();
+ /*
+ * All F's are returned in case of error, but code polls for a set bit
+ * after changes that can make such error appear (e.g. clock settings).
+ * Return 0 so caller won't have to test for all F's in that case.
+ */
+ return 0;
+ }
+ return val;
+}
+
+void write_scom_direct(uint64_t reg_address, uint64_t data)
+{
+ uint64_t hmer = 0;
+ do {
+ /* See comment in read_scom_direct() */
+ if ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED)
+ clear_hmer();
+
+ eieio();
+ asm volatile(
+ "stdcix %0, %1, %2"::
+ "r"(data),
+ "b"(MMIO_GROUP0_CHIP0_SCOM_BASE_ADDR),
+ "r"(reg_address << 3));
+ eieio();
+ hmer = read_hmer();
+ } while ((hmer & SPR_HMER_XSCOM_STATUS) == SPR_HMER_XSCOM_OCCUPIED);
+
+ if (hmer & SPR_HMER_XSCOM_STATUS)
+ reset_scom_engine();
+}
+
+void write_scom_indirect(uint64_t reg_address, uint64_t value)
+{
+ uint64_t addr;
+ uint64_t data;
+ addr = reg_address & 0x7FFFFFFF;
+ data = reg_address & XSCOM_ADDR_IND_ADDR;
+ data |= value & XSCOM_ADDR_IND_DATA;
+
+ write_scom_direct(addr, data);
+
+ for (int retries = 0; retries < XSCOM_IND_MAX_RETRIES; ++retries) {
+ data = read_scom_direct(addr);
+ if ((data & XSCOM_DATA_IND_COMPLETE) && ((data & XSCOM_DATA_IND_ERR) == 0)) {
+ return;
+ } else if (data & XSCOM_DATA_IND_COMPLETE) {
+ printk(BIOS_EMERG, "SCOM WR error %16.16llx = %16.16llx : %16.16llx\n",
+ reg_address, value, data);
+ }
+ // TODO: delay?
+ }
+}
+
+uint64_t read_scom_indirect(uint64_t reg_address)
+{
+ uint64_t addr;
+ uint64_t data;
+ addr = reg_address & 0x7FFFFFFF;
+ data = XSCOM_DATA_IND_READ | (reg_address & XSCOM_ADDR_IND_ADDR);
+
+ write_scom_direct(addr, data);
+
+ for (int retries = 0; retries < XSCOM_IND_MAX_RETRIES; ++retries) {
+ data = read_scom_direct(addr);
+ if ((data & XSCOM_DATA_IND_COMPLETE) && ((data & XSCOM_DATA_IND_ERR) == 0)) {
+ break;
+ } else if (data & XSCOM_DATA_IND_COMPLETE) {
+ printk(BIOS_EMERG, "SCOM RD error %16.16llx : %16.16llx\n",
+ reg_address, data);
+ }
+ // TODO: delay?
+ }
+
+ return data & XSCOM_DATA_IND_DATA;
+}
+
+/* This function should be rarely called, don't make it inlined */
+void reset_scom_engine(void)
+{
+ /*
+ * With cross-CPU SCOM accesses, first register should be cleared on the
+ * executing CPU, the other two on target CPU. In that case it may be
+ * necessary to do the remote writes in assembly directly to skip checking
+ * HMER and possibly end in a loop.
+ */
+ write_scom_direct(XSCOM_RCVED_STAT_REG, 0);
+ write_scom_direct(XSCOM_LOG_REG, 0);
+ write_scom_direct(XSCOM_ERR_REG, 0);
+ clear_hmer();
+ eieio();
+}