diff --git a/arch/x86/include/asm/numachip/numachip_csr.h b/arch/x86/include/asm/numachip/numachip_csr.h
index 29719ee..9548d76 100644
--- a/arch/x86/include/asm/numachip/numachip_csr.h
+++ b/arch/x86/include/asm/numachip/numachip_csr.h
@@ -63,6 +63,7 @@ static inline void write_lcsr(unsigned long offset, unsigned int val)
 #define NUMACHIP2_TIMER_INT       0x200008
 #define NUMACHIP2_TIMER_NOW       0x200018
 #define NUMACHIP2_TIMER_RESET     0x200020
+#define NUMACHIP2_BTE_OFFSET      0x400000
 
 static inline void __iomem *numachip2_lcsr_address(unsigned long offset)
 {
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b2fb6dbf..63ff202 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -57,3 +57,4 @@ obj-$(CONFIG_ECHO)		+= echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
 obj-$(CONFIG_PANEL)             += panel.o
+obj-$(CONFIG_X86_NUMACHIP)	+= ncbte.o
diff --git a/drivers/misc/ncbte.c b/drivers/misc/ncbte.c
new file mode 100644
index 0000000..e9eb0cc
--- /dev/null
+++ b/drivers/misc/ncbte.c
@@ -0,0 +1,394 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Block Transfer Engine driver for NumaChip node controllers
+ *
+ * Copyright (C) 2015 Numascale AS. All rights reserved.
+ *
+ * Send feedback to <support@numascale.com>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/highmem.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+
+#include <asm/numachip/numachip.h>
+#include <asm/numachip/numachip_csr.h>
+#include <linux/ncbte_io.h>
+
+struct ncbte_file {
+	spinlock_t pin_lock;		/* lock for pinned memory */
+	struct list_head pin_list;	/* list of pinned memory */
+};
+
+struct ncbte_map {
+	void *u_vaddr;			/* user-space vaddr/non-aligned */
+	struct page **page_list;	/* list of pages used by user buff */
+	uint64_t *phys_addr;            /* array of page physical addresses */
+	unsigned int nr_pages;		/* number of pages */
+	unsigned int size;		/* size in bytes */
+	struct list_head pin_list;	/* list of pinned memory for dev */
+};
+
+/**
+ * ncbte_search_pin() - Search for the mapping for a userspace address
+ * @cfile:	Descriptor of opened file
+ * @u_addr:	User virtual address
+ * @size:	Size of buffer
+ *
+ * Return: Pointer to the corresponding mapping	NULL if not found
+ */
+static struct ncbte_map *ncbte_search_pin(struct ncbte_file *cfile,
+					  unsigned long u_addr,
+					  unsigned int size)
+{
+	unsigned long flags;
+	struct ncbte_map *m;
+
+	spin_lock_irqsave(&cfile->pin_lock, flags);
+
+	list_for_each_entry(m, &cfile->pin_list, pin_list) {
+		if ((((u64)m->u_vaddr) <= (u_addr)) &&
+		    (((u64)m->u_vaddr + m->size) >= (u_addr + size))) {
+			spin_unlock_irqrestore(&cfile->pin_lock, flags);
+			return m;
+		}
+	}
+	spin_unlock_irqrestore(&cfile->pin_lock, flags);
+	return NULL;
+}
+
+/**
+ * free_user_pages() - Give pinned pages back
+ *
+ */
+static int free_user_pages(struct page **page_list, unsigned int nr_pages)
+{
+	unsigned int i;
+
+	for (i = 0; i < nr_pages; i++) {
+		if (page_list[i] != NULL)
+			put_page(page_list[i]);
+	}
+	return 0;
+}
+
+/**
+ * ncbte_user_vmap() - Map user-space memory to virtual kernel memory
+ * @m:          mapping params
+ * @uaddr:      user virtual address
+ * @size:       size of memory to be mapped
+ *
+ * Return: 0 if success
+ */
+static int ncbte_user_vmap(struct ncbte_map *m, void *uaddr, unsigned long size)
+{
+	int rc = -EINVAL;
+	unsigned long data, offs;
+	unsigned int i;
+
+	if ((uaddr == NULL) || (size == 0)) {
+		m->size = 0;	/* mark unused and not added */
+		return -EINVAL;
+	}
+	m->u_vaddr = uaddr;
+	m->size    = size;
+
+	/* determine space needed for page_list. */
+	data = (unsigned long)uaddr;
+	offs = offset_in_page(data);
+	m->nr_pages = DIV_ROUND_UP(offs + size, PAGE_SIZE);
+
+	m->page_list = kcalloc(m->nr_pages,
+			       sizeof(struct page *) + sizeof(uint64_t),
+			       GFP_KERNEL);
+	if (!m->page_list) {
+		pr_err("ncbte failed to allocate structure for %d pages\n", m->nr_pages);
+		m->nr_pages = 0;
+		m->u_vaddr = NULL;
+		m->size = 0;	/* mark unused and not added */
+		return -ENOMEM;
+	}
+	m->phys_addr = (uint64_t *)(m->page_list + m->nr_pages);
+
+	/* pin user pages in memory */
+	rc = __get_user_pages_fast(data & PAGE_MASK, m->nr_pages, 0, m->page_list);
+	if (rc < 0)
+		goto fail_get_user_pages;
+
+	/* assumption: get_user_pages can be killed by signals. */
+	if (rc < m->nr_pages) {
+		free_user_pages(m->page_list, rc);
+		rc = -EFAULT;
+		goto fail_get_user_pages;
+	}
+
+	for (i = 0; i < m->nr_pages; i++)
+		m->phys_addr[i] = (uint64_t)page_to_phys(m->page_list[i]);
+
+	return 0;
+
+ fail_get_user_pages:
+	kfree(m->page_list);
+	m->page_list = NULL;
+	m->phys_addr = NULL;
+	m->nr_pages = 0;
+	m->u_vaddr = NULL;
+	m->size = 0;		/* mark unused and not added */
+	return rc;
+}
+
+/**
+ * ncbte_user_vunmap() - Undo mapping of user-space mem to virtual kernel
+ *                       memory
+ * @m:          mapping params
+ */
+int ncbte_user_vunmap(struct ncbte_map *m)
+{
+	if (m->page_list) {
+		free_user_pages(m->page_list, m->nr_pages);
+		kfree(m->page_list);
+		m->page_list = NULL;
+		m->phys_addr = NULL;
+		m->nr_pages = 0;
+	}
+
+	m->u_vaddr = NULL;
+	m->size = 0;		/* mark as unused and not added */
+	return 0;
+}
+
+static int ncbte_pin_mem(struct ncbte_file *cfile, unsigned long arg)
+{
+	struct ncbte_mem __user *io = (struct ncbte_mem __user *)arg;
+	struct ncbte_mem m;
+	struct ncbte_map *bte_map;
+	unsigned long map_addr, map_size;
+	unsigned long flags;
+	int rc;
+
+	if (copy_from_user(&m, io, sizeof(m)))
+		return -EFAULT;
+
+	if (m.addr == 0x0)
+		return -EINVAL;
+
+	map_addr = (m.addr & PAGE_MASK);
+	map_size = round_up(m.size + (m.addr & ~PAGE_MASK), PAGE_SIZE);
+
+	bte_map = kzalloc(sizeof(struct ncbte_map), GFP_ATOMIC);
+	if (bte_map == NULL)
+		return -ENOMEM;
+
+	rc = ncbte_user_vmap(bte_map, (void *)map_addr, map_size);
+	if (rc != 0) {
+		kfree(bte_map);
+		return rc;
+	}
+
+	spin_lock_irqsave(&cfile->pin_lock, flags);
+	list_add(&bte_map->pin_list, &cfile->pin_list);
+	spin_unlock_irqrestore(&cfile->pin_lock, flags);
+
+	/* If we have room to copy the physical map, do it now */
+	if (m.phys_addr && m.nr_pages >= bte_map->nr_pages) {
+		if (copy_to_user((void __user *)m.phys_addr, bte_map->phys_addr, bte_map->nr_pages * sizeof(uint64_t))) {
+			pr_err("ncbte failed to copy physical address map to user space\n");
+			return -EFAULT;
+		}
+	}
+	put_user(bte_map->nr_pages, &io->nr_pages);
+
+	return 0;
+}
+
+static int ncbte_unpin_mem(struct ncbte_file *cfile, unsigned long arg)
+{
+	struct ncbte_mem __user *io = (struct ncbte_mem __user *)arg;
+	struct ncbte_mem m;
+	struct ncbte_map *bte_map;
+	unsigned long map_addr, map_size;
+	unsigned long flags;
+
+	if (copy_from_user(&m, io, sizeof(m)))
+		return -EFAULT;
+
+	if (m.addr == 0x0)
+		return -EINVAL;
+
+	map_addr = (m.addr & PAGE_MASK);
+	map_size = round_up(m.size + (m.addr & ~PAGE_MASK), PAGE_SIZE);
+
+	bte_map = ncbte_search_pin(cfile, map_addr, map_size);
+	if (bte_map == NULL)
+		return -ENOENT;
+
+	spin_lock_irqsave(&cfile->pin_lock, flags);
+	list_del(&bte_map->pin_list);
+	spin_unlock_irqrestore(&cfile->pin_lock, flags);
+
+	ncbte_user_vunmap(bte_map);
+	kfree(bte_map);
+	return 0;
+}
+
+/**
+ * ncbte_open() - file open
+ * @inode:      file system information
+ * @filp:	file handle
+ *
+ * This function is executed whenever an application calls
+ * open("/dev/ncbte",..).
+ *
+ * Return: 0 if successful or <0 if errors
+ */
+static int ncbte_open(struct inode *inode, struct file *filp)
+{
+	struct ncbte_file *cfile;
+
+	cfile = kzalloc(sizeof(*cfile), GFP_KERNEL);
+	if (cfile == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&cfile->pin_lock);  /* list of user pinned memory */
+	INIT_LIST_HEAD(&cfile->pin_list);
+
+	filp->private_data = cfile;
+
+	return 0;
+}
+
+/**
+ * ncbte_release() - file close
+ * @inode:      file system information
+ * @filp:       file handle
+ *
+ * This function is executed whenever an application calls 'close(fd_ncbte)'
+ *
+ * Return: always 0
+ */
+static int ncbte_release(struct inode *inode, struct file *filp)
+{
+	struct ncbte_file *cfile = (struct ncbte_file *)filp->private_data;
+	struct list_head *node, *next;
+	struct ncbte_map *bte_map;
+
+	list_for_each_safe(node, next, &cfile->pin_list) {
+		bte_map = list_entry(node, struct ncbte_map, pin_list);
+		/*
+		 * This is not a bug, because a killed processed might
+		 * not call the unpin ioctl, which is supposed to free
+		 * the resources.
+		 *
+		 * Pinnings are dymically allocated and need to be
+		 * deleted.
+		 */
+		list_del_init(&bte_map->pin_list);
+		ncbte_user_vunmap(bte_map);
+		kfree(bte_map);
+	}
+
+	kfree(cfile);
+	return 0;
+}
+
+/**
+ * ncbte_ioctl() - IO control
+ * @filp:       file handle
+ * @cmd:        command identifier (passed from user)
+ * @arg:        argument (passed from user)
+ *
+ * Return: 0 if successful or <0 if errors
+ */
+static long ncbte_ioctl(struct file *filp, unsigned cmd,
+			unsigned long arg)
+{
+	struct ncbte_file *cfile = (struct ncbte_file *)filp->private_data;
+	int err = -EBADRQC;
+
+	if (_IOC_TYPE(cmd) != NCBTE_IOC_CODE)
+		return -EINVAL;
+
+	switch (cmd) {
+	case NCBTE_PIN_MEM:
+		err = ncbte_pin_mem(cfile, arg);
+		break;
+	case NCBTE_UNPIN_MEM:
+		err = ncbte_unpin_mem(cfile, arg);
+		break;
+	}
+
+	return err;
+}
+
+/**
+ * ncbte_mmap() - mmap
+ * @filp:       file handle
+ * @vma:        user Virtial Memory Area
+ *
+ * Return: 0 if successful or <0 if errors
+ */
+static int ncbte_mmap(struct file* filp, struct vm_area_struct* vma)
+{
+	unsigned long physical = (NUMACHIP2_LCSR_BASE + NUMACHIP2_BTE_OFFSET) >> PAGE_SHIFT;
+	unsigned long vsize = vma->vm_end - vma->vm_start;
+
+	if (vsize > NCBTE_MAX_MMAP_SIZE)
+		return -EINVAL; /* Spans too much */
+
+	/* Map it with write-combining enabled so we maximize throughput */
+	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+	if (io_remap_pfn_range(vma, vma->vm_start, physical, vsize, vma->vm_page_prot))
+		return -ENOMEM;
+
+	return 0;
+}
+
+static struct file_operations ncbte_miscdev_fops = {
+	.owner          = THIS_MODULE,
+	.open		= ncbte_open,
+	.unlocked_ioctl = ncbte_ioctl,
+	.mmap           = ncbte_mmap,
+	.release	= ncbte_release,
+	.llseek		= noop_llseek,
+};
+
+static struct miscdevice ncbte_miscdev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name  = NCBTE_DEVNAME,
+	.fops  = &ncbte_miscdev_fops,
+	.mode  = S_IRUGO | S_IWUGO,
+};
+
+static int __init ncbte_init(void)
+{
+	int err;
+
+	if (numachip_system < 2)
+		return 0;
+
+	err = misc_register(&ncbte_miscdev);
+	if (unlikely(err < 0))
+		pr_err("Failed to register ncbte device, error %d\n", err);
+	else
+		pr_err("registered ncbte misc device...\n");
+
+	return err;
+}
+
+static void __exit ncbte_exit(void)
+{
+	misc_deregister(&ncbte_miscdev);
+}
+
+module_init(ncbte_init);
+module_exit(ncbte_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/ncbte_io.h b/include/uapi/linux/ncbte_io.h
new file mode 100644
index 0000000..9fd2444
--- /dev/null
+++ b/include/uapi/linux/ncbte_io.h
@@ -0,0 +1,42 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * User API for Numachip Block Transfer Engine driver
+ *
+ * Copyright (C) 2011 Numascale AS. All rights reserved.
+ *
+ * Send feedback to <support@numascale.com>
+ *
+ */
+
+#ifndef __NCBTE_IO_H__
+#define __NCBTE_IO_H__
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define NCBTE_DEVNAME		"ncbte"
+#define NCBTE_IOC_CODE		0xca
+#define NCBTE_MAX_MMAP_SIZE	0x200000
+
+/**
+ * struct ncbte_region - Memory pinning/unpinning information
+ * @addr:          virtual user space address
+ * @size:          size of the area pin/dma-map/unmap
+ * @page_cnt:      number of page entries
+ * @phys_addr:     array holding physical addresses for pages
+ *
+ */
+struct ncbte_mem {
+	__u64 addr;
+	__u64 size;
+	__u64* phys_addr;
+	__u32 nr_pages;
+};
+
+#define NCBTE_PIN_MEM		_IOWR(NCBTE_IOC_CODE, 100, struct ncbte_mem)
+#define NCBTE_UNPIN_MEM		_IOWR(NCBTE_IOC_CODE, 101, struct ncbte_mem)
+
+#endif /* __NCBTE_IO_H__ */
