[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