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

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/kasan.h>
#include <linux/highmem.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/arm-smccc.h>
#include <linux/psci.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <uapi/linux/psci.h>
#include <linux/debugfs.h>
#include <linux/sched/signal.h>
#include <linux/dma-mapping.h>
#include <linux/of_reserved_mem.h>
#include <linux/vmalloc.h>
#include <linux/clk.h>
#include <asm/cacheflush.h>
#include <linux/amlogic/scpi_protocol.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include "bridge_common.h"
#include "bridge_ringbuffer.h"
#include "bridge_pcm_hal.h"

#define DRIVER_NAME   "audio-bridge"
#define PREFIX	"aml-audio-line,"

#define RING_BUFFER_SIZE    (1024 * 10)
static struct list_head bridge_devs = LIST_HEAD_INIT(bridge_devs);

static ssize_t bridge_enable_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	struct audio_pcm_bridge_t *bridge =
		container_of(attr, struct audio_pcm_bridge_t, enable_attr);
	if (!bridge)
		return sprintf(buf, "%s err\n", attr->attr.name);
	return sprintf(buf, "%s\n", bridge->enable ? "enable" : "disable");
}

static ssize_t bridge_enable_store(struct kobject *kobj,
		 struct kobj_attribute *attr,
		 const char *buf, size_t len)
{
	int ret;
	int check;
	struct audio_pcm_bridge_t *bridge =
		container_of(attr, struct audio_pcm_bridge_t, enable_attr);
	if (!bridge)
		return len;

	ret = kstrtouint(buf, 10, &check);
	if (ret < 0) {
		pr_err("%s err!", __func__);
		return len;
	}
	if (check) {
		bridge->enable = true;
		bridge->audio_cap->start(bridge->audio_cap);
		bridge->audio_play->start(bridge->audio_play);
	} else {
		bridge->enable = false;
		bridge->audio_cap->stop(bridge->audio_cap);
		bridge->audio_play->stop(bridge->audio_play);
	}
	return len;
}

static ssize_t bridge_line_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	struct audio_pcm_bridge_t *bridge =
		container_of(attr, struct audio_pcm_bridge_t, line_attr);
	if (!bridge)
		return sprintf(buf, "%s err\n", attr->attr.name);
	return sprintf(buf, "%s-%s-%s<--->%s-%s-%s\n",
				find_core_desc(bridge->audio_cap->coreid),
				find_card_desc(bridge->audio_cap->cardid),
				find_mode_desc(bridge->audio_cap->modeid),
				find_core_desc(bridge->audio_play->coreid),
				find_card_desc(bridge->audio_play->cardid),
				find_mode_desc(bridge->audio_play->modeid));
}

static ssize_t bridge_ringbuf_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	struct audio_pcm_bridge_t *bridge =
		container_of(attr, struct audio_pcm_bridge_t, ringbuf_attr);
	if (!bridge)
		return sprintf(buf, "%s err\n", attr->attr.name);
	return sprintf(buf, "size:%d, use:%d free:%d\n", bridge_ring_buffer_len(bridge->rb),
		bridge_ring_buffer_used_len(bridge->rb), bridge_ring_buffer_free_len(bridge->rb));
}

static ssize_t bridge_isolated_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	struct audio_pcm_bridge_t *bridge =
		container_of(attr, struct audio_pcm_bridge_t, isolated_attr);
	if (!bridge)
		return sprintf(buf, "%s err\n", attr->attr.name);
	return sprintf(buf, "%s\n", bridge->isolated_enable ? "enable" : "disable");
}

static ssize_t bridge_isolated_store(struct kobject *kobj,
		 struct kobj_attribute *attr,
		 const char *buf, size_t len)
{
	int ret;
	int check;
	struct audio_pcm_bridge_t *bridge =
		container_of(attr, struct audio_pcm_bridge_t, isolated_attr);
	if (!bridge)
		return len;

	ret = kstrtouint(buf, 10, &check);
	if (ret < 0) {
		pr_err("%s err!", __func__);
		return len;
	}
	if (check)
		bridge->isolated_enable = true;
	else
		bridge->isolated_enable = false;

	return len;
}

static struct audio_pcm_function_t *aml_bridge_parse_of(struct device *dev,
		struct device_node *node, char *name)
{
	int ret;
	u32 core, card;
	enum PCM_MODE mode;
	struct audio_pcm_function_t *audio_p;
	struct device_node *nb = of_get_child_by_name(node, name);
	u32 enable = 0;

	if (!nb)
		goto err_parse0;

