diff --git a/drivers/arm/gic/v3/gic600_multichip.c b/drivers/arm/gic/v3/gic600_multichip.c
new file mode 100644
index 0000000000000000000000000000000000000000..c62c3f5dd404c1a30c8fa40afbac7d181ff4bcac
--- /dev/null
+++ b/drivers/arm/gic/v3/gic600_multichip.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2019, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * GIC-600 driver extension for multichip setup
+ */
+
+#include <assert.h>
+#include <common/debug.h>
+
+#include <drivers/arm/gicv3.h>
+#include <drivers/arm/gic600_multichip.h>
+
+#include "gic600_multichip_private.h"
+#include "../common/gic_common_private.h"
+
+#warning "GIC-600 Multichip driver is currently experimental and the API may change in future."
+
+/*******************************************************************************
+ * GIC-600 multichip operation related helper functions
+ ******************************************************************************/
+static void gicd_dchipr_wait_for_power_update_progress(uintptr_t base)
+{
+	unsigned int retry = GICD_PUP_UPDATE_RETRIES;
+
+	while ((read_gicd_dchipr(base) & GICD_DCHIPR_PUP_BIT) != 0U) {
+		if (retry-- == 0) {
+			ERROR("GIC-600 connection to Routing Table Owner timed "
+					 "out\n");
+			panic();
+		}
+	}
+}
+
+/*******************************************************************************
+ * Sets up the routing table owner.
+ ******************************************************************************/
+static void set_gicd_dchipr_rt_owner(uintptr_t base, unsigned int rt_owner)
+{
+	/*
+	 * Ensure that Group enables in GICD_CTLR are disabled and no pending
+	 * register writes to GICD_CTLR.
+	 */
+	if ((gicd_read_ctlr(base) &
+			(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1S_BIT |
+			 CTLR_ENABLE_G1NS_BIT | GICD_CTLR_RWP_BIT)) != 0) {
+		ERROR("GICD_CTLR group interrupts are either enabled or have "
+				"pending writes. Cannot set RT owner.\n");
+		panic();
+	}
+
+	/* Poll till PUP is zero before intiating write */
+	gicd_dchipr_wait_for_power_update_progress(base);
+
+	write_gicd_dchipr(base, read_gicd_dchipr(base) |
+			(rt_owner << GICD_DCHIPR_RT_OWNER_SHIFT));
+
+	/* Poll till PUP is zero to ensure write is complete */
+	gicd_dchipr_wait_for_power_update_progress(base);
+}
+
+/*******************************************************************************
+ * Configures the Chip Register to make connections to GICDs on
+ * a multichip platform.
+ ******************************************************************************/
+static void set_gicd_chipr_n(uintptr_t base,
+				unsigned int chip_id,
+				uint64_t chip_addr,
+				unsigned int spi_id_min,
+				unsigned int spi_id_max)
+{
+	unsigned int spi_block_min, spi_blocks;
+	uint64_t chipr_n_val;
+
+	/*
+	 * Ensure that group enables in GICD_CTLR are disabled and no pending
+	 * register writes to GICD_CTLR.
+	 */
+	if ((gicd_read_ctlr(base) &
+			(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1S_BIT |
+			 CTLR_ENABLE_G1NS_BIT | GICD_CTLR_RWP_BIT)) != 0) {
+		ERROR("GICD_CTLR group interrupts are either enabled or have "
+				"pending writes. Cannot set CHIPR register.\n");
+		panic();
+	}
+
+	/*
+	 * spi_id_min and spi_id_max of value 0 is used to intidicate that the
+	 * chip doesn't own any SPI block. Re-assign min and max values as SPI
+	 * id starts from 32.
+	 */
+	if (spi_id_min == 0 && spi_id_max == 0) {
+		spi_id_min = GIC600_SPI_ID_MIN;
+		spi_id_max = GIC600_SPI_ID_MIN;
+	}
+
+	spi_block_min = SPI_BLOCK_MIN_VALUE(spi_id_min);
+	spi_blocks    = SPI_BLOCKS_VALUE(spi_id_min, spi_id_max);
+
+	chipr_n_val = (GICD_CHIPR_VALUE(chip_addr, spi_block_min, spi_blocks)) |
+		GICD_CHIPRx_SOCKET_STATE;
+
+	/*
+	 * Wait for DCHIPR.PUP to be zero before commencing writes to
+	 * GICD_CHIPRx.
+	 */
+	gicd_dchipr_wait_for_power_update_progress(base);
+
+	/*
+	 * Assign chip addr, spi min block, number of spi blocks and bring chip
+	 * online by setting SocketState.
+	 */
+	write_gicd_chipr_n(base, chip_id, chipr_n_val);
+
+	/*
+	 * Poll until DCHIP.PUP is zero to verify connection to rt_owner chip
+	 * is complete.
+	 */
+	gicd_dchipr_wait_for_power_update_progress(base);
+
+	/*
+	 * Ensure that write to GICD_CHIPRx is successful and the chip_n came
+	 * online.
+	 */
+	if (read_gicd_chipr_n(base, chip_id) != chipr_n_val) {
+		ERROR("GICD_CHIPR%u write failed\n", chip_id);
+		panic();
+	}
+
+	/* Ensure that chip is in consistent state */
+	if (((read_gicd_chipsr(base) & GICD_CHIPSR_RTS_MASK) >>
+				GICD_CHIPSR_RTS_SHIFT) !=
+			GICD_CHIPSR_RTS_STATE_CONSISTENT) {
+		ERROR("Chip %u routing table is not in consistent state\n",
+				chip_id);
+		panic();
+	}
+}
+
+/*******************************************************************************
+ * Validates the GIC-600 Multichip data structure passed by the platform.
+ ******************************************************************************/
+static void gic600_multichip_validate_data(
+		struct gic600_multichip_data *multichip_data)
+{
+	unsigned int i, spi_id_min, spi_id_max, blocks_of_32;
+	unsigned int multichip_spi_blocks = 0;
+
+	assert(multichip_data != NULL);
+
+	if (multichip_data->chip_count > GIC600_MAX_MULTICHIP) {
+		ERROR("GIC-600 Multichip count should not exceed %d\n",
+				GIC600_MAX_MULTICHIP);
+		panic();
+	}
+
+	for (i = 0; i < multichip_data->chip_count; i++) {
+		spi_id_min = multichip_data->spi_ids[i][SPI_MIN_INDEX];
+		spi_id_max = multichip_data->spi_ids[i][SPI_MAX_INDEX];
+
+		if ((spi_id_min != 0) || (spi_id_max != 0)) {
+
+			/* SPI IDs range check */
+			if (!(spi_id_min >= GIC600_SPI_ID_MIN) ||
+			    !(spi_id_max < GIC600_SPI_ID_MAX) ||
+			    !(spi_id_min <= spi_id_max) ||
+			    !((spi_id_max - spi_id_min + 1) % 32 == 0)) {
+				ERROR("Invalid SPI IDs {%u, %u} passed for "
+						"Chip %u\n", spi_id_min,
+						spi_id_max, i);
+				panic();
+			}
+
+			/* SPI IDs overlap check */
+			blocks_of_32 = BLOCKS_OF_32(spi_id_min, spi_id_max);
+			if ((multichip_spi_blocks & blocks_of_32) != 0) {
+				ERROR("SPI IDs of Chip %u overlapping\n", i);
+				panic();
+			}
+			multichip_spi_blocks |= blocks_of_32;
+		}
+	}
+}
+
+/*******************************************************************************
+ * Intialize GIC-600 Multichip operation.
+ ******************************************************************************/
+void gic600_multichip_init(struct gic600_multichip_data *multichip_data)
+{
+	unsigned int i;
+
+	gic600_multichip_validate_data(multichip_data);
+
+	INFO("GIC-600 Multichip driver is experimental\n");
+
+	/*
+	 * Ensure that G0/G1S/G1NS interrupts are disabled. This also ensures
+	 * that GIC-600 Multichip configuration is done first.
+	 */
+	if ((gicd_read_ctlr(multichip_data->rt_owner_base) &
+			(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1S_BIT |
+			 CTLR_ENABLE_G1NS_BIT | GICD_CTLR_RWP_BIT)) != 0) {
+		ERROR("GICD_CTLR group interrupts are either enabled or have "
+				"pending writes.\n");
+		panic();
+	}
+
+	/* Ensure that the routing table owner is in disconnected state */
+	if (((read_gicd_chipsr(multichip_data->rt_owner_base) &
+		GICD_CHIPSR_RTS_MASK) >> GICD_CHIPSR_RTS_SHIFT) !=
+			GICD_CHIPSR_RTS_STATE_DISCONNECTED) {
+		ERROR("GIC-600 routing table owner is not in disconnected "
+				"state to begin multichip configuration\n");
+		panic();
+	}
+
+	/* Initialize the GICD which is marked as routing table owner first */
+	set_gicd_dchipr_rt_owner(multichip_data->rt_owner_base,
+			multichip_data->rt_owner);
+
+	set_gicd_chipr_n(multichip_data->rt_owner_base, multichip_data->rt_owner,
+			multichip_data->chip_addrs[multichip_data->rt_owner],
+			multichip_data->
+			spi_ids[multichip_data->rt_owner][SPI_MIN_INDEX],
+			multichip_data->
+			spi_ids[multichip_data->rt_owner][SPI_MAX_INDEX]);
+
+	for (i = 0; i < multichip_data->chip_count; i++) {
+		if (i == multichip_data->rt_owner)
+			continue;
+
+		set_gicd_chipr_n(multichip_data->rt_owner_base, i,
+				multichip_data->chip_addrs[i],
+				multichip_data->spi_ids[i][SPI_MIN_INDEX],
+				multichip_data->spi_ids[i][SPI_MAX_INDEX]);
+	}
+}
diff --git a/drivers/arm/gic/v3/gic600_multichip_private.h b/drivers/arm/gic/v3/gic600_multichip_private.h
new file mode 100644
index 0000000000000000000000000000000000000000..b0217b6d411971b6124f9f034cae7d6dbcacb249
--- /dev/null
+++ b/drivers/arm/gic/v3/gic600_multichip_private.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2019, ARM Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef GIC600_MULTICHIP_PRIVATE_H
+#define GIC600_MULTICHIP_PRIVATE_H
+
+#include <drivers/arm/gic600_multichip.h>
+
+#include "gicv3_private.h"
+
+/* GIC600 GICD multichip related offsets */
+#define GICD_CHIPSR			U(0xC000)
+#define GICD_DCHIPR			U(0xC004)
+#define GICD_CHIPR			U(0xC008)
+
+/* GIC600 GICD multichip related masks */
+#define GICD_CHIPRx_PUP_BIT		BIT_64(1)
+#define GICD_CHIPRx_SOCKET_STATE	BIT_64(0)
+#define GICD_DCHIPR_PUP_BIT		BIT_32(0)
+#define GICD_CHIPSR_RTS_MASK		(BIT_32(4) | BIT_32(5))
+
+/* GIC600 GICD multichip related shifts */
+#define GICD_CHIPRx_ADDR_SHIFT		16
+#define GICD_CHIPRx_SPI_BLOCK_MIN_SHIFT	10
+#define GICD_CHIPRx_SPI_BLOCKS_SHIFT	5
+#define GICD_CHIPSR_RTS_SHIFT		4
+#define GICD_DCHIPR_RT_OWNER_SHIFT	4
+
+#define GICD_CHIPSR_RTS_STATE_DISCONNECTED	U(0)
+#define GICD_CHIPSR_RTS_STATE_UPDATING		U(1)
+#define GICD_CHIPSR_RTS_STATE_CONSISTENT	U(2)
+
+/* SPI interrupt id minimum and maximum range */
+#define GIC600_SPI_ID_MIN		32
+#define GIC600_SPI_ID_MAX		960
+
+/* Number of retries for PUP update */
+#define GICD_PUP_UPDATE_RETRIES		10000
+
+#define SPI_MIN_INDEX			0
+#define SPI_MAX_INDEX			1
+
+#define SPI_BLOCK_MIN_VALUE(spi_id_min) \
+			(((spi_id_min) - GIC600_SPI_ID_MIN) / \
+			GIC600_SPI_ID_MIN)
+#define SPI_BLOCKS_VALUE(spi_id_min, spi_id_max) \
+			(((spi_id_max) - (spi_id_min) + 1) / \
+			GIC600_SPI_ID_MIN)
+#define GICD_CHIPR_VALUE(chip_addr, spi_block_min, spi_blocks) \
+			(((chip_addr) << GICD_CHIPRx_ADDR_SHIFT) | \
+			((spi_block_min) << GICD_CHIPRx_SPI_BLOCK_MIN_SHIFT) | \
+			((spi_blocks) << GICD_CHIPRx_SPI_BLOCKS_SHIFT))
+
+/*
+ * Multichip data assertion macros
+ */
+/* Set bits from 0 to ((spi_id_max + 1) / 32) */
+#define SPI_BLOCKS_TILL_MAX(spi_id_max)	((1 << (((spi_id_max) + 1) >> 5)) - 1)
+/* Set bits from 0 to (spi_id_min / 32) */
+#define SPI_BLOCKS_TILL_MIN(spi_id_min)	((1 << ((spi_id_min) >> 5)) - 1)
+/* Set bits from (spi_id_min / 32) to ((spi_id_max + 1) / 32) */
+#define BLOCKS_OF_32(spi_id_min, spi_id_max) \
+					SPI_BLOCKS_TILL_MAX(spi_id_max) ^ \
+					SPI_BLOCKS_TILL_MIN(spi_id_min)
+
+/*******************************************************************************
+ * GIC-600 multichip operation related helper functions
+ ******************************************************************************/
+static inline uint32_t read_gicd_dchipr(uintptr_t base)
+{
+	return mmio_read_32(base + GICD_DCHIPR);
+}
+
+static inline uint64_t read_gicd_chipr_n(uintptr_t base, uint8_t n)
+{
+	return mmio_read_64(base + (GICD_CHIPR + (8U * n)));
+}
+
+static inline uint32_t read_gicd_chipsr(uintptr_t base)
+{
+	return mmio_read_32(base + GICD_CHIPSR);
+}
+
+static inline void write_gicd_dchipr(uintptr_t base, uint32_t val)
+{
+	mmio_write_32(base + GICD_DCHIPR, val);
+}
+
+static inline void write_gicd_chipr_n(uintptr_t base, uint8_t n, uint64_t val)
+{
+	mmio_write_64(base + (GICD_CHIPR + (8U * n)), val);
+}
+
+#endif /* GIC600_MULTICHIP_PRIVATE_H */
diff --git a/include/drivers/arm/gic600_multichip.h b/include/drivers/arm/gic600_multichip.h
new file mode 100644
index 0000000000000000000000000000000000000000..bda406bba399cd36a363b8568d2a15285caad8c4
--- /dev/null
+++ b/include/drivers/arm/gic600_multichip.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2019, ARM Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef GIC600_MULTICHIP_H
+#define GIC600_MULTICHIP_H
+
+#include <stdint.h>
+
+/*
+ * GIC-600 microarchitecture supports coherent multichip environments containing
+ * up to 16 chips.
+ */
+#define GIC600_MAX_MULTICHIP	16
+
+/* SPI IDs array consist of min and max ids */
+#define GIC600_SPI_IDS_SIZE	2
+
+/*******************************************************************************
+ * GIC-600 multichip data structure describes platform specific attributes
+ * related to GIC-600 multichip. Platform port is expected to define these
+ * attributes to initialize the multichip related registers and create
+ * successful connections between the GIC-600s in a multichip system.
+ *
+ * The 'rt_owner_base' field contains the base address of the GIC Distributor
+ * which owns the routing table.
+ *
+ * The 'rt_owner' field contains the chip number which owns the routing table.
+ * Chip number or chip_id starts from 0.
+ *
+ * The 'chip_count' field contains the total number of chips in a multichip
+ * system. This should match the number of entries in 'chip_addrs' and 'spi_ids'
+ * fields.
+ *
+ * The 'chip_addrs' field contains array of chip addresses. These addresses are
+ * implementation specific values.
+ *
+ * The 'spi_ids' field contains array of minimum and maximum SPI interrupt ids
+ * that each chip owns. Note that SPI interrupt ids can range from 32 to 960 and
+ * it should be group of 32 (i.e., SPI minimum and (SPI maximum + 1) should be
+ * a multiple of 32). If a chip doesn't own any SPI interrupts a value of {0, 0}
+ * should be passed.
+ ******************************************************************************/
+struct gic600_multichip_data {
+	uintptr_t rt_owner_base;
+	unsigned int rt_owner;
+	unsigned int chip_count;
+	uint64_t chip_addrs[GIC600_MAX_MULTICHIP];
+	unsigned int spi_ids[GIC600_MAX_MULTICHIP][GIC600_SPI_IDS_SIZE];
+};
+
+void gic600_multichip_init(struct gic600_multichip_data *multichip_data);
+#endif /* GIC600_MULTICHIP_H */
diff --git a/include/plat/arm/common/plat_arm.h b/include/plat/arm/common/plat_arm.h
index 07a46c518d6654da89f7a0b8d1e8d8830b989a80..c00a0412646556bf6413b0e58355d0c5a0f46125 100644
--- a/include/plat/arm/common/plat_arm.h
+++ b/include/plat/arm/common/plat_arm.h
@@ -254,6 +254,11 @@ void plat_arm_program_trusted_mailbox(uintptr_t address);
 int plat_arm_bl1_fwu_needed(void);
 __dead2 void plat_arm_error_handler(int err);
 
