diff options
Diffstat (limited to 'roms/skiboot/hw/lpc.c')
-rw-r--r-- | roms/skiboot/hw/lpc.c | 1407 |
1 files changed, 1407 insertions, 0 deletions
diff --git a/roms/skiboot/hw/lpc.c b/roms/skiboot/hw/lpc.c new file mode 100644 index 000000000..bf3ab1fae --- /dev/null +++ b/roms/skiboot/hw/lpc.c @@ -0,0 +1,1407 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Low Pin Count (LPC) Bus. + * + * Copyright 2013-2019 IBM Corp. + */ + +#define pr_fmt(fmt) "LPC: " fmt + +#include <skiboot.h> +#include <xscom.h> +#include <io.h> +#include <lock.h> +#include <chip.h> +#include <lpc.h> +#include <timebase.h> +#include <errorlog.h> +#include <opal-api.h> +#include <platform.h> +#include <psi.h> +#include <interrupts.h> + +//#define DBG_IRQ(fmt...) prerror(fmt) +#define DBG_IRQ(fmt...) do { } while(0) + +DEFINE_LOG_ENTRY(OPAL_RC_LPC_READ, OPAL_PLATFORM_ERR_EVT, OPAL_LPC, + OPAL_MISC_SUBSYSTEM, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA); + +DEFINE_LOG_ENTRY(OPAL_RC_LPC_WRITE, OPAL_PLATFORM_ERR_EVT, OPAL_LPC, + OPAL_MISC_SUBSYSTEM, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA); + +DEFINE_LOG_ENTRY(OPAL_RC_LPC_SYNC, OPAL_PLATFORM_ERR_EVT, OPAL_LPC, + OPAL_MISC_SUBSYSTEM, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA); + +/* Used exclusively in manufacturing mode */ +DEFINE_LOG_ENTRY(OPAL_RC_LPC_SYNC_PERF, OPAL_PLATFORM_ERR_EVT, OPAL_LPC, + OPAL_MISC_SUBSYSTEM, OPAL_UNRECOVERABLE_ERR_DEGRADE_PERF, + OPAL_NA); + +#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 PPC_BITMASK(4,7) +#define ECCB_CTL_READ PPC_BIT(15) +#define ECCB_CTL_ADDRLEN PPC_BITMASK(23,25) +#define ECCB_ADDRLEN_4B 0x4 +#define ECCB_CTL_ADDR PPC_BITMASK(32,63) + +#define ECCB_STAT_PIB_ERR PPC_BITMASK(0,5) +#define ECCB_STAT_RD_DATA PPC_BITMASK(6,37) +#define ECCB_STAT_BUSY PPC_BIT(44) +#define ECCB_STAT_ERRORS1 PPC_BITMASK(45,51) +#define ECCB_STAT_OP_DONE PPC_BIT(52) +#define ECCB_STAT_ERRORS2 PPC_BITMASK(53,55) + +#define ECCB_STAT_ERR_MASK (ECCB_STAT_PIB_ERR | \ + ECCB_STAT_ERRORS1 | \ + ECCB_STAT_ERRORS2) + +#define ECCB_TIMEOUT 1000000 + +/* OPB Master LS registers */ +#define OPB_MASTER_LS_IRQ_STAT 0x50 +#define OPB_MASTER_LS_IRQ_MASK 0x54 +#define OPB_MASTER_LS_IRQ_POL 0x58 +#define OPB_MASTER_IRQ_LPC 0x00000800 + +/* 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 +#define LPC_HC_IRQSER_CTRL 0x30 +#define LPC_HC_IRQSER_EN 0x80000000 +#define LPC_HC_IRQSER_QMODE 0x40000000 +#define LPC_HC_IRQSER_START_MASK 0x03000000 +#define LPC_HC_IRQSER_START_4CLK 0x00000000 +#define LPC_HC_IRQSER_START_6CLK 0x01000000 +#define LPC_HC_IRQSER_START_8CLK 0x02000000 +#define LPC_HC_IRQSER_AUTO_CLEAR 0x00800000 +#define LPC_HC_IRQMASK 0x34 /* same bit defs as LPC_HC_IRQSTAT */ +#define LPC_HC_IRQSTAT 0x38 +#define LPC_HC_IRQ_SERIRQ0 0x80000000u /* all bits down to ... */ +#define LPC_HC_IRQ_SERIRQ16 0x00008000 /* IRQ16=IOCHK#, IRQ2=SMI# */ +#define LPC_HC_IRQ_SERIRQ_ALL 0xffff8000 +#define LPC_HC_IRQ_LRESET 0x00000400 +#define LPC_HC_IRQ_SYNC_ABNORM_ERR 0x00000080 +#define LPC_HC_IRQ_SYNC_NORESP_ERR 0x00000040 +#define LPC_HC_IRQ_SYNC_NORM_ERR 0x00000020 +#define LPC_HC_IRQ_SYNC_TIMEOUT_ERR 0x00000010 +#define LPC_HC_IRQ_TARG_TAR_ERR 0x00000008 +#define LPC_HC_IRQ_BM_TAR_ERR 0x00000004 +#define LPC_HC_IRQ_BM0_REQ 0x00000002 +#define LPC_HC_IRQ_BM1_REQ 0x00000001 +#define LPC_HC_IRQ_BASE_IRQS ( \ + LPC_HC_IRQ_LRESET | \ + LPC_HC_IRQ_SYNC_ABNORM_ERR | \ + LPC_HC_IRQ_SYNC_NORESP_ERR | \ + LPC_HC_IRQ_SYNC_NORM_ERR | \ + LPC_HC_IRQ_SYNC_TIMEOUT_ERR | \ + LPC_HC_IRQ_TARG_TAR_ERR | \ + LPC_HC_IRQ_BM_TAR_ERR) +#define LPC_HC_ERROR_ADDRESS 0x40 + +#define LPC_NUM_SERIRQ 17 + +enum { + LPC_ROUTE_FREE = 0, + LPC_ROUTE_OPAL, + LPC_ROUTE_LINUX +}; + +struct lpc_error_entry { + int64_t rc; + const char *description; +}; + +struct lpcm { + uint32_t chip_id; + uint32_t xbase; + void *mbase; + struct lock lock; + uint8_t fw_idsel; + uint8_t fw_rdsz; + struct list_head clients; + bool has_serirq; + uint8_t sirq_routes[LPC_NUM_SERIRQ]; + bool sirq_routed[LPC_NUM_SERIRQ]; + uint32_t sirq_rmasks[4]; + uint8_t sirq_ralloc[4]; + struct dt_node *node; +}; + + +#define LPC_BUS_DEGRADED_PERF_THRESHOLD 5 + +struct lpc_client_entry { + struct list_node node; + const struct lpc_client *clt; + uint32_t policy; +}; + +/* Default LPC bus */ +static int32_t lpc_default_chip_id = -1; +static bool lpc_irqs_ready; + +/* + * 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 uint32_t opb_master_reg_base = 0xc0010000; + +static int64_t opb_mmio_write(struct lpcm *lpc, uint32_t addr, uint32_t data, + uint32_t sz) +{ + switch (sz) { + case 1: + out_8(lpc->mbase + addr, data); + return OPAL_SUCCESS; + case 2: + out_be16(lpc->mbase + addr, data); + return OPAL_SUCCESS; + case 4: + out_be32(lpc->mbase + addr, data); + return OPAL_SUCCESS; + } + prerror("Invalid data size %d\n", sz); + return OPAL_PARAMETER; +} + +static int64_t opb_write(struct lpcm *lpc, uint32_t addr, uint32_t data, + uint32_t sz) +{ + uint64_t ctl = ECCB_CTL_MAGIC, stat; + int64_t rc, tout; + uint64_t data_reg; + + if (lpc->mbase) + return opb_mmio_write(lpc, addr, data, sz); + + 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("Invalid data size %d\n", sz); + return OPAL_PARAMETER; + } + + rc = xscom_write(lpc->chip_id, 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(lpc->chip_id, 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(lpc->chip_id, 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_nopoll(100); + } + log_simple_error(&e_info(OPAL_RC_LPC_WRITE), "LPC: Write timeout !\n"); + return OPAL_HARDWARE; +} + +static int64_t opb_mmio_read(struct lpcm *lpc, uint32_t addr, uint32_t *data, + uint32_t sz) +{ + switch (sz) { + case 1: + *data = in_8(lpc->mbase + addr); + return OPAL_SUCCESS; + case 2: + *data = in_be16(lpc->mbase + addr); + return OPAL_SUCCESS; + case 4: + *data = in_be32(lpc->mbase + addr); + return OPAL_SUCCESS; + } + prerror("Invalid data size %d\n", sz); + return OPAL_PARAMETER; +} + +static int64_t opb_read(struct lpcm *lpc, uint32_t addr, uint32_t *data, + uint32_t sz) +{ + uint64_t ctl = ECCB_CTL_MAGIC | ECCB_CTL_READ, stat; + int64_t rc, tout; + + if (lpc->mbase) + return opb_mmio_read(lpc, addr, data, sz); + + if (sz != 1 && sz != 2 && sz != 4) { + prerror("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(lpc->chip_id, 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(lpc->chip_id, 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_nopoll(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 lpcm *lpc, uint8_t idsel) +{ + uint32_t val; + int64_t rc; + + if (idsel == lpc->fw_idsel) + return OPAL_SUCCESS; + if (idsel > 0xf) + return OPAL_PARAMETER; + + rc = opb_read(lpc, lpc_reg_opb_base + LPC_HC_FW_SEG_IDSEL, + &val, 4); + if (rc) { + prerror("Failed to read HC_FW_SEG_IDSEL register !\n"); + return rc; + } + val = (val & 0xfffffff0) | idsel; + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_FW_SEG_IDSEL, + val, 4); + if (rc) { + prerror("Failed to write HC_FW_SEG_IDSEL register !\n"); + return rc; + } + lpc->fw_idsel = idsel; + return OPAL_SUCCESS; +} + +static int64_t lpc_set_fw_rdsz(struct lpcm *lpc, uint8_t rdsz) +{ + uint32_t val; + int64_t rc; + + if (rdsz == 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(lpc, lpc_reg_opb_base + LPC_HC_FW_RD_ACC_SIZE, + val, 4); + if (rc) { + prerror("Failed to write LPC_HC_FW_RD_ACC_SIZE !\n"); + return rc; + } + lpc->fw_rdsz = rdsz; + return OPAL_SUCCESS; +} + +static int64_t lpc_opb_prepare(struct lpcm *lpc, + 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(lpc, fw_idsel); + if (rc) + return rc; + /* Set read access size */ + if (!is_write) { + rc = lpc_set_fw_rdsz(lpc, sz); + if (rc) + return rc; + } + break; + default: + return OPAL_PARAMETER; + } + return OPAL_SUCCESS; +} + +#define LPC_ERROR_IDX(x) (__builtin_ffs(x) - 1 - 2) +#define LPC_ERROR(_sts, _rc, _description) \ + [LPC_ERROR_IDX(_sts)] = { _rc, _description } +static const struct lpc_error_entry lpc_error_table[] = { + LPC_ERROR(LPC_HC_IRQ_BM_TAR_ERR, OPAL_WRONG_STATE, "Got bus master TAR error."), + LPC_ERROR(LPC_HC_IRQ_TARG_TAR_ERR, OPAL_WRONG_STATE, "Got abnormal TAR error."), + LPC_ERROR(LPC_HC_IRQ_SYNC_TIMEOUT_ERR, OPAL_TIMEOUT, "Got SYNC timeout error."), + LPC_ERROR(LPC_HC_IRQ_SYNC_NORM_ERR, OPAL_WRONG_STATE, "Got SYNC normal error."), + LPC_ERROR(LPC_HC_IRQ_SYNC_NORESP_ERR, OPAL_HARDWARE, "Got SYNC no-response error."), + LPC_ERROR(LPC_HC_IRQ_SYNC_ABNORM_ERR, OPAL_WRONG_STATE, "Got SYNC abnormal error."), +}; + +static int64_t lpc_probe_prepare(struct lpcm *lpc) +{ + const uint32_t irqmask_addr = lpc_reg_opb_base + LPC_HC_IRQMASK; + const uint32_t irqstat_addr = lpc_reg_opb_base + LPC_HC_IRQSTAT; + uint32_t irqmask; + int rc; + + rc = opb_read(lpc, irqmask_addr, &irqmask, 4); + if (rc) + return rc; + + irqmask &= ~LPC_HC_IRQ_SYNC_NORESP_ERR; + rc = opb_write(lpc, irqmask_addr, irqmask, 4); + if (rc) + return rc; + + return opb_write(lpc, irqstat_addr, LPC_HC_IRQ_SYNC_NORESP_ERR, 4); +} + +static int64_t lpc_probe_test(struct lpcm *lpc) +{ + const uint32_t irqmask_addr = lpc_reg_opb_base + LPC_HC_IRQMASK; + const uint32_t irqstat_addr = lpc_reg_opb_base + LPC_HC_IRQSTAT; + uint32_t irqmask, irqstat; + int64_t idx; + int rc; + + rc = opb_read(lpc, irqstat_addr, &irqstat, 4); + if (rc) + return rc; + + rc = opb_write(lpc, irqstat_addr, LPC_HC_IRQ_SYNC_NORESP_ERR, 4); + if (rc) + return rc; + + rc = opb_read(lpc, irqmask_addr, &irqmask, 4); + if (rc) + return rc; + + irqmask |= LPC_HC_IRQ_SYNC_NORESP_ERR; + rc = opb_write(lpc, irqmask_addr, irqmask, 4); + if (rc) + return rc; + + if (!(irqstat & LPC_HC_IRQ_BASE_IRQS)) + return OPAL_SUCCESS; + + /* Ensure we can perform a valid lookup in the error table */ + idx = LPC_ERROR_IDX(irqstat); + if (idx < 0 || idx >= ARRAY_SIZE(lpc_error_table)) { + prerror("LPC bus error translation failed with status 0x%x\n", + irqstat); + return OPAL_PARAMETER; + } + + rc = lpc_error_table[idx].rc; + return rc; +} + +static int64_t __lpc_write(struct lpcm *lpc, enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t data, uint32_t sz, + bool probe) +{ + uint32_t opb_base; + int64_t rc; + + lock(&lpc->lock); + if (probe) { + rc = lpc_probe_prepare(lpc); + if (rc) + goto bail; + } + + /* + * Convert to an OPB access and handle LPC HC configuration + * for FW accesses (IDSEL) + */ + rc = lpc_opb_prepare(lpc, addr_type, addr, sz, &opb_base, true); + if (rc) + goto bail; + + /* Perform OPB access */ + rc = opb_write(lpc, opb_base + addr, data, sz); + if (rc) + goto bail; + + if (probe) + rc = lpc_probe_test(lpc); + bail: + unlock(&lpc->lock); + return rc; +} + +static int64_t __lpc_write_sanity(enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t data, uint32_t sz, + bool probe) +{ + struct proc_chip *chip; + + if (lpc_default_chip_id < 0) + return OPAL_PARAMETER; + chip = get_chip(lpc_default_chip_id); + if (!chip || !chip->lpc) + return OPAL_PARAMETER; + return __lpc_write(chip->lpc, addr_type, addr, data, sz, probe); +} + +int64_t lpc_write(enum OpalLPCAddressType addr_type, uint32_t addr, + uint32_t data, uint32_t sz) +{ + return __lpc_write_sanity(addr_type, addr, data, sz, false); +} + +int64_t lpc_probe_write(enum OpalLPCAddressType addr_type, uint32_t addr, + uint32_t data, uint32_t sz) +{ + return __lpc_write_sanity(addr_type, addr, data, sz, true); +} + +/* + * 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) +{ + struct proc_chip *chip; + int64_t rc; + + chip = get_chip(chip_id); + if (!chip || !chip->lpc) + return OPAL_PARAMETER; + + if (addr_type == OPAL_LPC_FW || sz == 1) + return __lpc_write(chip->lpc, addr_type, addr, data, sz, false); + while(sz--) { + rc = __lpc_write(chip->lpc, addr_type, addr, data & 0xff, 1, false); + if (rc) + return rc; + addr++; + data >>= 8; + } + return OPAL_SUCCESS; +} + +static int64_t __lpc_read(struct lpcm *lpc, enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t *data, uint32_t sz, + bool probe) +{ + uint32_t opb_base; + int64_t rc; + + lock(&lpc->lock); + if (probe) { + rc = lpc_probe_prepare(lpc); + if (rc) + goto bail; + } + + /* + * Convert to an OPB access and handle LPC HC configuration + * for FW accesses (IDSEL and read size) + */ + rc = lpc_opb_prepare(lpc, addr_type, addr, sz, &opb_base, false); + if (rc) + goto bail; + + /* Perform OPB access */ + rc = opb_read(lpc, opb_base + addr, data, sz); + if (rc) + goto bail; + + if (probe) + rc = lpc_probe_test(lpc); + bail: + unlock(&lpc->lock); + return rc; +} + +static int64_t __lpc_read_sanity(enum OpalLPCAddressType addr_type, + uint32_t addr, uint32_t *data, uint32_t sz, + bool probe) +{ + struct proc_chip *chip; + + if (lpc_default_chip_id < 0) + return OPAL_PARAMETER; + chip = get_chip(lpc_default_chip_id); + if (!chip || !chip->lpc) + return OPAL_PARAMETER; + return __lpc_read(chip->lpc, addr_type, addr, data, sz, probe); +} + +int64_t lpc_read(enum OpalLPCAddressType addr_type, uint32_t addr, + uint32_t *data, uint32_t sz) +{ + return __lpc_read_sanity(addr_type, addr, data, sz, false); +} + +int64_t lpc_probe_read(enum OpalLPCAddressType addr_type, uint32_t addr, + uint32_t *data, uint32_t sz) +{ + return __lpc_read_sanity(addr_type, addr, data, sz, true); +} + +/* + * 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, __be32 *data, uint32_t sz) +{ + struct proc_chip *chip; + int64_t rc; + uint32_t tmp; + + chip = get_chip(chip_id); + if (!chip || !chip->lpc) + return OPAL_PARAMETER; + + if (addr_type == OPAL_LPC_FW) { + rc = __lpc_read(chip->lpc, addr_type, addr, &tmp, sz, false); + if (rc) + return rc; + + } else { + tmp = 0; + while (sz--) { + uint32_t byte; + + rc = __lpc_read(chip->lpc, addr_type, addr, &byte, 1, false); + if (rc) + return rc; + tmp = tmp | (byte << (8 * sz)); + addr++; + } + } + + *data = cpu_to_be32(tmp); + + return OPAL_SUCCESS; +} + +bool lpc_present(void) +{ + return lpc_default_chip_id >= 0; +} + +/* Called with LPC lock held */ +static void lpc_setup_serirq(struct lpcm *lpc) +{ + struct lpc_client_entry *ent; + uint32_t mask = LPC_HC_IRQ_BASE_IRQS; + int rc; + + if (!lpc_irqs_ready) + return; + + /* Collect serirq enable bits */ + list_for_each(&lpc->clients, ent, node) + mask |= ent->clt->interrupts & LPC_HC_IRQ_SERIRQ_ALL; + + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQMASK, mask, 4); + if (rc) { + prerror("Failed to update irq mask\n"); + return; + } + DBG_IRQ("IRQ mask set to 0x%08x\n", mask); + + /* Enable the LPC interrupt in the OPB Master */ + opb_write(lpc, opb_master_reg_base + OPB_MASTER_LS_IRQ_POL, 0, 4); + rc = opb_write(lpc, opb_master_reg_base + OPB_MASTER_LS_IRQ_MASK, + OPB_MASTER_IRQ_LPC, 4); + if (rc) + prerror("Failed to enable IRQs in OPB\n"); + + /* Check whether we should enable serirq */ + if (mask & LPC_HC_IRQ_SERIRQ_ALL) { + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQSER_CTRL, + LPC_HC_IRQSER_EN | + LPC_HC_IRQSER_START_4CLK | + /* + * New mode bit for P9N DD2.0 (ignored otherwise) + * when set we no longer have to manually clear + * the SerIRQs on EOI. + */ + LPC_HC_IRQSER_AUTO_CLEAR, 4); + DBG_IRQ("SerIRQ enabled\n"); + } else { + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQSER_CTRL, + 0, 4); + DBG_IRQ("SerIRQ disabled\n"); + } + if (rc) + prerror("Failed to configure SerIRQ\n"); + { + u32 val; + rc = opb_read(lpc, lpc_reg_opb_base + LPC_HC_IRQMASK, &val, 4); + if (rc) + prerror("Failed to readback mask"); + else + DBG_IRQ("MASK READBACK=%x\n", val); + + rc = opb_read(lpc, lpc_reg_opb_base + LPC_HC_IRQSER_CTRL, + &val, 4); + if (rc) + prerror("Failed to readback ctrl"); + else + DBG_IRQ("CTRL READBACK=%x\n", val); + } +} + +static void lpc_route_serirq(struct lpcm *lpc, uint32_t sirq, + uint32_t psi_idx) +{ + uint32_t reg, shift, val, psi_old; + int64_t rc; + + psi_old = lpc->sirq_routes[sirq]; + lpc->sirq_rmasks[psi_old] &= ~(LPC_HC_IRQ_SERIRQ0 >> sirq); + lpc->sirq_rmasks[psi_idx] |= (LPC_HC_IRQ_SERIRQ0 >> sirq); + lpc->sirq_routes[sirq] = psi_idx; + lpc->sirq_routed[sirq] = true; + + /* We may not be ready yet ... */ + if (!lpc->has_serirq) + return; + + if (sirq < 14) { + reg = 0xc; + shift = 4 + (sirq << 1); + } else { + reg = 0x8; + shift = 8 + ((sirq - 14) << 1); + } + shift = 30-shift; + rc = opb_read(lpc, opb_master_reg_base + reg, &val, 4); + if (rc) + return; + val = val & ~(3 << shift); + val |= (psi_idx & 3) << shift; + opb_write(lpc, opb_master_reg_base + reg, val, 4); +} + +static void lpc_alloc_route(struct lpcm *lpc, unsigned int irq, + unsigned int policy) +{ + unsigned int i, r, c; + int route = -1; + + if (policy == IRQ_ATTR_TARGET_OPAL) + r = LPC_ROUTE_OPAL; + else + r = LPC_ROUTE_LINUX; + + prlog(PR_DEBUG, "Routing irq %d, policy: %d (r=%d)\n", + irq, policy, r); + + /* Are we already routed ? */ + if (lpc->sirq_routed[irq] && + r != lpc->sirq_ralloc[lpc->sirq_routes[irq]]) { + prerror("irq %d has conflicting policies\n", irq); + return; + } + + /* First try to find a free route. Leave one for another + * policy though + */ + for (i = 0, c = 0; i < 4; i++) { + /* Count routes with identical policy */ + if (lpc->sirq_ralloc[i] == r) + c++; + + /* Use the route if it's free and there is no more + * than 3 existing routes with that policy + */ + if (lpc->sirq_ralloc[i] == LPC_ROUTE_FREE && c < 4) { + lpc->sirq_ralloc[i] = r; + route = i; + break; + } + } + + /* If we couldn't get a free one, try to find an existing one + * with a matching policy + */ + for (i = 0; route < 0 && i < 4; i++) { + if (lpc->sirq_ralloc[i] == r) + route = i; + } + + /* Still no route ? bail. That should never happen */ + if (route < 0) { + prerror("Can't find a route for irq %d\n", irq); + return; + } + + /* Program route */ + lpc_route_serirq(lpc, irq, route); + + prlog(PR_DEBUG, "SerIRQ %d using route %d targetted at %s\n", + irq, route, r == LPC_ROUTE_LINUX ? "OS" : "OPAL"); +} + +unsigned int lpc_get_irq_policy(uint32_t chip_id, uint32_t psi_idx) +{ + struct proc_chip *c = get_chip(chip_id); + + if (!c || !c->lpc) + return IRQ_ATTR_TARGET_LINUX; + + if (c->lpc->sirq_ralloc[psi_idx] == LPC_ROUTE_LINUX) + return IRQ_ATTR_TARGET_LINUX; + else + return IRQ_ATTR_TARGET_OPAL | IRQ_ATTR_TYPE_LSI; +} + +static void lpc_create_int_map(struct lpcm *lpc, struct dt_node *psi_node) +{ + __be32 map[LPC_NUM_SERIRQ * 5], *pmap; + uint32_t i; + + if (!psi_node) + return; + pmap = map; + for (i = 0; i < LPC_NUM_SERIRQ; i++) { + if (!lpc->sirq_routed[i]) + continue; + *(pmap++) = 0; + *(pmap++) = 0; + *(pmap++) = cpu_to_be32(i); + *(pmap++) = cpu_to_be32(psi_node->phandle); + *(pmap++) = cpu_to_be32(lpc->sirq_routes[i] + P9_PSI_IRQ_LPC_SIRQ0); + } + if (pmap == map) + return; + dt_add_property(lpc->node, "interrupt-map", map, + (pmap - map) * sizeof(uint32_t)); + dt_add_property_cells(lpc->node, "interrupt-map-mask", 0, 0, 0xff); + dt_add_property_cells(lpc->node, "#interrupt-cells", 1); +} + +void lpc_finalize_interrupts(void) +{ + struct proc_chip *chip; + + lpc_irqs_ready = true; + + for_each_chip(chip) { + if (chip->lpc && chip->psi && + (chip->type == PROC_CHIP_P9_NIMBUS || + chip->type == PROC_CHIP_P9_CUMULUS || + chip->type == PROC_CHIP_P9P || + chip->type == PROC_CHIP_P10)) + lpc_create_int_map(chip->lpc, chip->psi->node); + } +} + +static void lpc_init_interrupts_one(struct proc_chip *chip) +{ + struct lpcm *lpc = chip->lpc; + int i, rc; + + lock(&lpc->lock); + + /* First mask them all */ + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQMASK, 0, 4); + if (rc) { + prerror("Failed to init interrutps\n"); + goto bail; + } + + switch(chip->type) { + case PROC_CHIP_P8_MURANO: + case PROC_CHIP_P8_VENICE: + /* On Murano/Venice, there is no SerIRQ, only enable error + * interrupts + */ + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQMASK, + LPC_HC_IRQ_BASE_IRQS, 4); + if (rc) { + prerror("Failed to set interrupt mask\n"); + goto bail; + } + opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQSER_CTRL, 0, 4); + break; + case PROC_CHIP_P8_NAPLES: + /* On Naples, we support LPC interrupts, enable them based + * on what clients requests. This will setup the mask and + * enable processing + */ + lpc->has_serirq = true; + lpc_setup_serirq(lpc); + break; + case PROC_CHIP_P9_NIMBUS: + case PROC_CHIP_P9_CUMULUS: + case PROC_CHIP_P9P: + case PROC_CHIP_P10: + /* On P9, we additionally setup the routing. */ + lpc->has_serirq = true; + for (i = 0; i < LPC_NUM_SERIRQ; i++) { + if (lpc->sirq_routed[i]) + lpc_route_serirq(lpc, i, lpc->sirq_routes[i]); + } + lpc_setup_serirq(lpc); + break; + default: + ; + } + bail: + unlock(&lpc->lock); +} + +void lpc_init_interrupts(void) +{ + struct proc_chip *chip; + + lpc_irqs_ready = true; + + for_each_chip(chip) { + if (chip->lpc) + lpc_init_interrupts_one(chip); + } +} + +static void lpc_dispatch_reset(struct lpcm *lpc) +{ + struct lpc_client_entry *ent; + + /* XXX We are going to hit this repeatedly while reset is + * asserted which might be sub-optimal. We should instead + * detect assertion and start a poller that will wait for + * de-assertion. We could notify clients of LPC being + * on/off rather than just reset + */ + + prerror("Got LPC reset on chip 0x%x !\n", lpc->chip_id); + + /* Collect serirq enable bits */ + list_for_each(&lpc->clients, ent, node) { + if (!ent->clt->reset) + continue; + unlock(&lpc->lock); + ent->clt->reset(lpc->chip_id); + lock(&lpc->lock); + } + + /* Reconfigure serial interrupts */ + if (lpc->has_serirq) + lpc_setup_serirq(lpc); +} + +static void lpc_dispatch_err_irqs(struct lpcm *lpc, uint32_t irqs) +{ + const struct lpc_error_entry *err; + static int lpc_bus_err_count; + struct opal_err_info *info; + uint32_t addr; + int64_t idx; + int rc; + + /* Write back to clear error interrupts, we clear SerIRQ later + * as they are handled as level interrupts + */ + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQSTAT, + LPC_HC_IRQ_BASE_IRQS, 4); + if (rc) + prerror("Failed to clear IRQ error latches !\n"); + + if (irqs & LPC_HC_IRQ_LRESET) { + lpc_dispatch_reset(lpc); + return; + } + + /* Ensure we can perform a valid lookup in the error table */ + idx = LPC_ERROR_IDX(irqs); + if (idx < 0 || idx >= ARRAY_SIZE(lpc_error_table)) { + prerror("LPC bus error translation failed with status 0x%x\n", + irqs); + return; + } + + /* Find and report the error */ + err = &lpc_error_table[idx]; + lpc_bus_err_count++; + if (manufacturing_mode && (lpc_bus_err_count > LPC_BUS_DEGRADED_PERF_THRESHOLD)) + info = &e_info(OPAL_RC_LPC_SYNC_PERF); + else + info = &e_info(OPAL_RC_LPC_SYNC); + + rc = opb_read(lpc, lpc_reg_opb_base + LPC_HC_ERROR_ADDRESS, &addr, 4); + if (rc) + log_simple_error(info, "LPC[%03x]: %s " + "Error reading error address register\n", + lpc->chip_id, err->description); + else + log_simple_error(info, "LPC[%03x]: %s Error address reg: " + "0x%08x\n", + lpc->chip_id, err->description, addr); +} + +static void lpc_dispatch_ser_irqs(struct lpcm *lpc, uint32_t irqs, + bool clear_latch) +{ + struct lpc_client_entry *ent; + uint32_t cirqs; + int rc; + + irqs &= LPC_HC_IRQ_SERIRQ_ALL; + + /* Collect serirq enable bits */ + list_for_each(&lpc->clients, ent, node) { + if (!ent->clt->interrupt) + continue; + cirqs = ent->clt->interrupts & irqs; + if (cirqs) { + unlock(&lpc->lock); + ent->clt->interrupt(lpc->chip_id, cirqs); + lock(&lpc->lock); + } + } + + /* Our SerIRQ are level sensitive, we clear the latch after + * we call the handler. + */ + if (!clear_latch) + return; + + rc = opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQSTAT, irqs, 4); + if (rc) + prerror("Failed to clear SerIRQ latches !\n"); +} + +void lpc_interrupt(uint32_t chip_id) +{ + struct proc_chip *chip = get_chip(chip_id); + struct lpcm *lpc; + uint32_t irqs, opb_irqs; + int rc; + + /* No initialized LPC controller on that chip */ + if (!chip || !chip->lpc) + return; + lpc = chip->lpc; + + lock(&lpc->lock); + + /* Grab OPB Master LS interrupt status */ + rc = opb_read(lpc, opb_master_reg_base + OPB_MASTER_LS_IRQ_STAT, + &opb_irqs, 4); + if (rc) { + prerror("Failed to read OPB IRQ state\n"); + unlock(&lpc->lock); + return; + } + + DBG_IRQ("OPB IRQ on chip 0x%x, oirqs=0x%08x\n", chip_id, opb_irqs); + + /* Check if it's an LPC interrupt */ + if (!(opb_irqs & OPB_MASTER_IRQ_LPC)) { + /* Something we don't support ? Ack it anyway... */ + goto bail; + } + + /* Handle the lpc interrupt source (errors etc...) */ + rc = opb_read(lpc, lpc_reg_opb_base + LPC_HC_IRQSTAT, &irqs, 4); + if (rc) { + prerror("Failed to read LPC IRQ state\n"); + goto bail; + } + + DBG_IRQ("LPC IRQ on chip 0x%x, irqs=0x%08x\n", chip_id, irqs); + + /* Handle error interrupts */ + if (irqs & LPC_HC_IRQ_BASE_IRQS) + lpc_dispatch_err_irqs(lpc, irqs); + + /* Handle SerIRQ interrupts */ + if (irqs & LPC_HC_IRQ_SERIRQ_ALL) + lpc_dispatch_ser_irqs(lpc, irqs, true); + bail: + /* Ack it at the OPB level */ + opb_write(lpc, opb_master_reg_base + OPB_MASTER_LS_IRQ_STAT, + opb_irqs, 4); + unlock(&lpc->lock); +} + +void lpc_serirq(uint32_t chip_id, uint32_t index) +{ + struct proc_chip *chip = get_chip(chip_id); + struct lpcm *lpc; + uint32_t irqs, rmask; + int rc; + + /* No initialized LPC controller on that chip */ + if (!chip || !chip->lpc) + return; + lpc = chip->lpc; + + lock(&lpc->lock); + + /* Handle the lpc interrupt source (errors etc...) */ + rc = opb_read(lpc, lpc_reg_opb_base + LPC_HC_IRQSTAT, &irqs, 4); + if (rc) { + prerror("Failed to read LPC IRQ state\n"); + goto bail; + } + rmask = lpc->sirq_rmasks[index]; + + DBG_IRQ("IRQ on chip 0x%x, irqs=0x%08x rmask=0x%08x\n", + chip_id, irqs, rmask); + irqs &= rmask; + + /* + * Handle SerIRQ interrupts. Don't clear the latch, + * it will be done in our special EOI callback if + * necessary on DD1 + */ + if (irqs) + lpc_dispatch_ser_irqs(lpc, irqs, false); + + bail: + unlock(&lpc->lock); +} + +void lpc_all_interrupts(uint32_t chip_id) +{ + struct proc_chip *chip = get_chip(chip_id); + struct lpcm *lpc; + + /* No initialized LPC controller on that chip */ + if (!chip || !chip->lpc) + return; + lpc = chip->lpc; + + /* Dispatch all */ + lock(&lpc->lock); + lpc_dispatch_ser_irqs(lpc, LPC_HC_IRQ_SERIRQ_ALL, false); + unlock(&lpc->lock); +} + +static void lpc_init_chip_p8(struct dt_node *xn) + { + uint32_t gcid = dt_get_chip_id(xn); + struct proc_chip *chip; + struct lpcm *lpc; + + chip = get_chip(gcid); + assert(chip); + + lpc = zalloc(sizeof(struct lpcm)); + assert(lpc); + lpc->chip_id = gcid; + lpc->xbase = dt_get_address(xn, 0, NULL); + lpc->fw_idsel = 0xff; + lpc->fw_rdsz = 0xff; + lpc->node = xn; + list_head_init(&lpc->clients); + init_lock(&lpc->lock); + + if (lpc_default_chip_id < 0 || + dt_has_node_property(xn, "primary", NULL)) { + lpc_default_chip_id = gcid; + } + + /* Mask all interrupts for now */ + opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQMASK, 0, 4); + + printf("LPC[%03x]: Initialized, access via XSCOM @0x%x\n", + gcid, lpc->xbase); + + dt_add_property(xn, "interrupt-controller", NULL, 0); + dt_add_property_cells(xn, "#interrupt-cells", 1); + assert(dt_prop_get_u32(xn, "#address-cells") == 2); + + chip->lpc = lpc; +} + +static void lpc_init_chip_p9(struct dt_node *opb_node) +{ + uint32_t gcid = dt_get_chip_id(opb_node); + struct dt_node *lpc_node; + struct proc_chip *chip; + struct lpcm *lpc; + u64 addr; + u32 val; + + chip = get_chip(gcid); + assert(chip); + + /* Grab OPB base address */ + addr = dt_prop_get_cell(opb_node, "ranges", 1); + addr <<= 32; + addr |= dt_prop_get_cell(opb_node, "ranges", 2); + + /* Find the "lpc" child node */ + lpc_node = dt_find_compatible_node(opb_node, NULL, "ibm,power9-lpc"); + if (!lpc_node) + return; + + lpc = zalloc(sizeof(struct lpcm)); + assert(lpc); + lpc->chip_id = gcid; + lpc->mbase = (void *)addr; + lpc->fw_idsel = 0xff; + lpc->fw_rdsz = 0xff; + lpc->node = lpc_node; + list_head_init(&lpc->clients); + init_lock(&lpc->lock); + + if (lpc_default_chip_id < 0 || + dt_has_node_property(opb_node, "primary", NULL)) { + lpc_default_chip_id = gcid; + } + + /* Mask all interrupts for now */ + opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQMASK, 0, 4); + + /* Clear any stale LPC bus errors */ + opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQSTAT, + LPC_HC_IRQ_BASE_IRQS, 4); + + /* Default with routing to PSI SerIRQ 0, this will be updated + * later when interrupts are initialized. + */ + opb_read(lpc, opb_master_reg_base + 8, &val, 4); + val &= 0xff03ffff; + opb_write(lpc, opb_master_reg_base + 8, val, 4); + opb_read(lpc, opb_master_reg_base + 0xc, &val, 4); + val &= 0xf0000000; + opb_write(lpc, opb_master_reg_base + 0xc, val, 4); + + prlog(PR_INFO, "LPC[%03x]: Initialized\n", gcid); + prlog(PR_DEBUG,"access via MMIO @%p\n", lpc->mbase); + + chip->lpc = lpc; +} + +void lpc_init(void) +{ + struct dt_node *xn; + bool has_lpc = false; + + /* Look for P9 first as the DT is compatile for both 8 and 9 */ + dt_for_each_compatible(dt_root, xn, "ibm,power9-lpcm-opb") { + lpc_init_chip_p9(xn); + has_lpc = true; + } + + if (!has_lpc) { + dt_for_each_compatible(dt_root, xn, "ibm,power8-lpc") { + lpc_init_chip_p8(xn); + has_lpc = true; + } + } + if (lpc_default_chip_id >= 0) + prlog(PR_DEBUG, "Default bus on chip 0x%x\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) { + struct lpcm *lpc = chip->lpc; + if (lpc) { + lpc->lock.in_con_path = true; + lock(&lpc->lock); + unlock(&lpc->lock); + } + } +} + +bool lpc_ok(void) +{ + struct proc_chip *chip; + + if (lpc_default_chip_id < 0) + return false; + if (!xscom_ok()) + return false; + chip = get_chip(lpc_default_chip_id); + if (!chip->lpc) + return false; + return !lock_held_by_me(&chip->lpc->lock); +} + +void lpc_register_client(uint32_t chip_id, + const struct lpc_client *clt, + uint32_t policy) +{ + struct lpc_client_entry *ent; + struct proc_chip *chip; + struct lpcm *lpc; + bool has_routes; + + chip = get_chip(chip_id); + assert(chip); + lpc = chip->lpc; + if (!lpc) { + prerror("Attempt to register client on bad chip 0x%x\n", + chip_id); + return; + } + + has_routes = + chip->type == PROC_CHIP_P9_NIMBUS || + chip->type == PROC_CHIP_P9_CUMULUS || + chip->type == PROC_CHIP_P9P || + chip->type == PROC_CHIP_P10; + + if (policy != IRQ_ATTR_TARGET_OPAL && !has_routes) { + prerror("Chip doesn't support OS interrupt policy\n"); + return; + } + + ent = malloc(sizeof(*ent)); + assert(ent); + ent->clt = clt; + ent->policy = policy; + lock(&lpc->lock); + list_add(&lpc->clients, &ent->node); + + if (has_routes) { + unsigned int i; + for (i = 0; i < LPC_NUM_SERIRQ; i++) + if (clt->interrupts & LPC_IRQ(i)) + lpc_alloc_route(lpc, i, policy); + } + + if (lpc->has_serirq) + lpc_setup_serirq(lpc); + unlock(&lpc->lock); +} |