diff --git a/.gitignore b/.gitignore
index a44566f..3226b3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,6 +57,7 @@ libdrm_exynos.pc
 libdrm_freedreno.pc
 libdrm_amdgpu.pc
 libdrm_vc4.pc
+libdrm_etnaviv.pc
 libkms.pc
 libtool
 ltmain.sh
diff --git a/Makefile.am b/Makefile.am
index 2ceb352..630edc4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,6 +36,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = \
 	--enable-freedreno \
 	--enable-freedreno-kgsl\
 	--enable-tegra-experimental-api \
+	--enable-etnaviv-experimental-api \
 	--enable-install-test-programs \
 	--enable-cairo-tests \
 	--enable-manpages \
@@ -84,6 +85,10 @@ if HAVE_VC4
 VC4_SUBDIR = vc4
 endif
 
+if HAVE_ETNAVIV
+ETNAVIV_SUBDIR = etnaviv
+endif
+
 if BUILD_MANPAGES
 if HAVE_MANPAGES_STYLESHEET
 MAN_SUBDIR = man
@@ -102,6 +107,7 @@ SUBDIRS = \
 	$(FREEDRENO_SUBDIR) \
 	$(TEGRA_SUBDIR) \
 	$(VC4_SUBDIR) \
+	$(ETNAVIV_SUBDIR) \
 	tests \
 	$(MAN_SUBDIR)
 
