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.c316
1 files changed, 284 insertions, 32 deletions
diff --git a/hw/ppc/pnv_lpc.c b/hw/ppc/pnv_lpc.c
index 172a915cfc..641e2046db 100644
--- a/hw/ppc/pnv_lpc.c
+++ b/hw/ppc/pnv_lpc.c
@@ -39,6 +39,8 @@ enum {
};
/* 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
@@ -89,10 +91,11 @@ enum {
#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
static int pnv_lpc_dt_xscom(PnvXScomInterface *dev, void *fdt, int xscom_offset)
{
@@ -117,6 +120,100 @@ static int pnv_lpc_dt_xscom(PnvXScomInterface *dev, void *fdt, int xscom_offset)
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.
@@ -241,9 +338,78 @@ static const MemoryRegionOps pnv_lpc_xscom_ops = {
.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) {
@@ -266,7 +432,7 @@ static void pnv_lpc_eval_irqs(PnvLpcController *lpc)
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);
+ 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)
@@ -294,7 +460,7 @@ static uint64_t lpc_hc_read(void *opaque, hwaddr addr, unsigned size)
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;
@@ -332,7 +498,7 @@ static void lpc_hc_write(void *opaque, hwaddr addr, uint64_t val,
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);
}
}
@@ -357,6 +523,12 @@ static uint64_t opb_master_read(void *opaque, hwaddr addr, unsigned size)
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;
@@ -370,7 +542,7 @@ static uint64_t opb_master_read(void *opaque, hwaddr addr, unsigned size)
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);
}
@@ -383,6 +555,12 @@ static void opb_master_write(void *opaque, hwaddr 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);
@@ -399,8 +577,8 @@ static void opb_master_write(void *opaque, hwaddr addr,
/* 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);
}
}
@@ -418,11 +596,102 @@ static const MemoryRegionOps opb_master_ops = {
},
};
+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 *error = NULL;
+ 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;
@@ -462,46 +731,29 @@ static void pnv_lpc_realize(DeviceState *dev, Error **errp)
"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);
-
- /* 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)
{
DeviceClass *dc = DEVICE_CLASS(klass);
- PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass);
-
- xdc->dt_xscom = pnv_lpc_dt_xscom;
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)