510 lines
12 KiB
C
510 lines
12 KiB
C
/* Copyright 2013-2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#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);
|
|
}
|
|
|