blob: 66e035e60aa1007953e38dea4d7a8c64c0b090ce [file] [log] [blame]
// 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");