diff --git a/arch/arm/cpu/armv8/start.S b/arch/arm/cpu/armv8/start.S
index 67edf94520c5fb2214eadaf036ae38fb1942cab8..19c771dba3abc2dad0971474d2f9d7773119f771 100644
--- a/arch/arm/cpu/armv8/start.S
+++ b/arch/arm/cpu/armv8/start.S
@@ -53,6 +53,11 @@ _bss_end_ofs:
 	.quad	__bss_end - _start
 
 reset:
+	/* Allow the board to save important registers */
+	b	save_boot_params
+.globl	save_boot_params_ret
+save_boot_params_ret:
+
 #ifdef CONFIG_SYS_RESET_SCTRL
 	bl reset_sctrl
 #endif
@@ -282,3 +287,7 @@ ENTRY(c_runtime_cpu_setup)
 
 	ret
 ENDPROC(c_runtime_cpu_setup)
+
+WEAK(save_boot_params)
+	b	save_boot_params_ret	/* back to my caller */
+ENDPROC(save_boot_params)
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
index acecb7c3594ea6febf96ec6770d37fd8fbabfe3a..ca8712a7a74eb4785aee37121af1c985a70acc11 100644
--- a/arch/arm/dts/Makefile
+++ b/arch/arm/dts/Makefile
@@ -53,7 +53,8 @@ dtb-$(CONFIG_TEGRA) += tegra20-harmony.dtb \
 	tegra124-jetson-tk1.dtb \
 	tegra124-nyan-big.dtb \
 	tegra124-venice2.dtb \
-	tegra186-p2771-0000.dtb \
+	tegra186-p2771-0000-a02.dtb \
+	tegra186-p2771-0000-b00.dtb \
 	tegra210-e2220-1170.dtb \
 	tegra210-p2371-0000.dtb \
 	tegra210-p2371-2180.dtb \