diff --git a/configure.ac b/configure.ac
index e3048c7..64f3e6c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -132,6 +132,11 @@ AC_ARG_ENABLE(vc4,
 	      [Enable support for vc4's API (default: auto, enabled on arm)]),
 	      [VC4=$enableval], [VC4=auto])
 
+AC_ARG_ENABLE(etnaviv-experimental-api,
+	      AS_HELP_STRING([--enable-etnaviv-experimental-api],
+	      [Enable support for etnaviv's experimental API (default: disabled)]),
+	      [ETNAVIV=$enableval], [ETNAVIV=no])
+
 AC_ARG_ENABLE(install-test-programs,
 		  AS_HELP_STRING([--enable-install-test-programs],
 		  [Install test programs (default: no)]),
@@ -274,6 +279,9 @@ if test "x$drm_cv_atomic_primitives" = "xnone"; then
 
 	LIBDRM_ATOMICS_NOT_FOUND_MSG($TEGRA, tegra, NVIDIA Tegra, tegra-experimental-api)
 	TEGRA=no
+
+	LIBDRM_ATOMICS_NOT_FOUND_MSG($ETNAVIV, etnaviv, Vivante, etnaviv-experimental-api)
+	ETNAVIV=no
 else
 	if test "x$INTEL" = xauto; then
 		case $host_cpu in
@@ -413,6 +421,11 @@ if test "x$VC4" = xyes; then
 	AC_DEFINE(HAVE_VC4, 1, [Have VC4 support])
 fi
 
+AM_CONDITIONAL(HAVE_ETNAVIV, [test "x$ETNAVIV" = xyes])
+if test "x$ETNAVIV" = xyes; then
+	AC_DEFINE(HAVE_ETNAVIV, 1, [Have etnaviv support])
+fi
+
 AM_CONDITIONAL(HAVE_INSTALL_TESTS, [test "x$INSTALL_TESTS" = xyes])
 if test "x$INSTALL_TESTS" = xyes; then
 	AC_DEFINE(HAVE_INSTALL_TESTS, 1, [Install test programs])
@@ -524,6 +537,8 @@ AC_CONFIG_FILES([
 	tegra/libdrm_tegra.pc
 	vc4/Makefile
 	vc4/libdrm_vc4.pc
+	etnaviv/Makefile
+	etnaviv/libdrm_etnaviv.pc
 	tests/Makefile
 	tests/modeprint/Makefile
 	tests/modetest/Makefile
@@ -555,4 +570,5 @@ echo "  EXYNOS API     $EXYNOS"
 echo "  Freedreno API  $FREEDRENO (kgsl: $FREEDRENO_KGSL)"
 echo "  Tegra API      $TEGRA"
 echo "  VC4 API        $VC4"
+echo "  Etnaviv API    $ETNAVIV"
 echo ""
diff --git a/etnaviv/Android.mk b/etnaviv/Android.mk
new file mode 100644
index 0000000..a3a2295
--- /dev/null
+++ b/etnaviv/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Import variables LIBDRM_ETNAVIV_FILES, LIBDRM_ETNAVIV_H_FILES
+include $(LOCAL_PATH)/Makefile.sources
+
+LOCAL_MODULE := libdrm_etnaviv
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := libdrm
+
+LOCAL_SRC_FILES := $(patsubst %.h, , $(LIBDRM_ETNAVIV_FILES))
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+
+LOCAL_CFLAGS := \
+	-DHAVE_LIBDRM_ATOMIC_PRIMITIVES=1
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/etnaviv/Makefile.am b/etnaviv/Makefile.am
new file mode 100644
index 0000000..1c40e56
--- /dev/null
+++ b/etnaviv/Makefile.am
@@ -0,0 +1,27 @@
+AUTOMAKE_OPTIONS=subdir-objects
+include Makefile.sources
+
+AM_CFLAGS = \
+	$(WARN_CFLAGS) \
+	-I$(top_srcdir) \
+	$(PTHREADSTUBS_CFLAGS) \
+	-I$(top_srcdir)/include/drm
+
+libdrm_etnaviv_ladir = $(libdir)
+libdrm_etnaviv_la_LTLIBRARIES = libdrm_etnaviv.la
+libdrm_etnaviv_la_LDFLAGS = -version-number 0:0:0 -no-undefined
+libdrm_etnaviv_la_LIBADD = \
+	../libdrm.la \
+	@PTHREADSTUBS_LIBS@ \
+	@CLOCK_LIB@
+
+libdrm_etnaviv_la_SOURCES = $(LIBDRM_ETNAVIV_FILES)
+
+libdrm_etnavivincludedir = ${includedir}/libdrm
+libdrm_etnavivinclude_HEADERS = $(LIBDRM_ETNAVIV_H_FILES)
+
+pkgconfigdir = @pkgconfigdir@
+pkgconfig_DATA = libdrm_etnaviv.pc
+
+TESTS = etnaviv-symbol-check
+EXTRA_DIST = $(TESTS)
diff --git a/etnaviv/Makefile.sources b/etnaviv/Makefile.sources
new file mode 100644
index 0000000..5258056
--- /dev/null
+++ b/etnaviv/Makefile.sources
@@ -0,0 +1,12 @@
+LIBDRM_ETNAVIV_FILES := \
+	etnaviv_device.c \
+	etnaviv_gpu.c \
+	etnaviv_bo.c \
+	etnaviv_bo_cache.c \
+	etnaviv_pipe.c \
+	etnaviv_cmd_stream.c \
+	etnaviv_drm.h \
+	etnaviv_priv.h
+
+LIBDRM_ETNAVIV_H_FILES := \
+	etnaviv_drmif.h
diff --git a/etnaviv/etnaviv-symbol-check b/etnaviv/etnaviv-symbol-check
new file mode 100755
index 0000000..7795023
--- /dev/null
+++ b/etnaviv/etnaviv-symbol-check
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# The following symbols (past the first five) are taken from the public headers.
+# A list of the latter should be available Makefile.sources/LIBDRM_FREEDRENO_H_FILES
+
+FUNCS=$(nm -D --format=bsd --defined-only ${1-.libs/libdrm_etnaviv.so} | awk '{print $3}'| while read func; do
+( grep -q "^$func$" || echo $func )  <<EOF
+__bss_start
+_edata
+_end
+_fini
+_init
+etna_device_new
+etna_device_ref
+etna_device_del
+etna_gpu_new
+etna_gpu_del
+etna_gpu_get_param
+etna_pipe_new
+etna_pipe_del
+etna_pipe_wait
+etna_bo_new
+etna_bo_from_handle
+etna_bo_from_name
+etna_bo_from_dmabuf
+etna_bo_ref
+etna_bo_del
+etna_bo_get_name
+etna_bo_handle
+etna_bo_dmabuf
+etna_bo_size
+etna_bo_map
+etna_bo_cpu_prep
+etna_bo_cpu_fini
+etna_cmd_stream_new
+etna_cmd_stream_del
+etna_cmd_stream_timestamp
+etna_cmd_stream_flush
+etna_cmd_stream_finish
+etna_cmd_stream_reloc
+EOF
+done)
+
+test ! -n "$FUNCS" || echo $FUNCS
+test ! -n "$FUNCS"
diff --git a/etnaviv/etnaviv_bo.c b/etnaviv/etnaviv_bo.c
new file mode 100644
index 0000000..833f8bd
--- /dev/null
+++ b/etnaviv/etnaviv_bo.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2014 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "etnaviv_priv.h"
+#include "etnaviv_drmif.h"
+
+drm_private pthread_mutex_t table_lock = PTHREAD_MUTEX_INITIALIZER;
+drm_private void bo_del(struct etna_bo *bo);
+
+/* set buffer name, and add to table, call w/ table_lock held: */
+static void set_name(struct etna_bo *bo, uint32_t name)
+{
+	bo->name = name;
+	/* add ourself into the name table: */
+	drmHashInsert(bo->dev->name_table, name, bo);
+}
+
+/* Called under table_lock */
+drm_private void bo_del(struct etna_bo *bo)
+{
+	if (bo->map)
+		drm_munmap(bo->map, bo->size);
+
+	if (bo->name)
+		drmHashDelete(bo->dev->name_table, bo->name);
+
+	if (bo->handle) {
+		struct drm_gem_close req = {
+			.handle = bo->handle,
+		};
+
+		drmHashDelete(bo->dev->handle_table, bo->handle);
+		drmIoctl(bo->dev->fd, DRM_IOCTL_GEM_CLOSE, &req);
+	}
+
+	free(bo);
+}
+
+/* lookup a buffer from it's handle, call w/ table_lock held: */
+static struct etna_bo *lookup_bo(void *tbl, uint32_t handle)
+{
+	struct etna_bo *bo = NULL;
+
+	if (!drmHashLookup(tbl, handle, (void **)&bo)) {
+		/* found, incr refcnt and return: */
+		bo = etna_bo_ref(bo);
+
+		/* don't break the bucket if this bo was found in one */
+		list_delinit(&bo->list);
+	}
+
+	return bo;
+}
+
+/* allocate a new buffer object, call w/ table_lock held */
+static struct etna_bo *bo_from_handle(struct etna_device *dev,
+		uint32_t size, uint32_t handle, uint32_t flags)
+{
+	struct etna_bo *bo = calloc(sizeof(*bo), 1);
+
+	if (!bo) {
+		struct drm_gem_close req = {
+			.handle = handle,
+		};
+
+		drmIoctl(dev->fd, DRM_IOCTL_GEM_CLOSE, &req);
+
+		return NULL;
+	}
+
+	bo->dev = etna_device_ref(dev);
+	bo->size = size;
+	bo->handle = handle;
+	bo->flags = flags;
+	atomic_set(&bo->refcnt, 1);
+	list_inithead(&bo->list);
+	/* add ourselves to the handle table: */
+	drmHashInsert(dev->handle_table, handle, bo);
+
+	return bo;
+}
+
+/* allocate a new (un-tiled) buffer object */
+struct etna_bo *etna_bo_new(struct etna_device *dev, uint32_t size,
+		uint32_t flags)
+{
+	struct etna_bo *bo;
+	int ret;
+	struct drm_etnaviv_gem_new req = {
+			.flags = flags,
+	};
+
+	bo = etna_bo_cache_alloc(&dev->bo_cache, &size, flags);
+	if (bo)
+		return bo;
+
+	req.size = size;
+	ret = drmCommandWriteRead(dev->fd, DRM_ETNAVIV_GEM_NEW,
+			&req, sizeof(req));
+	if (ret)
+		return NULL;
+
+	pthread_mutex_lock(&table_lock);
+	bo = bo_from_handle(dev, size, req.handle, flags);
+	bo->reuse = 1;
+	pthread_mutex_unlock(&table_lock);
+
+	return bo;
+}
+
+struct etna_bo *etna_bo_ref(struct etna_bo *bo)
+{
+	atomic_inc(&bo->refcnt);
+
+	return bo;
+}
+
+/* get buffer info */
+static int get_buffer_info(struct etna_bo *bo)
+{
+	int ret;
+	struct drm_etnaviv_gem_info req = {
+		.handle = bo->handle,
+	};
+
+	ret = drmCommandWriteRead(bo->dev->fd, DRM_ETNAVIV_GEM_INFO,
+			&req, sizeof(req));
+	if (ret) {
+		return ret;
+	}
+
+	/* really all we need for now is mmap offset */
+	bo->offset = req.offset;
+
+	return 0;
+}
+
+/* import a buffer object from DRI2 name */
+struct etna_bo *etna_bo_from_name(struct etna_device *dev, uint32_t name)
+{
+	struct etna_bo *bo;
+	struct drm_gem_open req = {
+		.name = name,
+	};
+
+	pthread_mutex_lock(&table_lock);
+
+	/* check name table first, to see if bo is already open: */
+	bo = lookup_bo(dev->name_table, req.handle);
+	if (bo)
+		goto out_unlock;
+
+	if (drmIoctl(dev->fd, DRM_IOCTL_GEM_OPEN, &req)) {
+		ERROR_MSG("gem-open failed: %s", strerror(errno));
+		goto out_unlock;
+	}
+
+	bo = lookup_bo(dev->handle_table, req.handle);
+	if (bo)
+		goto out_unlock;
+
+	bo = bo_from_handle(dev, req.size, req.handle, 0);
+	if (bo)
+		set_name(bo, name);
+
+out_unlock:
+	pthread_mutex_unlock(&table_lock);
+
+	return bo;
+}
+
+/* import a buffer from dmabuf fd, does not take ownership of the
+ * fd so caller should close() the fd when it is otherwise done
+ * with it (even if it is still using the 'struct etna_bo *')
+ */
+struct etna_bo *etna_bo_from_dmabuf(struct etna_device *dev, int fd)
+{
+	struct etna_bo *bo;
+	int ret, size;
+	uint32_t handle;
+
+	pthread_mutex_lock(&table_lock);
+
+	ret = drmPrimeFDToHandle(dev->fd, fd, &handle);
+	if (ret) {
+		return NULL;
+	}
+
+	bo = lookup_bo(dev->handle_table, handle);
+	if (bo)
+		goto out_unlock;
+
+	/* lseek() to get bo size */
+	size = lseek(fd, 0, SEEK_END);
+	lseek(fd, 0, SEEK_CUR);
+
+	bo = bo_from_handle(dev, size, handle, 0);
+
+out_unlock:
+	pthread_mutex_unlock(&table_lock);
+
+	return bo;
+}
+
+/* destroy a buffer object */
+void etna_bo_del(struct etna_bo *bo)
+{
+	struct etna_device *dev = bo->dev;
+
+	if (!bo)
+		return;
+
+	if (!atomic_dec_and_test(&bo->refcnt))
+		return;
+
+	pthread_mutex_lock(&table_lock);
+
+	if (bo->reuse && (etna_bo_cache_free(&dev->bo_cache, bo) == 0))
+		goto out;
+
+	bo_del(bo);
+	etna_device_del_locked(dev);
+out:
+	pthread_mutex_unlock(&table_lock);
+}
+
+/* get the global flink/DRI2 buffer name */
+int etna_bo_get_name(struct etna_bo *bo, uint32_t *name)
+{
+	if (!bo->name) {
+		struct drm_gem_flink req = {
+			.handle = bo->handle,
+		};
+		int ret;
+
+		ret = drmIoctl(bo->dev->fd, DRM_IOCTL_GEM_FLINK, &req);
+		if (ret) {
+			return ret;
+		}
+
+		pthread_mutex_lock(&table_lock);
+		set_name(bo, req.name);
+		pthread_mutex_unlock(&table_lock);
+		bo->reuse = 0;
+	}
+
+	*name = bo->name;
+
+	return 0;
+}
+
+uint32_t etna_bo_handle(struct etna_bo *bo)
+{
+	return bo->handle;
+}
+
+/* caller owns the dmabuf fd that is returned and is responsible
+ * to close() it when done
+ */
+int etna_bo_dmabuf(struct etna_bo *bo)
+{
+	int ret, prime_fd;
+
+	ret = drmPrimeHandleToFD(bo->dev->fd, bo->handle, DRM_CLOEXEC,
+				&prime_fd);
+	if (ret) {
+		ERROR_MSG("failed to get dmabuf fd: %d", ret);
+		return ret;
+	}
+
+	bo->reuse = 0;
+
+	return prime_fd;
+}
+
+uint32_t etna_bo_size(struct etna_bo *bo)
+{
+	return bo->size;
+}
+
+void *etna_bo_map(struct etna_bo *bo)
+{
+	if (!bo->map) {
+		if (!bo->offset) {
+			get_buffer_info(bo);
+		}
+
+		bo->map = drm_mmap(0, bo->size, PROT_READ | PROT_WRITE,
+				MAP_SHARED, bo->dev->fd, bo->offset);
+		if (bo->map == MAP_FAILED) {
+			ERROR_MSG("mmap failed: %s", strerror(errno));
+			bo->map = NULL;
+		}
+	}
+
+	return bo->map;
+}
+
+int etna_bo_cpu_prep(struct etna_bo *bo, uint32_t op)
+{
+	struct drm_etnaviv_gem_cpu_prep req = {
+		.handle = bo->handle,
+		.op = op,
+	};
+
+	get_abs_timeout(&req.timeout, 5000);
+
+	return drmCommandWrite(bo->dev->fd, DRM_ETNAVIV_GEM_CPU_PREP,
+			&req, sizeof(req));
+}
+
+void etna_bo_cpu_fini(struct etna_bo *bo)
+{
+	struct drm_etnaviv_gem_cpu_fini req = {
+		.handle = bo->handle,
+	};
+
+	drmCommandWrite(bo->dev->fd, DRM_ETNAVIV_GEM_CPU_FINI,
+			&req, sizeof(req));
+}
diff --git a/etnaviv/etnaviv_bo_cache.c b/etnaviv/etnaviv_bo_cache.c
new file mode 100644
index 0000000..8924651
--- /dev/null
+++ b/etnaviv/etnaviv_bo_cache.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2016 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "etnaviv_priv.h"
+#include "etnaviv_drmif.h"
+
+drm_private void bo_del(struct etna_bo *bo);
+drm_private extern pthread_mutex_t table_lock;
+
+static void add_bucket(struct etna_bo_cache *cache, int size)
+{
+	unsigned i = cache->num_buckets;
+
+	assert(i < ARRAY_SIZE(cache->cache_bucket));
+
+	list_inithead(&cache->cache_bucket[i].list);
+	cache->cache_bucket[i].size = size;
+	cache->num_buckets++;
+}
+
+drm_private void etna_bo_cache_init(struct etna_bo_cache *cache)
+{
+	unsigned long size, cache_max_size = 64 * 1024 * 1024;
+
+	/* OK, so power of two buckets was too wasteful of memory.
+	 * Give 3 other sizes between each power of two, to hopefully
+	 * cover things accurately enough.  (The alternative is
+	 * probably to just go for exact matching of sizes, and assume
+	 * that for things like composited window resize the tiled
+	 * width/height alignment and rounding of sizes to pages will
+	 * get us useful cache hit rates anyway)
+	 */
+	add_bucket(cache, 4096);
+	add_bucket(cache, 4096 * 2);
+	add_bucket(cache, 4096 * 3);
+
+	/* Initialize the linked lists for BO reuse cache. */
+	for (size = 4 * 4096; size <= cache_max_size; size *= 2) {
+		add_bucket(cache, size);
+		add_bucket(cache, size + size * 1 / 4);
+		add_bucket(cache, size + size * 2 / 4);
+		add_bucket(cache, size + size * 3 / 4);
+	}
+}
+
+/* Frees older cached buffers.  Called under table_lock */
+drm_private void etna_bo_cache_cleanup(struct etna_bo_cache *cache, time_t time)
+{
+	unsigned i;
+
+	if (cache->time == time)
+		return;
+
+	for (i = 0; i < cache->num_buckets; i++) {
+		struct etna_bo_bucket *bucket = &cache->cache_bucket[i];
+		struct etna_bo *bo;
+
+		while (!LIST_IS_EMPTY(&bucket->list)) {
+			bo = LIST_ENTRY(struct etna_bo, bucket->list.next, list);
+
+			/* keep things in cache for at least 1 second: */
+			if (time && ((time - bo->free_time) <= 1))
+				break;
+
+			list_del(&bo->list);
+			bo_del(bo);
+		}
+	}
+
+	cache->time = time;
+}
+
+static struct etna_bo_bucket *get_bucket(struct etna_bo_cache *cache, uint32_t size)
+{
+	unsigned i;
+
+	/* hmm, this is what intel does, but I suppose we could calculate our
+	 * way to the correct bucket size rather than looping..
+	 */
+	for (i = 0; i < cache->num_buckets; i++) {
+		struct etna_bo_bucket *bucket = &cache->cache_bucket[i];
+		if (bucket->size >= size) {
+			return bucket;
+		}
+	}
+
+	return NULL;
+}
+
+static int is_idle(struct etna_bo *bo)
+{
+	return etna_bo_cpu_prep(bo,
+			DRM_ETNA_PREP_READ |
+			DRM_ETNA_PREP_WRITE |
+			DRM_ETNA_PREP_NOSYNC) == 0;
+}
+
+static struct etna_bo *find_in_bucket(struct etna_bo_bucket *bucket, uint32_t flags)
+{
+	struct etna_bo *bo = NULL;
+
+	pthread_mutex_lock(&table_lock);
+	while (!LIST_IS_EMPTY(&bucket->list)) {
+		bo = LIST_ENTRY(struct etna_bo, bucket->list.next, list);
+
+		if (bo->flags == flags && is_idle(bo)) {
+			list_del(&bo->list);
+			break;
+		}
+
+		bo = NULL;
+		break;
+	}
+	pthread_mutex_unlock(&table_lock);
+
+	return bo;
+}
+
+/* allocate a new (un-tiled) buffer object
+ *
+ * NOTE: size is potentially rounded up to bucket size
+ */
+drm_private struct etna_bo *etna_bo_cache_alloc(struct etna_bo_cache *cache, uint32_t *size,
+    uint32_t flags)
+{
+	struct etna_bo *bo;
+	struct etna_bo_bucket *bucket;
+
+	*size = ALIGN(*size, 4096);
+	bucket = get_bucket(cache, *size);
+
+	/* see if we can be green and recycle: */
+	if (bucket) {
+		*size = bucket->size;
+		bo = find_in_bucket(bucket, flags);
+		if (bo) {
+			atomic_set(&bo->refcnt, 1);
+			etna_device_ref(bo->dev);
+			return bo;
+		}
+	}
+
+	return NULL;
+}
+
+drm_private int etna_bo_cache_free(struct etna_bo_cache *cache, struct etna_bo *bo)
+{
+	struct etna_bo_bucket *bucket = get_bucket(cache, bo->size);
+
+	/* see if we can be green and recycle: */
+	if (bucket) {
+		struct timespec time;
+
+		clock_gettime(CLOCK_MONOTONIC, &time);
+
+		bo->free_time = time.tv_sec;
+		list_addtail(&bo->list, &bucket->list);
+		etna_bo_cache_cleanup(cache, time.tv_sec);
+
+		/* bo's in the bucket cache don't have a ref and
+		 * don't hold a ref to the dev:
+		 */
+		etna_device_del_locked(bo->dev);
+
+		return 0;
+	}
+
+	return -1;
+}
diff --git a/etnaviv/etnaviv_cmd_stream.c b/etnaviv/etnaviv_cmd_stream.c
new file mode 100644
index 0000000..9ce3f36
--- /dev/null
+++ b/etnaviv/etnaviv_cmd_stream.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2014-2015 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+
+#include "etnaviv_drmif.h"
+#include "etnaviv_priv.h"
+
+static pthread_mutex_t idx_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static void *grow(void *ptr, uint32_t nr, uint32_t *max, uint32_t sz)
+{
+	if ((nr + 1) > *max) {
+		if ((*max * 2) < (nr + 1))
+			*max = nr + 5;
+		else
+			*max = *max * 2;
+		ptr = realloc(ptr, *max * sz);
+	}
+
+	return ptr;
+}
+
+#define APPEND(x, name) ({ \
+	(x)->name = grow((x)->name, (x)->nr_ ## name, &(x)->max_ ## name, sizeof((x)->name[0])); \
+	(x)->nr_ ## name ++; \
+})
+
+static inline struct etna_cmd_stream_priv *
+etna_cmd_stream_priv(struct etna_cmd_stream *stream)
+{
+    return (struct etna_cmd_stream_priv *)stream;
+}
+
+struct etna_cmd_stream *etna_cmd_stream_new(struct etna_pipe *pipe, uint32_t size,
+		void (*reset_notify)(struct etna_cmd_stream *stream, void *priv),
+		void *priv)
+{
+	struct etna_cmd_stream_priv *stream = NULL;
+
+	if (size == 0) {
+		ERROR_MSG("invalid size of 0");
+		goto fail;
+	}
+
+	stream = calloc(1, sizeof(*stream));
+	if (!stream) {
+		ERROR_MSG("allocation failed");
+		goto fail;
+	}
+
+	/* allocate even number of 32-bit words */
+	size = ALIGN(size, 2);
+
+	stream->base.buffer = malloc(size * sizeof(uint32_t));
+	if (!stream->base.buffer) {
+		ERROR_MSG("allocation failed");
+		goto fail;
+	}
+
+	stream->base.size = size;
+	stream->pipe = pipe;
+	stream->reset_notify = reset_notify;
+	stream->reset_notify_priv = priv;
+
+	return &stream->base;
+
+fail:
+	if (stream)
+		etna_cmd_stream_del(&stream->base);
+
+	return NULL;
+}
+
+void etna_cmd_stream_del(struct etna_cmd_stream *stream)
+{
+	struct etna_cmd_stream_priv *priv = etna_cmd_stream_priv(stream);
+
+	free(stream->buffer);
+	free(priv->submit.relocs);
+	free(priv);
+}
+
+static void reset_buffer(struct etna_cmd_stream *stream)
+{
+	struct etna_cmd_stream_priv *priv = etna_cmd_stream_priv(stream);
+
+	stream->offset = 0;
+	priv->submit.nr_bos = 0;
+	priv->submit.nr_relocs = 0;
+	priv->nr_bos = 0;
+
+	if (priv->reset_notify)
+		priv->reset_notify(stream, priv->reset_notify_priv);
+}
+
+uint32_t etna_cmd_stream_timestamp(struct etna_cmd_stream *stream)
+{
+	return etna_cmd_stream_priv(stream)->last_timestamp;
+}
+
+static uint32_t append_bo(struct etna_cmd_stream *stream, struct etna_bo *bo)
+{
+	struct etna_cmd_stream_priv *priv = etna_cmd_stream_priv(stream);
+	uint32_t idx;
+
+	idx = APPEND(&priv->submit, bos);
+	idx = APPEND(priv, bos);
+
+	priv->submit.bos[idx].flags = 0;
+	priv->submit.bos[idx].handle = bo->handle;
+
+	priv->bos[idx] = etna_bo_ref(bo);
+
+	return idx;
+}
+
+/* add (if needed) bo, return idx: */
+static uint32_t bo2idx(struct etna_cmd_stream *stream, struct etna_bo *bo,
+		uint32_t flags)
+{
+	struct etna_cmd_stream_priv *priv = etna_cmd_stream_priv(stream);
+	uint32_t idx;
+
+	pthread_mutex_lock(&idx_lock);
+
+	if (!bo->current_stream) {
+		idx = append_bo(stream, bo);
+		bo->current_stream = stream;
+		bo->idx = idx;
+	} else if (bo->current_stream == stream) {
+		idx = bo->idx;
+	} else {
+		/* slow-path: */
+		for (idx = 0; idx < priv->nr_bos; idx++)
+			if (priv->bos[idx] == bo)
+				break;
+		if (idx == priv->nr_bos) {
+			/* not found */
+			idx = append_bo(stream, bo);
+		}
+	}
+	pthread_mutex_unlock(&idx_lock);
+
+	if (flags & ETNA_RELOC_READ)
+		priv->submit.bos[idx].flags |= ETNA_SUBMIT_BO_READ;
+	if (flags & ETNA_RELOC_WRITE)
+		priv->submit.bos[idx].flags |= ETNA_SUBMIT_BO_WRITE;
+
+	return idx;
+}
+
+static void flush(struct etna_cmd_stream *stream)
+{
+	struct etna_cmd_stream_priv *priv = etna_cmd_stream_priv(stream);
+	int ret, id = priv->pipe->id;
+	struct etna_gpu *gpu = priv->pipe->gpu;
+
+	struct drm_etnaviv_gem_submit req = {
+		.pipe = gpu->core,
+		.exec_state = id,
+		.bos = VOID2U64(priv->submit.bos),
+		.nr_bos = priv->submit.nr_bos,
+		.relocs = VOID2U64(priv->submit.relocs),
+		.nr_relocs = priv->submit.nr_relocs,
+		.stream = VOID2U64(stream->buffer),
+		.stream_size = stream->offset * 4, /* in bytes */
+	};
+
+	ret = drmCommandWriteRead(gpu->dev->fd, DRM_ETNAVIV_GEM_SUBMIT,
+			&req, sizeof(req));
+
+	if (ret)
+		ERROR_MSG("submit failed: %d (%s)", ret, strerror(errno));
+	else
+		priv->last_timestamp = req.fence;
+
+	for (uint32_t i = 0; i < priv->nr_bos; i++) {
+		struct etna_bo *bo = priv->bos[i];
+
+		bo->current_stream = NULL;
+		etna_bo_del(bo);
+	}
+}
+
+void etna_cmd_stream_flush(struct etna_cmd_stream *stream)
+{
+	flush(stream);
+	reset_buffer(stream);
+}
+
+void etna_cmd_stream_finish(struct etna_cmd_stream *stream)
+{
+	struct etna_cmd_stream_priv *priv = etna_cmd_stream_priv(stream);
+
+	flush(stream);
+	etna_pipe_wait(priv->pipe, priv->last_timestamp, 5000);
+	reset_buffer(stream);
+}
+
+void etna_cmd_stream_reloc(struct etna_cmd_stream *stream, const struct etna_reloc *r)
+{
+	struct etna_cmd_stream_priv *priv = etna_cmd_stream_priv(stream);
+	struct drm_etnaviv_gem_submit_reloc *reloc;
+	uint32_t idx = APPEND(&priv->submit, relocs);
+	uint32_t addr = 0;
+
+	reloc = &priv->submit.relocs[idx];
+
+	reloc->reloc_idx = bo2idx(stream, r->bo, r->flags);
+	reloc->reloc_offset = r->offset;
+	reloc->submit_offset = stream->offset * 4; /* in bytes */
+	reloc->flags = 0;
+
+	etna_cmd_stream_emit(stream, addr);
+}
diff --git a/etnaviv/etnaviv_device.c b/etnaviv/etnaviv_device.c
new file mode 100644
index 0000000..f954ca4
--- /dev/null
+++ b/etnaviv/etnaviv_device.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include <xf86drm.h>
+#include <xf86atomic.h>
+
+#include "etnaviv_priv.h"
+#include "etnaviv_drmif.h"
+
+static pthread_mutex_t table_lock = PTHREAD_MUTEX_INITIALIZER;
+
+struct etna_device *etna_device_new(int fd)
+{
+	struct etna_device *dev = calloc(sizeof(*dev), 1);
+
+	if (!dev)
+		return NULL;
+
+	atomic_set(&dev->refcnt, 1);
+	dev->fd = fd;
+	dev->handle_table = drmHashCreate();
+	dev->name_table = drmHashCreate();
+	etna_bo_cache_init(&dev->bo_cache);
+
+	return dev;
+}
+
+struct etna_device *etna_device_ref(struct etna_device *dev)
+{
+	atomic_inc(&dev->refcnt);
+
+	return dev;
+}
+
+static void etna_device_del_impl(struct etna_device *dev)
+{
+	etna_bo_cache_cleanup(&dev->bo_cache, 0);
+	drmHashDestroy(dev->handle_table);
+	drmHashDestroy(dev->name_table);
+
+	free(dev);
+}
+
+drm_private void etna_device_del_locked(struct etna_device *dev)
+{
+	if (!atomic_dec_and_test(&dev->refcnt))
+		return;
+
+	etna_device_del_impl(dev);
+}
+
+void etna_device_del(struct etna_device *dev)
+{
+	if (!atomic_dec_and_test(&dev->refcnt))
+		return;
+
+	pthread_mutex_lock(&table_lock);
+	etna_device_del_impl(dev);
+	pthread_mutex_unlock(&table_lock);
+}
diff --git a/etnaviv/etnaviv_drm.h b/etnaviv/etnaviv_drm.h
new file mode 100644
index 0000000..462241c
--- /dev/null
+++ b/etnaviv/etnaviv_drm.h
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2015 Etnaviv Project
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ETNAVIV_DRM_H__
+#define __ETNAVIV_DRM_H__
+
+#include "drm.h"
+
+/* Please note that modifications to all structs defined here are
+ * subject to backwards-compatibility constraints:
+ *  1) Do not use pointers, use __u64 instead for 32 bit / 64 bit
+ *     user/kernel compatibility
+ *  2) Keep fields aligned to their size
+ *  3) Because of how drm_ioctl() works, we can add new fields at
+ *     the end of an ioctl if some care is taken: drm_ioctl() will
+ *     zero out the new fields at the tail of the ioctl, so a zero
+ *     value should have a backwards compatible meaning.  And for
+ *     output params, userspace won't see the newly added output
+ *     fields.. so that has to be somehow ok.
+ */
+
+/* timeouts are specified in clock-monotonic absolute times (to simplify
+ * restarting interrupted ioctls).  The following struct is logically the
+ * same as 'struct timespec' but 32/64b ABI safe.
+ */
+struct drm_etnaviv_timespec {
+	__s64 tv_sec;          /* seconds */
+	__s64 tv_nsec;         /* nanoseconds */
+};
+
+#define ETNAVIV_PARAM_GPU_MODEL                     0x01
+#define ETNAVIV_PARAM_GPU_REVISION                  0x02
+#define ETNAVIV_PARAM_GPU_FEATURES_0                0x03
+#define ETNAVIV_PARAM_GPU_FEATURES_1                0x04
+#define ETNAVIV_PARAM_GPU_FEATURES_2                0x05
+#define ETNAVIV_PARAM_GPU_FEATURES_3                0x06
+#define ETNAVIV_PARAM_GPU_FEATURES_4                0x07
+#define ETNAVIV_PARAM_GPU_FEATURES_5                0x08
+#define ETNAVIV_PARAM_GPU_FEATURES_6                0x09
+
+#define ETNAVIV_PARAM_GPU_STREAM_COUNT              0x10
+#define ETNAVIV_PARAM_GPU_REGISTER_MAX              0x11
+#define ETNAVIV_PARAM_GPU_THREAD_COUNT              0x12
+#define ETNAVIV_PARAM_GPU_VERTEX_CACHE_SIZE         0x13
+#define ETNAVIV_PARAM_GPU_SHADER_CORE_COUNT         0x14
+#define ETNAVIV_PARAM_GPU_PIXEL_PIPES               0x15
+#define ETNAVIV_PARAM_GPU_VERTEX_OUTPUT_BUFFER_SIZE 0x16
+#define ETNAVIV_PARAM_GPU_BUFFER_SIZE               0x17
+#define ETNAVIV_PARAM_GPU_INSTRUCTION_COUNT         0x18
+#define ETNAVIV_PARAM_GPU_NUM_CONSTANTS             0x19
+#define ETNAVIV_PARAM_GPU_NUM_VARYINGS              0x1a
+
+#define ETNA_MAX_PIPES 4
+
+struct drm_etnaviv_param {
+	__u32 pipe;           /* in */
+	__u32 param;          /* in, ETNAVIV_PARAM_x */
+	__u64 value;          /* out (get_param) or in (set_param) */
+};
+
+/*
+ * GEM buffers:
+ */
+
+#define ETNA_BO_CACHE_MASK   0x000f0000
+/* cache modes */
+#define ETNA_BO_CACHED       0x00010000
+#define ETNA_BO_WC           0x00020000
+#define ETNA_BO_UNCACHED     0x00040000
+/* map flags */
+#define ETNA_BO_FORCE_MMU    0x00100000
+
+struct drm_etnaviv_gem_new {
+	__u64 size;           /* in */
+	__u32 flags;          /* in, mask of ETNA_BO_x */
+	__u32 handle;         /* out */
+};
+
+struct drm_etnaviv_gem_info {
+	__u32 handle;         /* in */
+	__u32 pad;
+	__u64 offset;         /* out, offset to pass to mmap() */
+};
+
+#define ETNA_PREP_READ        0x01
+#define ETNA_PREP_WRITE       0x02
+#define ETNA_PREP_NOSYNC      0x04
+
+struct drm_etnaviv_gem_cpu_prep {
+	__u32 handle;         /* in */
+	__u32 op;             /* in, mask of ETNA_PREP_x */
+	struct drm_etnaviv_timespec timeout;   /* in */
+};
+
+struct drm_etnaviv_gem_cpu_fini {
+	__u32 handle;         /* in */
+	__u32 flags;          /* in, placeholder for now, no defined values */
+};
+
+/*
+ * Cmdstream Submission:
+ */
+
+/* The value written into the cmdstream is logically:
+ * relocbuf->gpuaddr + reloc_offset
+ *
+ * NOTE that reloc's must be sorted by order of increasing submit_offset,
+ * otherwise EINVAL.
+ */
+struct drm_etnaviv_gem_submit_reloc {
+	__u32 submit_offset;  /* in, offset from submit_bo */
+	__u32 reloc_idx;      /* in, index of reloc_bo buffer */
+	__u64 reloc_offset;   /* in, offset from start of reloc_bo */
+	__u32 flags;          /* in, placeholder for now, no defined values */
+};
+
+/* Each buffer referenced elsewhere in the cmdstream submit (ie. the
+ * cmdstream buffer(s) themselves or reloc entries) has one (and only
+ * one) entry in the submit->bos[] table.
+ *
+ * As a optimization, the current buffer (gpu virtual address) can be
+ * passed back through the 'presumed' field.  If on a subsequent reloc,
+ * userspace passes back a 'presumed' address that is still valid,
+ * then patching the cmdstream for this entry is skipped.  This can
+ * avoid kernel needing to map/access the cmdstream bo in the common
+ * case.
+ */
+#define ETNA_SUBMIT_BO_READ             0x0001
+#define ETNA_SUBMIT_BO_WRITE            0x0002
+struct drm_etnaviv_gem_submit_bo {
+	__u32 flags;          /* in, mask of ETNA_SUBMIT_BO_x */
+	__u32 handle;         /* in, GEM handle */
+	__u64 presumed;       /* in/out, presumed buffer address */
+};
+
+/* Each cmdstream submit consists of a table of buffers involved, and
+ * one or more cmdstream buffers.  This allows for conditional execution
+ * (context-restore), and IB buffers needed for per tile/bin draw cmds.
+ */
+#define ETNA_PIPE_3D      0x00
+#define ETNA_PIPE_2D      0x01
+#define ETNA_PIPE_VG      0x02
+struct drm_etnaviv_gem_submit {
+	__u32 fence;          /* out */
+	__u32 pipe;           /* in */
+	__u32 exec_state;     /* in, initial execution state (ETNA_PIPE_x) */
+	__u32 nr_bos;         /* in, number of submit_bo's */
+	__u32 nr_relocs;      /* in, number of submit_reloc's */
+	__u32 stream_size;    /* in, cmdstream size */
+	__u64 bos;            /* in, ptr to array of submit_bo's */
+	__u64 relocs;         /* in, ptr to array of submit_reloc's */
+	__u64 stream;         /* in, ptr to cmdstream */
+};
+
+/* The normal way to synchronize with the GPU is just to CPU_PREP on
+ * a buffer if you need to access it from the CPU (other cmdstream
+ * submission from same or other contexts, PAGE_FLIP ioctl, etc, all
+ * handle the required synchronization under the hood).  This ioctl
+ * mainly just exists as a way to implement the gallium pipe_fence
+ * APIs without requiring a dummy bo to synchronize on.
+ */
+#define ETNA_WAIT_NONBLOCK      0x01
+struct drm_etnaviv_wait_fence {
+	__u32 pipe;           /* in */
+	__u32 fence;          /* in */
+	__u32 flags;          /* in, mask of ETNA_WAIT_x  */
+	__u32 pad;
+	struct drm_etnaviv_timespec timeout;   /* in */
+};
+
+#define ETNA_USERPTR_READ	0x01
+#define ETNA_USERPTR_WRITE	0x02
+struct drm_etnaviv_gem_userptr {
+	__u64 user_ptr;	/* in, page aligned user pointer */
+	__u64 user_size;	/* in, page aligned user size */
+	__u32 flags;		/* in, flags */
+	__u32 handle;	/* out, non-zero handle */
+};
+
+struct drm_etnaviv_gem_wait {
+	__u32 pipe;				/* in */
+	__u32 handle;				/* in, bo to be waited for */
+	__u32 flags;                            /* in, mask of ETNA_WAIT_x  */
+	__u32 pad;
+	struct drm_etnaviv_timespec timeout;	/* in */
+};
+
+#define DRM_ETNAVIV_GET_PARAM          0x00
+/* placeholder:
+#define DRM_ETNAVIV_SET_PARAM          0x01
+ */
+#define DRM_ETNAVIV_GEM_NEW            0x02
+#define DRM_ETNAVIV_GEM_INFO           0x03
+#define DRM_ETNAVIV_GEM_CPU_PREP       0x04
+#define DRM_ETNAVIV_GEM_CPU_FINI       0x05
+#define DRM_ETNAVIV_GEM_SUBMIT         0x06
+#define DRM_ETNAVIV_WAIT_FENCE         0x07
+#define DRM_ETNAVIV_GEM_USERPTR        0x08
+#define DRM_ETNAVIV_GEM_WAIT           0x09
+#define DRM_ETNAVIV_NUM_IOCTLS         0x0a
+
+#define DRM_IOCTL_ETNAVIV_GET_PARAM    DRM_IOWR(DRM_COMMAND_BASE + DRM_ETNAVIV_GET_PARAM, struct drm_etnaviv_param)
+#define DRM_IOCTL_ETNAVIV_GEM_NEW      DRM_IOWR(DRM_COMMAND_BASE + DRM_ETNAVIV_GEM_NEW, struct drm_etnaviv_gem_new)
+#define DRM_IOCTL_ETNAVIV_GEM_INFO     DRM_IOWR(DRM_COMMAND_BASE + DRM_ETNAVIV_GEM_INFO, struct drm_etnaviv_gem_info)
+#define DRM_IOCTL_ETNAVIV_GEM_CPU_PREP DRM_IOW(DRM_COMMAND_BASE + DRM_ETNAVIV_GEM_CPU_PREP, struct drm_etnaviv_gem_cpu_prep)
+#define DRM_IOCTL_ETNAVIV_GEM_CPU_FINI DRM_IOW(DRM_COMMAND_BASE + DRM_ETNAVIV_GEM_CPU_FINI, struct drm_etnaviv_gem_cpu_fini)
+#define DRM_IOCTL_ETNAVIV_GEM_SUBMIT   DRM_IOWR(DRM_COMMAND_BASE + DRM_ETNAVIV_GEM_SUBMIT, struct drm_etnaviv_gem_submit)
+#define DRM_IOCTL_ETNAVIV_WAIT_FENCE   DRM_IOW(DRM_COMMAND_BASE + DRM_ETNAVIV_WAIT_FENCE, struct drm_etnaviv_wait_fence)
+#define DRM_IOCTL_ETNAVIV_GEM_USERPTR  DRM_IOWR(DRM_COMMAND_BASE + DRM_ETNAVIV_GEM_USERPTR, struct drm_etnaviv_gem_userptr)
+#define DRM_IOCTL_ETNAVIV_GEM_WAIT     DRM_IOW(DRM_COMMAND_BASE + DRM_ETNAVIV_GEM_WAIT, struct drm_etnaviv_gem_wait)
+
+#endif /* __ETNAVIV_DRM_H__ */
diff --git a/etnaviv/etnaviv_drmif.h b/etnaviv/etnaviv_drmif.h
new file mode 100644
index 0000000..979b16a
--- /dev/null
+++ b/etnaviv/etnaviv_drmif.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2014-2015 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifndef ETNAVIV_DRMIF_H_
+#define ETNAVIV_DRMIF_H_
+
+#include <xf86drm.h>
+#include <stdint.h>
+
+struct etna_bo;
+struct etna_pipe;
+struct etna_gpu;
+struct etna_device;
+struct etna_cmd_stream;
+
+enum etna_pipe_id {
+	ETNA_PIPE_3D = 0,
+	ETNA_PIPE_2D = 1,
+	ETNA_PIPE_VG = 2,
+	ETNA_PIPE_MAX
+};
+
+enum etna_param_id {
+	ETNA_GPU_MODEL                     = 0x1,
+	ETNA_GPU_REVISION                  = 0x2,
+	ETNA_GPU_FEATURES_0                = 0x3,
+	ETNA_GPU_FEATURES_1                = 0x4,
+	ETNA_GPU_FEATURES_2                = 0x5,
+	ETNA_GPU_FEATURES_3                = 0x6,
+	ETNA_GPU_FEATURES_4                = 0x7,
+	ETNA_GPU_FEATURES_5                = 0x8,
+	ETNA_GPU_FEATURES_6                = 0x9,
+
+	ETNA_GPU_STREAM_COUNT              = 0x10,
+	ETNA_GPU_REGISTER_MAX              = 0x11,
+	ETNA_GPU_THREAD_COUNT              = 0x12,
+	ETNA_GPU_VERTEX_CACHE_SIZE         = 0x13,
+	ETNA_GPU_SHADER_CORE_COUNT         = 0x14,
+	ETNA_GPU_PIXEL_PIPES               = 0x15,
+	ETNA_GPU_VERTEX_OUTPUT_BUFFER_SIZE = 0x16,
+	ETNA_GPU_BUFFER_SIZE               = 0x17,
+	ETNA_GPU_INSTRUCTION_COUNT         = 0x18,
+	ETNA_GPU_NUM_CONSTANTS             = 0x19,
+	ETNA_GPU_NUM_VARYINGS              = 0x1a
+};
+
+/* bo flags: */
+#define DRM_ETNA_GEM_CACHE_CACHED       0x00010000
+#define DRM_ETNA_GEM_CACHE_WC           0x00020000
+#define DRM_ETNA_GEM_CACHE_UNCACHED     0x00040000
+#define DRM_ETNA_GEM_CACHE_MASK         0x000f0000
+/* map flags */
+#define DRM_ETNA_GEM_FORCE_MMU          0x00100000
+
+/* bo access flags: (keep aligned to ETNA_PREP_x) */
+#define DRM_ETNA_PREP_READ              0x01
+#define DRM_ETNA_PREP_WRITE             0x02
+#define DRM_ETNA_PREP_NOSYNC            0x04
+
+/* device functions:
+ */
+
+struct etna_device *etna_device_new(int fd);
+struct etna_device *etna_device_ref(struct etna_device *dev);
+void etna_device_del(struct etna_device *dev);
+
+/* gpu functions:
+ */
+
+struct etna_gpu *etna_gpu_new(struct etna_device *dev, unsigned int core);
+void etna_gpu_del(struct etna_gpu *gpu);
+int etna_gpu_get_param(struct etna_gpu *gpu, enum etna_param_id param,
+		uint64_t *value);
+
+
+/* pipe functions:
+ */
+
+struct etna_pipe *etna_pipe_new(struct etna_gpu *gpu, enum etna_pipe_id id);
+void etna_pipe_del(struct etna_pipe *pipe);
+int etna_pipe_wait(struct etna_pipe *pipe, uint32_t timestamp, uint32_t ms);
+
+
+/* buffer-object functions:
+ */
+
+struct etna_bo *etna_bo_new(struct etna_device *dev,
+		uint32_t size, uint32_t flags);
+struct etna_bo *etna_bo_from_handle(struct etna_device *dev,
+		uint32_t handle, uint32_t size);
+struct etna_bo *etna_bo_from_name(struct etna_device *dev, uint32_t name);
+struct etna_bo *etna_bo_from_dmabuf(struct etna_device *dev, int fd);
+struct etna_bo *etna_bo_ref(struct etna_bo *bo);
+void etna_bo_del(struct etna_bo *bo);
+int etna_bo_get_name(struct etna_bo *bo, uint32_t *name);
+uint32_t etna_bo_handle(struct etna_bo *bo);
+int etna_bo_dmabuf(struct etna_bo *bo);
+uint32_t etna_bo_size(struct etna_bo *bo);
+void * etna_bo_map(struct etna_bo *bo);
+int etna_bo_cpu_prep(struct etna_bo *bo, uint32_t op);
+void etna_bo_cpu_fini(struct etna_bo *bo);
+
+
+/* cmd stream functions:
+ */
+
+struct etna_cmd_stream {
+	uint32_t *buffer;
+	uint32_t offset;	/* in 32-bit words */
+	uint32_t size;		/* in 32-bit words */
+};
+
+struct etna_cmd_stream *etna_cmd_stream_new(struct etna_pipe *pipe, uint32_t size,
+		void (*reset_notify)(struct etna_cmd_stream *stream, void *priv),
+		void *priv);
+void etna_cmd_stream_del(struct etna_cmd_stream *stream);
+uint32_t etna_cmd_stream_timestamp(struct etna_cmd_stream *stream);
+void etna_cmd_stream_flush(struct etna_cmd_stream *stream);
+void etna_cmd_stream_finish(struct etna_cmd_stream *stream);
+
+static inline uint32_t etna_cmd_stream_avail(struct etna_cmd_stream *stream)
+{
+	static const uint32_t END_CLEARANCE = 2; /* LINK op code */
+
+	return stream->size - stream->offset - END_CLEARANCE;
+}
+
+static inline void etna_cmd_stream_reserve(struct etna_cmd_stream *stream, size_t n)
+{
+	if (etna_cmd_stream_avail(stream) < n)
+		etna_cmd_stream_flush(stream);
+}
+
+static inline void etna_cmd_stream_emit(struct etna_cmd_stream *stream, uint32_t data)
+{
+	stream->buffer[stream->offset++] = data;
+}
+
+static inline uint32_t etna_cmd_stream_get(struct etna_cmd_stream *stream, uint32_t offset)
+{
+	return stream->buffer[offset];
+}
+
+static inline void etna_cmd_stream_set(struct etna_cmd_stream *stream, uint32_t offset,
+		uint32_t data)
+{
+	stream->buffer[offset] = data;
+}
+
+static inline uint32_t etna_cmd_stream_offset(struct etna_cmd_stream *stream)
+{
+	return stream->offset;
+}
+
+struct etna_reloc {
+	struct etna_bo *bo;
+#define ETNA_RELOC_READ             0x0001
+#define ETNA_RELOC_WRITE            0x0002
+	uint32_t flags;
+	uint32_t offset;
+};
+
+void etna_cmd_stream_reloc(struct etna_cmd_stream *stream, const struct etna_reloc *r);
+
+#endif /* ETNAVIV_DRMIF_H_ */
diff --git a/etnaviv/etnaviv_gpu.c b/etnaviv/etnaviv_gpu.c
new file mode 100644
index 0000000..35dec6c
--- /dev/null
+++ b/etnaviv/etnaviv_gpu.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "etnaviv_priv.h"
+#include "etnaviv_drmif.h"
+
+static uint64_t get_param(struct etna_device *dev, uint32_t core, uint32_t param)
+{
+	struct drm_etnaviv_param req = {
+		.pipe = core,
+		.param = param,
+	};
+	int ret;
+
+	ret = drmCommandWriteRead(dev->fd, DRM_ETNAVIV_GET_PARAM, &req, sizeof(req));
+	if (ret) {
+		ERROR_MSG("get-param (%x) failed! %d (%s)", param, ret, strerror(errno));
+		return 0;
+	}
+
+	return req.value;
+}
+
+struct etna_gpu *etna_gpu_new(struct etna_device *dev, unsigned int core)
+{
+	struct etna_gpu *gpu;
+
+	gpu = calloc(1, sizeof(*gpu));
+	if (!gpu) {
+		ERROR_MSG("allocation failed");
+		goto fail;
+	}
+
+	gpu->dev = dev;
+	gpu->core = core;
+
+	/* get specs from kernel space */
+	gpu->specs.model    	= get_param(dev, core, ETNAVIV_PARAM_GPU_MODEL);
+	gpu->specs.revision 	= get_param(dev, core, ETNAVIV_PARAM_GPU_REVISION);
+	gpu->specs.features[0] = get_param(dev, core, ETNAVIV_PARAM_GPU_FEATURES_0);
+	gpu->specs.features[1] = get_param(dev, core, ETNAVIV_PARAM_GPU_FEATURES_1);
+	gpu->specs.features[2] = get_param(dev, core, ETNAVIV_PARAM_GPU_FEATURES_2);
+	gpu->specs.features[3] = get_param(dev, core, ETNAVIV_PARAM_GPU_FEATURES_3);
+	gpu->specs.features[4] = get_param(dev, core, ETNAVIV_PARAM_GPU_FEATURES_4);
+	gpu->specs.features[5] = get_param(dev, core, ETNAVIV_PARAM_GPU_FEATURES_5);
+	gpu->specs.features[6] = get_param(dev, core, ETNAVIV_PARAM_GPU_FEATURES_6);
+	gpu->specs.stream_count = get_param(dev, core, ETNA_GPU_STREAM_COUNT);
+	gpu->specs.register_max = get_param(dev, core, ETNA_GPU_REGISTER_MAX);
+	gpu->specs.thread_count = get_param(dev, core, ETNA_GPU_THREAD_COUNT);
+	gpu->specs.vertex_cache_size = get_param(dev, core, ETNA_GPU_VERTEX_CACHE_SIZE);
+	gpu->specs.shader_core_count = get_param(dev, core, ETNA_GPU_SHADER_CORE_COUNT);
+	gpu->specs.pixel_pipes = get_param(dev, core, ETNA_GPU_PIXEL_PIPES);
+	gpu->specs.vertex_output_buffer_size = get_param(dev, core, ETNA_GPU_VERTEX_OUTPUT_BUFFER_SIZE);
+	gpu->specs.buffer_size = get_param(dev, core, ETNA_GPU_BUFFER_SIZE);
+	gpu->specs.instruction_count = get_param(dev, core, ETNA_GPU_INSTRUCTION_COUNT);
+	gpu->specs.num_constants = get_param(dev, core, ETNA_GPU_NUM_CONSTANTS);
+	gpu->specs.num_varyings = get_param(dev, core, ETNA_GPU_NUM_VARYINGS);
+
+	if (!gpu->specs.model)
+		goto fail;
+
+	INFO_MSG(" GPU model:          0x%x (rev %x)", gpu->specs.model, gpu->specs.revision);
+
+	return gpu;
+fail:
+	if (gpu)
+		etna_gpu_del(gpu);
+
+	return NULL;
+}
+
+void etna_gpu_del(struct etna_gpu *gpu)
+{
+	free(gpu);
+}
+
+int etna_gpu_get_param(struct etna_gpu *gpu, enum etna_param_id param,
+		uint64_t *value)
+{
+	switch(param) {
+	case ETNA_GPU_MODEL:
+		*value = gpu->specs.model;
+		return 0;
+	case ETNA_GPU_REVISION:
+		*value = gpu->specs.revision;
+		return 0;
+	case ETNA_GPU_FEATURES_0:
+		*value = gpu->specs.features[0];
+		return 0;
+	case ETNA_GPU_FEATURES_1:
+		*value = gpu->specs.features[1];
+		return 0;
+	case ETNA_GPU_FEATURES_2:
+		*value = gpu->specs.features[2];
+		return 0;
+	case ETNA_GPU_FEATURES_3:
+		*value = gpu->specs.features[3];
+		return 0;
+	case ETNA_GPU_FEATURES_4:
+		*value = gpu->specs.features[4];
+		return 0;
+	case ETNA_GPU_FEATURES_5:
+		*value = gpu->specs.features[5];
+		return 0;
+	case ETNA_GPU_FEATURES_6:
+		*value = gpu->specs.features[6];
+		return 0;
+	case ETNA_GPU_STREAM_COUNT:
+		*value = gpu->specs.stream_count;
+		return 0;
+	case ETNA_GPU_REGISTER_MAX:
+		*value = gpu->specs.register_max;
+		return 0;
+	case ETNA_GPU_THREAD_COUNT:
+		*value = gpu->specs.thread_count;
+		return 0;
+	case ETNA_GPU_VERTEX_CACHE_SIZE:
+		*value = gpu->specs.vertex_cache_size;
+		return 0;
+	case ETNA_GPU_SHADER_CORE_COUNT:
+		*value = gpu->specs.shader_core_count;
+		return 0;
+	case ETNA_GPU_PIXEL_PIPES:
+		*value = gpu->specs.pixel_pipes;
+		return 0;
+	case ETNA_GPU_VERTEX_OUTPUT_BUFFER_SIZE:
+		*value = gpu->specs.vertex_output_buffer_size;
+		return 0;
+	case ETNA_GPU_BUFFER_SIZE:
+		*value = gpu->specs.buffer_size;
+		return 0;
+	case ETNA_GPU_INSTRUCTION_COUNT:
+		*value = gpu->specs.instruction_count;
+		return 0;
+	case ETNA_GPU_NUM_CONSTANTS:
+		*value = gpu->specs.num_constants;
+		return 0;
+	case ETNA_GPU_NUM_VARYINGS:
+		*value = gpu->specs.num_varyings;
+		return 0;
+
+	default:
+		ERROR_MSG("invalid param id: %d", param);
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/etnaviv/etnaviv_pipe.c b/etnaviv/etnaviv_pipe.c
new file mode 100644
index 0000000..402b71d
--- /dev/null
+++ b/etnaviv/etnaviv_pipe.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014-2015 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "etnaviv_priv.h"
+
+int etna_pipe_wait(struct etna_pipe *pipe, uint32_t timestamp, uint32_t ms)
+{
+	struct etna_device *dev = pipe->gpu->dev;
+	int ret;
+
+	struct drm_etnaviv_wait_fence req = {
+		.pipe = pipe->gpu->core,
+		.fence = timestamp,
+	};
+
+	if (ms == 0)
+		req.flags |= ETNA_WAIT_NONBLOCK;
+
+	get_abs_timeout(&req.timeout, ms);
+
+	ret = drmCommandWrite(dev->fd, DRM_ETNAVIV_WAIT_FENCE, &req, sizeof(req));
+	if (ret) {
+		ERROR_MSG("wait-fence failed! %d (%s)", ret, strerror(errno));
+		return ret;
+	}
+
+	return 0;
+}
+
+void etna_pipe_del(struct etna_pipe *pipe)
+{
+	free(pipe);
+}
+
+struct etna_pipe *etna_pipe_new(struct etna_gpu *gpu, enum etna_pipe_id id)
+{
+	struct etna_pipe *pipe;
+
+	pipe = calloc(1, sizeof(*pipe));
+	if (!pipe) {
+		ERROR_MSG("allocation failed");
+		goto fail;
+	}
+
+	pipe->id = id;
+	pipe->gpu = gpu;
+
+	return pipe;
+fail:
+	return NULL;
+}
diff --git a/etnaviv/etnaviv_priv.h b/etnaviv/etnaviv_priv.h
new file mode 100644
index 0000000..42d9a45
--- /dev/null
+++ b/etnaviv/etnaviv_priv.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014-2015 Etnaviv Project
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ *    Christian Gmeiner <christian.gmeiner@gmail.com>
+ */
+
+#ifndef ETNAVIV_PRIV_H_
+#define ETNAVIV_PRIV_H_
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "libdrm_macros.h"
+#include "xf86drm.h"
+#include "xf86atomic.h"
+
+#include "util_double_list.h"
+
+#include "etnaviv_drmif.h"
+//#include "drm.h"
+#include "etnaviv_drm.h"
+
+#define VIV_FEATURES_WORD_COUNT 6
+
+struct etna_specs {
+	uint32_t model;
+	uint32_t revision;
+	uint32_t features[VIV_FEATURES_WORD_COUNT + 1];
+	uint32_t stream_count;
+	uint32_t register_max;
+	uint32_t thread_count;
+	uint32_t shader_core_count;
+	uint32_t vertex_cache_size;
+	uint32_t vertex_output_buffer_size;
+	uint32_t pixel_pipes;
+	uint32_t instruction_count;
+	uint32_t num_constants;
+	uint32_t num_varyings;
+	uint32_t buffer_size;
+};
+
+struct etna_bo_bucket {
+	uint32_t size;
+	struct list_head list;
+};
+
+struct etna_bo_cache {
+	struct etna_bo_bucket cache_bucket[14 * 4];
+	unsigned num_buckets;
+	time_t time;
+};
+
+struct etna_device {
+	int fd;
+	atomic_t refcnt;
+
+	/* tables to keep track of bo's, to avoid "evil-twin" etna_bo objects:
+	 *
+	 *   handle_table: maps handle to etna_bo
+	 *   name_table: maps flink name to etna_bo
+	 *
+	 * We end up needing two tables, because DRM_IOCTL_GEM_OPEN always
+	 * returns a new handle.  So we need to figure out if the bo is already
+	 * open in the process first, before calling gem-open.
+	 */
+	void *handle_table, *name_table;
+
+	struct etna_bo_cache bo_cache;
+};
+
+drm_private void etna_bo_cache_init(struct etna_bo_cache *cache);
+drm_private void etna_bo_cache_cleanup(struct etna_bo_cache *cache, time_t time);
+drm_private struct etna_bo *etna_bo_cache_alloc(struct etna_bo_cache *cache,
+		uint32_t *size, uint32_t flags);
+drm_private int etna_bo_cache_free(struct etna_bo_cache *cache, struct etna_bo *bo);
+
+/* for where @table_lock is already held: */
+drm_private void etna_device_del_locked(struct etna_device *dev);
+
+/* a GEM buffer object allocated from the DRM device */
+struct etna_bo {
+	struct etna_device      *dev;
+	void            *map;           /* userspace mmap'ing (if there is one) */
+	uint32_t        size;
+	uint32_t        handle;
+	uint32_t        flags;
+	uint32_t        name;           /* flink global handle (DRI2 name) */
+	uint64_t        offset;         /* offset to mmap() */
+	atomic_t        refcnt;
+
+	/* in the common case, a bo won't be referenced by more than a single
+	 * command stream.  So to avoid looping over all the bo's in the
+	 * reloc table to find the idx of a bo that might already be in the
+	 * table, we cache the idx in the bo.  But in order to detect the
+	 * slow-path where bo is ref'd in multiple streams, we also must track
+	 * the current_stream for which the idx is valid.  See bo2idx().
+	 */
+	struct etna_cmd_stream *current_stream;
+	uint32_t idx;
+
+	int reuse;
+	struct list_head list;   /* bucket-list entry */
+	time_t free_time;        /* time when added to bucket-list */
+};
+
+struct etna_gpu {
+	struct etna_device *dev;
+	struct etna_specs specs;
+	uint32_t core;
+};
+
+struct etna_pipe {
+	enum etna_pipe_id id;
+	struct etna_gpu *gpu;
+};
+
+struct etna_cmd_stream_priv {
+	struct etna_cmd_stream base;
+	struct etna_pipe *pipe;
+
+	uint32_t last_timestamp;
+
+	/* submit ioctl related tables: */
+	struct {
+		/* bo's table: */
+		struct drm_etnaviv_gem_submit_bo *bos;
+		uint32_t nr_bos, max_bos;
+
+		/* reloc's table: */
+		struct drm_etnaviv_gem_submit_reloc *relocs;
+		uint32_t nr_relocs, max_relocs;
+	} submit;
+
+	/* should have matching entries in submit.bos: */
+	struct etna_bo **bos;
+	uint32_t nr_bos, max_bos;
+
+	/* notify callback if buffer reset happend */
+	void (*reset_notify)(struct etna_cmd_stream *stream, void *priv);
+	void *reset_notify_priv;
+};
+
+#define ALIGN(v,a) (((v) + (a) - 1) & ~((a) - 1))
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+#define enable_debug 1  /* TODO make dynamic */
+
+#define INFO_MSG(fmt, ...) \
+		do { drmMsg("[I] "fmt " (%s:%d)\n", \
+				##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0)
+#define DEBUG_MSG(fmt, ...) \
+		do if (enable_debug) { drmMsg("[D] "fmt " (%s:%d)\n", \
+				##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0)
+#define WARN_MSG(fmt, ...) \
+		do { drmMsg("[W] "fmt " (%s:%d)\n", \
+				##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0)
+#define ERROR_MSG(fmt, ...) \
+		do { drmMsg("[E] " fmt " (%s:%d)\n", \
+				##__VA_ARGS__, __FUNCTION__, __LINE__); } while (0)
+
+#define VOID2U64(x) ((uint64_t)(unsigned long)(x))
+
+static inline void get_abs_timeout(struct drm_etnaviv_timespec *tv, uint32_t ms)
+{
+	struct timespec t;
+	uint32_t s = ms / 1000;
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	tv->tv_sec = t.tv_sec + s;
+	tv->tv_nsec = t.tv_nsec + ((ms - (s * 1000)) * 1000000);
+}
+
+#endif /* ETNAVIV_PRIV_H_ */
diff --git a/etnaviv/libdrm_etnaviv.pc.in b/etnaviv/libdrm_etnaviv.pc.in
new file mode 100644
index 0000000..13fed01
--- /dev/null
+++ b/etnaviv/libdrm_etnaviv.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libdrm_etnaviv
+Description: Userspace interface to etnaviv kernel DRM services
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -ldrm_etnaviv
+Cflags: -I${includedir} -I${includedir}/libdrm
+Requires.private: libdrm
