aboutsummaryrefslogtreecommitdiff
path: root/hw/ppc/pnv_lpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/ppc/pnv_lpc.c')
-rw-r--r--hw/ppc/pnv_lpc.c106
1 files changed, 95 insertions, 11 deletions
diff --git a/hw/ppc/pnv_lpc.c b/hw/ppc/pnv_lpc.c
index 78db524..f03a80a 100644
--- a/hw/ppc/pnv_lpc.c
+++ b/hw/ppc/pnv_lpc.c
@@ -92,14 +92,6 @@ enum {
#define LPC_HC_REGS_OPB_SIZE 0x00001000
-/*
- * TODO: the "primary" cell should only be added on chip 0. This is
- * how skiboot chooses the default LPC controller on multichip
- * systems.
- *
- * It would be easly done if we can change the populate() interface to
- * replace the PnvXScomInterface parameter by a PnvChip one
- */
static int pnv_lpc_populate(PnvXScomInterface *dev, void *fdt, int xscom_offset)
{
const char compat[] = "ibm,power8-lpc\0ibm,lpc";
@@ -119,7 +111,6 @@ static int pnv_lpc_populate(PnvXScomInterface *dev, void *fdt, int xscom_offset)
_FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg))));
_FDT((fdt_setprop_cell(fdt, offset, "#address-cells", 2)));
_FDT((fdt_setprop_cell(fdt, offset, "#size-cells", 1)));
- _FDT((fdt_setprop(fdt, offset, "primary", NULL, 0)));
_FDT((fdt_setprop(fdt, offset, "compatible", compat, sizeof(compat))));
return 0;
}
@@ -250,6 +241,34 @@ static const MemoryRegionOps pnv_lpc_xscom_ops = {
.endianness = DEVICE_BIG_ENDIAN,
};
+static void pnv_lpc_eval_irqs(PnvLpcController *lpc)
+{
+ bool lpc_to_opb_irq = false;
+
+ /* Update LPC controller to OPB line */
+ if (lpc->lpc_hc_irqser_ctrl & LPC_HC_IRQSER_EN) {
+ uint32_t irqs;
+
+ irqs = lpc->lpc_hc_irqstat & lpc->lpc_hc_irqmask;
+ lpc_to_opb_irq = (irqs != 0);
+ }
+
+ /* We don't honor the polarity register, it's pointless and unused
+ * anyway
+ */
+ if (lpc_to_opb_irq) {
+ lpc->opb_irq_input |= OPB_MASTER_IRQ_LPC;
+ } else {
+ lpc->opb_irq_input &= ~OPB_MASTER_IRQ_LPC;
+ }
+
+ /* Update OPB internal latch */
+ lpc->opb_irq_stat |= lpc->opb_irq_input & lpc->opb_irq_mask;
+
+ /* Reflect the interrupt */
+ pnv_psi_irq_set(lpc->psi, PSIHB_IRQ_LPC_I2C, lpc->opb_irq_stat != 0);
+}
+
static uint64_t lpc_hc_read(void *opaque, hwaddr addr, unsigned size)
{
PnvLpcController *lpc = opaque;
@@ -300,12 +319,15 @@ static void lpc_hc_write(void *opaque, hwaddr addr, uint64_t val,
break;
case LPC_HC_IRQSER_CTRL:
lpc->lpc_hc_irqser_ctrl = val;
+ pnv_lpc_eval_irqs(lpc);
break;
case LPC_HC_IRQMASK:
lpc->lpc_hc_irqmask = val;
+ pnv_lpc_eval_irqs(lpc);
break;
case LPC_HC_IRQSTAT:
lpc->lpc_hc_irqstat &= ~val;
+ pnv_lpc_eval_irqs(lpc);
break;
case LPC_HC_ERROR_ADDRESS:
break;
@@ -363,14 +385,15 @@ static void opb_master_write(void *opaque, hwaddr addr,
switch (addr) {
case OPB_MASTER_LS_IRQ_STAT:
lpc->opb_irq_stat &= ~val;
+ pnv_lpc_eval_irqs(lpc);
break;
case OPB_MASTER_LS_IRQ_MASK:
- /* XXX Filter out reserved bits */
lpc->opb_irq_mask = val;
+ pnv_lpc_eval_irqs(lpc);
break;
case OPB_MASTER_LS_IRQ_POL:
- /* XXX Filter out reserved bits */
lpc->opb_irq_pol = val;
+ pnv_lpc_eval_irqs(lpc);
break;
case OPB_MASTER_LS_IRQ_INPUT:
/* Read only */
@@ -398,6 +421,8 @@ static const MemoryRegionOps opb_master_ops = {
static void pnv_lpc_realize(DeviceState *dev, Error **errp)
{
PnvLpcController *lpc = PNV_LPC(dev);
+ Object *obj;
+ Error *error = NULL;
/* Reg inits */
lpc->lpc_hc_fw_rd_acc_size = LPC_HC_FW_RD_4B;
@@ -441,6 +466,15 @@ static void pnv_lpc_realize(DeviceState *dev, Error **errp)
pnv_xscom_region_init(&lpc->xscom_regs, OBJECT(dev),
&pnv_lpc_xscom_ops, lpc, "xscom-lpc",
PNV_XSCOM_LPC_SIZE);
+
+ /* get PSI object from chip */
+ obj = object_property_get_link(OBJECT(dev), "psi", &error);
+ if (!obj) {
+ error_setg(errp, "%s: required link 'psi' not found: %s",
+ __func__, error_get_pretty(error));
+ return;
+ }
+ lpc->psi = PNV_PSI(obj);
}
static void pnv_lpc_class_init(ObjectClass *klass, void *data)
@@ -470,3 +504,53 @@ static void pnv_lpc_register_types(void)
}
type_init(pnv_lpc_register_types)
+
+/* If we don't use the built-in LPC interrupt deserializer, we need
+ * to provide a set of qirqs for the ISA bus or things will go bad.
+ *
+ * Most machines using pre-Naples chips (without said deserializer)
+ * have a CPLD that will collect the SerIRQ and shoot them as a
+ * single level interrupt to the P8 chip. So let's setup a hook
+ * for doing just that.
+ */
+static void pnv_lpc_isa_irq_handler_cpld(void *opaque, int n, int level)
+{
+ PnvMachineState *pnv = POWERNV_MACHINE(qdev_get_machine());
+ uint32_t old_state = pnv->cpld_irqstate;
+ PnvLpcController *lpc = PNV_LPC(opaque);
+
+ if (level) {
+ pnv->cpld_irqstate |= 1u << n;
+ } else {
+ pnv->cpld_irqstate &= ~(1u << n);
+ }
+
+ if (pnv->cpld_irqstate != old_state) {
+ pnv_psi_irq_set(lpc->psi, PSIHB_IRQ_EXTERNAL, pnv->cpld_irqstate != 0);
+ }
+}
+
+static void pnv_lpc_isa_irq_handler(void *opaque, int n, int level)
+{
+ PnvLpcController *lpc = PNV_LPC(opaque);
+
+ /* The Naples HW latches the 1 levels, clearing is done by SW */
+ if (level) {
+ lpc->lpc_hc_irqstat |= LPC_HC_IRQ_SERIRQ0 >> n;
+ pnv_lpc_eval_irqs(lpc);
+ }
+}
+
+qemu_irq *pnv_lpc_isa_irq_create(PnvLpcController *lpc, int chip_type,
+ int nirqs)
+{
+ /* Not all variants have a working serial irq decoder. If not,
+ * handling of LPC interrupts becomes a platform issue (some
+ * platforms have a CPLD to do it).
+ */
+ if (chip_type == PNV_CHIP_POWER8NVL) {
+ return qemu_allocate_irqs(pnv_lpc_isa_irq_handler, lpc, nirqs);
+ } else {
+ return qemu_allocate_irqs(pnv_lpc_isa_irq_handler_cpld, lpc, nirqs);
+ }
+}