linuxOS_D21X/source/linux-5.10/drivers/misc/artinchip-adcim.c
2024-11-29 16:33:21 +08:00

557 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* ADCIM driver of ArtInChip SoC
*
* Copyright (C) 2020-2024 ArtInChip Technology Co., Ltd.
* Authors: Matteo <duanmt@artinchip.com>
*/
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/reset.h>
#include <linux/of.h>
#include <linux/delay.h>
/* Register of ADCIM */
#define ADCIM_MCSR 0x000
#define ADCIM_CALCSR 0x004
#define ADCIM_FIFOSTS 0x008
#define ADCIM_VERSION 0xFFC
#define ADCIM_MCSR_BUSY BIT(16)
#define ADCIM_MCSR_SEMFLAG_SHIFT 8
#define ADCIM_MCSR_SEMFLAG_MASK GENMASK(15, 8)
#define ADCIM_MCSR_CHN_MASK GENMASK(3, 0)
#define ADCIM_CALCSR_CALVAL_UPD BIT(31)
#define ADCIM_CALCSR_CALVAL_SHIFT 16
#define ADCIM_CALCSR_CALVAL_MASK GENMASK(27, 16)
#define ADCIM_CALCSR_ADC_ACQ_SHIFT 8
#define ADCIM_CALCSR_ADC_ACQ_MASK GENMASK(15, 8)
#define ADCIM_CALCSR_DCAL BIT(1)
#define ADCIM_CALCSR_CAL_ENABLE BIT(0)
#define ADCIM_FIFOSTS_ADC_ARBITER_IDLE BIT(6)
#define ADCIM_FIFOSTS_FIFO_ERR BIT(5)
#define ADCIM_FIFOSTS_CTR_MASK GENMASK(4, 0)
#define ADCDM_CAL_ADC_STANDARD_VAL 0x800
#define ADCIM_CAL_ADC_VAL_OFFSET 0x32
#define ADCIM_CALCSR_NUM 6
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
#define ADCDM_RTP_CFG 0x03F0
#define ADCDM_RTP_STS 0x03F4
#define ADCDM_SRAM_CTL 0x03F8
#define ADCDM_SRAM_BASE 0x0400
#define ADCDM_RTP_CAL_VAL_SHIFT 16
#define ADCDM_RTP_CAL_VAL_MASK GENMASK(27, 16)
#define ADCDM_RTP_PDET BIT(0)
#define ADCDM_RTP_DRV_SHIFT 4
#define ADCDM_RTP_DRV_MASK GENMASK(7, 4)
#define ADCDM_RTP_VPSEL_SHIFT 2
#define ADCDM_RTP_VPSEL_MASK GENMASK(3, 2)
#define ADCDM_RTP_VNSEL_MASK GENMASK(1, 0)
#define ADCDM_SRAM_CLR_SHIFT 16
#define ADCDM_SRAM_CLR(n) (1 << ((n) + ADCDM_SRAM_CLR_SHIFT))
#define ADCDM_SRAM_MODE_SHIFT 8
#define ADCDM_SRAM_MODE BIT(8)
#define ADCDM_SRAM_SEL_MASK GENMASK(3, 0)
#define ADCDM_SRAM_SEL(n) (n)
#define ADCDM_SRAM_SIZE (512 * 4)
#define ADCDM_CHAN_NUM 16
enum adcdm_sram_mode {
ADCDM_NORMAL_MODE,
ADCDM_DEBUG_MODE
};
#endif
struct adcim_dev {
struct attribute_group attrs;
void __iomem *regs;
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
u32 dm_cur_chan;
#endif
struct platform_device *pdev;
struct clk *clk;
struct reset_control *rst;
int usr_cnt;
};
static struct adcim_dev *g_adcim_dev;
static int g_adcim_caled_param;
static DEFINE_SPINLOCK(user_lock);
void adcim_request(void)
{
spin_lock(&user_lock);
g_adcim_dev->usr_cnt++;
spin_unlock(&user_lock);
}
EXPORT_SYMBOL(adcim_request);
void adcim_release(void)
{
spin_lock(&user_lock);
g_adcim_dev->usr_cnt--;
spin_unlock(&user_lock);
}
EXPORT_SYMBOL(adcim_release);
int adcim_set_calibration(unsigned int val)
{
int cal;
if (val > 4095) {
pr_err("The calibration value %d is too big\n", val);
return -EINVAL;
}
spin_lock(&user_lock);
cal = readl(g_adcim_dev->regs + ADCIM_CALCSR);
cal = (cal & ~ADCIM_CALCSR_CALVAL_MASK)
| (val << ADCIM_CALCSR_CALVAL_SHIFT);
cal = cal | ADCIM_CALCSR_CALVAL_UPD;
writel(cal, g_adcim_dev->regs + ADCIM_CALCSR);
spin_unlock(&user_lock);
return 0;
}
EXPORT_SYMBOL(adcim_set_calibration);
static int adcim_set_dcalmask(void)
{
int val;
val = readl(g_adcim_dev->regs + ADCIM_CALCSR);
val = val | ADCIM_CALCSR_DCAL | ADCIM_CALCSR_ADC_ACQ_MASK;
writel(val, g_adcim_dev->regs + ADCIM_CALCSR);
val = readl(g_adcim_dev->regs + ADCIM_CALCSR);
return val;
}
static ssize_t status_show(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int mcsr;
int fifo;
int version;
struct adcim_dev *adcim = dev_get_drvdata(dev);
void __iomem *regs = adcim->regs;
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
int rtp_cfg, rtp_sts, sram_ctl;
#endif
spin_lock(&user_lock);
mcsr = readl(regs + ADCIM_MCSR);
fifo = readl(regs + ADCIM_FIFOSTS);
version = readl(regs + ADCIM_VERSION);
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
rtp_cfg = readl(regs + ADCDM_RTP_CFG);
rtp_sts = readl(regs + ADCDM_RTP_STS);
sram_ctl = readl(regs + ADCDM_SRAM_CTL);
#endif
spin_unlock(&user_lock);
return sprintf(buf, "In ADCIM V%d.%02d:\n"
"Busy state: %d\n"
"Semflag: %ld\n"
"Current Channel: %ld\n"
"ADC Arbiter Idel: %d\n"
"FIFO Error: %d\n"
"FIFO Counter: %ld\n"
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
"\nIn DM:\nMode: %s, Current channel: %d/%ld\n"
"Calibration val: %ld\n"
"RTP PDET: %ld, DRV: %ld, VPSEL: %ld, VNSEL: %ld\n"
#endif
, version >> 8, version & 0xff,
(mcsr & ADCIM_MCSR_BUSY) ? 1 : 0,
(mcsr & ADCIM_MCSR_SEMFLAG_MASK)
>> ADCIM_MCSR_SEMFLAG_SHIFT,
mcsr & ADCIM_MCSR_CHN_MASK,
fifo & ADCIM_FIFOSTS_ADC_ARBITER_IDLE ? 1 : 0,
fifo & ADCIM_FIFOSTS_FIFO_ERR ? 1 : 0,
fifo & ADCIM_FIFOSTS_CTR_MASK
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
, sram_ctl & ADCDM_SRAM_MODE ? "Debug" : "Normal",
adcim->dm_cur_chan, sram_ctl & ADCDM_SRAM_SEL_MASK,
(rtp_cfg & ADCDM_RTP_CAL_VAL_MASK)
>> ADCDM_RTP_CAL_VAL_SHIFT,
rtp_cfg & ADCDM_RTP_PDET,
(rtp_sts & ADCDM_RTP_DRV_MASK) >> ADCDM_RTP_DRV_SHIFT,
(rtp_sts & ADCDM_RTP_VPSEL_MASK)
>> ADCDM_RTP_VPSEL_SHIFT,
rtp_sts & ADCDM_RTP_VNSEL_MASK
#endif
);
}
static DEVICE_ATTR_RO(status);
static ssize_t version_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
int version;
struct adcim_dev *adcim = dev_get_drvdata(dev);
void __iomem *regs = adcim->regs;
version = readl(regs + ADCIM_VERSION);
return sprintf(buf, "%d.%02d\n", version >> 8, version & 0xff);
}
static DEVICE_ATTR_RO(version);
static ssize_t calibration_show(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int cal;
spin_lock(&user_lock);
cal = readl(g_adcim_dev->regs + ADCIM_CALCSR);
spin_unlock(&user_lock);
return sprintf(buf, "Calibration Enable: %d\n \
Current value: %ld\n \
ADC ACQ: %ld\n", (cal & ADCIM_CALCSR_CAL_ENABLE) ? 0 : 1,
(cal & ADCIM_CALCSR_CALVAL_MASK) >> ADCIM_CALCSR_CALVAL_SHIFT,
(cal & ADCIM_CALCSR_ADC_ACQ_MASK) >> ADCIM_CALCSR_ADC_ACQ_SHIFT);
}
static ssize_t calibration_store(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
int ret;
unsigned long val = 0;
ret = kstrtoul(buf, 0, &val);
if (ret)
return ret;
ret = adcim_set_calibration(val);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR_RW(calibration);
u16 adcim_get_cali_param(void)
{
u32 flag = 1;
u32 data = 0;
int count = 0;
int max = 0;
int min = 0;
int i;
void __iomem *regs = g_adcim_dev->regs;
u32 cal_array[ADCIM_CALCSR_NUM];
for (i = 0; i < ADCIM_CALCSR_NUM; i++) {
writel(0x08002f03, regs + ADCIM_CALCSR);//auto cal
do {
flag = readl(regs + ADCIM_CALCSR) & 0x00000001;
count++;
if (count > 10000) {
dev_err(&g_adcim_dev->pdev->dev, "Adcim auto calibration parameter acquisition timeout");
return -1;
}
} while (flag);
cal_array[i] = (readl(regs + ADCIM_CALCSR) >> ADCIM_CALCSR_CALVAL_SHIFT) & 0xfff;
if (cal_array[i] > max)
max = cal_array[i];
if (i == 0)
min = cal_array[0];
else if (cal_array[i] < min)
min = cal_array[i];
data += cal_array[i];
}
g_adcim_caled_param = (data - min - max) / (ADCIM_CALCSR_NUM - 2);
return 0;
}
EXPORT_SYMBOL(adcim_get_cali_param);
u16 adcim_auto_calibration(u16 *adc_val, struct device *dev)
{
*adc_val = *adc_val + ADCDM_CAL_ADC_STANDARD_VAL - g_adcim_caled_param
+ ADCIM_CAL_ADC_VAL_OFFSET;
return 0;
}
EXPORT_SYMBOL(adcim_auto_calibration);
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
static ssize_t dm_chan_show(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
struct adcim_dev *adcim = dev_get_drvdata(dev);
return sprintf(buf, "Current chan: %d/%ld\n", adcim->dm_cur_chan,
readl(adcim->regs + ADCDM_SRAM_CTL)
& ADCDM_SRAM_SEL_MASK);
}
static ssize_t dm_chan_store(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
int ret;
unsigned long val = 0;
struct adcim_dev *adcim = dev_get_drvdata(dev);
ret = kstrtoul(buf, 0, &val);
if (ret)
return 0;
if (val >= ADCDM_CHAN_NUM) {
dev_err(dev, "Invalid channel number %lu", val);
return 0;
}
spin_lock(&user_lock);
adcim->dm_cur_chan = val;
ret = readl(adcim->regs + ADCDM_SRAM_CTL);
ret &= ~ADCDM_SRAM_SEL_MASK;
ret |= val;
writel(ret, adcim->regs + ADCDM_SRAM_CTL);
spin_unlock(&user_lock);
return count;
}
static DEVICE_ATTR_RW(dm_chan);
static ssize_t rtp_down_store(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
int ret;
unsigned long val = 0;
struct adcim_dev *adcim = dev_get_drvdata(dev);
ret = kstrtoul(buf, 0, &val);
if (ret)
return 0;
spin_lock(&user_lock);
ret = readl(adcim->regs + ADCDM_RTP_CFG);
if (val)
ret &= ~ADCDM_RTP_PDET;
else
ret |= ADCDM_RTP_PDET;
writel(ret, adcim->regs + ADCDM_RTP_CFG);
spin_unlock(&user_lock);
return count;
}
static DEVICE_ATTR_WO(rtp_down);
#endif
static struct attribute *adcim_attr[] = {
&dev_attr_status.attr,
&dev_attr_calibration.attr,
&dev_attr_version.attr,
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
&dev_attr_dm_chan.attr,
&dev_attr_rtp_down.attr,
#endif
NULL
};
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
static void adcdm_sram_clear(u32 chan)
{
u32 val = 0;
void __iomem *regs = g_adcim_dev->regs;
spin_lock(&user_lock);
val = readl(regs + ADCDM_SRAM_CTL);
val |= ADCDM_SRAM_CLR(chan);
writel(val, regs + ADCDM_SRAM_CTL);
udelay(10);
val &= ~ADCDM_SRAM_CLR(chan);
writel(val, regs + ADCDM_SRAM_CTL);
spin_unlock(&user_lock);
}
static void adcdm_sram_mode(enum adcdm_sram_mode mode)
{
u32 val = 0;
void __iomem *regs = g_adcim_dev->regs;
spin_lock(&user_lock);
val = readl(regs + ADCDM_SRAM_CTL);
if (mode)
val |= mode << ADCDM_SRAM_MODE_SHIFT;
else
val &= ~ADCDM_SRAM_MODE;
writel(val, regs + ADCDM_SRAM_CTL);
spin_unlock(&user_lock);
}
static void adcdm_sram_select(u32 chan)
{
u32 val = 0;
void __iomem *regs = g_adcim_dev->regs;
spin_lock(&user_lock);
val = readl(regs + ADCDM_SRAM_CTL);
val &= ~ADCDM_SRAM_SEL_MASK;
val |= chan;
writel(val, regs + ADCDM_SRAM_CTL);
spin_unlock(&user_lock);
}
static ssize_t adcdm_sram_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t offset, size_t count)
{
int i;
int *data = (int *)buf;
if (count + offset > ADCDM_SRAM_SIZE)
return 0;
adcdm_sram_mode(ADCDM_DEBUG_MODE);
adcdm_sram_select(g_adcim_dev->dm_cur_chan);
for (i = 0; i < count / 4; i++)
writel(*data++, g_adcim_dev->regs + ADCDM_SRAM_BASE + i * 4);
adcdm_sram_mode(ADCDM_NORMAL_MODE);
for (i = 0; i < ADCDM_CHAN_NUM; i++)
adcdm_sram_clear(i);
return count;
}
static struct bin_attribute sram_attr = {
.attr = {.name = "sram", .mode = (0200)},
.read = NULL,
.write = adcdm_sram_write,
.mmap = NULL,
};
#endif
static int adcim_probe(struct platform_device *pdev)
{
int ret;
struct adcim_dev *adcim;
adcim = devm_kzalloc(&pdev->dev, sizeof(struct adcim_dev), GFP_KERNEL);
if (!adcim)
return -ENOMEM;
adcim->pdev = pdev;
adcim->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(adcim->regs))
return PTR_ERR(adcim->regs);
adcim->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(adcim->clk)) {
dev_err(&pdev->dev, "no clock defined\n");
return PTR_ERR(adcim->clk);
}
ret = clk_prepare_enable(adcim->clk);
if (ret)
return ret;
adcim->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
if (IS_ERR(adcim->rst)) {
ret = PTR_ERR(adcim->rst);
goto disable_clk;
}
reset_control_deassert(adcim->rst);
adcim->attrs.attrs = adcim_attr;
ret = sysfs_create_group(&pdev->dev.kobj, &adcim->attrs);
if (ret)
goto disable_rst;
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
ret = device_create_bin_file(&pdev->dev, &sram_attr);
if (ret)
goto disable_rst;
#endif
adcim->usr_cnt = 0;
dev_info(&pdev->dev, "Artinchip ADCIM Loaded\n");
platform_set_drvdata(pdev, adcim);
g_adcim_dev = adcim;
adcim_set_dcalmask();
adcim_get_cali_param();
return 0;
disable_rst:
reset_control_assert(adcim->rst);
disable_clk:
clk_disable_unprepare(adcim->clk);
return ret;
}
static int adcim_remove(struct platform_device *pdev)
{
struct adcim_dev *adcim = platform_get_drvdata(pdev);
#ifdef CONFIG_ARTINCHIP_ADCIM_DM
device_remove_bin_file(&pdev->dev, &sram_attr);
#endif
spin_lock(&user_lock);
if (adcim->usr_cnt == 0) {
reset_control_assert(adcim->rst);
clk_disable_unprepare(adcim->clk);
}
spin_unlock(&user_lock);
return 0;
}
static const struct of_device_id aic_adcim_dt_ids[] = {
{.compatible = "artinchip,aic-adcim-v1.0"},
{}
};
MODULE_DEVICE_TABLE(of, aic_adcim_dt_ids);
static struct platform_driver adcim_driver = {
.driver = {
.name = "adcim",
.of_match_table = of_match_ptr(aic_adcim_dt_ids),
},
.probe = adcim_probe,
.remove = adcim_remove,
};
/* Must init ADCIM before ADC/THS/RTP, so use postcore_initcall() */
static int __init adcim_init(void)
{
return platform_driver_register(&adcim_driver);
}
postcore_initcall(adcim_init);
MODULE_AUTHOR("Matteo <duanmt@artinchip.com>");
MODULE_DESCRIPTION("ADCIM driver of Artinchip SoC");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:adcim");