From patchwork Tue May 12 07:03:07 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Herrenschmidt X-Patchwork-Id: 471155 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4085F1400A0 for ; Tue, 12 May 2015 17:03:17 +1000 (AEST) Received: from ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id F1E091A02AC for ; Tue, 12 May 2015 17:03:16 +1000 (AEST) X-Original-To: skiboot@lists.ozlabs.org Delivered-To: skiboot@lists.ozlabs.org Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id AF0B81A0243 for ; Tue, 12 May 2015 17:03:12 +1000 (AEST) Received: from localhost (localhost.localdomain [127.0.0.1]) by gate.crashing.org (8.14.1/8.13.8) with ESMTP id t4C737vB011995 for ; Tue, 12 May 2015 02:03:08 -0500 Message-ID: <1431414187.20218.28.camel@kernel.crashing.org> From: Benjamin Herrenschmidt To: skiboot@lists.ozlabs.org Date: Tue, 12 May 2015 17:03:07 +1000 X-Mailer: Evolution 3.12.11-0ubuntu3 Mime-Version: 1.0 Subject: [Skiboot] [RFC PATCH 2/2] Support for Naples LPC serial interrupts X-BeenThere: skiboot@lists.ozlabs.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Mailing list for skiboot development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: skiboot-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Skiboot" 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 --- 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 #include #include +#include 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 #include +//#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 #include +/* 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 #include #include +#include #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 #include #include +#include #include #include #include @@ -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; }