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

wefu at redhat.com wefu at redhat.com
Fri Mar 29 13:01:24 PDT 2024


From: Wei Fu <wefu at redhat.com>

Signed-off-by: Wei Fu <wefu at 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 at 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 at 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 at 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;
+};
+
-- 
2.44.0




More information about the linux-riscv mailing list