unplugged-kernel/drivers/misc/mediatek/apusys/vpu/p1/vpu_mem.c

379 lines
7.9 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/of.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/highmem.h>
#include <mt-plat/aee.h>
#include "vpu_cfg.h"
#include "vpu_mem.h"
#include "vpu_debug.h"
static int vpu_map_kva_to_sgt(
const char *buf, size_t len, struct sg_table *sgt);
static dma_addr_t vpu_map_sg_to_iova(
struct platform_device *pdev, struct scatterlist *sg,
unsigned int nents, size_t len, dma_addr_t given_iova);
static void vpu_dump_sg(struct scatterlist *s)
{
unsigned int i = 0;
if (!s || !vpu_debug_on(VPU_DBG_MEM))
return;
while (s) {
struct page *p = sg_page(s);
phys_addr_t phys;
if (!p)
break;
phys = page_to_phys(p);
pr_info("%s: s[%d]: pfn: %lx, pa: %lx, len: %lx, dma_addr: %lx\n",
__func__, i,
(unsigned long) page_to_pfn(p),
(unsigned long) phys,
(unsigned long) s->length,
(unsigned long) s->dma_address);
s = sg_next(s);
i++;
}
}
static void vpu_dump_sgt(struct sg_table *sgt)
{
if (!sgt || !sgt->sgl)
return;
vpu_dump_sg(sgt->sgl);
}
static int
vpu_mem_alloc(struct platform_device *pdev,
struct vpu_iova *i, dma_addr_t given_iova)
{
int ret = 0;
void *kva;
dma_addr_t iova;
if (!i) {
ret = -EINVAL;
goto out;
}
vpu_mem_debug("%s: size: 0x%x, given iova: 0x%llx (%s alloc)\n",
__func__, i->size, (u64)given_iova,
(given_iova == VPU_IOVA_END) ? "dynamic" : "static");
kva = kvmalloc(i->size, GFP_KERNEL);
if (!kva) {
dev_info(&pdev->dev, "%s: kvmalloc: failed\n",
__func__);
ret = -ENOMEM;
goto error;
}
vpu_mem_debug("%s: kvmalloc: %llx\n", __func__, (uint64_t)kva);
ret = vpu_map_kva_to_sgt(kva, i->size, &i->sgt);
if (ret)
goto error;
iova = vpu_map_sg_to_iova(pdev, i->sgt.sgl, i->sgt.nents,
i->size, given_iova);
if (!iova)
goto error;
i->m.va = (uint64_t)kva;
i->m.pa = (uint32_t)iova;
i->m.length = i->size;
goto out;
error:
kvfree(kva);
out:
return ret;
}
void vpu_mem_free(struct vpu_mem *m)
{
kvfree((void *)m->va);
}
static int
vpu_map_kva_to_sgt(const char *buf, size_t len, struct sg_table *sgt)
{
struct page **pages = NULL;
unsigned int nr_pages;
unsigned int index;
const char *p;
int ret;
vpu_mem_debug("%s: buf: %p, len: %lx, sgt: %p\n",
__func__, buf, len, sgt);
nr_pages = DIV_ROUND_UP((unsigned long)buf + len, PAGE_SIZE)
- ((unsigned long)buf / PAGE_SIZE);
pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return -ENOMEM;
p = buf - offset_in_page(buf);
for (index = 0; index < nr_pages; index++) {
if (is_vmalloc_addr(p))
pages[index] = vmalloc_to_page(p);
else
pages[index] = kmap_to_page((void *)p);
if (!pages[index]) {
pr_info("%s: map failed\n", __func__);
ret = -EFAULT;
goto out;
}
p += PAGE_SIZE;
}
vpu_mem_debug("%s: nr_pages: %d\n", __func__, nr_pages);
ret = sg_alloc_table_from_pages(sgt, pages, index,
offset_in_page(buf), len, GFP_KERNEL);
if (ret) {
pr_info("%s: sg_alloc_table_from_pages: %d\n",
__func__, ret);
goto out;
}
vpu_dump_sgt(sgt);
out:
kfree(pages);
return ret;
}
static dma_addr_t
vpu_map_sg_to_iova(
struct platform_device *pdev, struct scatterlist *sg,
unsigned int nents, size_t len, dma_addr_t given_iova)
{
struct device *dev = &pdev->dev;
dma_addr_t mask;
dma_addr_t iova = 0;
bool dyn_alloc = false;
bool match = false;
int ret;
if (!sg)
return 0;
if (given_iova >= VPU_IOVA_END) {
dyn_alloc = true;
mask = (VPU_IOVA_END - 1) | VPU_IOVA_BANK;
given_iova |= VPU_IOVA_BANK;
vpu_mem_debug("%s: dev: %p, len: %zx, given_iova mask: %llx (dynamic alloc)\n",
__func__, dev, len, (u64)given_iova);
} else {
mask = (given_iova + len - 1) | VPU_IOVA_BANK;
given_iova |= VPU_IOVA_BANK;
vpu_mem_debug("%s: dev: %p, len: %zx, given_iova start ~ end(mask): %llx ~ %llx\n",
__func__, dev, len, (u64)given_iova, (u64)mask);
}
dma_set_mask_and_coherent(dev, mask);
if (!dev->dma_parms) {
dev->dma_parms =
devm_kzalloc(dev, sizeof(*dev->dma_parms), GFP_KERNEL);
}
if (dev->dma_parms) {
ret = dma_set_max_seg_size(dev, (unsigned int)DMA_BIT_MASK(34));
if (ret)
dev_info(dev, "Failed to set DMA segment size\n");
}
ret = dma_map_sg_attrs(dev, sg, nents,
DMA_BIDIRECTIONAL, DMA_ATTR_SKIP_CPU_SYNC);
if (ret <= 0) {
dev_info(dev,
"%s: dma_map_sg_attrs: failed with %d\n",
__func__, ret);
return 0;
}
iova = sg_dma_address(&sg[0]);
if (given_iova == iova)
match = true;
dev_info(dev,
"%s: sg_dma_address: size: %lx, mapped iova: 0x%llx %s\n",
__func__, len, (u64)iova,
dyn_alloc ? "(dynamic alloc)" :
(match ? "(static alloc)" : "(unexpected)"));
if (!dyn_alloc && !match)
vpu_aee_warn("VPU", "iova mapping error");
return iova;
}
static dma_addr_t
vpu_map_to_iova(struct platform_device *pdev, void *addr, size_t len,
dma_addr_t given_iova, struct sg_table *sgt)
{
dma_addr_t iova = 0;
int ret;
if (!sgt)
goto out;
ret = vpu_map_kva_to_sgt(addr, len, sgt);
if (ret)
goto out;
iova = vpu_map_sg_to_iova(pdev, sgt->sgl, sgt->nents, len, given_iova);
out:
return iova;
}
dma_addr_t vpu_iova_alloc(struct platform_device *pdev,
struct vpu_iova *i)
{
int ret = 0;
dma_addr_t iova = 0;
/* mt6885, mt6873 maps va to iova */
unsigned long base = (unsigned long)vpu_drv->bin_va;
if (!pdev || !i || !i->size)
goto out;
iova = i->addr ? i->addr : VPU_IOVA_END;
i->sgt.sgl = NULL;
i->m.handle = NULL;
i->m.va = 0;
i->m.pa = 0;
i->m.length = 0;
/* allocate kvm and map */
if (i->bin == VPU_MEM_ALLOC) {
ret = vpu_mem_alloc(pdev, i, iova);
iova = i->m.pa;
/* map from vpu firmware loaded at bootloader */
} else if (i->size) {
iova = vpu_map_to_iova(pdev,
(void *)(base + i->bin), i->size, iova,
&i->sgt);
} else {
dev_info(&pdev->dev,
"%s: unknown setting (%x, %x, %x)\n",
__func__, i->addr, i->bin, i->size);
iova = 0;
}
out:
return iova;
}
void vpu_iova_free(struct device *dev, struct vpu_iova *i)
{
vpu_mem_free(&i->m);
if (i->sgt.sgl) {
dma_unmap_sg_attrs(dev, i->sgt.sgl,
i->sgt.nents, DMA_BIDIRECTIONAL,
DMA_ATTR_SKIP_CPU_SYNC);
sg_free_table(&i->sgt);
}
}
void vpu_iova_sync_for_device(struct device *dev,
struct vpu_iova *i)
{
dma_sync_sg_for_device(dev, i->sgt.sgl, i->sgt.nents,
DMA_TO_DEVICE);
}
void vpu_iova_sync_for_cpu(struct device *dev,
struct vpu_iova *i)
{
dma_sync_sg_for_cpu(dev, i->sgt.sgl, i->sgt.nents,
DMA_FROM_DEVICE);
}
int vpu_iova_dts(struct platform_device *pdev,
const char *name, struct vpu_iova *i)
{
if (of_property_read_u32_array(pdev->dev.of_node,
name, &i->addr, 3)) {
dev_info(&pdev->dev, "%s: vpu: unable to get %s\n",
__func__, name);
return -ENODEV;
}
dev_info(&pdev->dev, "%s: %s: addr: %08xh, size: %08xh, bin: %08xh\n",
__func__, name, i->addr, i->size, i->bin);
return 0;
}
void *vpu_vmap(phys_addr_t start, size_t size,
unsigned int memtype)
{
struct page **pages = NULL;
phys_addr_t page_start = 0;
unsigned int page_count = 0;
pgprot_t prot;
unsigned int i;
void *vaddr = NULL;
if (!size) {
pr_info("%s: input size should not be zero\n", __func__);
return NULL;
}
page_start = start - offset_in_page(start);
page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE);
if (memtype)
prot = pgprot_noncached(PAGE_KERNEL);
else
prot = pgprot_writecombine(PAGE_KERNEL);
pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return NULL;
for (i = 0; i < page_count; i++) {
phys_addr_t addr = page_start + i * PAGE_SIZE;
pages[i] = pfn_to_page(addr >> PAGE_SHIFT);
}
vaddr = vmap(pages, page_count, VM_MAP, prot);
kfree(pages);
if (!vaddr) {
pr_info("%s: failed to get vaddr from vmap\n", __func__);
return NULL;
}
/*
* Since vmap() uses page granularity, we must add the offset
* into the page here, to get the byte granularity address
* into the mapping to represent the actual "start" location.
*/
return vaddr + offset_in_page(start);
}