[RFC PATCH 2/2] soc: ti: Add Support for the TI Page-based Address Translator (PAT)

From: Andrew F. Davis
Date: Fri Jun 07 2019 - 15:40:06 EST


This patch adds a driver for the Page-based Address Translator (PAT)
present on various TI SoCs. A PAT device performs address translation
using tables stored in an internal SRAM. Each PAT supports a set number
of pages, each occupying a programmable 4KB, 16KB, 64KB, or 1MB of
addresses in a window for which an incoming transaction will be
translated.

Signed-off-by: Andrew F. Davis <afd@xxxxxx>
---
drivers/soc/ti/Kconfig | 9 +
drivers/soc/ti/Makefile | 1 +
drivers/soc/ti/ti-pat.c | 569 ++++++++++++++++++++++++++++++++++++
include/uapi/linux/ti-pat.h | 44 +++
4 files changed, 623 insertions(+)
create mode 100644 drivers/soc/ti/ti-pat.c
create mode 100644 include/uapi/linux/ti-pat.h

diff --git a/drivers/soc/ti/Kconfig b/drivers/soc/ti/Kconfig
index f0be35d3dcba..b838ae74d01f 100644
--- a/drivers/soc/ti/Kconfig
+++ b/drivers/soc/ti/Kconfig
@@ -86,4 +86,13 @@ config TI_SCI_INTA_MSI_DOMAIN
help
Driver to enable Interrupt Aggregator specific MSI Domain.

