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

From: Chee Nouk Phoo
Date: Thu Sep 03 2015 - 05:58:51 EST


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-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/