ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/mfd/asr_auxadc.c b/marvell/linux/drivers/mfd/asr_auxadc.c
new file mode 100644
index 0000000..66e035e
--- /dev/null
+++ b/marvell/linux/drivers/mfd/asr_auxadc.c
@@ -0,0 +1,487 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for asr auxadc driver
+ *
+ * Copyright 2023 ASR Microelectronics (Shanghai) Co., Ltd.
+ *
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/uaccess.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/cputype.h>
+#include <soc/asr/addr-map.h>
+
+#define APBS_REG_12	(0x12c)
+#define APBS_REG_3	(0x108)
+
+#define AUX_REG_BASE_OFFSET (0x80)
+#define AUX_REG_DATA (0x80 - AUX_REG_BASE_OFFSET)
+#define AUX_INT_CLR_REG (0xF0 - AUX_REG_BASE_OFFSET)
+#define AUX_INT_STS_REG (0xA0 - AUX_REG_BASE_OFFSET)
+
+#define OLD_AUXADC_REG_CTRL			(0x0)
+#define OLD_AUXADC_REG_CFG			(0x4)
+#define OLD_AUXADC_REG_INT_STATUS	(0x8)
+#define OLD_AUXADC_REG_ADCTIME_CTRL	(0xC)
+#define OLD_AUXADC_REG_DATA			(0x10)
+#define OLD_AUXADC_REG_CTRL2		(0x70)
+
+
+struct asr_auxadc_fusedata {
+	u16 auxadc_gain_offset;
+	u16 auxadc_gain_error;
+	u16 fuse_bits;
+	u16 fuse_mask_all;
+	u16 fuse_mask_data;
+};
+
+struct asr_auxadc {
+	struct device	*dev;
+	struct clk	*clk;
+	void __iomem	*base;
+	void __iomem	*adc_ctrl_reg;
+	struct asr_auxadc_fusedata fuse_data;
+};
+DEFINE_MUTEX(asr_auxadc_lock);
+static struct asr_auxadc *g_auxadc;
+extern int extern_get_auxadc_fusedata(u16 *gain_offset, u16 *gain_error);
+
+static int asr_auxadc_fusedata_init(struct asr_auxadc *asr_auxadc)
+{
+	extern_get_auxadc_fusedata(&asr_auxadc->fuse_data.auxadc_gain_offset,
+				&asr_auxadc->fuse_data.auxadc_gain_error);
+	if (cpu_is_asr1828() || cpu_is_asr1901() || cpu_is_asr1906()) {
+		asr_auxadc->fuse_data.fuse_bits = 12;
+		asr_auxadc->fuse_data.fuse_mask_data = 0x7ff;
+		asr_auxadc->fuse_data.fuse_mask_all = 0xfff;
+	} else {
+		asr_auxadc->fuse_data.fuse_bits = 5;
+		asr_auxadc->fuse_data.fuse_mask_data = 0xf;
+		asr_auxadc->fuse_data.fuse_mask_all = 0x1f;	
+	}
+
+	pr_info("auxadc fusedata: %03x %03x\n",
+			asr_auxadc->fuse_data.auxadc_gain_offset,
+			asr_auxadc->fuse_data.auxadc_gain_error);
+
+	return 0;
+}
+
+static int asr_auxadc_base_init(struct asr_auxadc *asr_auxadc)
+{
+	u32 value;
+	void __iomem *apbs_base = regs_addr_get_va(REGS_ADDR_APBS);
+
+	if (cpu_is_asr1828() || cpu_is_asr1903())
+		g_auxadc->adc_ctrl_reg = apbs_base + APBS_REG_3;
+	else
+		g_auxadc->adc_ctrl_reg = apbs_base + APBS_REG_12;
+
+	if (cpu_is_asr1806()) {
+		value = readl(g_auxadc->adc_ctrl_reg);
+		value &= ~(0xffff0000);
+		value |= (0x1060 << 16); /* clk div4 and 2clk sample delay */
+		writel(value, g_auxadc->adc_ctrl_reg);
+	} else if (cpu_is_asr1903() || cpu_is_asr1828()) {
+		value = readl(g_auxadc->adc_ctrl_reg);
+		value |= (0x1 << 22);
+		if (cpu_is_asr1828())
+			value |= (0x3 << 24);
+		writel(value, g_auxadc->adc_ctrl_reg);
+	} else if (cpu_is_asr1901() || cpu_is_asr1906()) {
+		value = readl(asr_auxadc->base + OLD_AUXADC_REG_CFG);
+		value &= ~(0x1 << 19);
+		writel(value, asr_auxadc->base + OLD_AUXADC_REG_CFG);
+	}
+
+	return 0;
+}
+
+static ssize_t auxadc_debug_read(struct file *file, char __user *userbuf,
+			       size_t count, loff_t *ppos)
+{
+	unsigned int len = 0;
+	char *buf;
+	ssize_t ret;
+
+	if (*ppos)
+		return 0;
+
+	buf = kzalloc(128, GFP_KERNEL);
+	if (!buf) {
+		pr_err("Cannot allocate buffer!\n");
+		return -ENOMEM;
+	}
+
+	len += sprintf(buf + len, "adc1: %04d\n", extern_get_auxadc_volt(ASR_AUXADC1));
+	len += sprintf(buf + len, "adc2: %04d\n", extern_get_auxadc_volt(ASR_AUXADC2));
+	len += sprintf(buf + len, "adc3: %04d\n", extern_get_auxadc_volt(ASR_AUXADC3));
+	len += sprintf(buf + len, "adc4: %04d\n", extern_get_auxadc_volt(ASR_AUXADC4));
+	len += sprintf(buf + len, "adc5: %04d\n", extern_get_auxadc_volt(ASR_AUXADC5));
+
+	ret = simple_read_from_buffer(userbuf, count, ppos, buf, len);
+	kfree(buf);
+	return ret;
+}
+
+static const struct file_operations auxadc_debug_ops = {
+	.owner		= THIS_MODULE,
+	.open		= simple_open,
+	.read		= auxadc_debug_read,
+	.write		= NULL,
+};
+
+static int asr_auxadc_debugfs_init(struct asr_auxadc *asr_auxadc)
+{
+	struct dentry *auxadc_entry;
+
+	auxadc_entry = debugfs_create_file("auxadc", S_IRUGO | S_IFREG,
+			    NULL, (void *)asr_auxadc, &auxadc_debug_ops);
+
+	if (auxadc_entry == NULL) {
+		dev_err(g_auxadc->dev, "create auxadc debugfs error!\n");
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+int __extern_get_old_auxadc_volt(int adc_id)
+{
+	u32 value;
+	u8 adc_channel;
+	int timeout, out_value, raw_value = 0;
+	void __iomem *base = g_auxadc->base;
+	int nr_avg = 0, sample_index = 0, nr_total_samples = (16 + 2);
+	int raw_min = 0xfffffff, raw_max = 0;
+
+	adc_channel = (adc_id - ASR_AUXADC1) & 0xf;
+
+	might_sleep();
+	mutex_lock(&asr_auxadc_lock);
+	/* step 1. powerup adc */
+	value = readl(base + OLD_AUXADC_REG_CTRL2);
+	value |= (0x1 << 6);
+	writel(value, base + OLD_AUXADC_REG_CTRL2);
+	udelay(20);
+
+	/* step 2. clear CTRL and INT STATUS */
+	while (sample_index < nr_total_samples) {
+		writel(0x0, base + OLD_AUXADC_REG_CTRL);
+		writel(0x3ff, base + OLD_AUXADC_REG_INT_STATUS);
+
+		usleep_range(50,50);
+		/* step 3. start SOC */
+		value = readl(base + OLD_AUXADC_REG_CTRL);
+		value |= (0x1 | (0x1 << (adc_channel + 1)) | (0x1 << (adc_channel + 16)));
+		writel(value, base + OLD_AUXADC_REG_CTRL);
+
+		/* step 4. polling  int status */
+		timeout = 10000;
+		while ((readl(base + OLD_AUXADC_REG_INT_STATUS) == 0) && timeout--)
+			ndelay(100);
+
+		if (timeout <= 0) {
+			dev_err(g_auxadc->dev, "aux adc%d timeout: 0x%x 0x%x\n",
+				adc_channel, readl(base + OLD_AUXADC_REG_CTRL), readl(base + OLD_AUXADC_REG_CFG));
+			out_value = -EINVAL;
+			goto err_out;
+		}
+
+		if (sample_index >= 2) {
+			out_value = readl(base + OLD_AUXADC_REG_DATA + (adc_channel << 2)) & 0xFFF;
+			if (raw_min > out_value)
+				raw_min = out_value;
+			if (raw_max < out_value)
+				raw_max = out_value;
+			raw_value += out_value;
+			nr_avg++;
+		}
+		sample_index++;
+	}
+
+	raw_value -= (raw_max + raw_min);
+	raw_value = raw_value / (nr_avg - 2);
+
+	pr_debug("timeout: %d, raw_value: %x 0x%x\n", timeout, raw_value, readl(g_auxadc->base + OLD_AUXADC_REG_INT_STATUS));
+
+	if ((0x1 << 11) & g_auxadc->fuse_data.auxadc_gain_error)
+		raw_value = raw_value * (16384 - (1 * ((0x1 << 12) - ((g_auxadc->fuse_data.auxadc_gain_error & 0xfff)))));
+	else
+		raw_value = raw_value * (16384 + (1 * (g_auxadc->fuse_data.auxadc_gain_error & 0x7ff)));
+
+	raw_value = ((raw_value * 24) >> 12) * 50;
+
+	if ((0x1 << 11) & g_auxadc->fuse_data.auxadc_gain_offset)
+		raw_value = raw_value - (328 * ((0x1 << 12) - (g_auxadc->fuse_data.auxadc_gain_offset & 0xfff)));
+	else
+		raw_value = raw_value + (328 * (g_auxadc->fuse_data.auxadc_gain_offset & 0x7ff));
+
+	if (raw_value < 0) {
+		pr_info("aux neg: %d\n", raw_value);
+		raw_value = 0;
+	}
+	out_value = (raw_value >> 14);
+
+err_out:
+	writel(0x0, base + OLD_AUXADC_REG_CTRL);
+	writel(0x3ff, base + OLD_AUXADC_REG_INT_STATUS);
+	/* power off adc */
+	value = readl(base + OLD_AUXADC_REG_CTRL2);
+	value &= ~(0x1 << 6);
+	writel(value, base + OLD_AUXADC_REG_CTRL2);
+	mutex_unlock(&asr_auxadc_lock);
+
+	return out_value;
+}
+
+int asr1828_calc_adc_val(int raw_value)
+{
+	int div_value;
+
+	if ((0x1 << (g_auxadc->fuse_data.fuse_bits - 1)) & g_auxadc->fuse_data.auxadc_gain_offset)
+		raw_value = raw_value + ((0x1 << g_auxadc->fuse_data.fuse_bits) - (g_auxadc->fuse_data.auxadc_gain_offset & g_auxadc->fuse_data.fuse_mask_all));
+	else
+		raw_value = raw_value - ((g_auxadc->fuse_data.auxadc_gain_offset & g_auxadc->fuse_data.fuse_mask_data));
+
+	if ((0x1 << (g_auxadc->fuse_data.fuse_bits - 1)) & g_auxadc->fuse_data.auxadc_gain_error)
+		div_value = (4800 + (((0x1 << g_auxadc->fuse_data.fuse_bits) - ((g_auxadc->fuse_data.auxadc_gain_error & g_auxadc->fuse_data.fuse_mask_all)))));
+	else
+		div_value = (4800 - ((g_auxadc->fuse_data.auxadc_gain_error & g_auxadc->fuse_data.fuse_mask_data)));
+
+	raw_value = (raw_value * 4800 * 60) >> 12;
+
+	raw_value = (raw_value * 20) / div_value;
+
+	if (raw_value < 0) {
+		pr_info("aux neg: %d\n", raw_value);
+		raw_value = 0;
+	}
+
+	return raw_value;
+}
+
+int __extern_get_new_auxadc_volt(int adc_id)
+{
+	u32 value;
+	u8 adc_channel;
+	int timeout, out_value, raw_value = 0;
+	int nr_avg = 0, sample_index = 0, nr_total_samples = (3 + 16);
+	int raw_min = 0xfffffff, raw_max = 0;
+
+	might_sleep();
+	mutex_lock(&asr_auxadc_lock);
+	/* step 1. powerup adc*/
+	value = readl(g_auxadc->adc_ctrl_reg);
+	if (value == 0)
+		BUG();
+	value |= (0x1 << 8);
+	writel(value, g_auxadc->adc_ctrl_reg);
+
+	if (cpu_is_asr1828())
+		udelay(20);
+
+	adc_channel = (adc_id - ASR_AUXADC1 + 1) & 0x1f;
+
+	while (sample_index < nr_total_samples) {
+		/* 
+		* step 2. set adc channel, clr soc and continous mode
+		* clear INT STATUS 
+		*/
+		value = readl(g_auxadc->adc_ctrl_reg);
+		if (value == 0)
+			BUG();
+		value &= ~(0x7F << 9);
+		value |= (adc_channel << 11);
+		writel(value, g_auxadc->adc_ctrl_reg);
+
+		usleep_range(50,50);
+		if (!cpu_is_asr1828())
+			writel(0x1, g_auxadc->base + AUX_INT_CLR_REG);
+
+		/* clear the data reg */
+		if (cpu_is_asr1828())
+			writel(0x0, g_auxadc->base + AUX_REG_DATA + (0 << 2));
+
+		/* step 3. set soc */
+		value = readl(g_auxadc->adc_ctrl_reg);
+		if (value == 0)
+			BUG();
+		value |= (0x1 << 9);
+		writel(value, g_auxadc->adc_ctrl_reg);
+
+		if (cpu_is_asr1828()) {
+			/* step 4. polling adc value */
+			timeout = 200;
+			while (((readl(g_auxadc->base + AUX_REG_DATA + (0 << 2)) & 0xFFF0000) == 0) && (timeout--))
+				ndelay(100);
+			if (timeout <= 0) {
+				printk(KERN_DEBUG "aux adc%d timeout\n", adc_channel);
+			}
+		} else {
+			/* step 4. polling int status */
+			timeout = 10000;
+			while ((readl(g_auxadc->base + AUX_INT_STS_REG) == 0) && timeout--)
+				ndelay(100);
+
+			if (timeout <= 0) {
+				dev_err(g_auxadc->dev, "aux adc%d timeout: 0x%x\n",
+					adc_channel, readl(g_auxadc->adc_ctrl_reg));
+				out_value = -EINVAL;
+				goto err_out;
+			}
+		}
+
+		if (sample_index >= 3) {
+			if (cpu_is_asr1828())
+				out_value = (readl(g_auxadc->base + AUX_REG_DATA + (0 << 2)) >> 16) & 0xFFF;
+			else
+				out_value = readl(g_auxadc->base + AUX_REG_DATA + (0 << 2)) & 0xFFF;
+			if (raw_min > out_value)
+				raw_min = out_value;
+			if (raw_max < out_value)
+				raw_max = out_value;
+			raw_value += out_value;
+			nr_avg++;
+		}
+		sample_index++;
+	}
+	raw_value -= (raw_max + raw_min);
+	raw_value = raw_value / (nr_avg - 2);
+
+	pr_debug("timeout: %d, raw_value: %x 0x%x\n", timeout, raw_value, readl(g_auxadc->base + AUX_INT_STS_REG));
+
+	if (cpu_is_asr1828()) {
+		out_value = asr1828_calc_adc_val(raw_value);
+		goto err_out;
+	}
+
+	if ((0x1 << (g_auxadc->fuse_data.fuse_bits - 1)) & g_auxadc->fuse_data.auxadc_gain_error)
+		raw_value = raw_value * (6000 - (10 * ((0x1 << g_auxadc->fuse_data.fuse_bits) - ((g_auxadc->fuse_data.auxadc_gain_error & g_auxadc->fuse_data.fuse_mask_all)))));
+	else
+		raw_value = raw_value * (6000 + (10 * (g_auxadc->fuse_data.auxadc_gain_error & g_auxadc->fuse_data.fuse_mask_data)));
+
+	if ((0x1 << (g_auxadc->fuse_data.fuse_bits - 1)) & g_auxadc->fuse_data.auxadc_gain_offset)
+		raw_value = raw_value - (12000 * ((0x1 << g_auxadc->fuse_data.fuse_bits) - (g_auxadc->fuse_data.auxadc_gain_offset & g_auxadc->fuse_data.fuse_mask_all)));
+	else
+		raw_value = raw_value + (12000 * (g_auxadc->fuse_data.auxadc_gain_offset & g_auxadc->fuse_data.fuse_mask_data));
+
+	if (raw_value < 0) {
+		pr_info("aux neg: %d\n", raw_value);
+		raw_value = 0;
+	}
+	out_value = (raw_value >> 12) / 5;
+
+err_out:
+	if (!cpu_is_asr1828())
+		writel(0x1, g_auxadc->base + AUX_INT_CLR_REG);
+	/* power off adc */
+	value = readl(g_auxadc->adc_ctrl_reg);
+	if (value == 0)
+		BUG();
+	value &= ~(0xff << 8);
+	writel(value, g_auxadc->adc_ctrl_reg);
+	mutex_unlock(&asr_auxadc_lock);
+
+	return out_value;
+}
+
+int extern_get_auxadc_volt(int adc_id)
+{
+	if (cpu_is_asr1806() || cpu_is_asr1903() || cpu_is_asr1828())
+		return __extern_get_new_auxadc_volt(adc_id);
+	else if (cpu_is_asr1901() || cpu_is_asr1906())
+		return __extern_get_old_auxadc_volt(adc_id);
+	else {
+		pr_err("auxadc not supported\n");
+		return -EINVAL;
+	}
+}
+
+static int asr_auxadc_probe(struct platform_device *pdev)
+{
+	struct asr_auxadc *asr_auxadc;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	int ret = 0;
+
+	asr_auxadc = devm_kzalloc(dev, sizeof(*asr_auxadc), GFP_KERNEL);
+	if (!asr_auxadc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	asr_auxadc->base = ioremap(res->start, resource_size(res));
+	if (IS_ERR(asr_auxadc->base)) {
+		dev_err(&pdev->dev, "asr_auxadc base error\n");
+		return PTR_ERR(asr_auxadc->base);
+	}
+	asr_auxadc->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(asr_auxadc->clk)) {
+		dev_err(&pdev->dev, "asr_auxadc clk error\n");
+		return PTR_ERR(asr_auxadc->clk);
+	}
+	clk_prepare_enable(asr_auxadc->clk);
+	asr_auxadc->dev = &pdev->dev;
+	g_auxadc = asr_auxadc;
+	ret = asr_auxadc_fusedata_init(asr_auxadc);
+	asr_auxadc_base_init(asr_auxadc);
+	asr_auxadc_debugfs_init(asr_auxadc);
+	platform_set_drvdata(pdev, asr_auxadc);
+
+	dev_info(&pdev->dev, "auxadc done\n");
+
+	return ret;
+}
+
+static int asr_auxadc_remove(struct platform_device *pdev)
+{
+	struct asr_auxadc *asr_auxadc = platform_get_drvdata(pdev);
+	platform_set_drvdata(pdev, NULL);
+	kfree(asr_auxadc);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id asr_auxadc_id_table[] = {
+	{ .compatible = "asr,auxadc" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, asr_auxadc_id_table);
+#endif
+
+static struct platform_driver asr_auxadc_driver = {
+	.probe		= asr_auxadc_probe,
+	.remove		= asr_auxadc_remove,
+	.driver		= {
+		.name	= "asr-auxadc",
+		.owner	= THIS_MODULE,
+#ifdef CONFIG_OF
+		.of_match_table = of_match_ptr(asr_auxadc_id_table),
+#endif
+	},
+};
+
+static int __init asr_auxadc_init(void)
+{
+	return platform_driver_register(&asr_auxadc_driver);
+}
+
+static void __exit asr_auxadc_exit(void)
+{
+	platform_driver_unregister(&asr_auxadc_driver);
+}
+
+arch_initcall(asr_auxadc_init);
+module_exit(asr_auxadc_exit);
+
+MODULE_DESCRIPTION("ASR AUXADC DRIVER");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:asr-auxadc");