[PATCH] 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           |   12 ++
 drivers/iio/adc/Makefile          |    1 +
 drivers/iio/adc/alt_modular_adc.c |  307 +++++++++++++++++++++++++++++++++++++
 3 files changed, 320 insertions(+), 0 deletions(-)
 create mode 100644 drivers/iio/adc/alt_modular_adc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index eb0cd89..b0b1cff 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -117,6 +117,18 @@ config AD799X
 	  i2c analog to digital converters (ADC). Provides direct access
 	  via sysfs.
 
+config ALT_MODULAR_ADC
+	tristate "Altera Modular ADC driver"
+	depends on NIOS2
+	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 100644
index 0000000..a5649d7
--- /dev/null
+++ b/drivers/iio/adc/alt_modular_adc.c
@@ -0,0 +1,307 @@
+/*
+ * 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 MODE_MAX_SLOT		64
+#define MODE_MAX_ADC		2
+#define MODE_MAX_CHANNEL	18
+#define MODE_SINGLE_ADC		1
+#define MODE_DUAL_ADC		2
+#define MODE_ADC_BITS		12
+#define MODE_ADC_STORE_BITS	16
+
+/* 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[MODE_MAX_ADC][MODE_MAX_SLOT];
+	unsigned int		adc_number;
+
+	u32			value;
+	u32			address;
+};
+
+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;
+
+	if (mask != IIO_CHAN_INFO_RAW)
+		return -EINVAL;
+
+	adc->value = readl(adc->sample_regs + (chan->address * 4));
+
+	if (adc->mode == MODE_SINGLE_ADC) {
+		*val = (adc->value & ADC_LOW_MSK);
+		ret = IIO_VAL_INT;
+	} else if (adc->mode == MODE_DUAL_ADC) {
+		*val = (adc->value & ADC_LOW_MSK);
+		*val2 = ((adc->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 str[20];
+	int i;
+
+	chan_array = kcalloc(adc->slot_count,
+		sizeof(struct iio_chan_spec), GFP_KERNEL);
+
+	if (chan_array == 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) {
+			sprintf(str, "adc%d-ch%d", adc->adc_number,
+				adc->slot_sequence[0][i]);
+		} else if (adc->mode == MODE_DUAL_ADC) {
+			sprintf(str, "adc1-ch%d_adc2-ch%d",
+				adc->slot_sequence[0][i],
+				adc->slot_sequence[1][i]);
+		} else {
+			return -EINVAL;
+		}
+
+		chan->datasheet_name = str;
+		chan->extend_name = str,
+		chan->scan_index = i;
+		chan->scan_type.sign = 'u';
+		chan->scan_type.realbits = MODE_ADC_BITS;
+		chan->scan_type.storagebits = MODE_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 > MODE_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 > MODE_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 > MODE_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';
+
+			sprintf(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 > MODE_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 = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *indio_dev = NULL;
+	struct resource *mem;
+	int ret = -ENODEV;
+
+	if (!np)
+		return ret;
+
+	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(pdev, IORESOURCE_MEM, 0);
+	adc->seq_regs = devm_ioremap_resource(&pdev->dev, mem);
+	if (!adc->seq_regs) {
+		ret = -ENOMEM;
+		goto err_iio;
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	adc->sample_regs = devm_ioremap_resource(&pdev->dev, mem);
+	if (!adc->sample_regs) {
+		ret = -ENOMEM;
+		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(0, (adc->sample_regs + ADC_IER_REG));
+
+	/* Start Continuous Sampling */
+	writel((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