diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
index 435af7cf4e48..474e791bbe50 100644
--- a/hw/ppc/pnv.c
+++ b/hw/ppc/pnv.c
@@ -645,6 +645,15 @@ static void pnv_chip_init(Object *obj)
     object_property_add_child(obj, "lpc", OBJECT(&chip->lpc), NULL);
 }
 
+static void pnv_chip_icp_realize(PnvChip *chip, Error **errp)
+{
+    char *name;
+
+    name = g_strdup_printf("icp-%x", chip->chip_id);
+    memory_region_init(&chip->icp_mmio, OBJECT(chip), name, PNV_ICP_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(chip), &chip->icp_mmio);
+}
+
 static void pnv_chip_realize(DeviceState *dev, Error **errp)
 {
     PnvChip *chip = PNV_CHIP(dev);
@@ -667,6 +676,13 @@ static void pnv_chip_realize(DeviceState *dev, Error **errp)
     }
     sysbus_mmio_map(SYS_BUS_DEVICE(chip), 0, PNV_XSCOM_BASE(chip));
 
+    /* Interrupt Controller */
+    pnv_chip_icp_realize(chip, &error);
+    if (error) {
+        error_propagate(errp, error);
+        return;
+    }
+
     /* Cores */
     pnv_chip_core_sanitize(chip, &error);
     if (error) {
diff --git a/hw/ppc/pnv_core.c b/hw/ppc/pnv_core.c
index 76ce854b0c40..426b18b59ed8 100644
--- a/hw/ppc/pnv_core.c
+++ b/hw/ppc/pnv_core.c
@@ -26,6 +26,128 @@
 #include "hw/ppc/pnv_core.h"
 #include "hw/ppc/pnv_xscom.h"
 
+static uint64_t pnv_core_icp_read(void *opaque, hwaddr addr, unsigned width)
+{
+    ICPState *icp = opaque;
+    bool byte0 = (width == 1 && (addr & 0x3) == 0);
+    uint64_t val = 0xffffffff;
+
+    switch (addr & 0xffc) {
+    case 0: /* poll */
+        val = icp_ipoll(icp, NULL);
+        if (byte0) {
+            val >>= 24;
+        } else if (width != 4) {
+            goto bad_access;
+        }
+        break;
+    case 4: /* xirr */
+        if (byte0) {
+            val = icp_ipoll(icp, NULL) >> 24;
+        } else if (width == 4) {
+            val = icp_accept(icp);
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 12:
+        if (byte0) {
+            val = icp->mfrr;
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 16:
+        if (width == 4) {
+            val = icp->links[0];
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 20:
+        if (width == 4) {
+            val = icp->links[1];
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 24:
+        if (width == 4) {
+            val = icp->links[2];
+        } else {
+            goto bad_access;
+        }
+        break;
+    default:
+bad_access:
+        qemu_log_mask(LOG_GUEST_ERROR, "XICS: Bad ICP access 0x%"
+                      HWADDR_PRIx"/%d\n", addr, width);
+    }
+
+    return val;
+}
+
+static void pnv_core_icp_write(void *opaque, hwaddr addr, uint64_t val,
+                              unsigned width)
+{
+    ICPState *icp = opaque;
+    bool byte0 = (width == 1 && (addr & 0x3) == 0);
+
+    switch (addr & 0xffc) {
+    case 4: /* xirr */
+        if (byte0) {
+            icp_set_cppr(icp, val);
+        } else if (width == 4) {
+            icp_eoi(icp, val);
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 12:
+        if (byte0) {
+            icp_set_mfrr(icp, val);
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 16:
+        if (width == 4) {
+            icp->links[0] = val;
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 20:
+        if (width == 4) {
+            icp->links[1] = val;
+        } else {
+            goto bad_access;
+        }
+        break;
+    case 24:
+        if (width == 4) {
+            icp->links[2] = val;
+        } else {
+            goto bad_access;
+        }
+        break;
+    default:
+bad_access:
+        qemu_log_mask(LOG_GUEST_ERROR, "XICS: Bad ICP access 0x%"
+                      HWADDR_PRIx"/%d\n", addr, width);
+    }
+}
+
+static const MemoryRegionOps pnv_core_icp_ops = {
+    .read = pnv_core_icp_read,
+    .write = pnv_core_icp_write,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 4,
+    .impl.min_access_size = 1,
+    .impl.max_access_size = 4,
+    .endianness = DEVICE_BIG_ENDIAN,
+};
+
 static void powernv_cpu_reset(void *opaque)
 {
     PowerPCCPU *cpu = opaque;
@@ -129,6 +251,14 @@ static void pnv_core_realize_child(Object *child, Error **errp)
     }
 }
 
+static ICPState *xics_get_icp_per_pir(XICSState* xics, int pir)
+{
+    int index = xics_get_cpu_index_by_pir(pir);
+    assert(index != -1);
+
+    return &xics->ss[index];
+}
+
 static void pnv_core_realize(DeviceState *dev, Error **errp)
 {
     PnvCore *pc = PNV_CORE(OBJECT(dev));
@@ -140,6 +270,7 @@ static void pnv_core_realize(DeviceState *dev, Error **errp)
     void *obj;
     int i, j;
     char name[32];
+    PnvMachineState *pnv = POWERNV_MACHINE(qdev_get_machine());
 
     pc->threads = g_malloc0(size * cc->nr_threads);
     for (i = 0; i < cc->nr_threads; i++) {
@@ -169,8 +300,15 @@ static void pnv_core_realize(DeviceState *dev, Error **errp)
     snprintf(name, sizeof(name), "xscom-core.%d", cc->core_id);
     pnv_xscom_region_init(&pc->xscom_regs, OBJECT(dev), &pnv_core_xscom_ops,
                           pc, name, PNV_XSCOM_EX_CORE_SIZE);
-    return;
 
+    pc->icp_mmios = g_new0(MemoryRegion, cc->nr_threads);
+    for (i = 0; i < cc->nr_threads; i++) {
+        ICPState *icp = xics_get_icp_per_pir(&pnv->xics, pc->pir + i);
+        snprintf(name, sizeof(name), "icp-core.%d", cc->core_id);
+        memory_region_init_io(&pc->icp_mmios[i], OBJECT(dev),
+                              &pnv_core_icp_ops, icp, name, 0x1000);
+    }
+    return;
 err:
     while (--i >= 0) {
         obj = pc->threads + i * size;
diff --git a/include/hw/ppc/pnv.h b/include/hw/ppc/pnv.h
index cb9499728fda..2b1cb04b4792 100644
--- a/include/hw/ppc/pnv.h
+++ b/include/hw/ppc/pnv.h
@@ -55,6 +55,7 @@ typedef struct PnvChip {
     MemoryRegion xscom_mmio;
     MemoryRegion xscom;
     AddressSpace xscom_as;
+    MemoryRegion icp_mmio;
 
     PnvLpcController lpc;
 } PnvChip;
@@ -128,4 +129,23 @@ typedef struct PnvMachineState {
 #define PNV_XSCOM_BASE(chip)                                            \
     (chip->xscom_base + ((uint64_t)(chip)->chip_id) * PNV_XSCOM_SIZE)
 
+/*
+ * XSCOM 0x20109CA defines the ICP BAR:
+ *
+ * 0:29   : bits 14 to 43 of address to define 1 MB region.
+ * 30     : 1 to enable ICP to receive loads/stores against its BAR region
+ * 31:63  : Constant 0
+ *
+ * Usually defined as :
+ *
+ *      0xffffe00200000000 -> 0x0003ffff80000000
+ *      0xffffe00600000000 -> 0x0003ffff80100000
+ *      0xffffe02200000000 -> 0x0003ffff80800000
+ *      0xffffe02600000000 -> 0x0003ffff80900000
+ *
+ * TODO: make a macro using the chip hw id
+ */
+#define PNV_ICP_BASE(chip)   0x0003ffff80000000ull
+#define PNV_ICP_SIZE         0x0000000000100000ull
+
 #endif /* _PPC_PNV_H */
diff --git a/include/hw/ppc/pnv_core.h b/include/hw/ppc/pnv_core.h
index 2955a41c901f..5521b07b3b1f 100644
--- a/include/hw/ppc/pnv_core.h
+++ b/include/hw/ppc/pnv_core.h
@@ -38,6 +38,8 @@ typedef struct PnvCore {
     uint32_t pir;
 
     MemoryRegion xscom_regs;
+
+    MemoryRegion *icp_mmios;
 } PnvCore;
 
 typedef struct PnvCoreClass {
diff --git a/include/hw/ppc/xics.h b/include/hw/ppc/xics.h
index c58a09916f37..3b5e976f8a1a 100644
--- a/include/hw/ppc/xics.h
+++ b/include/hw/ppc/xics.h
@@ -120,6 +120,11 @@ struct ICPState {
     bool cap_irq_xics_enabled;
 
     XICSState *xics;
+
+    /*
+     * for XICSNative (not used by Linux).
+     */
+    uint32_t links[3];
 };
 
 #define TYPE_ICS_BASE "ics-base"
