// SPDX-License-Identifier: GPL-2.0 // // Copyright (c) 2018 MediaTek Inc. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");