935 lines
23 KiB
C
935 lines
23 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.
|
|
*/
|
|
|
|
/*
|
|
* Service Processor serial console handling code
|
|
*/
|
|
#include <io.h>
|
|
#include <psi.h>
|
|
#include <fsp.h>
|
|
#include <opal.h>
|
|
#include <interrupts.h>
|
|
#include <cpu.h>
|
|
#include <dio-p9.h>
|
|
#include <trace.h>
|
|
#include <xscom.h>
|
|
#include <chip.h>
|
|
#include <lpc.h>
|
|
#include <i2c.h>
|
|
#include <timebase.h>
|
|
#include <platform.h>
|
|
#include <errorlog.h>
|
|
#include <xive.h>
|
|
#include <sbe-p9.h>
|
|
#include <phys-map.h>
|
|
#include <occ.h>
|
|
|
|
static LIST_HEAD(psis);
|
|
static u64 psi_link_timer;
|
|
static u64 psi_link_timeout;
|
|
static bool psi_link_poll_active;
|
|
static bool psi_ext_irq_policy = EXTERNAL_IRQ_POLICY_LINUX;
|
|
|
|
static void psi_activate_phb(struct psi *psi);
|
|
|
|
struct lock psi_lock = LOCK_UNLOCKED;
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_PSI_TIMEOUT, OPAL_PLATFORM_ERR_EVT, OPAL_PSI,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_UNRECOVERABLE_ERR_LOSS_OF_FUNCTION, OPAL_NA);
|
|
|
|
void psi_set_link_polling(bool active)
|
|
{
|
|
printf("PSI: %sing link polling\n",
|
|
active ? "start" : "stopp");
|
|
psi_link_poll_active = active;
|
|
}
|
|
|
|
void psi_disable_link(struct psi *psi)
|
|
{
|
|
lock(&psi_lock);
|
|
|
|
/*
|
|
* Note: This can be called with the link already down but
|
|
* not detected as such yet by this layer since psi_check_link_active()
|
|
* operates locklessly and thus won't update the PSI structure. This
|
|
* is a non-issue, the only consequence is the messages in the log
|
|
* mentioning first the link having gone down then being disabled.
|
|
*/
|
|
if (psi->active) {
|
|
u64 reg;
|
|
psi->active = false;
|
|
|
|
/* Mask errors in SEMR */
|
|
reg = in_be64(psi->regs + PSIHB_SEMR);
|
|
reg &= ((0xfffull << 36) | (0xfffull << 20));
|
|
out_be64(psi->regs + PSIHB_SEMR, reg);
|
|
printf("PSI: SEMR set to %llx\n", reg);
|
|
|
|
/* Reset all the error bits in PSIHB_CR and
|
|
* disable FSP interrupts
|
|
*/
|
|
reg = in_be64(psi->regs + PSIHB_CR);
|
|
reg &= ~(0x7ffull << 20);
|
|
reg &= ~PSIHB_CR_PSI_LINK_ENABLE; /* flip link enable */
|
|
/*
|
|
* Ensure no commands/spurious interrupts reach
|
|
* the processor, by flipping the command enable.
|
|
*/
|
|
reg &= ~PSIHB_CR_FSP_CMD_ENABLE;
|
|
reg &= ~PSIHB_CR_FSP_IRQ_ENABLE;
|
|
reg &= ~PSIHB_CR_FSP_IRQ; /* Clear interrupt state too */
|
|
printf("PSI[0x%03x]: Disabling link!\n", psi->chip_id);
|
|
out_be64(psi->regs + PSIHB_CR, reg);
|
|
printf("PSI: PSIHB_CR (error bits) set to %llx\n",
|
|
in_be64(psi->regs + PSIHB_CR));
|
|
psi_set_link_polling(true);
|
|
}
|
|
|
|
unlock(&psi_lock);
|
|
}
|
|
|
|
/*
|
|
* Resetting the FSP is a multi step sequence:
|
|
* 1. Read the PSIHBCR
|
|
* 2. Set the PSIHBCR[6] -- write register back.
|
|
* 3. Read PSIHBCR again
|
|
* 4. Reset PSIHBCR[6] -- write register back.
|
|
*/
|
|
void psi_reset_fsp(struct psi *psi)
|
|
{
|
|
lock(&psi_lock);
|
|
|
|
if (psi->active) {
|
|
u64 reg;
|
|
|
|
printf("PSI: Driving FSP reset via PSI\n");
|
|
reg = in_be64(psi->regs + PSIHB_CR);
|
|
reg &= ~(0xfffull << 20); /* Reset error bits */
|
|
reg |= PSIHB_CR_FSP_RESET; /* FSP reset trigger start */
|
|
out_be64(psi->regs + PSIHB_CR, reg);
|
|
printf("PSI[0x%03x]: FSP reset start PSIHBCR set to %llx\n",
|
|
psi->chip_id, in_be64(psi->regs + PSIHB_CR));
|
|
|
|
reg = in_be64(psi->regs + PSIHB_CR);
|
|
reg &= ~PSIHB_CR_FSP_RESET; /* Clear FSP reset bit */
|
|
out_be64(psi->regs + PSIHB_CR, reg); /* Complete reset */
|
|
printf("PSI[0x%03x]: FSP reset complete. PSIHBCR set to %llx\n",
|
|
psi->chip_id, in_be64(psi->regs + PSIHB_CR));
|
|
}
|
|
unlock(&psi_lock);
|
|
|
|
/* Now bring down the PSI link too... */
|
|
psi_disable_link(psi);
|
|
}
|
|
|
|
bool psi_check_link_active(struct psi *psi)
|
|
{
|
|
u64 val = in_be64(psi->regs + PSIHB_CR);
|
|
|
|
/*
|
|
* Unlocked, used during fsp_poke_msg so we really want
|
|
* to avoid fancy link re-entrancy and deadlocks here
|
|
*/
|
|
if (!psi->active)
|
|
return false;
|
|
return (val & PSIHB_CR_PSI_LINK_ENABLE) &&
|
|
(val & PSIHB_CR_FSP_LINK_ACTIVE);
|
|
}
|
|
|
|
struct psi *psi_find_link(uint32_t chip_id)
|
|
{
|
|
struct psi *psi;
|
|
|
|
list_for_each(&psis, psi, list) {
|
|
if (psi->chip_id == chip_id)
|
|
return psi;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#define PSI_LINK_CHECK_INTERVAL 10 /* Interval in secs */
|
|
#define PSI_LINK_RECOVERY_TIMEOUT 1800 /* 30 minutes */
|
|
|
|
static void psi_link_poll(void *data __unused)
|
|
{
|
|
struct psi *psi;
|
|
u64 now;
|
|
|
|
if (!psi_link_poll_active)
|
|
return;
|
|
|
|
now = mftb();
|
|
if (psi_link_timer == 0 ||
|
|
(tb_compare(now, psi_link_timer) == TB_AAFTERB) ||
|
|
(tb_compare(now, psi_link_timer) == TB_AEQUALB)) {
|
|
|
|
lock(&psi_lock);
|
|
|
|
list_for_each(&psis, psi, list) {
|
|
u64 val;
|
|
|
|
if (psi->active)
|
|
continue;
|
|
|
|
val = in_be64(psi->regs + PSIHB_CR);
|
|
|
|
printf("PSI[0x%03x]: Poll CR=0x%016llx\n",
|
|
psi->chip_id, val);
|
|
|
|
if ((val & PSIHB_CR_PSI_LINK_ENABLE) &&
|
|
(val & PSIHB_CR_FSP_LINK_ACTIVE)) {
|
|
printf("PSI[0x%03x]: Found active link!\n",
|
|
psi->chip_id);
|
|
psi_link_timeout = 0;
|
|
psi->active = true;
|
|
psi_activate_phb(psi);
|
|
psi_set_link_polling(false);
|
|
unlock(&psi_lock);
|
|
if (platform.psi && platform.psi->link_established)
|
|
platform.psi->link_established();
|
|
return;
|
|
}
|
|
}
|
|
if (!psi_link_timeout)
|
|
psi_link_timeout =
|
|
now + secs_to_tb(PSI_LINK_RECOVERY_TIMEOUT);
|
|
|
|
if (tb_compare(now, psi_link_timeout) == TB_AAFTERB) {
|
|
log_simple_error(&e_info(OPAL_RC_PSI_TIMEOUT),
|
|
"PSI: Link timeout -- loss of FSP\n");
|
|
/* Reset the link timeout and continue looking */
|
|
psi_link_timeout = 0;
|
|
}
|
|
|
|
/* Poll every 10 seconds */
|
|
psi_link_timer = now + secs_to_tb(PSI_LINK_CHECK_INTERVAL);
|
|
|
|
unlock(&psi_lock);
|
|
}
|
|
}
|
|
|
|
void psi_enable_fsp_interrupt(struct psi *psi)
|
|
{
|
|
/* Enable FSP interrupts in the GXHB */
|
|
lock(&psi_lock);
|
|
out_be64(psi->regs + PSIHB_CR,
|
|
in_be64(psi->regs + PSIHB_CR) | PSIHB_CR_FSP_IRQ_ENABLE);
|
|
unlock(&psi_lock);
|
|
}
|
|
|
|
/* Multiple bits can be set on errors */
|
|
static void decode_psihb_error(u64 val)
|
|
{
|
|
if (val & PSIHB_CR_PSI_ERROR)
|
|
printf("PSI: PSI Reported Error\n");
|
|
if (val & PSIHB_CR_PSI_LINK_INACTIVE)
|
|
printf("PSI: PSI Link Inactive Transition\n");
|
|
if (val & PSIHB_CR_FSP_ACK_TIMEOUT)
|
|
printf("PSI: FSP Ack Timeout\n");
|
|
if (val & PSIHB_CR_MMIO_LOAD_TIMEOUT)
|
|
printf("PSI: MMIO Load Timeout\n");
|
|
if (val & PSIHB_CR_MMIO_LENGTH_ERROR)
|
|
printf("PSI: MMIO Length Error\n");
|
|
if (val & PSIHB_CR_MMIO_ADDRESS_ERROR)
|
|
printf("PSI: MMIO Address Error\n");
|
|
if (val & PSIHB_CR_MMIO_TYPE_ERROR)
|
|
printf("PSI: MMIO Type Error\n");
|
|
if (val & PSIHB_CR_UE)
|
|
printf("PSI: UE Detected\n");
|
|
if (val & PSIHB_CR_PARITY_ERROR)
|
|
printf("PSI: Internal Parity Error\n");
|
|
if (val & PSIHB_CR_SYNC_ERR_ALERT1)
|
|
printf("PSI: Sync Error Alert1\n");
|
|
if (val & PSIHB_CR_SYNC_ERR_ALERT2)
|
|
printf("PSI: Sync Error Alert2\n");
|
|
if (val & PSIHB_CR_FSP_COMMAND_ERROR)
|
|
printf("PSI: FSP Command Error\n");
|
|
}
|
|
|
|
|
|
static void handle_psi_interrupt(struct psi *psi, u64 val)
|
|
{
|
|
printf("PSI[0x%03x]: PSI mgmnt interrupt CR=0x%016llx\n",
|
|
psi->chip_id, val);
|
|
|
|
if (val & (0xfffull << 20)) {
|
|
decode_psihb_error(val);
|
|
psi_disable_link(psi);
|
|
} else if (val & (0x1full << 11))
|
|
printf("PSI: FSP error detected\n");
|
|
}
|
|
|
|
static void psi_spurious_fsp_irq(struct psi *psi)
|
|
{
|
|
u64 reg, bit;
|
|
|
|
prerror("PSI: Spurious interrupt, attempting clear\n");
|
|
|
|
if (proc_gen == proc_gen_p9) {
|
|
reg = PSIHB_XSCOM_P9_HBCSR_CLR;
|
|
bit = PSIHB_XSCOM_P9_HBSCR_FSP_IRQ;
|
|
} else if (proc_gen == proc_gen_p8) {
|
|
reg = PSIHB_XSCOM_P8_HBCSR_CLR;
|
|
bit = PSIHB_XSCOM_P8_HBSCR_FSP_IRQ;
|
|
} else {
|
|
assert(false);
|
|
}
|
|
xscom_write(psi->chip_id, psi->xscom_base + reg, bit);
|
|
}
|
|
|
|
bool psi_poll_fsp_interrupt(struct psi *psi)
|
|
{
|
|
return !!(in_be64(psi->regs + PSIHB_CR) & PSIHB_CR_FSP_IRQ);
|
|
}
|
|
|
|
static void psihb_interrupt(struct irq_source *is, uint32_t isn __unused)
|
|
{
|
|
struct psi *psi = is->data;
|
|
u64 val;
|
|
|
|
val = in_be64(psi->regs + PSIHB_CR);
|
|
|
|
if (psi_link_poll_active) {
|
|
printf("PSI[0x%03x]: PSI interrupt CR=0x%016llx (A=%d)\n",
|
|
psi->chip_id, val, psi->active);
|
|
}
|
|
|
|
/* Handle PSI interrupts first in case it's a link down */
|
|
if (val & PSIHB_CR_PSI_IRQ) {
|
|
handle_psi_interrupt(psi, val);
|
|
|
|
/*
|
|
* If the link went down, re-read PSIHB_CR as
|
|
* the FSP interrupt might have been cleared.
|
|
*/
|
|
if (!psi->active)
|
|
val = in_be64(psi->regs + PSIHB_CR);
|
|
}
|
|
|
|
|
|
/*
|
|
* We avoid forwarding FSP interrupts if the link isn't
|
|
* active. They should be masked anyway but it looks
|
|
* like the CR bit can remain set.
|
|
*/
|
|
if (val & PSIHB_CR_FSP_IRQ) {
|
|
/*
|
|
* We have a case a flood with FSP mailbox interrupts
|
|
* when the link is down, see if we manage to clear
|
|
* the condition
|
|
*/
|
|
if (!psi->active)
|
|
psi_spurious_fsp_irq(psi);
|
|
else {
|
|
if (platform.psi && platform.psi->fsp_interrupt)
|
|
platform.psi->fsp_interrupt();
|
|
}
|
|
}
|
|
|
|
if (platform.psi && platform.psi->psihb_interrupt)
|
|
platform.psi->psihb_interrupt();
|
|
}
|
|
|
|
|
|
static const uint32_t psi_p8_irq_to_xivr[P8_IRQ_PSI_IRQ_COUNT] = {
|
|
[P8_IRQ_PSI_FSP] = PSIHB_XIVR_FSP,
|
|
[P8_IRQ_PSI_OCC] = PSIHB_XIVR_OCC,
|
|
[P8_IRQ_PSI_FSI] = PSIHB_XIVR_FSI,
|
|
[P8_IRQ_PSI_LPC] = PSIHB_XIVR_LPC,
|
|
[P8_IRQ_PSI_LOCAL_ERR] = PSIHB_XIVR_LOCAL_ERR,
|
|
[P8_IRQ_PSI_EXTERNAL]= PSIHB_XIVR_HOST_ERR,
|
|
};
|
|
|
|
static void psi_cleanup_irq(struct psi *psi)
|
|
{
|
|
uint32_t irq;
|
|
uint64_t xivr, xivr_p;
|
|
|
|
for (irq = 0; irq < P8_IRQ_PSI_IRQ_COUNT; irq++) {
|
|
prlog(PR_DEBUG, "PSI[0x%03x]: Cleaning up IRQ %d\n",
|
|
psi->chip_id, irq);
|
|
|
|
xivr_p = psi_p8_irq_to_xivr[irq];
|
|
xivr = in_be64(psi->regs + xivr_p);
|
|
xivr |= (0xffull << 32);
|
|
out_be64(psi->regs + xivr_p, xivr);
|
|
time_wait_ms_nopoll(10);
|
|
xivr = in_be64(psi->regs + xivr_p);
|
|
if (xivr & PPC_BIT(39)) {
|
|
printf(" Need EOI !\n");
|
|
icp_send_eoi(psi->interrupt + irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Called on a fast reset, make sure we aren't stuck with
|
|
* an accepted and never EOId PSI interrupt
|
|
*/
|
|
void psi_irq_reset(void)
|
|
{
|
|
struct psi *psi;
|
|
|
|
printf("PSI: Hot reset!\n");
|
|
|
|
assert(proc_gen == proc_gen_p8);
|
|
|
|
list_for_each(&psis, psi, list) {
|
|
psi_cleanup_irq(psi);
|
|
}
|
|
}
|
|
|
|
static int64_t psi_p8_set_xive(struct irq_source *is, uint32_t isn,
|
|
uint16_t server, uint8_t priority)
|
|
{
|
|
struct psi *psi = is->data;
|
|
uint64_t xivr_p, xivr;
|
|
uint32_t irq_idx = isn & 7;
|
|
|
|
if (irq_idx >= P8_IRQ_PSI_IRQ_COUNT)
|
|
return OPAL_PARAMETER;
|
|
xivr_p = psi_p8_irq_to_xivr[irq_idx];
|
|
|
|
/* Populate the XIVR */
|
|
xivr = (uint64_t)server << 40;
|
|
xivr |= (uint64_t)priority << 32;
|
|
xivr |= (uint64_t)(isn & 7) << 29;
|
|
|
|
out_be64(psi->regs + xivr_p, xivr);
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static int64_t psi_p8_get_xive(struct irq_source *is, uint32_t isn __unused,
|
|
uint16_t *server, uint8_t *priority)
|
|
{
|
|
struct psi *psi = is->data;
|
|
uint64_t xivr_p, xivr;
|
|
uint32_t irq_idx = isn & 7;
|
|
|
|
if (irq_idx >= P8_IRQ_PSI_IRQ_COUNT)
|
|
return OPAL_PARAMETER;
|
|
|
|
xivr_p = psi_p8_irq_to_xivr[irq_idx];
|
|
|
|
/* Read & decode the XIVR */
|
|
xivr = in_be64(psi->regs + xivr_p);
|
|
|
|
*server = (xivr >> 40) & 0xffff;
|
|
*priority = (xivr >> 32) & 0xff;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static void psihb_p8_interrupt(struct irq_source *is, uint32_t isn)
|
|
{
|
|
struct psi *psi = is->data;
|
|
uint32_t idx = isn - psi->interrupt;
|
|
|
|
switch (idx) {
|
|
case P8_IRQ_PSI_FSP:
|
|
psihb_interrupt(is, isn);
|
|
break;
|
|
case P8_IRQ_PSI_OCC:
|
|
occ_p8_interrupt(psi->chip_id);
|
|
break;
|
|
case P8_IRQ_PSI_FSI:
|
|
printf("PSI: FSI irq received\n");
|
|
break;
|
|
case P8_IRQ_PSI_LPC:
|
|
lpc_interrupt(psi->chip_id);
|
|
|
|
/*
|
|
* i2c interrupts are ORed with the LPC ones on
|
|
* Murano DD2.1 and Venice DD2.0
|
|
*/
|
|
p8_i2c_interrupt(psi->chip_id);
|
|
break;
|
|
case P8_IRQ_PSI_LOCAL_ERR:
|
|
prd_psi_interrupt(psi->chip_id);
|
|
break;
|
|
case P8_IRQ_PSI_EXTERNAL:
|
|
if (platform.external_irq)
|
|
platform.external_irq(psi->chip_id);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* TODO: Per Vicente Chung, CRESPs don't generate interrupts,
|
|
* and are just informational. Need to define the policy
|
|
* to handle them.
|
|
*/
|
|
}
|
|
|
|
static uint64_t psi_p8_irq_attributes(struct irq_source *is, uint32_t isn)
|
|
{
|
|
struct psi *psi = is->data;
|
|
uint32_t idx = isn - psi->interrupt;
|
|
uint64_t attr;
|
|
|
|
if (psi->no_lpc_irqs && idx == P8_IRQ_PSI_LPC)
|
|
return IRQ_ATTR_TARGET_LINUX;
|
|
|
|
if (idx == P8_IRQ_PSI_EXTERNAL &&
|
|
psi_ext_irq_policy == EXTERNAL_IRQ_POLICY_LINUX)
|
|
return IRQ_ATTR_TARGET_LINUX;
|
|
|
|
attr = IRQ_ATTR_TARGET_OPAL | IRQ_ATTR_TYPE_LSI;
|
|
if (idx == P8_IRQ_PSI_EXTERNAL || idx == P8_IRQ_PSI_LPC ||
|
|
idx == P8_IRQ_PSI_FSP)
|
|
attr |= IRQ_ATTR_TARGET_FREQUENT;
|
|
return attr;
|
|
}
|
|
|
|
static char *psi_p8_irq_name(struct irq_source *is, uint32_t isn)
|
|
{
|
|
struct psi *psi = is->data;
|
|
uint32_t idx = isn - psi->interrupt;
|
|
|
|
static const char *names[P8_IRQ_PSI_IRQ_COUNT] = {
|
|
"psi:fsp",
|
|
"psi:occ",
|
|
"psi:fsi",
|
|
"psi:lpchc",
|
|
"psi:local_err",
|
|
"psi:external",
|
|
};
|
|
|
|
if (idx >= P8_IRQ_PSI_IRQ_COUNT)
|
|
return NULL;
|
|
return strdup(names[idx]);
|
|
}
|
|
|
|
static const struct irq_source_ops psi_p8_irq_ops = {
|
|
.get_xive = psi_p8_get_xive,
|
|
.set_xive = psi_p8_set_xive,
|
|
.interrupt = psihb_p8_interrupt,
|
|
.attributes = psi_p8_irq_attributes,
|
|
.name = psi_p8_irq_name,
|
|
};
|
|
|
|
static void psihb_p9_interrupt(struct irq_source *is, uint32_t isn)
|
|
{
|
|
struct psi *psi = is->data;
|
|
uint32_t idx = isn - psi->interrupt;
|
|
|
|
switch (idx) {
|
|
case P9_PSI_IRQ_PSI:
|
|
psihb_interrupt(is, isn);
|
|
break;
|
|
case P9_PSI_IRQ_OCC:
|
|
occ_p9_interrupt(psi->chip_id);
|
|
break;
|
|
case P9_PSI_IRQ_FSI:
|
|
printf("PSI: FSI irq received\n");
|
|
break;
|
|
case P9_PSI_IRQ_LPCHC:
|
|
lpc_interrupt(psi->chip_id);
|
|
break;
|
|
case P9_PSI_IRQ_LOCAL_ERR:
|
|
prd_psi_interrupt(psi->chip_id);
|
|
break;
|
|
case P9_PSI_IRQ_GLOBAL_ERR:
|
|
printf("PSI: Global error irq received\n");
|
|
break;
|
|
case P9_PSI_IRQ_EXTERNAL:
|
|
if (platform.external_irq)
|
|
platform.external_irq(psi->chip_id);
|
|
break;
|
|
case P9_PSI_IRQ_LPC_SIRQ0:
|
|
case P9_PSI_IRQ_LPC_SIRQ1:
|
|
case P9_PSI_IRQ_LPC_SIRQ2:
|
|
case P9_PSI_IRQ_LPC_SIRQ3:
|
|
lpc_serirq(psi->chip_id, idx - P9_PSI_IRQ_LPC_SIRQ0);
|
|
break;
|
|
case P9_PSI_IRQ_SBE_I2C:
|
|
p8_i2c_interrupt(psi->chip_id);
|
|
break;
|
|
case P9_PSI_IRQ_DIO:
|
|
printf("PSI: DIO irq received\n");
|
|
dio_interrupt_handler(psi->chip_id);
|
|
break;
|
|
case P9_PSI_IRQ_PSU:
|
|
p9_sbe_interrupt(psi->chip_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint64_t psi_p9_irq_attributes(struct irq_source *is __unused,
|
|
uint32_t isn)
|
|
{
|
|
struct psi *psi = is->data;
|
|
unsigned int idx = isn & 0xf;
|
|
bool is_lpc_serirq;
|
|
|
|
is_lpc_serirq =
|
|
(idx == P9_PSI_IRQ_LPC_SIRQ0 ||
|
|
idx == P9_PSI_IRQ_LPC_SIRQ1 ||
|
|
idx == P9_PSI_IRQ_LPC_SIRQ2 ||
|
|
idx == P9_PSI_IRQ_LPC_SIRQ3);
|
|
|
|
/* If LPC interrupts are disabled, route them to Linux
|
|
* (who will not request them since they aren't referenced
|
|
* in the device tree)
|
|
*/
|
|
if (is_lpc_serirq && psi->no_lpc_irqs)
|
|
return IRQ_ATTR_TARGET_LINUX;
|
|
|
|
/* For serirq, check the LPC layer for policy */
|
|
if (is_lpc_serirq)
|
|
return lpc_get_irq_policy(psi->chip_id, idx - P9_PSI_IRQ_LPC_SIRQ0);
|
|
|
|
return IRQ_ATTR_TARGET_OPAL | IRQ_ATTR_TYPE_LSI;
|
|
}
|
|
|
|
static char *psi_p9_irq_name(struct irq_source *is, uint32_t isn)
|
|
{
|
|
struct psi *psi = is->data;
|
|
uint32_t idx = isn - psi->interrupt;
|
|
|
|
static const char *names[P9_PSI_NUM_IRQS] = {
|
|
"psi:fsp",
|
|
"psi:occ",
|
|
"psi:fsi",
|
|
"psi:lpchc",
|
|
"psi:local_err",
|
|
"psi:global_err",
|
|
"psi:external",
|
|
"psi:lpc_serirq_mux0", /* Have a callback to get name ? */
|
|
"psi:lpc_serirq_mux1", /* Have a callback to get name ? */
|
|
"psi:lpc_serirq_mux2", /* Have a callback to get name ? */
|
|
"psi:lpc_serirq_mux3", /* Have a callback to get name ? */
|
|
"psi:i2c",
|
|
"psi:dio",
|
|
"psi:psu"
|
|
};
|
|
|
|
if (idx >= P9_PSI_NUM_IRQS)
|
|
return NULL;
|
|
return strdup(names[idx]);
|
|
}
|
|
|
|
static const struct irq_source_ops psi_p9_irq_ops = {
|
|
.interrupt = psihb_p9_interrupt,
|
|
.attributes = psi_p9_irq_attributes,
|
|
.name = psi_p9_irq_name,
|
|
};
|
|
|
|
void psi_set_external_irq_policy(bool policy)
|
|
{
|
|
psi_ext_irq_policy = policy;
|
|
}
|
|
|
|
static void psi_init_p8_interrupts(struct psi *psi)
|
|
{
|
|
uint32_t irq;
|
|
uint64_t xivr_p;
|
|
|
|
/* On P8 we get a block of 8, set up the base/mask
|
|
* and mask all the sources for now
|
|
*/
|
|
out_be64(psi->regs + PSIHB_IRSN,
|
|
SETFIELD(PSIHB_IRSN_COMP, 0ul, psi->interrupt) |
|
|
SETFIELD(PSIHB_IRSN_MASK, 0ul, 0x7fff8ul) |
|
|
PSIHB_IRSN_DOWNSTREAM_EN |
|
|
PSIHB_IRSN_UPSTREAM_EN);
|
|
|
|
for (irq = 0; irq < P8_IRQ_PSI_IRQ_COUNT; irq++) {
|
|
xivr_p = psi_p8_irq_to_xivr[irq];
|
|
out_be64(psi->regs + xivr_p, (0xffull << 32) | (irq << 29));
|
|
}
|
|
|
|
/*
|
|
* Register the IRQ sources FSP, OCC, FSI, LPC
|
|
* and Local Error. Host Error is actually the
|
|
* external interrupt and the policy for that comes
|
|
* from the platform
|
|
*/
|
|
register_irq_source(&psi_p8_irq_ops, psi,
|
|
psi->interrupt, P8_IRQ_PSI_IRQ_COUNT);
|
|
}
|
|
|
|
static void psi_init_p9_interrupts(struct psi *psi)
|
|
{
|
|
struct proc_chip *chip;
|
|
u64 val;
|
|
|
|
/* Grab chip */
|
|
chip = get_chip(psi->chip_id);
|
|
if (!chip)
|
|
return;
|
|
|
|
/* Configure the CI BAR */
|
|
phys_map_get(chip->id, PSIHB_ESB, 0, &val, NULL);
|
|
val |= PSIHB_ESB_CI_VALID;
|
|
out_be64(psi->regs + PSIHB_ESB_CI_BASE, val);
|
|
|
|
val = in_be64(psi->regs + PSIHB_ESB_CI_BASE);
|
|
psi->esb_mmio = (void *)(val & ~PSIHB_ESB_CI_VALID);
|
|
prlog(PR_DEBUG, "PSI[0x%03x]: ESB MMIO at @%p\n",
|
|
psi->chip_id, psi->esb_mmio);
|
|
|
|
/* Grab and configure the notification port */
|
|
val = xive_get_notify_port(psi->chip_id, XIVE_HW_SRC_PSI);
|
|
val |= PSIHB_ESB_NOTIF_VALID;
|
|
out_be64(psi->regs + PSIHB_ESB_NOTIF_ADDR, val);
|
|
|
|
/* Setup interrupt offset */
|
|
val = xive_get_notify_base(psi->interrupt);
|
|
val <<= 32;
|
|
out_be64(psi->regs + PSIHB_IVT_OFFSET, val);
|
|
|
|
/* Register sources */
|
|
prlog(PR_DEBUG,
|
|
"PSI[0x%03x]: Interrupts sources registered for P9 DD2.x\n",
|
|
psi->chip_id);
|
|
xive_register_hw_source(psi->interrupt, P9_PSI_NUM_IRQS,
|
|
12, psi->esb_mmio, XIVE_SRC_LSI,
|
|
psi, &psi_p9_irq_ops);
|
|
|
|
/* Reset irq handling and switch to ESB mode */
|
|
out_be64(psi->regs + PSIHB_INTERRUPT_CONTROL, PSIHB_IRQ_RESET);
|
|
out_be64(psi->regs + PSIHB_INTERRUPT_CONTROL, 0);
|
|
}
|
|
|
|
static void psi_init_interrupts(struct psi *psi)
|
|
{
|
|
/* Configure the interrupt BUID and mask it */
|
|
switch (proc_gen) {
|
|
case proc_gen_p8:
|
|
psi_init_p8_interrupts(psi);
|
|
break;
|
|
case proc_gen_p9:
|
|
psi_init_p9_interrupts(psi);
|
|
break;
|
|
default:
|
|
/* Unknown: just no interrupts */
|
|
prerror("PSI: Unknown interrupt type\n");
|
|
}
|
|
}
|
|
|
|
static void psi_activate_phb(struct psi *psi)
|
|
{
|
|
u64 reg;
|
|
|
|
/*
|
|
* Disable interrupt emission in the control register,
|
|
* it will be re-enabled later, after the mailbox one
|
|
* will have been enabled.
|
|
*/
|
|
reg = in_be64(psi->regs + PSIHB_CR);
|
|
reg &= ~PSIHB_CR_FSP_IRQ_ENABLE;
|
|
out_be64(psi->regs + PSIHB_CR, reg);
|
|
|
|
/* Enable interrupts in the mask register. We enable everything
|
|
* except for bit "FSP command error detected" which the doc
|
|
* (P7 BookIV) says should be masked for normal ops. It also
|
|
* seems to be masked under OPAL.
|
|
*/
|
|
reg = 0x0000010000100000ull;
|
|
out_be64(psi->regs + PSIHB_SEMR, reg);
|
|
|
|
#if 0
|
|
/* Dump the GXHB registers */
|
|
printf(" PSIHB_BBAR : %llx\n",
|
|
in_be64(psi->regs + PSIHB_BBAR));
|
|
printf(" PSIHB_FSPBAR : %llx\n",
|
|
in_be64(psi->regs + PSIHB_FSPBAR));
|
|
printf(" PSIHB_FSPMMR : %llx\n",
|
|
in_be64(psi->regs + PSIHB_FSPMMR));
|
|
printf(" PSIHB_TAR : %llx\n",
|
|
in_be64(psi->regs + PSIHB_TAR));
|
|
printf(" PSIHB_CR : %llx\n",
|
|
in_be64(psi->regs + PSIHB_CR));
|
|
printf(" PSIHB_SEMR : %llx\n",
|
|
in_be64(psi->regs + PSIHB_SEMR));
|
|
printf(" PSIHB_XIVR : %llx\n",
|
|
in_be64(psi->regs + PSIHB_XIVR));
|
|
#endif
|
|
}
|
|
|
|
static void psi_create_p9_int_map(struct psi *psi, struct dt_node *np)
|
|
{
|
|
uint32_t map[P9_PSI_NUM_IRQS][4];
|
|
int i;
|
|
|
|
for (i = 0; i < P9_PSI_NUM_IRQS; i++) {
|
|
map[i][0] = i;
|
|
map[i][1] = get_ics_phandle();
|
|
map[i][2] = psi->interrupt + i;
|
|
map[i][3] = 1;
|
|
}
|
|
dt_add_property(np, "interrupt-map", map, sizeof(map));
|
|
dt_add_property_cells(np, "#address-cells", 0);
|
|
dt_add_property_cells(np, "#interrupt-cells", 1);
|
|
}
|
|
|
|
static void psi_create_mm_dtnode(struct psi *psi)
|
|
{
|
|
struct dt_node *np;
|
|
uint64_t addr = (uint64_t)psi->regs;
|
|
|
|
np = dt_new_addr(dt_root, "psi", addr);
|
|
if (!np)
|
|
return;
|
|
|
|
/* Hard wire size to 4G */
|
|
dt_add_property_u64s(np, "reg", addr, 0x100000000ull);
|
|
switch (proc_gen) {
|
|
case proc_gen_p8:
|
|
dt_add_property_strings(np, "compatible", "ibm,psi",
|
|
"ibm,power8-psi");
|
|
break;
|
|
case proc_gen_p9:
|
|
dt_add_property_strings(np, "compatible", "ibm,psi",
|
|
"ibm,power9-psi");
|
|
psi_create_p9_int_map(psi, np);
|
|
break;
|
|
default:
|
|
dt_add_property_strings(np, "compatible", "ibm,psi");
|
|
}
|
|
dt_add_property_cells(np, "interrupt-parent", get_ics_phandle());
|
|
dt_add_property_cells(np, "interrupts", psi->interrupt, 1);
|
|
dt_add_property_cells(np, "ibm,chip-id", psi->chip_id);
|
|
psi->node = np;
|
|
}
|
|
|
|
static struct psi *alloc_psi(struct proc_chip *chip, uint64_t base)
|
|
{
|
|
struct psi *psi;
|
|
|
|
psi = zalloc(sizeof(struct psi));
|
|
if (!psi) {
|
|
prerror("PSI: Could not allocate memory\n");
|
|
return NULL;
|
|
}
|
|
psi->xscom_base = base;
|
|
psi->chip_id = chip->id;
|
|
return psi;
|
|
}
|
|
|
|
static struct psi *psi_probe_p8(struct proc_chip *chip, u64 base)
|
|
{
|
|
struct psi *psi = NULL;
|
|
uint64_t rc, val;
|
|
|
|
rc = xscom_read(chip->id, base + PSIHB_XSCOM_P8_BASE, &val);
|
|
if (rc) {
|
|
prerror("PSI[0x%03x]: Error %llx reading PSIHB BAR\n",
|
|
chip->id, rc);
|
|
return NULL;
|
|
}
|
|
if (val & PSIHB_XSCOM_P8_HBBAR_EN) {
|
|
psi = alloc_psi(chip, base);
|
|
if (!psi)
|
|
return NULL;
|
|
psi->regs = (void *)(val & ~PSIHB_XSCOM_P8_HBBAR_EN);
|
|
psi->interrupt = get_psi_interrupt(chip->id);
|
|
} else
|
|
printf("PSI[0x%03x]: Working chip not found\n", chip->id);
|
|
|
|
return psi;
|
|
}
|
|
|
|
static struct psi *psi_probe_p9(struct proc_chip *chip, u64 base)
|
|
{
|
|
struct psi *psi = NULL;
|
|
uint64_t addr;
|
|
|
|
phys_map_get(chip->id, PSIHB_REG, 0, &addr, NULL);
|
|
xscom_write(chip->id, base + PSIHB_XSCOM_P9_BASE,
|
|
addr | PSIHB_XSCOM_P9_HBBAR_EN);
|
|
|
|
psi = alloc_psi(chip, base);
|
|
if (!psi)
|
|
return NULL;
|
|
psi->regs = (void *)addr;
|
|
psi->interrupt = xive_alloc_hw_irqs(chip->id, P9_PSI_NUM_IRQS, 16);
|
|
return psi;
|
|
}
|
|
|
|
static bool psi_init_psihb(struct dt_node *psihb)
|
|
{
|
|
uint32_t chip_id = dt_get_chip_id(psihb);
|
|
struct proc_chip *chip = get_chip(chip_id);
|
|
struct psi *psi = NULL;
|
|
u64 base, val;
|
|
|
|
if (!chip) {
|
|
prerror("PSI: Can't find chip!\n");
|
|
return false;
|
|
}
|
|
|
|
base = dt_get_address(psihb, 0, NULL);
|
|
|
|
if (dt_node_is_compatible(psihb, "ibm,power8-psihb-x"))
|
|
psi = psi_probe_p8(chip, base);
|
|
else if (dt_node_is_compatible(psihb, "ibm,power9-psihb-x"))
|
|
psi = psi_probe_p9(chip, base);
|
|
else {
|
|
prerror("PSI: Unknown processor type\n");
|
|
return false;
|
|
}
|
|
if (!psi)
|
|
return false;
|
|
|
|
list_add(&psis, &psi->list);
|
|
|
|
val = in_be64(psi->regs + PSIHB_CR);
|
|
if (val & PSIHB_CR_FSP_LINK_ACTIVE) {
|
|
lock(&psi_lock);
|
|
psi->active = true;
|
|
unlock(&psi_lock);
|
|
}
|
|
chip->psi = psi;
|
|
|
|
if (dt_has_node_property(psihb, "no-lpc-interrupts", NULL))
|
|
psi->no_lpc_irqs = true;
|
|
|
|
psi_activate_phb(psi);
|
|
psi_init_interrupts(psi);
|
|
psi_create_mm_dtnode(psi);
|
|
|
|
prlog(PR_INFO, "PSI[0x%03x]: Found PSI bridge [active=%d]\n",
|
|
psi->chip_id, psi->active);
|
|
return true;
|
|
}
|
|
|
|
void psi_fsp_link_in_use(struct psi *psi __unused)
|
|
{
|
|
static bool poller_created = false;
|
|
|
|
/* Do this once only */
|
|
if (!poller_created) {
|
|
poller_created = true;
|
|
opal_add_poller(psi_link_poll, NULL);
|
|
}
|
|
}
|
|
|
|
struct psi *psi_find_functional_chip(void)
|
|
{
|
|
return list_top(&psis, struct psi, list);
|
|
}
|
|
|
|
void psi_init(void)
|
|
{
|
|
struct dt_node *np;
|
|
|
|
dt_for_each_compatible(dt_root, np, "ibm,psihb-x")
|
|
psi_init_psihb(np);
|
|
}
|
|
|
|
|