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