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

#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/amlogic/vmem.h>

#define TEST_VMEM_ENTRY

static LIST_HEAD(vmem_controller_list);

struct vmem_controller *vmem_get_controller(int bus_num)
{
	struct vmem_controller *vmemctlr;

	list_for_each_entry(vmemctlr, &vmem_controller_list, list)
		if (vmemctlr && vmemctlr->bus_num == bus_num)
			return vmemctlr;

	return NULL;
}

struct vmem_device *
vmem_get_device(struct vmem_controller *vmemctlr, int dev_num)
{
	struct vmem_device *vmemdev;

	list_for_each_entry(vmemdev, &vmemctlr->device_list, list)
		if (vmemdev && vmemdev->dev_num == dev_num)
			return vmemdev;

	return NULL;
}

struct vmem_controller *vmem_create_controller(struct device *dev, int size)
{
	struct vmem_controller *vmemctlr;
	u32 bus_num;

	if (!dev)
		return NULL;

	if (of_property_read_u32(dev->of_node, "vmem-bus-num", &bus_num))
		bus_num = 0;

	vmemctlr = vmem_get_controller(bus_num);
	if (vmemctlr) {
		dev_err(dev, "vmemctlr created already\n");
		return NULL;
	}

	vmemctlr = kzalloc(sizeof(*vmemctlr) + size, GFP_KERNEL);
	if (!vmemctlr)
		return NULL;

	vmemctlr->dev.parent = dev;
	device_initialize(&vmemctlr->dev);
	dev_set_drvdata(&vmemctlr->dev, &vmemctlr[1]);
	dev_set_name(&vmemctlr->dev, "vmemctlr%u", bus_num);
	if (device_add(&vmemctlr->dev)) {
		kfree(vmemctlr);
		dev_err(dev, "vmemctlr add device failed\n");
		return NULL;
	}

	vmemctlr->bus_num = bus_num;
	list_add_tail(&vmemctlr->list, &vmem_controller_list);
	INIT_LIST_HEAD(&vmemctlr->device_list);
	dev_info(dev, "vmemctlr %s created!\n", dev_name(&vmemctlr->dev));

	return vmemctlr;
}

void vmem_destroy_controller(struct vmem_controller *vmemctlr)
{
	device_del(&vmemctlr->dev);
	list_del(&vmemctlr->list);
	kfree(vmemctlr);
}

struct vmem_device *vmem_create_device(struct vmem_controller *vmemctlr,
				       int dev_num, int mem_size)
{
	struct device *dev;
	struct vmem_device *vmemdev;

	dev = &vmemctlr->dev;
	vmemdev = vmem_get_device(vmemctlr, dev_num);
	if (vmemdev) {
		dev_err(dev, "vmemdev created already\n");
		return NULL;
	}

	vmemdev = kzalloc(sizeof(*vmemdev) + mem_size, GFP_KERNEL);
	if (!vmemdev)
		return NULL;

	vmemdev->dev.parent = dev;
	device_initialize(&vmemdev->dev);
	vmemdev->mem = (u8 *)&vmemdev[1];
	vmemdev->size = mem_size;
	dev_set_name(&vmemdev->dev, "%s.%u", dev_name(dev), dev_num);
	if (device_add(&vmemdev->dev)) {
		kfree(vmemdev);
		dev_err(dev, "vmemdev add device failed\n");
		return NULL;
	}

	vmemdev->vmemctlr = vmemctlr;
	vmemdev->dev_num = dev_num;
	list_add_tail(&vmemdev->list, &vmemctlr->device_list);
	dev_info(dev, "vmemdev %s created!\n", dev_name(&vmemdev->dev));

	return vmemdev;
}

void vmem_destroy_device(struct vmem_device *vmemdev)
{
	device_del(&vmemdev->dev);
	list_del(&vmemdev->list);
	kfree(vmemdev);
}

int vmem_setup(struct vmem_device *vmemdev)
{
	struct vmem_controller *vmemctlr = vmemdev->vmemctlr;
	int ret = 0;

	if (vmemctlr->setup)
		ret = vmemctlr->setup(vmemdev);

	return ret;
}

int vmem_cleanup(struct vmem_device *vmemdev)
{
	struct vmem_controller *vmemctlr = vmemdev->vmemctlr;
	int ret = 0;

	if (vmemctlr->cleanup)
		ret = vmemctlr->cleanup(vmemdev);

	return ret;
}

#ifdef TEST_VMEM_ENTRY
static struct vmem_device *test_vmem;

static int test_vmem_cmd_notify(u8 cmd_val, int offset, int size)
{
	return 0;
}

static int test_vmem_data_notify(u8 cmd_val, int offset, int size)
{
	pr_info("\ncmd 0x%x, offset 0x%x, size 0x%x\n", cmd_val, offset, size);
	return 0;
}

static ssize_t create_store(struct class *class, struct class_attribute *attr,
			    const char *buf, size_t count)
{
	struct vmem_controller *vmemctlr;
	unsigned int bus_num, dev_num, size;
	int i;

	if (test_vmem) {
		pr_info("error: test vmem existing\n");
		return -EINVAL;
	}

	if (sscanf(buf, "%d%d%d", &bus_num, &dev_num, &size) != 3) {
		bus_num = 0;
		dev_num = 0;
		size = 32;
	}
	vmemctlr = vmem_get_controller(bus_num);
	if (!vmemctlr)
		return -ENODEV;

	test_vmem = vmem_create_device(vmemctlr, dev_num, size);
	test_vmem->cmd_notify = test_vmem_cmd_notify;
	test_vmem->data_notify = test_vmem_data_notify;
	for (i = 0; i < size; i++)
		test_vmem->mem[i] = i + 0x80;

	return count;
}

static ssize_t destroy_store(struct class *class, struct class_attribute *attr,
			     const char *buf, size_t count)
{
	if (test_vmem) {
		vmem_destroy_device(test_vmem);
		test_vmem = NULL;
	}
	return count;
}

static ssize_t data_show(struct class *class, struct class_attribute *attr,
			 char *buf)
{
	int i;

	if (test_vmem) {
		for (i = 0; i < test_vmem->size; i++)
			pr_info("mem[%d]= 0x%x\n", i, test_vmem->mem[i]);
	}

	return 0;
}

static CLASS_ATTR_WO(create);
static CLASS_ATTR_WO(destroy);
static CLASS_ATTR_RO(data);

static struct attribute *test_vmem_class_attrs[] = {
	&class_attr_create.attr,
	&class_attr_destroy.attr,
	&class_attr_data.attr,
	NULL,
};

ATTRIBUTE_GROUPS(test_vmem_class);

static struct class test_vmem_class = {
	.name = "test_vmem",
	.owner = THIS_MODULE,
	.class_groups = test_vmem_class_groups
};

#ifdef MODULE
int __init vmem_init(void)
{
	test_vmem = NULL;
	return class_register(&test_vmem_class);
}
#else
static int __init vmem_init(void)
{
	test_vmem = NULL;
	return class_register(&test_vmem_class);
}

postcore_initcall(vmem_init);
#endif
#endif

