[PATCH] iio: adc: add support for Intel ADC

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Recent Intel SoCs have an integrated 14-bit, 4 MS/sec ADC. This patch
adds support for that controller.

Signed-off-by: Felipe Balbi <felipe.balbi@xxxxxxxxxxxxxxx>
---
 drivers/iio/adc/Kconfig     |   9 +
 drivers/iio/adc/Makefile    |   1 +
 drivers/iio/adc/intel-adc.c | 482 ++++++++++++++++++++++++++++++++++++
 3 files changed, 492 insertions(+)
 create mode 100644 drivers/iio/adc/intel-adc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 7e3286265a38..e4810a38b25f 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -432,6 +432,15 @@ config INGENIC_ADC
 	  This driver can also be built as a module. If so, the module will be
 	  called ingenic_adc.
 
+config INTEL_ADC
+	tristate "Intel ADC IIO driver"
+	depends on PCI
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  Say yes here to build support for Intel ADC available on
+	  recent SoCs.
+
 config IMX7D_ADC
 	tristate "Freescale IMX7D ADC driver"
 	depends on ARCH_MXC || COMPILE_TEST
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index ef9cc485fb67..f04e1bf89826 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_HX711) += hx711.o
 obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o
 obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
 obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o
+obj-$(CONFIG_INTEL_ADC) += intel-adc.o
 obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
 obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
