summaryrefslogtreecommitdiff
path: root/drivers/dma/pxp/pxp_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/dma/pxp/pxp_device.c')
-rw-r--r--drivers/dma/pxp/pxp_device.c878
1 files changed, 878 insertions, 0 deletions
diff --git a/drivers/dma/pxp/pxp_device.c b/drivers/dma/pxp/pxp_device.c
new file mode 100644
index 000000000000..a4a65a45d30c
--- /dev/null
+++ b/drivers/dma/pxp/pxp_device.c
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2010-2015 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <linux/interrupt.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/pxp_device.h>
+#include <linux/atomic.h>
+#include <linux/platform_data/dma-imx.h>
+
+#define BUFFER_HASH_ORDER 4
+
+static struct pxp_buffer_hash bufhash;
+static struct pxp_irq_info irq_info[NR_PXP_VIRT_CHANNEL];
+
+static int pxp_ht_create(struct pxp_buffer_hash *hash, int order)
+{
+ unsigned long i;
+ unsigned long table_size;
+
+ table_size = 1U << order;
+
+ hash->order = order;
+ hash->hash_table = kmalloc(sizeof(*hash->hash_table) * table_size, GFP_KERNEL);
+
+ if (!hash->hash_table) {
+ pr_err("%s: Out of memory for hash table\n", __func__);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < table_size; i++)
+ INIT_HLIST_HEAD(&hash->hash_table[i]);
+
+ return 0;
+}
+
+static int pxp_ht_insert_item(struct pxp_buffer_hash *hash,
+ struct pxp_buf_obj *new)
+{
+ unsigned long hashkey;
+ struct hlist_head *h_list;
+
+ hashkey = hash_long(new->offset >> PAGE_SHIFT, hash->order);
+ h_list = &hash->hash_table[hashkey];
+
+ spin_lock(&hash->hash_lock);
+ hlist_add_head_rcu(&new->item, h_list);
+ spin_unlock(&hash->hash_lock);
+
+ return 0;
+}
+
+static int pxp_ht_remove_item(struct pxp_buffer_hash *hash,
+ struct pxp_buf_obj *obj)
+{
+ spin_lock(&hash->hash_lock);
+ hlist_del_init_rcu(&obj->item);
+ spin_unlock(&hash->hash_lock);
+ return 0;
+}
+
+static struct hlist_node *pxp_ht_find_key(struct pxp_buffer_hash *hash,
+ unsigned long key)
+{
+ struct pxp_buf_obj *entry;
+ struct hlist_head *h_list;
+ unsigned long hashkey;
+
+ hashkey = hash_long(key, hash->order);
+ h_list = &hash->hash_table[hashkey];
+
+ hlist_for_each_entry_rcu(entry, h_list, item) {
+ if (entry->offset >> PAGE_SHIFT == key)
+ return &entry->item;
+ }
+
+ return NULL;
+}
+
+static void pxp_ht_destroy(struct pxp_buffer_hash *hash)
+{
+ kfree(hash->hash_table);
+ hash->hash_table = NULL;
+}
+
+static int pxp_buffer_handle_create(struct pxp_file *file_priv,
+ struct pxp_buf_obj *obj,
+ uint32_t *handlep)
+{
+ int ret;
+
+ idr_preload(GFP_KERNEL);
+ spin_lock(&file_priv->buffer_lock);
+
+ ret = idr_alloc(&file_priv->buffer_idr, obj, 1, 0, GFP_NOWAIT);
+
+ spin_unlock(&file_priv->buffer_lock);
+ idr_preload_end();
+
+ if (ret < 0)
+ return ret;
+
+ *handlep = ret;
+
+ return 0;
+}
+
+static struct pxp_buf_obj *
+pxp_buffer_object_lookup(struct pxp_file *file_priv,
+ uint32_t handle)
+{
+ struct pxp_buf_obj *obj;
+
+ spin_lock(&file_priv->buffer_lock);
+
+ obj = idr_find(&file_priv->buffer_idr, handle);
+ if (!obj) {
+ spin_unlock(&file_priv->buffer_lock);
+ return NULL;
+ }
+
+ spin_unlock(&file_priv->buffer_lock);
+
+ return obj;
+}
+
+static int pxp_buffer_handle_delete(struct pxp_file *file_priv,
+ uint32_t handle)
+{
+ struct pxp_buf_obj *obj;
+
+ spin_lock(&file_priv->buffer_lock);
+
+ obj = idr_find(&file_priv->buffer_idr, handle);
+ if (!obj) {
+ spin_unlock(&file_priv->buffer_lock);
+ return -EINVAL;
+ }
+
+ idr_remove(&file_priv->buffer_idr, handle);
+ spin_unlock(&file_priv->buffer_lock);
+
+ return 0;
+}
+
+static int pxp_channel_handle_create(struct pxp_file *file_priv,
+ struct pxp_chan_obj *obj,
+ uint32_t *handlep)
+{
+ int ret;
+
+ idr_preload(GFP_KERNEL);
+ spin_lock(&file_priv->channel_lock);
+
+ ret = idr_alloc(&file_priv->channel_idr, obj, 0, 0, GFP_NOWAIT);
+
+ spin_unlock(&file_priv->channel_lock);
+ idr_preload_end();
+
+ if (ret < 0)
+ return ret;
+
+ *handlep = ret;
+
+ return 0;
+}
+
+static struct pxp_chan_obj *
+pxp_channel_object_lookup(struct pxp_file *file_priv,
+ uint32_t handle)
+{
+ struct pxp_chan_obj *obj;
+
+ spin_lock(&file_priv->channel_lock);
+
+ obj = idr_find(&file_priv->channel_idr, handle);
+ if (!obj) {
+ spin_unlock(&file_priv->channel_lock);
+ return NULL;
+ }
+
+ spin_unlock(&file_priv->channel_lock);
+
+ return obj;
+}
+
+static int pxp_channel_handle_delete(struct pxp_file *file_priv,
+ uint32_t handle)
+{
+ struct pxp_chan_obj *obj;
+
+ spin_lock(&file_priv->channel_lock);
+
+ obj = idr_find(&file_priv->channel_idr, handle);
+ if (!obj) {
+ spin_unlock(&file_priv->channel_lock);
+ return -EINVAL;
+ }
+
+ idr_remove(&file_priv->channel_idr, handle);
+ spin_unlock(&file_priv->channel_lock);
+
+ return 0;
+}
+
+static int pxp_alloc_dma_buffer(struct pxp_buf_obj *obj)
+{
+ obj->virtual = dma_alloc_coherent(NULL, PAGE_ALIGN(obj->size),
+ (dma_addr_t *) (&obj->offset),
+ GFP_DMA | GFP_KERNEL);
+ pr_debug("[ALLOC] mem alloc phys_addr = 0x%lx\n", obj->offset);
+
+ if (obj->virtual == NULL) {
+ printk(KERN_ERR "Physical memory allocation error!\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void pxp_free_dma_buffer(struct pxp_buf_obj *obj)
+{
+ if (obj->virtual != NULL) {
+ dma_free_coherent(0, PAGE_ALIGN(obj->size),
+ obj->virtual, (dma_addr_t)obj->offset);
+ }
+}
+
+static int
+pxp_buffer_object_free(int id, void *ptr, void *data)
+{
+ struct pxp_file *file_priv = data;
+ struct pxp_buf_obj *obj = ptr;
+ int ret;
+
+ ret = pxp_buffer_handle_delete(file_priv, obj->handle);
+ if (ret < 0)
+ return ret;
+
+ pxp_ht_remove_item(&bufhash, obj);
+ pxp_free_dma_buffer(obj);
+ kfree(obj);
+
+ return 0;
+}
+
+static int
+pxp_channel_object_free(int id, void *ptr, void *data)
+{
+ struct pxp_file *file_priv = data;
+ struct pxp_chan_obj *obj = ptr;
+ int chan_id;
+
+ chan_id = obj->chan->chan_id;
+ wait_event(irq_info[chan_id].waitq,
+ atomic_read(&irq_info[chan_id].irq_pending) == 0);
+
+ pxp_channel_handle_delete(file_priv, obj->handle);
+ dma_release_channel(obj->chan);
+ kfree(obj);
+
+ return 0;
+}
+
+static void pxp_free_buffers(struct pxp_file *file_priv)
+{
+ idr_for_each(&file_priv->buffer_idr,
+ &pxp_buffer_object_free, file_priv);
+ idr_destroy(&file_priv->buffer_idr);
+}
+
+static void pxp_free_channels(struct pxp_file *file_priv)
+{
+ idr_for_each(&file_priv->channel_idr,
+ &pxp_channel_object_free, file_priv);
+ idr_destroy(&file_priv->channel_idr);
+}
+
+/* Callback function triggered after PxP receives an EOF interrupt */
+static void pxp_dma_done(void *arg)
+{
+ struct pxp_tx_desc *tx_desc = to_tx_desc(arg);
+ struct dma_chan *chan = tx_desc->txd.chan;
+ struct pxp_channel *pxp_chan = to_pxp_channel(chan);
+ int chan_id = pxp_chan->dma_chan.chan_id;
+
+ pr_debug("DMA Done ISR, chan_id %d\n", chan_id);
+
+ atomic_dec(&irq_info[chan_id].irq_pending);
+ irq_info[chan_id].hist_status = tx_desc->hist_status;
+
+ wake_up(&(irq_info[chan_id].waitq));
+}
+
+static int pxp_ioc_config_chan(struct pxp_file *priv, unsigned long arg)
+{
+ struct scatterlist *sg;
+ struct pxp_tx_desc *desc;
+ struct dma_async_tx_descriptor *txd;
+ struct pxp_config_data *pxp_conf;
+ dma_cookie_t cookie;
+ int handle, chan_id;
+ struct dma_chan *chan;
+ struct pxp_chan_obj *obj;
+ int i = 0, j = 0, k = 0, m = 0, length, ret, sg_len;
+
+ pxp_conf = kzalloc(sizeof(*pxp_conf), GFP_KERNEL);
+ if (!pxp_conf)
+ return -ENOMEM;
+
+ ret = copy_from_user(pxp_conf,
+ (struct pxp_config_data *)arg,
+ sizeof(struct pxp_config_data));
+ if (ret) {
+ kfree(pxp_conf);
+ return -EFAULT;
+ }
+
+ handle = pxp_conf->handle;
+ obj = pxp_channel_object_lookup(priv, handle);
+ if (!obj) {
+ kfree(pxp_conf);
+ return -EINVAL;
+ }
+ chan = obj->chan;
+ chan_id = chan->chan_id;
+
+ sg_len = 3;
+ if (pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_A)
+ sg_len += 4;
+ if (pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_B)
+ sg_len += 4;
+ if (pxp_conf->proc_data.engine_enable & PXP_ENABLE_DITHER)
+ sg_len += 4;
+
+ sg = kmalloc(sizeof(*sg) * sg_len, GFP_KERNEL);
+ if (!sg) {
+ kfree(pxp_conf);
+ return -ENOMEM;
+ }
+
+ sg_init_table(sg, sg_len);
+
+ txd = chan->device->device_prep_slave_sg(chan,
+ sg, sg_len,
+ DMA_TO_DEVICE,
+ DMA_PREP_INTERRUPT,
+ NULL);
+ if (!txd) {
+ pr_err("Error preparing a DMA transaction descriptor.\n");
+ kfree(pxp_conf);
+ kfree(sg);
+ return -EIO;
+ }
+
+ txd->callback_param = txd;
+ txd->callback = pxp_dma_done;
+
+ desc = to_tx_desc(txd);
+
+ length = desc->len;
+ for (i = 0; i < length; i++) {
+ if (i == 0) { /* S0 */
+ memcpy(&desc->proc_data,
+ &pxp_conf->proc_data,
+ sizeof(struct pxp_proc_data));
+ memcpy(&desc->layer_param.s0_param,
+ &pxp_conf->s0_param,
+ sizeof(struct pxp_layer_param));
+ desc = desc->next;
+ } else if (i == 1) { /* Output */
+ memcpy(&desc->layer_param.out_param,
+ &pxp_conf->out_param,
+ sizeof(struct pxp_layer_param));
+ desc = desc->next;
+ } else if (i == 2) {
+ /* OverLay */
+ memcpy(&desc->layer_param.ol_param,
+ &pxp_conf->ol_param,
+ sizeof(struct pxp_layer_param));
+ desc = desc->next;
+ } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_A) && (j < 4)) {
+ for (j = 0; j < 4; j++) {
+ if (j == 0) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_a_fetch_param[0],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH0;
+ } else if (j == 1) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_a_fetch_param[1],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH1;
+ } else if (j == 2) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_a_store_param[0],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE0;
+ } else if (j == 3) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_a_store_param[1],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE1;
+ }
+
+ desc = desc->next;
+ }
+
+ i += 4;
+
+ } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_B) && (m < 4)) {
+ for (m = 0; m < 4; m++) {
+ if (m == 0) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_b_fetch_param[0],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_B_FETCH0;
+ } else if (m == 1) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_b_fetch_param[1],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_B_FETCH1;
+ } else if (m == 2) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_b_store_param[0],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_B_STORE0;
+ } else if (m == 3) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->wfe_b_store_param[1],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_B_STORE1;
+ }
+
+ desc = desc->next;
+ }
+
+ i += 4;
+
+ } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_DITHER) && (k < 4)) {
+ for (k = 0; k < 4; k++) {
+ if (k == 0) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->dither_fetch_param[0],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_FETCH0;
+ } else if (k == 1) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->dither_fetch_param[1],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_FETCH1;
+ } else if (k == 2) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->dither_store_param[0],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_STORE0;
+ } else if (k == 3) {
+ memcpy(&desc->layer_param.processing_param,
+ &pxp_conf->dither_store_param[1],
+ sizeof(struct pxp_layer_param));
+ desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_STORE1;
+ }
+
+ desc = desc->next;
+ }
+
+ i += 4;
+ }
+ }
+
+ cookie = txd->tx_submit(txd);
+ if (cookie < 0) {
+ pr_err("Error tx_submit\n");
+ kfree(pxp_conf);
+ kfree(sg);
+ return -EIO;
+ }
+
+ atomic_inc(&irq_info[chan_id].irq_pending);
+
+ kfree(pxp_conf);
+ kfree(sg);
+
+ return 0;
+}
+
+static int pxp_device_open(struct inode *inode, struct file *filp)
+{
+ struct pxp_file *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+
+ if (!priv)
+ return -ENOMEM;
+
+ filp->private_data = priv;
+ priv->filp = filp;
+
+ idr_init(&priv->buffer_idr);
+ spin_lock_init(&priv->buffer_lock);
+
+ idr_init(&priv->channel_idr);
+ spin_lock_init(&priv->channel_lock);
+
+ return 0;
+}
+
+static int pxp_device_release(struct inode *inode, struct file *filp)
+{
+ struct pxp_file *priv = filp->private_data;
+
+ if (priv) {
+ pxp_free_channels(priv);
+ pxp_free_buffers(priv);
+ kfree(priv);
+ filp->private_data = NULL;
+ }
+
+ return 0;
+}
+
+static int pxp_device_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ int request_size;
+ struct hlist_node *node;
+ struct pxp_buf_obj *obj;
+
+ request_size = vma->vm_end - vma->vm_start;
+
+ pr_debug("start=0x%x, pgoff=0x%x, size=0x%x\n",
+ (unsigned int)(vma->vm_start), (unsigned int)(vma->vm_pgoff),
+ request_size);
+
+ node = pxp_ht_find_key(&bufhash, vma->vm_pgoff);
+ if (!node)
+ return -EINVAL;
+
+ obj = list_entry(node, struct pxp_buf_obj, item);
+ if (obj->offset + (obj->size >> PAGE_SHIFT) <
+ (vma->vm_pgoff + vma_pages(vma)))
+ return -ENOMEM;
+
+ switch (obj->mem_type) {
+ case MEMORY_TYPE_UNCACHED:
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ break;
+ case MEMORY_TYPE_WC:
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ break;
+ case MEMORY_TYPE_CACHED:
+ break;
+ default:
+ pr_err("%s: invalid memory type!\n", __func__);
+ return -EINVAL;
+ }
+
+ return remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ request_size, vma->vm_page_prot) ? -EAGAIN : 0;
+}
+
+static bool chan_filter(struct dma_chan *chan, void *arg)
+{
+ if (imx_dma_is_pxp(chan))
+ return true;
+ else
+ return false;
+}
+
+static long pxp_device_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ struct pxp_file *file_priv = filp->private_data;
+
+ switch (cmd) {
+ case PXP_IOC_GET_CHAN:
+ {
+ int ret;
+ struct dma_chan *chan = NULL;
+ dma_cap_mask_t mask;
+ struct pxp_chan_obj *obj = NULL;
+
+ pr_debug("drv: PXP_IOC_GET_CHAN Line %d\n", __LINE__);
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_PRIVATE, mask);
+
+ chan = dma_request_channel(mask, chan_filter, NULL);
+ if (!chan) {
+ pr_err("Unsccessfully received channel!\n");
+ return -EBUSY;
+ }
+
+ pr_debug("Successfully received channel."
+ "chan_id %d\n", chan->chan_id);
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (!obj) {
+ dma_release_channel(chan);
+ return -ENOMEM;
+ }
+ obj->chan = chan;
+
+ ret = pxp_channel_handle_create(file_priv, obj,
+ &obj->handle);
+ if (ret) {
+ dma_release_channel(chan);
+ kfree(obj);
+ return ret;
+ }
+
+ init_waitqueue_head(&(irq_info[chan->chan_id].waitq));
+ if (put_user(obj->handle, (u32 __user *) arg)) {
+ pxp_channel_handle_delete(file_priv, obj->handle);
+ dma_release_channel(chan);
+ kfree(obj);
+ return -EFAULT;
+ }
+
+ break;
+ }
+ case PXP_IOC_PUT_CHAN:
+ {
+ int handle;
+ struct pxp_chan_obj *obj;
+
+ if (get_user(handle, (u32 __user *) arg))
+ return -EFAULT;
+
+ pr_debug("%d release handle %d\n", __LINE__, handle);
+
+ obj = pxp_channel_object_lookup(file_priv, handle);
+ if (!obj)
+ return -EINVAL;
+
+ pxp_channel_handle_delete(file_priv, obj->handle);
+ dma_release_channel(obj->chan);
+ kfree(obj);
+
+ break;
+ }
+ case PXP_IOC_CONFIG_CHAN:
+ {
+ int ret;
+
+ ret = pxp_ioc_config_chan(file_priv, arg);
+ if (ret)
+ return ret;
+
+ break;
+ }
+ case PXP_IOC_START_CHAN:
+ {
+ int handle;
+ struct pxp_chan_obj *obj = NULL;
+
+ if (get_user(handle, (u32 __user *) arg))
+ return -EFAULT;
+
+ obj = pxp_channel_object_lookup(file_priv, handle);
+ if (!obj)
+ return -EINVAL;
+
+ dma_async_issue_pending(obj->chan);
+
+ break;
+ }
+ case PXP_IOC_GET_PHYMEM:
+ {
+ struct pxp_mem_desc buffer;
+ struct pxp_buf_obj *obj;
+
+ ret = copy_from_user(&buffer,
+ (struct pxp_mem_desc *)arg,
+ sizeof(struct pxp_mem_desc));
+ if (ret)
+ return -EFAULT;
+
+ pr_debug("[ALLOC] mem alloc size = 0x%x\n",
+ buffer.size);
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return -ENOMEM;
+ obj->size = buffer.size;
+ obj->mem_type = buffer.mtype;
+
+ ret = pxp_alloc_dma_buffer(obj);
+ if (ret == -1) {
+ printk(KERN_ERR
+ "Physical memory allocation error!\n");
+ kfree(obj);
+ return ret;
+ }
+
+ ret = pxp_buffer_handle_create(file_priv, obj, &obj->handle);
+ if (ret) {
+ pxp_free_dma_buffer(obj);
+ kfree(obj);
+ return ret;
+ }
+ buffer.handle = obj->handle;
+ buffer.phys_addr = obj->offset;
+
+ ret = copy_to_user((void __user *)arg, &buffer,
+ sizeof(struct pxp_mem_desc));
+ if (ret) {
+ pxp_buffer_handle_delete(file_priv, buffer.handle);
+ pxp_free_dma_buffer(obj);
+ kfree(obj);
+ return -EFAULT;
+ }
+
+ pxp_ht_insert_item(&bufhash, obj);
+
+ break;
+ }
+ case PXP_IOC_PUT_PHYMEM:
+ {
+ struct pxp_mem_desc pxp_mem;
+ struct pxp_buf_obj *obj;
+
+ ret = copy_from_user(&pxp_mem,
+ (struct pxp_mem_desc *)arg,
+ sizeof(struct pxp_mem_desc));
+ if (ret)
+ return -EACCES;
+
+ obj = pxp_buffer_object_lookup(file_priv, pxp_mem.handle);
+ if (!obj)
+ return -EINVAL;
+
+ ret = pxp_buffer_handle_delete(file_priv, obj->handle);
+ if (ret)
+ return ret;
+
+ pxp_ht_remove_item(&bufhash, obj);
+ pxp_free_dma_buffer(obj);
+ kfree(obj);
+
+ break;
+ }
+ case PXP_IOC_FLUSH_PHYMEM:
+ {
+ int ret;
+ struct pxp_mem_flush flush;
+ struct pxp_buf_obj *obj;
+
+ ret = copy_from_user(&flush,
+ (struct pxp_mem_flush *)arg,
+ sizeof(struct pxp_mem_flush));
+ if (ret)
+ return -EACCES;
+
+ obj = pxp_buffer_object_lookup(file_priv, flush.handle);
+ if (!obj)
+ return -EINVAL;
+
+ switch (flush.type) {
+ case CACHE_CLEAN:
+ dma_sync_single_for_device(NULL, obj->offset,
+ obj->size, DMA_TO_DEVICE);
+ break;
+ case CACHE_INVALIDATE:
+ dma_sync_single_for_device(NULL, obj->offset,
+ obj->size, DMA_FROM_DEVICE);
+ break;
+ case CACHE_FLUSH:
+ dma_sync_single_for_device(NULL, obj->offset,
+ obj->size, DMA_TO_DEVICE);
+ dma_sync_single_for_device(NULL, obj->offset,
+ obj->size, DMA_FROM_DEVICE);
+ break;
+ default:
+ pr_err("%s: invalid cache flush type\n", __func__);
+ return -EINVAL;
+ }
+
+ break;
+ }
+ case PXP_IOC_WAIT4CMPLT:
+ {
+ struct pxp_chan_handle chan_handle;
+ int ret, chan_id, handle;
+ struct pxp_chan_obj *obj = NULL;
+
+ ret = copy_from_user(&chan_handle,
+ (struct pxp_chan_handle *)arg,
+ sizeof(struct pxp_chan_handle));
+ if (ret)
+ return -EFAULT;
+
+ handle = chan_handle.handle;
+ obj = pxp_channel_object_lookup(file_priv, handle);
+ if (!obj)
+ return -EINVAL;
+ chan_id = obj->chan->chan_id;
+
+ ret = wait_event_interruptible
+ (irq_info[chan_id].waitq,
+ (atomic_read(&irq_info[chan_id].irq_pending) == 0));
+ if (ret < 0)
+ return -ERESTARTSYS;
+
+ chan_handle.hist_status = irq_info[chan_id].hist_status;
+ ret = copy_to_user((struct pxp_chan_handle *)arg,
+ &chan_handle,
+ sizeof(struct pxp_chan_handle));
+ if (ret)
+ return -EFAULT;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct file_operations pxp_device_fops = {
+ .open = pxp_device_open,
+ .release = pxp_device_release,
+ .unlocked_ioctl = pxp_device_ioctl,
+ .mmap = pxp_device_mmap,
+};
+
+static struct miscdevice pxp_device_miscdev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "pxp_device",
+ .fops = &pxp_device_fops,
+};
+
+int register_pxp_device(void)
+{
+ int ret;
+
+ ret = misc_register(&pxp_device_miscdev);
+ if (ret)
+ return ret;
+
+ ret = pxp_ht_create(&bufhash, BUFFER_HASH_ORDER);
+ if (ret)
+ return ret;
+ spin_lock_init(&(bufhash.hash_lock));
+
+ pr_debug("PxP_Device registered Successfully\n");
+ return 0;
+}
+
+void unregister_pxp_device(void)
+{
+ pxp_ht_destroy(&bufhash);
+ misc_deregister(&pxp_device_miscdev);
+}