	of_property_read_u32(nb, "core", &core);
	of_property_read_u32(nb, "sound-card", &card);
	if (!strcmp(name, "src"))
		mode = PCM_CAPTURE;
	else
		mode = PCM_PLAYBACK;
	audio_p = audio_pcm_alloc(dev, core, card, mode);
	if (!audio_p)
		goto err_parse0;

	of_property_read_u32(nb, "channels", &audio_p->channels);
	of_property_read_u32(nb, "sample-rate", &audio_p->rate);
	of_property_read_u32(nb, "format", &audio_p->format);

	of_property_read_u32(nb, "create-vir-card", &enable);
	if (enable && audio_p->create_virtual_sound_card)
		audio_p->create_virtual_sound_card(audio_p);
	audio_p->enable_create_card = enable;

	if (!audio_p->set_hw)
		goto err_parse1;
	ret = audio_p->set_hw(audio_p, audio_p->channels, audio_p->rate, audio_p->format);
	if (ret < 0)
		goto err_parse1;

	pr_info("%s [core:%d card:%d] channels:%d rate:%d format:%d create-vir-card:%d\n",
				name, core, card, audio_p->channels,
				audio_p->rate, audio_p->format, enable);
	return audio_p;
err_parse1:
	if (enable && audio_p->destroy_virtual_sound_card)
		audio_p->destroy_virtual_sound_card(audio_p);
	audio_pcm_free(audio_p);
err_parse0:
	return NULL;
}

static int aml_bridge_create_attribute(struct device *dev, struct audio_pcm_bridge_t *bridge)
{
	int ret = 0;
	char bridge_name[128];

	snprintf(bridge_name, sizeof(bridge_name), "bridge%d", bridge->id);
	bridge->kobj = kobject_create_and_add(bridge_name, &dev->kobj);
	if (!bridge->kobj) {
		ret = -ENOMEM;
		goto err_attr0;
	}
	bridge->enable_attr.attr.name = __stringify(enable);
	bridge->enable_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0644);
	bridge->enable_attr.show = bridge_enable_show;
	bridge->enable_attr.store = bridge_enable_store;
	ret = sysfs_create_file(bridge->kobj, &bridge->enable_attr.attr);
	if (ret < 0) {
		ret = -ENOMEM;
		goto err_attr1;
	}
	bridge->line_attr.attr.name = __stringify(bridge_line);
	bridge->line_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0444);
	bridge->line_attr.show  = bridge_line_show;
	ret = sysfs_create_file(bridge->kobj, &bridge->line_attr.attr);
	if (ret < 0) {
		ret = -ENOMEM;
		goto err_attr2;
	}
	bridge->ringbuf_attr.attr.name = __stringify(bridge_ringbuf);
	bridge->ringbuf_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0444);
	bridge->ringbuf_attr.show  = bridge_ringbuf_show;
	ret = sysfs_create_file(bridge->kobj, &bridge->ringbuf_attr.attr);
	if (ret < 0) {
		ret = -ENOMEM;
		goto err_attr3;
	}
	bridge->isolated_attr.attr.name = __stringify(bridge_isolated);
	bridge->isolated_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0644);
	bridge->isolated_attr.show  = bridge_isolated_show;
	bridge->isolated_attr.store = bridge_isolated_store;
	ret = sysfs_create_file(bridge->kobj, &bridge->isolated_attr.attr);
	if (ret < 0) {
		ret = -ENOMEM;
		goto err_attr4;
	}
	if (bridge->audio_cap->create_attr) {
		ret = bridge->audio_cap->create_attr(bridge->audio_cap, bridge->kobj);
		if (ret < 0) {
			ret = -EINVAL;
			goto err_attr5;
		}
	}
	if (bridge->audio_play->create_attr) {
		ret = bridge->audio_play->create_attr(bridge->audio_play, bridge->kobj);
		if (ret < 0) {
			ret = -EINVAL;
			goto err_attr6;
		}
	}
	return 0;
err_attr6:
	if (bridge->audio_cap->destroy_attr)
		bridge->audio_cap->destroy_attr(bridge->audio_cap, bridge->kobj);
err_attr5:
	sysfs_remove_file(bridge->kobj, &bridge->isolated_attr.attr);
err_attr4:
	sysfs_remove_file(bridge->kobj, &bridge->ringbuf_attr.attr);
err_attr3:
	sysfs_remove_file(bridge->kobj, &bridge->line_attr.attr);
err_attr2:
	sysfs_remove_file(bridge->kobj, &bridge->enable_attr.attr);
