299 lines
7.4 KiB
C
299 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Copyright (c) 2018 MediaTek Inc.
|
|
#include <linux/err.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/dma-buf.h>
|
|
#include <linux/dma-heap.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dma-direction.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fdtable.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/sched/task.h>
|
|
#include <linux/platform_data/mtk_ccd.h>
|
|
#include <linux/remoteproc/mtk_ccd_mem.h>
|
|
#include <linux/rpmsg/mtk_ccd_rpmsg.h>
|
|
#include <uapi/linux/mtk_ccd_controls.h>
|
|
#include <uapi/linux/dma-heap.h>
|
|
|
|
#define CCD_ALLOCATE_MAX_BUFFER_SIZE 0x20000000UL /*512MB*/
|
|
struct mtk_ccd_buf;
|
|
|
|
struct mtk_ccd_buf {
|
|
struct device *dev;
|
|
void *vaddr;
|
|
unsigned long size;
|
|
dma_addr_t dma_addr;
|
|
/* DMABUF related */
|
|
struct sg_table *dma_sgt;
|
|
struct dma_buf *dbuf;
|
|
struct dma_buf_attachment *db_attach;
|
|
struct dma_buf_map map;
|
|
};
|
|
|
|
static struct mtk_ccd_buf *mtk_ccd_buf_alloc(
|
|
struct device *dev, unsigned long size)
|
|
{
|
|
struct mtk_ccd_buf *buf;
|
|
struct dma_heap *dma_heap;
|
|
struct dma_buf_map map;
|
|
memset(&map, 0, sizeof(map));
|
|
|
|
buf = kzalloc(sizeof(*buf), GFP_KERNEL);
|
|
if (!buf)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
dma_heap = dma_heap_find("mtk_mm-uncached");
|
|
if (!dma_heap) {
|
|
pr_info("dma_heap find fail\n");
|
|
goto fail_alloc;
|
|
}
|
|
|
|
buf->dbuf = dma_heap_buffer_alloc(dma_heap, size,
|
|
O_RDWR | O_CLOEXEC, DMA_HEAP_VALID_HEAP_FLAGS);
|
|
if (IS_ERR(buf->dbuf)) {
|
|
pr_info("dma_heap buffer alloc fail\n");
|
|
goto fail_alloc;
|
|
}
|
|
|
|
buf->db_attach = dma_buf_attach(buf->dbuf, dev);
|
|
if (IS_ERR(buf->db_attach)) {
|
|
pr_info("dma_heap attach fail\n");
|
|
goto fail_alloc;
|
|
}
|
|
|
|
buf->dma_sgt = dma_buf_map_attachment(buf->db_attach,
|
|
DMA_BIDIRECTIONAL);
|
|
if (IS_ERR(buf->dma_sgt)) {
|
|
pr_info("dma_heap map failed\n");
|
|
goto fail_map_attach;
|
|
}
|
|
|
|
if (dma_buf_vmap(buf->dbuf, &map) < 0) {
|
|
pr_info("dma_heap vmap failed\n");
|
|
goto fail_vmap;
|
|
}
|
|
|
|
buf->vaddr = map.vaddr;
|
|
buf->map = map;
|
|
buf->dma_addr = sg_dma_address(buf->dma_sgt->sgl);
|
|
buf->dev = get_device(dev);
|
|
buf->size = size;
|
|
return buf;
|
|
|
|
fail_vmap:
|
|
dma_buf_unmap_attachment(
|
|
buf->db_attach, buf->dma_sgt, DMA_BIDIRECTIONAL);
|
|
fail_map_attach:
|
|
dma_buf_detach(buf->dbuf, buf->db_attach);
|
|
fail_alloc:
|
|
kfree(buf);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
static void mtk_ccd_buf_put(struct mtk_ccd_buf *buf)
|
|
{
|
|
/* free va */
|
|
if (buf->vaddr) {
|
|
dma_buf_vunmap(buf->dbuf, &buf->map);
|
|
}
|
|
|
|
/* free iova */
|
|
if (buf->db_attach && buf->dma_sgt)
|
|
dma_buf_unmap_attachment(
|
|
buf->db_attach, buf->dma_sgt, DMA_BIDIRECTIONAL);
|
|
|
|
if (buf->dbuf && buf->db_attach)
|
|
dma_buf_detach(buf->dbuf, buf->db_attach);
|
|
|
|
/* decrease file count */
|
|
dma_heap_buffer_free(buf->dbuf);
|
|
|
|
put_device(buf->dev);
|
|
kfree(buf);
|
|
}
|
|
|
|
static dma_addr_t mtk_ccd_buf_get_daddr(struct mtk_ccd_buf *buf)
|
|
{
|
|
return buf->dma_addr;
|
|
}
|
|
|
|
static void *mtk_ccd_buf_get_vaddr(struct mtk_ccd_buf *buf)
|
|
{
|
|
if (!buf->vaddr && buf->db_attach) {
|
|
if (dma_buf_vmap(buf->db_attach->dmabuf, &buf->map) < 0) {
|
|
pr_info("dma_heap vmap failed\n");
|
|
return NULL;
|
|
}
|
|
buf->vaddr = buf->map.vaddr;
|
|
}
|
|
|
|
return buf->vaddr;
|
|
}
|
|
|
|
struct dma_buf *mtk_ccd_get_buffer_dmabuf(struct mtk_ccd *ccd, void *mem_priv)
|
|
{
|
|
struct mtk_ccd_buf *buf = mem_priv;
|
|
|
|
if (ccd == NULL || buf == NULL) {
|
|
pr_info("mtk_ccd or mem_priv is NULL\n");
|
|
return NULL;
|
|
}
|
|
return buf->dbuf;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_ccd_get_buffer_dmabuf);
|
|
|
|
struct mtk_ccd_memory *mtk_ccd_mem_init(struct device *dev)
|
|
{
|
|
struct mtk_ccd_memory *ccd_memory;
|
|
|
|
ccd_memory = kzalloc(sizeof(*ccd_memory), GFP_KERNEL);
|
|
if (!ccd_memory)
|
|
return NULL;
|
|
ccd_memory->dev = dev;
|
|
ccd_memory->num_buffers = 0;
|
|
mutex_init(&ccd_memory->mmap_lock);
|
|
return ccd_memory;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_ccd_mem_init);
|
|
|
|
void mtk_ccd_mem_release(struct mtk_ccd *ccd)
|
|
{
|
|
struct mtk_ccd_memory *ccd_memory = ccd->ccd_memory;
|
|
kfree(ccd_memory);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_ccd_mem_release);
|
|
|
|
void *mtk_ccd_get_buffer(struct mtk_ccd *ccd,
|
|
struct mem_obj *mem_buff_data)
|
|
{
|
|
void *va;
|
|
dma_addr_t da;
|
|
unsigned int buffers;
|
|
struct mtk_ccd_buf *buf;
|
|
struct mtk_ccd_mem *ccd_buffer;
|
|
struct mtk_ccd_memory *ccd_memory = ccd->ccd_memory;
|
|
|
|
mem_buff_data->iova = 0;
|
|
mem_buff_data->va = NULL;
|
|
mutex_lock(&ccd_memory->mmap_lock);
|
|
buffers = ccd_memory->num_buffers;
|
|
if (mem_buff_data->len > CCD_ALLOCATE_MAX_BUFFER_SIZE ||
|
|
mem_buff_data->len == 0U ||
|
|
buffers >= MAX_NUMBER_OF_BUFFER) {
|
|
dev_info(ccd_memory->dev,
|
|
"%s: Failed: buffer len = %u num_buffers = %d !!\n",
|
|
__func__, mem_buff_data->len, buffers);
|
|
mutex_unlock(&ccd_memory->mmap_lock);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
ccd_buffer = &ccd_memory->bufs[buffers];
|
|
buf = mtk_ccd_buf_alloc(ccd_memory->dev, mem_buff_data->len);
|
|
ccd_buffer->mem_priv = buf;
|
|
ccd_buffer->size = mem_buff_data->len;
|
|
if (IS_ERR(ccd_buffer->mem_priv)) {
|
|
dev_info(ccd_memory->dev, "%s: CCD buf allocation failed\n",
|
|
__func__);
|
|
mutex_unlock(&ccd_memory->mmap_lock);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
va = mtk_ccd_buf_get_vaddr(buf);
|
|
da = mtk_ccd_buf_get_daddr(buf);
|
|
mem_buff_data->iova = da;
|
|
mem_buff_data->va = va;
|
|
ccd_memory->num_buffers++;
|
|
mutex_unlock(&ccd_memory->mmap_lock);
|
|
dev_info(ccd_memory->dev,
|
|
"Num_bufs = %d iova = %pad va = %p size = %d priv = %p\n",
|
|
ccd_memory->num_buffers, &mem_buff_data->iova,
|
|
mem_buff_data->va,
|
|
(unsigned int)ccd_buffer->size,
|
|
ccd_buffer->mem_priv);
|
|
|
|
return ccd_buffer->mem_priv;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_ccd_get_buffer);
|
|
|
|
int mtk_ccd_put_buffer(struct mtk_ccd *ccd,
|
|
struct mem_obj *mem_buff_data)
|
|
{
|
|
struct mtk_ccd_buf *buf;
|
|
void *va;
|
|
dma_addr_t da;
|
|
int ret = -EINVAL;
|
|
struct mtk_ccd_mem *ccd_buffer;
|
|
unsigned int buffer, num_buffers, last_buffer;
|
|
struct mtk_ccd_memory *ccd_memory = ccd->ccd_memory;
|
|
|
|
mutex_lock(&ccd_memory->mmap_lock);
|
|
num_buffers = ccd_memory->num_buffers;
|
|
if (num_buffers != 0U) {
|
|
for (buffer = 0; buffer < num_buffers; buffer++) {
|
|
ccd_buffer = &ccd_memory->bufs[buffer];
|
|
buf = (struct mtk_ccd_buf *)ccd_buffer->mem_priv;
|
|
va = mtk_ccd_buf_get_vaddr(buf);
|
|
da = mtk_ccd_buf_get_daddr(buf);
|
|
if (mem_buff_data->va == va &&
|
|
mem_buff_data->len == ccd_buffer->size) {
|
|
dev_info(ccd_memory->dev,
|
|
"Free buff = %d iova = %pad va = %p, queue_num = %d, f_count = %d\n",
|
|
buffer, &mem_buff_data->iova,
|
|
mem_buff_data->va,
|
|
num_buffers, atomic_long_read(&buf->dbuf->file->f_count));
|
|
mtk_ccd_buf_put(buf);
|
|
last_buffer = num_buffers - 1U;
|
|
if (last_buffer != buffer)
|
|
ccd_memory->bufs[buffer] =
|
|
ccd_memory->bufs[last_buffer];
|
|
ccd_memory->bufs[last_buffer].mem_priv = NULL;
|
|
ccd_memory->bufs[last_buffer].size = 0;
|
|
ccd_memory->num_buffers--;
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
mutex_unlock(&ccd_memory->mmap_lock);
|
|
|
|
if (ret != 0)
|
|
dev_info(ccd_memory->dev,
|
|
"Can not free memory va %p iova %pad len %u!\n",
|
|
mem_buff_data->va, &mem_buff_data->iova,
|
|
mem_buff_data->len);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_ccd_put_buffer);
|
|
|
|
int mtk_ccd_get_buffer_fd(struct mtk_ccd *ccd, void *mem_priv)
|
|
{
|
|
struct mtk_ccd_buf *buf = mem_priv;
|
|
int fd;
|
|
|
|
if (ccd == NULL || buf == NULL) {
|
|
pr_info("mtk_ccd or buf is NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fd = dma_buf_fd(buf->dbuf, O_RDWR | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
pr_info("mtk_ccd couldn't get fd from dma_buf\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* increase file count, close fd in userspace driver*/
|
|
dma_buf_get(fd);
|
|
|
|
return fd;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_ccd_get_buffer_fd);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MediaTek ccd memory interface");
|