#include "qemu/osdep.h"
#include "sysemu/sysemu.h"
-#include "target-ppc/cpu.h"
+#include "target/ppc/cpu.h"
#include "qapi/error.h"
#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/isa/isa.h"
#include "hw/ppc/pnv.h"
#include "hw/ppc/pnv_lpc.h"
};
/* OPB Master LS registers */
+#define OPB_MASTER_LS_ROUTE0 0x8
+#define OPB_MASTER_LS_ROUTE1 0xC
#define OPB_MASTER_LS_IRQ_STAT 0x50
#define OPB_MASTER_IRQ_LPC 0x00000800
#define OPB_MASTER_LS_IRQ_MASK 0x54
#define ISA_IO_SIZE 0x00010000
#define ISA_MEM_SIZE 0x10000000
+#define ISA_FW_SIZE 0x10000000
#define LPC_IO_OPB_ADDR 0xd0010000
#define LPC_IO_OPB_SIZE 0x00010000
#define LPC_MEM_OPB_ADDR 0xe0010000
#define LPC_FW_OPB_SIZE 0x10000000
#define LPC_OPB_REGS_OPB_ADDR 0xc0010000
-#define LPC_OPB_REGS_OPB_SIZE 0x00002000
+#define LPC_OPB_REGS_OPB_SIZE 0x00000060
+#define LPC_OPB_REGS_OPBA_ADDR 0xc0011000
+#define LPC_OPB_REGS_OPBA_SIZE 0x00000008
#define LPC_HC_REGS_OPB_ADDR 0xc0012000
-#define LPC_HC_REGS_OPB_SIZE 0x00001000
+#define LPC_HC_REGS_OPB_SIZE 0x00000100
-
-/*
- * 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)
+static int pnv_lpc_dt_xscom(PnvXScomInterface *dev, void *fdt, int xscom_offset)
{
const char compat[] = "ibm,power8-lpc\0ibm,lpc";
char *name;
_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;
}
+/* POWER9 only */
+int pnv_dt_lpc(PnvChip *chip, void *fdt, int root_offset)
+{
+ const char compat[] = "ibm,power9-lpcm-opb\0simple-bus";
+ const char lpc_compat[] = "ibm,power9-lpc\0ibm,lpc";
+ char *name;
+ int offset, lpcm_offset;
+ uint64_t lpcm_addr = PNV9_LPCM_BASE(chip);
+ uint32_t opb_ranges[8] = { 0,
+ cpu_to_be32(lpcm_addr >> 32),
+ cpu_to_be32((uint32_t)lpcm_addr),
+ cpu_to_be32(PNV9_LPCM_SIZE / 2),
+ cpu_to_be32(PNV9_LPCM_SIZE / 2),
+ cpu_to_be32(lpcm_addr >> 32),
+ cpu_to_be32(PNV9_LPCM_SIZE / 2),
+ cpu_to_be32(PNV9_LPCM_SIZE / 2),
+ };
+ uint32_t opb_reg[4] = { cpu_to_be32(lpcm_addr >> 32),
+ cpu_to_be32((uint32_t)lpcm_addr),
+ cpu_to_be32(PNV9_LPCM_SIZE >> 32),
+ cpu_to_be32((uint32_t)PNV9_LPCM_SIZE),
+ };
+ uint32_t reg[2];
+
+ /*
+ * OPB bus
+ */
+ name = g_strdup_printf("lpcm-opb@%"PRIx64, lpcm_addr);
+ lpcm_offset = fdt_add_subnode(fdt, root_offset, name);
+ _FDT(lpcm_offset);
+ g_free(name);
+
+ _FDT((fdt_setprop(fdt, lpcm_offset, "reg", opb_reg, sizeof(opb_reg))));
+ _FDT((fdt_setprop_cell(fdt, lpcm_offset, "#address-cells", 1)));
+ _FDT((fdt_setprop_cell(fdt, lpcm_offset, "#size-cells", 1)));
+ _FDT((fdt_setprop(fdt, lpcm_offset, "compatible", compat, sizeof(compat))));
+ _FDT((fdt_setprop_cell(fdt, lpcm_offset, "ibm,chip-id", chip->chip_id)));
+ _FDT((fdt_setprop(fdt, lpcm_offset, "ranges", opb_ranges,
+ sizeof(opb_ranges))));
+
+ /*
+ * OPB Master registers
+ */
+ name = g_strdup_printf("opb-master@%x", LPC_OPB_REGS_OPB_ADDR);
+ offset = fdt_add_subnode(fdt, lpcm_offset, name);
+ _FDT(offset);
+ g_free(name);
+
+ reg[0] = cpu_to_be32(LPC_OPB_REGS_OPB_ADDR);
+ reg[1] = cpu_to_be32(LPC_OPB_REGS_OPB_SIZE);
+ _FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg))));
+ _FDT((fdt_setprop_string(fdt, offset, "compatible",
+ "ibm,power9-lpcm-opb-master")));
+
+ /*
+ * OPB arbitrer registers
+ */
+ name = g_strdup_printf("opb-arbitrer@%x", LPC_OPB_REGS_OPBA_ADDR);
+ offset = fdt_add_subnode(fdt, lpcm_offset, name);
+ _FDT(offset);
+ g_free(name);
+
+ reg[0] = cpu_to_be32(LPC_OPB_REGS_OPBA_ADDR);
+ reg[1] = cpu_to_be32(LPC_OPB_REGS_OPBA_SIZE);
+ _FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg))));
+ _FDT((fdt_setprop_string(fdt, offset, "compatible",
+ "ibm,power9-lpcm-opb-arbiter")));
+
+ /*
+ * LPC Host Controller registers
+ */
+ name = g_strdup_printf("lpc-controller@%x", LPC_HC_REGS_OPB_ADDR);
+ offset = fdt_add_subnode(fdt, lpcm_offset, name);
+ _FDT(offset);
+ g_free(name);
+
+ reg[0] = cpu_to_be32(LPC_HC_REGS_OPB_ADDR);
+ reg[1] = cpu_to_be32(LPC_HC_REGS_OPB_SIZE);
+ _FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg))));
+ _FDT((fdt_setprop_string(fdt, offset, "compatible",
+ "ibm,power9-lpc-controller")));
+
+ name = g_strdup_printf("lpc@0");
+ offset = fdt_add_subnode(fdt, lpcm_offset, name);
+ _FDT(offset);
+ g_free(name);
+ _FDT((fdt_setprop_cell(fdt, offset, "#address-cells", 2)));
+ _FDT((fdt_setprop_cell(fdt, offset, "#size-cells", 1)));
+ _FDT((fdt_setprop(fdt, offset, "compatible", lpc_compat,
+ sizeof(lpc_compat))));
+
+ return 0;
+}
+
/*
* These read/write handlers of the OPB address space should be common
* with the P9 LPC Controller which uses direct MMIOs.
static bool opb_read(PnvLpcController *lpc, uint32_t addr, uint8_t *data,
int sz)
{
- bool success;
-
/* XXX Handle access size limits and FW read caching here */
- success = !address_space_rw(&lpc->opb_as, addr, MEMTXATTRS_UNSPECIFIED,
- data, sz, false);
-
- return success;
+ return !address_space_rw(&lpc->opb_as, addr, MEMTXATTRS_UNSPECIFIED,
+ data, sz, false);
}
static bool opb_write(PnvLpcController *lpc, uint32_t addr, uint8_t *data,
int sz)
{
- bool success;
-
/* XXX Handle access size limits here */
- success = !address_space_rw(&lpc->opb_as, addr, MEMTXATTRS_UNSPECIFIED,
- data, sz, true);
-
- return success;
+ return !address_space_rw(&lpc->opb_as, addr, MEMTXATTRS_UNSPECIFIED,
+ data, sz, true);
}
-#define ECCB_CTL_READ (1ull << (63 - 15))
+#define ECCB_CTL_READ PPC_BIT(15)
#define ECCB_CTL_SZ_LSH (63 - 7)
-#define ECCB_CTL_SZ_MASK (0xfull << ECCB_CTL_SZ_LSH)
-#define ECCB_CTL_ADDR_MASK 0xffffffffu;
+#define ECCB_CTL_SZ_MASK PPC_BITMASK(4, 7)
+#define ECCB_CTL_ADDR_MASK PPC_BITMASK(32, 63)
-#define ECCB_STAT_OP_DONE (1ull << (63 - 52))
-#define ECCB_STAT_OP_ERR (1ull << (63 - 52))
+#define ECCB_STAT_OP_DONE PPC_BIT(52)
+#define ECCB_STAT_OP_ERR PPC_BIT(52)
#define ECCB_STAT_RD_DATA_LSH (63 - 37)
#define ECCB_STAT_RD_DATA_MASK (0xffffffff << ECCB_STAT_RD_DATA_LSH)
/* XXX Check for magic bits at the top, addr size etc... */
unsigned int sz = (cmd & ECCB_CTL_SZ_MASK) >> ECCB_CTL_SZ_LSH;
uint32_t opb_addr = cmd & ECCB_CTL_ADDR_MASK;
- uint8_t data[4];
+ uint8_t data[8];
bool success;
+ if (sz > sizeof(data)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ECCB: invalid operation at @0x%08x size %d\n", opb_addr, sz);
+ return;
+ }
+
if (cmd & ECCB_CTL_READ) {
success = opb_read(lpc, opb_addr, data, sz);
if (success) {
.endianness = DEVICE_BIG_ENDIAN,
};
+static uint64_t pnv_lpc_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PnvLpcController *lpc = PNV_LPC(opaque);
+ uint64_t val = 0;
+ uint32_t opb_addr = addr & ECCB_CTL_ADDR_MASK;
+ MemTxResult result;
+
+ switch (size) {
+ case 4:
+ val = address_space_ldl(&lpc->opb_as, opb_addr, MEMTXATTRS_UNSPECIFIED,
+ &result);
+ break;
+ case 1:
+ val = address_space_ldub(&lpc->opb_as, opb_addr, MEMTXATTRS_UNSPECIFIED,
+ &result);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "OPB read failed at @0x%"
+ HWADDR_PRIx " invalid size %d\n", addr, size);
+ return 0;
+ }
+
+ if (result != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR, "OPB read failed at @0x%"
+ HWADDR_PRIx "\n", addr);
+ }
+
+ return val;
+}
+
+static void pnv_lpc_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PnvLpcController *lpc = PNV_LPC(opaque);
+ uint32_t opb_addr = addr & ECCB_CTL_ADDR_MASK;
+ MemTxResult result;
+
+ switch (size) {
+ case 4:
+ address_space_stl(&lpc->opb_as, opb_addr, val, MEMTXATTRS_UNSPECIFIED,
+ &result);
+ break;
+ case 1:
+ address_space_stb(&lpc->opb_as, opb_addr, val, MEMTXATTRS_UNSPECIFIED,
+ &result);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "OPB write failed at @0x%"
+ HWADDR_PRIx " invalid size %d\n", addr, size);
+ return;
+ }
+
+ if (result != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR, "OPB write failed at @0x%"
+ HWADDR_PRIx "\n", addr);
+ }
+}
+
+static const MemoryRegionOps pnv_lpc_mmio_ops = {
+ .read = pnv_lpc_mmio_read,
+ .write = pnv_lpc_mmio_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static void pnv_lpc_eval_irqs(PnvLpcController *lpc)
+{
+ bool lpc_to_opb_irq = false;
+ PnvLpcClass *plc = PNV_LPC_GET_CLASS(lpc);
+
+ /* 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, plc->psi_irq, lpc->opb_irq_stat != 0);
+}
+
static uint64_t lpc_hc_read(void *opaque, hwaddr addr, unsigned size)
{
PnvLpcController *lpc = opaque;
val = lpc->lpc_hc_error_addr;
break;
default:
- qemu_log_mask(LOG_UNIMP, "LPC HC Unimplemented register: Ox%"
+ qemu_log_mask(LOG_UNIMP, "LPC HC Unimplemented register: 0x%"
HWADDR_PRIx "\n", addr);
}
return 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;
default:
- qemu_log_mask(LOG_UNIMP, "LPC HC Unimplemented register: Ox%"
+ qemu_log_mask(LOG_UNIMP, "LPC HC Unimplemented register: 0x%"
HWADDR_PRIx "\n", addr);
}
}
uint64_t val = 0xfffffffffffffffful;
switch (addr) {
+ case OPB_MASTER_LS_ROUTE0: /* TODO */
+ val = lpc->opb_irq_route0;
+ break;
+ case OPB_MASTER_LS_ROUTE1: /* TODO */
+ val = lpc->opb_irq_route1;
+ break;
case OPB_MASTER_LS_IRQ_STAT:
val = lpc->opb_irq_stat;
break;
val = lpc->opb_irq_input;
break;
default:
- qemu_log_mask(LOG_UNIMP, "OPB MASTER Unimplemented register: Ox%"
+ qemu_log_mask(LOG_UNIMP, "OPBM: read on unimplemented register: 0x%"
HWADDR_PRIx "\n", addr);
}
PnvLpcController *lpc = opaque;
switch (addr) {
+ case OPB_MASTER_LS_ROUTE0: /* TODO */
+ lpc->opb_irq_route0 = val;
+ break;
+ case OPB_MASTER_LS_ROUTE1: /* TODO */
+ lpc->opb_irq_route1 = val;
+ break;
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 */
break;
default:
- qemu_log_mask(LOG_UNIMP, "OPB MASTER Unimplemented register: Ox%"
- HWADDR_PRIx "\n", addr);
+ qemu_log_mask(LOG_UNIMP, "OPBM: write on unimplemented register: 0x%"
+ HWADDR_PRIx " val=0x%08"PRIx64"\n", addr, val);
}
}
},
};
+static void pnv_lpc_power8_realize(DeviceState *dev, Error **errp)
+{
+ PnvLpcController *lpc = PNV_LPC(dev);
+ PnvLpcClass *plc = PNV_LPC_GET_CLASS(dev);
+ Error *local_err = NULL;
+
+ plc->parent_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ /* P8 uses a XSCOM region for LPC registers */
+ pnv_xscom_region_init(&lpc->xscom_regs, OBJECT(lpc),
+ &pnv_lpc_xscom_ops, lpc, "xscom-lpc",
+ PNV_XSCOM_LPC_SIZE);
+}
+
+static void pnv_lpc_power8_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass);
+ PnvLpcClass *plc = PNV_LPC_CLASS(klass);
+
+ dc->desc = "PowerNV LPC Controller POWER8";
+
+ xdc->dt_xscom = pnv_lpc_dt_xscom;
+
+ plc->psi_irq = PSIHB_IRQ_LPC_I2C;
+
+ device_class_set_parent_realize(dc, pnv_lpc_power8_realize,
+ &plc->parent_realize);
+}
+
+static const TypeInfo pnv_lpc_power8_info = {
+ .name = TYPE_PNV8_LPC,
+ .parent = TYPE_PNV_LPC,
+ .instance_size = sizeof(PnvLpcController),
+ .class_init = pnv_lpc_power8_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_PNV_XSCOM_INTERFACE },
+ { }
+ }
+};
+
+static void pnv_lpc_power9_realize(DeviceState *dev, Error **errp)
+{
+ PnvLpcController *lpc = PNV_LPC(dev);
+ PnvLpcClass *plc = PNV_LPC_GET_CLASS(dev);
+ Error *local_err = NULL;
+
+ plc->parent_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ /* P9 uses a MMIO region */
+ memory_region_init_io(&lpc->xscom_regs, OBJECT(lpc), &pnv_lpc_mmio_ops,
+ lpc, "lpcm", PNV9_LPCM_SIZE);
+}
+
+static void pnv_lpc_power9_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PnvLpcClass *plc = PNV_LPC_CLASS(klass);
+
+ dc->desc = "PowerNV LPC Controller POWER9";
+
+ plc->psi_irq = PSIHB9_IRQ_LPCHC;
+
+ device_class_set_parent_realize(dc, pnv_lpc_power9_realize,
+ &plc->parent_realize);
+}
+
+static const TypeInfo pnv_lpc_power9_info = {
+ .name = TYPE_PNV9_LPC,
+ .parent = TYPE_PNV_LPC,
+ .instance_size = sizeof(PnvLpcController),
+ .class_init = pnv_lpc_power9_class_init,
+};
+
static void pnv_lpc_realize(DeviceState *dev, Error **errp)
{
PnvLpcController *lpc = PNV_LPC(dev);
+ Object *obj;
+ Error *local_err = NULL;
+
+ obj = object_property_get_link(OBJECT(dev), "psi", &local_err);
+ if (!obj) {
+ error_propagate(errp, local_err);
+ error_prepend(errp, "required link 'psi' not found: ");
+ return;
+ }
+ /* The LPC controller needs PSI to generate interrupts */
+ lpc->psi = PNV_PSI(obj);
/* Reg inits */
lpc->lpc_hc_fw_rd_acc_size = LPC_HC_FW_RD_4B;
*/
memory_region_init(&lpc->isa_io, OBJECT(dev), "isa-io", ISA_IO_SIZE);
memory_region_init(&lpc->isa_mem, OBJECT(dev), "isa-mem", ISA_MEM_SIZE);
+ memory_region_init(&lpc->isa_fw, OBJECT(dev), "isa-fw", ISA_FW_SIZE);
/* Create windows from the OPB space to the ISA space */
memory_region_init_alias(&lpc->opb_isa_io, OBJECT(dev), "lpc-isa-io",
memory_region_add_subregion(&lpc->opb_mr, LPC_MEM_OPB_ADDR,
&lpc->opb_isa_mem);
memory_region_init_alias(&lpc->opb_isa_fw, OBJECT(dev), "lpc-isa-fw",
- &lpc->isa_mem, 0, LPC_FW_OPB_SIZE);
+ &lpc->isa_fw, 0, LPC_FW_OPB_SIZE);
memory_region_add_subregion(&lpc->opb_mr, LPC_FW_OPB_ADDR,
&lpc->opb_isa_fw);
"lpc-hc", LPC_HC_REGS_OPB_SIZE);
memory_region_add_subregion(&lpc->opb_mr, LPC_HC_REGS_OPB_ADDR,
&lpc->lpc_hc_regs);
-
- /* XScom region for LPC registers */
- pnv_xscom_region_init(&lpc->xscom_regs, OBJECT(dev),
- &pnv_lpc_xscom_ops, lpc, "xscom-lpc",
- PNV_XSCOM_LPC_SIZE);
}
static void pnv_lpc_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
- PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass);
-
- xdc->populate = pnv_lpc_populate;
dc->realize = pnv_lpc_realize;
+ dc->desc = "PowerNV LPC Controller";
}
static const TypeInfo pnv_lpc_info = {
.name = TYPE_PNV_LPC,
.parent = TYPE_DEVICE,
- .instance_size = sizeof(PnvLpcController),
.class_init = pnv_lpc_class_init,
- .interfaces = (InterfaceInfo[]) {
- { TYPE_PNV_XSCOM_INTERFACE },
- { }
- }
+ .class_size = sizeof(PnvLpcClass),
+ .abstract = true,
};
static void pnv_lpc_register_types(void)
{
type_register_static(&pnv_lpc_info);
+ type_register_static(&pnv_lpc_power8_info);
+ type_register_static(&pnv_lpc_power9_info);
}
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 = PNV_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);
+ }
+}
+
+ISABus *pnv_lpc_isa_create(PnvLpcController *lpc, bool use_cpld, Error **errp)
+{
+ Error *local_err = NULL;
+ ISABus *isa_bus;
+ qemu_irq *irqs;
+ qemu_irq_handler handler;
+
+ /* let isa_bus_new() create its own bridge on SysBus otherwise
+ * devices speficied on the command line won't find the bus and
+ * will fail to create.
+ */
+ isa_bus = isa_bus_new(NULL, &lpc->isa_mem, &lpc->isa_io, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ /* 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 (use_cpld) {
+ handler = pnv_lpc_isa_irq_handler_cpld;
+ } else {
+ handler = pnv_lpc_isa_irq_handler;
+ }
+
+ irqs = qemu_allocate_irqs(handler, lpc, ISA_NUM_IRQS);
+
+ isa_bus_irqs(isa_bus, irqs);
+ return isa_bus;
+}