diff --git a/drivers/iio/adc/intel-adc.c b/drivers/iio/adc/intel-adc.c
new file mode 100644
index 000000000000..381958668563
--- /dev/null
+++ b/drivers/iio/adc/intel-adc.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * intel-adc.c - Intel ADC Driver
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Author: Felipe Balbi <felipe.balbi@xxxxxxxxxxxxxxx>
+ */
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#define PCI_DEVICE_ID_INTEL_EHLLP	0x4bb8
+
+#define ADC_DMA_CTRL			0x0000
+#define ADC_FIFO_STTS			0x0004
+#define ADC_DMA_DEBUG			0x0008
+#define ADC_PWR_STAT			0x000c
+
+#define ADC_CTRL			0x0400
+#define ADC_LOOP_CTRL			0x0404
+#define ADC_LOOP_SEQ			0x0408
+#define ADC_LOOP_DELAY_0		0x040c
+#define ADC_LOOP_DELAY_1		0x0410
+#define ADC_LOOP_DELAY_2		0x0414
+#define ADC_LOOP_DELAY_3		0x0418
+#define ADC_LOOP_DELAY_4		0x041c
+#define ADC_LOOP_DELAY_5		0x0420
+#define ADC_LOOP_DELAY_6		0x0424
+#define ADC_LOOP_DELAY_7		0x0428
+#define ADC_CAL_CTRL			0x042c
+#define ADC_CONV_CTRL			0x0430
+#define ADC_CONV_DELAY			0x0434
+#define ADC_CONFIG1			0x0438
+#define ADC_CONFIG2			0x043c
+#define ADC_FIFO_CTRL			0x0440
+#define ADC_STAT			0x0444
+#define ADC_FIFO_RD_POINTER		0x0448
+#define ADC_RAW_DATA			0x044c
+#define ADC_DATA_THRESHOLD_0		0x0450
+#define ADC_DATA_THRESHOLD_1		0x0454
+#define ADC_DATA_THRESHOLD_2		0x0458
+#define ADC_DATA_THRESHOLD_3		0x045c
+#define ADC_DATA_THRESHOLD_4		0x0460
+#define ADC_DATA_THRESHOLD_5		0x0464
+#define ADC_DATA_THRESHOLD_6		0x0468
+#define ADC_DATA_THRESHOLD_7		0x046c
+#define ADC_THRESHOLD_CONFIG		0x0470
+#define ADC_RIS				0x0474
+#define ADC_IMSC			0x0478
+#define ADC_MIS				0x047c
+#define ADC_LOOP_CFG_0			0x0480
+#define ADC_LOOP_CFG_1			0x0484
+#define ADC_LOOP_CFG_2			0x0488
+#define ADC_LOOP_CFG_3			0x048c
+#define ADC_LOOP_CFG_4			0x0490
+#define ADC_LOOP_CFG_5			0x0494
+#define ADC_LOOP_CFG_6			0x0498
+#define ADC_LOOP_CFG_7			0x049c
+#define ADC_FIFO_DATA			0x0800
+
+#define ADC_BITS			14
+
+/* ADC DMA Ctrl */
+#define ADC_DMA_CTRL_EN			BIT(0)
+#define ADC_DMA_CTRL_BRST_THRSLD	GENMASK(10, 1)
+
+/* ADC FIFO Status */
+#define ADC_FIFO_STTS_COUNT		GENMASK(9, 0)
+
+/* ADC Ctrl */
+#define ADC_CTRL_EN			BIT(0)
+#define ADC_CTRL_DATA_THRSHLD_MODE(r)	(((r) >> 1) & 3)
+
+/* ADC Conversion Ctrl */
+#define ADC_CONV_CTRL_NUM_SMPL_MASK	GENMASK(17, 8)
+#define ADC_CONV_CTRL_NUM_SMPL(n)	(((n) - 1) << 8)
+#define ADC_CONV_CTRL_CONV_MODE		BIT(4)
+#define ADC_CONV_CTRL_REQ		BIT(0)
+
+/* ADC Config1 */
+#define ADC_CONFIG1_ATTEN_TRIM		GENMASK(31, 30)
+#define ADC_CONFIG1_INBUF_CUR		GENMASK(29, 28)
+#define ADC_CONFIG1_BG_BYPASS		BIT(24)
+#define ADC_CONFIG1_BG_TRIM		GENMASK(23, 19)
+#define ADC_CONFIG1_BG_CTRIM		GENMASK(18, 16)
+#define ADC_CONFIG1_REF_TRIM		GENMASK(15, 8)
+#define ADC_CONFIG1_ADC_RESET		BIT(6)
+#define ADC_CONFIG1_REF_BYPASS_EN	BIT(5)
+#define ADC_CONFIG1_REF_EN		BIT(4)
+#define ADC_CONFIG1_CNL_SEL_MASK	GENMASK(3, 1)
+#define ADC_CONFIG1_CNL_SEL(ch)		((ch) << 1)
+#define ADC_CONFIG1_DIFF_SE_SEL		BIT(0)
+
+/* ADC Interrupt Mask Register */
+#define ADC_INTR_LOOP_DONE_INTR		BIT(22)
+#define ADC_INTR_FIFO_EMPTY_INTR	BIT(21)
+#define ADC_INTR_DMA_DONE_INTR		BIT(20)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_7 BIT(19)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_7 BIT(18)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_6 BIT(17)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_6 BIT(16)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_5 BIT(15)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_5 BIT(14)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_4 BIT(13)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_4 BIT(12)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_3 BIT(11)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_3 BIT(10)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_2 BIT(9)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_2 BIT(8)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_1 BIT(7)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_1 BIT(6)
+#define ADC_INTR_DATA_THRSHLD_LOW_INTR_0 BIT(5)
+#define ADC_INTR_DATA_THRSHLD_HIGH_INTR_0 BIT(4)
+#define ADC_INTR_PWR_DWN_EXIT_INTR	BIT(3)
+#define ADC_INTR_FIFO_FULL_INTR		BIT(2)
+#define ADC_INTR_SMPL_DONE_INTR		BIT(0)
+
+#define ADC_INTR_ALL_MASK	(ADC_INTR_LOOP_DONE_INTR |		\
+				ADC_INTR_FIFO_EMPTY_INTR |		\
+				ADC_INTR_DMA_DONE_INTR |		\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_7 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_7 |	\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_6 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_6 |	\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_5 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_5 |	\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_4 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_4 |	\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_3 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_3 |	\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_2 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_2 |	\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_1 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_1 |	\
+				ADC_INTR_DATA_THRSHLD_LOW_INTR_0 |	\
+				ADC_INTR_DATA_THRSHLD_HIGH_INTR_0 |	\
+				ADC_INTR_PWR_DWN_EXIT_INTR |		\
+				ADC_INTR_FIFO_FULL_INTR |		\
+				ADC_INTR_SMPL_DONE_INTR)
+
+#define ADC_VREF_UV		1600000 /* uV */
+#define ADC_DEFAULT_CONVERSION_TIMEOUT 5000 /* ms */
+
+struct intel_adc {
+	struct completion completion;
+	struct pci_dev *pci;
+	struct iio_dev *iio;
+
+	void __iomem *regs;
+
+	u32 value;
+};
+
+static inline void intel_adc_writel(void __iomem *base, u32 offset, u32 value)
+{
+	writel(value, base + offset);
+}
+
+static inline u32 intel_adc_readl(void __iomem *base, u32 offset)
+{
+	return readl(base + offset);
+}
+
+static void intel_adc_enable(struct intel_adc *adc)
+{
+	u32 ctrl;
+	u32 cfg1;
+
+	cfg1 = intel_adc_readl(adc->regs, ADC_CONFIG1);
+	cfg1 &= ~ADC_CONFIG1_ADC_RESET;
+	intel_adc_writel(adc->regs, ADC_CONFIG1, cfg1);
+
+	ctrl = intel_adc_readl(adc->regs, ADC_CTRL);
+	ctrl |= ADC_CTRL_EN;
+	intel_adc_writel(adc->regs, ADC_CTRL, ctrl);
+
+	cfg1 |= ADC_CONFIG1_REF_EN;
+	intel_adc_writel(adc->regs, ADC_CONFIG1, cfg1);
+
+	/* must wait 1ms before allowing any further accesses */
+	usleep_range(1000, 1500);
+}
+
+static void intel_adc_disable(struct intel_adc *adc)
+{
+	u32 ctrl;
+
+	ctrl = intel_adc_readl(adc->regs, ADC_CTRL);
+	ctrl &= ~ADC_CTRL_EN;
+	intel_adc_writel(adc->regs, ADC_CTRL, ctrl);
+}
+
+static int intel_adc_single_channel_conversion(struct intel_adc *adc,
+		struct iio_chan_spec const *channel, int *val)
+{
+	u32 ctrl;
+	u32 reg;
+
+	ctrl = intel_adc_readl(adc->regs, ADC_CONV_CTRL);
+	ctrl |= ADC_CONV_CTRL_CONV_MODE;
+	ctrl &= ~ADC_CONV_CTRL_NUM_SMPL_MASK;
+	ctrl |= ADC_CONV_CTRL_NUM_SMPL(1);
+	intel_adc_writel(adc->regs, ADC_CONV_CTRL, ctrl);
+
+	reg = intel_adc_readl(adc->regs, ADC_CONFIG1);
+	reg &= ~ADC_CONFIG1_CNL_SEL_MASK;
+	reg |= ADC_CONFIG1_CNL_SEL(channel->scan_index);
+
+	if (channel->differential)
+		reg &= ~ADC_CONFIG1_DIFF_SE_SEL;
+	else
+		reg |= ADC_CONFIG1_DIFF_SE_SEL;
+
+	intel_adc_writel(adc->regs, ADC_CONFIG1, reg);
+
+	ctrl |= ADC_CONV_CTRL_REQ;
+	intel_adc_writel(adc->regs, ADC_CONV_CTRL, ctrl);
+
+	/* enable sample done IRQ event */
+	reg = intel_adc_readl(adc->regs, ADC_IMSC);
+	reg &= ~ADC_INTR_SMPL_DONE_INTR;
+	intel_adc_writel(adc->regs, ADC_IMSC, reg);
+
+	usleep_range(1000, 5000);
+	adc->value = intel_adc_readl(adc->regs, ADC_FIFO_DATA);
+
+	return 0;
+}
+
+static int intel_adc_read_raw(struct iio_dev *iio,
+		struct iio_chan_spec const *channel, int *val, int *val2,
+		long mask)
+{
+	struct intel_adc *adc = iio_priv(iio);
+	int shift;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		shift = channel->scan_type.shift;
+
+		ret = iio_device_claim_direct_mode(iio);
+		if (ret)
+			break;
+
+		intel_adc_enable(adc);
+
+		ret = intel_adc_single_channel_conversion(adc, channel, val);
+		if (ret) {
+			intel_adc_disable(adc);
+			iio_device_release_direct_mode(iio);
+			break;
+		}
+		intel_adc_disable(adc);
+		ret = IIO_VAL_INT;
+		iio_device_release_direct_mode(iio);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct iio_info intel_adc_info = {
+	.read_raw = intel_adc_read_raw,
+};
+
+static const struct iio_event_spec intel_adc_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+			BIT(IIO_EV_INFO_ENABLE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	}, {
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+			BIT(IIO_EV_INFO_PERIOD),
+	},
+};
+
+#define INTEL_ADC_DIFF_CHAN(c1, c2)			\
+{							\
+	.type = IIO_VOLTAGE,				\
+	.differential = true,				\
+	.indexed = 1,					\
+	.channel = (c1),				\
+	.channel2 = (c2),				\
+	.scan_index = (c1),				\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),	\
+	.scan_type = {					\
+		.sign = 's',				\
+		.realbits = 14,				\
+		.storagebits = 32,			\
+		.endianness = IIO_CPU,			\
+	},						\
+	.event_spec = intel_adc_events,			\
+	.num_event_specs = ARRAY_SIZE(intel_adc_events),\
+	.datasheet_name = "ain"#c1"-ain"#c2,		\
+}
+
+#define INTEL_ADC_SINGLE_CHAN(c)			\
+{							\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = (c),					\
+	.scan_index = (c),				\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),	\
+	.scan_type = {					\
+		.sign = 's',				\
+		.realbits = 14,				\
+		.storagebits = 32,			\
+		.shift = 0,				\
+		.endianness = IIO_CPU,			\
+	},						\
+	.event_spec = intel_adc_events,			\
+	.num_event_specs = ARRAY_SIZE(intel_adc_events),\
+	.datasheet_name = "ain"#c,			\
+}
+
+static struct iio_chan_spec const intel_adc_channels[] = {
+	INTEL_ADC_DIFF_CHAN(0, 1),
+	INTEL_ADC_DIFF_CHAN(2, 3),
+	INTEL_ADC_DIFF_CHAN(4, 5),
+	INTEL_ADC_DIFF_CHAN(6, 7),
+	INTEL_ADC_SINGLE_CHAN(0),
+	INTEL_ADC_SINGLE_CHAN(1),
+	INTEL_ADC_SINGLE_CHAN(2),
+	INTEL_ADC_SINGLE_CHAN(3),
+	INTEL_ADC_SINGLE_CHAN(4),
+	INTEL_ADC_SINGLE_CHAN(5),
+	INTEL_ADC_SINGLE_CHAN(6),
+	INTEL_ADC_SINGLE_CHAN(7),
+};
+
+static irqreturn_t intel_adc_irq(int irq, void *_adc)
+{
+	struct intel_adc *adc = _adc;
+	u32 status;
+
+	status = intel_adc_readl(adc->regs, ADC_MIS);
+
+	if (!status)
+		return IRQ_NONE;
+
+	intel_adc_writel(adc->regs, ADC_IMSC, ADC_INTR_ALL_MASK);
+	adc->value = intel_adc_readl(adc->regs, ADC_FIFO_DATA);
+	complete(&adc->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int intel_adc_probe(struct pci_dev *pci, const struct pci_device_id *id)
+{
+	struct intel_adc *adc;
+	struct iio_dev *iio;
+	int ret;
+	int irq;
+
+	iio = devm_iio_device_alloc(&pci->dev, sizeof(*adc));
+	if (!iio)
+		return -ENOMEM;
+
+	adc = iio_priv(iio);
+	adc->pci = pci;
+	adc->iio = iio;
+
+	ret = pcim_enable_device(pci);
+	if (ret)
+		return ret;
+
+	pci_set_master(pci);
+
+	ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci));
+	if (ret)
+		return ret;
+
+	adc->regs = pcim_iomap_table(pci)[0];
+	if (!adc->regs) {
+		ret = -EFAULT;
+		return ret;
+	}
+
+	pci_set_drvdata(pci, adc);
+	init_completion(&adc->completion);
+	iio->dev.parent = &pci->dev;
+	iio->name = dev_name(&pci->dev);
+	iio->modes = INDIO_DIRECT_MODE;
+	iio->info = &intel_adc_info;
+	iio->channels = intel_adc_channels;
+	iio->num_channels = ARRAY_SIZE(intel_adc_channels);
+
+	ret = devm_iio_device_register(&pci->dev, iio);
+	if (ret)
+		return ret;
+
+	ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_ALL_TYPES);
+	if (ret < 0)
+		return ret;
+
+	irq = pci_irq_vector(pci, 0);
+	ret = devm_request_irq(&pci->dev, irq, intel_adc_irq,
+			IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING,
+			"intel-adc", adc);
+	if (ret)
+		goto err;
+
+	pm_runtime_set_autosuspend_delay(&pci->dev, 1000);
+	pm_runtime_use_autosuspend(&pci->dev);
+	pm_runtime_put_autosuspend(&pci->dev);
+	pm_runtime_allow(&pci->dev);
+
+	return 0;
+
+err:
+	pci_free_irq_vectors(pci);
+	return ret;
+}
+
+static void intel_adc_remove(struct pci_dev *pci)
+{
+	pm_runtime_forbid(&pci->dev);
+	pm_runtime_get_noresume(&pci->dev);
+
+	pci_free_irq_vectors(pci);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int intel_adc_suspend(struct device *dev)
+{
+	return 0;
+}
+
+static int intel_adc_resume(struct device *dev)
+{
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(intel_adc_pm_ops, intel_adc_suspend, intel_adc_resume);
+
+static const struct pci_device_id intel_adc_id_table[] = {
+	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_EHLLP), },
+	{  } /* Terminating Entry */
+};
+MODULE_DEVICE_TABLE(pci, intel_adc_id_table);
+
+static struct pci_driver intel_adc_driver = {
+	.name		= "intel-adc",
+	.probe		= intel_adc_probe,
+	.remove		= intel_adc_remove,
+	.id_table	= intel_adc_id_table,
+	.driver = {
+	.pm = &intel_adc_pm_ops,
+	}
+};
+module_pci_driver(intel_adc_driver);
+
+MODULE_AUTHOR("Felipe Balbi <felipe.balbi@xxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Intel ADC");
+MODULE_LICENSE("GPL v2");
-- 
2.23.0




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux