aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hw/ast-bmc/ast-io.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hw/ast-bmc/ast-io.c')
-rw-r--r--roms/skiboot/hw/ast-bmc/ast-io.c498
1 files changed, 498 insertions, 0 deletions
diff --git a/roms/skiboot/hw/ast-bmc/ast-io.c b/roms/skiboot/hw/ast-bmc/ast-io.c
new file mode 100644
index 000000000..f0f8c4c4d
--- /dev/null
+++ b/roms/skiboot/hw/ast-bmc/ast-io.c
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Note about accesses to the AST2400 internal memory map:
+ *
+ * There are two ways to genrate accesses to the AHB bus of the AST2400
+ * from the host. The LPC->AHB bridge and the iLPC->AHB bridge.
+ *
+ * LPC->AHB bridge
+ * ---------------
+ *
+ * This bridge directly converts memory or firmware accesses using
+ * a set of registers for establishing a remapping window. We prefer
+ * using FW space as normal memory space is limited to byte accesses
+ * to a fixed 256M window, while FW space allows us to use different
+ * access sizes and to control the IDSEL bits which essentially enable
+ * a full 4G address space.
+ *
+ * The way FW accesses map onto AHB is controlled via two registers
+ * in the BMC's LPC host controller:
+ *
+ * HICR7 at 0x1e789088 [31:16] : ADRBASE
+ * [15:00] : HWMBASE
+ *
+ * HICR8 at 0x1e78908c [31:16] : ADRMASK
+ * [15:00] : HWNCARE
+ *
+ * All decoding/remapping happens on the top 16 bits of the LPC address
+ * named LPC_ADDR as follow:
+ *
+ * - For decoding, LPC_ADDR bits are compared with HWMBASE if the
+ * corresponding bit in HWNCARE is 0.
+ *
+ * - For remapping, the AHB address is constructed by taking bits
+ * from LPC_ADDR if the corresponding bit in ADRMASK is 0 or in
+ * ADRBASE if the corresponding bit in ADRMASK is 1
+ *
+ * Example of 2MB SPI flash, LPC 0xFCE00000~0xFCFFFFFF onto
+ * AHB 0x30000000~0x301FFFFF (SPI flash)
+ *
+ * ADRBASE=0x3000 HWMBASE=0xFCE0
+ * ADRMASK=0xFFE0 HWNCARE=0x001F
+ *
+ * This comes pre-configured by the BMC or HostBoot to access the PNOR
+ * flash from IDSEL 0 as follow:
+ *
+ * ADRBASE=0x3000 HWMBASE=0x0e00 for 32MB
+ * ADRMASK=0xfe00 HWNCARE=0x01ff
+ *
+ * Which means mapping of LPC 0x0e000000..0x0fffffff onto
+ * AHB 0x30000000..0x31ffffff
+ *
+ * iLPC->AHB bridge
+ * ---------------
+ *
+ * This bridge is hosted in the SuperIO part of the BMC and is
+ * controlled by a series of byte-sized registers accessed indirectly
+ * via IO ports 0x2e and 0x2f.
+ *
+ * Via these, byte by byte, we can construct an AHB address and
+ * fill a data buffer to trigger a write cycle, or we can do a
+ * read cycle and read back the data, byte after byte.
+ *
+ * This is fairly convoluted and slow but works regardless of what
+ * mapping was established in the LPC->AHB bridge.
+ *
+ * For the time being, we use the iLPC->AHB for everything except
+ * pnor accesses. In the long run, we will reconfigure the LPC->AHB
+ * to provide more direct access to all of the BMC address space but
+ * we'll only do that after the boot script/program on the BMC is
+ * updated to restore the bridge to a state compatible with the SBE
+ * expectations on boot.
+ *
+ * Copyright 2013-2019 IBM Corp.
+ */
+
+#include <skiboot.h>
+#include <lpc.h>
+#include <lock.h>
+#include <device.h>
+
+#include "ast.h"
+
+#define BMC_SIO_SCR28 0x28
+#define BOOT_FLAGS_VERSION 0x42
+
+/*
+ * SIO Register 0x29: Boot Flags (normal bit ordering)
+ *
+ * [7:6] Hostboot Boot mode:
+ * 00 : Normal
+ * 01 : Terminate on first error
+ * 10 : istep mode
+ * 11 : reserved
+ * [5:4] Boot options
+ * 00 : reserved
+ * 01 : Memboot
+ * 10 : Clear gard
+ * 11 : reserved
+ * [ 3 ] BMC mbox PNOR driver
+ * [2:0] Hostboot Log level:
+ * 000 : Normal
+ * 001 : Enable Scan trace
+ * xxx : reserved
+ */
+
+#define BMC_SIO_SCR29 0x29
+#define BMC_SIO_SCR29_MBOX 0x08
+#define BMC_SIO_SCR29_MEMBOOT 0x10
+
+/*
+ * SIO Register 0x2d: Platform Flags (normal bit ordering)
+ *
+ * [ 7 ] Hostboot configures SUART
+ * [ 6 ] Hostboot configures VUART
+ * [5:1] Reserved
+ * [ 0 ] Isolate Service Processor
+ */
+#define BMC_SIO_PLAT_FLAGS 0x2d
+#define BMC_SIO_PLAT_ISOLATE_SP 0x01
+
+enum {
+ BMC_SIO_DEV_NONE = -1,
+ BMC_SIO_DEV_UART1 = 2,
+ BMC_SIO_DEV_UART2 = 3,
+ BMC_SIO_DEV_SWC = 4,
+ BMC_SIO_DEV_KBC = 5,
+ BMC_SIO_DEV_P80 = 7,
+ BMC_SIO_DEV_UART3 = 0xb,
+ BMC_SIO_DEV_UART4 = 0xc,
+ BMC_SIO_DEV_LPC2AHB = 0xd,
+ BMC_SIO_DEV_MBOX = 0xe,
+};
+
+static struct lock bmc_sio_lock = LOCK_UNLOCKED;
+static int bmc_sio_cur_dev = BMC_SIO_DEV_NONE;
+
+/*
+ * SuperIO indirect accesses
+ */
+static void bmc_sio_outb(uint8_t val, uint8_t reg)
+{
+ lpc_outb(reg, 0x2e);
+ lpc_outb(val, 0x2f);
+}
+
+static uint8_t bmc_sio_inb(uint8_t reg)
+{
+ lpc_outb(reg, 0x2e);
+ return lpc_inb(0x2f);
+}
+
+static void bmc_sio_get(int dev)
+{
+ lock(&bmc_sio_lock);
+
+ if (bmc_sio_cur_dev == dev || dev < 0)
+ return;
+
+ if (bmc_sio_cur_dev == BMC_SIO_DEV_NONE) {
+ /* Send SuperIO password */
+ lpc_outb(0xa5, 0x2e);
+ lpc_outb(0xa5, 0x2e);
+ }
+
+ /* Select logical dev */
+ bmc_sio_outb(dev, 0x07);
+
+ bmc_sio_cur_dev = dev;
+}
+
+static void bmc_sio_put(bool lock_sio)
+{
+ if (lock_sio) {
+ /* Re-lock SuperIO */
+ lpc_outb(0xaa, 0x2e);
+
+ bmc_sio_cur_dev = BMC_SIO_DEV_NONE;
+ }
+ unlock(&bmc_sio_lock);
+}
+
+/*
+ * AHB accesses via iLPC->AHB in SuperIO. Works on byteswapped
+ * values (ie. Little Endian registers)
+ */
+static void bmc_sio_ahb_prep(uint32_t reg, uint8_t type)
+{
+ /* Enable iLPC->AHB */
+ bmc_sio_outb(0x01, 0x30);
+
+ /* Address */
+ bmc_sio_outb((reg >> 24) & 0xff, 0xf0);
+ bmc_sio_outb((reg >> 16) & 0xff, 0xf1);
+ bmc_sio_outb((reg >> 8) & 0xff, 0xf2);
+ bmc_sio_outb((reg ) & 0xff, 0xf3);
+
+ /* bytes cycle type */
+ bmc_sio_outb(type, 0xf8);
+}
+
+static void bmc_sio_ahb_writel(uint32_t val, uint32_t reg)
+{
+ bmc_sio_get(BMC_SIO_DEV_LPC2AHB);
+
+ bmc_sio_ahb_prep(reg, 2);
+
+ /* Write data */
+ bmc_sio_outb(val >> 24, 0xf4);
+ bmc_sio_outb(val >> 16, 0xf5);
+ bmc_sio_outb(val >> 8, 0xf6);
+ bmc_sio_outb(val , 0xf7);
+
+ /* Trigger */
+ bmc_sio_outb(0xcf, 0xfe);
+
+ bmc_sio_put(false);
+}
+
+static uint32_t bmc_sio_ahb_readl(uint32_t reg)
+{
+ uint32_t val = 0;
+
+ bmc_sio_get(BMC_SIO_DEV_LPC2AHB);
+
+ bmc_sio_ahb_prep(reg, 2);
+
+ /* Trigger */
+ bmc_sio_inb(0xfe);
+
+ /* Read results */
+ val = (val << 8) | bmc_sio_inb(0xf4);
+ val = (val << 8) | bmc_sio_inb(0xf5);
+ val = (val << 8) | bmc_sio_inb(0xf6);
+ val = (val << 8) | bmc_sio_inb(0xf7);
+
+ bmc_sio_put(false);
+
+ return val;
+}
+
+/*
+ * External API
+ *
+ * We only support 4-byte accesses to all of AHB. We additionally
+ * support 1-byte accesses to the flash area only.
+ *
+ * We could support all access sizes via iLPC but we don't need
+ * that for now.
+ */
+
+void ast_ahb_writel(uint32_t val, uint32_t reg)
+{
+ /* For now, always use iLPC->AHB, it will byteswap */
+ bmc_sio_ahb_writel(val, reg);
+}
+
+uint32_t ast_ahb_readl(uint32_t reg)
+{
+ /* For now, always use iLPC->AHB, it will byteswap */
+ return bmc_sio_ahb_readl(reg);
+}
+
+static void ast_setup_sio_irq_polarity(void)
+{
+ /* Select logical dev 2 */
+ bmc_sio_get(BMC_SIO_DEV_UART1);
+ bmc_sio_outb(0x01, 0x71); /* level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev 3 */
+ bmc_sio_get(BMC_SIO_DEV_UART2);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev 4 */
+ bmc_sio_get(BMC_SIO_DEV_SWC);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev 5 */
+ bmc_sio_get(BMC_SIO_DEV_KBC);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_outb(0x01, 0x73); /* irq level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev 7 */
+ bmc_sio_get(BMC_SIO_DEV_P80);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev d */
+ bmc_sio_get(BMC_SIO_DEV_UART3);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev c */
+ bmc_sio_get(BMC_SIO_DEV_UART4);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev d */
+ bmc_sio_get(BMC_SIO_DEV_LPC2AHB);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_put(false);
+
+ /* Select logical dev e */
+ bmc_sio_get(BMC_SIO_DEV_MBOX);
+ bmc_sio_outb(0x01, 0x71); /* irq level low */
+ bmc_sio_put(true);
+}
+
+bool ast_sio_is_enabled(void)
+{
+ bool enabled;
+ int64_t rc;
+
+ lock(&bmc_sio_lock);
+ /*
+ * Probe by attempting to lock the SIO device, this way the
+ * post-condition is that the SIO device is locked or not able to be
+ * unlocked. This turns out neater than trying to use the unlock code.
+ */
+ rc = lpc_probe_write(OPAL_LPC_IO, 0x2e, 0xaa, 1);
+ if (rc) {
+ enabled = false;
+ /* If we can't lock it, then we can't unlock it either */
+ goto out;
+ }
+
+ /*
+ * Now that we know that is locked and able to be unlocked, unlock it
+ * if skiboot's recorded device state indicates it was previously
+ * unlocked.
+ */
+ if (bmc_sio_cur_dev != BMC_SIO_DEV_NONE) {
+ /* Send SuperIO password */
+ lpc_outb(0xa5, 0x2e);
+ lpc_outb(0xa5, 0x2e);
+
+ /* Ensure the previously selected logical dev is selected */
+ bmc_sio_outb(bmc_sio_cur_dev, 0x07);
+ }
+
+ enabled = true;
+out:
+ unlock(&bmc_sio_lock);
+
+ return enabled;
+}
+
+bool ast_sio_init(void)
+{
+ bool enabled = ast_sio_is_enabled();
+
+ /* Configure all AIO interrupts to level low */
+ if (enabled)
+ ast_setup_sio_irq_polarity();
+
+ return enabled;
+}
+
+bool ast_io_is_rw(void)
+{
+ return !(ast_ahb_readl(LPC_HICRB) & LPC_HICRB_ILPC_DISABLE);
+}
+
+bool ast_io_init(void)
+{
+ return ast_io_is_rw();
+}
+
+bool ast_lpc_fw_ipmi_hiomap(void)
+{
+ return platform.bmc->sw->ipmi_oem_hiomap_cmd != 0;
+}
+
+bool ast_lpc_fw_mbox_hiomap(void)
+{
+ struct dt_node *n;
+
+ n = dt_find_compatible_node(dt_root, NULL, "mbox");
+
+ return n != NULL;
+}
+
+bool ast_lpc_fw_maps_flash(void)
+{
+ uint8_t boot_version;
+ uint8_t boot_flags;
+
+ boot_version = bmc_sio_inb(BMC_SIO_SCR28);
+ if (boot_version != BOOT_FLAGS_VERSION)
+ return true;
+
+ boot_flags = bmc_sio_inb(BMC_SIO_SCR29);
+ return !(boot_flags & BMC_SIO_SCR29_MEMBOOT);
+}
+
+bool ast_scratch_reg_is_mbox(void)
+{
+ uint8_t boot_version;
+ uint8_t boot_flags;
+
+ boot_version = bmc_sio_inb(BMC_SIO_SCR28);
+ if (boot_version != BOOT_FLAGS_VERSION)
+ return false;
+
+ boot_flags = bmc_sio_inb(BMC_SIO_SCR29);
+ return boot_flags & BMC_SIO_SCR29_MBOX;
+}
+
+void ast_setup_ibt(uint16_t io_base, uint8_t irq)
+{
+ uint32_t v;
+
+ v = bmc_sio_ahb_readl(LPC_iBTCR0);
+ v = v & ~(0xfffffc00u);
+ v = v | (((uint32_t)io_base) << 16);
+ v = v | (((uint32_t)irq) << 12);
+ bmc_sio_ahb_writel(v, LPC_iBTCR0);
+}
+
+bool ast_is_vuart1_enabled(void)
+{
+ uint32_t v;
+
+ v = bmc_sio_ahb_readl(VUART1_GCTRLA);
+ return !!(v & 1);
+}
+
+void ast_setup_vuart1(uint16_t io_base, uint8_t irq)
+{
+ uint32_t v;
+
+ /* IRQ level low */
+ v = bmc_sio_ahb_readl(VUART1_GCTRLA);
+ v = v & ~2u;
+ bmc_sio_ahb_writel(v, VUART1_GCTRLA);
+ v = bmc_sio_ahb_readl(VUART1_GCTRLA);
+
+ /* IRQ number */
+ v = bmc_sio_ahb_readl(VUART1_GCTRLB);
+ v = (v & ~0xf0u) | (irq << 4);
+ bmc_sio_ahb_writel(v, VUART1_GCTRLB);
+
+ /* Address */
+ bmc_sio_ahb_writel(io_base & 0xff, VUART1_ADDRL);
+ bmc_sio_ahb_writel(io_base >> 8, VUART1_ADDRH);
+}
+
+/* Setup SuperIO UART 1 */
+void ast_setup_sio_uart1(uint16_t io_base, uint8_t irq)
+{
+ bmc_sio_get(BMC_SIO_DEV_UART1);
+
+ /* Disable UART1 for configuration */
+ bmc_sio_outb(0x00, 0x30);
+
+ /* Configure base and interrupt */
+ bmc_sio_outb(io_base >> 8, 0x60);
+ bmc_sio_outb(io_base & 0xff, 0x61);
+ bmc_sio_outb(irq, 0x70);
+ bmc_sio_outb(0x01, 0x71); /* level low */
+
+ /* Enable UART1 */
+ bmc_sio_outb(0x01, 0x30);
+
+ bmc_sio_put(true);
+}
+
+void ast_disable_sio_uart1(void)
+{
+ bmc_sio_get(BMC_SIO_DEV_UART1);
+
+ /* Disable UART1 */
+ bmc_sio_outb(0x00, 0x30);
+
+ bmc_sio_put(true);
+}
+
+void ast_setup_sio_mbox(uint16_t io_base, uint8_t irq)
+{
+ bmc_sio_get(BMC_SIO_DEV_MBOX);
+
+ /* Disable for configuration */
+ bmc_sio_outb(0x00, 0x30);
+
+ bmc_sio_outb(io_base >> 8, 0x60);
+ bmc_sio_outb(io_base & 0xff, 0x61);
+ bmc_sio_outb(irq, 0x70);
+ bmc_sio_outb(0x01, 0x71); /* level low */
+
+ /* Enable MailBox */
+ bmc_sio_outb(0x01, 0x30);
+
+ bmc_sio_put(true);
+}
+