diff options
Diffstat (limited to 'roms/skiboot/hw/ast-bmc/ast-io.c')
-rw-r--r-- | roms/skiboot/hw/ast-bmc/ast-io.c | 498 |
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); +} + |