// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2021 MediaTek Inc.
 * Author Owen.Tseng <Owen.Tseng@mediatek.com>
 */

#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/of_reserved_mem.h>
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/refcount.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/types.h>
#include <linux/of_address.h>
#include <linux/types.h>
#include <linux/io.h>

#define REG_280_TOP_DBG	0x280
#define REG_284_TOP_DBG	0x284
#define REG_288_TOP_DBG	0x288
/*topmux*/
#define REG_28C_TOP_DBG	0x28C
/*top_timeout_info*/
#define REG_290_TOP_DBG	0x290
#define REG_294_TOP_DBG	0x294
#define REG_298_TOP_DBG	0x298

/*top_au_flag*/
#define REG_2B8_TOP_DBG	0x2B8
/*top_au_mux*/
#define REG_2BC_TOP_DBG	0x2BC
/*au_timeout_info*/
#define REG_2C0_TOP_DBG	0x2C0
#define REG_2C4_TOP_DBG	0x2C4

/*top_codec_flag*/
#define REG_2E0_TOP_DBG	0x2E0
/*top_codec_mux*/
#define REG_2E4_TOP_DBG	0x2E4
/*codec_timeout_info*/
#define REG_2E8_TOP_DBG	0x2E8
#define REG_2EC_TOP_DBG	0x2EC

/*pm_flag*/
#define REG_C8_TOP_DBG	0xC8
#define REG_CC_TOP_DBG	0xCC
/*pm_mux*/
#define REG_D0_TOP_DBG	0xD0
/*pm_info*/
#define REG_D4_TOP_DBG	0xD4
#define REG_D8_TOP_DBG	0xD8

/*xc_flag*/
#define REG_9C_TOP_DBG	0x9C
/*xc_mux*/
#define REG_A0_TOP_DBG	0xA0
/*xc_info*/
#define REG_AC_TOP_DBG	0xAC
/*0x14800 bank xc en*/
#define REG_34_TOP_DBG	0x34

#define FULL_MASK	0xFFFF
#define TOP_MUX_SHIFT	16
#define P2PSIZE		32
#define P2PDELAY	20
#define AU_MUX_SHIFT	3
#define CODEC_MUX_SHIFT	2
#define PM_MUX_SHIFT	1
#define XC_ID	2
#define XC_ID_EN	3
#define BYTE_SHIFT	8


struct mtk_infrap2p {
	void __iomem *base_top;
	void __iomem *base_pm;
	void __iomem *base_xc;
	void __iomem *base_xc_en;
};

static inline uint16_t  p2p_readw_relaxed(void __iomem *reg32)
{
	return readw_relaxed(reg32);
}

static inline void p2p_writew_relaxed(void __iomem *reg32, uint16_t val16)
{
	writew_relaxed(val16, reg32);
}

static void xc_p2p_error_info_set(struct mtk_infrap2p *p2p)
{
	p2p_writew_relaxed((p2p->base_xc_en + REG_34_TOP_DBG), 0);
	p2p_writew_relaxed((p2p->base_xc + REG_9C_TOP_DBG), FULL_MASK);
}

