[[PATCH v2] 2/2] Altera Modular ADC driver support

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

 



From: Chee Nouk Phoon <cnphoon@xxxxxxxxxx>

Altera Modular ADC is soft IP that wraps the hardened ADC block in a Max10
device. It can be configured to dual ADC mode that supports two channel
synchronous sampling or independent single ADCs. ADC sampled values will be
written into memory slots in sequence determined by a user configurable
sequencer block.

This patch adds Altera Modular ADC driver support

Signed-off-by: Chee Nouk Phoon <cnphoon@xxxxxxxxxx>
---
 drivers/iio/adc/Kconfig           |   11 ++
 drivers/iio/adc/Makefile          |    1 +
 drivers/iio/adc/alt_modular_adc.c |  322 +++++++++++++++++++++++++++++++++++++
 3 files changed, 334 insertions(+), 0 deletions(-)
 mode change 100644 => 100755 drivers/iio/adc/Kconfig
 create mode 100755 drivers/iio/adc/alt_modular_adc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
old mode 100644
new mode 100755
index 50c103d..e1a67cb
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -118,6 +118,17 @@ config AD799X
 	  To compile this driver as a module, choose M here: the module will be
 	  called ad799x.
 
+config ALT_MODULAR_ADC
+	tristate "Altera Modular ADC driver"
+	select ANON_INODES
+
+	help
+	  Say yes here to have support for Altera Modular ADC. The driver
+	  supports both Altera Modular ADC and Altera Modular Dual ADC.
+
+	  The driver can also be build as a module. If so the module will be
+	  called alt_modular_adc.
+
 config AT91_ADC
 	tristate "Atmel AT91 ADC"
 	depends on ARCH_AT91
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index a096210..d7f10e0 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -38,3 +38,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_ALT_MODULAR_ADC) += alt_modular_adc.o
diff --git a/drivers/iio/adc/alt_modular_adc.c b/drivers/iio/adc/alt_modular_adc.c
new file mode 100755
index 0000000..3d2d870
--- /dev/null
+++ b/drivers/iio/adc/alt_modular_adc.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2015 Altera Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/iio/iio.h>
+
+/* Constant Definitions */
+#define MAX_SLOT		64
+#define MAX_ADC			2
+#define MAX_CHANNEL		18
+#define MODE_SINGLE_ADC		1
+#define MODE_DUAL_ADC		2
+#define ADC_BITS		12
+#define ADC_STORE_BITS		16
+#define ADC_MAX_STR_SIZE        20
+
+/* Register Definitions */
+#define ADC_CMD_REG		0x0
+#define ADC_IER_REG		0x100
+#define ADC_ISR_REG		0x104
+
+#define ADC_RUN_MSK		0x1
+#define ADC_SINGLE_MKS		0x2
+#define ADC_STOP_MSK		0x0
+#define ADC_LOW_MSK		0xFFF
+#define ADC_HIGH_MSK		0xFFF0000
+
+struct altera_adc {
+	void __iomem		*seq_regs;
+	void __iomem		*sample_regs;
+
+	unsigned int		mode;
+	unsigned int		slot_count;
+	unsigned int		slot_sequence[MAX_ADC][MAX_SLOT];
+	unsigned int		adc_number;
+};
+
+static int alt_modular_adc_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct altera_adc *adc = iio_priv(indio_dev);
+	int ret = -EINVAL;
+	u32 value;
+
+	if (mask != IIO_CHAN_INFO_RAW)
+		return -EINVAL;
+
+	value = readl_relaxed(adc->sample_regs + (chan->address * 4));
+
+	if (adc->mode == MODE_SINGLE_ADC) {
+		*val = (value & ADC_LOW_MSK);
+		ret = IIO_VAL_INT;
+	} else if (adc->mode == MODE_DUAL_ADC) {
+		*val = (value & ADC_LOW_MSK);
+		*val2 = ((value & ADC_HIGH_MSK) >> 16);
+		ret = IIO_VAL_INT_MULTIPLE;
+	}
+
+	return ret;
+}
+
+static const struct iio_info adc_iio_info = {
+	.read_raw = &alt_modular_adc_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+static int alt_modular_adc_channel_init(struct iio_dev *indio_dev)
+{
+	struct altera_adc *adc = iio_priv(indio_dev);
+	struct iio_chan_spec *chan_array;
+	struct iio_chan_spec *chan;
+	char **channel_name;
+	int i;
+
+	chan_array = devm_kzalloc(&indio_dev->dev, (adc->slot_count *
+		sizeof(*chan_array)), GFP_KERNEL);
+	if (chan_array == NULL)
+		return -ENOMEM;
+
+	channel_name = devm_kzalloc(&indio_dev->dev, adc->slot_count,
+		GFP_KERNEL);
+	if (channel_name == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < adc->slot_count; i++) {
+		channel_name[i] = devm_kzalloc(&indio_dev->dev,
+				ADC_MAX_STR_SIZE, GFP_KERNEL);
+		if (channel_name == NULL)
+			return -ENOMEM;
+	}
+
+	chan = chan_array;
+	for (i = 0; i < adc->slot_count; i++, chan++) {
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = i;
+		chan->address = i;
+
+		/* Construct iio sysfs name*/
+		if (adc->mode == MODE_SINGLE_ADC) {
+			snprintf(channel_name[i], ADC_MAX_STR_SIZE,
+				"adc%d-ch%d",
+				adc->adc_number,
+				adc->slot_sequence[0][i]);
+		} else if (adc->mode == MODE_DUAL_ADC) {
+			snprintf(channel_name[i], ADC_MAX_STR_SIZE,
+				"adc1-ch%d_adc2-ch%d",
+				adc->slot_sequence[0][i],
+				adc->slot_sequence[1][i]);
+		} else {
+			return -EINVAL;
+		}
+
+		chan->datasheet_name = channel_name[i];
+		chan->extend_name = channel_name[i];
+		chan->scan_index = i;
+		chan->scan_type.sign = 'u';
+		chan->scan_type.realbits = ADC_BITS;
+		chan->scan_type.storagebits = ADC_STORE_BITS;
+		chan->scan_type.endianness = IIO_CPU;
+		chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+	}
+
+	indio_dev->channels = chan_array;
+
+	return 0;
+}
+
+static const struct of_device_id alt_modular_adc_match[] = {
+	{ .compatible = "altr,modular-adc-1.0" },
+	{ .compatible = "altr,modular-dual-adc-1.0" },
+	{ /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, alt_modular_adc_match);
+
+static int alt_modular_adc_parse_dt(struct iio_dev *indio_dev,
+				struct device *dev)
+{
+	struct altera_adc *adc = iio_priv(indio_dev);
+	struct device_node *np = dev->of_node;
+	u32 value;
+	int ret, i, j;
+	char str[50];
+
+	ret = of_property_read_u32(np, "altr,adc-mode", &value);
+	if (ret < 0)
+		return ret;
+	if (value == 0 || value > MAX_ADC) {
+		dev_err(dev, "Invalid ADC mode value");
+		return -EINVAL;
+	}
+	adc->mode = value;
+
+	ret = of_property_read_u32(np, "altr,adc-slot-count", &value);
+	if (ret < 0)
+		return ret;
+	if (value == 0 || value > MAX_SLOT) {
+		dev_err(dev, "Invalid ADC slot count value");
+		return -EINVAL;
+	}
+	adc->slot_count = value;
+
+	ret = of_property_read_u32(np, "altr,adc-number", &value);
+	if (ret < 0)
+		return ret;
+	if (value == 0 || value > MAX_ADC) {
+		dev_err(dev, "Invalid ADC number value");
+		return -EINVAL;
+	}
+	adc->adc_number = value;
+
+	/* Device tree lookup for channels for each memory slots */
+	for (j = 0; j < adc->mode; j++) {
+		for (i = 0; i < adc->slot_count; i++) {
+			str[0] = '\0';
+
+			snprintf(str, sizeof(str),
+				"altr,adc%d-slot-sequence-%d",
+				(j + 1), (i + 1));
+
+			ret = of_property_read_u32(np, str, &value);
+			if (ret < 0)
+				return ret;
+			if (value > MAX_CHANNEL) {
+				dev_err(dev, "Invalid ADC channel value");
+				return -EINVAL;
+			}
+			adc->slot_sequence[j][i] = value;
+		}
+	}
+
+	return 0;
+}
+
+static int alt_modular_adc_probe(struct platform_device *pdev)
+{
+	struct altera_adc *adc;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *indio_dev;
+	struct resource *mem;
+	int ret;
+
+	if (!np)
+		return -ENODEV;
+
+	indio_dev = iio_device_alloc(sizeof(struct altera_adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	adc = iio_priv(indio_dev);
+
+	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+		"sequencer_csr");
+	adc->seq_regs = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(adc->seq_regs)) {
+		ret = PTR_ERR(adc->seq_regs);
+		goto err_iio;
+	}
+
+	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+		"sample_store_csr");
+	adc->sample_regs = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(adc->sample_regs)) {
+		ret = PTR_ERR(adc->sample_regs);
+		goto err_iio;
+	}
+
+	ret = alt_modular_adc_parse_dt(indio_dev, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to parse device tree\n");
+		goto err_iio;
+	}
+
+	ret = alt_modular_adc_channel_init(indio_dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed initialize ADC channels\n");
+		goto err_iio;
+	}
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	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 = &adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->num_channels = adc->slot_count;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_iio;
+
+	/* Disable Interrupt */
+	writel_relaxed(0, (adc->sample_regs + ADC_IER_REG));
+
+	/* Start Continuous Sampling */
+	writel_relaxed((ADC_RUN_MSK), (adc->seq_regs + ADC_CMD_REG));
+
+	return 0;
+
+
+err_iio:
+	iio_device_free(indio_dev);
+	return ret;
+}
+
+static int alt_modular_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct altera_adc *adc = iio_priv(indio_dev);
+
+	/* Stop ADC */
+	writel((ADC_STOP_MSK), (adc->seq_regs + ADC_CMD_REG));
+
+	/* Unregister ADC */
+	iio_device_unregister(indio_dev);
+
+	return 0;
+}
+
+static struct platform_driver altr_modular_adc_driver = {
+	.probe		= alt_modular_adc_probe,
+	.remove		= alt_modular_adc_remove,
+	.driver		= {
+		.name	= "alt-modular-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = alt_modular_adc_match,
+	},
+};
+
+
+module_platform_driver(altr_modular_adc_driver);
+
+MODULE_DESCRIPTION("Altera Modular ADC Driver");
+MODULE_AUTHOR("Chee Nouk Phoon <cnphoon@xxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[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