// SPDX-License-Identifier: GPL-2.0
/*
 * mt8532-afe-acc.c  --  Mediatek 8518 afe acc function
 *
 * Copyright (c) 2021 MediaTek Inc.
 * Author: Hidalgo Huang <hidalgo.huang@mediatek.com>
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>


struct mt8532_afe_acc_dev_priv {
	struct miscdevice misc_dev;
	struct device *dev;
	struct clk *clock;
	unsigned int acc_pa;
};

#define miscdev_to_ppc_priv(d) \
	container_of(d, struct mt8532_afe_acc_dev_priv, misc_dev)


static int mt8532_afe_acc_open(struct inode *inode,
	struct file *filp)
{
	struct mt8532_afe_acc_dev_priv *priv =
		miscdev_to_ppc_priv(filp->private_data);
	int ret;

	if (priv->clock) {
		ret = clk_prepare_enable(priv->clock);
		if (ret) {
			dev_info(priv->dev, "Failed to enable clk\n");
			return ret;
		}
	}

	return 0;
}

static int mt8532_afe_acc_release(struct inode *inode,
	struct file *filp)
{
	struct mt8532_afe_acc_dev_priv *priv =
		miscdev_to_ppc_priv(filp->private_data);

	if (priv->clock)
		clk_disable_unprepare(priv->clock);

	return 0;
}

static int mt8532_afe_acc_mmap(struct file *filp, struct vm_area_struct *vma)
{
	struct mt8532_afe_acc_dev_priv *priv =
		miscdev_to_ppc_priv(filp->private_data);
	unsigned long size = vma->vm_end - vma->vm_start;
	unsigned long acc_addr = priv->acc_pa;

	/* only allows one page from the head */
	if ((vma->vm_pgoff > 0) || (size > PAGE_SIZE))
		return -EIO;

	/* disallow writable mapping */
	if (vma->vm_flags & VM_WRITE)
		return -EPERM;

	if (acc_addr == 0)
		return -EINVAL;

	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

	if (remap_pfn_range(vma, vma->vm_start, __phys_to_pfn(acc_addr),
			    PAGE_SIZE, vma->vm_page_prot)) {
		return -EAGAIN;
	}

	return 0;
}

static const struct file_operations mt8532_afe_acc_fops = {
	.open = mt8532_afe_acc_open,
	.release = mt8532_afe_acc_release,
	.mmap = mt8532_afe_acc_mmap,
};

static int mt8532_afe_acc_parse_of(struct mt8532_afe_acc_dev_priv *priv)
{
	struct device *dev = priv->dev;
	struct device_node *np = dev->of_node;
	unsigned int temp;
	int ret;

	priv->clock = devm_clk_get(dev, "top_aud_intbus");
	if (IS_ERR(priv->clock)) {
		dev_info(dev, "%s get clock fail\n", __func__);
		return PTR_ERR(priv->clock);
	}

	ret = of_property_read_u32(np, "mediatek,acc-phys-addr", &temp);
	if (ret == 0)
		priv->acc_pa = temp;

	return 0;
}

static int mt8532_afe_acc_probe(struct platform_device *pdev)
{
	struct mt8532_afe_acc_dev_priv *priv;
	struct device *dev = &pdev->dev;
	int ret;

	priv = devm_kzalloc(dev, sizeof(struct mt8532_afe_acc_dev_priv),
		GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->misc_dev.minor = MISC_DYNAMIC_MINOR;
	priv->misc_dev.name = "mtk_afe_acc";
	priv->misc_dev.fops = &mt8532_afe_acc_fops;

	ret = misc_register(&priv->misc_dev);
	if (ret < 0) {
		dev_info(dev, "%s misc_register failed\n", __func__);
		return ret;
	}

	priv->dev = dev;

	ret = mt8532_afe_acc_parse_of(priv);
	if (ret)
		return ret;

	platform_set_drvdata(pdev, priv);

	return 0;
}

static int mt8532_afe_acc_remove(struct platform_device *pdev)
{
	struct mt8532_afe_acc_dev_priv *priv = platform_get_drvdata(pdev);

	misc_deregister(&priv->misc_dev);

	return 0;
}

static const struct of_device_id mt8532_afe_acc_dt_match[] = {
	{ .compatible = "mediatek,mt8532-afe-acc", },
	{ }
};
MODULE_DEVICE_TABLE(of, mt8532_afe_acc_dt_match);

static struct platform_driver mt8532_afe_acc_driver = {
	.driver = {
		.name = "mt8532-afe-acc",
		.of_match_table = mt8532_afe_acc_dt_match,
	},
	.probe = mt8532_afe_acc_probe,
	.remove = mt8532_afe_acc_remove,
};

module_platform_driver(mt8532_afe_acc_driver);

