[PATCH 1/3] drivers/iio/adc: Add XuanTie TH1520 ADC driver

wefu
2024-03-29T20:07:18Z

Signed-off-by: Wei Fu <wefu@redhat.com>
---
drivers/iio/adc/Kconfig | 13 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/xuantie-th1520-adc.c | 572 +++++++++++++++++++++++++++
drivers/iio/adc/xuantie-th1520-adc.h | 193 +++++++++
4 files changed, 779 insertions(+)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 0d9282fa67f5..9e37ba2a877a 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1515,4 +1515,17 @@ config XILINX_AMS
The driver can also be built as a module. If so, the module will be called
xilinx-ams.

+config XUANTIE_TH1520_ADC
+ tristate "XuanTie TH1520 ADC driver"
+ depends on OF
+ depends on HAS_IOMEM
+ depends on ARCH_THEAD
+ default y
+ help
+ Say yes here to support for XuanTie TH1520 MPW analog-to-digital
+ converter.
+
+ This driver can also be built as a module. If so, the module will be
+ called xuantie-th1520-adc.
+
endmenu
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index b3c434722364..820e2a136b33 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -135,4 +135,5 @@ obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o
obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o
obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
+obj-$(CONFIG_XUANTIE_TH1520_ADC) += xuantie-th1520-adc.o
obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o
diff --git a/drivers/iio/adc/xuantie-th1520-adc.c b/drivers/iio/adc/xuantie-th1520-adc.c
new file mode 100644
index 000000000000..df0452c2abe7
--- /dev/null
+++ b/drivers/iio/adc/xuantie-th1520-adc.c
@@ -0,0 +1,572 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * XuanTie TH1520 ADC driver
+ *
+ * Copyright (C) 2021-2024 Alibaba Group Holding Limited.
+ * Fugang Duan <duanfugang.dfg@linux.alibaba.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/err.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/sysfs.h>
+
+#include "xuantie-th1520-adc.h"
+
+static inline void th1520_adc_cfg_init(struct th1520_adc *info)
+{
+ struct th1520_adc_feature *adc_feature = &info->adc_feature;
+
+ /* set default Configuration for ADC controller */
+ adc_feature->selres_sel = TH1520_ADC_SELRES_12BIT;
+ adc_feature->input_mode = TH1520_ADC_SINGLE_ENDED_INPUTS;
+ adc_feature->vol_ref = TH1520_ADC_VOL_VREF;
+ adc_feature->offset_mode = TH1520_ADC_OFFSET_DIS;
+ adc_feature->conv_mode = TH1520_ADC_MODE_SINGLE;
+ adc_feature->clk_sel = TH1520_ADC_FCLK_TYP_1M;
+
+ adc_feature->int_actual = TH1520_ADC_ACTUAL_ALL;
+ adc_feature->int_detal = TH1520_ADC_DETAL_ALL;
+
+ info->ch0_offmeas = 0;
+ info->ch1_offmeas = 0;
+}
+
+static void th1520_adc_reg_set(struct th1520_adc *info)
+{
+ u32 phy_cfg = 0;
+ u32 op_ctrl = 0;
+ struct th1520_adc_feature *adc_feature = &info->adc_feature;
+
+ /* phy_cfg */
+ switch (adc_feature->selres_sel) {
+ case TH1520_ADC_SELRES_6BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_6BIT;
+ break;
+ case TH1520_ADC_SELRES_8BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_8BIT;
+ break;
+ case TH1520_ADC_SELRES_10BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_10BIT;
+ break;
+ case TH1520_ADC_SELRES_12BIT:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELRES_12BIT;
+ break;
+ default:
+ break;
+ }
+
+ switch (adc_feature->input_mode) {
+ case TH1520_ADC_SINGLE_ENDED_INPUTS:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELDIFF_SINGLE_ENDED_INPUTS;
+ break;
+ case TH1520_ADC_DIFFERENTIAL_INPUTS:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELDIFF_DIFFERENTIAL_INPUTS;
+ break;
+ default:
+ break;
+ }
+
+ switch (adc_feature->vol_ref) {
+ case TH1520_ADC_VOL_VREF:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELBG_EXTERNAL |
+ TH1520_ADC_PHY_CFG_SELREF_EXT;
+ break;
+ case TH1520_ADC_VOL_INTE:
+ phy_cfg |= TH1520_ADC_PHY_CFG_SELBG_INTERNAL |
+ TH1520_ADC_PHY_CFG_SELREF_INTERNAL;
+ break;
+ default:
+ break;
+ }
+
+ /* op_ctrl */
+ switch (adc_feature->conv_mode) {
+ case TH1520_ADC_MODE_SINGLE:
+ op_ctrl |= TH1520_ADC_OP_CTRL_MODE_SINGLE;
+ break;
+ case TH1520_ADC_MODE_CONTINOUS:
+ op_ctrl |= TH1520_ADC_OP_CTRL_MODE_CONTINOUS;
+ break;
+ default:
+ break;
+ }
+
+ writel(phy_cfg, info->regs + TH1520_ADC_PHY_CFG);
+ writel(op_ctrl, info->regs + TH1520_ADC_OP_CTRL);
+
+ writel(TH1520_ADC_PHY_ENCTR, info->regs + TH1520_ADC_PHY_TEST);
+
+ /* disable the irq */
+ writel(0xff, info->regs + TH1520_ADC_INT_CTRL1);
+ writel(0xff, info->regs + TH1520_ADC_INT_CTRL2);
+
+ if (adc_feature->conv_mode == TH1520_ADC_MODE_CONTINOUS)
+ writel(TH1520_ADC_PHY_CTRL_ENADC_EN,
+ info->regs + TH1520_ADC_PHY_CTRL);
+}
+
+static void th1520_adc_fclk_set(struct th1520_adc *info)
+{
+ int fclk_ctrl = 0;
+ int start_time = 0;
+ int sample_time = 0;
+ struct th1520_adc_feature *adc_feature = &info->adc_feature;
+
+ switch (adc_feature->clk_sel) {
+ case TH1520_ADC_FCLK_TYP_1M:
+ fclk_ctrl = TH1520_ADC_FCLK_CTRL_TYP_1M;
+ start_time = TH1520_ADC_START_TIME_TYP_1M;
+ if (adc_feature->selres_sel == TH1520_ADC_SELRES_6BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_6BIT;
+ else if (adc_feature->selres_sel == TH1520_ADC_SELRES_8BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_8BIT;
+ else if (adc_feature->selres_sel == TH1520_ADC_SELRES_10BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_10BIT;
+ else if (adc_feature->selres_sel == TH1520_ADC_SELRES_12BIT)
+ sample_time = TH1520_ADC_SAMPLE_TIME_TYP_12BIT;
+ else {
+ pr_err("[%s,%d]invalid selres select\n",
+ __func__, __LINE__);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ writel(fclk_ctrl, info->regs + TH1520_ADC_FCLK_CTRL);
+ writel(start_time, info->regs + TH1520_ADC_START_TIME);
+ writel(sample_time, info->regs + TH1520_ADC_SAMPLE_TIME);
+}
+
+static void th1520_adc_hw_init(struct th1520_adc *info)
+{
+ th1520_adc_reg_set(info);
+ th1520_adc_fclk_set(info);
+}
+
+static const struct iio_chan_spec th1520_adc_iio_channels[] = {
+ TH1520_ADC_CHAN(0, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(1, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(2, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(3, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(4, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(5, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(6, IIO_VOLTAGE),
+ TH1520_ADC_CHAN(7, IIO_VOLTAGE),
+ /* sentinel */
+};
+
+static irqreturn_t th1520_adc_isr(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct th1520_adc *info = iio_priv(indio_dev);
+ /* TBD */
+ complete(&info->completion);
+ return IRQ_HANDLED;
+}
+
+static int th1520_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int tmp;
+ long ret;
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&info->mlock);
+
+ if (info->adc_feature.conv_mode == TH1520_ADC_MODE_SINGLE) {
+ uint ievent;
+ uint vld_flag;
+ uint phy_ctrl;
+ uint real_chan;
+ uint op_ctrl = 0;
+ uint single_retry = TH1520_ADC_FIFO_DATA_SIZE;
+
+ op_ctrl = readl(info->regs + TH1520_ADC_OP_CTRL);
+ op_ctrl &= ~TH1520_ADC_OP_CTRL_CH_EN_ALL;
+ op_ctrl |= (BIT(chan->channel + TH1520_ADC_OP_CTRL_CH_EN_0) &
+ TH1520_ADC_OP_CTRL_CH_EN_ALL);
+ writel(op_ctrl, info->regs + TH1520_ADC_OP_CTRL);
+
+ writel(TH1520_ADC_PHY_CTRL_ENADC_EN,
+ info->regs + TH1520_ADC_PHY_CTRL);
+
+ vld_flag = TH1520_ADC_SAMPLE_DATA_CH0_VLD;
+
+ while (single_retry--) {
+ writel(TH1520_ADC_OP_SINGLE_START_EN,
+ info->regs + TH1520_ADC_OP_SINGLE_START);
+ /* wait the sampling result */
+ ret = readl_poll_timeout(info->regs +
+ TH1520_ADC_SAMPLE_DATA,
+ ievent,
+ ievent & vld_flag, 100,
+ TH1520_ADC_TIMEOUT);
+ if (ret)
+ pr_err("wait the sampling timeout\n");
+
+ real_chan =
+ (ievent & TH1520_ADC_SAMPLE_DATA_CH0_NUMBER) >>
+ TH1520_ADC_SAMPLE_DATA_CH0_NUMBER_OFF;
+ if (real_chan == chan->channel)
+ break;
+ }
+
+ phy_ctrl = readl(info->regs + TH1520_ADC_PHY_CTRL);
+ phy_ctrl &= ~TH1520_ADC_PHY_CTRL_ENADC_EN;
+ writel(phy_ctrl, info->regs + TH1520_ADC_PHY_CTRL);
+
+ /* read the sampling data */
+ *val = (ievent & TH1520_ADC_SAMPLE_DATA_CH0) >>
+ TH1520_ADC_SAMPLE_DATA_CH0_OFF;
+ } else {
+ uint ievent;
+ uint vld_flag;
+ uint op_single;
+ uint op_ctrl = 0;
+
+ op_ctrl = readl(info->regs + TH1520_ADC_OP_CTRL);
+ op_ctrl &= ~TH1520_ADC_OP_CTRL_CH_EN_ALL;
+ op_ctrl |= (BIT(chan->channel + TH1520_ADC_OP_CTRL_CH_EN_0) &
+ TH1520_ADC_OP_CTRL_CH_EN_ALL);
+ writel(op_ctrl, info->regs + TH1520_ADC_OP_CTRL);
+
+ op_single = readl(info->regs +
+ TH1520_ADC_OP_SINGLE_START);
+ op_single &= ~TH1520_ADC_OP_SINGLE_START_EN;
+ writel(op_single,
+ info->regs + TH1520_ADC_OP_SINGLE_START);
+
+ vld_flag = TH1520_ADC_SAMPLE_DATA_CH0_VLD |
+ TH1520_ADC_SAMPLE_DATA_CH1_VLD;
+
+ /* wait the sampling result */
+ ret = readl_poll_timeout(info->regs +
+ TH1520_ADC_SAMPLE_DATA,
+ ievent, ievent & vld_flag, 10,
+ TH1520_ADC_TIMEOUT);
+ if (ret)
+ pr_err("wait the sampling timeout\n");
+
+ /* read the sampling data */
+ tmp = readl(info->regs + TH1520_ADC_SAMPLE_DATA);
+ if (tmp & TH1520_ADC_SAMPLE_DATA_CH0_VLD)
+ *val = (tmp & TH1520_ADC_SAMPLE_DATA_CH0) >>
+ TH1520_ADC_SAMPLE_DATA_CH0_OFF;
+ else
+ *val = (tmp & TH1520_ADC_SAMPLE_DATA_CH1) >>
+ TH1520_ADC_SAMPLE_DATA_CH1_OFF;
+ }
+
+ mutex_unlock(&info->mlock);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = info->vref_uv / 1000;
+ *val2 = info->adc_feature.selres_sel;
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = info->current_clk;
+ *val2 = 0;
+ return IIO_VAL_INT;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static void th1520_adc_reset(struct th1520_adc *info)
+{
+ u32 tmp = readl(info->regs + TH1520_ADC_PHY_CTRL);
+
+ tmp |= TH1520_ADC_PHY_CTRL_RST_EN;
+ writel(tmp, info->regs + TH1520_ADC_PHY_CTRL);
+
+ usleep_range(10, 15);
+
+ tmp &= ~TH1520_ADC_PHY_CTRL_RST_EN;
+ writel(tmp, info->regs + TH1520_ADC_PHY_CTRL);
+}
+
+static void th1520_adc_set_clk(struct th1520_adc *info, int val)
+{
+ u32 count;
+ u32 apb_clk;
+ int fclk_ctrl;
+
+ apb_clk = clk_get_rate(info->clk);
+ count = DIV_ROUND_UP(apb_clk, val);
+ info->current_clk = apb_clk / count;
+
+ fclk_ctrl = readl(info->regs + TH1520_ADC_FCLK_CTRL);
+ fclk_ctrl &= ~TH1520_ADC_FCLK_CTRL_FCLLK_DIV;
+ fclk_ctrl |= count;
+ writel(fclk_ctrl, info->regs + TH1520_ADC_FCLK_CTRL);
+}
+
+static int th1520_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ if (mask != IIO_CHAN_INFO_SAMP_FREQ)
+ return -EINVAL;
+
+ mutex_lock(&info->mlock);
+ th1520_adc_set_clk(info, val);
+ mutex_unlock(&info->mlock);
+
+ return 0;
+}
+
+static const struct iio_info th1520_adc_iio_info = {
+ .read_raw = &th1520_read_raw,
+ .write_raw = &th1520_write_raw,
+};
+
+static const struct of_device_id th1520_adc_match[] = {
+ { .compatible = "thead,th1520-adc", },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, th1520_adc_match);
+
+static ssize_t th1520_adc_res_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ size_t bufpos = 0, count = 5;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ snprintf(buf + bufpos, count - bufpos, "%.*x: ", 4,
+ info->adc_feature.selres_sel);
+ bufpos += 4;
+ buf[bufpos++] = '\n';
+
+ return bufpos;
+}
+
+static ssize_t th1520_adc_res_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ unsigned long res;
+ char *start = (char *)buf;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ if (kstrtoul(start, 0, &res))
+ return -EINVAL;
+
+ switch (res) {
+ case TH1520_ADC_SELRES_6BIT:
+ case TH1520_ADC_SELRES_8BIT:
+ case TH1520_ADC_SELRES_10BIT:
+ case TH1520_ADC_SELRES_12BIT:
+ info->adc_feature.selres_sel = res;
+ th1520_adc_reset(info);
+ th1520_adc_hw_init(info);
+ break;
+ default:
+ dev_err(dev, "not support res\n");
+ return -EINVAL;
+ }
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(th1520_adc_res);
+
+static int th1520_adc_probe(struct platform_device *pdev)
+{
+ int irq;
+ int ret;
+ struct resource *mem;
+ struct th1520_adc *info;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev,
+ sizeof(struct th1520_adc));
+ if (!indio_dev) {
+ dev_err(&pdev->dev, "Failed allocating iio device\n");
+ return -ENOMEM;
+ }
+
+ info = iio_priv(indio_dev);
+ info->dev = &pdev->dev;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(info->dev, irq, th1520_adc_isr, 0,
+ dev_name(&pdev->dev), indio_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", irq);
+ return ret;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ info->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(info->regs))
+ return PTR_ERR(info->regs);
+
+ info->clk = devm_clk_get(&pdev->dev, "adc");
+ if (IS_ERR(info->clk)) {
+ dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+ PTR_ERR(info->clk));
+ return PTR_ERR(info->clk);
+ }
+
+ info->vref = devm_regulator_get(&pdev->dev, "vref");
+ if (IS_ERR(info->vref))
+ return PTR_ERR(info->vref);
+
+ ret = regulator_enable(info->vref);
+ if (ret)
+ return ret;
+
+ info->vref_uv = regulator_get_voltage(info->vref);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ mutex_init(&info->mlock);
+ init_completion(&info->completion);
+
+ indio_dev->name = dev_name(&pdev->dev);
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->dev.of_node = pdev->dev.of_node;
+ indio_dev->info = &th1520_adc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = th1520_adc_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(th1520_adc_iio_channels);
+
+ ret = clk_prepare_enable(info->clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Could not prepare or enable the clock.\n");
+ goto error_adc_clk_enable;
+ }
+
+ th1520_adc_cfg_init(info);
+ th1520_adc_reset(info);
+ th1520_adc_hw_init(info);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't register the device.\n");
+ goto error_iio_device_register;
+ }
+
+ ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_th1520_adc_res.attr);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create adc debug sysfs.\n");
+ goto error_iio_device_register;
+ }
+
+ dev_info(&pdev->dev, "XuanTie TH1520 adc registered.\n");
+ return 0;
+
+error_iio_device_register:
+ clk_disable_unprepare(info->clk);
+error_adc_clk_enable:
+ regulator_disable(info->vref);
+
+ return ret;
+}
+
+static int th1520_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ regulator_disable(info->vref);
+ clk_disable_unprepare(info->clk);
+
+ return 0;
+}
+
+static int __maybe_unused th1520_adc_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+ int phy_ctrl;
+
+ phy_ctrl = readl(info->regs + TH1520_ADC_PHY_CTRL);
+ phy_ctrl &= ~TH1520_ADC_PHY_CTRL_ENADC_EN;
+ writel(phy_ctrl, info->regs + TH1520_ADC_PHY_CTRL);
+
+ clk_disable_unprepare(info->clk);
+ regulator_disable(info->vref);
+
+ return 0;
+}
+
+static int __maybe_unused th1520_adc_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct th1520_adc *info = iio_priv(indio_dev);
+ int ret;
+
+ ret = regulator_enable(info->vref);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(info->clk);
+ if (ret)
+ goto disable_reg;
+
+ th1520_adc_reset(info);
+ th1520_adc_set_clk(info, info->current_clk);
+ th1520_adc_hw_init(info);
+
+ return 0;
+
+disable_reg:
+ regulator_disable(info->vref);
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(th1520_adc_pm_ops,
+ th1520_adc_suspend, th1520_adc_resume);
+
+static struct platform_driver th1520_adc_driver = {
+ .probe = th1520_adc_probe,
+ .remove = th1520_adc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = th1520_adc_match,
+ .pm = &th1520_adc_pm_ops,
+ },
+};
+module_platform_driver(th1520_adc_driver);
+
+MODULE_AUTHOR("fugang.duan <duanfugang.dfg@linux.alibaba.com>");
+MODULE_DESCRIPTION("XuanTie TH1520 ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/xuantie-th1520-adc.h b/drivers/iio/adc/xuantie-th1520-adc.h
new file mode 100644
index 000000000000..c38d17fc6bbe
--- /dev/null
+++ b/drivers/iio/adc/xuantie-th1520-adc.h
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * XuanTie TH1520 ADC driver
+ *
+ * Copyright (C) 2021-2024 Alibaba Group Holding Limited.
+ * Fugang Duan <duanfugang.dfg@linux.alibaba.com>
+ *
+ */
+
+#include <linux/bitops.h>
+
+/* This will be the driver name the kernel reports */
+#define DRIVER_NAME "xuantie-th1520-adc"
+
+/* ADC registers */
+#define TH1520_ADC_PHY_CFG 0x00
+#define TH1520_ADC_PHY_CTRL 0x04
+#define TH1520_ADC_PHY_TEST 0x08
+#define TH1520_ADC_OP_CTRL 0x0C
+#define TH1520_ADC_OP_SINGLE_START 0x10
+#define TH1520_ADC_FCLK_CTRL 0x14
+#define TH1520_ADC_START_TIME 0x18
+#define TH1520_ADC_SAMPLE_TIME 0x1C
+#define TH1520_ADC_SAMPLE_DATA 0x20
+#define TH1520_ADC_INT_CTRL1 0x50
+#define TH1520_ADC_INT_CTRL2 0x54
+#define TH1520_ADC_INT_STATUS 0x58
+#define TH1520_ADC_INT_ACTUAL_VALUE_CH0 0x60
+#define TH1520_ADC_INT_ACTUAL_VALUE_CH1 0x64
+#define TH1520_ADC_INT_DELTA_VALUE_CH0 0x90
+#define TH1520_ADC_INT_DELTA_VALUE_CH1 0x94
+
+/* Configuration register field define */
+#define TH1520_ADC_PHY_CFG_SELRES_6BIT (0x0)
+#define TH1520_ADC_PHY_CFG_SELRES_8BIT (0x1)
+#define TH1520_ADC_PHY_CFG_SELRES_10BIT (0x2)
+#define TH1520_ADC_PHY_CFG_SELRES_12BIT (0x3)
+#define TH1520_ADC_PHY_CFG_SELDIFF_SINGLE_ENDED_INPUTS (0x0 << 4)
+#define TH1520_ADC_PHY_CFG_SELDIFF_DIFFERENTIAL_INPUTS (0x1 << 4)
+#define TH1520_ADC_PHY_CFG_SELBG_INTERNAL (0x1 << 8)
+#define TH1520_ADC_PHY_CFG_SELBG_EXTERNAL (0x0 << 8)
+#define TH1520_ADC_PHY_CFG_SELREF_INTERNAL (0x1 << 12)
+#define TH1520_ADC_PHY_CFG_SELREF_EXT (0x0 << 12)
+
+/* PHY CTRL register field define */
+#define TH1520_ADC_PHY_CTRL_ENOFFSET_EN (0x1 << 12)
+#define TH1520_ADC_PHY_CTRL_ENOFFMEAS_EN (0x1 << 8)
+#define TH1520_ADC_PHY_CTRL_RST_EN (0x1 << 4)
+#define TH1520_ADC_PHY_CTRL_ENADC_EN (0x1 << 0)
+
+/* ADC OP ctrl field define */
+#define TH1520_ADC_OP_CTRL_CH_EN_ALL GENMASK(19, 12)
+#define TH1520_ADC_OP_CTRL_CH_EN_0 (12)
+#define TH1520_ADC_OP_CTRL_MODE_SINGLE (0x1 << 0)
+#define TH1520_ADC_OP_CTRL_MODE_CONTINOUS (0x0 << 0)
+
+/* ADC OP single start */
+#define TH1520_ADC_OP_SINGLE_START_EN BIT(0)
+
+/* ADC fclk ctrl */
+#define TH1520_ADC_FCLK_CTRL_FCLLK_DIV GENMASK(6, 0)
+#define TH1520_ADC_FCLK_CTRL_TYP_1M (0x10004)
+#define TH1520_ADC_START_TIME_TYP_1M (0x160)
+#define TH1520_ADC_SAMPLE_TIME_TYP_1M (0x10)
+#define TH1520_ADC_SAMPLE_TIME_TYP_6BIT (8)
+#define TH1520_ADC_SAMPLE_TIME_TYP_8BIT (10)
+#define TH1520_ADC_SAMPLE_TIME_TYP_10BIT (12)
+#define TH1520_ADC_SAMPLE_TIME_TYP_12BIT (14)
+
+/* ADC sample data */
+#define TH1520_ADC_SAMPLE_DATA_CH1 GENMASK(27, 16)
+#define TH1520_ADC_SAMPLE_DATA_CH1_OFF (16)
+#define TH1520_ADC_SAMPLE_DATA_CH1_VLD BIT(31)
+#define TH1520_ADC_SAMPLE_DATA_CH1_NUMBER GENMASK(30, 28)
+#define TH1520_ADC_SAMPLE_DATA_CH1_NUMBER_OFF (28)
+#define TH1520_ADC_SAMPLE_DATA_CH0 GENMASK(11, 0)
+#define TH1520_ADC_SAMPLE_DATA_CH0_VLD BIT(15)
+#define TH1520_ADC_SAMPLE_DATA_CH0_OFF (0)
+#define TH1520_ADC_SAMPLE_DATA_CH0_NUMBER GENMASK(14, 12)
+#define TH1520_ADC_SAMPLE_DATA_CH0_NUMBER_OFF (12)
+
+/* ADC INT Ctrl */
+#define TH1520_ADC_INT_CTRL1_CH1_INT_MODE BIT(1)
+#define TH1520_ADC_INT_CTRL1_CH0_INT_MODE BIT(0)
+#define TH1520_ADC_INT_CTRL2_CH1_INT_MASK BIT(1)
+#define TH1520_ADC_INT_CTRL2_CH0_INT_MASK BIT(0)
+#define TH1520_ADC_INT_STS_CH1_INT_STS BIT(1)
+#define TH1520_ADC_INT_STS_CH0_INT_STS BIT(0)
+
+#define TH1520_ADC_ACTUAL_VALUE_CH0_HVAL GENMASK(27, 16)
+#define TH1520_ADC_ACTUAL_VALUE_CH0_HVAL_OFF (16)
+#define TH1520_ADC_ACTUAL_VALUE_CH0_LVAL GENMASK(11, 0)
+#define TH1520_ADC_ACTUAL_VALUE_CH0_LVAL_OFF (0)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_HVAL GENMASK(27, 16)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_HVAL_OFF (16)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_LVAL GENMASK(11, 0)
+#define TH1520_ADC_ACTUAL_VALUE_CH1_LVAL_OFF (0)
+
+#define TH1520_ADC_DLT_VALUE_CH0_HVAL GENMASK(27, 16)
+#define TH1520_ADC_DLT_VALUE_CH0_HVAL_OFF (16)
+#define TH1520_ADC_DLT_VALUE_CH0_LVAL GENMASK(11, 0)
+#define TH1520_ADC_DLT_VALUE_CH0_LVAL_OFF (0)
+#define TH1520_ADC_DLT_VALUE_CH1_HVAL GENMASK(27, 16)
+#define TH1520_ADC_DLT_VALUE_CH1_HVAL_OFF (16)
+#define TH1520_ADC_DLT_VALUE_CH1_LVAL GENMASK(11, 0)
+#define TH1520_ADC_DLT_VALUE_CH1_LVAL_OFF (0)
+
+#define TH1520_ADC_FIFO_DATA_SIZE 32
+#define TH1520_ADC_PHY_ENCTR 0x8e0
+#define TH1520_ADC_TIMEOUT 500000
+
+#define TH1520_ADC_CHAN(_idx, _chan_type) { \
+ .type = (_chan_type), \
+ .indexed = 1, \
+ .channel = (_idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+}
+
+enum vol_ref {
+ TH1520_ADC_VOL_VREF,
+ TH1520_ADC_VOL_INTE,
+};
+
+enum input_mode_sel {
+ TH1520_ADC_SINGLE_ENDED_INPUTS,
+ TH1520_ADC_DIFFERENTIAL_INPUTS,
+};
+
+enum selres_sel {
+ TH1520_ADC_SELRES_6BIT = 6,
+ TH1520_ADC_SELRES_8BIT = 8,
+ TH1520_ADC_SELRES_10BIT = 10,
+ TH1520_ADC_SELRES_12BIT = 12,
+};
+
+enum offset_mode_sel {
+ TH1520_ADC_OFFSET_DIS = 0,
+ TH1520_ADC_OFFSET_EN,
+};
+
+enum conversion_mode_sel {
+ TH1520_ADC_MODE_SINGLE,
+ TH1520_ADC_MODE_CONTINOUS,
+};
+
+enum clk_sel {
+ TH1520_ADC_FCLK_TYP_1M,
+};
+
+enum int_actual_mask {
+ TH1520_ADC_ACTUAL_CH0,
+ TH1520_ADC_ACTUAL_CH1,
+ TH1520_ADC_ACTUAL_ALL,
+
+};
+
+enum int_delta_mask {
+ TH1520_ADC_DETAL_CH0,
+ TH1520_ADC_DETAL_CH1,
+ TH1520_ADC_DETAL_ALL,
+};
+
+struct th1520_adc_feature {
+ enum selres_sel selres_sel;
+ enum input_mode_sel input_mode;
+ enum vol_ref vol_ref;
+ enum offset_mode_sel offset_mode;
+ enum conversion_mode_sel conv_mode;
+ enum clk_sel clk_sel;
+ enum int_actual_mask int_actual;
+ enum int_delta_mask int_detal;
+};
+
+struct th1520_adc {
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *clk;
+
+ u32 vref_uv;
+ u32 value;
+ struct regulator *vref;
+ struct th1520_adc_feature adc_feature;
+ u32 current_clk;
+ u32 ch0_offmeas;
+ u32 ch1_offmeas;
+
+ struct completion completion;
+ /* lock to protect against multiple access to the device */
+ struct mutex mlock;
+};
+