[PATCH linux dev-4.13 v1 2/2] ADC: npcm: add NPCM7xx ADC driver

Tomer Maimon tmaimon77 at gmail.com
Wed Dec 27 19:30:26 AEDT 2017


Add Nuvoton BMC NPCM7xx Analog-to-digital
converter (ADC) driver

The NPCM7xx ADC is a 10-bit converter for eight channel
inputs, the ADC module includes an eight-to-one multiplexer.

Signed-off-by: Tomer Maimon <tmaimon77 at gmail.com>
---
 drivers/iio/adc/Kconfig       |   9 ++
 drivers/iio/adc/Makefile      |   1 +
 drivers/iio/adc/npcm7xx-adc.c | 352 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 362 insertions(+)
 create mode 100644 drivers/iio/adc/npcm7xx-adc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 614fa41559b1..77705fde21ca 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -857,4 +857,13 @@ config XILINX_XADC
 	  The driver can also be build as a module. If so, the module will be called
 	  xilinx-xadc.
 
+config NPCM7XX_ADC
+	tristate "NPCM7XX ADC driver"
+	depends on ARCH_NPCM7XX || COMPILE_TEST
+	depends on HAS_IOMEM
+	help
+	  Say yes here to build support for NPCM7XX ADC.
+	  This driver can also be built as a module. If so, the module will be
+	  called npcm750_adc.
+
 endmenu
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index b546736a5541..871074d7b0cd 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -78,3 +78,4 @@ obj-$(CONFIG_VF610_ADC) += vf610_adc.o
 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_NPCM7XX_ADC) += npcm7xx-adc.o
