[Skiboot] [PATCH] Support for Naples LPC serial interrupts

Benjamin Herrenschmidt benh at kernel.crashing.org
Mon Jun 15 10:00:14 AEST 2015


This adds support for the HW SerIRQ deserializer of the P8 LPC
bridge which is properly wired up on Naples. It also adds support
for detecting and reporting LPC error interrupts on all P8s.

On most platforms (Rhesus is the exception here due to the way it
lets Linux handle the UART interrupts directly), we modify the
device-tree to properly represent the LPC controller as a cascaded
interrupt-controller and the "interrupts" property of LPC devices
to contain the actual LPC interrupt number for the device.

We add a mechanism for drivers to register specific LPC interrupts,
and a "workaround" for pre-Naples P8 which platforms can use to call
all of them for when the external FPGA based deserializer is used.

There's also a callback on LPC resets which isn't used yet, we need
a bit more work on the general LPC error handling, but it can be
done a separate patches.

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 8a9e935..0fcd215 100644
--- a/hw/bt.c
+++ b/hw/bt.c
@@ -432,7 +432,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;
 
@@ -499,10 +499,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");
@@ -542,4 +547,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 1b4f4c4..9cecace 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 790f3db..9f5fa31 100644
--- a/include/skiboot.h
+++ b/include/skiboot.h
@@ -232,7 +232,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 362f1b6..cda9d06 100644
--- a/platforms/astbmc/firestone.c
+++ b/platforms/astbmc/firestone.c
@@ -40,7 +40,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 7a43b1f..b597ba0 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 <libflash/blocklevel.h>
@@ -156,7 +157,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
@@ -193,14 +194,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());
+	}
 }
 
 /*
@@ -224,7 +226,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;
 
@@ -240,13 +242,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;
@@ -264,14 +267,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