err_attr1:
	kobject_put(bridge->kobj);
err_attr0:
	return ret;
}

static void aml_bridge_destroy_attribute(struct audio_pcm_bridge_t *bridge)
{
	if (bridge->audio_play->destroy_attr)
		bridge->audio_play->destroy_attr(bridge->audio_play, bridge->kobj);
	if (bridge->audio_cap->destroy_attr)
		bridge->audio_cap->destroy_attr(bridge->audio_cap, bridge->kobj);
	sysfs_remove_file(bridge->kobj, &bridge->isolated_attr.attr);
	sysfs_remove_file(bridge->kobj, &bridge->ringbuf_attr.attr);
	sysfs_remove_file(bridge->kobj, &bridge->line_attr.attr);
	sysfs_remove_file(bridge->kobj, &bridge->enable_attr.attr);
	kobject_put(bridge->kobj);
}

static void bridge_control_work(struct work_struct *data)
{
	struct audio_pcm_bridge_t *bridge = container_of(data, struct audio_pcm_bridge_t,
					ctr_work);
	if (bridge->audio_cap && bridge->audio_cap->control)
		bridge->audio_cap->control(bridge->audio_cap,
			bridge->bridge_ctr_cmd, bridge->bridge_ctr_value);

	if (bridge->audio_play && bridge->audio_play->control)
		bridge->audio_play->control(bridge->audio_play,
			bridge->bridge_ctr_cmd, bridge->bridge_ctr_value);
}

static int aml_bridge_init(struct device *dev, struct device_node *node, int idx)
{
	int ret = 0;
	u32 value;
	struct audio_pcm_bridge_t *bridge;

	bridge = vzalloc(sizeof(*bridge));
	if (!bridge) {
		ret = -ENOMEM;
		goto err_init0;
	}
	bridge->id = idx;
	bridge->rb = bridge_ring_buffer_init(RING_BUFFER_SIZE);
	if (!bridge->rb) {
		ret = -ENOMEM;
		goto err_init1;
	}

	bridge->audio_cap = aml_bridge_parse_of(dev, node, "src");
	if (!bridge->audio_cap) {
		ret = -ENOMEM;
		goto err_init2;
	}

	bridge->audio_play = aml_bridge_parse_of(dev, node, "dst");
	if (!bridge->audio_play) {
		ret = -ENOMEM;
		goto err_init3;
	}
	if (!bridge->audio_cap->start || !bridge->audio_play->start) {
		ret = -EINVAL;
		goto err_init4;
	}
	bridge->audio_cap->rb = bridge->rb;
	bridge->audio_play->rb = bridge->rb;

	bridge->audio_cap->opposite = bridge->audio_play;
	bridge->audio_play->opposite = bridge->audio_cap;

	bridge->audio_cap->audio_bridge = bridge;
	bridge->audio_play->audio_bridge = bridge;
	ret = aml_bridge_create_attribute(dev, bridge);
	if (ret < 0) {
		ret = -EINVAL;
		goto err_init4;
	}
	ret = of_property_read_u32(node, "isolated", &value);
	if (ret >= 0 && value)
		bridge->isolated_enable = true;

	ret = of_property_read_u32(node, "def-volume", &value);
	if (ret >= 0 && value >= VOLUME_MIN && value <= VOLUME_MAX)
		bridge->def_volume = value;
	else
		bridge->def_volume = VOLUME_MAX;

	if (bridge->audio_cap->control)
		bridge->audio_cap->control(bridge->audio_cap, PCM_VOLUME, bridge->def_volume);

	if (bridge->audio_play->control)
		bridge->audio_play->control(bridge->audio_play, PCM_VOLUME, bridge->def_volume);

	ret = of_property_read_u32(node, "auto-running", &value);
	if (ret >= 0 && value) {
		bridge->enable = true;
		ret = bridge->audio_cap->start(bridge->audio_cap);
		if (ret < 0) {
			ret = -EINVAL;
			goto err_init5;
		}
		ret = bridge->audio_play->start(bridge->audio_play);
		if (ret < 0) {
			ret = -EINVAL;
			goto err_init6;
		}
	}
	INIT_WORK(&bridge->ctr_work, bridge_control_work);
	list_add_tail(&bridge->pcm_list, &bridge_devs);
	return ret;
err_init6:
	bridge->audio_cap->stop(bridge->audio_cap);
err_init5:
	aml_bridge_destroy_attribute(bridge);
err_init4:
	audio_pcm_free(bridge->audio_play);
err_init3:
	audio_pcm_free(bridge->audio_cap);
err_init2:
	bridge_ring_buffer_release(bridge->rb);
err_init1:
	vfree(bridge);
err_init0:
	return ret;
}