diff --git a/drivers/iio/adc/npcm7xx-adc.c b/drivers/iio/adc/npcm7xx-adc.c
new file mode 100644
index 000000000000..734e350b5b7d
--- /dev/null
+++ b/drivers/iio/adc/npcm7xx-adc.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2014-2017 Nuvoton Technology corporation.
+ *
+ * Released under the GPLv2 only.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/signal.h>
+#include <linux/spinlock.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/driver.h>
+#include <linux/iio/sysfs.h>
+
+#include <linux/clk.h>
+
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+static struct regmap *rst_regmap;
+
+#define  IPSRST1_OFFSET 0x020
+
+struct npcm7xx_adc {
+	struct device *dev;
+	void __iomem *regs;
+	struct clk *adc_clk;
+	u32 vref;
+	u32 adc_clk_rate;
+	u32 ADCReading;
+	u8  ADCChannelNum;
+};
+
+/* ADC registers */
+#define NPCM7XX_ADCCON	 0x00
+#define NPCM7XX_ADCDATA	 0x04
+
+/* ADCCON Register Bits */
+#define NPCM7XX_ADCCON_ADC_INT_EN	BIT(21)
+#define NPCM7XX_ADCCON_REFSEL		BIT(19)
+#define NPCM7XX_ADCCON_ADC_INT		BIT(18)
+#define NPCM7XX_ADCCON_ADC_EN		BIT(17)
+#define NPCM7XX_ADCCON_ADC_RST		BIT(16)
+#define NPCM7XX_ADCCON_ADC_CONV		BIT(13)
+
+#define NPCM7XX_ADCCON_ADCMUX(x)		(((x) & 0x0F)<<24)
+#define NPCM7XX_ADCCON_ADC_DIV(x)		(((x) & 0xFF)<<24)
+#define NPCM7XX_ADCCON_ADC_DATA_MASK(x)		((x) & 0x3FF)
+#define NPCM7XX_ADCCON_MUXMASK			(0x0F<<24)
+
+/* ADC General Defintion */
+#define NPCM7XX_ADC_INPUT_CLK_DIV		0
+#define NPCM7XX_ADC_CONVERT_MAX_RETRY_CNT	1000
+
+#define NPCM7XX_ADC_MAX_CHNL_NUM	8
+
+#define NPCM7XX_ADC_CHNL0_ADCI0		0
+#define NPCM7XX_ADC_CHNL1_ADCI1		1
+#define NPCM7XX_ADC_CHNL2_ADCI2		2
+#define NPCM7XX_ADC_CHNL3_ADCI3		3
+#define NPCM7XX_ADC_CHNL4_ADCI4		4
+#define NPCM7XX_ADC_CHNL5_ADCI5		5
+#define NPCM7XX_ADC_CHNL6_ADCI6		6
+#define NPCM7XX_ADC_CHNL7_ADCI7		7
+
+#define ADC_MAX_CLOCK 12500000
+#define VREF_MVOLT 2048		//vref = 2.000v
+
+#define NPCM7XX_ADC_CHAN(_idx) {			\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = (_idx),				\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),	\
+}
+
+static const struct iio_chan_spec npcm7xx_adc_iio_channels[] = {
+	NPCM7XX_ADC_CHAN(0),
+	NPCM7XX_ADC_CHAN(1),
+	NPCM7XX_ADC_CHAN(2),
+	NPCM7XX_ADC_CHAN(3),
+	NPCM7XX_ADC_CHAN(4),
+	NPCM7XX_ADC_CHAN(5),
+	NPCM7XX_ADC_CHAN(6),
+	NPCM7XX_ADC_CHAN(7),
+};
+
+//#define ADC_DEBUG
+
+#ifdef ADC_DEBUG
+static char *S_ADCChnlString[] = {
+	"ADCI0", "ADCI1", "ADCI2", "ADCI3", "ADCI4", "ADCI5", "ADCI6", "ADCI7"
+};
+#define PDEBUG(fmt, args...) pr_info("aess_adcdrv %s() " fmt, __func__, ##args)
+#else
+#define PDEBUG(fmt, args...)
+#endif
+#define PERROR(fmt, args...) pr_err("aess_adcdrv %s(): " fmt, __func__, ##args)
+
+static int adcsensor_read(struct npcm7xx_adc *info)
+{
+	u8  u8ChannelNum = info->ADCChannelNum;
+	u32 regtemp = 0;
+	int cnt = 0;
+
+	/* Select ADC channal */
+	regtemp = ioread32(info->regs + NPCM7XX_ADCCON);
+	regtemp &= ~NPCM7XX_ADCCON_MUXMASK;
+
+	iowrite32((u32) (regtemp | NPCM7XX_ADCCON_ADCMUX(u8ChannelNum) |
+			 NPCM7XX_ADCCON_ADC_EN | NPCM7XX_ADCCON_REFSEL),
+		  info->regs + NPCM7XX_ADCCON);
+
+	/* Activate convert the ADC input */
+	regtemp = ioread32(info->regs + NPCM7XX_ADCCON);
+	iowrite32((u32) (regtemp | NPCM7XX_ADCCON_ADC_CONV),
+		  info->regs + NPCM7XX_ADCCON);
+
+	/* Wait value */
+	while (((regtemp = ioread32(info->regs + NPCM7XX_ADCCON)) &
+	       NPCM7XX_ADCCON_ADC_CONV) != 0) {
+		if (cnt < NPCM7XX_ADC_CONVERT_MAX_RETRY_CNT)
+			cnt++;
+		else {
+			PERROR("ADC CONVERT FAIL - Timeout\n");
+			PERROR("NPCM7XX_ADCCON=0x%08X, ADC_MUX=%d u8ChannelNum="
+			       "%d!!\n", regtemp, (regtemp>>24)&0xF,
+			       u8ChannelNum);
+			if (((regtemp>>24) & 0xF) != u8ChannelNum)
+				PERROR("ADC_MUX != u8ChannelNum, I suspect that"
+				       " 2 threads are trying to access this "
+				       "read and it is not protected "
+				       "by mutex\n");
+
+			/* if convertion failed - reset ADC module */
+			regmap_write(rst_regmap, IPSRST1_OFFSET, 0x08000000);
+			msleep(100);
+			regmap_write(rst_regmap, IPSRST1_OFFSET, 0x0);
+			msleep(100);
+			PERROR("RESET ADC Complete\n");
+			return (-EAGAIN);
+		}
+	}
+
+/* When an ADC conversion operation finished, a delay must be added before
+ * the next conversion operation.
+ * The delay depend on the ADC clock:
+ * When ADC clock is 0.5 MHz: delay is 4 us.
+ * When ADC clock is 12.5 MHz: delay is 160 ns.
+ *
+ * In the current driver the ADC clock is 12.5MHz, so delay is not needed.
+ * (already the R/W register take more than 160ns)
+ * If the ADC clock will be lower than 12.5MHz please add delay according
+ * the details above
+ * udelay(conv_delay);
+ */
+
+	/* finish to convert */
+	info->ADCReading = NPCM7XX_ADCCON_ADC_DATA_MASK
+		(ioread32(info->regs + NPCM7XX_ADCDATA));
+
+	PDEBUG("[%d_%s] ADCReading=%ld [%ldmV]\n",
+	       u8ChannelNum, S_ADCChnlString[u8ChannelNum],
+	       (long int)info->ADCReading,
+	       (long int)(info->ADCReading * info->vref / 1024));
+
+	return 0;
+}
+
+static int npcm7xx_adc_read_raw(struct iio_dev *indio_dev,
+			struct iio_chan_spec const *chan, int *val, int *val2,
+			long mask)
+{
+	int err_check;
+	struct npcm7xx_adc *info = iio_priv(indio_dev);
+
+	info->ADCChannelNum = chan->channel;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+
+		switch (info->ADCChannelNum) {
+		case NPCM7XX_ADC_CHNL0_ADCI0:
+		case NPCM7XX_ADC_CHNL1_ADCI1:
+		case NPCM7XX_ADC_CHNL2_ADCI2:
+		case NPCM7XX_ADC_CHNL3_ADCI3:
+		case NPCM7XX_ADC_CHNL4_ADCI4:
+		case NPCM7XX_ADC_CHNL5_ADCI5:
+		case NPCM7XX_ADC_CHNL6_ADCI6:
+		case NPCM7XX_ADC_CHNL7_ADCI7:
+			mutex_lock(&indio_dev->mlock);
+			err_check = adcsensor_read(info);
+			PDEBUG("%d = aess_adcsensor_read()\n", err_check);
+			if (err_check) {
+				PERROR("err_check %d\n", err_check);
+				mutex_unlock(&indio_dev->mlock);
+				return err_check;
+			}
+			*val = info->ADCReading;
+			mutex_unlock(&indio_dev->mlock);
+			return IIO_VAL_INT;
+		default:
+			PERROR("aess_adcsensor_ioctl, Unsupport channel number"
+			       " [%d]!\n", info->ADCChannelNum);
+			err_check = -ENODEV;
+			break;
+		}
+		break;
+
+	default:
+		PERROR("aess_adcsensor_ioctl, command error!!!\n");
+		err_check = -EINVAL;
+	}
+
+	/* 0->ok, minus->fail */
+	return err_check;
+}
+
+static const struct iio_info npcm7xx_adc_iio_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &npcm7xx_adc_read_raw,
+};
+
+static const struct of_device_id npcm7xx_adc_match[] = {
+	{ .compatible = "nuvoton,npcm7xx-adc", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, npcm7xx_adc_match);
+
+
+static int npcm7xx_adc_probe(struct platform_device *pdev)
+{
+	struct npcm7xx_adc *info;
+	struct iio_dev *indio_dev;
+	struct resource *mem;
+	struct device *dev = &pdev->dev;
+	int ret;
+	u32 regtemp = 0;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "Failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(indio_dev);
+	info->dev = &pdev->dev;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	info->regs = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(info->regs)) {
+		ret = PTR_ERR(info->regs);
+		dev_err(&pdev->dev, "Failed to remap adc memory, err = %d\n",
+			ret);
+		return ret;
+	}
+
+	ret = of_property_read_u32(dev->of_node, "vref", &info->vref);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed getting reference voltage, Assuming"
+				   " reference voltage 2V(2048)\n");
+		info->vref = VREF_MVOLT;
+		ret = 0;
+	}
+
+	info->adc_clk = devm_clk_get(&pdev->dev, "clk_adc");
+	if (IS_ERR(info->adc_clk)) {
+		dev_err(&pdev->dev, "ADC clock failed: can't read clk. "
+				    "Assuming ADC clock Rate 12.5MHz\n");
+		info->adc_clk_rate = ADC_MAX_CLOCK;
+	} else {
+		/* calculate ADC clock divider */
+		regtemp = ioread32(info->regs + NPCM7XX_ADCCON);
+		regtemp = regtemp >> 1;
+		regtemp &= 0xff;
+
+		info->adc_clk_rate = clk_get_rate(info->adc_clk) /
+			((regtemp+1)*2);
+	}
+
+	rst_regmap = syscon_regmap_lookup_by_compatible("nuvoton,npcm750-rst");
+	if (IS_ERR(rst_regmap)) {
+		pr_err("%s: failed to find nuvoton,npcm750-rst\n", __func__);
+		return IS_ERR(rst_regmap);
+	}
+
+	pr_info("ADC clock Rate %d\n", info->adc_clk_rate);
+
+	/** Enable the ADC Module **/
+	iowrite32((u32) NPCM7XX_ADCCON_ADC_EN, info->regs + NPCM7XX_ADCCON);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->info = &npcm7xx_adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = npcm7xx_adc_iio_channels;
+	indio_dev->num_channels = ARRAY_SIZE(npcm7xx_adc_iio_channels);
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "Couldn't register the device.\n");
+		clk_disable_unprepare(info->adc_clk);
+		return ret;
+	}
+
+	pr_info("NPCM7XX ADC driver probed\n");
+
+	return 0;
+}
+
+static int npcm7xx_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct npcm7xx_adc *info = iio_priv(indio_dev);
+	u32 regtemp = 0;
+
+	regtemp = ioread32(info->regs + NPCM7XX_ADCCON);
+
+	/* Disable the ADC Module */
+	iowrite32((u32) (regtemp & ~NPCM7XX_ADCCON_ADC_EN),
+		  info->regs + NPCM7XX_ADCCON);
+
+	iio_device_unregister(indio_dev);
+
+	pr_info("NPCM7XX ADC driver removed\n");
+
+	return 0;
+}
+
+static struct platform_driver npcm7xx_adc_driver = {
+	.probe		= npcm7xx_adc_probe,
+	.remove		= npcm7xx_adc_remove,
+	.driver		= {
+		.name	= "npcm7xx_adc",
+		.of_match_table = npcm7xx_adc_match,
+	},
+};
+
+module_platform_driver(npcm7xx_adc_driver);
+
+MODULE_DESCRIPTION("NPCM7XX ADC Sensor Driver");
+MODULE_AUTHOR("Tomer Maimon <tomer.maimon at nuvoton.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.1



More information about the openbmc mailing list