diff --git a/arch/arm/dts/tegra186-p2771-0000-a02.dts b/arch/arm/dts/tegra186-p2771-0000-a02.dts
new file mode 100644
index 0000000000000000000000000000000000000000..70f4326c091320fde5cb81ed161ebbf30286c5cd
--- /dev/null
+++ b/arch/arm/dts/tegra186-p2771-0000-a02.dts
@@ -0,0 +1,8 @@
+/dts-v1/;
+
+#include "tegra186-p2771-0000.dtsi"
+
+/ {
+	model = "NVIDIA P2771-0000 A02";
+	compatible = "nvidia,p2771-0000-a02", "nvidia,p2771-0000", "nvidia,tegra186";
+};
diff --git a/arch/arm/dts/tegra186-p2771-0000-b00.dts b/arch/arm/dts/tegra186-p2771-0000-b00.dts
new file mode 100644
index 0000000000000000000000000000000000000000..2384a65e870a2114d6a42b5841fd4899d8805ae7
--- /dev/null
+++ b/arch/arm/dts/tegra186-p2771-0000-b00.dts
@@ -0,0 +1,8 @@
+/dts-v1/;
+
+#include "tegra186-p2771-0000.dtsi"
+
+/ {
+	model = "NVIDIA P2771-0000 B00";
+	compatible = "nvidia,p2771-0000-b00", "nvidia,p2771-0000", "nvidia,tegra186";
+};
diff --git a/arch/arm/dts/tegra186-p2771-0000.dts b/arch/arm/dts/tegra186-p2771-0000.dtsi
similarity index 96%
rename from arch/arm/dts/tegra186-p2771-0000.dts
rename to arch/arm/dts/tegra186-p2771-0000.dtsi
index 5f29ee450194466071298ab500cee85fde12809c..87f0427e80c1c85ee2479013fe160d4a07330519 100644
--- a/arch/arm/dts/tegra186-p2771-0000.dts
+++ b/arch/arm/dts/tegra186-p2771-0000.dtsi
@@ -1,5 +1,3 @@
-/dts-v1/;
-
 #include "tegra186.dtsi"
 
 / {
diff --git a/arch/arm/dts/tegra186.dtsi b/arch/arm/dts/tegra186.dtsi
index fce34fa6500e0ed84ac04a6767921f9ad30dee60..99d49254b36b0e91553e85accb5e86fc542ff6dd 100644
--- a/arch/arm/dts/tegra186.dtsi
+++ b/arch/arm/dts/tegra186.dtsi
@@ -1,5 +1,5 @@
 #include "skeleton.dtsi"
-#include <dt-bindings/gpio/tegra-gpio.h>
+#include <dt-bindings/gpio/tegra186-gpio.h>
 #include <dt-bindings/interrupt-controller/arm-gic.h>
 #include <dt-bindings/mailbox/tegra-hsp.h>
 
diff --git a/arch/arm/include/asm/arch-tegra/ivc.h b/arch/arm/include/asm/arch-tegra/ivc.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f2287ae28022f92a0e95ccc8c8ae605ea58be5c
--- /dev/null
+++ b/arch/arm/include/asm/arch-tegra/ivc.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2016, NVIDIA CORPORATION.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _ASM_ARCH_TEGRA_IVC_H
+#define _ASM_ARCH_TEGRA_IVC_H
+
+#include <common.h>
+
+/*
+ * Tegra IVC is a communication protocol that transfers fixed-size frames
+ * bi-directionally and in-order between the local CPU and some remote entity.
+ * Communication is via a statically sized and allocated buffer in shared
+ * memory and a notification mechanism.
+ *
+ * This API handles all aspects of the shared memory buffer's metadata, and
+ * leaves all aspects of the frame content to the calling code; frames
+ * typically contain some higher-level protocol. The notification mechanism is
+ * also handled externally to this API, since it can vary from instance to
+ * instance.
+ *
+ * The client model is to first find some free (for TX) or filled (for RX)
+ * frame, process that frame's memory buffer (fill or read it), and then
+ * inform the protocol that the frame has been filled/read, i.e. advance the
+ * write/read pointer. If the channel is full, there may be no available frames
+ * to fill/read. In this case, client code may either poll for an available
+ * frame, or wait for the remote entity to send a notification to the local
+ * CPU.
+ */
+
+/**
+ * struct tegra_ivc - In-memory shared memory layout.
+ *
+ * This is described in detail in ivc.c.
+ */
+struct tegra_ivc_channel_header;
+
+/**
+ * struct tegra_ivc - Software state of an IVC channel.
+ *
+ * This state is internal to the IVC code and should not be accessed directly
+ * by clients. It is public solely so clients can allocate storage for the
+ * structure.
+ */
+struct tegra_ivc {
+	/**
+	 * rx_channel - Pointer to the shared memory region used to receive
+	 * messages from the remote entity.
+	 */
+	struct tegra_ivc_channel_header *rx_channel;
+	/**
+	 * tx_channel - Pointer to the shared memory region used to send
+	 * messages to the remote entity.
+	 */
+	struct tegra_ivc_channel_header *tx_channel;
+	/**
+	 * r_pos - The position in list of frames in rx_channel that we are
+	 * reading from.
+	 */
+	uint32_t r_pos;
+	/**
+	 * w_pos - The position in list of frames in tx_channel that we are
+	 * writing to.
+	 */
+	uint32_t w_pos;
+	/**
+	 * nframes - The number of frames allocated (in each direction) in
+	 * shared memory.
+	 */
+	uint32_t nframes;
+	/**
+	 * frame_size - The size of each frame in shared memory.
+	 */
+	uint32_t frame_size;
+	/**
+	 * notify - Function to call to notify the remote processor of a
+	 * change in channel state.
+	 */
+	void (*notify)(struct tegra_ivc *);
+};
+
+/**
+ * tegra_ivc_read_get_next_frame - Locate the next frame to receive.
+ *
+ * Locate the next frame to be received/processed, return the address of the
+ * frame, and do not remove it from the queue. Repeated calls to this function
+ * will return the same address until tegra_ivc_read_advance() is called.
+ *
+ * @ivc		The IVC channel.
+ * @frame	Pointer to be filled with the address of the frame to receive.
+ *
+ * @return 0 if a frame is available, else a negative error code.
+ */
+int tegra_ivc_read_get_next_frame(struct tegra_ivc *ivc, void **frame);
+
+/**
+ * tegra_ivc_read_advance - Advance the read queue.
+ *
+ * Inform the protocol and remote entity that the frame returned by
+ * tegra_ivc_read_get_next_frame() has been processed. The remote end may then
+ * re-use it to transmit further data. Subsequent to this function returning,
+ * tegra_ivc_read_get_next_frame() will return a different frame.
+ *
+ * @ivc		The IVC channel.
+ *
+ * @return 0 if OK, else a negative error code.
+ */
+int tegra_ivc_read_advance(struct tegra_ivc *ivc);
+
+/**
+ * tegra_ivc_write_get_next_frame - Locate the next frame to fill for transmit.
+ *
+ * Locate the next frame to be filled for transmit, return the address of the
+ * frame, and do not add it to the queue. Repeated calls to this function
+ * will return the same address until tegra_ivc_read_advance() is called.
+ *
+ * @ivc		The IVC channel.
+ * @frame	Pointer to be filled with the address of the frame to fill.
+ *
+ * @return 0 if a frame is available, else a negative error code.
+ */
+int tegra_ivc_write_get_next_frame(struct tegra_ivc *ivc, void **frame);
+
+/**
+ * tegra_ivc_write_advance - Advance the write queue.
+ *
+ * Inform the protocol and remote entity that the frame returned by
+ * tegra_ivc_write_get_next_frame() has been filled and should be transmitted.
+ * The remote end may then read data from it. Subsequent to this function
+ * returning, tegra_ivc_write_get_next_frame() will return a different frame.
+ *
+ * @ivc		The IVC channel.
+ *
+ * @return 0 if OK, else a negative error code.
+ */
+int tegra_ivc_write_advance(struct tegra_ivc *ivc);
+
+/**
+ * tegra_ivc_channel_notified - handle internal messages
+ *
+ * This function must be called following every notification.
+ *
+ * @ivc		The IVC channel.
+ *
+ * @return 0 if the channel is ready for communication, or -EAGAIN if a
+ * channel reset is in progress.
+ */
+int tegra_ivc_channel_notified(struct tegra_ivc *ivc);
+
+/**
+ * tegra_ivc_channel_reset - initiates a reset of the shared memory state
+ *
+ * This function must be called after a channel is initialized but before it
+ * is used for communication. The channel will be ready for use when a
+ * subsequent call to notify the remote of the channel reset indicates the
+ * reset operation is complete.
+ *
+ * @ivc		The IVC channel.
+ */
+void tegra_ivc_channel_reset(struct tegra_ivc *ivc);
+
+/**
+ * tegra_ivc_init - Initialize a channel's software state.
+ *
+ * @ivc		The IVC channel.
+ * @rx_base	Address of the the RX shared memory buffer.
+ * @tx_base	Address of the the TX shared memory buffer.
+ * @nframes	Number of frames in each shared memory buffer.
+ * @frame_size	Size of each frame.
+ *
+ * @return 0 if OK, else a negative error code.
+ */
+int tegra_ivc_init(struct tegra_ivc *ivc, ulong rx_base, ulong tx_base,
+		   uint32_t nframes, uint32_t frame_size,
+		   void (*notify)(struct tegra_ivc *));
+
+#endif
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index f4affa5512bcf8c5f78a75a8e0c7b020d1674821..85ae3b7f420992c2a125fc7c981c87fb6f4cf6a0 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -1,5 +1,13 @@
 if TEGRA
 
+config TEGRA_IVC
+	bool "Tegra IVC protocol"
+	help
+	  IVC (Inter-VM Communication) protocol is a Tegra-specific IPC
+	  (Inter Processor Communication) framework. Within the context of
+	  U-Boot, it is typically used for communication between the main CPU
+	  and various auxiliary processors.
+
 config TEGRA_COMMON
 	bool "Tegra common options"
 	select DM
@@ -60,6 +68,7 @@ config TEGRA186
 	select TEGRA186_GPIO
 	select TEGRA_ARMV8_COMMON
 	select TEGRA_HSP
+	select TEGRA_IVC
 
 endchoice
 
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index 12ee1cd7495b48a9055e6bb6ff154df3ed1080cf..d0bf5a6e75ef2dc178bf612d85565a66b05eef0e 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -15,23 +15,24 @@ else
 obj-$(CONFIG_CMD_ENTERRCM) += cmd_enterrcm.o
 endif
 
-obj-$(CONFIG_ARM64) += arm64-mmu.o
 obj-y += ap.o
 obj-y += board.o board2.o
 obj-y += cache.o
 obj-y += clock.o
-obj-y += lowlevel_init.o
 obj-y += pinmux-common.o
 obj-y += powergate.o
 obj-y += xusb-padctl-dummy.o
-obj-$(CONFIG_DISPLAY_CPUINFO) += sys_info.o
-obj-$(CONFIG_TEGRA_GPU) += gpu.o
-obj-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o
+endif
 
+obj-$(CONFIG_ARM64) += arm64-mmu.o
+obj-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o
+obj-$(CONFIG_TEGRA_GPU) += gpu.o
+obj-$(CONFIG_TEGRA_IVC) += ivc.o
+obj-y += lowlevel_init.o
 ifndef CONFIG_SPL_BUILD
 obj-$(CONFIG_ARMV7_PSCI) += psci.o
 endif
-endif
+obj-$(CONFIG_DISPLAY_CPUINFO) += sys_info.o
 
 obj-$(CONFIG_TEGRA20) += tegra20/
 obj-$(CONFIG_TEGRA30) += tegra30/
diff --git a/arch/arm/mach-tegra/board186.c b/arch/arm/mach-tegra/board186.c
index f4b6152a79374b3f9d87090e9ff33b854f9b634c..876ccba5e599cb45fea9351eb92448d47cbd2e2b 100644
--- a/arch/arm/mach-tegra/board186.c
+++ b/arch/arm/mach-tegra/board186.c
@@ -11,12 +11,6 @@
 
 DECLARE_GLOBAL_DATA_PTR;
 
-int dram_init(void)
-{
-	gd->ram_size = (1.5 * 1024 * 1024 * 1024);
-	return 0;
-}
-
 int board_early_init_f(void)
 {
 	return 0;
@@ -32,12 +26,6 @@ int board_late_init(void)
 	return 0;
 }
 
-void dram_init_banksize(void)
-{
-	gd->bd->bi_dram[0].start = CONFIG_SYS_SDRAM_BASE;
-	gd->bd->bi_dram[0].size = gd->ram_size;
-}
-
 void pad_init_mmc(struct mmc_host *host)
 {
 }
diff --git a/arch/arm/mach-tegra/ivc.c b/arch/arm/mach-tegra/ivc.c
new file mode 100644
index 0000000000000000000000000000000000000000..cf6626fb12c7d60983ca8600d9d7e865c8a123f9
--- /dev/null
+++ b/arch/arm/mach-tegra/ivc.c
@@ -0,0 +1,553 @@
+/*
+ * Copyright (c) 2016, NVIDIA CORPORATION.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <asm/arch-tegra/ivc.h>
+
+#define TEGRA_IVC_ALIGN 64
+
+/*
+ * IVC channel reset protocol.
+ *
+ * Each end uses its tx_channel.state to indicate its synchronization state.
+ */
+enum ivc_state {
+	/*
+	 * This value is zero for backwards compatibility with services that
+	 * assume channels to be initially zeroed. Such channels are in an
+	 * initially valid state, but cannot be asynchronously reset, and must
+	 * maintain a valid state at all times.
+	 *
+	 * The transmitting end can enter the established state from the sync or
+	 * ack state when it observes the receiving endpoint in the ack or
+	 * established state, indicating that has cleared the counters in our
+	 * rx_channel.
+	 */
+	ivc_state_established = 0,
+
+	/*
+	 * If an endpoint is observed in the sync state, the remote endpoint is
+	 * allowed to clear the counters it owns asynchronously with respect to
+	 * the current endpoint. Therefore, the current endpoint is no longer
+	 * allowed to communicate.
+	 */
+	ivc_state_sync,
+
+	/*
+	 * When the transmitting end observes the receiving end in the sync
+	 * state, it can clear the w_count and r_count and transition to the ack
+	 * state. If the remote endpoint observes us in the ack state, it can
+	 * return to the established state once it has cleared its counters.
+	 */
+	ivc_state_ack
+};
+
+/*
+ * This structure is divided into two-cache aligned parts, the first is only
+ * written through the tx_channel pointer, while the second is only written
+ * through the rx_channel pointer. This delineates ownership of the cache lines,
+ * which is critical to performance and necessary in non-cache coherent
+ * implementations.
+ */
+struct tegra_ivc_channel_header {
+	union {
+		/* fields owned by the transmitting end */
+		struct {
+			uint32_t w_count;
+			uint32_t state;
+		};
+		uint8_t w_align[TEGRA_IVC_ALIGN];
+	};
+	union {
+		/* fields owned by the receiving end */
+		uint32_t r_count;
+		uint8_t r_align[TEGRA_IVC_ALIGN];
+	};
+};
+
+static inline void tegra_ivc_invalidate_counter(struct tegra_ivc *ivc,
+					struct tegra_ivc_channel_header *h,
+					ulong offset)
+{
+	ulong base = ((ulong)h) + offset;
+	invalidate_dcache_range(base, base + TEGRA_IVC_ALIGN);
+}
+
+static inline void tegra_ivc_flush_counter(struct tegra_ivc *ivc,
+					   struct tegra_ivc_channel_header *h,
+					   ulong offset)
+{
+	ulong base = ((ulong)h) + offset;
+	flush_dcache_range(base, base + TEGRA_IVC_ALIGN);
+}
+
+static inline ulong tegra_ivc_frame_addr(struct tegra_ivc *ivc,
+					 struct tegra_ivc_channel_header *h,
+					 uint32_t frame)
+{
+	BUG_ON(frame >= ivc->nframes);
+
+	return ((ulong)h) + sizeof(struct tegra_ivc_channel_header) +
+	       (ivc->frame_size * frame);
+}
+
+static inline void *tegra_ivc_frame_pointer(struct tegra_ivc *ivc,
+					    struct tegra_ivc_channel_header *ch,
+					    uint32_t frame)
+{
+	return (void *)tegra_ivc_frame_addr(ivc, ch, frame);
+}
+
+static inline void tegra_ivc_invalidate_frame(struct tegra_ivc *ivc,
+					struct tegra_ivc_channel_header *h,
+					unsigned frame)
+{
+	ulong base = tegra_ivc_frame_addr(ivc, h, frame);
+	invalidate_dcache_range(base, base + ivc->frame_size);
+}
+
+static inline void tegra_ivc_flush_frame(struct tegra_ivc *ivc,
+					 struct tegra_ivc_channel_header *h,
+					 unsigned frame)
+{
+	ulong base = tegra_ivc_frame_addr(ivc, h, frame);
+	flush_dcache_range(base, base + ivc->frame_size);
+}
+
+static inline int tegra_ivc_channel_empty(struct tegra_ivc *ivc,
+					  struct tegra_ivc_channel_header *ch)
+{
+	/*
+	 * This function performs multiple checks on the same values with
+	 * security implications, so create snapshots with ACCESS_ONCE() to
+	 * ensure that these checks use the same values.
+	 */
+	uint32_t w_count = ACCESS_ONCE(ch->w_count);
+	uint32_t r_count = ACCESS_ONCE(ch->r_count);
+
+	/*
+	 * Perform an over-full check to prevent denial of service attacks where
+	 * a server could be easily fooled into believing that there's an
+	 * extremely large number of frames ready, since receivers are not
+	 * expected to check for full or over-full conditions.
+	 *
+	 * Although the channel isn't empty, this is an invalid case caused by
+	 * a potentially malicious peer, so returning empty is safer, because it
+	 * gives the impression that the channel has gone silent.
+	 */
+	if (w_count - r_count > ivc->nframes)
+		return 1;
+
+	return w_count == r_count;
+}
+
+static inline int tegra_ivc_channel_full(struct tegra_ivc *ivc,
+					 struct tegra_ivc_channel_header *ch)
+{
+	/*
+	 * Invalid cases where the counters indicate that the queue is over
+	 * capacity also appear full.
+	 */
+	return (ACCESS_ONCE(ch->w_count) - ACCESS_ONCE(ch->r_count)) >=
+	       ivc->nframes;
+}
+
+static inline void tegra_ivc_advance_rx(struct tegra_ivc *ivc)
+{
+	ACCESS_ONCE(ivc->rx_channel->r_count) =
+			ACCESS_ONCE(ivc->rx_channel->r_count) + 1;
+
+	if (ivc->r_pos == ivc->nframes - 1)
+		ivc->r_pos = 0;
+	else
+		ivc->r_pos++;
+}
+
+static inline void tegra_ivc_advance_tx(struct tegra_ivc *ivc)
+{
+	ACCESS_ONCE(ivc->tx_channel->w_count) =
+			ACCESS_ONCE(ivc->tx_channel->w_count) + 1;
+
+	if (ivc->w_pos == ivc->nframes - 1)
+		ivc->w_pos = 0;
+	else
+		ivc->w_pos++;
+}
+
+static inline int tegra_ivc_check_read(struct tegra_ivc *ivc)
+{
+	ulong offset;
+
+	/*
+	 * tx_channel->state is set locally, so it is not synchronized with
+	 * state from the remote peer. The remote peer cannot reset its
+	 * transmit counters until we've acknowledged its synchronization
+	 * request, so no additional synchronization is required because an
+	 * asynchronous transition of rx_channel->state to ivc_state_ack is not
+	 * allowed.
+	 */
+	if (ivc->tx_channel->state != ivc_state_established)
+		return -ECONNRESET;
+
+	/*
+	 * Avoid unnecessary invalidations when performing repeated accesses to
+	 * an IVC channel by checking the old queue pointers first.
+	 * Synchronization is only necessary when these pointers indicate empty
+	 * or full.
+	 */
+	if (!tegra_ivc_channel_empty(ivc, ivc->rx_channel))
+		return 0;
+
+	offset = offsetof(struct tegra_ivc_channel_header, w_count);
+	tegra_ivc_invalidate_counter(ivc, ivc->rx_channel, offset);
+	return tegra_ivc_channel_empty(ivc, ivc->rx_channel) ? -ENOMEM : 0;
+}
+
+static inline int tegra_ivc_check_write(struct tegra_ivc *ivc)
+{
+	ulong offset;
+
+	if (ivc->tx_channel->state != ivc_state_established)
+		return -ECONNRESET;
+
+	if (!tegra_ivc_channel_full(ivc, ivc->tx_channel))
+		return 0;
+
+	offset = offsetof(struct tegra_ivc_channel_header, r_count);
+	tegra_ivc_invalidate_counter(ivc, ivc->tx_channel, offset);
+	return tegra_ivc_channel_full(ivc, ivc->tx_channel) ? -ENOMEM : 0;
+}
+
+static inline uint32_t tegra_ivc_channel_avail_count(struct tegra_ivc *ivc,
+	struct tegra_ivc_channel_header *ch)
+{
+	/*
+	 * This function isn't expected to be used in scenarios where an
+	 * over-full situation can lead to denial of service attacks. See the
+	 * comment in tegra_ivc_channel_empty() for an explanation about
+	 * special over-full considerations.
+	 */
+	return ACCESS_ONCE(ch->w_count) - ACCESS_ONCE(ch->r_count);
+}
+
+int tegra_ivc_read_get_next_frame(struct tegra_ivc *ivc, void **frame)
+{
+	int result = tegra_ivc_check_read(ivc);
+	if (result < 0)
+		return result;
+
+	/*
+	 * Order observation of w_pos potentially indicating new data before
+	 * data read.
+	 */
+	mb();
+
+	tegra_ivc_invalidate_frame(ivc, ivc->rx_channel, ivc->r_pos);
+	*frame = tegra_ivc_frame_pointer(ivc, ivc->rx_channel, ivc->r_pos);
+
+	return 0;
+}
+
+int tegra_ivc_read_advance(struct tegra_ivc *ivc)
+{
+	ulong offset;
+	int result;
+
+	/*
+	 * No read barriers or synchronization here: the caller is expected to
+	 * have already observed the channel non-empty. This check is just to
+	 * catch programming errors.
+	 */
+	result = tegra_ivc_check_read(ivc);
+	if (result)
+		return result;
+
+	tegra_ivc_advance_rx(ivc);
+	offset = offsetof(struct tegra_ivc_channel_header, r_count);
+	tegra_ivc_flush_counter(ivc, ivc->rx_channel, offset);
+
+	/*
+	 * Ensure our write to r_pos occurs before our read from w_pos.
+	 */
+	mb();
+
+	offset = offsetof(struct tegra_ivc_channel_header, w_count);
+	tegra_ivc_invalidate_counter(ivc, ivc->rx_channel, offset);
+
+	if (tegra_ivc_channel_avail_count(ivc, ivc->rx_channel) ==
+	    ivc->nframes - 1)
+		ivc->notify(ivc);
+
+	return 0;
+}
+
+int tegra_ivc_write_get_next_frame(struct tegra_ivc *ivc, void **frame)
+{
+	int result = tegra_ivc_check_write(ivc);
+	if (result)
+		return result;
+
+	*frame = tegra_ivc_frame_pointer(ivc, ivc->tx_channel, ivc->w_pos);
+
+	return 0;
+}
+
+int tegra_ivc_write_advance(struct tegra_ivc *ivc)
+{
+	ulong offset;
+	int result;
+
+	result = tegra_ivc_check_write(ivc);
+	if (result)
+		return result;
+
+	tegra_ivc_flush_frame(ivc, ivc->tx_channel, ivc->w_pos);
+
+	/*
+	 * Order any possible stores to the frame before update of w_pos.
+	 */
+	mb();
+
+	tegra_ivc_advance_tx(ivc);
+	offset = offsetof(struct tegra_ivc_channel_header, w_count);
+	tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset);
+
+	/*
+	 * Ensure our write to w_pos occurs before our read from r_pos.
+	 */
+	mb();
+
+	offset = offsetof(struct tegra_ivc_channel_header, r_count);
+	tegra_ivc_invalidate_counter(ivc, ivc->tx_channel, offset);
+
+	if (tegra_ivc_channel_avail_count(ivc, ivc->tx_channel) == 1)
+		ivc->notify(ivc);
+
+	return 0;
+}
+
+/*
+ * ===============================================================
+ *  IVC State Transition Table - see tegra_ivc_channel_notified()
+ * ===============================================================
+ *
+ *	local	remote	action
+ *	-----	------	-----------------------------------
+ *	SYNC	EST	<none>
+ *	SYNC	ACK	reset counters; move to EST; notify
+ *	SYNC	SYNC	reset counters; move to ACK; notify
+ *	ACK	EST	move to EST; notify
+ *	ACK	ACK	move to EST; notify
+ *	ACK	SYNC	reset counters; move to ACK; notify
+ *	EST	EST	<none>
+ *	EST	ACK	<none>
+ *	EST	SYNC	reset counters; move to ACK; notify
+ *
+ * ===============================================================
+ */
+int tegra_ivc_channel_notified(struct tegra_ivc *ivc)
+{
+	ulong offset;
+	enum ivc_state peer_state;
+
+	/* Copy the receiver's state out of shared memory. */
+	offset = offsetof(struct tegra_ivc_channel_header, w_count);
+	tegra_ivc_invalidate_counter(ivc, ivc->rx_channel, offset);
+	peer_state = ACCESS_ONCE(ivc->rx_channel->state);
+
+	if (peer_state == ivc_state_sync) {
+		/*
+		 * Order observation of ivc_state_sync before stores clearing
+		 * tx_channel.
+		 */
+		mb();
+
+		/*
+		 * Reset tx_channel counters. The remote end is in the SYNC
+		 * state and won't make progress until we change our state,
+		 * so the counters are not in use at this time.
+		 */
+		ivc->tx_channel->w_count = 0;
+		ivc->rx_channel->r_count = 0;
+
+		ivc->w_pos = 0;
+		ivc->r_pos = 0;
+
+		/*
+		 * Ensure that counters appear cleared before new state can be
+		 * observed.
+		 */
+		mb();
+
+		/*
+		 * Move to ACK state. We have just cleared our counters, so it
+		 * is now safe for the remote end to start using these values.
+		 */
+		ivc->tx_channel->state = ivc_state_ack;
+		offset = offsetof(struct tegra_ivc_channel_header, w_count);
+		tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset);
+
+		/*
+		 * Notify remote end to observe state transition.
+		 */
+		ivc->notify(ivc);
+	} else if (ivc->tx_channel->state == ivc_state_sync &&
+			peer_state == ivc_state_ack) {
+		/*
+		 * Order observation of ivc_state_sync before stores clearing
+		 * tx_channel.
+		 */
+		mb();
+
+		/*
+		 * Reset tx_channel counters. The remote end is in the ACK
+		 * state and won't make progress until we change our state,
+		 * so the counters are not in use at this time.
+		 */
+		ivc->tx_channel->w_count = 0;
+		ivc->rx_channel->r_count = 0;
+
+		ivc->w_pos = 0;
+		ivc->r_pos = 0;
+
+		/*
+		 * Ensure that counters appear cleared before new state can be
+		 * observed.
+		 */
+		mb();
+
+		/*
+		 * Move to ESTABLISHED state. We know that the remote end has
+		 * already cleared its counters, so it is safe to start
+		 * writing/reading on this channel.
+		 */
+		ivc->tx_channel->state = ivc_state_established;
+		offset = offsetof(struct tegra_ivc_channel_header, w_count);
+		tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset);
+
+		/*
+		 * Notify remote end to observe state transition.
+		 */
+		ivc->notify(ivc);
+	} else if (ivc->tx_channel->state == ivc_state_ack) {
+		/*
+		 * At this point, we have observed the peer to be in either
+		 * the ACK or ESTABLISHED state. Next, order observation of
+		 * peer state before storing to tx_channel.
+		 */
+		mb();
+
+		/*
+		 * Move to ESTABLISHED state. We know that we have previously
+		 * cleared our counters, and we know that the remote end has
+		 * cleared its counters, so it is safe to start writing/reading
+		 * on this channel.
+		 */
+		ivc->tx_channel->state = ivc_state_established;
+		offset = offsetof(struct tegra_ivc_channel_header, w_count);
+		tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset);
+
+		/*
+		 * Notify remote end to observe state transition.
+		 */
+		ivc->notify(ivc);
+	} else {
+		/*
+		 * There is no need to handle any further action. Either the
+		 * channel is already fully established, or we are waiting for
+		 * the remote end to catch up with our current state. Refer
+		 * to the diagram in "IVC State Transition Table" above.
+		 */
+	}
+
+	if (ivc->tx_channel->state != ivc_state_established)
+		return -EAGAIN;
+
+	return 0;
+}
+
+void tegra_ivc_channel_reset(struct tegra_ivc *ivc)
+{
+	ulong offset;
+
+	ivc->tx_channel->state = ivc_state_sync;
+	offset = offsetof(struct tegra_ivc_channel_header, w_count);
+	tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset);
+	ivc->notify(ivc);
+}
+
+static int check_ivc_params(ulong qbase1, ulong qbase2, uint32_t nframes,
+			    uint32_t frame_size)
+{
+	int ret = 0;
+
+	BUG_ON(offsetof(struct tegra_ivc_channel_header, w_count) &
+	       (TEGRA_IVC_ALIGN - 1));
+	BUG_ON(offsetof(struct tegra_ivc_channel_header, r_count) &
+	       (TEGRA_IVC_ALIGN - 1));
+	BUG_ON(sizeof(struct tegra_ivc_channel_header) &
+	       (TEGRA_IVC_ALIGN - 1));
+
+	if ((uint64_t)nframes * (uint64_t)frame_size >= 0x100000000) {
+		error("tegra_ivc: nframes * frame_size overflows\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * The headers must at least be aligned enough for counters
+	 * to be accessed atomically.
+	 */
+	if ((qbase1 & (TEGRA_IVC_ALIGN - 1)) ||
+	    (qbase2 & (TEGRA_IVC_ALIGN - 1))) {
+		error("tegra_ivc: channel start not aligned\n");
+		return -EINVAL;
+	}
+
+	if (frame_size & (TEGRA_IVC_ALIGN - 1)) {
+		error("tegra_ivc: frame size not adequately aligned\n");
+		return -EINVAL;
+	}
+
+	if (qbase1 < qbase2) {
+		if (qbase1 + frame_size * nframes > qbase2)
+			ret = -EINVAL;
+	} else {
+		if (qbase2 + frame_size * nframes > qbase1)
+			ret = -EINVAL;
+	}
+
+	if (ret) {
+		error("tegra_ivc: queue regions overlap\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+int tegra_ivc_init(struct tegra_ivc *ivc, ulong rx_base, ulong tx_base,
+		   uint32_t nframes, uint32_t frame_size,
+		   void (*notify)(struct tegra_ivc *))
+{
+	int ret;
+
+	if (!ivc)
+		return -EINVAL;
+
+	ret = check_ivc_params(rx_base, tx_base, nframes, frame_size);
+	if (ret)
+		return ret;
+
+	ivc->rx_channel = (struct tegra_ivc_channel_header *)rx_base;
+	ivc->tx_channel = (struct tegra_ivc_channel_header *)tx_base;
+	ivc->w_pos = 0;
+	ivc->r_pos = 0;
+	ivc->nframes = nframes;
+	ivc->frame_size = frame_size;
+	ivc->notify = notify;
+
+	return 0;
+}
diff --git a/arch/arm/mach-tegra/tegra186/Makefile b/arch/arm/mach-tegra/tegra186/Makefile
index ce4610d8f809a999f5ade4d75a298b6b718de959..033d6005fb44f25252133e20c7ad32b9dc26bcf7 100644
--- a/arch/arm/mach-tegra/tegra186/Makefile
+++ b/arch/arm/mach-tegra/tegra186/Makefile
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: GPL-2.0
 
-obj-y += ../arm64-mmu.o
 obj-y += ../board186.o
-obj-y += ../lowlevel_init.o
-obj-$(CONFIG_DISPLAY_CPUINFO) += ../sys_info.o
+obj-y += nvtboot_ll.o
+obj-y += nvtboot_mem.o
diff --git a/arch/arm/mach-tegra/tegra186/nvtboot_ll.S b/arch/arm/mach-tegra/tegra186/nvtboot_ll.S
new file mode 100644
index 0000000000000000000000000000000000000000..1eab890958c7aa4b67763474e197b35203db3a55
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra186/nvtboot_ll.S
@@ -0,0 +1,20 @@
+/*
+ * Save nvtboot-related boot-time CPU state
+ *
+ * (C) Copyright 2015-2016 NVIDIA Corporation <www.nvidia.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <config.h>
+#include <linux/linkage.h>
+
+.globl	nvtboot_boot_x0
+nvtboot_boot_x0:
+	.dword 0
+
+ENTRY(save_boot_params)
+	adr	x8, nvtboot_boot_x0
+	str	x0, [x8]
+	b	save_boot_params_ret
+ENDPROC(save_boot_params)
diff --git a/arch/arm/mach-tegra/tegra186/nvtboot_mem.c b/arch/arm/mach-tegra/tegra186/nvtboot_mem.c
new file mode 100644
index 0000000000000000000000000000000000000000..37dd8d43348ad46cc93e24bf88ed3561a4ecaca1
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra186/nvtboot_mem.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2016, NVIDIA CORPORATION.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <fdt_support.h>
+#include <fdtdec.h>
+#include <asm/arch/tegra.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+extern unsigned long nvtboot_boot_x0;
+
+/*
+ * A parsed version of /memory/reg from the DTB that is passed to U-Boot in x0.
+ *
+ * We only support up to two banks since that's all the binary  bootloader
+ * ever sets. We assume bank 0 is RAM below 4G and bank 1 is RAM  above 4G.
+ * This is all a fairly safe assumption, since the L4T kernel makes  the same
+ * assumptions, so the bootloader is unlikely to change.
+ *
+ * This is written to before relocation, and hence cannot be in .bss, since
+ * .bss overlaps the DTB that's appended to the U-Boot binary. The initializer
+ * forces this into .data and avoids this issue. This also has the nice side-
+ * effect of the content being valid after relocation.
+ */
+static struct {
+	u64 start;
+	u64 size;
+} ram_banks[2] = {{1}};
+
+int dram_init(void)
+{
+	unsigned int na, ns;
+	const void *nvtboot_blob = (void *)nvtboot_boot_x0;
+	int node, len, i;
+	const u32 *prop;
+
+	memset(ram_banks, 0, sizeof(ram_banks));
+
+	na = fdtdec_get_uint(nvtboot_blob, 0, "#address-cells", 2);
+	ns = fdtdec_get_uint(nvtboot_blob, 0, "#size-cells", 2);
+
+	node = fdt_path_offset(nvtboot_blob, "/memory");
+	if (node < 0) {
+		error("Can't find /memory node in nvtboot DTB");
+		hang();
+	}
+	prop = fdt_getprop(nvtboot_blob, node, "reg", &len);
+	if (!prop) {
+		error("Can't find /memory/reg property in nvtboot DTB");
+		hang();
+	}
+
+	len /= (na + ns);
+	if (len > ARRAY_SIZE(ram_banks))
+		len = ARRAY_SIZE(ram_banks);
+
+	gd->ram_size = 0;
+	for (i = 0; i < len; i++) {
+		ram_banks[i].start = of_read_number(prop, na);
+		prop += na;
+		ram_banks[i].size = of_read_number(prop, ns);
+		prop += ns;
+		gd->ram_size += ram_banks[i].size;
+	}
+
+	return 0;
+}
+
+extern unsigned long nvtboot_boot_x0;
+
+void dram_init_banksize(void)
+{
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		gd->bd->bi_dram[i].start = ram_banks[i].start;
+		gd->bd->bi_dram[i].size = ram_banks[i].size;
+	}
+}
+
+ulong board_get_usable_ram_top(ulong total_size)
+{
+	return ram_banks[0].start + ram_banks[0].size;
+}
diff --git a/board/nvidia/p2371-2180/p2371-2180.c b/board/nvidia/p2371-2180/p2371-2180.c
index 0f587eaaa79695aafe5f9918546cce4e643521e8..dbdc1b65e6807ee380d502dc7671b257701f553d 100644
--- a/board/nvidia/p2371-2180/p2371-2180.c
+++ b/board/nvidia/p2371-2180/p2371-2180.c
@@ -30,6 +30,28 @@ void pin_mux_mmc(void)
 	ret = dm_i2c_write(dev, MAX77620_CNFG1_L2_REG, &val, 1);
 	if (ret)
 		printf("i2c_write 0 0x3c 0x27 failed: %d\n", ret);
+
+	/* Disable LDO4 discharge */
+	ret = dm_i2c_read(dev, MAX77620_CNFG2_L4_REG, &val, 1);
+	if (ret) {
+		printf("i2c_read 0 0x3c 0x2c failed: %d\n", ret);
+	} else {
+		val &= ~BIT(1); /* ADE */
+		ret = dm_i2c_write(dev, MAX77620_CNFG2_L4_REG, &val, 1);
+		if (ret)
+			printf("i2c_write 0 0x3c 0x2c failed: %d\n", ret);
+	}
+
+	/* Set MBLPD */
+	ret = dm_i2c_read(dev, MAX77620_CNFGGLBL1_REG, &val, 1);
+	if (ret) {
+		printf("i2c_write 0 0x3c 0x00 failed: %d\n", ret);
+	} else {
+		val |= BIT(6); /* MBLPD */
+		ret = dm_i2c_write(dev, MAX77620_CNFGGLBL1_REG, &val, 1);
+		if (ret)
+			printf("i2c_write 0 0x3c 0x00 failed: %d\n", ret);
+	}
 }
 
 /*
diff --git a/board/nvidia/p2571/max77620_init.h b/board/nvidia/p2571/max77620_init.h
index 92c3719112a4cedb6660a1d483b6466a7853280f..39e550149aae669bf073bcc54be7bbc90ef90f8d 100644
--- a/board/nvidia/p2571/max77620_init.h
+++ b/board/nvidia/p2571/max77620_init.h
@@ -13,6 +13,8 @@
 #define MAX77620_I2C_ADDR		0x78
 #define MAX77620_I2C_ADDR_7BIT		0x3C
 
+#define MAX77620_CNFGGLBL1_REG		0x00
+
 #define MAX77620_SD0_REG		0x16
 #define MAX77620_SD1_REG		0x17
 #define MAX77620_SD2_REG		0x18
diff --git a/configs/p2771-0000_defconfig b/configs/p2771-0000-a02_defconfig
similarity index 85%
rename from configs/p2771-0000_defconfig
rename to configs/p2771-0000-a02_defconfig
index 9f2c418f9fe13ea9ad69c118da06aeeddb3b9699..1fe25f58f199d6a085a461e7bae186e144ec4b55 100644
--- a/configs/p2771-0000_defconfig
+++ b/configs/p2771-0000-a02_defconfig
@@ -2,10 +2,10 @@ CONFIG_ARM=y
 CONFIG_TEGRA=y
 CONFIG_TEGRA186=y
 CONFIG_TARGET_P2771_0000=y
-CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000"
+CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000-a02"
 CONFIG_OF_SYSTEM_SETUP=y
 CONFIG_HUSH_PARSER=y
-CONFIG_SYS_PROMPT="Tegra186 (P2771-0000) # "
+CONFIG_SYS_PROMPT="Tegra186 (P2771-0000 A02) # "
 # CONFIG_CMD_IMI is not set
 # CONFIG_CMD_IMLS is not set
 # CONFIG_CMD_FLASH is not set
diff --git a/configs/p2771-0000-b00_defconfig b/configs/p2771-0000-b00_defconfig
new file mode 100644
index 0000000000000000000000000000000000000000..552fb6cec78b54378426b8d01e0c8b7368ae7ef1
--- /dev/null
+++ b/configs/p2771-0000-b00_defconfig
@@ -0,0 +1,31 @@
+CONFIG_ARM=y
+CONFIG_TEGRA=y
+CONFIG_TEGRA186=y
+CONFIG_TARGET_P2771_0000=y
+CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000-b00"
+CONFIG_OF_SYSTEM_SETUP=y
+CONFIG_HUSH_PARSER=y
+CONFIG_SYS_PROMPT="Tegra186 (P2771-0000 B00) # "
+# CONFIG_CMD_IMI is not set
+# CONFIG_CMD_IMLS is not set
+# CONFIG_CMD_FLASH is not set
+CONFIG_CMD_MMC=y
+CONFIG_CMD_SF=y
+CONFIG_CMD_SPI=y
+CONFIG_CMD_I2C=y
+CONFIG_CMD_USB=y
+# CONFIG_CMD_FPGA is not set
+CONFIG_CMD_GPIO=y
+# CONFIG_CMD_SETEXPR is not set
+CONFIG_CMD_DHCP=y
+# CONFIG_CMD_NFS is not set
+CONFIG_CMD_MII=y
+CONFIG_CMD_PING=y
+CONFIG_CMD_EXT2=y
+CONFIG_CMD_EXT4=y
+CONFIG_CMD_EXT4_WRITE=y
+CONFIG_CMD_FAT=y
+CONFIG_CMD_FS_GENERIC=y
+CONFIG_SYS_NS16550=y
+CONFIG_USB=y
+CONFIG_DM_USB=y