[Skiboot] [RFC PATCH 2/2] Support for Naples LPC serial interrupts
Benjamin Herrenschmidt
benh at kernel.crashing.org
Tue May 12 17:03:07 AEST 2015
This also adds error and reset, though there is still some work
to do for the reset to be actually useful
NOTE: The reset stuff needs more work, this is more for a base
review of the overall idea.
Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
---
core/chip.c | 1 +
hw/bt.c | 12 +-
hw/lpc-uart.c | 33 ++--
hw/lpc.c | 352 ++++++++++++++++++++++++++++++++++++++++++-
include/bt.h | 1 -
include/chip.h | 1 +
include/lpc.h | 59 +++++++-
include/skiboot.h | 1 -
platforms/astbmc/astbmc.h | 2 +-
platforms/astbmc/common.c | 20 ++-
platforms/astbmc/firestone.c | 2 +-
platforms/astbmc/habanero.c | 2 +-
platforms/astbmc/palmetto.c | 2 +-
platforms/rhesus/rhesus.c | 27 ++--
14 files changed, 464 insertions(+), 51 deletions(-)
diff --git a/core/chip.c b/core/chip.c
index 2ba7b6e..7059ec3 100644
--- a/core/chip.c
+++ b/core/chip.c
@@ -95,5 +95,6 @@ void init_chips(void)
chip->pcid = dt_prop_get_u32_def(xn, "ibm,proc-chip-id",
0xffffffff);
list_head_init(&chip->i2cms);
+ list_head_init(&chip->lpc_clients);
};
}
diff --git a/hw/bt.c b/hw/bt.c
index 15d3ebc..a7483e5 100644
--- a/hw/bt.c
+++ b/hw/bt.c
@@ -444,7 +444,7 @@ static int bt_add_ipmi_msg(struct ipmi_msg *ipmi_msg)
return 0;
}
-void bt_irq(void)
+static void bt_irq(uint32_t chip_id __unused, uint32_t irq_mask __unused)
{
uint8_t ireg;
@@ -511,10 +511,15 @@ static struct ipmi_backend bt_backend = {
.dequeue_msg = bt_del_ipmi_msg,
};
+static struct lpc_client bt_lpc_client = {
+ .interrupt = bt_irq,
+};
+
void bt_init(void)
{
struct dt_node *n;
const struct dt_property *prop;
+ uint32_t irq;
/* We support only one */
n = dt_find_compatible_node(dt_root, NULL, "ipmi-bt");
@@ -554,4 +559,9 @@ void bt_init(void)
* point we turn it into a background poller
*/
schedule_timer(&bt.poller, msecs_to_tb(BT_DEFAULT_POLL_MS));
+
+ irq = dt_prop_get_u32(n, "interrupts");
+ bt_lpc_client.interrupts = LPC_IRQ(irq);
+ lpc_register_client(dt_get_chip_id(n), &bt_lpc_client);
+ prlog(PR_DEBUG, "BT: Using LPC IRQ %d\n", irq);
}
diff --git a/hw/lpc-uart.c b/hw/lpc-uart.c
index 7c3190e..a4c0086 100644
--- a/hw/lpc-uart.c
+++ b/hw/lpc-uart.c
@@ -25,6 +25,7 @@
#include <trace.h>
#include <timebase.h>
#include <cpu.h>
+#include <chip.h>
DEFINE_LOG_ENTRY(OPAL_RC_UART_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_UART,
OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
@@ -365,7 +366,7 @@ static void uart_console_poll(void *data __unused)
__uart_do_poll(TRACE_UART_CTX_POLL);
}
-void uart_irq(void)
+static void uart_irq(uint32_t chip_id __unused, uint32_t irq_mask __unused)
{
if (!irq_ok) {
prlog(PR_DEBUG, "UART: IRQ functional !\n");
@@ -464,12 +465,16 @@ static bool uart_init_hw(unsigned int speed, unsigned int clock)
return false;
}
-void uart_init(bool enable_interrupt)
+static struct lpc_client uart_lpc_client = {
+ .interrupt = uart_irq,
+};
+
+void uart_init(bool use_interrupt)
{
const struct dt_property *prop;
struct dt_node *n;
char *path __unused;
- uint32_t irqchip, irq;
+ uint32_t chip_id, irq;
if (!lpc_present())
return;
@@ -504,6 +509,7 @@ void uart_init(bool enable_interrupt)
dt_add_property_strings(n, "status", "bad");
return;
}
+ chip_id = dt_get_chip_id(uart_node);
/*
* Mark LPC used by the console (will mark the relevant
@@ -514,13 +520,16 @@ void uart_init(bool enable_interrupt)
/* Install console backend for printf() */
set_console(&uart_con_driver);
- /* Setup the interrupts properties since HB couldn't do it */
- irqchip = dt_prop_get_u32(n, "ibm,irq-chip-id");
- irq = get_psi_interrupt(irqchip) + P8_IRQ_PSI_HOST_ERR;
- prlog(PR_DEBUG, "UART: IRQ connected to chip %d, irq# is 0x%x\n", irqchip, irq);
- has_irq = enable_interrupt;
- if (has_irq) {
- dt_add_property_cells(n, "interrupts", irq);
- dt_add_property_cells(n, "interrupt-parent", get_ics_phandle());
- }
+ /* On Naples, use the SerIRQ, which Linux will have to share with
+ * OPAL as we don't really play the cascaded interrupt game at this
+ * point...
+ */
+ if (use_interrupt) {
+ irq = dt_prop_get_u32(n, "interrupts");
+ uart_lpc_client.interrupts = LPC_IRQ(irq);
+ lpc_register_client(chip_id, &uart_lpc_client);
+ has_irq = true;
+ prlog(PR_DEBUG, "UART: Using LPC IRQ %d\n", irq);
+ } else
+ has_irq = false;
}
diff --git a/hw/lpc.c b/hw/lpc.c
index b287020..34c71b8 100644
--- a/hw/lpc.c
+++ b/hw/lpc.c
@@ -24,6 +24,9 @@
#include <errorlog.h>
#include <opal-api.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, NULL);
@@ -56,14 +59,47 @@ DEFINE_LOG_ENTRY(OPAL_RC_LPC_WRITE, OPAL_PLATFORM_ERR_EVT, OPAL_LPC,
#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_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_IRQMASK 0x34 /* same bit defs as LPC_HC_IRQSTAT */
+#define LPC_HC_IRQSTAT 0x38
+#define LPC_HC_IRQ_SERIRQ0 0x80000000 /* 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_SYNC_TARG_TAR_ERR 0x00000008
+#define LPC_HC_IRQ_SYNC_BM_TAR_ERR 0x00000004
+#define LPC_HC_IRQ_SYNC_BM0_REQ 0x00000002
+#define LPC_HC_IRQ_SYNC_BM1_REQ 0x00000001
+#define LPC_HC_ERROR_ADDRESS 0x40
+
+struct lpc_client_entry {
+ struct list_node node;
+ const struct lpc_client *clt;
+};
/* Default LPC bus */
static int32_t lpc_default_chip_id = -1;
@@ -77,6 +113,7 @@ 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_write(struct proc_chip *chip, uint32_t addr, uint32_t data,
uint32_t sz)
@@ -448,9 +485,287 @@ bool lpc_present(void)
return lpc_default_chip_id >= 0;
}
-void __attrconst lpc_interrupt(uint32_t chip_id __unused)
+/* OPB Master 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_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_IRQMASK 0x34 /* same bit defs as LPC_HC_IRQSTAT */
+#define LPC_HC_IRQSTAT 0x38
+#define LPC_HC_IRQ_SERIRQ0 0x80000000 /* 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_SYNC_TARG_TAR_ERR 0x00000008
+#define LPC_HC_IRQ_SYNC_BM_TAR_ERR 0x00000004
+#define LPC_HC_IRQ_SYNC_BM0_REQ 0x00000002
+#define LPC_HC_IRQ_SYNC_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_SYNC_TARG_TAR_ERR | \
+ LPC_HC_IRQ_SYNC_BM_TAR_ERR)
+#define LPC_HC_ERROR_ADDRESS 0x40
+
+/* Called with LPC lock held */
+static void lpc_setup_serirq(struct proc_chip *chip)
{
- /* Handle the lpc interrupt source (errors etc...) TODO... */
+ struct lpc_client_entry *ent;
+ uint32_t mask = LPC_HC_IRQ_BASE_IRQS;
+ int rc;
+
+ /* Collect serirq enable bits */
+ list_for_each(&chip->lpc_clients, ent, node)
+ mask |= ent->clt->interrupts & LPC_HC_IRQ_SERIRQ_ALL;
+
+ rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_IRQMASK, mask, 4);
+ if (rc) {
+ prerror("LPC: Failed to update irq mask\n");
+ return;
+ }
+ DBG_IRQ("LPC: IRQ mask set to 0x%08x\n", mask);
+
+ /* Enable the LPC interrupt in the OPB Master */
+ opb_write(chip, opb_master_reg_base + OPB_MASTER_LS_IRQ_POL, 0, 4);
+ rc = opb_write(chip, opb_master_reg_base + OPB_MASTER_LS_IRQ_MASK,
+ OPB_MASTER_IRQ_LPC, 4);
+ if (rc)
+ prerror("LPC: Failed to enable IRQs in OPB\n");
+
+ /* Check whether we should enable serirq */
+ if (mask & LPC_HC_IRQ_SERIRQ_ALL) {
+ rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_IRQSER_CTRL,
+ LPC_HC_IRQSER_EN | LPC_HC_IRQSER_START_4CLK, 4);
+ DBG_IRQ("LPC: SerIRQ enabled\n");
+ } else {
+ rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_IRQSER_CTRL,
+ 0, 4);
+ DBG_IRQ("LPC: SerIRQ disabled\n");
+ }
+ if (rc)
+ prerror("LPC: Failed to configure SerIRQ\n");
+ {
+ u32 val;
+ rc = opb_read(chip, lpc_reg_opb_base + LPC_HC_IRQMASK, &val, 4);
+ DBG_IRQ("LPC: MASK READBACK=%x\n", val);
+ rc = opb_read(chip, lpc_reg_opb_base + LPC_HC_IRQSER_CTRL, &val, 4);
+ DBG_IRQ("LPC: CTRL READBACK=%x\n", val);
+ }
+}
+
+static void lpc_init_interrupts(struct proc_chip *chip)
+{
+ int rc;
+
+ /* First mask them all */
+ rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_IRQMASK, 0, 4);
+ if (rc) {
+ prerror("LPC: Failed to init interrutps\n");
+ return;
+ }
+
+ 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(chip, lpc_reg_opb_base + LPC_HC_IRQMASK,
+ LPC_HC_IRQ_BASE_IRQS, 4);
+ if (rc) {
+ prerror("LPC: Failed to set interrupt mask\n");
+ return;
+ }
+ opb_write(chip, 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
+ */
+ lock(&chip->lpc_lock);
+ lpc_setup_serirq(chip);
+ unlock(&chip->lpc_lock);
+ break;
+ default:
+ /* We aren't getting here, are we ? */
+ return;
+ }
+}
+
+static void lpc_dispatch_reset(struct proc_chip *chip)
+{
+ 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("LPC: Got LPC reset !\n");
+
+ /* Collect serirq enable bits */
+ list_for_each(&chip->lpc_clients, ent, node) {
+ if (!ent->clt->reset)
+ continue;
+ unlock(&chip->lpc_lock);
+ ent->clt->reset(chip->id);
+ lock(&chip->lpc_lock);
+ }
+
+ /* Reconfigure serial interrupts */
+ if (chip->type == PROC_CHIP_P8_NAPLES)
+ lpc_setup_serirq(chip);
+}
+
+static void lpc_dispatch_err_irqs(struct proc_chip *chip, uint32_t irqs)
+{
+ int rc;
+ uint32_t err_addr;
+
+ /* Write back to clear error interrupts, we clear SerIRQ later
+ * as they are handled as level interrupts
+ */
+ rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_IRQSTAT,
+ LPC_HC_IRQ_BASE_IRQS, 4);
+ if (rc)
+ prerror("LPC: Failed to clear IRQ error latches !\n");
+
+
+ if (irqs & LPC_HC_IRQ_LRESET)
+ lpc_dispatch_reset(chip);
+ if (irqs & LPC_HC_IRQ_SYNC_ABNORM_ERR)
+ prerror("LPC: Got abnormal SYNC error\n");
+ if (irqs & LPC_HC_IRQ_SYNC_NORESP_ERR)
+ prerror("LPC: Got abnormal SYNC error\n");
+ if (irqs & LPC_HC_IRQ_SYNC_NORM_ERR)
+ prerror("LPC: Got abnormal SYNC error\n");
+ if (irqs & LPC_HC_IRQ_SYNC_TIMEOUT_ERR)
+ prerror("LPC: Got abnormal SYNC error\n");
+ if (irqs & LPC_HC_IRQ_SYNC_TARG_TAR_ERR)
+ prerror("LPC: Got abnormal SYNC error\n");
+ if (irqs & LPC_HC_IRQ_SYNC_BM_TAR_ERR)
+ prerror("LPC: Got abnormal SYNC error\n");
+
+ rc = opb_read(chip, lpc_reg_opb_base + LPC_HC_ERROR_ADDRESS,
+ &err_addr, 4);
+ if (rc)
+ prerror("LPC: Error reading error address register\n");
+ else
+ prerror("LPC: Error address reg: 0x%08x\n", err_addr);
+}
+
+static void lpc_dispatch_ser_irqs(struct proc_chip *chip, 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(&chip->lpc_clients, ent, node) {
+ if (!ent->clt->interrupt)
+ continue;
+ cirqs = ent->clt->interrupts & irqs;
+ if (cirqs) {
+ unlock(&chip->lpc_lock);
+ ent->clt->interrupt(chip->id, cirqs);
+ lock(&chip->lpc_lock);
+ }
+ }
+
+ /* Our SerIRQ are level sensitive, we clear the latch after
+ * we call the handler.
+ */
+ if (!clear_latch)
+ return;
+
+ rc = opb_write(chip, lpc_reg_opb_base + LPC_HC_IRQSTAT,
+ irqs, 4);
+ if (rc)
+ prerror("LPC: Failed to clear SerIRQ latches !\n");
+}
+
+void lpc_interrupt(uint32_t chip_id)
+{
+ struct proc_chip *chip = get_chip(chip_id);
+ uint32_t irqs, opb_irqs;
+ int rc;
+
+ /* No initialized LPC controller on that chip */
+ if (!chip->lpc_xbase)
+ return;
+
+ lock(&chip->lpc_lock);
+
+ /* Grab OPB Master LS interrupt status */
+ rc = opb_read(chip, opb_master_reg_base + OPB_MASTER_LS_IRQ_STAT,
+ &opb_irqs, 4);
+ if (rc) {
+ prerror("LPC: Failed to read OPB IRQ state\n");
+ goto bail;
+ }
+
+ /* Check if it's an LPC interrupt */
+ if (!(opb_irqs & OPB_MASTER_IRQ_LPC)) {
+ /* Something we don't support ? Ack it anyway... */
+ opb_write(chip, opb_master_reg_base + OPB_MASTER_LS_IRQ_STAT,
+ opb_irqs, 4);
+ goto bail;
+ }
+
+ /* Handle the lpc interrupt source (errors etc...) */
+ rc = opb_read(chip, lpc_reg_opb_base + LPC_HC_IRQSTAT, &irqs, 4);
+ if (rc) {
+ prerror("LPC: 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(chip, irqs);
+
+ /* Handle SerIRQ interrupts */
+ if (irqs & LPC_HC_IRQ_SERIRQ_ALL)
+ lpc_dispatch_ser_irqs(chip, irqs, true);
+
+ /* Ack it at the OPB level */
+ opb_write(chip, opb_master_reg_base + OPB_MASTER_LS_IRQ_STAT,
+ opb_irqs, 4);
+ bail:
+ unlock(&chip->lpc_lock);
+}
+
+void lpc_all_interrupts(uint32_t chip_id)
+{
+ struct proc_chip *chip = get_chip(chip_id);
+
+ /* Dispatch all */
+ lock(&chip->lpc_lock);
+ lpc_dispatch_ser_irqs(chip, LPC_HC_IRQ_SERIRQ_ALL, false);
+ unlock(&chip->lpc_lock);
}
void lpc_init(void)
@@ -478,6 +793,10 @@ void lpc_init(void)
printf("LPC: Bus on chip %d PCB_Addr=0x%x\n",
chip->id, chip->lpc_xbase);
has_lpc = true;
+
+ lpc_init_interrupts(chip);
+ if (chip->type == PROC_CHIP_P8_NAPLES)
+ dt_add_property(xn, "interrupt-controller", NULL, 0);
}
if (lpc_default_chip_id >= 0)
printf("LPC: Default bus on chip %d\n", lpc_default_chip_id);
@@ -512,3 +831,22 @@ bool lpc_ok(void)
chip = get_chip(lpc_default_chip_id);
return !lock_held_by_me(&chip->lpc_lock);
}
+
+void lpc_register_client(uint32_t chip_id,
+ const struct lpc_client *clt)
+{
+ struct lpc_client_entry *ent;
+ struct proc_chip *chip;
+
+ chip = get_chip(chip_id);
+ assert(chip);
+ ent = malloc(sizeof(*ent));
+ assert(ent);
+ ent->clt = clt;
+ lock(&chip->lpc_lock);
+ list_add(&chip->lpc_clients, &ent->node);
+ /* Re-evaluate ser irqs on Naples */
+ if (chip->type == PROC_CHIP_P8_NAPLES)
+ lpc_setup_serirq(chip);
+ unlock(&chip->lpc_lock);
+}
diff --git a/include/bt.h b/include/bt.h
index 43843d2..1763d9f 100644
--- a/include/bt.h
+++ b/include/bt.h
@@ -19,6 +19,5 @@
/* Initialise the BT interface */
void bt_init(void);
-void bt_irq(void);
#endif
diff --git a/include/chip.h b/include/chip.h
index 0547902..3ea8ed4 100644
--- a/include/chip.h
+++ b/include/chip.h
@@ -136,6 +136,7 @@ struct proc_chip {
struct lock lpc_lock;
uint8_t lpc_fw_idsel;
uint8_t lpc_fw_rdsz;
+ struct list_head lpc_clients;
/* Used by hw/slw.c */
uint64_t slw_base;
diff --git a/include/lpc.h b/include/lpc.h
index a0990fd..a79d256 100644
--- a/include/lpc.h
+++ b/include/lpc.h
@@ -20,6 +20,42 @@
#include <opal.h>
#include <ccan/endian/endian.h>
+/* Note about LPC interrupts
+ *
+ * LPC interrupts come in two categories:
+ *
+ * - External device LPC interrupts
+ * - Error interrupts generated by the LPC controller
+ *
+ * The former is implemented differently depending on whether
+ * you are using Murano/Venice or Naples.
+ *
+ * The former two chips don't have a pin to deserialize the LPC
+ * SerIRQ protocol, so the only source of LPC device interrupts
+ * is an external interrupt pin, which is usually connected to a
+ * CPLD which deserializes SerIRQ.
+ *
+ * So in that case, we get external interrupts from the PSI which
+ * are in effect the "OR" of all the active LPC interrupts.
+ *
+ * The error interrupt generated by the LPC controllers however
+ * are internally routed normally to the PSI bridge and muxed with
+ * the I2C interrupts.
+ *
+ * On Naples, there is a pin to deserialize SerIRQ, so the individual
+ * LPC device interrupts (up to 19) are represented in the same status
+ * and mask register as the LPC error interrupts. They are still all
+ * then turned into a single XIVE interrupts in the PSI however, muxed
+ * with the I2C.
+ *
+ * In order to more/less transparently handle this, we let individual
+ * "drivers" register for specific LPC interrupts. On Naples, the handlers
+ * will be called individually based on what has been demuxed by the
+ * controller. On Venice/Murano, all the handlers will be called on
+ * every external interrupt. The platform is responsible of calling
+ * lpc_all_interrupts() from the platform external interrupt handler.
+ */
+
/* Routines for accessing the LPC bus on Power8 */
extern void lpc_init(void);
@@ -33,8 +69,27 @@ extern bool lpc_present(void);
*/
extern bool lpc_ok(void);
-/* Handle the interrupt from LPC source */
-extern void __attrconst lpc_interrupt(uint32_t chip_id);
+/* Handle the interrupt from the LPC controller */
+extern void lpc_interrupt(uint32_t chip_id);
+
+/* Call all external handlers */
+extern void lpc_all_interrupts(uint32_t chip_id);
+
+/* Register/deregister handler */
+struct lpc_client {
+ /* Callback on LPC reset */
+ void (*reset)(uint32_t chip_id);
+
+ /* Callback on LPC interrupt */
+ void (*interrupt)(uint32_t chip_id, uint32_t irq_msk);
+ /* Bitmask of interrupts this client is interested in
+ * Note: beware of ordering, use LPC_IRQ() macro
+ */
+ uint32_t interrupts;
+#define LPC_IRQ(n) (0x80000000 >> (n))
+};
+
+extern void lpc_register_client(uint32_t chip_id, const struct lpc_client *clt);
/* Default bus accessors */
extern int64_t lpc_write(enum OpalLPCAddressType addr_type, uint32_t addr,
diff --git a/include/skiboot.h b/include/skiboot.h
index 9751a31..f4ac992 100644
--- a/include/skiboot.h
+++ b/include/skiboot.h
@@ -227,7 +227,6 @@ extern void nvram_init(void);
extern void nvram_read_complete(bool success);
/* UART stuff */
-extern void uart_irq(void);
extern void uart_setup_linux_passthrough(void);
extern void uart_setup_opal_console(void);
diff --git a/platforms/astbmc/astbmc.h b/platforms/astbmc/astbmc.h
index cee475a..489ffd2 100644
--- a/platforms/astbmc/astbmc.h
+++ b/platforms/astbmc/astbmc.h
@@ -22,7 +22,7 @@ extern void astbmc_early_init(void);
extern int64_t astbmc_ipmi_reboot(void);
extern int64_t astbmc_ipmi_power_down(uint64_t request);
extern void astbmc_init(void);
-extern void astbmc_ext_irq(unsigned int chip_id);
+extern void astbmc_ext_irq_serirq_cpld(unsigned int chip_id);
extern int pnor_init(void);
#endif /* __ASTBMC_H */
diff --git a/platforms/astbmc/common.c b/platforms/astbmc/common.c
index c89af63..50341d6 100644
--- a/platforms/astbmc/common.c
+++ b/platforms/astbmc/common.c
@@ -25,6 +25,7 @@
#include <ipmi.h>
#include <bt.h>
#include <errorlog.h>
+#include <lpc.h>
#include "astbmc.h"
@@ -38,10 +39,9 @@
#define BT_IO_COUNT 3
#define BT_LPC_IRQ 10
-void astbmc_ext_irq(unsigned int chip_id __unused)
+void astbmc_ext_irq_serirq_cpld(unsigned int chip_id)
{
- uart_irq();
- bt_irq();
+ lpc_all_interrupts(chip_id);
}
static void astbmc_ipmi_error(struct ipmi_msg *msg)
@@ -180,6 +180,9 @@ static void astbmc_fixup_dt_bt(struct dt_node *lpc)
/* Mark it as reserved to avoid Linux trying to claim it */
dt_add_property_strings(bt, "status", "reserved");
+
+ dt_add_property_cells(bt, "interrupts", BT_LPC_IRQ);
+ dt_add_property_cells(bt, "interrupt-parent", lpc->phandle);
}
static void astbmc_fixup_dt_uart(struct dt_node *lpc)
@@ -222,14 +225,9 @@ static void astbmc_fixup_dt_uart(struct dt_node *lpc)
*/
dt_add_property_strings(uart, "device_type", "serial");
- /*
- * Add interrupt. This simulates coming from HostBoot which
- * does not know our interrupt numbering scheme. Instead, it
- * just tells us which chip the interrupt is wired to, it will
- * be the PSI "host error" interrupt of that chip. For now we
- * assume the same chip as the LPC bus is on.
- */
- dt_add_property_cells(uart, "ibm,irq-chip-id", dt_get_chip_id(lpc));
+ /* Add interrupt */
+ dt_add_property_cells(uart, "interrupts", UART_LPC_IRQ);
+ dt_add_property_cells(uart, "interrupt-parent", lpc->phandle);
}
static void del_compatible(struct dt_node *node)
diff --git a/platforms/astbmc/firestone.c b/platforms/astbmc/firestone.c
index 4d3be19..2ec6fa6 100644
--- a/platforms/astbmc/firestone.c
+++ b/platforms/astbmc/firestone.c
@@ -39,7 +39,7 @@ DECLARE_PLATFORM(firestone) = {
.name = "Firestone",
.probe = firestone_probe,
.init = astbmc_init,
- .external_irq = astbmc_ext_irq,
+ .external_irq = astbmc_ext_irq_serirq_cpld,
.cec_power_down = astbmc_ipmi_power_down,
.cec_reboot = astbmc_ipmi_reboot,
.elog_commit = ipmi_elog_commit,
diff --git a/platforms/astbmc/habanero.c b/platforms/astbmc/habanero.c
index a2eec4a..7ae5dc4 100644
--- a/platforms/astbmc/habanero.c
+++ b/platforms/astbmc/habanero.c
@@ -49,7 +49,7 @@ DECLARE_PLATFORM(habanero) = {
.name = "Habanero",
.probe = habanero_probe,
.init = astbmc_init,
- .external_irq = astbmc_ext_irq,
+ .external_irq = astbmc_ext_irq_serirq_cpld,
.cec_power_down = astbmc_ipmi_power_down,
.cec_reboot = astbmc_ipmi_reboot,
.elog_commit = ipmi_elog_commit,
diff --git a/platforms/astbmc/palmetto.c b/platforms/astbmc/palmetto.c
index 803ca46..7c2a157 100644
--- a/platforms/astbmc/palmetto.c
+++ b/platforms/astbmc/palmetto.c
@@ -49,7 +49,7 @@ DECLARE_PLATFORM(palmetto) = {
.name = "Palmetto",
.probe = palmetto_probe,
.init = astbmc_init,
- .external_irq = astbmc_ext_irq,
+ .external_irq = astbmc_ext_irq_serirq_cpld,
.cec_power_down = astbmc_ipmi_power_down,
.cec_reboot = astbmc_ipmi_reboot,
.elog_commit = ipmi_elog_commit,
diff --git a/platforms/rhesus/rhesus.c b/platforms/rhesus/rhesus.c
index 50769cf..11dd12f 100644
--- a/platforms/rhesus/rhesus.c
+++ b/platforms/rhesus/rhesus.c
@@ -20,6 +20,7 @@
#include <lpc.h>
#include <console.h>
#include <opal.h>
+#include <interrupts.h>
#include <libflash/libflash.h>
#include <libflash/libffs.h>
#include <sfc-ctrl.h>
@@ -155,7 +156,7 @@ static void rhesus_init(void)
uart_setup_linux_passthrough();
}
-static void rhesus_dt_fixup_uart(struct dt_node *lpc)
+static void rhesus_dt_fixup_uart(struct dt_node *lpc, bool has_irq)
{
/*
* The official OF ISA/LPC binding is a bit odd, it prefixes
@@ -192,14 +193,15 @@ static void rhesus_dt_fixup_uart(struct dt_node *lpc)
*/
dt_add_property_strings(uart, "device_type", "serial");
- /*
- * Add interrupt. This simulates coming from HostBoot which
- * does not know our interrupt numbering scheme. Instead, it
- * just tells us which chip the interrupt is wired to, it will
- * be the PSI "host error" interrupt of that chip. For now we
- * assume the same chip as the LPC bus is on.
+ /* Expose the external interrupt if supported
*/
- dt_add_property_cells(uart, "ibm,irq-chip-id", dt_get_chip_id(lpc));
+ if (has_irq) {
+ uint32_t chip_id = dt_get_chip_id(lpc);
+ uint32_t irq = get_psi_interrupt(chip_id) + P8_IRQ_PSI_HOST_ERR;
+ dt_add_property_cells(uart, "interrupts", irq);
+ dt_add_property_cells(uart, "interrupt-parent",
+ get_ics_phandle());
+ }
}
/*
@@ -223,7 +225,7 @@ static void rhesus_dt_fixup_rtc(struct dt_node *lpc)
(EC_RTC_BLOCK_SIZE / 128) * 2);
}
-static void rhesus_dt_fixup(void)
+static void rhesus_dt_fixup(bool has_uart_irq)
{
struct dt_node *n, *primary_lpc = NULL;
@@ -239,13 +241,14 @@ static void rhesus_dt_fixup(void)
return;
rhesus_dt_fixup_rtc(primary_lpc);
- rhesus_dt_fixup_uart(primary_lpc);
+ rhesus_dt_fixup_uart(primary_lpc, has_uart_irq);
}
static bool rhesus_probe(void)
{
const char *model;
int rev;
+ bool has_uart_irq = false;
if (!dt_node_is_compatible(dt_root, "ibm,powernv"))
return false;
@@ -263,14 +266,14 @@ static bool rhesus_probe(void)
prerror("Rhesus board revision not found !\n");
/* Add missing bits of device-tree such as the UART */
- rhesus_dt_fixup();
+ rhesus_dt_fixup(has_uart_irq);
/*
* Setup UART and use it as console. For now, we
* don't expose the interrupt as we know it's not
* working properly yet
*/
- uart_init(false);
+ uart_init(has_uart_irq);
return true;
}
More information about the Skiboot
mailing list