+config TI_PAT
+ tristate "TI PAT DMA-BUF exporter"
+ select REGMAP
+ help
+ Driver for TI Page-based Address Translator (PAT). This driver
+ provides the an API allowing the remapping of a non-contiguous
+ DMA-BUF into a contiguous one that is sutable for devices needing
+ coniguous memory.
+
endif # SOC_TI
diff --git a/drivers/soc/ti/Makefile b/drivers/soc/ti/Makefile
index b3868d392d4f..1369642b40c3 100644
--- a/drivers/soc/ti/Makefile
+++ b/drivers/soc/ti/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_AMX3_PM) += pm33xx.o
obj-$(CONFIG_WKUP_M3_IPC) += wkup_m3_ipc.o
obj-$(CONFIG_TI_SCI_PM_DOMAINS) += ti_sci_pm_domains.o
obj-$(CONFIG_TI_SCI_INTA_MSI_DOMAIN) += ti_sci_inta_msi.o
+obj-$(CONFIG_TI_PAT) += ti-pat.o
diff --git a/drivers/soc/ti/ti-pat.c b/drivers/soc/ti/ti-pat.c
new file mode 100644
index 000000000000..7359ea0f7ccf
--- /dev/null
+++ b/drivers/soc/ti/ti-pat.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI PAT mapped DMA-BUF memory re-exporter
+ *
+ * Copyright (C) 2018-2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Andrew F. Davis <afd@xxxxxx>
+ */
+
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/regmap.h>
+#include <linux/dma-buf.h>
+#include <linux/genalloc.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+
+#include <linux/ti-pat.h>
+
+#define TI_PAT_DRIVER_NAME "ti-pat"
+
+/* TI PAT MMRS registers */
+#define TI_PAT_MMRS_PID 0x0 /* Revision Register */
+#define TI_PAT_MMRS_CONFIG 0x4 /* Config Register */
+#define TI_PAT_MMRS_CONTROL 0x10 /* Control Register */
+
+/* TI PAT CONTROL register field values */
+#define TI_PAT_CONTROL_ARB_MODE_UF 0x0 /* Updates first */
+#define TI_PAT_CONTROL_ARB_MODE_RR 0x2 /* Round-robin */
+
+#define TI_PAT_CONTROL_PAGE_SIZE_4KB 0x0
+#define TI_PAT_CONTROL_PAGE_SIZE_16KB 0x1
+#define TI_PAT_CONTROL_PAGE_SIZE_64KB 0x2
+#define TI_PAT_CONTROL_PAGE_SIZE_1MB 0x3
+
+static unsigned int ti_pat_page_sizes[] = {
+ [TI_PAT_CONTROL_PAGE_SIZE_4KB] = 4 * 1024,
+ [TI_PAT_CONTROL_PAGE_SIZE_16KB] = 16 * 1024,
+ [TI_PAT_CONTROL_PAGE_SIZE_64KB] = 64 * 1024,
+ [TI_PAT_CONTROL_PAGE_SIZE_1MB] = 1024 * 1024,
+};
+
+enum ti_pat_mmrs_fields {
+ /* Revision */
+ F_PID_MAJOR,
+ F_PID_MINOR,
+
+ /* Controls */
+ F_CONTROL_ARB_MODE,
+ F_CONTROL_PAGE_SIZE,
+ F_CONTROL_REPLACE_OID_EN,
+ F_CONTROL_EN,
+
+ /* sentinel */
+ F_MAX_FIELDS
+};
+
+static const struct reg_field ti_pat_mmrs_reg_fields[] = {
+ /* Revision */
+ [F_PID_MAJOR] = REG_FIELD(TI_PAT_MMRS_PID, 8, 10),
+ [F_PID_MINOR] = REG_FIELD(TI_PAT_MMRS_PID, 0, 5),
+ /* Controls */
+ [F_CONTROL_ARB_MODE] = REG_FIELD(TI_PAT_MMRS_CONTROL, 6, 7),
+ [F_CONTROL_PAGE_SIZE] = REG_FIELD(TI_PAT_MMRS_CONTROL, 4, 5),
+ [F_CONTROL_REPLACE_OID_EN] = REG_FIELD(TI_PAT_MMRS_CONTROL, 1, 1),
+ [F_CONTROL_EN] = REG_FIELD(TI_PAT_MMRS_CONTROL, 0, 0),
+};
+
+/**
+ * struct ti_pat_data - PAT device instance data
+ * @dev: PAT device structure
+ * @mdev: misc device
+ * @mmrs_map: Register map of MMRS region
+ * @table_base: Base address of TABLE region
+ */
+struct ti_pat_data {
+ struct device *dev;
+ struct miscdevice mdev;
+ struct regmap *mmrs_map;
+ struct regmap_field *mmrs_fields[F_MAX_FIELDS];
+ void __iomem *table_base;
+ unsigned int page_count;
+ unsigned int page_size;
+ phys_addr_t window_base;
+ struct gen_pool *pool;
+};
+
+struct ti_pat_dma_buf_attachment {
+ struct device *dev;
+ struct sg_table *table;
+ struct ti_pat_buffer *buffer;
+ struct list_head list;
+};
+
+struct ti_pat_buffer {
+ struct ti_pat_data *pat;
+ struct dma_buf *i_dma_buf;
+ size_t size;
+ unsigned long offset;
+ struct dma_buf *e_dma_buf;
+
+ struct dma_buf_attachment *attachment;
+ struct sg_table *sgt;
+
+ struct list_head attachments;
+ int map_count;
+
+ struct mutex lock;
+};
+
+static const struct regmap_config ti_pat_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int ti_pat_dma_buf_attach(struct dma_buf *dmabuf,
+ struct dma_buf_attachment *attachment)
+{
+ struct ti_pat_dma_buf_attachment *a;
+ struct ti_pat_buffer *buffer = dmabuf->priv;
+
+ a = kzalloc(sizeof(*a), GFP_KERNEL);
+ if (!a)
+ return -ENOMEM;
+
+ a->dev = attachment->dev;
+ a->buffer = buffer;
+ INIT_LIST_HEAD(&a->list);
+
+ a->table = kzalloc(sizeof(*a->table), GFP_KERNEL);
+ if (!a->table) {
+ kfree(a);
+ return -ENOMEM;
+ }
+
+ if (sg_alloc_table(a->table, 1, GFP_KERNEL)) {
+ kfree(a->table);
+ kfree(a);
+ return -ENOMEM;
+ }
+
+ sg_set_page(a->table->sgl, pfn_to_page(PFN_DOWN(buffer->offset)), buffer->size, 0);
+
+ attachment->priv = a;
+
+ mutex_lock(&buffer->lock);
+ /* First time attachment we attach to parent */
+ if (list_empty(&buffer->attachments)) {
+ buffer->attachment = dma_buf_attach(buffer->i_dma_buf, buffer->pat->dev);
+ if (IS_ERR(buffer->attachment)) {
+ dev_err(buffer->pat->dev, "Unable to attach to parent DMA-BUF\n");
+ mutex_unlock(&buffer->lock);
+ kfree(a->table);
+ kfree(a);
+ return PTR_ERR(buffer->attachment);
+ }
+ }
+ list_add(&a->list, &buffer->attachments);
+ mutex_unlock(&buffer->lock);
+
+ return 0;
+}
+
+static void ti_pat_dma_buf_detatch(struct dma_buf *dmabuf,
+ struct dma_buf_attachment *attachment)
+{
+ struct ti_pat_dma_buf_attachment *a = attachment->priv;
+ struct ti_pat_buffer *buffer = dmabuf->priv;
+
+ mutex_lock(&buffer->lock);
+ list_del(&a->list);
+ /* Last attachment we detach from parent */
+ if (list_empty(&buffer->attachments)) {
+ dma_buf_detach(buffer->i_dma_buf, buffer->attachment);
+ buffer->attachment = NULL;
+ }
+ mutex_unlock(&buffer->lock);
+
+ kfree(a);
+}
+
+static void ti_pat_set_page(struct ti_pat_data *pat, size_t page_id, dma_addr_t dma_address)
+{
+ unsigned int j = page_id >> 8;
+ unsigned int k = page_id & 0xff;
+ dma_addr_t offset = (j * 0x1000) + (k * 0x8);
+ void __iomem *table_base_l = pat->table_base + offset;
+ void __iomem *table_base_h = table_base_l + 0x4;
+
+ u32 base_l = dma_address >> 12;
+ u32 base_h = (dma_address >> 44) & GENMASK(3, 0);
+
+ dev_dbg(pat->dev, "Enabling PAT index: %zu pointing to %pad\n", page_id, &dma_address);
+
+ writel(base_l, table_base_l);
+ writel(BIT(31) | base_h, table_base_h);
+}
+
+static void ti_pat_unset_page(struct ti_pat_data *pat, size_t page_id)
+{
+ unsigned int j = page_id >> 8;
+ unsigned int k = page_id & 0xff;
+ dma_addr_t offset = (j * 0x1000) + (k * 0x8);
+ void __iomem *table_base_l = pat->table_base + offset;
+ void __iomem *table_base_h = table_base_l + 0x4;
+
+ dev_dbg(pat->dev, "Disabling PAT index: %zu\n", page_id);
+
+ writel(0, table_base_h);
+}
+
+static struct sg_table *ti_pat_map_dma_buf(struct dma_buf_attachment *attachment,
+ enum dma_data_direction direction)
+{
+ struct ti_pat_dma_buf_attachment *a = attachment->priv;
+ struct ti_pat_buffer *buffer = a->buffer;
+ struct ti_pat_data *pat = buffer->pat;
+ struct sg_table *table = a->table;
+ struct scatterlist *s;
+ unsigned int i, s_len;
+ size_t page_id;
+ int ret;
+
+ mutex_lock(&buffer->lock);
+ /* First time mapping we map to parent */
+ if (!buffer->map_count) {
+ buffer->sgt = dma_buf_map_attachment(buffer->attachment, DMA_BIDIRECTIONAL);
+ if (IS_ERR(buffer->sgt)) {
+ dev_err(pat->dev, "Unable to map parent DMA-BUF\n");
+ return buffer->sgt;
+ }
+
+ /* And program PAT area for this set of pages */
+ page_id = (buffer->offset - pat->window_base) / pat->page_size;
+ for_each_sg(buffer->sgt->sgl, s, buffer->sgt->nents, i) {
+ if (s->offset) {
+ dev_err(pat->dev, "Cannot use offset buffers\n");
+ ret = -EINVAL;
+ goto unmap;
+ }
+
+ if (s->length % pat->page_size) {
+ dev_err(pat->dev, "Cannot use buffers not a multiple of page size\n");
+ ret = -EINVAL;
+ goto unmap;
+ }
+
+ for (s_len = 0; s_len < s->length; s_len += pat->page_size)
+ ti_pat_set_page(pat, page_id++, s->dma_address + s_len);
+ }
+ }
+ buffer->map_count++;
+ mutex_unlock(&buffer->lock);
+
+ /* Map the attached device's table to get DMA addresses */
+ if (!dma_map_sg_attrs(attachment->dev, table->sgl, table->nents, direction, DMA_ATTR_SKIP_CPU_SYNC))
+ return ERR_PTR(-ENOMEM);
+
+ return table;
+
+unmap:
+ dma_buf_unmap_attachment(buffer->attachment, buffer->sgt, DMA_BIDIRECTIONAL);
+ return ERR_PTR(ret);
+}
+
+static void ti_pat_unmap_dma_buf(struct dma_buf_attachment *attachment,
+ struct sg_table *table,
+ enum dma_data_direction direction)
+{
+ struct ti_pat_dma_buf_attachment *a = attachment->priv;
+ struct ti_pat_buffer *buffer = a->buffer;
+ struct ti_pat_data *pat = buffer->pat;
+
+ /* Unmap the attached device's table */
+ dma_unmap_sg_attrs(attachment->dev, table->sgl, table->nents, direction, DMA_ATTR_SKIP_CPU_SYNC);
+
+ mutex_lock(&buffer->lock);
+ buffer->map_count--;
+ /* Last mapping we unmap from parent */
+ if (!buffer->map_count) {
+ /* Disable PAT pages for this area */
+ size_t page_start = (buffer->offset - pat->window_base) / pat->page_size;
+ size_t page_end = page_start + (buffer->size / pat->page_size);
+ for (; page_start < page_end; page_start++)
+ ti_pat_unset_page(pat, page_start);
+
+ dma_buf_unmap_attachment(buffer->attachment, buffer->sgt, DMA_BIDIRECTIONAL);
+ buffer->sgt = NULL;
+ }
+ mutex_unlock(&buffer->lock);
+}
+
+static void ti_pat_dma_buf_release(struct dma_buf *dmabuf)
+{
+ struct ti_pat_buffer *buffer = dmabuf->priv;
+
+ if (buffer->attachment && buffer->sgt)
+ dma_buf_unmap_attachment(buffer->attachment, buffer->sgt, DMA_BIDIRECTIONAL);
+ if (buffer->i_dma_buf && !IS_ERR_OR_NULL(buffer->attachment))
+ dma_buf_detach(buffer->i_dma_buf, buffer->attachment);
+ if (buffer->i_dma_buf)
+ dma_buf_put(buffer->i_dma_buf);
+
+ if (buffer->offset)
+ gen_pool_free(buffer->pat->pool, buffer->offset, buffer->size);
+
+ kfree(buffer);
+}
+
+void *ti_pat_dma_buf_map(struct dma_buf *dma_buf, unsigned long page_num)
+{
+ return NULL;
+}
+
+int ti_pat_dma_buf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma)
+{
+ return -EINVAL;
+}
+
+static const struct dma_buf_ops dma_buf_ops = {
+ .attach = ti_pat_dma_buf_attach,
+ .detach = ti_pat_dma_buf_detatch,
+
+ .map_dma_buf = ti_pat_map_dma_buf,
+ .unmap_dma_buf = ti_pat_unmap_dma_buf,
+
+ .release = ti_pat_dma_buf_release,
+
+ .map = ti_pat_dma_buf_map,
+ .mmap = ti_pat_dma_buf_mmap,
+};
+
+int ti_pat_export(struct ti_pat_data *pat,
+ struct dma_buf *i_dma_buf,
+ struct dma_buf **e_dma_buf)
+{
+ struct ti_pat_buffer *buffer;
+ DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
+ int ret;
+
+ buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ buffer->pat = pat;
+ buffer->i_dma_buf = i_dma_buf;
+ buffer->size = buffer->i_dma_buf->size;
+ mutex_init(&buffer->lock);
+ INIT_LIST_HEAD(&buffer->attachments);
+ buffer->map_count = 0;
+
+ /* Reserve PAT space */
+ buffer->offset = gen_pool_alloc(buffer->pat->pool, buffer->size);
+ if (!buffer->offset) {
+ ret = -ENOMEM;
+ goto free_buffer;
+ }
+
+ exp_info.ops = &dma_buf_ops;
+ exp_info.size = buffer->size;
+ exp_info.flags = O_RDWR;
+ exp_info.priv = buffer;
+
+ *e_dma_buf = dma_buf_export(&exp_info);
+ if (IS_ERR(*e_dma_buf)) {
+ ret = PTR_ERR(*e_dma_buf);
+ goto free_pool;
+ }
+
+ return 0;
+
+free_pool:
+ gen_pool_free(buffer->pat->pool, buffer->offset, buffer->size);
+free_buffer:
+ kfree(buffer);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ti_pat_export);
+
+static long ti_pat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct ti_pat_data *pat = container_of(file->private_data, struct ti_pat_data, mdev);
+
+ switch (cmd) {
+ case TI_PAT_IOC_EXPORT:
+ {
+ struct ti_pat_export_data export;
+ struct dma_buf *i_dma_buf;
+ struct dma_buf *e_dma_buf;
+ int ret;
+
+ if (_IOC_SIZE(cmd) > sizeof(export))
+ return -EINVAL;
+
+ if (copy_from_user(&export, (void __user *)arg, _IOC_SIZE(cmd)))
+ return -EFAULT;
+
+ i_dma_buf = dma_buf_get(export.fd);
+ if (IS_ERR(i_dma_buf))
+ return PTR_ERR(i_dma_buf);
+
+ ret = ti_pat_export(pat, i_dma_buf, &e_dma_buf);
+ if (ret) {
+ dma_buf_put(i_dma_buf);
+ return ret;
+ }
+
+ export.fd = dma_buf_fd(e_dma_buf, O_CLOEXEC);
+ if (export.fd < 0) {
+ dma_buf_put(e_dma_buf);
+ dma_buf_put(i_dma_buf);
+ return export.fd;
+ }
+
+ if (copy_to_user((void __user *)arg, &export, _IOC_SIZE(cmd)))
+ return -EFAULT;
+
+ break;
+ }
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+static const struct file_operations ti_pat_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = ti_pat_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = ti_pat_ioctl,
+#endif
+};
+
+static const struct of_device_id ti_pat_of_match[] = {
+ { .compatible = "ti,j721e-pat", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ti_pat_of_match);
+
+static int ti_pat_probe(struct platform_device *pdev)
+{
+ struct ti_pat_data *pat;
+ struct resource *res;
+ void __iomem *base;
+ unsigned int revision_major;
+ unsigned int revision_minor;
+ resource_size_t size;
+ size_t page_size;
+ int i, ret;
+
+ pat = devm_kzalloc(&pdev->dev, sizeof(*pat), GFP_KERNEL);
+ if (!pat)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, pat);
+ pat->dev = &pdev->dev;
+
+ /* MMRS */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(pat->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ pat->mmrs_map = devm_regmap_init_mmio(pat->dev, base, &ti_pat_regmap_config);
+ if (IS_ERR(pat->mmrs_map)) {
+ dev_err(pat->dev, "Unable to allocate MMRS register map\n");
+ return PTR_ERR(pat->mmrs_map);
+ }
+
+ for (i = 0; i < F_MAX_FIELDS; i++) {
+ pat->mmrs_fields[i] = devm_regmap_field_alloc(pat->dev, pat->mmrs_map, ti_pat_mmrs_reg_fields[i]);
+ if (IS_ERR(pat->mmrs_fields[i])) {
+ dev_err(pat->dev, "Unable to allocate Regmap fields\n");
+ return PTR_ERR(pat->mmrs_fields[i]);
+ }
+ }
+
+ ret = regmap_read(pat->mmrs_map, TI_PAT_MMRS_CONFIG, &pat->page_count);
+ if (ret) {
+ dev_err(pat->dev, "Unable to read device page count\n");
+ return ret;
+ }
+
+ ret = regmap_field_read(pat->mmrs_fields[F_PID_MAJOR], &revision_major);
+ if (ret) {
+ dev_err(pat->dev, "Unable to read device major revision\n");
+ return ret;
+ }
+
+ ret = regmap_field_read(pat->mmrs_fields[F_PID_MINOR], &revision_minor);
+ if (ret) {
+ dev_err(pat->dev, "Unable to read device minor revision\n");
+ return ret;
+ }
+
+ dev_info(pat->dev, "Found PAT Rev %d.%d with %d pages\n", revision_major, revision_minor, pat->page_count);
+
+ /* TABLE */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(pat->dev, "Unable to find TABLE IO resource\n");
+ return -ENOENT;
+ }
+ size = resource_size(res);
+
+ /* 256 pages per 4KB of table space */
+ if (size != (pat->page_count << 4))
+ dev_warn(pat->dev, "TABLE region size (%llu) does not match reported page count\n", size);
+
+ pat->table_base = devm_ioremap_resource(pat->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /* WINDOW */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (!res) {
+ dev_err(pat->dev, "Unable to find WINDOW IO resource\n");
+ return -ENOENT;
+ }
+ pat->window_base = res->start;
+ size = resource_size(res);
+
+ pat->page_size = PAGE_SIZE;
+ for (page_size = 0; page_size < ARRAY_SIZE(ti_pat_page_sizes); page_size++)
+ if (ti_pat_page_sizes[page_size] == pat->page_size)
+ break;
+ if (page_size == ARRAY_SIZE(ti_pat_page_sizes)) {
+ dev_err(pat->dev, "Unsupported PAGE_SIZE (%d)\n", pat->page_size);
+ return -EINVAL;
+ }
+ regmap_field_write(pat->mmrs_fields[F_CONTROL_PAGE_SIZE], page_size);
+
+ /* Enable this PAT module */
+ regmap_field_write(pat->mmrs_fields[F_CONTROL_EN], 1);
+
+ pat->pool = gen_pool_create(PAGE_SHIFT, -1);
+ if (!pat->pool)
+ return -ENOMEM;
+ gen_pool_add(pat->pool, pat->window_base, size, -1);
+
+ pat->mdev.minor = MISC_DYNAMIC_MINOR;
+ pat->mdev.name = pdev->name;
+ pat->mdev.fops = &ti_pat_fops;
+ pat->mdev.parent = NULL;
+ ret = misc_register(&pat->mdev);
+ if (ret) {
+ dev_err(pat->dev, "Unable to register misc device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver ti_pat_driver = {
+ .probe = ti_pat_probe,
+ .driver = {
+ .name = TI_PAT_DRIVER_NAME,
+ .of_match_table = ti_pat_of_match,
+ },
+};
+module_platform_driver(ti_pat_driver);
+
+MODULE_AUTHOR("Andrew F. Davis <afd@xxxxxx>");
+MODULE_DESCRIPTION("TI PAT mapped DMA-BUF memory exporter");
+MODULE_LICENSE("GPL v2");
diff --git a/include/uapi/linux/ti-pat.h b/include/uapi/linux/ti-pat.h
new file mode 100644
index 000000000000..b06d79454815
--- /dev/null
+++ b/include/uapi/linux/ti-pat.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * TI PAT mapped DMA-BUF memory exporter UAPI
+ *
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Andrew F. Davis <afd@xxxxxx>
+ */
+
+#ifndef _UAPI_LINUX_TI_PAT_H
+#define _UAPI_LINUX_TI_PAT_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/**
+ * DOC: TI PAT Userspace API
+ *
+ * create a client by opening /dev/ti-pat
+ * most operations handled via following ioctls
+ */
+
+/**
+ * struct ti_pat_allocation_data - metadata passed from userspace for allocations
+ * @fd: populated with DMA-BUF FD for this allocation
+ * @flags: flags for the allocation
+ *
+ * Provided by userspace as an argument to the ioctl
+ */
+struct ti_pat_export_data {
+ __u32 fd;
+ __u32 flags;
+};
+
+#define TI_PAT_IOC_MAGIC 'P'
+
+/**
+ * DOC: TI_PAT_IOC_EXPORT - Re-export DMA-BUF through TI PAT
+ *
+ * Takes an ti_pat_export_data struct and returns it with the fd field
+ * populated with the DMA-BUF handle for the new export.
+ */
+#define TI_PAT_IOC_EXPORT _IOWR(TI_PAT_IOC_MAGIC, 0, struct ti_pat_export_data)
+
+#endif /* _UAPI_LINUX_TI_PAT_H */
--
2.17.1