static int aml_bridge_free(void)
{
	struct audio_pcm_bridge_t *cur, *n;

	list_for_each_entry_safe(cur, n, &bridge_devs, pcm_list) {
		cur->enable = false;
		list_del(&cur->pcm_list);
		if (cur->kobj)
			aml_bridge_destroy_attribute(cur);
		if (cur->audio_cap) {
			if (cur->audio_cap->enable_create_card &&
				cur->audio_cap->destroy_virtual_sound_card) {
				cur->audio_cap->destroy_virtual_sound_card(cur->audio_cap);
			}
			cur->audio_cap->stop(cur->audio_cap);
			audio_pcm_free(cur->audio_cap);
			cur->audio_cap = NULL;
		}
		if (cur->audio_play) {
			if (cur->audio_play->enable_create_card &&
				cur->audio_play->destroy_virtual_sound_card) {
				cur->audio_play->destroy_virtual_sound_card(cur->audio_play);
			}
			cur->audio_play->stop(cur->audio_play);
			audio_pcm_free(cur->audio_play);
			cur->audio_play = NULL;
		}
		if (cur->rb) {
			bridge_ring_buffer_release(cur->rb);
			cur->rb = NULL;
		}
		vfree(cur);
	}
	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int audio_bridge_prepare(struct device *dev)
{
	return 0;
}

static void audio_bridge_complete(struct device *dev)
{
}

static int audio_bridge_suspend(struct device *dev)
{
	struct audio_pcm_bridge_t *cur, *n;

	list_for_each_entry_safe(cur, n, &bridge_devs, pcm_list) {
		if (cur->enable) {
			if (cur->audio_cap)
				cur->audio_cap->stop(cur->audio_cap);
			if (cur->audio_play)
				cur->audio_play->stop(cur->audio_play);
		}
	}

	return 0;
}

static int audio_bridge_resume(struct device *dev)
{
	struct audio_pcm_bridge_t *cur, *n;

	list_for_each_entry_safe(cur, n, &bridge_devs, pcm_list) {
		if (cur->enable) {
			if (cur->audio_cap)
				cur->audio_cap->start(cur->audio_cap);
			if (cur->audio_play)
				cur->audio_play->start(cur->audio_play);
		}
	}

	return 0;
}

static const struct dev_pm_ops audio_bridge_dev_pm_ops = {
	.prepare	= audio_bridge_prepare,
	.complete	= audio_bridge_complete,

	SET_SYSTEM_SLEEP_PM_OPS(audio_bridge_suspend, audio_bridge_resume)
};
#endif

static int audio_bridge_probe(struct platform_device *pdev)
{
	int ret;
	int i = 0;
	struct device_node *node = pdev->dev.of_node;
	struct device *dev = &pdev->dev;
	struct device_node *dai_link;

	dai_link = of_get_child_by_name(node, PREFIX "link");
	if (dai_link) {
		struct device_node *np = NULL;

		for_each_child_of_node(node, np) {
			dev_info(dev, "\tlink %d:\n", i);
			ret = aml_bridge_init(dev, np, i);
			if (ret < 0) {
				of_node_put(np);
				goto err_probe1;
			}
			i++;
		}
	}

	dev_info(dev, "%s init done\n", __func__);
	return 0;
err_probe1:
	aml_bridge_free();
	return ret;
}

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

static const struct of_device_id audio_bridge_of_match[] = {
	{ .compatible = "amlogic, audio-bridge" },
	{},
};

static struct platform_driver audio_bridge_driver = {
	.probe = audio_bridge_probe,
	.remove = audio_bridge_remove,
	.driver = {
		.owner          = THIS_MODULE,
		.name = DRIVER_NAME,
		.of_match_table = audio_bridge_of_match,
#ifdef CONFIG_PM_SLEEP
		.pm = &audio_bridge_dev_pm_ops,
#endif
	},
};

static int __init audio_bridge_init(void)
{
	return platform_driver_register(&audio_bridge_driver);
}

static void __exit audio_bridge_exit(void)
{
	platform_driver_unregister(&audio_bridge_driver);
}

module_init(audio_bridge_init);
module_exit(audio_bridge_exit);

MODULE_AUTHOR("ziheng li <ziheng.li@amlogic.com>");
MODULE_LICENSE("GPL");
