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");