All of lore.kernel.org
 help / color / mirror / Atom feed
From: Tomer Maimon <tmaimon77@gmail.com>
To: openbmc@lists.ozlabs.org
Cc: Joel Stanley <joel@jms.id.au>, Tomer Maimon <tmaimon77@gmail.com>
Subject: [PATCH linux dev-4.13 v1 2/2] ADC: npcm: add NPCM7xx ADC  driver
Date: Wed, 27 Dec 2017 10:30:26 +0200	[thread overview]
Message-ID: <1514363426-9079-3-git-send-email-tmaimon77@gmail.com> (raw)
In-Reply-To: <1514363426-9079-1-git-send-email-tmaimon77@gmail.com>

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@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@nuvoton.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.1

      parent reply	other threads:[~2017-12-27  8:30 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-12-27  8:30 [PATCH linux dev-4.13 v1 0/2] ADC: npcm: add NPCM7xx ADC iio driver Tomer Maimon
2017-12-27  8:30 ` [PATCH linux dev-4.13 v1 1/2] dt-binding: iio: document NPCM7xx ADC DT bindings Tomer Maimon
2017-12-27  8:30 ` Tomer Maimon [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1514363426-9079-3-git-send-email-tmaimon77@gmail.com \
    --to=tmaimon77@gmail.com \
    --cc=joel@jms.id.au \
    --cc=openbmc@lists.ozlabs.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.