// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_reserved_mem.h>
#include <linux/uaccess.h>
#include <linux/scatterlist.h>
#include <linux/dma-buf.h>
#include <linux/cma.h>

#include <linux/amlogic/ion.h>
#include "dev_ion.h"

#define MESON_MAX_ION_HEAP 8

static struct heap_type_desc {
	char *name;
	int heap_type;
	struct ion_heap_ops *ops;
	unsigned long flags;
} meson_heap_descs[] = {
	{
		.name = "codec_mm_cma",
		.heap_type = ION_HEAP_TYPE_CUSTOM,
		.ops = &codec_mm_heap_ops,
		.flags = ION_HEAP_FLAG_DEFER_FREE,
	},
	{
		.name = "ion-dev",
		.heap_type = ION_HEAP_TYPE_DMA,
		.ops = &ion_cma_ops,
	},
	{
		.name = "ion-fb",
		.heap_type = ION_HEAP_TYPE_DMA,
		.ops = &ion_cma_ops,
	},
};

struct device *ion_dev;
static int num_heaps;
static struct ion_cma_heap *heaps[MESON_MAX_ION_HEAP];

struct device *meson_ion_get_dev(void)
{
	return ion_dev;
}
EXPORT_SYMBOL(meson_ion_get_dev);

void meson_ion_buffer_to_phys(struct ion_buffer *buffer,
			      phys_addr_t *addr, size_t *len)
{
	struct sg_table *sg_table;
	struct page *page;

	if (buffer) {
		sg_table = buffer->sg_table;
		page = sg_page(sg_table->sgl);
		*addr = PFN_PHYS(page_to_pfn(page));
		*len = buffer->size;
	}
}
EXPORT_SYMBOL(meson_ion_buffer_to_phys);

int meson_ion_share_fd_to_phys(int fd, phys_addr_t *addr, size_t *len)
{
	struct dma_buf *dmabuf;
	struct ion_buffer *buffer;

	dmabuf = dma_buf_get(fd);
	if (IS_ERR_OR_NULL(dmabuf))
		return PTR_ERR(dmabuf);

	buffer = (struct ion_buffer *)dmabuf->priv;
	meson_ion_buffer_to_phys(buffer, addr, len);
	dma_buf_put(dmabuf);

	return 0;
}
EXPORT_SYMBOL(meson_ion_share_fd_to_phys);

static unsigned int meson_ion_heap_id_get(char *heap_name)
{
	int i;
	struct ion_cma_heap *heap;

	for (i = 0; i < num_heaps; i++) {
		heap = heaps[i];
		if (!strcmp(heap->heap.name, heap_name))
			return heap->heap.id;
	}

	return 0;
}

unsigned int meson_ion_codecmm_heap_id_get(void)
{
	return meson_ion_heap_id_get("codec_mm_cma");
}
EXPORT_SYMBOL(meson_ion_codecmm_heap_id_get);

unsigned int meson_ion_cma_heap_id_get(void)
{
	return meson_ion_heap_id_get("ion-dev");
}
EXPORT_SYMBOL(meson_ion_cma_heap_id_get);

unsigned int meson_ion_fb_heap_id_get(void)
{
	return meson_ion_heap_id_get("ion-fb");
}
EXPORT_SYMBOL(meson_ion_fb_heap_id_get);

void __meson_ion_add_heap(struct ion_heap *heap,
			  struct heap_type_desc *desc)
{
	heap->ops = desc->ops;
	heap->type = desc->heap_type;
	heap->flags = desc->flags;
	heap->name = desc->name;

	ion_device_add_heap(heap);
	DION_INFO("%s,heap->name=%s heap->id=%d\n",
		  __func__, heap->name, heap->id);
}

static int meson_ion_add_heap(struct cma *cma, void *data)
{
	int i, heap_type;
	bool need_add = false;
	struct heap_type_desc *desc;
	struct ion_cma_heap *heap;
	int *cma_nr = data;
	const char *cma_name = cma_get_name(cma);

	for (i = 0; i < ARRAY_SIZE(meson_heap_descs); i++) {
		desc = &meson_heap_descs[i];

		if (strstr(cma_name, desc->name)) {
			heap_type = desc->heap_type;
			need_add = true;
			break;
		}
	}

	if (need_add) {
		heap = kzalloc(sizeof(*heap), GFP_KERNEL);
		if (!heap)
			return -ENOMEM;
		__meson_ion_add_heap(&heap->heap, desc);

		heap->cma = cma;
		mutex_init(&heap->mutex);
		heaps[num_heaps++] = heap;
		*cma_nr = num_heaps;
	}

	return 0;
}

static int dev_ion_probe(struct platform_device *pdev)
{
	int ret;
	int nr = 0;

	ret = cma_for_each_area(meson_ion_add_heap, &nr);
	if (ret) {
		for (nr = 0; nr < num_heaps && heaps[nr]; nr++)
			kfree(heaps[nr]);
	}
	ion_dev = &pdev->dev;

	return ret;
}

static int dev_ion_remove(struct platform_device *pdev)
{
	return 0;
}

static const struct of_device_id amlogic_ion_dev_dt_match[] = {
	{ .compatible = "amlogic, ion_dev", },
	{ },
};

static struct platform_driver ion_driver = {
	.probe = dev_ion_probe,
	.remove = dev_ion_remove,
	.driver = {
		.name = "ion_dev",
		.owner = THIS_MODULE,
		.of_match_table = amlogic_ion_dev_dt_match
	}
};

int __init ion_init(void)
{
	return platform_driver_register(&ion_driver);
}

void __exit ion_exit(void)
{
	platform_driver_unregister(&ion_driver);
}