static u8 xc_p2p_error_info_check(struct mtk_infrap2p *p2p)
{
	u16 timeout = 0;
	u16 timeout_cnt = 0;
	u16 timeout_tmp = 0;

	timeout = p2p_readw_relaxed((p2p->base_xc + REG_AC_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return timeout_cnt;
	return 0;
}

static void xc_p2p_error_mux_set(struct mtk_infrap2p *p2p, u16 p2pmux)
{
	p2p_writew_relaxed((p2p->base_xc + REG_A0_TOP_DBG), p2pmux);
}

static void pm_p2p_error_info_set(struct mtk_infrap2p *p2p)
{
	p2p_writew_relaxed((p2p->base_pm + REG_C8_TOP_DBG), FULL_MASK);
	p2p_writew_relaxed((p2p->base_pm + REG_CC_TOP_DBG), FULL_MASK);
}

static u8 pm_p2p_error_info_check(struct mtk_infrap2p *p2p)
{
	u16 timeout = 0;
	u16 timeout_cnt = 0;
	u16 timeout_tmp = 0;

	timeout = p2p_readw_relaxed((p2p->base_pm + REG_D4_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return timeout_cnt;

	timeout = p2p_readw_relaxed((p2p->base_pm + REG_D8_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return (timeout_cnt + PM_MUX_SHIFT);
	return 0;
}

static void pm_p2p_error_mux_set(struct mtk_infrap2p *p2p, u16 p2pmux)
{
	p2p_writew_relaxed((p2p->base_pm + REG_D0_TOP_DBG), p2pmux);
}

static void codec_p2p_error_info_set(struct mtk_infrap2p *p2p)
{
	p2p_writew_relaxed((p2p->base_top + REG_2E0_TOP_DBG), FULL_MASK);
}

static u8 codec_p2p_error_info_check(struct mtk_infrap2p *p2p)
{
	u16 timeout = 0;
	u16 timeout_cnt = 0;
	u16 timeout_tmp = 0;

	timeout = p2p_readw_relaxed((p2p->base_top + REG_2E8_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return timeout_cnt;

	timeout = p2p_readw_relaxed((p2p->base_top + REG_2EC_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return (timeout_cnt + CODEC_MUX_SHIFT);
	return 0;
}

static void codec_p2p_error_mux_set(struct mtk_infrap2p *p2p, u16 p2pmux)
{
	p2p_writew_relaxed((p2p->base_top + REG_2E4_TOP_DBG), p2pmux);
}

static void au_p2p_error_info_set(struct mtk_infrap2p *p2p)
{
	p2p_writew_relaxed((p2p->base_top + REG_2B8_TOP_DBG), FULL_MASK);
}

static u8 au_p2p_error_info_check(struct mtk_infrap2p *p2p)
{
	u16 timeout = 0;
	u16 timeout_cnt = 0;
	u16 timeout_tmp = 0;

	timeout = p2p_readw_relaxed((p2p->base_top + REG_2C0_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return timeout_cnt;

	timeout = p2p_readw_relaxed((p2p->base_top + REG_2C4_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return (timeout_cnt + AU_MUX_SHIFT);
	return 0;
}

static void au_p2p_error_mux_set(struct mtk_infrap2p *p2p, u16 p2pmux)
{
	p2p_writew_relaxed((p2p->base_top + REG_2BC_TOP_DBG), p2pmux);
}

static void top_p2p_error_info_set(struct mtk_infrap2p *p2p)
{
	p2p_writew_relaxed((p2p->base_top + REG_280_TOP_DBG), FULL_MASK);
	p2p_writew_relaxed((p2p->base_top + REG_284_TOP_DBG), FULL_MASK);
	p2p_writew_relaxed((p2p->base_top + REG_288_TOP_DBG), FULL_MASK);
}

static u8 top_p2p_error_info_check(struct mtk_infrap2p *p2p)
{
	u16 timeout = 0;
	u16 timeout_cnt = 0;
	u16 timeout_tmp = 0;

	timeout = p2p_readw_relaxed((p2p->base_top + REG_290_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return timeout_cnt;

	timeout = p2p_readw_relaxed((p2p->base_top + REG_294_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return (timeout_cnt + TOP_MUX_SHIFT);

	timeout = p2p_readw_relaxed((p2p->base_top + REG_298_TOP_DBG));
	timeout_tmp = timeout;
	msleep(P2PDELAY);
	while (timeout_tmp) {
		if ((timeout & (1 << timeout_cnt)) == 0) {
			timeout_cnt++;
			timeout_tmp >>= 1;
		} else
			break;
	}
	if (timeout_cnt)
		return (timeout_cnt + TOP_MUX_SHIFT + TOP_MUX_SHIFT);
	return 0;
}

static void top_p2p_error_mux_set(struct mtk_infrap2p *p2p, u16 p2pmux)
{
	p2p_writew_relaxed((p2p->base_top + REG_28C_TOP_DBG), p2pmux);
}

static ssize_t xc_mux_show(struct device *child,
			   struct device_attribute *attr,
			   char *buf)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	u16 reg_tmp = 0;

	reg_tmp = xc_p2p_error_info_check(p2p);
	xc_p2p_error_mux_set(p2p, reg_tmp);
	return scnprintf(buf, P2PSIZE, "%d\n", reg_tmp);
}

static ssize_t xc_mux_store(struct device *child,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);
	xc_p2p_error_mux_set(p2p, val);

	return ret ? : size;
}
static DEVICE_ATTR_RW(xc_mux);

static ssize_t pm_mux_show(struct device *child,
			   struct device_attribute *attr,
			   char *buf)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	u16 reg_tmp = 0;

	reg_tmp = pm_p2p_error_info_check(p2p);
	pm_p2p_error_mux_set(p2p, reg_tmp);
	return scnprintf(buf, P2PSIZE, "%d\n", reg_tmp);
}

static ssize_t pm_mux_store(struct device *child,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);
	pm_p2p_error_mux_set(p2p, val);

	return ret ? : size;
}
static DEVICE_ATTR_RW(pm_mux);

static ssize_t codec_mux_show(struct device *child,
			   struct device_attribute *attr,
			   char *buf)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	u16 reg_tmp = 0;

	reg_tmp = codec_p2p_error_info_check(p2p);
	codec_p2p_error_mux_set(p2p, reg_tmp);
	return scnprintf(buf, P2PSIZE, "%d\n", reg_tmp);
}

static ssize_t codec_mux_store(struct device *child,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);
	codec_p2p_error_mux_set(p2p, val);

	return ret ? : size;
}
static DEVICE_ATTR_RW(codec_mux);

static ssize_t au_mux_show(struct device *child,
			   struct device_attribute *attr,
			   char *buf)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	u16 reg_tmp = 0;

	reg_tmp = au_p2p_error_info_check(p2p);
	au_p2p_error_mux_set(p2p, reg_tmp);
	return scnprintf(buf, P2PSIZE, "%d\n", reg_tmp);
}

static ssize_t au_mux_store(struct device *child,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);
	au_p2p_error_mux_set(p2p, val);

	return ret ? : size;
}
static DEVICE_ATTR_RW(au_mux);

static ssize_t top_mux_show(struct device *child,
			   struct device_attribute *attr,
			   char *buf)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	u16 reg_tmp = 0;

	reg_tmp = top_p2p_error_info_check(p2p);
	top_p2p_error_mux_set(p2p, reg_tmp);
	return scnprintf(buf, P2PSIZE, "%d\n", reg_tmp);
}

static ssize_t top_mux_store(struct device *child,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);
	top_p2p_error_mux_set(p2p, val);

	return ret ? : size;
}
static DEVICE_ATTR_RW(top_mux);

static ssize_t all_mux_show(struct device *child,
			   struct device_attribute *attr,
			   char *buf)
{
	struct mtk_infrap2p *p2p = dev_get_drvdata(child);
	u32 reg_tmp = 0;
	u32 reg_all = 0;

	reg_tmp = top_p2p_error_info_check(p2p);
	top_p2p_error_mux_set(p2p, reg_tmp);
	reg_all |= reg_tmp;
	reg_tmp = au_p2p_error_info_check(p2p);
	au_p2p_error_mux_set(p2p, reg_tmp);
	reg_tmp = codec_p2p_error_info_check(p2p);
	codec_p2p_error_mux_set(p2p, reg_tmp);
	reg_tmp = pm_p2p_error_info_check(p2p);
	pm_p2p_error_mux_set(p2p, reg_tmp);
	reg_tmp = xc_p2p_error_info_check(p2p);
	xc_p2p_error_mux_set(p2p, reg_tmp);
	return scnprintf(buf, P2PSIZE, "%d\n", reg_all);
}
static DEVICE_ATTR_RO(all_mux);

static struct attribute *p2p_extend_attrs[] = {
	&dev_attr_top_mux.attr,
	&dev_attr_au_mux.attr,
	&dev_attr_codec_mux.attr,
	&dev_attr_pm_mux.attr,
	&dev_attr_xc_mux.attr,
	&dev_attr_all_mux.attr,
	NULL,
};

static const struct attribute_group p2p_extend_attr_group = {
	.name = "p2p_extend",
	.attrs = p2p_extend_attrs
};

static int mtk_infrap2p_probe(struct platform_device *pdev)
{
	struct device_node *np = NULL;
	struct mtk_infrap2p *infra;
	int ret;

	np = pdev->dev.of_node;
	if (!np) {
		dev_err(&pdev->dev, "fail to find node\n");
		return -EINVAL;
	}

	infra = devm_kzalloc(&pdev->dev, sizeof(*infra), GFP_KERNEL);
	if (!infra)
		return -ENOMEM;

	infra->base_top = of_iomap(np, 0);
	infra->base_pm = of_iomap(np, 1);
	infra->base_xc = of_iomap(np, XC_ID);
	infra->base_xc_en = of_iomap(np, XC_ID_EN);

	if (!infra->base_top) {
		kfree(infra);
		return -EINVAL;
	}
	if (!infra->base_pm) {
		kfree(infra);
		return -EINVAL;
	}
	if (!infra->base_xc) {
		kfree(infra);
		return -EINVAL;
	}
	if (!infra->base_xc_en) {
		kfree(infra);
		return -EINVAL;
	}
	platform_set_drvdata(pdev, infra);
	top_p2p_error_info_set(infra);
	au_p2p_error_info_set(infra);
	codec_p2p_error_info_set(infra);
	pm_p2p_error_info_set(infra);
	xc_p2p_error_info_set(infra);
	ret = sysfs_create_group(&pdev->dev.kobj, &p2p_extend_attr_group);
	if (ret < 0)
		dev_info(&pdev->dev, "sysfs_create_group p2p failed: %d\n", ret);
	return 0;
}


#if defined(CONFIG_OF)
static const struct of_device_id mtkinfrap2p_of_device_ids[] = {
		{.compatible = "mediatek,infra-p2p" },
		{},
};
#endif

static struct platform_driver mtk_infrap2p_driver = {
	.probe		= mtk_infrap2p_probe,
	.driver		= {
#if defined(CONFIG_OF)
	.of_match_table = mtkinfrap2p_of_device_ids,
#endif
	.name   = "mediatek,infra-p2p",
	.owner  = THIS_MODULE,
}
};

module_platform_driver(mtk_infrap2p_driver);

MODULE_AUTHOR("Owen.Tseng@mediatek.com");
MODULE_DESCRIPTION("infrap2p driver");
MODULE_LICENSE("GPL");