+/*
+ * Optional function in ARM standard platforms
+ */
+void plat_arm_override_gicr_frames(const uintptr_t *plat_gicr_frames);
+
 #if ARM_PLAT_MT
 unsigned int plat_arm_get_cpu_pe_count(u_register_t mpidr);
 #endif
diff --git a/plat/arm/common/arm_gicv3.c b/plat/arm/common/arm_gicv3.c
index fef53761c219dd3cad58e77313edfdeb055adeea..cfc535939c854b5be752bad0bb0d0c048f5deed2 100644
--- a/plat/arm/common/arm_gicv3.c
+++ b/plat/arm/common/arm_gicv3.c
@@ -28,6 +28,15 @@
 /* The GICv3 driver only needs to be initialized in EL3 */
 static uintptr_t rdistif_base_addrs[PLATFORM_CORE_COUNT];
 
+/* Default GICR base address to be used for GICR probe. */
+static const uintptr_t gicr_base_addrs[2] = {
+	PLAT_ARM_GICR_BASE,	/* GICR Base address of the primary CPU */
+	0U			/* Zero Termination */
+};
+
+/* List of zero terminated GICR frame addresses which CPUs will probe */
+static const uintptr_t *gicr_frames = gicr_base_addrs;
+
 static const interrupt_prop_t arm_interrupt_props[] = {
 	PLAT_ARM_G1S_IRQ_PROPS(INTR_GROUP1S),
 	PLAT_ARM_G0_IRQ_PROPS(INTR_GROUP0)
@@ -76,6 +85,18 @@ static const gicv3_driver_data_t arm_gic_data __unused = {
 	.mpidr_to_core_pos = arm_gicv3_mpidr_hash
 };
 
+/*
+ * By default, gicr_frames will be pointing to gicr_base_addrs. If
+ * the platform supports a non-contiguous GICR frames (GICR frames located
+ * at uneven offset), plat_arm_override_gicr_frames function can be used by
+ * such platform to override the gicr_frames.
+ */
+void plat_arm_override_gicr_frames(const uintptr_t *plat_gicr_frames)
+{
+	assert(plat_gicr_frames != NULL);
+	gicr_frames = plat_gicr_frames;
+}
+
 void __init plat_arm_gic_driver_init(void)
 {
 	/*
@@ -88,7 +109,7 @@ void __init plat_arm_gic_driver_init(void)
 	(defined(__aarch64__) && defined(IMAGE_BL31))
 	gicv3_driver_init(&arm_gic_data);
 
-	if (gicv3_rdistif_probe(PLAT_ARM_GICR_BASE) == -1) {
+	if (gicv3_rdistif_probe(gicr_base_addrs[0]) == -1) {
 		ERROR("No GICR base frame found for Primary CPU\n");
 		panic();
 	}
@@ -124,14 +145,23 @@ void plat_arm_gic_cpuif_disable(void)
 /******************************************************************************
  * ARM common helper function to iterate over all GICR frames and discover the
  * corresponding per-cpu redistributor frame as well as initialize the
- * corresponding interface in GICv3. At the moment, Arm platforms do not have
- * non-contiguous GICR frames.
+ * corresponding interface in GICv3.
  *****************************************************************************/
 void plat_arm_gic_pcpu_init(void)
 {
 	int result;
+	const uintptr_t *plat_gicr_frames = gicr_frames;
+
+	do {
+		result = gicv3_rdistif_probe(*plat_gicr_frames);
+
+		/* If the probe is successful, no need to proceed further */
+		if (result == 0)
+			break;
+
+		plat_gicr_frames++;
+	} while (*plat_gicr_frames != 0U);
 
-	result = gicv3_rdistif_probe(PLAT_ARM_GICR_BASE);
 	if (result == -1) {
 		ERROR("No GICR base frame found for CPU 0x%lx\n", read_mpidr());
 		panic();