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 000000000000..7f2287ae2802
--- /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 f4affa5512bc..85ae3b7f4209 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 3b8d0127c7aa..d0bf5a6e75ef 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -27,6 +27,7 @@ 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
diff --git a/arch/arm/mach-tegra/ivc.c b/arch/arm/mach-tegra/ivc.c
new file mode 100644
index 000000000000..cf6626fb12c7
--- /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;
+}
