diff options
Diffstat (limited to 'hw/lpc.c')
-rw-r--r-- | hw/lpc.c | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/hw/lpc.c b/hw/lpc.c new file mode 100644 index 0000000..8dc533d --- /dev/null +++ b/hw/lpc.c @@ -0,0 +1,500 @@ +/* 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. + */ + +#include <skiboot.h> +#include <xscom.h> +#include <io.h> +#include <lock.h> +#include <chip.h> +#include <lpc.h> +#include <timebase.h> +#include <fsp-elog.h> + +DEFINE_LOG_ENTRY(OPAL_RC_LPC_READ, OPAL_PLATFORM_ERR_EVT, OPAL_LPC, + OPAL_MISC_SUBSYSTEM, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_LPC_WRITE, OPAL_PLATFORM_ERR_EVT, OPAL_LPC, + OPAL_MISC_SUBSYSTEM, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +#define ECCB_CTL 0 /* b0020 -> b00200 */ +#define ECCB_STAT 2 /* b0022 -> b00210 */ +#define ECCB_DATA 3 /* b0023 -> b00218 */ + +#define ECCB_CTL_MAGIC 0xd000000000000000ul +#define ECCB_CTL_DATASZ_MASK PPC_BITMASK(4,7) +#define ECCB_CTL_DATASZ_LSH PPC_BITLSHIFT(7) +#define ECCB_CTL_READ PPC_BIT(15) +#define ECCB_CTL_ADDRLEN_MASK PPC_BITMASK(23,25) +#define ECCB_CTL_ADDRLEN_LSH PPC_BITLSHIFT(25) +#define ECCB_ADDRLEN_4B 0x4 +#define ECCB_CTL_ADDR_MASK PPC_BITMASK(32,63) +#define ECCB_CTL_ADDR_LSH 0 + +#define ECCB_STAT_PIB_ERR_MASK PPC_BITMASK(0,5) +#define ECCB_STAT_PIB_ERR_LSH PPC_BITLSHIFT(5) +#define ECCB_STAT_RD_DATA_MASK PPC_BITMASK(6,37) +#define ECCB_STAT_RD_DATA_LSH PPC_BITLSHIFT(37) +#define ECCB_STAT_BUSY PPC_BIT(44) +#define ECCB_STAT_ERRORS1_MASK PPC_BITMASK(45,51) +#define ECCB_STAT_ERRORS1_LSH PPC_BITLSHIFT(51) +#define ECCB_STAT_OP_DONE PPC_BIT(52) +#define ECCB_STAT_ERRORS2_MASK PPC_BITMASK(53,55) +#define ECCB_STAT_ERRORS3_LSH PPC_BITLSHIFT(55) + +#define ECCB_STAT_ERR_MASK (ECCB_STAT_PIB_ERR_MASK | \ + ECCB_STAT_ERRORS1_MASK | \ + ECCB_STAT_ERRORS2_MASK) + +#define ECCB_TIMEOUT 1000000 + +/* LPC HC registers */ +#define LPC_HC_FW_SEG_IDSEL 0x24 +#define LPC_HC_FW_RD_ACC_SIZE 0x28 +#define LPC_HC_FW_RD_1B 0x00000000 +#define LPC_HC_FW_RD_2B 0x01000000 +#define LPC_HC_FW_RD_4B 0x02000000 +#define LPC_HC_FW_RD_16B 0x04000000 +#define LPC_HC_FW_RD_128B 0x07000000 + +/* Default LPC bus */ +static int32_t lpc_default_chip_id = -1; + +/* + * These are expected to be the same on all chips and should probably + * be read (or configured) dynamically. This is how things are configured + * today on Tuletta. + */ +static uint32_t lpc_io_opb_base = 0xd0010000; +static uint32_t lpc_mem_opb_base = 0xe0000000; +static uint32_t lpc_fw_opb_base = 0xf0000000; +static uint32_t lpc_reg_opb_base = 0xc0012000; + +static int64_t opb_write(struct proc_chip *chip, uint32_t addr, uint32_t data, + uint32_t sz) +{ + uint64_t ctl = ECCB_CTL_MAGIC, stat; + int64_t rc, tout; + uint64_t data_reg; + + switch(sz) { + case 1: + data_reg = ((uint64_t)data) << 56; + break; + case 2: + data_reg = ((uint64_t)data) << 48; + break; + case 4: + data_reg = ((uint64_t)data) << 32; + break; + default: + prerror("LPC: Invalid data size %d\n", sz); + return OPAL_PARAMETER; + } + + rc = xscom_write(chip->id, chip->lpc_xbase + ECCB_DATA, data_reg); + if (rc) { + log_simple_error(&e_info(OPAL_RC_LPC_WRITE), + "LPC: XSCOM write to ECCB DATA error %lld\n", rc); + return rc; + } + + ctl = SETFIELD(ECCB_CTL_DATASZ, ctl, sz); + ctl = SETFIELD(ECCB_CTL_ADDRLEN, ctl, ECCB_ADDRLEN_4B); + ctl = SETFIELD(ECCB_CTL_ADDR, ctl, addr); + rc = xscom_write(chip->id, chip->lpc_xbase + ECCB_CTL, ctl); + if (rc) { + log_simple_error(&e_info(OPAL_RC_LPC_WRITE), + "LPC: XSCOM write to ECCB CTL error %lld\n", rc); + return rc; + } + + for (tout = 0; tout < ECCB_TIMEOUT; tout++) { + rc = xscom_read(chip->id, chip->lpc_xbase + ECCB_STAT, &stat); + if (rc) { + log_simple_error(&e_info(OPAL_RC_LPC_WRITE), + "LPC: XSCOM read from ECCB STAT err %lld\n", + rc); + return rc; + } + if (stat & ECCB_STAT_OP_DONE) { + if (stat & ECCB_STAT_ERR_MASK) { + log_simple_error(&e_info(OPAL_RC_LPC_WRITE), + "LPC: Error status: 0x%llx\n", stat); + return OPAL_HARDWARE; + } + return OPAL_SUCCESS; + } + time_wait(100); + } + log_simple_error(&e_info(OPAL_RC_LPC_WRITE), "LPC: Write timeout !\n"); + return OPAL_HARDWARE; +} + +static int64_t opb_read(struct proc_chip *chip, uint32_t addr, uint32_t *data, + uint32_t sz) +{ + uint64_t ctl = ECCB_CTL_MAGIC | ECCB_CTL_READ, stat; + int64_t rc, tout; + + if (sz != 1 && sz != 2 && sz != 4) { + prerror("LPC: Invalid data size %d\n", sz); + return OPAL_PARAMETER; + } + + ctl = SETFIELD(ECCB_CTL_DATASZ, ctl, sz); + ctl = SETFIELD(ECCB_CTL_ADDRLEN, ctl, ECCB_ADDRLEN_4B); + ctl = SETFIELD(ECCB_CTL_ADDR, ctl, addr); + rc = xscom_write(chip->id, chip->lpc_xbase + ECCB_CTL, ctl); + if (rc) { + log_simple_error(&e_info(OPAL_RC_LPC_READ), + "LPC: XSCOM write to ECCB CTL error %lld\n", rc); + return rc; + } + + for (tout = 0; tout < ECCB_TIMEOUT; tout++) { + rc = xscom_read(chip->id, chip->lpc_xbase + ECCB_STAT, &stat); + if (rc) { + log_simple_error(&e_info(OPAL_RC_LPC_READ), + "LPC: XSCOM read from ECCB STAT err %lld\n", + rc); + return rc; + } + if (stat & ECCB_STAT_OP_DONE) { + uint32_t rdata = GETFIELD(ECCB_STAT_RD_DATA, stat); + if (stat & ECCB_STAT_ERR_MASK) { + log_simple_error(&e_info(OPAL_RC_LPC_READ), + "LPC: Error status: 0x%llx\n", stat); + return OPAL_HARDWARE; + } + switch(sz) { + case 1: + *data = rdata >> 24; + break; + case 2: + *data = rdata >> 16; + break; + default: + *data = rdata; + break; + } + return 0; + } + time_wait(100); + } + log_simple_error(&e_info(OPAL_RC_LPC_READ), "LPC: Read timeout !\n"); + return OPAL_HARDWARE; +} + +static int64_t lpc_set_fw_idsel(struct proc_chip *chip, uint8_t idsel) +{ + uint32_t val; + int64_t rc; + + if (idsel == chip->lpc_fw_idsel) + return OPAL_SUCCESS; + if (idsel > 0xf) + return OPAL_PARAMETER; + + rc = opb_read(chip, lpc_reg_opb_base + LPC_HC_FW_SEG_IDSEL, + &val, 4); + if (rc) { + prerror("LPC: Failed to read HC_FW_SEG_IDSEL register !\n"); + return rc; + } + val = (val & 0xfffffff0) | idsel; + rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_FW_SEG_IDSEL, + val, 4); + if (rc) { + prerror("LPC: Failed to write HC_FW_SEG_IDSEL register !\n"); + return rc; + } + chip->lpc_fw_idsel = idsel; + return OPAL_SUCCESS; +} + +static int64_t lpc_set_fw_rdsz(struct proc_chip *chip, uint8_t rdsz) +{ + uint32_t val; + int64_t rc; + + if (rdsz == chip->lpc_fw_rdsz) + return OPAL_SUCCESS; + switch(rdsz) { + case 1: + val = LPC_HC_FW_RD_1B; + break; + case 2: + val = LPC_HC_FW_RD_2B; + break; + case 4: + val = LPC_HC_FW_RD_4B; + break; + default: + /* + * The HW supports 16 and 128 via a buffer/cache + * but I have never exprimented with it and am not + * sure it works the way we expect so let's leave it + * at that for now + */ + return OPAL_PARAMETER; + } + rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_FW_RD_ACC_SIZE, + val, 4); + if (rc) { + prerror("LPC: Failed to write LPC_HC_FW_RD_ACC_SIZE !\n"); + return rc; + } + chip->lpc_fw_rdsz = rdsz; + return OPAL_SUCCESS; +} + +static int64_t lpc_opb_prepare(struct proc_chip *chip, + enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t sz, + uint32_t *opb_base, bool is_write) +{ + uint32_t top = addr + sz; + uint8_t fw_idsel; + int64_t rc; + + /* Address wraparound */ + if (top < addr) + return OPAL_PARAMETER; + + /* + * Bound check access and get the OPB base address for + * the window corresponding to the access type + */ + switch(addr_type) { + case OPAL_LPC_IO: + /* IO space is 64K */ + if (top > 0x10000) + return OPAL_PARAMETER; + /* And only supports byte accesses */ + if (sz != 1) + return OPAL_PARAMETER; + *opb_base = lpc_io_opb_base; + break; + case OPAL_LPC_MEM: + /* MEM space is 256M */ + if (top > 0x10000000) + return OPAL_PARAMETER; + /* And only supports byte accesses */ + if (sz != 1) + return OPAL_PARAMETER; + *opb_base = lpc_mem_opb_base; + break; + case OPAL_LPC_FW: + /* + * FW space is in segments of 256M controlled + * by IDSEL, make sure we don't cross segments + */ + *opb_base = lpc_fw_opb_base; + fw_idsel = (addr >> 28); + if (((top - 1) >> 28) != fw_idsel) + return OPAL_PARAMETER; + + /* Set segment */ + rc = lpc_set_fw_idsel(chip, fw_idsel); + if (rc) + return rc; + /* Set read access size */ + if (!is_write) { + rc = lpc_set_fw_rdsz(chip, sz); + if (rc) + return rc; + } + break; + default: + return OPAL_PARAMETER; + } + return OPAL_SUCCESS; +} + +static int64_t __lpc_write(uint32_t chip_id, enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t data, uint32_t sz) +{ + struct proc_chip *chip = get_chip(chip_id); + uint32_t opb_base; + int64_t rc; + + if (!chip || !chip->lpc_xbase) + return OPAL_PARAMETER; + + lock(&chip->lpc_lock); + + /* + * Convert to an OPB access and handle LPC HC configuration + * for FW accesses (IDSEL) + */ + rc = lpc_opb_prepare(chip, addr_type, addr, sz, &opb_base, true); + if (rc) + goto bail; + + /* Perform OPB access */ + rc = opb_write(chip, opb_base + addr, data, sz); + + unlock(&chip->lpc_lock); + + /* XXX Add LPC error handling/recovery */ + bail: + return rc; +} + +int64_t lpc_write(enum OpalLPCAddressType addr_type, uint32_t addr, + uint32_t data, uint32_t sz) +{ + if (lpc_default_chip_id < 0) + return OPAL_PARAMETER; + return __lpc_write(lpc_default_chip_id, addr_type, addr, data, sz); +} + +/* + * The "OPAL" variant add the emulation of 2 and 4 byte accesses using + * byte accesses for IO and MEM space in order to be compatible with + * existing Linux expectations + */ +static int64_t opal_lpc_write(uint32_t chip_id, enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t data, uint32_t sz) +{ + int64_t rc; + + if (addr_type == OPAL_LPC_FW || sz == 1) + return __lpc_write(chip_id, addr_type, addr, data, sz); + while(sz--) { + rc = __lpc_write(chip_id, addr_type, addr, data & 0xff, 1); + if (rc) + return rc; + addr++; + data >>= 8; + } + return OPAL_SUCCESS; +} + +static int64_t __lpc_read(uint32_t chip_id, enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t *data, uint32_t sz) +{ + struct proc_chip *chip = get_chip(chip_id); + uint32_t opb_base; + int64_t rc; + + if (!chip || !chip->lpc_xbase) + return OPAL_PARAMETER; + + lock(&chip->lpc_lock); + + /* + * Convert to an OPB access and handle LPC HC configuration + * for FW accesses (IDSEL and read size) + */ + rc = lpc_opb_prepare(chip, addr_type, addr, sz, &opb_base, false); + if (rc) + goto bail; + + /* Perform OPB access */ + rc = opb_read(chip, opb_base + addr, data, sz); + + unlock(&chip->lpc_lock); + + /* XXX Add LPC error handling/recovery */ + bail: + return rc; +} + +int64_t lpc_read(enum OpalLPCAddressType addr_type, uint32_t addr, + uint32_t *data, uint32_t sz) +{ + if (lpc_default_chip_id < 0) + return OPAL_PARAMETER; + return __lpc_read(lpc_default_chip_id, addr_type, addr, data, sz); +} + +/* + * The "OPAL" variant add the emulation of 2 and 4 byte accesses using + * byte accesses for IO and MEM space in order to be compatible with + * existing Linux expectations + */ +static int64_t opal_lpc_read(uint32_t chip_id, enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t *data, uint32_t sz) +{ + int64_t rc; + + if (addr_type == OPAL_LPC_FW || sz == 1) + return __lpc_read(chip_id, addr_type, addr, data, sz); + *data = 0; + while(sz--) { + uint32_t byte; + + rc = __lpc_read(chip_id, addr_type, addr, &byte, 1); + if (rc) + return rc; + *data = *data | (byte << (8 * sz)); + addr++; + } + return OPAL_SUCCESS; +} + +bool lpc_present(void) +{ + return lpc_default_chip_id >= 0; +} + +void lpc_init(void) +{ + struct dt_node *xn; + bool has_lpc = false; + + dt_for_each_compatible(dt_root, xn, "ibm,power8-lpc") { + uint32_t gcid = dt_get_chip_id(xn); + struct proc_chip *chip; + + chip = get_chip(gcid); + assert(chip); + + chip->lpc_xbase = dt_get_address(xn, 0, NULL); + chip->lpc_fw_idsel = 0xff; + chip->lpc_fw_rdsz = 0xff; + init_lock(&chip->lpc_lock); + + if (lpc_default_chip_id < 0 || + dt_has_node_property(xn, "primary", NULL)) { + lpc_default_chip_id = chip->id; + } + + printf("LPC: Bus on chip %d PCB_Addr=0x%x\n", + chip->id, chip->lpc_xbase); + has_lpc = true; + } + if (lpc_default_chip_id >= 0) + printf("LPC: Default bus on chip %d\n", lpc_default_chip_id); + + if (has_lpc) { + opal_register(OPAL_LPC_WRITE, opal_lpc_write, 5); + opal_register(OPAL_LPC_READ, opal_lpc_read, 5); + } +} + +void lpc_used_by_console(void) +{ + struct proc_chip *chip; + + xscom_used_by_console(); + + for_each_chip(chip) + chip->lpc_lock.in_con_path = true; +} |