* [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-26 10:56 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-26 10:56 UTC (permalink / raw)
To: jic23-DgEjT+Ai2ygdnm+yROfE0A, sachin.kamat-QSEj5FYQhm4dnm+yROfE0A
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A, b20596-KZfg59tc24xl57MIdRCFDg,
linux-iio-u79uwXL29TY76Z2rM5mHXA
Add Freescale Vybrid vf610 adc driver. The driver only support
ADC software trigger.
Signed-off-by: Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
---
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/vf610_adc.c | 744 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 756 insertions(+), 0 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 2209f28..d0476ec 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -204,4 +204,15 @@ config VIPERBOARD_ADC
Say yes here to access the ADC part of the Nano River
Technologies Viperboard.
+config VF610_ADC
+ tristate "Freescale vf610 ADC driver"
+ help
+ Say yes here if you want support for the Vybrid board
+ analog-to-digital converter.
+ Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the driver
+ also support the subsequent chips.
+
+ This driver can also be built as a module. If so, the module will be
+ called vf610_adc.
+
endmenu
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index ba9a10a..c39ae38 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
+obj-$(CONFIG_VF610_ADC) += vf610_adc.o
diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
new file mode 100644
index 0000000..0c200d91
--- /dev/null
+++ b/drivers/iio/adc/vf610_adc.c
@@ -0,0 +1,744 @@
+/*
+ * Freescale Vybrid vf610 ADC driver
+ *
+ * Copyright 2013 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/err.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+/* This will be the driver name the kernel reports */
+#define DRIVER_NAME "vf610-adc"
+
+/* Vybrid/IMX ADC registers */
+#define ADC_HC0 0x00
+#define ADC_HC1 0x04
+#define ADC_HS 0x08
+#define ADC_R0 0x0c
+#define ADC_R1 0x10
+#define ADC_CFG 0x14
+#define ADC_GC 0x18
+#define ADC_GS 0x1c
+#define ADC_CV 0x20
+#define ADC_OFS 0x24
+#define ADC_CAL 0x28
+#define ADC_PCTL 0x30
+
+/* Configuration register field define */
+#define VF610_ADC_MODE_BIT8 0x00
+#define VF610_ADC_MODE_BIT10 0x04
+#define VF610_ADC_MODE_BIT12 0x08
+#define VF610_ADC_DATA_BIT8_MASK 0xFF
+#define VF610_ADC_DATA_BIT10_MASK 0x3FF
+#define VF610_ADC_DATA_BIT12_MASK 0xFFF
+#define VF610_ADC_BUSCLK2_SEL 0x01
+#define VF610_ADC_ALTCLK_SEL 0x02
+#define VF610_ADC_ADACK_SEL 0x03
+#define VF610_ADC_CLK_DIV2 0x20
+#define VF610_ADC_CLK_DIV4 0x40
+#define VF610_ADC_CLK_DIV8 0x60
+#define VF610_ADC_ADLSMP_LONG 0x10
+#define VF610_ADC_ADSTS_SHORT 0x100
+#define VF610_ADC_ADSTS_NORMAL 0x200
+#define VF610_ADC_ADSTS_LONG 0x300
+#define VF610_ADC_ADLPC_EN 0x80
+#define VF610_ADC_ADHSC_EN 0x400
+#define VF610_ADC_REFSEL_VALT 0x100
+#define VF610_ADC_REFSEL_VBG 0x1000
+#define VF610_ADC_ADTRG_HARD 0x2000
+#define VF610_ADC_AVGS_8 0x4000
+#define VF610_ADC_AVGS_16 0x8000
+#define VF610_ADC_AVGS_32 0xC000
+#define VF610_ADC_OVWREN 0x10000
+
+/* General control register field define */
+#define VF610_ADC_ADACKEN 0x1
+#define VF610_ADC_DMAEN 0x2
+#define VF610_ADC_ACREN 0x4
+#define VF610_ADC_ACFGT 0x8
+#define VF610_ADC_ACFE 0x10
+#define VF610_ADC_AVGEN 0x20
+#define VF610_ADC_ADCON 0x40
+#define VF610_ADC_CAL 0x80
+
+/* Other field define */
+#define VF610_ADC_IOPCTL5 0x20
+#define VF610_ADC_ADCHC(x) ((x) & 0xF)
+#define VF610_ADC_AIEN (1 << 7)
+#define VF610_ADC_CONV_DISABLE 0x1F
+#define VF610_ADC_HS_COCO0 0x1
+#define VF610_ADC_CALF 0x2
+#define VF610_ADC_MAX_CHANS_NUM 16
+#define VF610_ADC_TIMEOUT (msecs_to_jiffies(100))
+
+enum clk_sel {
+ ADCIOC_BUSCLK_SET,
+ ADCIOC_ALTCLK_SET,
+ ADCIOC_ADACK_SET,
+};
+
+enum vol_ref {
+ VF610_ADCIOC_VR_VREF_SET,
+ VF610_ADCIOC_VR_VALT_SET,
+ VF610_ADCIOC_VR_VBG_SET,
+};
+
+struct vf610_adc_feature {
+ enum clk_sel clk_sel;
+ enum vol_ref vol_ref;
+
+ int pctl;
+ int clk_div_num;
+ int res_mode;
+ int sam_time;
+ int hw_sam;
+
+ bool calibration;
+ bool cc_en;
+ bool dataov_en;
+ bool hw_average;
+ bool tri_hw;
+ bool hs_oper;
+ bool lpm;
+ bool dma_en;
+ bool ac_clk_en;
+ bool cmp_func_en;
+ bool cmp_range_en;
+ bool cmp_greater_en;
+};
+
+struct vf610_adc {
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *clk;
+ unsigned int irq;
+
+ u32 vref_uv;
+ u32 value;
+ struct regulator *vref;
+ struct vf610_adc_feature adc_feature;
+
+ struct completion completion;
+};
+
+#define VF610_ADC_CHAN(_idx, _chan_type) { \
+ .type = (_chan_type), \
+ .indexed = 1, \
+ .channel = _idx, \
+ .address = _idx, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec vf610_adc_iio_channels[] = {
+ VF610_ADC_CHAN(0, IIO_VOLTAGE),
+ VF610_ADC_CHAN(1, IIO_VOLTAGE),
+ VF610_ADC_CHAN(2, IIO_VOLTAGE),
+ VF610_ADC_CHAN(3, IIO_VOLTAGE),
+ VF610_ADC_CHAN(4, IIO_VOLTAGE),
+ VF610_ADC_CHAN(5, IIO_VOLTAGE),
+ VF610_ADC_CHAN(6, IIO_VOLTAGE),
+ VF610_ADC_CHAN(7, IIO_VOLTAGE),
+ VF610_ADC_CHAN(8, IIO_VOLTAGE),
+ VF610_ADC_CHAN(9, IIO_VOLTAGE),
+ VF610_ADC_CHAN(10, IIO_VOLTAGE),
+ VF610_ADC_CHAN(11, IIO_VOLTAGE),
+ VF610_ADC_CHAN(12, IIO_VOLTAGE),
+ VF610_ADC_CHAN(13, IIO_VOLTAGE),
+ VF610_ADC_CHAN(14, IIO_VOLTAGE),
+ VF610_ADC_CHAN(15, IIO_VOLTAGE),
+};
+
+static void vf610_adc_cfg_of_init(struct vf610_adc *info)
+{
+ struct device_node *np = info->dev->of_node;
+
+ /*
+ * set default Configuration for ADC controller
+ * This config may upgrade to require from DT
+ */
+ info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
+ info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
+ info->adc_feature.calibration = true;
+ info->adc_feature.tri_hw = false;
+ info->adc_feature.dataov_en = true;
+ info->adc_feature.ac_clk_en = false;
+ info->adc_feature.dma_en = false;
+ info->adc_feature.cc_en = false;
+ info->adc_feature.cmp_func_en = false;
+ info->adc_feature.cmp_range_en = false;
+ info->adc_feature.cmp_greater_en = false;
+
+ if (of_property_read_u32(np, "fsl,adc-io-pinctl",
+ &info->adc_feature.pctl)) {
+ info->adc_feature.pctl = VF610_ADC_IOPCTL5;
+ dev_info(info->dev,
+ "Miss adc-io-pinctl in dt, enable pin SE5.\n");
+ }
+
+ if (info->vref)
+ info->vref_uv = regulator_get_voltage(info->vref);
+ else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
+ dev_err(info->dev,
+ "Miss adc-vref property or vref regulator in the DT.\n");
+
+ if (of_property_read_u32(np, "fsl,adc-clk-div",
+ &info->adc_feature.clk_div_num)) {
+ info->adc_feature.clk_div_num = 2;
+ dev_info(info->dev,
+ "Miss adc-clk-div in dt, set divider to 2.\n");
+ }
+
+ if (of_property_read_u32(np, "fsl,adc-res",
+ &info->adc_feature.res_mode)) {
+ info->adc_feature.res_mode = 12;
+ dev_info(info->dev,
+ "Miss adc-res property in dt, use 8bit mode.\n");
+ }
+
+ if (of_property_read_u32(np, "fsl,adc-sam-time",
+ &info->adc_feature.sam_time)) {
+ info->adc_feature.sam_time = 4;
+ dev_info(info->dev,
+ "Miss adc-sam-time property in dt, set to 4.\n");
+ }
+
+ info->adc_feature.hw_average = of_property_read_bool(np,
+ "fsl,adc-hw-aver-en");
+ if (info->adc_feature.hw_average &&
+ of_property_read_u32(np, "fsl,adc-aver-sam-sel",
+ &info->adc_feature.hw_sam)) {
+ info->adc_feature.hw_sam = 4;
+ dev_info(info->dev,
+ "Miss adc-aver-sam-sel property in dt, set to 4.\n");
+ }
+
+ info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-mode");
+ info->adc_feature.hs_oper = of_property_read_bool(np, "fsl,adc-high-speed-conv");
+}
+
+static inline void vf610_adc_cfg_post_set(struct vf610_adc *info)
+{
+ struct vf610_adc_feature *adc_feature = &(info->adc_feature);
+ int cfg_data = 0;
+ int gc_data = 0;
+
+ /* clock select and clock devider */
+ switch (adc_feature->clk_div_num) {
+ case 1:
+ break;
+ case 2:
+ cfg_data |= VF610_ADC_CLK_DIV2;
+ break;
+ case 4:
+ cfg_data |= VF610_ADC_CLK_DIV4;
+ break;
+ case 8:
+ cfg_data |= VF610_ADC_CLK_DIV8;
+ break;
+ case 16:
+ switch (adc_feature->clk_sel) {
+ case ADCIOC_BUSCLK_SET:
+ cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8;
+ break;
+ default:
+ dev_err(info->dev, "error clk divider\n");
+ break;
+ }
+ break;
+ }
+
+ switch (adc_feature->clk_sel) {
+ case ADCIOC_ALTCLK_SET:
+ cfg_data |= VF610_ADC_ALTCLK_SEL;
+ break;
+ case ADCIOC_ADACK_SET:
+ cfg_data |= VF610_ADC_ADACK_SEL;
+ break;
+ default:
+ break;
+ }
+
+ /* resolution mode */
+ switch (adc_feature->res_mode) {
+ case 8:
+ cfg_data |= VF610_ADC_MODE_BIT8;
+ break;
+ case 10:
+ cfg_data |= VF610_ADC_MODE_BIT10;
+ break;
+ case 12:
+ cfg_data |= VF610_ADC_MODE_BIT12;
+ break;
+ default:
+ dev_err(info->dev, "error resolution mode\n");
+ break;
+ }
+
+ /* Defines the sample time duration */
+ switch (adc_feature->sam_time) {
+ case 2:
+ break;
+ case 4:
+ cfg_data |= VF610_ADC_ADSTS_SHORT;
+ break;
+ case 6:
+ cfg_data |= VF610_ADC_ADSTS_NORMAL;
+ break;
+ case 8:
+ cfg_data |= VF610_ADC_ADSTS_LONG;
+ break;
+ case 12:
+ cfg_data |= VF610_ADC_ADLSMP_LONG;
+ break;
+ case 16:
+ cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_SHORT;
+ break;
+ case 20:
+ cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_NORMAL;
+ break;
+ case 24:
+ cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_LONG;
+ break;
+ default:
+ dev_err(info->dev, "error sample duration\n");
+ break;
+ }
+
+ /* low power configuration */
+ cfg_data |= VF610_ADC_ADLPC_EN;
+
+ /* high speed operation */
+ cfg_data |= VF610_ADC_ADHSC_EN;
+
+ /* voltage reference*/
+ switch (adc_feature->vol_ref) {
+ case VF610_ADCIOC_VR_VREF_SET:
+ break;
+ case VF610_ADCIOC_VR_VALT_SET:
+ cfg_data |= VF610_ADC_REFSEL_VALT;
+ break;
+ case VF610_ADCIOC_VR_VBG_SET:
+ cfg_data |= VF610_ADC_REFSEL_VBG;
+ break;
+ default:
+ dev_err(info->dev, "error voltage reference\n");
+ }
+
+ /* trigger select */
+ if (adc_feature->tri_hw)
+ cfg_data |= VF610_ADC_ADTRG_HARD;
+
+ /* hardware average select */
+ if (adc_feature->hw_average) {
+ switch (adc_feature->hw_sam) {
+ case 4:
+ break;
+ case 8:
+ cfg_data |= VF610_ADC_AVGS_8;
+ break;
+ case 16:
+ cfg_data |= VF610_ADC_AVGS_16;
+ break;
+ case 32:
+ cfg_data |= VF610_ADC_AVGS_32;
+ break;
+ default:
+ dev_err(info->dev,
+ "error hardware sample average select\n");
+ }
+
+ gc_data |= VF610_ADC_AVGEN;
+ }
+
+ /* data overwrite enable */
+ if (adc_feature->dataov_en)
+ cfg_data |= VF610_ADC_OVWREN;
+
+ /* Asynchronous clock output enable */
+ if (adc_feature->ac_clk_en)
+ gc_data |= VF610_ADC_ADACKEN;
+
+ /* dma enable */
+ if (adc_feature->dma_en)
+ gc_data |= VF610_ADC_DMAEN;
+
+ /* continue function enable */
+ if (adc_feature->cc_en)
+ gc_data |= VF610_ADC_ADCON;
+
+ /* compare function enable */
+ if (adc_feature->cmp_func_en)
+ gc_data |= VF610_ADC_ACFE;
+
+ /* greater than enable */
+ if (adc_feature->cmp_greater_en)
+ gc_data |= VF610_ADC_ACFGT;
+
+ /* range enable */
+ if (adc_feature->cmp_range_en)
+ gc_data |= VF610_ADC_ACREN;
+
+ writel(cfg_data, info->regs + ADC_CFG);
+ writel(gc_data, info->regs + ADC_GC);
+}
+
+static void vf610_adc_calibration(struct vf610_adc *info)
+{
+ int adc_gc, hc_cfg;
+ int timeout;
+
+ if (!info->adc_feature.calibration)
+ return;
+
+ /* enable calibration interrupt */
+ hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
+ writel(hc_cfg, info->regs + ADC_HC0);
+
+ adc_gc = readl(info->regs + ADC_GC);
+ writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
+
+ timeout = wait_for_completion_interruptible_timeout
+ (&info->completion, VF610_ADC_TIMEOUT);
+ if (timeout == 0)
+ dev_err(info->dev, "Timeout for adc calibration\n");
+
+ adc_gc = readl(info->regs + ADC_GS);
+ if (adc_gc & VF610_ADC_CALF)
+ dev_err(info->dev, "ADC calibration failed\n");
+
+ info->adc_feature.calibration = false;
+}
+
+static inline void vf610_adc_cfg_set(struct vf610_adc *info)
+{
+ struct vf610_adc_feature *adc_feature = &(info->adc_feature);
+ int cfg_data = 0;
+
+ cfg_data = readl(info->regs + ADC_CFG);
+
+ /* low power configuration */
+ if (!adc_feature->lpm)
+ cfg_data &= ~VF610_ADC_ADLPC_EN;
+
+ /* high speed operation */
+ if (!adc_feature->hs_oper)
+ cfg_data &= ~VF610_ADC_ADHSC_EN;
+
+ /* trigger select */
+ if (adc_feature->tri_hw)
+ cfg_data |= VF610_ADC_ADTRG_HARD;
+
+ writel(cfg_data, info->regs + ADC_CFG);
+}
+
+static void vf610_adc_hw_init(struct vf610_adc *info)
+{
+ /* pin control for Sliding rheostat */
+ writel(info->adc_feature.pctl, info->regs + ADC_PCTL);
+
+ /* CFG: Feature set */
+ vf610_adc_cfg_post_set(info);
+
+ /* adc calibration */
+ vf610_adc_calibration(info);
+
+ /* CFG: power and speed set */
+ vf610_adc_cfg_set(info);
+}
+
+static inline int vf610_adc_read_data(struct vf610_adc *info)
+{
+ int result;
+
+ result = readl(info->regs + ADC_R0);
+
+ switch (info->adc_feature.res_mode) {
+ case 8:
+ result &= VF610_ADC_DATA_BIT8_MASK;
+ break;
+ case 10:
+ result &= VF610_ADC_DATA_BIT10_MASK;
+ break;
+ case 12:
+ result &= VF610_ADC_DATA_BIT12_MASK;
+ break;
+ default:
+ dev_err(info->dev, "error resolution mode\n");
+ break;
+ }
+
+ return result;
+}
+
+static irqreturn_t vf610_adc_isr(int irq, void *dev_id)
+{
+ struct vf610_adc *info = (struct vf610_adc *)dev_id;
+ int coco;
+
+ coco = readl(info->regs + ADC_HS);
+ if (coco & VF610_ADC_HS_COCO0) {
+ info->value = vf610_adc_read_data(info);
+ complete(&info->completion);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int vf610_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct vf610_adc *info = iio_priv(indio_dev);
+ unsigned int hc_cfg;
+ unsigned long timeout;
+
+ /* Check for invalid channel */
+ if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&indio_dev->mlock);
+
+ hc_cfg = VF610_ADC_ADCHC(chan->channel);
+ hc_cfg |= VF610_ADC_AIEN;
+ writel(hc_cfg, info->regs + ADC_HC0);
+ timeout = wait_for_completion_interruptible_timeout
+ (&info->completion, VF610_ADC_TIMEOUT);
+ *val = info->value;
+
+ mutex_unlock(&indio_dev->mlock);
+
+ if (timeout == 0)
+ return -ETIMEDOUT;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = info->vref_uv >> info->adc_feature.res_mode;
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int vf610_adc_reg_access(struct iio_dev *indio_dev,
+ unsigned reg, unsigned writeval,
+ unsigned *readval)
+{
+ struct vf610_adc *info = iio_priv(indio_dev);
+
+ if (readval == NULL)
+ return -EINVAL;
+
+ *readval = readl(info->regs + reg);
+
+ return 0;
+}
+
+static const struct iio_info vf610_adc_iio_info = {
+ .read_raw = &vf610_read_raw,
+ .debugfs_reg_access = &vf610_adc_reg_access,
+ .driver_module = THIS_MODULE,
+};
+
+static const struct of_device_id vf610_adc_match[] = {
+ { .compatible = "fsl,vf610-adc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vf610_adc_dt_ids);
+
+static int vf610_adc_probe(struct platform_device *pdev)
+{
+ struct vf610_adc *info = NULL;
+ struct iio_dev *indio_dev;
+ struct resource *mem;
+ int ret = -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
+ 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))
+ return PTR_ERR(info->regs);
+
+ info->irq = platform_get_irq(pdev, 0);
+ if (info->irq < 0) {
+ dev_err(&pdev->dev, "no irq resource?\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(info->dev, info->irq,
+ vf610_adc_isr, 0,
+ dev_name(&pdev->dev), info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+ info->irq);
+ return ret;
+ }
+
+ info->clk = devm_clk_get(&pdev->dev, "adc");
+ if (IS_ERR(info->clk)) {
+ dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+ PTR_ERR(info->clk));
+ ret = PTR_ERR(info->clk);
+ return ret;
+ }
+
+ info->vref = devm_regulator_get(&pdev->dev, "vref");
+ if (!IS_ERR(info->vref)) {
+ ret = regulator_enable(info->vref);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to enable vref regulator: %d\n", ret);
+ return ret;
+ }
+ } else {
+ info->vref = NULL;
+ }
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ init_completion(&info->completion);
+
+ 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 = &vf610_adc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = vf610_adc_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(vf610_adc_iio_channels);
+
+ ret = devm_iio_device_register(&pdev->dev, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(info->clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Could not prepare or enable the clock.\n");
+ return ret;
+ }
+
+ vf610_adc_cfg_of_init(info);
+ vf610_adc_hw_init(info);
+
+ return 0;
+}
+
+static int vf610_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct vf610_adc *info = iio_priv(indio_dev);
+
+ if (info->vref)
+ regulator_disable(info->vref);
+ clk_disable_unprepare(info->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int vf610_adc_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct vf610_adc *info = iio_priv(indio_dev);
+ int hc_cfg;
+
+ /* enter to stop mode */
+ hc_cfg = readl(info->regs + ADC_HC0);
+ hc_cfg |= VF610_ADC_CONV_DISABLE;
+ writel(hc_cfg, info->regs + ADC_HC0);
+
+ clk_disable_unprepare(info->clk);
+ if (info->vref)
+ regulator_disable(info->vref);
+
+ return 0;
+}
+
+static int vf610_adc_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct vf610_adc *info = iio_priv(indio_dev);
+ int ret;
+
+ if (info->vref) {
+ ret = regulator_enable(info->vref);
+ if (ret)
+ return ret;
+ }
+
+ ret = clk_prepare_enable(info->clk);
+ if (ret)
+ return ret;
+
+ vf610_adc_hw_init(info);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops,
+ vf610_adc_suspend,
+ vf610_adc_resume);
+
+static struct platform_driver vf610_adc_driver = {
+ .probe = vf610_adc_probe,
+ .remove = vf610_adc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = vf610_adc_match,
+ .pm = &vf610_adc_pm_ops,
+ },
+};
+
+module_platform_driver(vf610_adc_driver);
+
+MODULE_AUTHOR("Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>");
+MODULE_DESCRIPTION("Freescale VF610 ADC driver");
+MODULE_LICENSE("GPL v2");
--
1.7.2.rc3
^ permalink raw reply related [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-26 11:51 ` Lars-Peter Clausen
0 siblings, 0 replies; 41+ messages in thread
From: Lars-Peter Clausen @ 2013-11-26 11:51 UTC (permalink / raw)
To: Fugang Duan; +Cc: jic23, sachin.kamat, devicetree, shawn.guo, b20596, linux-iio
On 11/26/2013 11:56 AM, Fugang Duan wrote:
> Add Freescale Vybrid vf610 adc driver. The driver only support
> ADC software trigger.
>
> Signed-off-by: Fugang Duan <B38611@freescale.com>
The driver itself looks mostly fine. I'm not so sure about the dt bindings
though.
> ---
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/vf610_adc.c | 744 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 756 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 2209f28..d0476ec 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
> Say yes here to access the ADC part of the Nano River
> Technologies Viperboard.
>
> +config VF610_ADC
Keep things in alphabetical order...
> + tristate "Freescale vf610 ADC driver"
> + help
> + Say yes here if you want support for the Vybrid board
> + analog-to-digital converter.
> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the driver
> + also support the subsequent chips.
> +
> + This driver can also be built as a module. If so, the module will be
> + called vf610_adc.
> +
> endmenu
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index ba9a10a..c39ae38 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
> obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
> obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
> +obj-$(CONFIG_VF610_ADC) += vf610_adc.o
Same here
> diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
> new file mode 100644
> index 0000000..0c200d91
> --- /dev/null
> +++ b/drivers/iio/adc/vf610_adc.c
> @@ -0,0 +1,744 @@
[...]
> +struct vf610_adc {
> + struct device *dev;
> + void __iomem *regs;
> + struct clk *clk;
> + unsigned int irq;
irq doesn't seem to be used outside of the probe function.
> +
> + u32 vref_uv;
> + u32 value;
> + struct regulator *vref;
> + struct vf610_adc_feature adc_feature;
> +
> + struct completion completion;
> +};
[...]
> +static void vf610_adc_cfg_of_init(struct vf610_adc *info)
> +{
> + struct device_node *np = info->dev->of_node;
> +
> + /*
> + * set default Configuration for ADC controller
> + * This config may upgrade to require from DT
> + */
> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> + info->adc_feature.calibration = true;
> + info->adc_feature.tri_hw = false;
> + info->adc_feature.dataov_en = true;
> + info->adc_feature.ac_clk_en = false;
> + info->adc_feature.dma_en = false;
> + info->adc_feature.cc_en = false;
> + info->adc_feature.cmp_func_en = false;
> + info->adc_feature.cmp_range_en = false;
> + info->adc_feature.cmp_greater_en = false;
> +
> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> + &info->adc_feature.pctl)) {
> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> + dev_info(info->dev,
> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
> + }
> +
> + if (info->vref)
> + info->vref_uv = regulator_get_voltage(info->vref);
> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> + dev_err(info->dev,
> + "Miss adc-vref property or vref regulator in the DT.\n");
If you have a fixed reference voltage use the fixed voltage regulator. Don't
invent custom ways of specifying this.
> +
> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> + &info->adc_feature.clk_div_num)) {
> + info->adc_feature.clk_div_num = 2;
> + dev_info(info->dev,
> + "Miss adc-clk-div in dt, set divider to 2.\n");
> + }
> +
> + if (of_property_read_u32(np, "fsl,adc-res",
> + &info->adc_feature.res_mode)) {
> + info->adc_feature.res_mode = 12;
> + dev_info(info->dev,
> + "Miss adc-res property in dt, use 8bit mode.\n");
> + }
> +
> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> + &info->adc_feature.sam_time)) {
> + info->adc_feature.sam_time = 4;
> + dev_info(info->dev,
> + "Miss adc-sam-time property in dt, set to 4.\n");
> + }
> +
> + info->adc_feature.hw_average = of_property_read_bool(np,
> + "fsl,adc-hw-aver-en");
> + if (info->adc_feature.hw_average &&
> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> + &info->adc_feature.hw_sam)) {
> + info->adc_feature.hw_sam = 4;
> + dev_info(info->dev,
> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> + }
> +
> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-mode");
> + info->adc_feature.hs_oper = of_property_read_bool(np, "fsl,adc-high-speed-conv");
Some of the properties look like they should be runtime configurable rather
then board specific settings. E.g. the resolution or the sampling frequency,
or whether averaging is enabled.
> +}
[...]
> +static void vf610_adc_calibration(struct vf610_adc *info)
> +{
> + int adc_gc, hc_cfg;
> + int timeout;
> +
> + if (!info->adc_feature.calibration)
> + return;
> +
> + /* enable calibration interrupt */
> + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
> + writel(hc_cfg, info->regs + ADC_HC0);
> +
> + adc_gc = readl(info->regs + ADC_GC);
> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
> +
> + timeout = wait_for_completion_interruptible_timeout
> + (&info->completion, VF610_ADC_TIMEOUT);
This should probably not be interruptible.
> + if (timeout == 0)
> + dev_err(info->dev, "Timeout for adc calibration\n");
> +
> + adc_gc = readl(info->regs + ADC_GS);
> + if (adc_gc & VF610_ADC_CALF)
> + dev_err(info->dev, "ADC calibration failed\n");
> +
> + info->adc_feature.calibration = false;
> +}
> +
> +static inline void vf610_adc_cfg_set(struct vf610_adc *info)
I'd drop the inline
> +{
[...]
> +}
> +static inline int vf610_adc_read_data(struct vf610_adc *info)
same here
> +{
[...]
> +}
> +
[...]
> +static int vf610_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val,
> + int *val2,
> + long mask)
> +{
> + struct vf610_adc *info = iio_priv(indio_dev);
> + unsigned int hc_cfg;
> + unsigned long timeout;
> +
> + /* Check for invalid channel */
> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
> + return -EINVAL;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + mutex_lock(&indio_dev->mlock);
> +
putting a reinit_completion() here would make sense
> + hc_cfg = VF610_ADC_ADCHC(chan->channel);
> + hc_cfg |= VF610_ADC_AIEN;
> + writel(hc_cfg, info->regs + ADC_HC0);
> + timeout = wait_for_completion_interruptible_timeout
> + (&info->completion, VF610_ADC_TIMEOUT);
> + *val = info->value;
> +
> + mutex_unlock(&indio_dev->mlock);
> +
> + if (timeout == 0)
> + return -ETIMEDOUT;
timeout can be negative if the thread was interrupted. In that case return
the error code in timeout.
> +
> + return IIO_VAL_INT;
> +
> + case IIO_CHAN_INFO_SCALE:
> + *val = info->vref_uv >> info->adc_feature.res_mode;
> + *val2 = 0;
> + return IIO_VAL_INT_PLUS_MICRO;
It's better to use:
*val = info->vref_uv / 1000;
*val2 = info->adc_feature.res_mode;
return IIO_VAL_FACTIONAL_LOG2;
> + default:
> + break;
> + }
> +
> + return -EINVAL;
> +}
[...]
> +static int vf610_adc_probe(struct platform_device *pdev)
> +{
> + struct vf610_adc *info = NULL;
> + struct iio_dev *indio_dev;
> + struct resource *mem;
> + int ret = -ENODEV;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
> + 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))
> + return PTR_ERR(info->regs);
> +
> + info->irq = platform_get_irq(pdev, 0);
> + if (info->irq < 0) {
0 isn't a valid irq number so this should be "irq <= 0"
> + dev_err(&pdev->dev, "no irq resource?\n");
> + return -EINVAL;
> + }
> +
[...]
> + vf610_adc_cfg_of_init(info);
> + vf610_adc_hw_init(info);
This should probably be done before registering the device. Same goes for
the clk_enable() call.
> +
> + return 0;
> +}
[...]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-26 11:51 ` Lars-Peter Clausen
0 siblings, 0 replies; 41+ messages in thread
From: Lars-Peter Clausen @ 2013-11-26 11:51 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A, sachin.kamat-QSEj5FYQhm4dnm+yROfE0A,
devicetree-u79uwXL29TY76Z2rM5mHXA,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A, b20596-KZfg59tc24xl57MIdRCFDg,
linux-iio-u79uwXL29TY76Z2rM5mHXA
On 11/26/2013 11:56 AM, Fugang Duan wrote:
> Add Freescale Vybrid vf610 adc driver. The driver only support
> ADC software trigger.
>
> Signed-off-by: Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
The driver itself looks mostly fine. I'm not so sure about the dt bindings
though.
> ---
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/vf610_adc.c | 744 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 756 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 2209f28..d0476ec 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
> Say yes here to access the ADC part of the Nano River
> Technologies Viperboard.
>
> +config VF610_ADC
Keep things in alphabetical order...
> + tristate "Freescale vf610 ADC driver"
> + help
> + Say yes here if you want support for the Vybrid board
> + analog-to-digital converter.
> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the driver
> + also support the subsequent chips.
> +
> + This driver can also be built as a module. If so, the module will be
> + called vf610_adc.
> +
> endmenu
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index ba9a10a..c39ae38 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
> obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
> obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
> +obj-$(CONFIG_VF610_ADC) += vf610_adc.o
Same here
> diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
> new file mode 100644
> index 0000000..0c200d91
> --- /dev/null
> +++ b/drivers/iio/adc/vf610_adc.c
> @@ -0,0 +1,744 @@
[...]
> +struct vf610_adc {
> + struct device *dev;
> + void __iomem *regs;
> + struct clk *clk;
> + unsigned int irq;
irq doesn't seem to be used outside of the probe function.
> +
> + u32 vref_uv;
> + u32 value;
> + struct regulator *vref;
> + struct vf610_adc_feature adc_feature;
> +
> + struct completion completion;
> +};
[...]
> +static void vf610_adc_cfg_of_init(struct vf610_adc *info)
> +{
> + struct device_node *np = info->dev->of_node;
> +
> + /*
> + * set default Configuration for ADC controller
> + * This config may upgrade to require from DT
> + */
> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> + info->adc_feature.calibration = true;
> + info->adc_feature.tri_hw = false;
> + info->adc_feature.dataov_en = true;
> + info->adc_feature.ac_clk_en = false;
> + info->adc_feature.dma_en = false;
> + info->adc_feature.cc_en = false;
> + info->adc_feature.cmp_func_en = false;
> + info->adc_feature.cmp_range_en = false;
> + info->adc_feature.cmp_greater_en = false;
> +
> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> + &info->adc_feature.pctl)) {
> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> + dev_info(info->dev,
> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
> + }
> +
> + if (info->vref)
> + info->vref_uv = regulator_get_voltage(info->vref);
> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> + dev_err(info->dev,
> + "Miss adc-vref property or vref regulator in the DT.\n");
If you have a fixed reference voltage use the fixed voltage regulator. Don't
invent custom ways of specifying this.
> +
> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> + &info->adc_feature.clk_div_num)) {
> + info->adc_feature.clk_div_num = 2;
> + dev_info(info->dev,
> + "Miss adc-clk-div in dt, set divider to 2.\n");
> + }
> +
> + if (of_property_read_u32(np, "fsl,adc-res",
> + &info->adc_feature.res_mode)) {
> + info->adc_feature.res_mode = 12;
> + dev_info(info->dev,
> + "Miss adc-res property in dt, use 8bit mode.\n");
> + }
> +
> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> + &info->adc_feature.sam_time)) {
> + info->adc_feature.sam_time = 4;
> + dev_info(info->dev,
> + "Miss adc-sam-time property in dt, set to 4.\n");
> + }
> +
> + info->adc_feature.hw_average = of_property_read_bool(np,
> + "fsl,adc-hw-aver-en");
> + if (info->adc_feature.hw_average &&
> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> + &info->adc_feature.hw_sam)) {
> + info->adc_feature.hw_sam = 4;
> + dev_info(info->dev,
> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> + }
> +
> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-mode");
> + info->adc_feature.hs_oper = of_property_read_bool(np, "fsl,adc-high-speed-conv");
Some of the properties look like they should be runtime configurable rather
then board specific settings. E.g. the resolution or the sampling frequency,
or whether averaging is enabled.
> +}
[...]
> +static void vf610_adc_calibration(struct vf610_adc *info)
> +{
> + int adc_gc, hc_cfg;
> + int timeout;
> +
> + if (!info->adc_feature.calibration)
> + return;
> +
> + /* enable calibration interrupt */
> + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
> + writel(hc_cfg, info->regs + ADC_HC0);
> +
> + adc_gc = readl(info->regs + ADC_GC);
> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
> +
> + timeout = wait_for_completion_interruptible_timeout
> + (&info->completion, VF610_ADC_TIMEOUT);
This should probably not be interruptible.
> + if (timeout == 0)
> + dev_err(info->dev, "Timeout for adc calibration\n");
> +
> + adc_gc = readl(info->regs + ADC_GS);
> + if (adc_gc & VF610_ADC_CALF)
> + dev_err(info->dev, "ADC calibration failed\n");
> +
> + info->adc_feature.calibration = false;
> +}
> +
> +static inline void vf610_adc_cfg_set(struct vf610_adc *info)
I'd drop the inline
> +{
[...]
> +}
> +static inline int vf610_adc_read_data(struct vf610_adc *info)
same here
> +{
[...]
> +}
> +
[...]
> +static int vf610_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val,
> + int *val2,
> + long mask)
> +{
> + struct vf610_adc *info = iio_priv(indio_dev);
> + unsigned int hc_cfg;
> + unsigned long timeout;
> +
> + /* Check for invalid channel */
> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
> + return -EINVAL;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + mutex_lock(&indio_dev->mlock);
> +
putting a reinit_completion() here would make sense
> + hc_cfg = VF610_ADC_ADCHC(chan->channel);
> + hc_cfg |= VF610_ADC_AIEN;
> + writel(hc_cfg, info->regs + ADC_HC0);
> + timeout = wait_for_completion_interruptible_timeout
> + (&info->completion, VF610_ADC_TIMEOUT);
> + *val = info->value;
> +
> + mutex_unlock(&indio_dev->mlock);
> +
> + if (timeout == 0)
> + return -ETIMEDOUT;
timeout can be negative if the thread was interrupted. In that case return
the error code in timeout.
> +
> + return IIO_VAL_INT;
> +
> + case IIO_CHAN_INFO_SCALE:
> + *val = info->vref_uv >> info->adc_feature.res_mode;
> + *val2 = 0;
> + return IIO_VAL_INT_PLUS_MICRO;
It's better to use:
*val = info->vref_uv / 1000;
*val2 = info->adc_feature.res_mode;
return IIO_VAL_FACTIONAL_LOG2;
> + default:
> + break;
> + }
> +
> + return -EINVAL;
> +}
[...]
> +static int vf610_adc_probe(struct platform_device *pdev)
> +{
> + struct vf610_adc *info = NULL;
> + struct iio_dev *indio_dev;
> + struct resource *mem;
> + int ret = -ENODEV;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
> + 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))
> + return PTR_ERR(info->regs);
> +
> + info->irq = platform_get_irq(pdev, 0);
> + if (info->irq < 0) {
0 isn't a valid irq number so this should be "irq <= 0"
> + dev_err(&pdev->dev, "no irq resource?\n");
> + return -EINVAL;
> + }
> +
[...]
> + vf610_adc_cfg_of_init(info);
> + vf610_adc_hw_init(info);
This should probably be done before registering the device. Same goes for
the clk_enable() call.
> +
> + return 0;
> +}
[...]
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 4:44 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 4:44 UTC (permalink / raw)
To: Lars-Peter Clausen
Cc: jic23@kernel.org, sachin.kamat@linaro.org,
devicetree@vger.kernel.org, shawn.guo@linaro.org, Frank Li,
linux-iio@vger.kernel.org
From: Lars-Peter Clausen <lars@metafoo.de>
Data: Tuesday, November 26, 2013 7:52 PM
>To: Duan Fugang-B38611
>Cc: jic23@kernel.org; sachin.kamat@linaro.org; devicetree@vger.kernel.org;
>shawn.guo@linaro.org; Li Frank-B20596; linux-iio@vger.kernel.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
>
>On 11/26/2013 11:56 AM, Fugang Duan wrote:
>> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> software trigger.
>>
>> Signed-off-by: Fugang Duan <B38611@freescale.com>
>
>The driver itself looks mostly fine. I'm not so sure about the dt bindings
>though.
>
>> ---
>> drivers/iio/adc/Kconfig | 11 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/vf610_adc.c | 744
>> +++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 756 insertions(+), 0 deletions(-)
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index
>> 2209f28..d0476ec 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
>> Say yes here to access the ADC part of the Nano River
>> Technologies Viperboard.
>>
>> +config VF610_ADC
>
>Keep things in alphabetical order...
>
>> + tristate "Freescale vf610 ADC driver"
>> + help
>> + Say yes here if you want support for the Vybrid board
>> + analog-to-digital converter.
>> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the
>driver
>> + also support the subsequent chips.
>> +
>> + This driver can also be built as a module. If so, the module will be
>> + called vf610_adc.
>> +
>> endmenu
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index
>> ba9a10a..c39ae38 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>> obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
>> obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
>> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
>> +obj-$(CONFIG_VF610_ADC) += vf610_adc.o
>
>Same here
>
>> diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
>> new file mode 100644 index 0000000..0c200d91
>> --- /dev/null
>> +++ b/drivers/iio/adc/vf610_adc.c
>> @@ -0,0 +1,744 @@
>[...]
>> +struct vf610_adc {
>> + struct device *dev;
>> + void __iomem *regs;
>> + struct clk *clk;
>> + unsigned int irq;
>
>irq doesn't seem to be used outside of the probe function.
Yes, I will remove the private variable.
>
>> +
>> + u32 vref_uv;
>> + u32 value;
>> + struct regulator *vref;
>> + struct vf610_adc_feature adc_feature;
>> +
>> + struct completion completion;
>> +};
>[...]
>> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> + struct device_node *np = info->dev->of_node;
>> +
>> + /*
>> + * set default Configuration for ADC controller
>> + * This config may upgrade to require from DT
>> + */
>> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
>> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
>> + info->adc_feature.calibration = true;
>> + info->adc_feature.tri_hw = false;
>> + info->adc_feature.dataov_en = true;
>> + info->adc_feature.ac_clk_en = false;
>> + info->adc_feature.dma_en = false;
>> + info->adc_feature.cc_en = false;
>> + info->adc_feature.cmp_func_en = false;
>> + info->adc_feature.cmp_range_en = false;
>> + info->adc_feature.cmp_greater_en = false;
>> +
>> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> + &info->adc_feature.pctl)) {
>> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
>> + dev_info(info->dev,
>> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>> + }
>> +
>> + if (info->vref)
>> + info->vref_uv = regulator_get_voltage(info->vref);
>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>> + dev_err(info->dev,
>> + "Miss adc-vref property or vref regulator in the DT.\n");
>
>If you have a fixed reference voltage use the fixed voltage regulator. Don't
>invent custom ways of specifying this.
>
Different boards design may use different method to supply the reference voltage.
There maybe fixed voltage regulator, maybe no regulator just use fixed voltage.
>> +
>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> + &info->adc_feature.clk_div_num)) {
>> + info->adc_feature.clk_div_num = 2;
>> + dev_info(info->dev,
>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> + }
>> +
>> + if (of_property_read_u32(np, "fsl,adc-res",
>> + &info->adc_feature.res_mode)) {
>> + info->adc_feature.res_mode = 12;
>> + dev_info(info->dev,
>> + "Miss adc-res property in dt, use 8bit mode.\n");
>> + }
>> +
>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> + &info->adc_feature.sam_time)) {
>> + info->adc_feature.sam_time = 4;
>> + dev_info(info->dev,
>> + "Miss adc-sam-time property in dt, set to 4.\n");
>> + }
>> +
>> + info->adc_feature.hw_average = of_property_read_bool(np,
>> + "fsl,adc-hw-aver-en");
>> + if (info->adc_feature.hw_average &&
>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> + &info->adc_feature.hw_sam)) {
>> + info->adc_feature.hw_sam = 4;
>> + dev_info(info->dev,
>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>> + }
>> +
>> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
>mode");
>> + info->adc_feature.hs_oper = of_property_read_bool(np,
>> +"fsl,adc-high-speed-conv");
>
>Some of the properties look like they should be runtime configurable rather
>then board specific settings. E.g. the resolution or the sampling frequency, or
>whether averaging is enabled.
>
>
So, I have question:
Since the ADC have many user configurable setting, I have import some of them from DT, and some of them use static define.
How to set/change them in runtime for user configuration ?
Introduce ioctl ?
>> +}
>[...]
The format is caused by outlook.
>> +static void vf610_adc_calibration(struct vf610_adc *info) {
>> + int adc_gc, hc_cfg;
>> + int timeout;
>> +
>> + if (!info->adc_feature.calibration)
>> + return;
>> +
>> + /* enable calibration interrupt */
>> + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
>> + writel(hc_cfg, info->regs + ADC_HC0);
>> +
>> + adc_gc = readl(info->regs + ADC_GC);
>> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
>> +
>> + timeout = wait_for_completion_interruptible_timeout
>> + (&info->completion, VF610_ADC_TIMEOUT);
>
>This should probably not be interruptible.
>
Agree.
>> + if (timeout == 0)
>> + dev_err(info->dev, "Timeout for adc calibration\n");
>> +
>> + adc_gc = readl(info->regs + ADC_GS);
>> + if (adc_gc & VF610_ADC_CALF)
>> + dev_err(info->dev, "ADC calibration failed\n");
>> +
>> + info->adc_feature.calibration = false; }
>> +
>> +static inline void vf610_adc_cfg_set(struct vf610_adc *info)
>
>I'd drop the inline
Agree, thanks!
>
>> +{
>[...]
>> +}
>
>> +static inline int vf610_adc_read_data(struct vf610_adc *info)
>same here
Ok, thanks.
>> +{
>[...]
>> +}
>> +
>[...]
>> +static int vf610_read_raw(struct iio_dev *indio_dev,
>> + struct iio_chan_spec const *chan,
>> + int *val,
>> + int *val2,
>> + long mask)
>> +{
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> + unsigned int hc_cfg;
>> + unsigned long timeout;
>> +
>> + /* Check for invalid channel */
>> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
>> + return -EINVAL;
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + mutex_lock(&indio_dev->mlock);
>> +
>
>putting a reinit_completion() here would make sense
Agree it , thanks!
>
>> + hc_cfg = VF610_ADC_ADCHC(chan->channel);
>> + hc_cfg |= VF610_ADC_AIEN;
>> + writel(hc_cfg, info->regs + ADC_HC0);
>> + timeout = wait_for_completion_interruptible_timeout
>> + (&info->completion, VF610_ADC_TIMEOUT);
>> + *val = info->value;
>> +
>> + mutex_unlock(&indio_dev->mlock);
>> +
>> + if (timeout == 0)
>> + return -ETIMEDOUT;
>
>timeout can be negative if the thread was interrupted. In that case return the
>error code in timeout.
Agree, thanks!
>
>> +
>> + return IIO_VAL_INT;
>> +
>> + case IIO_CHAN_INFO_SCALE:
>> + *val = info->vref_uv >> info->adc_feature.res_mode;
>> + *val2 = 0;
>> + return IIO_VAL_INT_PLUS_MICRO;
>
>It's better to use:
> *val = info->vref_uv / 1000;
> *val2 = info->adc_feature.res_mode;
> return IIO_VAL_FACTIONAL_LOG2;
>
Thanks for your suggestion.
>> + default:
>> + break;
>> + }
>> +
>> + return -EINVAL;
>> +}
>[...]
>> +static int vf610_adc_probe(struct platform_device *pdev) {
>> + struct vf610_adc *info = NULL;
>> + struct iio_dev *indio_dev;
>> + struct resource *mem;
>> + int ret = -ENODEV;
>> +
>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
>> + 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))
>> + return PTR_ERR(info->regs);
>> +
>> + info->irq = platform_get_irq(pdev, 0);
>> + if (info->irq < 0) {
>
>0 isn't a valid irq number so this should be "irq <= 0"
Greet. Thanks.
>
>> + dev_err(&pdev->dev, "no irq resource?\n");
>> + return -EINVAL;
>> + }
>> +
>[...]
>> + vf610_adc_cfg_of_init(info);
>> + vf610_adc_hw_init(info);
>
>This should probably be done before registering the device. Same goes for the
>clk_enable() call.
>
Got it, thanks!
>> +
>> + return 0;
>> +}
Lars-Peter, thanks for your review.
Andy
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 4:44 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 4:44 UTC (permalink / raw)
To: Lars-Peter Clausen
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org, Frank Li,
linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
From: Lars-Peter Clausen <lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org>
Data: Tuesday, November 26, 2013 7:52 PM
>To: Duan Fugang-B38611
>Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org; sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org;
>shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; Li Frank-B20596; linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
>
>On 11/26/2013 11:56 AM, Fugang Duan wrote:
>> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> software trigger.
>>
>> Signed-off-by: Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
>
>The driver itself looks mostly fine. I'm not so sure about the dt bindings
>though.
>
>> ---
>> drivers/iio/adc/Kconfig | 11 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/vf610_adc.c | 744
>> +++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 756 insertions(+), 0 deletions(-)
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index
>> 2209f28..d0476ec 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
>> Say yes here to access the ADC part of the Nano River
>> Technologies Viperboard.
>>
>> +config VF610_ADC
>
>Keep things in alphabetical order...
>
>> + tristate "Freescale vf610 ADC driver"
>> + help
>> + Say yes here if you want support for the Vybrid board
>> + analog-to-digital converter.
>> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the
>driver
>> + also support the subsequent chips.
>> +
>> + This driver can also be built as a module. If so, the module will be
>> + called vf610_adc.
>> +
>> endmenu
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index
>> ba9a10a..c39ae38 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>> obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
>> obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
>> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
>> +obj-$(CONFIG_VF610_ADC) += vf610_adc.o
>
>Same here
>
>> diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
>> new file mode 100644 index 0000000..0c200d91
>> --- /dev/null
>> +++ b/drivers/iio/adc/vf610_adc.c
>> @@ -0,0 +1,744 @@
>[...]
>> +struct vf610_adc {
>> + struct device *dev;
>> + void __iomem *regs;
>> + struct clk *clk;
>> + unsigned int irq;
>
>irq doesn't seem to be used outside of the probe function.
Yes, I will remove the private variable.
>
>> +
>> + u32 vref_uv;
>> + u32 value;
>> + struct regulator *vref;
>> + struct vf610_adc_feature adc_feature;
>> +
>> + struct completion completion;
>> +};
>[...]
>> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> + struct device_node *np = info->dev->of_node;
>> +
>> + /*
>> + * set default Configuration for ADC controller
>> + * This config may upgrade to require from DT
>> + */
>> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
>> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
>> + info->adc_feature.calibration = true;
>> + info->adc_feature.tri_hw = false;
>> + info->adc_feature.dataov_en = true;
>> + info->adc_feature.ac_clk_en = false;
>> + info->adc_feature.dma_en = false;
>> + info->adc_feature.cc_en = false;
>> + info->adc_feature.cmp_func_en = false;
>> + info->adc_feature.cmp_range_en = false;
>> + info->adc_feature.cmp_greater_en = false;
>> +
>> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> + &info->adc_feature.pctl)) {
>> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
>> + dev_info(info->dev,
>> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>> + }
>> +
>> + if (info->vref)
>> + info->vref_uv = regulator_get_voltage(info->vref);
>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>> + dev_err(info->dev,
>> + "Miss adc-vref property or vref regulator in the DT.\n");
>
>If you have a fixed reference voltage use the fixed voltage regulator. Don't
>invent custom ways of specifying this.
>
Different boards design may use different method to supply the reference voltage.
There maybe fixed voltage regulator, maybe no regulator just use fixed voltage.
>> +
>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> + &info->adc_feature.clk_div_num)) {
>> + info->adc_feature.clk_div_num = 2;
>> + dev_info(info->dev,
>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> + }
>> +
>> + if (of_property_read_u32(np, "fsl,adc-res",
>> + &info->adc_feature.res_mode)) {
>> + info->adc_feature.res_mode = 12;
>> + dev_info(info->dev,
>> + "Miss adc-res property in dt, use 8bit mode.\n");
>> + }
>> +
>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> + &info->adc_feature.sam_time)) {
>> + info->adc_feature.sam_time = 4;
>> + dev_info(info->dev,
>> + "Miss adc-sam-time property in dt, set to 4.\n");
>> + }
>> +
>> + info->adc_feature.hw_average = of_property_read_bool(np,
>> + "fsl,adc-hw-aver-en");
>> + if (info->adc_feature.hw_average &&
>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> + &info->adc_feature.hw_sam)) {
>> + info->adc_feature.hw_sam = 4;
>> + dev_info(info->dev,
>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>> + }
>> +
>> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
>mode");
>> + info->adc_feature.hs_oper = of_property_read_bool(np,
>> +"fsl,adc-high-speed-conv");
>
>Some of the properties look like they should be runtime configurable rather
>then board specific settings. E.g. the resolution or the sampling frequency, or
>whether averaging is enabled.
>
>
So, I have question:
Since the ADC have many user configurable setting, I have import some of them from DT, and some of them use static define.
How to set/change them in runtime for user configuration ?
Introduce ioctl ?
>> +}
>[...]
The format is caused by outlook.
>> +static void vf610_adc_calibration(struct vf610_adc *info) {
>> + int adc_gc, hc_cfg;
>> + int timeout;
>> +
>> + if (!info->adc_feature.calibration)
>> + return;
>> +
>> + /* enable calibration interrupt */
>> + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
>> + writel(hc_cfg, info->regs + ADC_HC0);
>> +
>> + adc_gc = readl(info->regs + ADC_GC);
>> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
>> +
>> + timeout = wait_for_completion_interruptible_timeout
>> + (&info->completion, VF610_ADC_TIMEOUT);
>
>This should probably not be interruptible.
>
Agree.
>> + if (timeout == 0)
>> + dev_err(info->dev, "Timeout for adc calibration\n");
>> +
>> + adc_gc = readl(info->regs + ADC_GS);
>> + if (adc_gc & VF610_ADC_CALF)
>> + dev_err(info->dev, "ADC calibration failed\n");
>> +
>> + info->adc_feature.calibration = false; }
>> +
>> +static inline void vf610_adc_cfg_set(struct vf610_adc *info)
>
>I'd drop the inline
Agree, thanks!
>
>> +{
>[...]
>> +}
>
>> +static inline int vf610_adc_read_data(struct vf610_adc *info)
>same here
Ok, thanks.
>> +{
>[...]
>> +}
>> +
>[...]
>> +static int vf610_read_raw(struct iio_dev *indio_dev,
>> + struct iio_chan_spec const *chan,
>> + int *val,
>> + int *val2,
>> + long mask)
>> +{
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> + unsigned int hc_cfg;
>> + unsigned long timeout;
>> +
>> + /* Check for invalid channel */
>> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
>> + return -EINVAL;
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + mutex_lock(&indio_dev->mlock);
>> +
>
>putting a reinit_completion() here would make sense
Agree it , thanks!
>
>> + hc_cfg = VF610_ADC_ADCHC(chan->channel);
>> + hc_cfg |= VF610_ADC_AIEN;
>> + writel(hc_cfg, info->regs + ADC_HC0);
>> + timeout = wait_for_completion_interruptible_timeout
>> + (&info->completion, VF610_ADC_TIMEOUT);
>> + *val = info->value;
>> +
>> + mutex_unlock(&indio_dev->mlock);
>> +
>> + if (timeout == 0)
>> + return -ETIMEDOUT;
>
>timeout can be negative if the thread was interrupted. In that case return the
>error code in timeout.
Agree, thanks!
>
>> +
>> + return IIO_VAL_INT;
>> +
>> + case IIO_CHAN_INFO_SCALE:
>> + *val = info->vref_uv >> info->adc_feature.res_mode;
>> + *val2 = 0;
>> + return IIO_VAL_INT_PLUS_MICRO;
>
>It's better to use:
> *val = info->vref_uv / 1000;
> *val2 = info->adc_feature.res_mode;
> return IIO_VAL_FACTIONAL_LOG2;
>
Thanks for your suggestion.
>> + default:
>> + break;
>> + }
>> +
>> + return -EINVAL;
>> +}
>[...]
>> +static int vf610_adc_probe(struct platform_device *pdev) {
>> + struct vf610_adc *info = NULL;
>> + struct iio_dev *indio_dev;
>> + struct resource *mem;
>> + int ret = -ENODEV;
>> +
>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
>> + 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))
>> + return PTR_ERR(info->regs);
>> +
>> + info->irq = platform_get_irq(pdev, 0);
>> + if (info->irq < 0) {
>
>0 isn't a valid irq number so this should be "irq <= 0"
Greet. Thanks.
>
>> + dev_err(&pdev->dev, "no irq resource?\n");
>> + return -EINVAL;
>> + }
>> +
>[...]
>> + vf610_adc_cfg_of_init(info);
>> + vf610_adc_hw_init(info);
>
>This should probably be done before registering the device. Same goes for the
>clk_enable() call.
>
Got it, thanks!
>> +
>> + return 0;
>> +}
Lars-Peter, thanks for your review.
Andy
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 8:20 ` Lars-Peter Clausen
0 siblings, 0 replies; 41+ messages in thread
From: Lars-Peter Clausen @ 2013-11-27 8:20 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23@kernel.org, sachin.kamat@linaro.org,
devicetree@vger.kernel.org, shawn.guo@linaro.org, Frank Li,
linux-iio@vger.kernel.org
On 11/27/2013 05:44 AM, Fugang Duan wrote:
[...]
>>> + if (info->vref)
>>> + info->vref_uv = regulator_get_voltage(info->vref);
>>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>>> + dev_err(info->dev,
>>> + "Miss adc-vref property or vref regulator in the DT.\n");
>>
>> If you have a fixed reference voltage use the fixed voltage regulator. Don't
>> invent custom ways of specifying this.
>>
> Different boards design may use different method to supply the reference voltage.
> There maybe fixed voltage regulator, maybe no regulator just use fixed voltage.
Well the voltage has to come from somewhere and that source should be
described in the DT with a fixed regulator.
>
>>> +
>>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>>> + &info->adc_feature.clk_div_num)) {
>>> + info->adc_feature.clk_div_num = 2;
>>> + dev_info(info->dev,
>>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>>> + }
>>> +
>>> + if (of_property_read_u32(np, "fsl,adc-res",
>>> + &info->adc_feature.res_mode)) {
>>> + info->adc_feature.res_mode = 12;
>>> + dev_info(info->dev,
>>> + "Miss adc-res property in dt, use 8bit mode.\n");
>>> + }
>>> +
>>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>>> + &info->adc_feature.sam_time)) {
>>> + info->adc_feature.sam_time = 4;
>>> + dev_info(info->dev,
>>> + "Miss adc-sam-time property in dt, set to 4.\n");
>>> + }
>>> +
>>> + info->adc_feature.hw_average = of_property_read_bool(np,
>>> + "fsl,adc-hw-aver-en");
>>> + if (info->adc_feature.hw_average &&
>>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>>> + &info->adc_feature.hw_sam)) {
>>> + info->adc_feature.hw_sam = 4;
>>> + dev_info(info->dev,
>>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>>> + }
>>> +
>>> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
>> mode");
>>> + info->adc_feature.hs_oper = of_property_read_bool(np,
>>> +"fsl,adc-high-speed-conv");
>>
>> Some of the properties look like they should be runtime configurable rather
>> then board specific settings. E.g. the resolution or the sampling frequency, or
>> whether averaging is enabled.
>>
>>
> So, I have question:
> Since the ADC have many user configurable setting, I have import some of them from DT, and some of them use static define.
> How to set/change them in runtime for user configuration ?
> Introduce ioctl ?
I think a lot of them already map to standard iio properties. E.g. the
samplerate. So you'd expose them as sysfs attributes. But please use
standard attributes here instead of inventing your own with cryptic names.
- Lars
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 8:20 ` Lars-Peter Clausen
0 siblings, 0 replies; 41+ messages in thread
From: Lars-Peter Clausen @ 2013-11-27 8:20 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org, Frank Li,
linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
On 11/27/2013 05:44 AM, Fugang Duan wrote:
[...]
>>> + if (info->vref)
>>> + info->vref_uv = regulator_get_voltage(info->vref);
>>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>>> + dev_err(info->dev,
>>> + "Miss adc-vref property or vref regulator in the DT.\n");
>>
>> If you have a fixed reference voltage use the fixed voltage regulator. Don't
>> invent custom ways of specifying this.
>>
> Different boards design may use different method to supply the reference voltage.
> There maybe fixed voltage regulator, maybe no regulator just use fixed voltage.
Well the voltage has to come from somewhere and that source should be
described in the DT with a fixed regulator.
>
>>> +
>>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>>> + &info->adc_feature.clk_div_num)) {
>>> + info->adc_feature.clk_div_num = 2;
>>> + dev_info(info->dev,
>>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>>> + }
>>> +
>>> + if (of_property_read_u32(np, "fsl,adc-res",
>>> + &info->adc_feature.res_mode)) {
>>> + info->adc_feature.res_mode = 12;
>>> + dev_info(info->dev,
>>> + "Miss adc-res property in dt, use 8bit mode.\n");
>>> + }
>>> +
>>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>>> + &info->adc_feature.sam_time)) {
>>> + info->adc_feature.sam_time = 4;
>>> + dev_info(info->dev,
>>> + "Miss adc-sam-time property in dt, set to 4.\n");
>>> + }
>>> +
>>> + info->adc_feature.hw_average = of_property_read_bool(np,
>>> + "fsl,adc-hw-aver-en");
>>> + if (info->adc_feature.hw_average &&
>>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>>> + &info->adc_feature.hw_sam)) {
>>> + info->adc_feature.hw_sam = 4;
>>> + dev_info(info->dev,
>>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>>> + }
>>> +
>>> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
>> mode");
>>> + info->adc_feature.hs_oper = of_property_read_bool(np,
>>> +"fsl,adc-high-speed-conv");
>>
>> Some of the properties look like they should be runtime configurable rather
>> then board specific settings. E.g. the resolution or the sampling frequency, or
>> whether averaging is enabled.
>>
>>
> So, I have question:
> Since the ADC have many user configurable setting, I have import some of them from DT, and some of them use static define.
> How to set/change them in runtime for user configuration ?
> Introduce ioctl ?
I think a lot of them already map to standard iio properties. E.g. the
samplerate. So you'd expose them as sysfs attributes. But please use
standard attributes here instead of inventing your own with cryptic names.
- Lars
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 8:31 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 8:31 UTC (permalink / raw)
To: Lars-Peter Clausen
Cc: jic23@kernel.org, sachin.kamat@linaro.org,
devicetree@vger.kernel.org, shawn.guo@linaro.org, Frank Li,
linux-iio@vger.kernel.org
From: Lars-Peter Clausen [mailto:lars@metafoo.de]
Data: Wednesday, November 27, 2013 4:21 PM
>To: Duan Fugang-B38611
>Cc: jic23@kernel.org; sachin.kamat@linaro.org; devicetree@vger.kernel.org;
>shawn.guo@linaro.org; Li Frank-B20596; linux-iio@vger.kernel.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc dr=
iver
>
>On 11/27/2013 05:44 AM, Fugang Duan wrote:
>[...]
>>>> + if (info->vref)
>>>> + info->vref_uv =3D regulator_get_voltage(info->vref);
>>>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>>>> + dev_err(info->dev,
>>>> + "Miss adc-vref property or vref regulator in the DT.\n");
>>>
>>> If you have a fixed reference voltage use the fixed voltage
>>> regulator. Don't invent custom ways of specifying this.
>>>
>> Different boards design may use different method to supply the reference
>voltage.
>> There maybe fixed voltage regulator, maybe no regulator just use fixed
>voltage.
>
>Well the voltage has to come from somewhere and that source should be desc=
ribed
>in the DT with a fixed regulator.
>
Good suggestion. Thanks.
>>
>>>> +
>>>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>>>> + &info->adc_feature.clk_div_num)) {
>>>> + info->adc_feature.clk_div_num =3D 2;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>>>> + }
>>>> +
>>>> + if (of_property_read_u32(np, "fsl,adc-res",
>>>> + &info->adc_feature.res_mode)) {
>>>> + info->adc_feature.res_mode =3D 12;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-res property in dt, use 8bit mode.\n");
>>>> + }
>>>> +
>>>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>>>> + &info->adc_feature.sam_time)) {
>>>> + info->adc_feature.sam_time =3D 4;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-sam-time property in dt, set to 4.\n");
>>>> + }
>>>> +
>>>> + info->adc_feature.hw_average =3D of_property_read_bool(np,
>>>> + "fsl,adc-hw-aver-en");
>>>> + if (info->adc_feature.hw_average &&
>>>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>>>> + &info->adc_feature.hw_sam)) {
>>>> + info->adc_feature.hw_sam =3D 4;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>>>> + }
>>>> +
>>>> + info->adc_feature.lpm =3D of_property_read_bool(np,
>>>> +"fsl,adc-low-power-
>>> mode");
>>>> + info->adc_feature.hs_oper =3D of_property_read_bool(np,
>>>> +"fsl,adc-high-speed-conv");
>>>
>>> Some of the properties look like they should be runtime configurable
>>> rather then board specific settings. E.g. the resolution or the
>>> sampling frequency, or whether averaging is enabled.
>>>
>>>
>> So, I have question:
>> Since the ADC have many user configurable setting, I have import some of=
them
>from DT, and some of them use static define.
>> How to set/change them in runtime for user configuration ?
>> Introduce ioctl ?
>
>I think a lot of them already map to standard iio properties. E.g. the
>samplerate. So you'd expose them as sysfs attributes. But please use stand=
ard
>attributes here instead of inventing your own with cryptic names.
>
>- Lars
Good suggestion, I will use the standard IIO interface for the ADC configur=
ation.
Thanks,
Andy
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 8:31 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 8:31 UTC (permalink / raw)
To: Lars-Peter Clausen
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org, Frank Li,
linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
From: Lars-Peter Clausen [mailto:lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org]
Data: Wednesday, November 27, 2013 4:21 PM
>To: Duan Fugang-B38611
>Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org; sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org;
>shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; Li Frank-B20596; linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
>
>On 11/27/2013 05:44 AM, Fugang Duan wrote:
>[...]
>>>> + if (info->vref)
>>>> + info->vref_uv = regulator_get_voltage(info->vref);
>>>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>>>> + dev_err(info->dev,
>>>> + "Miss adc-vref property or vref regulator in the DT.\n");
>>>
>>> If you have a fixed reference voltage use the fixed voltage
>>> regulator. Don't invent custom ways of specifying this.
>>>
>> Different boards design may use different method to supply the reference
>voltage.
>> There maybe fixed voltage regulator, maybe no regulator just use fixed
>voltage.
>
>Well the voltage has to come from somewhere and that source should be described
>in the DT with a fixed regulator.
>
Good suggestion. Thanks.
>>
>>>> +
>>>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>>>> + &info->adc_feature.clk_div_num)) {
>>>> + info->adc_feature.clk_div_num = 2;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>>>> + }
>>>> +
>>>> + if (of_property_read_u32(np, "fsl,adc-res",
>>>> + &info->adc_feature.res_mode)) {
>>>> + info->adc_feature.res_mode = 12;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-res property in dt, use 8bit mode.\n");
>>>> + }
>>>> +
>>>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>>>> + &info->adc_feature.sam_time)) {
>>>> + info->adc_feature.sam_time = 4;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-sam-time property in dt, set to 4.\n");
>>>> + }
>>>> +
>>>> + info->adc_feature.hw_average = of_property_read_bool(np,
>>>> + "fsl,adc-hw-aver-en");
>>>> + if (info->adc_feature.hw_average &&
>>>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>>>> + &info->adc_feature.hw_sam)) {
>>>> + info->adc_feature.hw_sam = 4;
>>>> + dev_info(info->dev,
>>>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>>>> + }
>>>> +
>>>> + info->adc_feature.lpm = of_property_read_bool(np,
>>>> +"fsl,adc-low-power-
>>> mode");
>>>> + info->adc_feature.hs_oper = of_property_read_bool(np,
>>>> +"fsl,adc-high-speed-conv");
>>>
>>> Some of the properties look like they should be runtime configurable
>>> rather then board specific settings. E.g. the resolution or the
>>> sampling frequency, or whether averaging is enabled.
>>>
>>>
>> So, I have question:
>> Since the ADC have many user configurable setting, I have import some of them
>from DT, and some of them use static define.
>> How to set/change them in runtime for user configuration ?
>> Introduce ioctl ?
>
>I think a lot of them already map to standard iio properties. E.g. the
>samplerate. So you'd expose them as sysfs attributes. But please use standard
>attributes here instead of inventing your own with cryptic names.
>
>- Lars
Good suggestion, I will use the standard IIO interface for the ADC configuration.
Thanks,
Andy
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
2013-11-26 10:56 ` Fugang Duan
(?)
(?)
@ 2013-11-26 12:24 ` Peter Meerwald
2013-11-26 12:49 ` Lars-Peter Clausen
2013-11-27 2:57 ` Fugang Duan
-1 siblings, 2 replies; 41+ messages in thread
From: Peter Meerwald @ 2013-11-26 12:24 UTC (permalink / raw)
To: Fugang Duan; +Cc: jic23, shawn.guo, linux-iio
On Tue, 26 Nov 2013, Fugang Duan wrote:
> Add Freescale Vybrid vf610 adc driver. The driver only support
> ADC software trigger.
nitpicking below
> Signed-off-by: Fugang Duan <B38611@freescale.com>
> ---
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/vf610_adc.c | 744 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 756 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 2209f28..d0476ec 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
> Say yes here to access the ADC part of the Nano River
> Technologies Viperboard.
>
> +config VF610_ADC
alphabetic order please
> + tristate "Freescale vf610 ADC driver"
> + help
> + Say yes here if you want support for the Vybrid board
> + analog-to-digital converter.
> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the driver
> + also support the subsequent chips.
> +
> + This driver can also be built as a module. If so, the module will be
> + called vf610_adc.
> +
> endmenu
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index ba9a10a..c39ae38 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
> obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
> obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
> +obj-$(CONFIG_VF610_ADC) += vf610_adc.o
keep alphabetic order
> diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
> new file mode 100644
> index 0000000..0c200d91
> --- /dev/null
> +++ b/drivers/iio/adc/vf610_adc.c
> @@ -0,0 +1,744 @@
> +/*
> + * Freescale Vybrid vf610 ADC driver
> + *
> + * Copyright 2013 Freescale Semiconductor, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that 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, write to the Free Software
> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +#include <linux/err.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +/* This will be the driver name the kernel reports */
> +#define DRIVER_NAME "vf610-adc"
> +
> +/* Vybrid/IMX ADC registers */
VF610_ prefix
> +#define ADC_HC0 0x00
> +#define ADC_HC1 0x04
> +#define ADC_HS 0x08
> +#define ADC_R0 0x0c
> +#define ADC_R1 0x10
> +#define ADC_CFG 0x14
> +#define ADC_GC 0x18
> +#define ADC_GS 0x1c
> +#define ADC_CV 0x20
> +#define ADC_OFS 0x24
> +#define ADC_CAL 0x28
> +#define ADC_PCTL 0x30
> +
> +/* Configuration register field define */
> +#define VF610_ADC_MODE_BIT8 0x00
> +#define VF610_ADC_MODE_BIT10 0x04
> +#define VF610_ADC_MODE_BIT12 0x08
> +#define VF610_ADC_DATA_BIT8_MASK 0xFF
> +#define VF610_ADC_DATA_BIT10_MASK 0x3FF
> +#define VF610_ADC_DATA_BIT12_MASK 0xFFF
> +#define VF610_ADC_BUSCLK2_SEL 0x01
> +#define VF610_ADC_ALTCLK_SEL 0x02
> +#define VF610_ADC_ADACK_SEL 0x03
> +#define VF610_ADC_CLK_DIV2 0x20
> +#define VF610_ADC_CLK_DIV4 0x40
> +#define VF610_ADC_CLK_DIV8 0x60
> +#define VF610_ADC_ADLSMP_LONG 0x10
> +#define VF610_ADC_ADSTS_SHORT 0x100
> +#define VF610_ADC_ADSTS_NORMAL 0x200
> +#define VF610_ADC_ADSTS_LONG 0x300
> +#define VF610_ADC_ADLPC_EN 0x80
> +#define VF610_ADC_ADHSC_EN 0x400
> +#define VF610_ADC_REFSEL_VALT 0x100
> +#define VF610_ADC_REFSEL_VBG 0x1000
> +#define VF610_ADC_ADTRG_HARD 0x2000
> +#define VF610_ADC_AVGS_8 0x4000
> +#define VF610_ADC_AVGS_16 0x8000
> +#define VF610_ADC_AVGS_32 0xC000
> +#define VF610_ADC_OVWREN 0x10000
> +
> +/* General control register field define */
> +#define VF610_ADC_ADACKEN 0x1
> +#define VF610_ADC_DMAEN 0x2
> +#define VF610_ADC_ACREN 0x4
> +#define VF610_ADC_ACFGT 0x8
> +#define VF610_ADC_ACFE 0x10
> +#define VF610_ADC_AVGEN 0x20
> +#define VF610_ADC_ADCON 0x40
> +#define VF610_ADC_CAL 0x80
> +
> +/* Other field define */
> +#define VF610_ADC_IOPCTL5 0x20
> +#define VF610_ADC_ADCHC(x) ((x) & 0xF)
> +#define VF610_ADC_AIEN (1 << 7)
> +#define VF610_ADC_CONV_DISABLE 0x1F
> +#define VF610_ADC_HS_COCO0 0x1
> +#define VF610_ADC_CALF 0x2
> +#define VF610_ADC_MAX_CHANS_NUM 16
> +#define VF610_ADC_TIMEOUT (msecs_to_jiffies(100))
no parenthesis needed
> +
> +enum clk_sel {
> + ADCIOC_BUSCLK_SET,
> + ADCIOC_ALTCLK_SET,
> + ADCIOC_ADACK_SET,
VF610 prefix
> +};
> +
> +enum vol_ref {
> + VF610_ADCIOC_VR_VREF_SET,
> + VF610_ADCIOC_VR_VALT_SET,
> + VF610_ADCIOC_VR_VBG_SET,
> +};
> +
> +struct vf610_adc_feature {
> + enum clk_sel clk_sel;
> + enum vol_ref vol_ref;
> +
> + int pctl;
> + int clk_div_num;
> + int res_mode;
> + int sam_time;
> + int hw_sam;
> +
> + bool calibration;
> + bool cc_en;
> + bool dataov_en;
> + bool hw_average;
> + bool tri_hw;
> + bool hs_oper;
> + bool lpm;
> + bool dma_en;
> + bool ac_clk_en;
> + bool cmp_func_en;
> + bool cmp_range_en;
> + bool cmp_greater_en;
> +};
> +
> +struct vf610_adc {
> + struct device *dev;
> + void __iomem *regs;
> + struct clk *clk;
> + unsigned int irq;
> +
> + u32 vref_uv;
> + u32 value;
> + struct regulator *vref;
> + struct vf610_adc_feature adc_feature;
> +
> + struct completion completion;
> +};
> +
> +#define VF610_ADC_CHAN(_idx, _chan_type) { \
> + .type = (_chan_type), \
> + .indexed = 1, \
> + .channel = _idx, \
> + .address = _idx, \
address not needed
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> +}
> +
> +static const struct iio_chan_spec vf610_adc_iio_channels[] = {
> + VF610_ADC_CHAN(0, IIO_VOLTAGE),
> + VF610_ADC_CHAN(1, IIO_VOLTAGE),
> + VF610_ADC_CHAN(2, IIO_VOLTAGE),
> + VF610_ADC_CHAN(3, IIO_VOLTAGE),
> + VF610_ADC_CHAN(4, IIO_VOLTAGE),
> + VF610_ADC_CHAN(5, IIO_VOLTAGE),
> + VF610_ADC_CHAN(6, IIO_VOLTAGE),
> + VF610_ADC_CHAN(7, IIO_VOLTAGE),
> + VF610_ADC_CHAN(8, IIO_VOLTAGE),
> + VF610_ADC_CHAN(9, IIO_VOLTAGE),
> + VF610_ADC_CHAN(10, IIO_VOLTAGE),
> + VF610_ADC_CHAN(11, IIO_VOLTAGE),
> + VF610_ADC_CHAN(12, IIO_VOLTAGE),
> + VF610_ADC_CHAN(13, IIO_VOLTAGE),
> + VF610_ADC_CHAN(14, IIO_VOLTAGE),
> + VF610_ADC_CHAN(15, IIO_VOLTAGE),
> +};
> +
> +static void vf610_adc_cfg_of_init(struct vf610_adc *info)
> +{
> + struct device_node *np = info->dev->of_node;
> +
> + /*
> + * set default Configuration for ADC controller
> + * This config may upgrade to require from DT
what does "This config may upgrade to require from DT" mean?
> + */
> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> + info->adc_feature.calibration = true;
> + info->adc_feature.tri_hw = false;
> + info->adc_feature.dataov_en = true;
> + info->adc_feature.ac_clk_en = false;
> + info->adc_feature.dma_en = false;
> + info->adc_feature.cc_en = false;
> + info->adc_feature.cmp_func_en = false;
> + info->adc_feature.cmp_range_en = false;
> + info->adc_feature.cmp_greater_en = false;
> +
> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> + &info->adc_feature.pctl)) {
> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> + dev_info(info->dev,
> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
"Missing" probably
> + }
> +
> + if (info->vref)
> + info->vref_uv = regulator_get_voltage(info->vref);
> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> + dev_err(info->dev,
> + "Miss adc-vref property or vref regulator in the DT.\n");
> +
> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> + &info->adc_feature.clk_div_num)) {
> + info->adc_feature.clk_div_num = 2;
> + dev_info(info->dev,
> + "Miss adc-clk-div in dt, set divider to 2.\n");
> + }
> +
> + if (of_property_read_u32(np, "fsl,adc-res",
> + &info->adc_feature.res_mode)) {
> + info->adc_feature.res_mode = 12;
> + dev_info(info->dev,
> + "Miss adc-res property in dt, use 8bit mode.\n");
> + }
> +
> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> + &info->adc_feature.sam_time)) {
> + info->adc_feature.sam_time = 4;
> + dev_info(info->dev,
> + "Miss adc-sam-time property in dt, set to 4.\n");
> + }
> +
> + info->adc_feature.hw_average = of_property_read_bool(np,
> + "fsl,adc-hw-aver-en");
> + if (info->adc_feature.hw_average &&
> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> + &info->adc_feature.hw_sam)) {
> + info->adc_feature.hw_sam = 4;
> + dev_info(info->dev,
> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> + }
> +
> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-mode");
> + info->adc_feature.hs_oper = of_property_read_bool(np, "fsl,adc-high-speed-conv");
> +}
> +
> +static inline void vf610_adc_cfg_post_set(struct vf610_adc *info)
> +{
> + struct vf610_adc_feature *adc_feature = &(info->adc_feature);
no parenthesis needed
> + int cfg_data = 0;
> + int gc_data = 0;
> +
> + /* clock select and clock devider */
> + switch (adc_feature->clk_div_num) {
> + case 1:
> + break;
> + case 2:
> + cfg_data |= VF610_ADC_CLK_DIV2;
> + break;
> + case 4:
> + cfg_data |= VF610_ADC_CLK_DIV4;
> + break;
> + case 8:
> + cfg_data |= VF610_ADC_CLK_DIV8;
> + break;
> + case 16:
> + switch (adc_feature->clk_sel) {
> + case ADCIOC_BUSCLK_SET:
> + cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8;
> + break;
> + default:
> + dev_err(info->dev, "error clk divider\n");
all those errors just logged and then ignored;
shouldn't this be warnings then or the handling improved?
> + break;
> + }
> + break;
> + }
> +
> + switch (adc_feature->clk_sel) {
> + case ADCIOC_ALTCLK_SET:
> + cfg_data |= VF610_ADC_ALTCLK_SEL;
> + break;
> + case ADCIOC_ADACK_SET:
> + cfg_data |= VF610_ADC_ADACK_SEL;
> + break;
> + default:
> + break;
> + }
> +
> + /* resolution mode */
> + switch (adc_feature->res_mode) {
> + case 8:
> + cfg_data |= VF610_ADC_MODE_BIT8;
> + break;
> + case 10:
> + cfg_data |= VF610_ADC_MODE_BIT10;
> + break;
> + case 12:
> + cfg_data |= VF610_ADC_MODE_BIT12;
> + break;
> + default:
> + dev_err(info->dev, "error resolution mode\n");
> + break;
> + }
> +
> + /* Defines the sample time duration */
> + switch (adc_feature->sam_time) {
> + case 2:
> + break;
> + case 4:
> + cfg_data |= VF610_ADC_ADSTS_SHORT;
> + break;
> + case 6:
> + cfg_data |= VF610_ADC_ADSTS_NORMAL;
> + break;
> + case 8:
> + cfg_data |= VF610_ADC_ADSTS_LONG;
> + break;
> + case 12:
> + cfg_data |= VF610_ADC_ADLSMP_LONG;
> + break;
> + case 16:
> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_SHORT;
> + break;
> + case 20:
> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_NORMAL;
> + break;
> + case 24:
> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_LONG;
> + break;
> + default:
> + dev_err(info->dev, "error sample duration\n");
> + break;
> + }
> +
> + /* low power configuration */
> + cfg_data |= VF610_ADC_ADLPC_EN;
> +
> + /* high speed operation */
> + cfg_data |= VF610_ADC_ADHSC_EN;
> +
> + /* voltage reference*/
blank before */
> + switch (adc_feature->vol_ref) {
> + case VF610_ADCIOC_VR_VREF_SET:
> + break;
> + case VF610_ADCIOC_VR_VALT_SET:
> + cfg_data |= VF610_ADC_REFSEL_VALT;
> + break;
> + case VF610_ADCIOC_VR_VBG_SET:
> + cfg_data |= VF610_ADC_REFSEL_VBG;
> + break;
> + default:
> + dev_err(info->dev, "error voltage reference\n");
> + }
> +
> + /* trigger select */
> + if (adc_feature->tri_hw)
> + cfg_data |= VF610_ADC_ADTRG_HARD;
> +
> + /* hardware average select */
> + if (adc_feature->hw_average) {
> + switch (adc_feature->hw_sam) {
> + case 4:
> + break;
> + case 8:
> + cfg_data |= VF610_ADC_AVGS_8;
> + break;
> + case 16:
> + cfg_data |= VF610_ADC_AVGS_16;
> + break;
> + case 32:
> + cfg_data |= VF610_ADC_AVGS_32;
> + break;
> + default:
> + dev_err(info->dev,
> + "error hardware sample average select\n");
> + }
> +
> + gc_data |= VF610_ADC_AVGEN;
> + }
> +
> + /* data overwrite enable */
> + if (adc_feature->dataov_en)
> + cfg_data |= VF610_ADC_OVWREN;
> +
> + /* Asynchronous clock output enable */
> + if (adc_feature->ac_clk_en)
> + gc_data |= VF610_ADC_ADACKEN;
> +
> + /* dma enable */
> + if (adc_feature->dma_en)
> + gc_data |= VF610_ADC_DMAEN;
> +
> + /* continue function enable */
> + if (adc_feature->cc_en)
> + gc_data |= VF610_ADC_ADCON;
> +
> + /* compare function enable */
> + if (adc_feature->cmp_func_en)
> + gc_data |= VF610_ADC_ACFE;
> +
> + /* greater than enable */
> + if (adc_feature->cmp_greater_en)
> + gc_data |= VF610_ADC_ACFGT;
> +
> + /* range enable */
> + if (adc_feature->cmp_range_en)
> + gc_data |= VF610_ADC_ACREN;
> +
> + writel(cfg_data, info->regs + ADC_CFG);
> + writel(gc_data, info->regs + ADC_GC);
> +}
> +
> +static void vf610_adc_calibration(struct vf610_adc *info)
> +{
> + int adc_gc, hc_cfg;
> + int timeout;
> +
> + if (!info->adc_feature.calibration)
> + return;
> +
> + /* enable calibration interrupt */
> + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
> + writel(hc_cfg, info->regs + ADC_HC0);
> +
> + adc_gc = readl(info->regs + ADC_GC);
> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
> +
> + timeout = wait_for_completion_interruptible_timeout
> + (&info->completion, VF610_ADC_TIMEOUT);
> + if (timeout == 0)
> + dev_err(info->dev, "Timeout for adc calibration\n");
error ignored
> +
> + adc_gc = readl(info->regs + ADC_GS);
> + if (adc_gc & VF610_ADC_CALF)
> + dev_err(info->dev, "ADC calibration failed\n");
> +
> + info->adc_feature.calibration = false;
> +}
> +
> +static inline void vf610_adc_cfg_set(struct vf610_adc *info)
> +{
why are some functions marked inline and others not?
this function does not seem time critical
> + struct vf610_adc_feature *adc_feature = &(info->adc_feature);
> + int cfg_data = 0;
> +
> + cfg_data = readl(info->regs + ADC_CFG);
> +
> + /* low power configuration */
> + if (!adc_feature->lpm)
> + cfg_data &= ~VF610_ADC_ADLPC_EN;
> +
> + /* high speed operation */
> + if (!adc_feature->hs_oper)
> + cfg_data &= ~VF610_ADC_ADHSC_EN;
> +
> + /* trigger select */
> + if (adc_feature->tri_hw)
> + cfg_data |= VF610_ADC_ADTRG_HARD;
> +
> + writel(cfg_data, info->regs + ADC_CFG);
> +}
> +
> +static void vf610_adc_hw_init(struct vf610_adc *info)
> +{
> + /* pin control for Sliding rheostat */
> + writel(info->adc_feature.pctl, info->regs + ADC_PCTL);
> +
> + /* CFG: Feature set */
> + vf610_adc_cfg_post_set(info);
> +
> + /* adc calibration */
> + vf610_adc_calibration(info);
> +
> + /* CFG: power and speed set */
> + vf610_adc_cfg_set(info);
> +}
> +
> +static inline int vf610_adc_read_data(struct vf610_adc *info)
> +{
> + int result;
> +
> + result = readl(info->regs + ADC_R0);
> +
> + switch (info->adc_feature.res_mode) {
maybe a lookup table could be used; value of res_mode is trusted at this
point, no error checking needed
> + case 8:
> + result &= VF610_ADC_DATA_BIT8_MASK;
> + break;
> + case 10:
> + result &= VF610_ADC_DATA_BIT10_MASK;
> + break;
> + case 12:
> + result &= VF610_ADC_DATA_BIT12_MASK;
> + break;
> + default:
> + dev_err(info->dev, "error resolution mode\n");
> + break;
> + }
> +
> + return result;
> +}
> +
> +static irqreturn_t vf610_adc_isr(int irq, void *dev_id)
> +{
> + struct vf610_adc *info = (struct vf610_adc *)dev_id;
> + int coco;
> +
> + coco = readl(info->regs + ADC_HS);
> + if (coco & VF610_ADC_HS_COCO0) {
> + info->value = vf610_adc_read_data(info);
> + complete(&info->completion);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int vf610_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val,
> + int *val2,
> + long mask)
> +{
> + struct vf610_adc *info = iio_priv(indio_dev);
> + unsigned int hc_cfg;
> + unsigned long timeout;
> +
> + /* Check for invalid channel */
> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
> + return -EINVAL;
should never happen
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + mutex_lock(&indio_dev->mlock);
> +
> + hc_cfg = VF610_ADC_ADCHC(chan->channel);
> + hc_cfg |= VF610_ADC_AIEN;
> + writel(hc_cfg, info->regs + ADC_HC0);
> + timeout = wait_for_completion_interruptible_timeout
> + (&info->completion, VF610_ADC_TIMEOUT);
> + *val = info->value;
> +
> + mutex_unlock(&indio_dev->mlock);
> +
> + if (timeout == 0)
> + return -ETIMEDOUT;
> +
> + return IIO_VAL_INT;
> +
> + case IIO_CHAN_INFO_SCALE:
> + *val = info->vref_uv >> info->adc_feature.res_mode;
> + *val2 = 0;
use IIO_VAL_FRACTIONAL_LOG2 probably
> + return IIO_VAL_INT_PLUS_MICRO;
> + default:
> + break;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int vf610_adc_reg_access(struct iio_dev *indio_dev,
> + unsigned reg, unsigned writeval,
> + unsigned *readval)
> +{
> + struct vf610_adc *info = iio_priv(indio_dev);
> +
> + if (readval == NULL)
> + return -EINVAL;
can this ever happen?
I'd rather check the value of reg
> +
> + *readval = readl(info->regs + reg);
> +
> + return 0;
> +}
> +
> +static const struct iio_info vf610_adc_iio_info = {
> + .read_raw = &vf610_read_raw,
> + .debugfs_reg_access = &vf610_adc_reg_access,
> + .driver_module = THIS_MODULE,
> +};
> +
> +static const struct of_device_id vf610_adc_match[] = {
> + { .compatible = "fsl,vf610-adc", },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, vf610_adc_dt_ids);
> +
> +static int vf610_adc_probe(struct platform_device *pdev)
> +{
> + struct vf610_adc *info = NULL;
> + struct iio_dev *indio_dev;
> + struct resource *mem;
> + int ret = -ENODEV;
initialization of ret and info not needed
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
> + 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))
> + return PTR_ERR(info->regs);
> +
> + info->irq = platform_get_irq(pdev, 0);
> + if (info->irq < 0) {
> + dev_err(&pdev->dev, "no irq resource?\n");
> + return -EINVAL;
> + }
> +
> + ret = devm_request_irq(info->dev, info->irq,
> + vf610_adc_isr, 0,
> + dev_name(&pdev->dev), info);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
> + info->irq);
> + return ret;
> + }
> +
> + info->clk = devm_clk_get(&pdev->dev, "adc");
> + if (IS_ERR(info->clk)) {
> + dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
> + PTR_ERR(info->clk));
> + ret = PTR_ERR(info->clk);
> + return ret;
> + }
> +
> + info->vref = devm_regulator_get(&pdev->dev, "vref");
> + if (!IS_ERR(info->vref)) {
> + ret = regulator_enable(info->vref);
> + if (ret) {
> + dev_err(&pdev->dev,
> + "Failed to enable vref regulator: %d\n", ret);
> + return ret;
> + }
> + } else {
> + info->vref = NULL;
> + }
> +
> + platform_set_drvdata(pdev, indio_dev);
> +
> + init_completion(&info->completion);
> +
> + 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 = &vf610_adc_iio_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->channels = vf610_adc_iio_channels;
> + indio_dev->num_channels = ARRAY_SIZE(vf610_adc_iio_channels);
> +
> + ret = devm_iio_device_register(&pdev->dev, indio_dev);
> + if (ret)
regulator stays enabled
> + return ret;
> +
> + ret = clk_prepare_enable(info->clk);
> + if (ret) {
> + dev_err(&pdev->dev,
> + "Could not prepare or enable the clock.\n");
> + return ret;
> + }
> +
> + vf610_adc_cfg_of_init(info);
> + vf610_adc_hw_init(info);
> +
> + return 0;
> +}
> +
> +static int vf610_adc_remove(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> + struct vf610_adc *info = iio_priv(indio_dev);
> +
> + if (info->vref)
> + regulator_disable(info->vref);
> + clk_disable_unprepare(info->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int vf610_adc_suspend(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct vf610_adc *info = iio_priv(indio_dev);
> + int hc_cfg;
> +
> + /* enter to stop mode */
comment does not add to clarity of the code, remove?
> + hc_cfg = readl(info->regs + ADC_HC0);
> + hc_cfg |= VF610_ADC_CONV_DISABLE;
> + writel(hc_cfg, info->regs + ADC_HC0);
> +
> + clk_disable_unprepare(info->clk);
> + if (info->vref)
> + regulator_disable(info->vref);
> +
> + return 0;
> +}
> +
> +static int vf610_adc_resume(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct vf610_adc *info = iio_priv(indio_dev);
> + int ret;
> +
> + if (info->vref) {
> + ret = regulator_enable(info->vref);
> + if (ret)
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(info->clk);
> + if (ret)
> + return ret;
> +
> + vf610_adc_hw_init(info);
> +
> + return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops,
> + vf610_adc_suspend,
> + vf610_adc_resume);
> +
> +static struct platform_driver vf610_adc_driver = {
> + .probe = vf610_adc_probe,
> + .remove = vf610_adc_remove,
> + .driver = {
> + .name = DRIVER_NAME,
> + .owner = THIS_MODULE,
> + .of_match_table = vf610_adc_match,
> + .pm = &vf610_adc_pm_ops,
> + },
> +};
> +
> +module_platform_driver(vf610_adc_driver);
> +
> +MODULE_AUTHOR("Fugang Duan <B38611@freescale.com>");
> +MODULE_DESCRIPTION("Freescale VF610 ADC driver");
> +MODULE_LICENSE("GPL v2");
>
--
Peter Meerwald
+43-664-2444418 (mobile)
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
2013-11-26 12:24 ` Peter Meerwald
@ 2013-11-26 12:49 ` Lars-Peter Clausen
2013-11-27 2:57 ` Fugang Duan
1 sibling, 0 replies; 41+ messages in thread
From: Lars-Peter Clausen @ 2013-11-26 12:49 UTC (permalink / raw)
To: Peter Meerwald; +Cc: Fugang Duan, jic23, shawn.guo, linux-iio
On 11/26/2013 01:24 PM, Peter Meerwald wrote:
>> +static inline void vf610_adc_cfg_set(struct vf610_adc *info)
>> +{
>
> why are some functions marked inline and others not?
> this function does not seem time critical
And the compiler knows much better which functions to (partially) inline and
which not, I think inline is mostly ignored by the compiler anyway, so yes,
it should be removed here.
>> +static int vf610_adc_reg_access(struct iio_dev *indio_dev,
>> + unsigned reg, unsigned writeval,
>> + unsigned *readval)
>> +{
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> +
>> + if (readval == NULL)
>> + return -EINVAL;
>
> can this ever happen?
>
> I'd rather check the value of reg
It is NULL when writing to a register.
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
2013-11-26 12:24 ` Peter Meerwald
2013-11-26 12:49 ` Lars-Peter Clausen
@ 2013-11-27 2:57 ` Fugang Duan
2013-11-27 7:17 ` Peter Meerwald
1 sibling, 1 reply; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 2:57 UTC (permalink / raw)
To: Peter Meerwald
Cc: jic23@kernel.org, shawn.guo@linaro.org, linux-iio@vger.kernel.org
From: Peter Meerwald <pmeerw@pmeerw.net>
Data: Tuesday, November 26, 2013 8:24 PM
>To: Duan Fugang-B38611
>Cc: jic23@kernel.org; shawn.guo@linaro.org; linux-iio@vger.kernel.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
>
>On Tue, 26 Nov 2013, Fugang Duan wrote:
>
>> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> software trigger.
>
>nitpicking below
>
>> Signed-off-by: Fugang Duan <B38611@freescale.com>
>> ---
>> drivers/iio/adc/Kconfig | 11 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/vf610_adc.c | 744
>> +++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 756 insertions(+), 0 deletions(-)
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index
>> 2209f28..d0476ec 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
>> Say yes here to access the ADC part of the Nano River
>> Technologies Viperboard.
>>
>> +config VF610_ADC
>
>alphabetic order please
Got it. Thanks.
>
>> + tristate "Freescale vf610 ADC driver"
>> + help
>> + Say yes here if you want support for the Vybrid board
>> + analog-to-digital converter.
>> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the
>driver
>> + also support the subsequent chips.
>> +
>> + This driver can also be built as a module. If so, the module will be
>> + called vf610_adc.
>> +
>> endmenu
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index
>> ba9a10a..c39ae38 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>> obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
>> obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
>> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
>> +obj-$(CONFIG_VF610_ADC) += vf610_adc.o
>
>keep alphabetic order
Got it. Thanks.
>
>> diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
>> new file mode 100644 index 0000000..0c200d91
>> --- /dev/null
>> +++ b/drivers/iio/adc/vf610_adc.c
>> @@ -0,0 +1,744 @@
>> +/*
>> + * Freescale Vybrid vf610 ADC driver
>> + *
>> + * Copyright 2013 Freescale Semiconductor, Inc.
>> + *
>> + * This program is free software; you can redistribute it and/or
>> +modify
>> + * it under the terms of the GNU General Public License as published
>> +by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that 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, write to the Free Software
>> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/kernel.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/regulator/consumer.h> #include <linux/of_platform.h>
>> +#include <linux/err.h>
>> +
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/iio/driver.h>
>> +
>> +/* This will be the driver name the kernel reports */ #define
>> +DRIVER_NAME "vf610-adc"
>> +
>> +/* Vybrid/IMX ADC registers */
>
>VF610_ prefix
Hmm, I think it is not necessary to add prefix for the register address.
>
>> +#define ADC_HC0 0x00
>> +#define ADC_HC1 0x04
>> +#define ADC_HS 0x08
>> +#define ADC_R0 0x0c
>> +#define ADC_R1 0x10
>> +#define ADC_CFG 0x14
>> +#define ADC_GC 0x18
>> +#define ADC_GS 0x1c
>> +#define ADC_CV 0x20
>> +#define ADC_OFS 0x24
>> +#define ADC_CAL 0x28
>> +#define ADC_PCTL 0x30
>> +
>> +/* Configuration register field define */
>> +#define VF610_ADC_MODE_BIT8 0x00
>> +#define VF610_ADC_MODE_BIT10 0x04
>> +#define VF610_ADC_MODE_BIT12 0x08
>> +#define VF610_ADC_DATA_BIT8_MASK 0xFF
>> +#define VF610_ADC_DATA_BIT10_MASK 0x3FF
>> +#define VF610_ADC_DATA_BIT12_MASK 0xFFF
>> +#define VF610_ADC_BUSCLK2_SEL 0x01
>> +#define VF610_ADC_ALTCLK_SEL 0x02
>> +#define VF610_ADC_ADACK_SEL 0x03
>> +#define VF610_ADC_CLK_DIV2 0x20
>> +#define VF610_ADC_CLK_DIV4 0x40
>> +#define VF610_ADC_CLK_DIV8 0x60
>> +#define VF610_ADC_ADLSMP_LONG 0x10
>> +#define VF610_ADC_ADSTS_SHORT 0x100
>> +#define VF610_ADC_ADSTS_NORMAL 0x200
>> +#define VF610_ADC_ADSTS_LONG 0x300
>> +#define VF610_ADC_ADLPC_EN 0x80
>> +#define VF610_ADC_ADHSC_EN 0x400
>> +#define VF610_ADC_REFSEL_VALT 0x100
>> +#define VF610_ADC_REFSEL_VBG 0x1000
>> +#define VF610_ADC_ADTRG_HARD 0x2000
>> +#define VF610_ADC_AVGS_8 0x4000
>> +#define VF610_ADC_AVGS_16 0x8000
>> +#define VF610_ADC_AVGS_32 0xC000
>> +#define VF610_ADC_OVWREN 0x10000
>> +
>> +/* General control register field define */
>> +#define VF610_ADC_ADACKEN 0x1
>> +#define VF610_ADC_DMAEN 0x2
>> +#define VF610_ADC_ACREN 0x4
>> +#define VF610_ADC_ACFGT 0x8
>> +#define VF610_ADC_ACFE 0x10
>> +#define VF610_ADC_AVGEN 0x20
>> +#define VF610_ADC_ADCON 0x40
>> +#define VF610_ADC_CAL 0x80
>> +
>> +/* Other field define */
>> +#define VF610_ADC_IOPCTL5 0x20
>> +#define VF610_ADC_ADCHC(x) ((x) & 0xF)
>> +#define VF610_ADC_AIEN (1 << 7)
>> +#define VF610_ADC_CONV_DISABLE 0x1F
>> +#define VF610_ADC_HS_COCO0 0x1
>> +#define VF610_ADC_CALF 0x2
>> +#define VF610_ADC_MAX_CHANS_NUM 16
>> +#define VF610_ADC_TIMEOUT (msecs_to_jiffies(100))
>
>no parenthesis needed
Got it, thanks!
>
>> +
>> +enum clk_sel {
>> + ADCIOC_BUSCLK_SET,
>> + ADCIOC_ALTCLK_SET,
>> + ADCIOC_ADACK_SET,
>
>VF610 prefix
Got it, thanks!
>
>> +};
>> +
>> +enum vol_ref {
>> + VF610_ADCIOC_VR_VREF_SET,
>> + VF610_ADCIOC_VR_VALT_SET,
>> + VF610_ADCIOC_VR_VBG_SET,
>> +};
>> +
>> +struct vf610_adc_feature {
>> + enum clk_sel clk_sel;
>> + enum vol_ref vol_ref;
>> +
>> + int pctl;
>> + int clk_div_num;
>> + int res_mode;
>> + int sam_time;
>> + int hw_sam;
>> +
>> + bool calibration;
>> + bool cc_en;
>> + bool dataov_en;
>> + bool hw_average;
>> + bool tri_hw;
>> + bool hs_oper;
>> + bool lpm;
>> + bool dma_en;
>> + bool ac_clk_en;
>> + bool cmp_func_en;
>> + bool cmp_range_en;
>> + bool cmp_greater_en;
>> +};
>> +
>> +struct vf610_adc {
>> + struct device *dev;
>> + void __iomem *regs;
>> + struct clk *clk;
>> + unsigned int irq;
>> +
>> + u32 vref_uv;
>> + u32 value;
>> + struct regulator *vref;
>> + struct vf610_adc_feature adc_feature;
>> +
>> + struct completion completion;
>> +};
>> +
>> +#define VF610_ADC_CHAN(_idx, _chan_type) { \
>> + .type = (_chan_type), \
>> + .indexed = 1, \
>> + .channel = _idx, \
>> + .address = _idx, \
>
>address not needed
Got it, thanks!
>
>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>> +}
>> +
>> +static const struct iio_chan_spec vf610_adc_iio_channels[] = {
>> + VF610_ADC_CHAN(0, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(1, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(2, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(3, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(4, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(5, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(6, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(7, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(8, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(9, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(10, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(11, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(12, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(13, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(14, IIO_VOLTAGE),
>> + VF610_ADC_CHAN(15, IIO_VOLTAGE),
>> +};
>> +
>> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> + struct device_node *np = info->dev->of_node;
>> +
>> + /*
>> + * set default Configuration for ADC controller
>> + * This config may upgrade to require from DT
>
>what does "This config may upgrade to require from DT" mean?
There many configurations for ADC, and I have import some setting from DT, below some setting is static defined.
In fact, all setting can get from DT.
I don't want to static define the ADC setting, expect for getting them from DT, how to config them in runtime for user ?
IIO subsystem don't have such interface for it. I think the only solution is to add ioctl interface for the driver. How do you think ?
>
>> + */
>> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
>> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
>> + info->adc_feature.calibration = true;
>> + info->adc_feature.tri_hw = false;
>> + info->adc_feature.dataov_en = true;
>> + info->adc_feature.ac_clk_en = false;
>> + info->adc_feature.dma_en = false;
>> + info->adc_feature.cc_en = false;
>> + info->adc_feature.cmp_func_en = false;
>> + info->adc_feature.cmp_range_en = false;
>> + info->adc_feature.cmp_greater_en = false;
>> +
>> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> + &info->adc_feature.pctl)) {
>> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
>> + dev_info(info->dev,
>> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>
>"Missing" probably
>
Got it, thanks!
>> + }
>> +
>> + if (info->vref)
>> + info->vref_uv = regulator_get_voltage(info->vref);
>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>> + dev_err(info->dev,
>> + "Miss adc-vref property or vref regulator in the DT.\n");
>> +
>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> + &info->adc_feature.clk_div_num)) {
>> + info->adc_feature.clk_div_num = 2;
>> + dev_info(info->dev,
>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> + }
>> +
>> + if (of_property_read_u32(np, "fsl,adc-res",
>> + &info->adc_feature.res_mode)) {
>> + info->adc_feature.res_mode = 12;
>> + dev_info(info->dev,
>> + "Miss adc-res property in dt, use 8bit mode.\n");
>> + }
>> +
>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> + &info->adc_feature.sam_time)) {
>> + info->adc_feature.sam_time = 4;
>> + dev_info(info->dev,
>> + "Miss adc-sam-time property in dt, set to 4.\n");
>> + }
>> +
>> + info->adc_feature.hw_average = of_property_read_bool(np,
>> + "fsl,adc-hw-aver-en");
>> + if (info->adc_feature.hw_average &&
>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> + &info->adc_feature.hw_sam)) {
>> + info->adc_feature.hw_sam = 4;
>> + dev_info(info->dev,
>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>> + }
>> +
>> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
>mode");
>> + info->adc_feature.hs_oper = of_property_read_bool(np,
>> +"fsl,adc-high-speed-conv"); }
>> +
>> +static inline void vf610_adc_cfg_post_set(struct vf610_adc *info) {
>> + struct vf610_adc_feature *adc_feature = &(info->adc_feature);
>
>no parenthesis needed
Got it , thanks!
>
>> + int cfg_data = 0;
>> + int gc_data = 0;
>> +
>> + /* clock select and clock devider */
>> + switch (adc_feature->clk_div_num) {
>> + case 1:
>> + break;
>> + case 2:
>> + cfg_data |= VF610_ADC_CLK_DIV2;
>> + break;
>> + case 4:
>> + cfg_data |= VF610_ADC_CLK_DIV4;
>> + break;
>> + case 8:
>> + cfg_data |= VF610_ADC_CLK_DIV8;
>> + break;
>> + case 16:
>> + switch (adc_feature->clk_sel) {
>> + case ADCIOC_BUSCLK_SET:
>> + cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8;
>> + break;
>> + default:
>> + dev_err(info->dev, "error clk divider\n");
>
>all those errors just logged and then ignored; shouldn't this be warnings then
>or the handling improved?
If the setting is error, use the default setting, and print out err log for user.
In there, it use the default setting in fact.
>
>> + break;
>> + }
>> + break;
>> + }
>> +
>> + switch (adc_feature->clk_sel) {
>> + case ADCIOC_ALTCLK_SET:
>> + cfg_data |= VF610_ADC_ALTCLK_SEL;
>> + break;
>> + case ADCIOC_ADACK_SET:
>> + cfg_data |= VF610_ADC_ADACK_SEL;
>> + break;
>> + default:
>> + break;
>> + }
>> +
>> + /* resolution mode */
>> + switch (adc_feature->res_mode) {
>> + case 8:
>> + cfg_data |= VF610_ADC_MODE_BIT8;
>> + break;
>> + case 10:
>> + cfg_data |= VF610_ADC_MODE_BIT10;
>> + break;
>> + case 12:
>> + cfg_data |= VF610_ADC_MODE_BIT12;
>> + break;
>> + default:
>> + dev_err(info->dev, "error resolution mode\n");
>> + break;
>> + }
>> +
>> + /* Defines the sample time duration */
>> + switch (adc_feature->sam_time) {
>> + case 2:
>> + break;
>> + case 4:
>> + cfg_data |= VF610_ADC_ADSTS_SHORT;
>> + break;
>> + case 6:
>> + cfg_data |= VF610_ADC_ADSTS_NORMAL;
>> + break;
>> + case 8:
>> + cfg_data |= VF610_ADC_ADSTS_LONG;
>> + break;
>> + case 12:
>> + cfg_data |= VF610_ADC_ADLSMP_LONG;
>> + break;
>> + case 16:
>> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_SHORT;
>> + break;
>> + case 20:
>> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_NORMAL;
>> + break;
>> + case 24:
>> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_LONG;
>> + break;
>> + default:
>> + dev_err(info->dev, "error sample duration\n");
>> + break;
>> + }
>> +
>> + /* low power configuration */
>> + cfg_data |= VF610_ADC_ADLPC_EN;
>> +
>> + /* high speed operation */
>> + cfg_data |= VF610_ADC_ADHSC_EN;
>> +
>> + /* voltage reference*/
>
>blank before */
You are very careful, thanks!
>
>> + switch (adc_feature->vol_ref) {
>> + case VF610_ADCIOC_VR_VREF_SET:
>> + break;
>> + case VF610_ADCIOC_VR_VALT_SET:
>> + cfg_data |= VF610_ADC_REFSEL_VALT;
>> + break;
>> + case VF610_ADCIOC_VR_VBG_SET:
>> + cfg_data |= VF610_ADC_REFSEL_VBG;
>> + break;
>> + default:
>> + dev_err(info->dev, "error voltage reference\n");
>> + }
>> +
>> + /* trigger select */
>> + if (adc_feature->tri_hw)
>> + cfg_data |= VF610_ADC_ADTRG_HARD;
>> +
>> + /* hardware average select */
>> + if (adc_feature->hw_average) {
>> + switch (adc_feature->hw_sam) {
>> + case 4:
>> + break;
>> + case 8:
>> + cfg_data |= VF610_ADC_AVGS_8;
>> + break;
>> + case 16:
>> + cfg_data |= VF610_ADC_AVGS_16;
>> + break;
>> + case 32:
>> + cfg_data |= VF610_ADC_AVGS_32;
>> + break;
>> + default:
>> + dev_err(info->dev,
>> + "error hardware sample average select\n");
>> + }
>> +
>> + gc_data |= VF610_ADC_AVGEN;
>> + }
>> +
>> + /* data overwrite enable */
>> + if (adc_feature->dataov_en)
>> + cfg_data |= VF610_ADC_OVWREN;
>> +
>> + /* Asynchronous clock output enable */
>> + if (adc_feature->ac_clk_en)
>> + gc_data |= VF610_ADC_ADACKEN;
>> +
>> + /* dma enable */
>> + if (adc_feature->dma_en)
>> + gc_data |= VF610_ADC_DMAEN;
>> +
>> + /* continue function enable */
>> + if (adc_feature->cc_en)
>> + gc_data |= VF610_ADC_ADCON;
>> +
>> + /* compare function enable */
>> + if (adc_feature->cmp_func_en)
>> + gc_data |= VF610_ADC_ACFE;
>> +
>> + /* greater than enable */
>> + if (adc_feature->cmp_greater_en)
>> + gc_data |= VF610_ADC_ACFGT;
>> +
>> + /* range enable */
>> + if (adc_feature->cmp_range_en)
>> + gc_data |= VF610_ADC_ACREN;
>> +
>> + writel(cfg_data, info->regs + ADC_CFG);
>> + writel(gc_data, info->regs + ADC_GC); }
>> +
>> +static void vf610_adc_calibration(struct vf610_adc *info) {
>> + int adc_gc, hc_cfg;
>> + int timeout;
>> +
>> + if (!info->adc_feature.calibration)
>> + return;
>> +
>> + /* enable calibration interrupt */
>> + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
>> + writel(hc_cfg, info->regs + ADC_HC0);
>> +
>> + adc_gc = readl(info->regs + ADC_GC);
>> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
>> +
>> + timeout = wait_for_completion_interruptible_timeout
>> + (&info->completion, VF610_ADC_TIMEOUT);
>> + if (timeout == 0)
>> + dev_err(info->dev, "Timeout for adc calibration\n");
>
>error ignored
>
Got it, thanks!
There cannot be interrupted for calibration.
So use wait_for_completion_timeout() instead of wait_for_completion_interruptible_timeout().
>> +
>> + adc_gc = readl(info->regs + ADC_GS);
>> + if (adc_gc & VF610_ADC_CALF)
>> + dev_err(info->dev, "ADC calibration failed\n");
>> +
>> + info->adc_feature.calibration = false; }
>> +
>> +static inline void vf610_adc_cfg_set(struct vf610_adc *info) {
>
>why are some functions marked inline and others not?
>this function does not seem time critical
Agree, I will remove the inline.
>
>> + struct vf610_adc_feature *adc_feature = &(info->adc_feature);
>> + int cfg_data = 0;
>> +
>> + cfg_data = readl(info->regs + ADC_CFG);
>> +
>> + /* low power configuration */
>> + if (!adc_feature->lpm)
>> + cfg_data &= ~VF610_ADC_ADLPC_EN;
>> +
>> + /* high speed operation */
>> + if (!adc_feature->hs_oper)
>> + cfg_data &= ~VF610_ADC_ADHSC_EN;
>> +
>> + /* trigger select */
>> + if (adc_feature->tri_hw)
>> + cfg_data |= VF610_ADC_ADTRG_HARD;
>> +
>> + writel(cfg_data, info->regs + ADC_CFG); }
>> +
>> +static void vf610_adc_hw_init(struct vf610_adc *info) {
>> + /* pin control for Sliding rheostat */
>> + writel(info->adc_feature.pctl, info->regs + ADC_PCTL);
>> +
>> + /* CFG: Feature set */
>> + vf610_adc_cfg_post_set(info);
>> +
>> + /* adc calibration */
>> + vf610_adc_calibration(info);
>> +
>> + /* CFG: power and speed set */
>> + vf610_adc_cfg_set(info);
>> +}
>> +
>> +static inline int vf610_adc_read_data(struct vf610_adc *info) {
>> + int result;
>> +
>> + result = readl(info->regs + ADC_R0);
>> +
>> + switch (info->adc_feature.res_mode) {
>
>maybe a lookup table could be used; value of res_mode is trusted at this point,
>no error checking needed
Yes, there don't need to check error.
The entry number is only three, so lookup table is not valuable.
>
>> + case 8:
>> + result &= VF610_ADC_DATA_BIT8_MASK;
>> + break;
>> + case 10:
>> + result &= VF610_ADC_DATA_BIT10_MASK;
>> + break;
>> + case 12:
>> + result &= VF610_ADC_DATA_BIT12_MASK;
>> + break;
>> + default:
>> + dev_err(info->dev, "error resolution mode\n");
>> + break;
>> + }
>> +
>> + return result;
>> +}
>> +
>> +static irqreturn_t vf610_adc_isr(int irq, void *dev_id) {
>> + struct vf610_adc *info = (struct vf610_adc *)dev_id;
>> + int coco;
>> +
>> + coco = readl(info->regs + ADC_HS);
>> + if (coco & VF610_ADC_HS_COCO0) {
>> + info->value = vf610_adc_read_data(info);
>> + complete(&info->completion);
>> + }
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int vf610_read_raw(struct iio_dev *indio_dev,
>> + struct iio_chan_spec const *chan,
>> + int *val,
>> + int *val2,
>> + long mask)
>> +{
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> + unsigned int hc_cfg;
>> + unsigned long timeout;
>> +
>> + /* Check for invalid channel */
>> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
>> + return -EINVAL;
>
>should never happen
>
Got it. Thanks!
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + mutex_lock(&indio_dev->mlock);
>> +
>> + hc_cfg = VF610_ADC_ADCHC(chan->channel);
>> + hc_cfg |= VF610_ADC_AIEN;
>> + writel(hc_cfg, info->regs + ADC_HC0);
>> + timeout = wait_for_completion_interruptible_timeout
>> + (&info->completion, VF610_ADC_TIMEOUT);
>> + *val = info->value;
>> +
>> + mutex_unlock(&indio_dev->mlock);
>> +
>> + if (timeout == 0)
>> + return -ETIMEDOUT;
>> +
>> + return IIO_VAL_INT;
>> +
>> + case IIO_CHAN_INFO_SCALE:
>> + *val = info->vref_uv >> info->adc_feature.res_mode;
>> + *val2 = 0;
>
>use IIO_VAL_FRACTIONAL_LOG2 probably
Yes, it is better to use IIO_VAL_FRACTIONAL_LOG2, thanks!
>
>> + return IIO_VAL_INT_PLUS_MICRO;
>> + default:
>> + break;
>> + }
>> +
>> + return -EINVAL;
>> +}
>> +
>> +static int vf610_adc_reg_access(struct iio_dev *indio_dev,
>> + unsigned reg, unsigned writeval,
>> + unsigned *readval)
>> +{
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> +
>> + if (readval == NULL)
>> + return -EINVAL;
>
>can this ever happen?
No, it never happen. Thanks.
static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct iio_dev *indio_dev = file->private_data;
char buf[20];
unsigned val = 0;
ssize_t len;
int ret;
ret = indio_dev->info->debugfs_reg_access(indio_dev,
indio_dev->cached_reg_addr,
0, &val);
...
}
>
>I'd rather check the value of reg
>> +
>> + *readval = readl(info->regs + reg);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct iio_info vf610_adc_iio_info = {
>> + .read_raw = &vf610_read_raw,
>> + .debugfs_reg_access = &vf610_adc_reg_access,
>> + .driver_module = THIS_MODULE,
>> +};
>> +
>> +static const struct of_device_id vf610_adc_match[] = {
>> + { .compatible = "fsl,vf610-adc", },
>> + { /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, vf610_adc_dt_ids);
>> +
>> +static int vf610_adc_probe(struct platform_device *pdev) {
>> + struct vf610_adc *info = NULL;
>> + struct iio_dev *indio_dev;
>> + struct resource *mem;
>> + int ret = -ENODEV;
>
>initialization of ret and info not needed
Got it.
>
>> +
>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
>> + 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))
>> + return PTR_ERR(info->regs);
>> +
>> + info->irq = platform_get_irq(pdev, 0);
>> + if (info->irq < 0) {
>> + dev_err(&pdev->dev, "no irq resource?\n");
>> + return -EINVAL;
>> + }
>> +
>> + ret = devm_request_irq(info->dev, info->irq,
>> + vf610_adc_isr, 0,
>> + dev_name(&pdev->dev), info);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>> + info->irq);
>> + return ret;
>> + }
>> +
>> + info->clk = devm_clk_get(&pdev->dev, "adc");
>> + if (IS_ERR(info->clk)) {
>> + dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>> + PTR_ERR(info->clk));
>> + ret = PTR_ERR(info->clk);
>> + return ret;
>> + }
>> +
>> + info->vref = devm_regulator_get(&pdev->dev, "vref");
>> + if (!IS_ERR(info->vref)) {
>> + ret = regulator_enable(info->vref);
>> + if (ret) {
>> + dev_err(&pdev->dev,
>> + "Failed to enable vref regulator: %d\n", ret);
>> + return ret;
>> + }
>> + } else {
>> + info->vref = NULL;
>> + }
>> +
>> + platform_set_drvdata(pdev, indio_dev);
>> +
>> + init_completion(&info->completion);
>> +
>> + 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 = &vf610_adc_iio_info;
>> + indio_dev->modes = INDIO_DIRECT_MODE;
>> + indio_dev->channels = vf610_adc_iio_channels;
>> + indio_dev->num_channels = ARRAY_SIZE(vf610_adc_iio_channels);
>> +
>> + ret = devm_iio_device_register(&pdev->dev, indio_dev);
>> + if (ret)
>
>regulator stays enabled
>
>> + return ret;
>> +
>> + ret = clk_prepare_enable(info->clk);
>> + if (ret) {
>> + dev_err(&pdev->dev,
>> + "Could not prepare or enable the clock.\n");
>> + return ret;
>> + }
>> +
>> + vf610_adc_cfg_of_init(info);
>> + vf610_adc_hw_init(info);
>> +
>> + return 0;
>> +}
>> +
>> +static int vf610_adc_remove(struct platform_device *pdev) {
>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> +
>> + if (info->vref)
>> + regulator_disable(info->vref);
>> + clk_disable_unprepare(info->clk);
>> +
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int vf610_adc_suspend(struct device *dev) {
>> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> + int hc_cfg;
>> +
>> + /* enter to stop mode */
>
>comment does not add to clarity of the code, remove?
Below code let ADC enter to stop mode when suspend.
>
>> + hc_cfg = readl(info->regs + ADC_HC0);
>> + hc_cfg |= VF610_ADC_CONV_DISABLE;
>> + writel(hc_cfg, info->regs + ADC_HC0);
>> +
>> + clk_disable_unprepare(info->clk);
>> + if (info->vref)
>> + regulator_disable(info->vref);
>> +
>> + return 0;
>> +}
>> +
>> +static int vf610_adc_resume(struct device *dev) {
>> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
>> + struct vf610_adc *info = iio_priv(indio_dev);
>> + int ret;
>> +
>> + if (info->vref) {
>> + ret = regulator_enable(info->vref);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + ret = clk_prepare_enable(info->clk);
>> + if (ret)
>> + return ret;
>> +
>> + vf610_adc_hw_init(info);
>> +
>> + return 0;
>> +}
>> +#endif
>> +
>> +static SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops,
>> + vf610_adc_suspend,
>> + vf610_adc_resume);
>> +
>> +static struct platform_driver vf610_adc_driver = {
>> + .probe = vf610_adc_probe,
>> + .remove = vf610_adc_remove,
>> + .driver = {
>> + .name = DRIVER_NAME,
>> + .owner = THIS_MODULE,
>> + .of_match_table = vf610_adc_match,
>> + .pm = &vf610_adc_pm_ops,
>> + },
>> +};
>> +
>> +module_platform_driver(vf610_adc_driver);
>> +
>> +MODULE_AUTHOR("Fugang Duan <B38611@freescale.com>");
>> +MODULE_DESCRIPTION("Freescale VF610 ADC driver"); MODULE_LICENSE("GPL
>> +v2");
>>
Peter,
thanks for your review.
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
2013-11-27 2:57 ` Fugang Duan
@ 2013-11-27 7:17 ` Peter Meerwald
2013-11-27 7:30 ` Fugang Duan
0 siblings, 1 reply; 41+ messages in thread
From: Peter Meerwald @ 2013-11-27 7:17 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23@kernel.org, shawn.guo@linaro.org, linux-iio@vger.kernel.org
some clarification below
> >To: Duan Fugang-B38611
> >Cc: jic23@kernel.org; shawn.guo@linaro.org; linux-iio@vger.kernel.org
> >Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
> >
> >On Tue, 26 Nov 2013, Fugang Duan wrote:
> >
> >> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
> >> software trigger.
> >
> >nitpicking below
> >
> >> Signed-off-by: Fugang Duan <B38611@freescale.com>
> >> ---
> >> drivers/iio/adc/Kconfig | 11 +
> >> drivers/iio/adc/Makefile | 1 +
> >> drivers/iio/adc/vf610_adc.c | 744
> >> +++++++++++++++++++++++++++++++++++++++++++
> >> 3 files changed, 756 insertions(+), 0 deletions(-)
> >>
> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index
> >> 2209f28..d0476ec 100644
> >> --- a/drivers/iio/adc/Kconfig
> >> +++ b/drivers/iio/adc/Kconfig
> >> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
> >> Say yes here to access the ADC part of the Nano River
> >> Technologies Viperboard.
> >>
> >> +config VF610_ADC
> >
> >alphabetic order please
>
> Got it. Thanks.
> >
> >> + tristate "Freescale vf610 ADC driver"
> >> + help
> >> + Say yes here if you want support for the Vybrid board
> >> + analog-to-digital converter.
> >> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons, the
> >driver
> >> + also support the subsequent chips.
> >> +
> >> + This driver can also be built as a module. If so, the module will be
> >> + called vf610_adc.
> >> +
> >> endmenu
> >> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index
> >> ba9a10a..c39ae38 100644
> >> --- a/drivers/iio/adc/Makefile
> >> +++ b/drivers/iio/adc/Makefile
> >> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
> >> obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
> >> obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
> >> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
> >> +obj-$(CONFIG_VF610_ADC) += vf610_adc.o
> >
> >keep alphabetic order
>
> Got it. Thanks.
> >
> >> diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
> >> new file mode 100644 index 0000000..0c200d91
> >> --- /dev/null
> >> +++ b/drivers/iio/adc/vf610_adc.c
> >> @@ -0,0 +1,744 @@
> >> +/*
> >> + * Freescale Vybrid vf610 ADC driver
> >> + *
> >> + * Copyright 2013 Freescale Semiconductor, Inc.
> >> + *
> >> + * This program is free software; you can redistribute it and/or
> >> +modify
> >> + * it under the terms of the GNU General Public License as published
> >> +by
> >> + * the Free Software Foundation; either version 2 of the License, or
> >> + * (at your option) any later version.
> >> + *
> >> + * This program is distributed in the hope that 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, write to the Free Software
> >> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> >> + */
> >> +
> >> +#include <linux/module.h>
> >> +#include <linux/platform_device.h>
> >> +#include <linux/interrupt.h>
> >> +#include <linux/delay.h>
> >> +#include <linux/kernel.h>
> >> +#include <linux/slab.h>
> >> +#include <linux/io.h>
> >> +#include <linux/clk.h>
> >> +#include <linux/completion.h>
> >> +#include <linux/of.h>
> >> +#include <linux/of_irq.h>
> >> +#include <linux/regulator/consumer.h> #include <linux/of_platform.h>
> >> +#include <linux/err.h>
> >> +
> >> +#include <linux/iio/iio.h>
> >> +#include <linux/iio/machine.h>
> >> +#include <linux/iio/driver.h>
> >> +
> >> +/* This will be the driver name the kernel reports */ #define
> >> +DRIVER_NAME "vf610-adc"
> >> +
> >> +/* Vybrid/IMX ADC registers */
> >
> >VF610_ prefix
>
> Hmm, I think it is not necessary to add prefix for the register address.
the argument for the prefix is that something in the headers you include
may #define ADC_SOMETHING in the future and cause breakage; prefixing
reduces this risk
plus other define have the prefix, so for the sake of consistency...
> >
> >> +#define ADC_HC0 0x00
> >> +#define ADC_HC1 0x04
> >> +#define ADC_HS 0x08
> >> +#define ADC_R0 0x0c
> >> +#define ADC_R1 0x10
> >> +#define ADC_CFG 0x14
> >> +#define ADC_GC 0x18
> >> +#define ADC_GS 0x1c
> >> +#define ADC_CV 0x20
> >> +#define ADC_OFS 0x24
> >> +#define ADC_CAL 0x28
> >> +#define ADC_PCTL 0x30
> >> +
> >> +/* Configuration register field define */
> >> +#define VF610_ADC_MODE_BIT8 0x00
> >> +#define VF610_ADC_MODE_BIT10 0x04
> >> +#define VF610_ADC_MODE_BIT12 0x08
> >> +#define VF610_ADC_DATA_BIT8_MASK 0xFF
> >> +#define VF610_ADC_DATA_BIT10_MASK 0x3FF
> >> +#define VF610_ADC_DATA_BIT12_MASK 0xFFF
> >> +#define VF610_ADC_BUSCLK2_SEL 0x01
> >> +#define VF610_ADC_ALTCLK_SEL 0x02
> >> +#define VF610_ADC_ADACK_SEL 0x03
> >> +#define VF610_ADC_CLK_DIV2 0x20
> >> +#define VF610_ADC_CLK_DIV4 0x40
> >> +#define VF610_ADC_CLK_DIV8 0x60
> >> +#define VF610_ADC_ADLSMP_LONG 0x10
> >> +#define VF610_ADC_ADSTS_SHORT 0x100
> >> +#define VF610_ADC_ADSTS_NORMAL 0x200
> >> +#define VF610_ADC_ADSTS_LONG 0x300
> >> +#define VF610_ADC_ADLPC_EN 0x80
> >> +#define VF610_ADC_ADHSC_EN 0x400
> >> +#define VF610_ADC_REFSEL_VALT 0x100
> >> +#define VF610_ADC_REFSEL_VBG 0x1000
> >> +#define VF610_ADC_ADTRG_HARD 0x2000
> >> +#define VF610_ADC_AVGS_8 0x4000
> >> +#define VF610_ADC_AVGS_16 0x8000
> >> +#define VF610_ADC_AVGS_32 0xC000
> >> +#define VF610_ADC_OVWREN 0x10000
> >> +
> >> +/* General control register field define */
> >> +#define VF610_ADC_ADACKEN 0x1
> >> +#define VF610_ADC_DMAEN 0x2
> >> +#define VF610_ADC_ACREN 0x4
> >> +#define VF610_ADC_ACFGT 0x8
> >> +#define VF610_ADC_ACFE 0x10
> >> +#define VF610_ADC_AVGEN 0x20
> >> +#define VF610_ADC_ADCON 0x40
> >> +#define VF610_ADC_CAL 0x80
> >> +
> >> +/* Other field define */
> >> +#define VF610_ADC_IOPCTL5 0x20
> >> +#define VF610_ADC_ADCHC(x) ((x) & 0xF)
> >> +#define VF610_ADC_AIEN (1 << 7)
> >> +#define VF610_ADC_CONV_DISABLE 0x1F
> >> +#define VF610_ADC_HS_COCO0 0x1
> >> +#define VF610_ADC_CALF 0x2
> >> +#define VF610_ADC_MAX_CHANS_NUM 16
> >> +#define VF610_ADC_TIMEOUT (msecs_to_jiffies(100))
> >
> >no parenthesis needed
>
> Got it, thanks!
> >
> >> +
> >> +enum clk_sel {
> >> + ADCIOC_BUSCLK_SET,
> >> + ADCIOC_ALTCLK_SET,
> >> + ADCIOC_ADACK_SET,
> >
> >VF610 prefix
>
> Got it, thanks!
> >
> >> +};
> >> +
> >> +enum vol_ref {
> >> + VF610_ADCIOC_VR_VREF_SET,
> >> + VF610_ADCIOC_VR_VALT_SET,
> >> + VF610_ADCIOC_VR_VBG_SET,
> >> +};
> >> +
> >> +struct vf610_adc_feature {
> >> + enum clk_sel clk_sel;
> >> + enum vol_ref vol_ref;
> >> +
> >> + int pctl;
> >> + int clk_div_num;
> >> + int res_mode;
> >> + int sam_time;
> >> + int hw_sam;
> >> +
> >> + bool calibration;
> >> + bool cc_en;
> >> + bool dataov_en;
> >> + bool hw_average;
> >> + bool tri_hw;
> >> + bool hs_oper;
> >> + bool lpm;
> >> + bool dma_en;
> >> + bool ac_clk_en;
> >> + bool cmp_func_en;
> >> + bool cmp_range_en;
> >> + bool cmp_greater_en;
> >> +};
> >> +
> >> +struct vf610_adc {
> >> + struct device *dev;
> >> + void __iomem *regs;
> >> + struct clk *clk;
> >> + unsigned int irq;
> >> +
> >> + u32 vref_uv;
> >> + u32 value;
> >> + struct regulator *vref;
> >> + struct vf610_adc_feature adc_feature;
> >> +
> >> + struct completion completion;
> >> +};
> >> +
> >> +#define VF610_ADC_CHAN(_idx, _chan_type) { \
> >> + .type = (_chan_type), \
> >> + .indexed = 1, \
> >> + .channel = _idx, \
> >> + .address = _idx, \
> >
> >address not needed
>
> Got it, thanks!
> >
> >> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> >> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> >> +}
> >> +
> >> +static const struct iio_chan_spec vf610_adc_iio_channels[] = {
> >> + VF610_ADC_CHAN(0, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(1, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(2, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(3, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(4, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(5, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(6, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(7, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(8, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(9, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(10, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(11, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(12, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(13, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(14, IIO_VOLTAGE),
> >> + VF610_ADC_CHAN(15, IIO_VOLTAGE),
> >> +};
> >> +
> >> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
> >> + struct device_node *np = info->dev->of_node;
> >> +
> >> + /*
> >> + * set default Configuration for ADC controller
> >> + * This config may upgrade to require from DT
> >
> >what does "This config may upgrade to require from DT" mean?
>
> There many configurations for ADC, and I have import some setting from DT, below some setting is static defined.
> In fact, all setting can get from DT.
>
> I don't want to static define the ADC setting, expect for getting them from DT, how to config them in runtime for user ?
> IIO subsystem don't have such interface for it. I think the only solution is to add ioctl interface for the driver. How do you think ?
I was just picking on the wording; I fail to understand what is meant by
the comment
> >> + */
> >> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> >> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> >> + info->adc_feature.calibration = true;
> >> + info->adc_feature.tri_hw = false;
> >> + info->adc_feature.dataov_en = true;
> >> + info->adc_feature.ac_clk_en = false;
> >> + info->adc_feature.dma_en = false;
> >> + info->adc_feature.cc_en = false;
> >> + info->adc_feature.cmp_func_en = false;
> >> + info->adc_feature.cmp_range_en = false;
> >> + info->adc_feature.cmp_greater_en = false;
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> >> + &info->adc_feature.pctl)) {
> >> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> >> + dev_info(info->dev,
> >> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
> >
> >"Missing" probably
> >
>
> Got it, thanks!
>
> >> + }
> >> +
> >> + if (info->vref)
> >> + info->vref_uv = regulator_get_voltage(info->vref);
> >> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> >> + dev_err(info->dev,
> >> + "Miss adc-vref property or vref regulator in the DT.\n");
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> >> + &info->adc_feature.clk_div_num)) {
> >> + info->adc_feature.clk_div_num = 2;
> >> + dev_info(info->dev,
> >> + "Miss adc-clk-div in dt, set divider to 2.\n");
> >> + }
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-res",
> >> + &info->adc_feature.res_mode)) {
> >> + info->adc_feature.res_mode = 12;
> >> + dev_info(info->dev,
> >> + "Miss adc-res property in dt, use 8bit mode.\n");
> >> + }
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> >> + &info->adc_feature.sam_time)) {
> >> + info->adc_feature.sam_time = 4;
> >> + dev_info(info->dev,
> >> + "Miss adc-sam-time property in dt, set to 4.\n");
> >> + }
> >> +
> >> + info->adc_feature.hw_average = of_property_read_bool(np,
> >> + "fsl,adc-hw-aver-en");
> >> + if (info->adc_feature.hw_average &&
> >> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> >> + &info->adc_feature.hw_sam)) {
> >> + info->adc_feature.hw_sam = 4;
> >> + dev_info(info->dev,
> >> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> >> + }
> >> +
> >> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
> >mode");
> >> + info->adc_feature.hs_oper = of_property_read_bool(np,
> >> +"fsl,adc-high-speed-conv"); }
> >> +
> >> +static inline void vf610_adc_cfg_post_set(struct vf610_adc *info) {
> >> + struct vf610_adc_feature *adc_feature = &(info->adc_feature);
> >
> >no parenthesis needed
>
> Got it , thanks!
> >
> >> + int cfg_data = 0;
> >> + int gc_data = 0;
> >> +
> >> + /* clock select and clock devider */
> >> + switch (adc_feature->clk_div_num) {
> >> + case 1:
> >> + break;
> >> + case 2:
> >> + cfg_data |= VF610_ADC_CLK_DIV2;
> >> + break;
> >> + case 4:
> >> + cfg_data |= VF610_ADC_CLK_DIV4;
> >> + break;
> >> + case 8:
> >> + cfg_data |= VF610_ADC_CLK_DIV8;
> >> + break;
> >> + case 16:
> >> + switch (adc_feature->clk_sel) {
> >> + case ADCIOC_BUSCLK_SET:
> >> + cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8;
> >> + break;
> >> + default:
> >> + dev_err(info->dev, "error clk divider\n");
> >
> >all those errors just logged and then ignored; shouldn't this be warnings then
> >or the handling improved?
>
> If the setting is error, use the default setting, and print out err log for user.
> In there, it use the default setting in fact.
> >
> >> + break;
> >> + }
> >> + break;
> >> + }
> >> +
> >> + switch (adc_feature->clk_sel) {
> >> + case ADCIOC_ALTCLK_SET:
> >> + cfg_data |= VF610_ADC_ALTCLK_SEL;
> >> + break;
> >> + case ADCIOC_ADACK_SET:
> >> + cfg_data |= VF610_ADC_ADACK_SEL;
> >> + break;
> >> + default:
> >> + break;
> >> + }
> >> +
> >> + /* resolution mode */
> >> + switch (adc_feature->res_mode) {
> >> + case 8:
> >> + cfg_data |= VF610_ADC_MODE_BIT8;
> >> + break;
> >> + case 10:
> >> + cfg_data |= VF610_ADC_MODE_BIT10;
> >> + break;
> >> + case 12:
> >> + cfg_data |= VF610_ADC_MODE_BIT12;
> >> + break;
> >> + default:
> >> + dev_err(info->dev, "error resolution mode\n");
> >> + break;
> >> + }
> >> +
> >> + /* Defines the sample time duration */
> >> + switch (adc_feature->sam_time) {
> >> + case 2:
> >> + break;
> >> + case 4:
> >> + cfg_data |= VF610_ADC_ADSTS_SHORT;
> >> + break;
> >> + case 6:
> >> + cfg_data |= VF610_ADC_ADSTS_NORMAL;
> >> + break;
> >> + case 8:
> >> + cfg_data |= VF610_ADC_ADSTS_LONG;
> >> + break;
> >> + case 12:
> >> + cfg_data |= VF610_ADC_ADLSMP_LONG;
> >> + break;
> >> + case 16:
> >> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_SHORT;
> >> + break;
> >> + case 20:
> >> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_NORMAL;
> >> + break;
> >> + case 24:
> >> + cfg_data |= VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_LONG;
> >> + break;
> >> + default:
> >> + dev_err(info->dev, "error sample duration\n");
> >> + break;
> >> + }
> >> +
> >> + /* low power configuration */
> >> + cfg_data |= VF610_ADC_ADLPC_EN;
> >> +
> >> + /* high speed operation */
> >> + cfg_data |= VF610_ADC_ADHSC_EN;
> >> +
> >> + /* voltage reference*/
> >
> >blank before */
>
> You are very careful, thanks!
>
> >
> >> + switch (adc_feature->vol_ref) {
> >> + case VF610_ADCIOC_VR_VREF_SET:
> >> + break;
> >> + case VF610_ADCIOC_VR_VALT_SET:
> >> + cfg_data |= VF610_ADC_REFSEL_VALT;
> >> + break;
> >> + case VF610_ADCIOC_VR_VBG_SET:
> >> + cfg_data |= VF610_ADC_REFSEL_VBG;
> >> + break;
> >> + default:
> >> + dev_err(info->dev, "error voltage reference\n");
> >> + }
> >> +
> >> + /* trigger select */
> >> + if (adc_feature->tri_hw)
> >> + cfg_data |= VF610_ADC_ADTRG_HARD;
> >> +
> >> + /* hardware average select */
> >> + if (adc_feature->hw_average) {
> >> + switch (adc_feature->hw_sam) {
> >> + case 4:
> >> + break;
> >> + case 8:
> >> + cfg_data |= VF610_ADC_AVGS_8;
> >> + break;
> >> + case 16:
> >> + cfg_data |= VF610_ADC_AVGS_16;
> >> + break;
> >> + case 32:
> >> + cfg_data |= VF610_ADC_AVGS_32;
> >> + break;
> >> + default:
> >> + dev_err(info->dev,
> >> + "error hardware sample average select\n");
> >> + }
> >> +
> >> + gc_data |= VF610_ADC_AVGEN;
> >> + }
> >> +
> >> + /* data overwrite enable */
> >> + if (adc_feature->dataov_en)
> >> + cfg_data |= VF610_ADC_OVWREN;
> >> +
> >> + /* Asynchronous clock output enable */
> >> + if (adc_feature->ac_clk_en)
> >> + gc_data |= VF610_ADC_ADACKEN;
> >> +
> >> + /* dma enable */
> >> + if (adc_feature->dma_en)
> >> + gc_data |= VF610_ADC_DMAEN;
> >> +
> >> + /* continue function enable */
> >> + if (adc_feature->cc_en)
> >> + gc_data |= VF610_ADC_ADCON;
> >> +
> >> + /* compare function enable */
> >> + if (adc_feature->cmp_func_en)
> >> + gc_data |= VF610_ADC_ACFE;
> >> +
> >> + /* greater than enable */
> >> + if (adc_feature->cmp_greater_en)
> >> + gc_data |= VF610_ADC_ACFGT;
> >> +
> >> + /* range enable */
> >> + if (adc_feature->cmp_range_en)
> >> + gc_data |= VF610_ADC_ACREN;
> >> +
> >> + writel(cfg_data, info->regs + ADC_CFG);
> >> + writel(gc_data, info->regs + ADC_GC); }
> >> +
> >> +static void vf610_adc_calibration(struct vf610_adc *info) {
> >> + int adc_gc, hc_cfg;
> >> + int timeout;
> >> +
> >> + if (!info->adc_feature.calibration)
> >> + return;
> >> +
> >> + /* enable calibration interrupt */
> >> + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
> >> + writel(hc_cfg, info->regs + ADC_HC0);
> >> +
> >> + adc_gc = readl(info->regs + ADC_GC);
> >> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
> >> +
> >> + timeout = wait_for_completion_interruptible_timeout
> >> + (&info->completion, VF610_ADC_TIMEOUT);
> >> + if (timeout == 0)
> >> + dev_err(info->dev, "Timeout for adc calibration\n");
> >
> >error ignored
> >
> Got it, thanks!
> There cannot be interrupted for calibration.
> So use wait_for_completion_timeout() instead of wait_for_completion_interruptible_timeout().
>
> >> +
> >> + adc_gc = readl(info->regs + ADC_GS);
> >> + if (adc_gc & VF610_ADC_CALF)
> >> + dev_err(info->dev, "ADC calibration failed\n");
> >> +
> >> + info->adc_feature.calibration = false; }
> >> +
> >> +static inline void vf610_adc_cfg_set(struct vf610_adc *info) {
> >
> >why are some functions marked inline and others not?
> >this function does not seem time critical
>
> Agree, I will remove the inline.
> >
> >> + struct vf610_adc_feature *adc_feature = &(info->adc_feature);
> >> + int cfg_data = 0;
> >> +
> >> + cfg_data = readl(info->regs + ADC_CFG);
> >> +
> >> + /* low power configuration */
> >> + if (!adc_feature->lpm)
> >> + cfg_data &= ~VF610_ADC_ADLPC_EN;
> >> +
> >> + /* high speed operation */
> >> + if (!adc_feature->hs_oper)
> >> + cfg_data &= ~VF610_ADC_ADHSC_EN;
> >> +
> >> + /* trigger select */
> >> + if (adc_feature->tri_hw)
> >> + cfg_data |= VF610_ADC_ADTRG_HARD;
> >> +
> >> + writel(cfg_data, info->regs + ADC_CFG); }
> >> +
> >> +static void vf610_adc_hw_init(struct vf610_adc *info) {
> >> + /* pin control for Sliding rheostat */
> >> + writel(info->adc_feature.pctl, info->regs + ADC_PCTL);
> >> +
> >> + /* CFG: Feature set */
> >> + vf610_adc_cfg_post_set(info);
> >> +
> >> + /* adc calibration */
> >> + vf610_adc_calibration(info);
> >> +
> >> + /* CFG: power and speed set */
> >> + vf610_adc_cfg_set(info);
> >> +}
> >> +
> >> +static inline int vf610_adc_read_data(struct vf610_adc *info) {
> >> + int result;
> >> +
> >> + result = readl(info->regs + ADC_R0);
> >> +
> >> + switch (info->adc_feature.res_mode) {
> >
> >maybe a lookup table could be used; value of res_mode is trusted at this point,
> >no error checking needed
>
> Yes, there don't need to check error.
> The entry number is only three, so lookup table is not valuable.
>
> >
> >> + case 8:
> >> + result &= VF610_ADC_DATA_BIT8_MASK;
> >> + break;
> >> + case 10:
> >> + result &= VF610_ADC_DATA_BIT10_MASK;
> >> + break;
> >> + case 12:
> >> + result &= VF610_ADC_DATA_BIT12_MASK;
> >> + break;
> >> + default:
> >> + dev_err(info->dev, "error resolution mode\n");
> >> + break;
> >> + }
> >> +
> >> + return result;
> >> +}
> >> +
> >> +static irqreturn_t vf610_adc_isr(int irq, void *dev_id) {
> >> + struct vf610_adc *info = (struct vf610_adc *)dev_id;
> >> + int coco;
> >> +
> >> + coco = readl(info->regs + ADC_HS);
> >> + if (coco & VF610_ADC_HS_COCO0) {
> >> + info->value = vf610_adc_read_data(info);
> >> + complete(&info->completion);
> >> + }
> >> +
> >> + return IRQ_HANDLED;
> >> +}
> >> +
> >> +static int vf610_read_raw(struct iio_dev *indio_dev,
> >> + struct iio_chan_spec const *chan,
> >> + int *val,
> >> + int *val2,
> >> + long mask)
> >> +{
> >> + struct vf610_adc *info = iio_priv(indio_dev);
> >> + unsigned int hc_cfg;
> >> + unsigned long timeout;
> >> +
> >> + /* Check for invalid channel */
> >> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
> >> + return -EINVAL;
> >
> >should never happen
> >
> Got it. Thanks!
>
> >> +
> >> + switch (mask) {
> >> + case IIO_CHAN_INFO_RAW:
> >> + mutex_lock(&indio_dev->mlock);
> >> +
> >> + hc_cfg = VF610_ADC_ADCHC(chan->channel);
> >> + hc_cfg |= VF610_ADC_AIEN;
> >> + writel(hc_cfg, info->regs + ADC_HC0);
> >> + timeout = wait_for_completion_interruptible_timeout
> >> + (&info->completion, VF610_ADC_TIMEOUT);
> >> + *val = info->value;
> >> +
> >> + mutex_unlock(&indio_dev->mlock);
> >> +
> >> + if (timeout == 0)
> >> + return -ETIMEDOUT;
> >> +
> >> + return IIO_VAL_INT;
> >> +
> >> + case IIO_CHAN_INFO_SCALE:
> >> + *val = info->vref_uv >> info->adc_feature.res_mode;
> >> + *val2 = 0;
> >
> >use IIO_VAL_FRACTIONAL_LOG2 probably
>
> Yes, it is better to use IIO_VAL_FRACTIONAL_LOG2, thanks!
> >
> >> + return IIO_VAL_INT_PLUS_MICRO;
> >> + default:
> >> + break;
> >> + }
> >> +
> >> + return -EINVAL;
> >> +}
> >> +
> >> +static int vf610_adc_reg_access(struct iio_dev *indio_dev,
> >> + unsigned reg, unsigned writeval,
> >> + unsigned *readval)
> >> +{
> >> + struct vf610_adc *info = iio_priv(indio_dev);
> >> +
> >> + if (readval == NULL)
> >> + return -EINVAL;
> >
> >can this ever happen?
>
> No, it never happen. Thanks.
see Lars comment; I was wrong
I think reg should be range checked in addition
> static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf,
> size_t count, loff_t *ppos)
> {
> struct iio_dev *indio_dev = file->private_data;
> char buf[20];
> unsigned val = 0;
> ssize_t len;
> int ret;
>
> ret = indio_dev->info->debugfs_reg_access(indio_dev,
> indio_dev->cached_reg_addr,
> 0, &val);
> ...
> }
>
> >
> >I'd rather check the value of reg
> >> +
> >> + *readval = readl(info->regs + reg);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static const struct iio_info vf610_adc_iio_info = {
> >> + .read_raw = &vf610_read_raw,
> >> + .debugfs_reg_access = &vf610_adc_reg_access,
> >> + .driver_module = THIS_MODULE,
> >> +};
> >> +
> >> +static const struct of_device_id vf610_adc_match[] = {
> >> + { .compatible = "fsl,vf610-adc", },
> >> + { /* sentinel */ }
> >> +};
> >> +MODULE_DEVICE_TABLE(of, vf610_adc_dt_ids);
> >> +
> >> +static int vf610_adc_probe(struct platform_device *pdev) {
> >> + struct vf610_adc *info = NULL;
> >> + struct iio_dev *indio_dev;
> >> + struct resource *mem;
> >> + int ret = -ENODEV;
> >
> >initialization of ret and info not needed
>
> Got it.
> >
> >> +
> >> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
> >> + 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))
> >> + return PTR_ERR(info->regs);
> >> +
> >> + info->irq = platform_get_irq(pdev, 0);
> >> + if (info->irq < 0) {
> >> + dev_err(&pdev->dev, "no irq resource?\n");
> >> + return -EINVAL;
> >> + }
> >> +
> >> + ret = devm_request_irq(info->dev, info->irq,
> >> + vf610_adc_isr, 0,
> >> + dev_name(&pdev->dev), info);
> >> + if (ret < 0) {
> >> + dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
> >> + info->irq);
> >> + return ret;
> >> + }
> >> +
> >> + info->clk = devm_clk_get(&pdev->dev, "adc");
> >> + if (IS_ERR(info->clk)) {
> >> + dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
> >> + PTR_ERR(info->clk));
> >> + ret = PTR_ERR(info->clk);
> >> + return ret;
> >> + }
> >> +
> >> + info->vref = devm_regulator_get(&pdev->dev, "vref");
> >> + if (!IS_ERR(info->vref)) {
> >> + ret = regulator_enable(info->vref);
> >> + if (ret) {
> >> + dev_err(&pdev->dev,
> >> + "Failed to enable vref regulator: %d\n", ret);
> >> + return ret;
> >> + }
> >> + } else {
> >> + info->vref = NULL;
> >> + }
> >> +
> >> + platform_set_drvdata(pdev, indio_dev);
> >> +
> >> + init_completion(&info->completion);
> >> +
> >> + 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 = &vf610_adc_iio_info;
> >> + indio_dev->modes = INDIO_DIRECT_MODE;
> >> + indio_dev->channels = vf610_adc_iio_channels;
> >> + indio_dev->num_channels = ARRAY_SIZE(vf610_adc_iio_channels);
> >> +
> >> + ret = devm_iio_device_register(&pdev->dev, indio_dev);
> >> + if (ret)
> >
> >regulator stays enabled
> >
> >> + return ret;
> >> +
> >> + ret = clk_prepare_enable(info->clk);
> >> + if (ret) {
> >> + dev_err(&pdev->dev,
> >> + "Could not prepare or enable the clock.\n");
> >> + return ret;
> >> + }
> >> +
> >> + vf610_adc_cfg_of_init(info);
> >> + vf610_adc_hw_init(info);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int vf610_adc_remove(struct platform_device *pdev) {
> >> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> >> + struct vf610_adc *info = iio_priv(indio_dev);
> >> +
> >> + if (info->vref)
> >> + regulator_disable(info->vref);
> >> + clk_disable_unprepare(info->clk);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +#ifdef CONFIG_PM_SLEEP
> >> +static int vf610_adc_suspend(struct device *dev) {
> >> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> >> + struct vf610_adc *info = iio_priv(indio_dev);
> >> + int hc_cfg;
> >> +
> >> + /* enter to stop mode */
> >
> >comment does not add to clarity of the code, remove?
>
> Below code let ADC enter to stop mode when suspend.
yes; VF610_ADC_CONV_DISABLE is pretty descriptive on its own :)
> >
> >> + hc_cfg = readl(info->regs + ADC_HC0);
> >> + hc_cfg |= VF610_ADC_CONV_DISABLE;
> >> + writel(hc_cfg, info->regs + ADC_HC0);
> >> +
> >> + clk_disable_unprepare(info->clk);
> >> + if (info->vref)
> >> + regulator_disable(info->vref);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int vf610_adc_resume(struct device *dev) {
> >> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> >> + struct vf610_adc *info = iio_priv(indio_dev);
> >> + int ret;
> >> +
> >> + if (info->vref) {
> >> + ret = regulator_enable(info->vref);
> >> + if (ret)
> >> + return ret;
> >> + }
> >> +
> >> + ret = clk_prepare_enable(info->clk);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + vf610_adc_hw_init(info);
> >> +
> >> + return 0;
> >> +}
> >> +#endif
> >> +
> >> +static SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops,
> >> + vf610_adc_suspend,
> >> + vf610_adc_resume);
> >> +
> >> +static struct platform_driver vf610_adc_driver = {
> >> + .probe = vf610_adc_probe,
> >> + .remove = vf610_adc_remove,
> >> + .driver = {
> >> + .name = DRIVER_NAME,
> >> + .owner = THIS_MODULE,
> >> + .of_match_table = vf610_adc_match,
> >> + .pm = &vf610_adc_pm_ops,
> >> + },
> >> +};
> >> +
> >> +module_platform_driver(vf610_adc_driver);
> >> +
> >> +MODULE_AUTHOR("Fugang Duan <B38611@freescale.com>");
> >> +MODULE_DESCRIPTION("Freescale VF610 ADC driver"); MODULE_LICENSE("GPL
> >> +v2");
> >>
>
> Peter,
> thanks for your review.
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
--
Peter Meerwald
+43-664-2444418 (mobile)
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
2013-11-27 7:17 ` Peter Meerwald
@ 2013-11-27 7:30 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 7:30 UTC (permalink / raw)
To: Peter Meerwald
Cc: jic23@kernel.org, shawn.guo@linaro.org, linux-iio@vger.kernel.org
From: Peter Meerwald <pmeerw@pmeerw.net>
Data: Wednesday, November 27, 2013 3:17 PM
>To: Duan Fugang-B38611
>Cc: jic23@kernel.org; shawn.guo@linaro.org; linux-iio@vger.kernel.org
>Subject: RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc dr=
iver
>
>
>some clarification below
>
>> >To: Duan Fugang-B38611
>> >Cc: jic23@kernel.org; shawn.guo@linaro.org; linux-iio@vger.kernel.org
>> >Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610
>> >adc driver
>> >
>> >On Tue, 26 Nov 2013, Fugang Duan wrote:
>> >
>> >> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> >> software trigger.
>> >
>> >nitpicking below
>> >
>> >> Signed-off-by: Fugang Duan <B38611@freescale.com>
>> >> ---
>> >> drivers/iio/adc/Kconfig | 11 +
>> >> drivers/iio/adc/Makefile | 1 +
>> >> drivers/iio/adc/vf610_adc.c | 744
>> >> +++++++++++++++++++++++++++++++++++++++++++
>> >> 3 files changed, 756 insertions(+), 0 deletions(-)
>> >>
>> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> >> index 2209f28..d0476ec 100644
>> >> --- a/drivers/iio/adc/Kconfig
>> >> +++ b/drivers/iio/adc/Kconfig
>> >> @@ -204,4 +204,15 @@ config VIPERBOARD_ADC
>> >> Say yes here to access the ADC part of the Nano River
>> >> Technologies Viperboard.
>> >>
>> >> +config VF610_ADC
>> >
>> >alphabetic order please
>>
>> Got it. Thanks.
>> >
>> >> + tristate "Freescale vf610 ADC driver"
>> >> + help
>> >> + Say yes here if you want support for the Vybrid board
>> >> + analog-to-digital converter.
>> >> + Since the IP is used for i.MX6SLX and i.MX7 serial sillicons,
>> >> +the
>> >driver
>> >> + also support the subsequent chips.
>> >> +
>> >> + This driver can also be built as a module. If so, the module will=
be
>> >> + called vf610_adc.
>> >> +
>> >> endmenu
>> >> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> >> index
>> >> ba9a10a..c39ae38 100644
>> >> --- a/drivers/iio/adc/Makefile
>> >> +++ b/drivers/iio/adc/Makefile
>> >> @@ -22,3 +22,4 @@ obj-$(CONFIG_TI_ADC081C) +=3D ti-adc081c.o
>> >> obj-$(CONFIG_TI_AM335X_ADC) +=3D ti_am335x_adc.o
>> >> obj-$(CONFIG_TWL6030_GPADC) +=3D twl6030-gpadc.o
>> >> obj-$(CONFIG_VIPERBOARD_ADC) +=3D viperboard_adc.o
>> >> +obj-$(CONFIG_VF610_ADC) +=3D vf610_adc.o
>> >
>> >keep alphabetic order
>>
>> Got it. Thanks.
>> >
>> >> diff --git a/drivers/iio/adc/vf610_adc.c
>> >> b/drivers/iio/adc/vf610_adc.c new file mode 100644 index
>> >> 0000000..0c200d91
>> >> --- /dev/null
>> >> +++ b/drivers/iio/adc/vf610_adc.c
>> >> @@ -0,0 +1,744 @@
>> >> +/*
>> >> + * Freescale Vybrid vf610 ADC driver
>> >> + *
>> >> + * Copyright 2013 Freescale Semiconductor, Inc.
>> >> + *
>> >> + * This program is free software; you can redistribute it and/or
>> >> +modify
>> >> + * it under the terms of the GNU General Public License as
>> >> +published by
>> >> + * the Free Software Foundation; either version 2 of the License,
>> >> +or
>> >> + * (at your option) any later version.
>> >> + *
>> >> + * This program is distributed in the hope that 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, write to the Free Software
>> >> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>> >> + */
>> >> +
>> >> +#include <linux/module.h>
>> >> +#include <linux/platform_device.h> #include <linux/interrupt.h>
>> >> +#include <linux/delay.h> #include <linux/kernel.h> #include
>> >> +<linux/slab.h> #include <linux/io.h> #include <linux/clk.h>
>> >> +#include <linux/completion.h> #include <linux/of.h> #include
>> >> +<linux/of_irq.h> #include <linux/regulator/consumer.h> #include
>> >> +<linux/of_platform.h> #include <linux/err.h>
>> >> +
>> >> +#include <linux/iio/iio.h>
>> >> +#include <linux/iio/machine.h>
>> >> +#include <linux/iio/driver.h>
>> >> +
>> >> +/* This will be the driver name the kernel reports */ #define
>> >> +DRIVER_NAME "vf610-adc"
>> >> +
>> >> +/* Vybrid/IMX ADC registers */
>> >
>> >VF610_ prefix
>>
>> Hmm, I think it is not necessary to add prefix for the register address.
>
>the argument for the prefix is that something in the headers you include m=
ay
>#define ADC_SOMETHING in the future and cause breakage; prefixing reduces =
this
>risk
>
>plus other define have the prefix, so for the sake of consistency...
Ok, I will add the prefix for them. Thanks.
>
>> >
>> >> +#define ADC_HC0 0x00
>> >> +#define ADC_HC1 0x04
>> >> +#define ADC_HS 0x08
>> >> +#define ADC_R0 0x0c
>> >> +#define ADC_R1 0x10
>> >> +#define ADC_CFG 0x14
>> >> +#define ADC_GC 0x18
>> >> +#define ADC_GS 0x1c
>> >> +#define ADC_CV 0x20
>> >> +#define ADC_OFS 0x24
>> >> +#define ADC_CAL 0x28
>> >> +#define ADC_PCTL 0x30
>> >> +
>> >> +/* Configuration register field define */
>> >> +#define VF610_ADC_MODE_BIT8 0x00
>> >> +#define VF610_ADC_MODE_BIT10 0x04
>> >> +#define VF610_ADC_MODE_BIT12 0x08
>> >> +#define VF610_ADC_DATA_BIT8_MASK 0xFF
>> >> +#define VF610_ADC_DATA_BIT10_MASK 0x3FF
>> >> +#define VF610_ADC_DATA_BIT12_MASK 0xFFF
>> >> +#define VF610_ADC_BUSCLK2_SEL 0x01
>> >> +#define VF610_ADC_ALTCLK_SEL 0x02
>> >> +#define VF610_ADC_ADACK_SEL 0x03
>> >> +#define VF610_ADC_CLK_DIV2 0x20
>> >> +#define VF610_ADC_CLK_DIV4 0x40
>> >> +#define VF610_ADC_CLK_DIV8 0x60
>> >> +#define VF610_ADC_ADLSMP_LONG 0x10
>> >> +#define VF610_ADC_ADSTS_SHORT 0x100
>> >> +#define VF610_ADC_ADSTS_NORMAL 0x200
>> >> +#define VF610_ADC_ADSTS_LONG 0x300
>> >> +#define VF610_ADC_ADLPC_EN 0x80
>> >> +#define VF610_ADC_ADHSC_EN 0x400
>> >> +#define VF610_ADC_REFSEL_VALT 0x100
>> >> +#define VF610_ADC_REFSEL_VBG 0x1000
>> >> +#define VF610_ADC_ADTRG_HARD 0x2000
>> >> +#define VF610_ADC_AVGS_8 0x4000
>> >> +#define VF610_ADC_AVGS_16 0x8000
>> >> +#define VF610_ADC_AVGS_32 0xC000
>> >> +#define VF610_ADC_OVWREN 0x10000
>> >> +
>> >> +/* General control register field define */
>> >> +#define VF610_ADC_ADACKEN 0x1
>> >> +#define VF610_ADC_DMAEN 0x2
>> >> +#define VF610_ADC_ACREN 0x4
>> >> +#define VF610_ADC_ACFGT 0x8
>> >> +#define VF610_ADC_ACFE 0x10
>> >> +#define VF610_ADC_AVGEN 0x20
>> >> +#define VF610_ADC_ADCON 0x40
>> >> +#define VF610_ADC_CAL 0x80
>> >> +
>> >> +/* Other field define */
>> >> +#define VF610_ADC_IOPCTL5 0x20
>> >> +#define VF610_ADC_ADCHC(x) ((x) & 0xF)
>> >> +#define VF610_ADC_AIEN (1 << 7)
>> >> +#define VF610_ADC_CONV_DISABLE 0x1F
>> >> +#define VF610_ADC_HS_COCO0 0x1
>> >> +#define VF610_ADC_CALF 0x2
>> >> +#define VF610_ADC_MAX_CHANS_NUM 16
>> >> +#define VF610_ADC_TIMEOUT (msecs_to_jiffies(100))
>> >
>> >no parenthesis needed
>>
>> Got it, thanks!
>> >
>> >> +
>> >> +enum clk_sel {
>> >> + ADCIOC_BUSCLK_SET,
>> >> + ADCIOC_ALTCLK_SET,
>> >> + ADCIOC_ADACK_SET,
>> >
>> >VF610 prefix
>>
>> Got it, thanks!
>> >
>> >> +};
>> >> +
>> >> +enum vol_ref {
>> >> + VF610_ADCIOC_VR_VREF_SET,
>> >> + VF610_ADCIOC_VR_VALT_SET,
>> >> + VF610_ADCIOC_VR_VBG_SET,
>> >> +};
>> >> +
>> >> +struct vf610_adc_feature {
>> >> + enum clk_sel clk_sel;
>> >> + enum vol_ref vol_ref;
>> >> +
>> >> + int pctl;
>> >> + int clk_div_num;
>> >> + int res_mode;
>> >> + int sam_time;
>> >> + int hw_sam;
>> >> +
>> >> + bool calibration;
>> >> + bool cc_en;
>> >> + bool dataov_en;
>> >> + bool hw_average;
>> >> + bool tri_hw;
>> >> + bool hs_oper;
>> >> + bool lpm;
>> >> + bool dma_en;
>> >> + bool ac_clk_en;
>> >> + bool cmp_func_en;
>> >> + bool cmp_range_en;
>> >> + bool cmp_greater_en;
>> >> +};
>> >> +
>> >> +struct vf610_adc {
>> >> + struct device *dev;
>> >> + void __iomem *regs;
>> >> + struct clk *clk;
>> >> + unsigned int irq;
>> >> +
>> >> + u32 vref_uv;
>> >> + u32 value;
>> >> + struct regulator *vref;
>> >> + struct vf610_adc_feature adc_feature;
>> >> +
>> >> + struct completion completion;
>> >> +};
>> >> +
>> >> +#define VF610_ADC_CHAN(_idx, _chan_type) { \
>> >> + .type =3D (_chan_type), \
>> >> + .indexed =3D 1, \
>> >> + .channel =3D _idx, \
>> >> + .address =3D _idx, \
>> >
>> >address not needed
>>
>> Got it, thanks!
>> >
>> >> + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \
>> >> + .info_mask_shared_by_type =3D BIT(IIO_CHAN_INFO_SCALE), \
>> >> +}
>> >> +
>> >> +static const struct iio_chan_spec vf610_adc_iio_channels[] =3D {
>> >> + VF610_ADC_CHAN(0, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(1, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(2, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(3, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(4, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(5, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(6, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(7, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(8, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(9, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(10, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(11, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(12, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(13, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(14, IIO_VOLTAGE),
>> >> + VF610_ADC_CHAN(15, IIO_VOLTAGE),
>> >> +};
>> >> +
>> >> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> >> + struct device_node *np =3D info->dev->of_node;
>> >> +
>> >> + /*
>> >> + * set default Configuration for ADC controller
>> >> + * This config may upgrade to require from DT
>> >
>> >what does "This config may upgrade to require from DT" mean?
>>
>> There many configurations for ADC, and I have import some setting from D=
T,
>below some setting is static defined.
>> In fact, all setting can get from DT.
>>
>> I don't want to static define the ADC setting, expect for getting them f=
rom
>DT, how to config them in runtime for user ?
>> IIO subsystem don't have such interface for it. I think the only solutio=
n is
>to add ioctl interface for the driver. How do you think ?
>
>I was just picking on the wording; I fail to understand what is meant by t=
he
>comment
>
For the ADC controller, there have many setting for it. Some of them are de=
fined statically, some of them are got from DT.
As the suggestion to set the configuration at runtime, how to do it ? Add =
ioctl interface for user ?=20
>> >> + */
>> >> + info->adc_feature.clk_sel =3D ADCIOC_BUSCLK_SET;
>> >> + info->adc_feature.vol_ref =3D VF610_ADCIOC_VR_VREF_SET;
>> >> + info->adc_feature.calibration =3D true;
>> >> + info->adc_feature.tri_hw =3D false;
>> >> + info->adc_feature.dataov_en =3D true;
>> >> + info->adc_feature.ac_clk_en =3D false;
>> >> + info->adc_feature.dma_en =3D false;
>> >> + info->adc_feature.cc_en =3D false;
>> >> + info->adc_feature.cmp_func_en =3D false;
>> >> + info->adc_feature.cmp_range_en =3D false;
>> >> + info->adc_feature.cmp_greater_en =3D false;
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> >> + &info->adc_feature.pctl)) {
>> >> + info->adc_feature.pctl =3D VF610_ADC_IOPCTL5;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>> >
>> >"Missing" probably
>> >
>>
>> Got it, thanks!
>>
>> >> + }
>> >> +
>> >> + if (info->vref)
>> >> + info->vref_uv =3D regulator_get_voltage(info->vref);
>> >> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>> >> + dev_err(info->dev,
>> >> + "Miss adc-vref property or vref regulator in the DT.\n");
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> >> + &info->adc_feature.clk_div_num)) {
>> >> + info->adc_feature.clk_div_num =3D 2;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> >> + }
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-res",
>> >> + &info->adc_feature.res_mode)) {
>> >> + info->adc_feature.res_mode =3D 12;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-res property in dt, use 8bit mode.\n");
>> >> + }
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> >> + &info->adc_feature.sam_time)) {
>> >> + info->adc_feature.sam_time =3D 4;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-sam-time property in dt, set to 4.\n");
>> >> + }
>> >> +
>> >> + info->adc_feature.hw_average =3D of_property_read_bool(np,
>> >> + "fsl,adc-hw-aver-en");
>> >> + if (info->adc_feature.hw_average &&
>> >> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> >> + &info->adc_feature.hw_sam)) {
>> >> + info->adc_feature.hw_sam =3D 4;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>> >> + }
>> >> +
>> >> + info->adc_feature.lpm =3D of_property_read_bool(np,
>> >> +"fsl,adc-low-power-
>> >mode");
>> >> + info->adc_feature.hs_oper =3D of_property_read_bool(np,
>> >> +"fsl,adc-high-speed-conv"); }
>> >> +
>> >> +static inline void vf610_adc_cfg_post_set(struct vf610_adc *info) {
>> >> + struct vf610_adc_feature *adc_feature =3D &(info->adc_feature);
>> >
>> >no parenthesis needed
>>
>> Got it , thanks!
>> >
>> >> + int cfg_data =3D 0;
>> >> + int gc_data =3D 0;
>> >> +
>> >> + /* clock select and clock devider */
>> >> + switch (adc_feature->clk_div_num) {
>> >> + case 1:
>> >> + break;
>> >> + case 2:
>> >> + cfg_data |=3D VF610_ADC_CLK_DIV2;
>> >> + break;
>> >> + case 4:
>> >> + cfg_data |=3D VF610_ADC_CLK_DIV4;
>> >> + break;
>> >> + case 8:
>> >> + cfg_data |=3D VF610_ADC_CLK_DIV8;
>> >> + break;
>> >> + case 16:
>> >> + switch (adc_feature->clk_sel) {
>> >> + case ADCIOC_BUSCLK_SET:
>> >> + cfg_data |=3D VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8;
>> >> + break;
>> >> + default:
>> >> + dev_err(info->dev, "error clk divider\n");
>> >
>> >all those errors just logged and then ignored; shouldn't this be
>> >warnings then or the handling improved?
>>
>> If the setting is error, use the default setting, and print out err log =
for
>user.
>> In there, it use the default setting in fact.
>> >
>> >> + break;
>> >> + }
>> >> + break;
>> >> + }
>> >> +
>> >> + switch (adc_feature->clk_sel) {
>> >> + case ADCIOC_ALTCLK_SET:
>> >> + cfg_data |=3D VF610_ADC_ALTCLK_SEL;
>> >> + break;
>> >> + case ADCIOC_ADACK_SET:
>> >> + cfg_data |=3D VF610_ADC_ADACK_SEL;
>> >> + break;
>> >> + default:
>> >> + break;
>> >> + }
>> >> +
>> >> + /* resolution mode */
>> >> + switch (adc_feature->res_mode) {
>> >> + case 8:
>> >> + cfg_data |=3D VF610_ADC_MODE_BIT8;
>> >> + break;
>> >> + case 10:
>> >> + cfg_data |=3D VF610_ADC_MODE_BIT10;
>> >> + break;
>> >> + case 12:
>> >> + cfg_data |=3D VF610_ADC_MODE_BIT12;
>> >> + break;
>> >> + default:
>> >> + dev_err(info->dev, "error resolution mode\n");
>> >> + break;
>> >> + }
>> >> +
>> >> + /* Defines the sample time duration */
>> >> + switch (adc_feature->sam_time) {
>> >> + case 2:
>> >> + break;
>> >> + case 4:
>> >> + cfg_data |=3D VF610_ADC_ADSTS_SHORT;
>> >> + break;
>> >> + case 6:
>> >> + cfg_data |=3D VF610_ADC_ADSTS_NORMAL;
>> >> + break;
>> >> + case 8:
>> >> + cfg_data |=3D VF610_ADC_ADSTS_LONG;
>> >> + break;
>> >> + case 12:
>> >> + cfg_data |=3D VF610_ADC_ADLSMP_LONG;
>> >> + break;
>> >> + case 16:
>> >> + cfg_data |=3D VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_SHORT;
>> >> + break;
>> >> + case 20:
>> >> + cfg_data |=3D VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_NORMAL;
>> >> + break;
>> >> + case 24:
>> >> + cfg_data |=3D VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_LONG;
>> >> + break;
>> >> + default:
>> >> + dev_err(info->dev, "error sample duration\n");
>> >> + break;
>> >> + }
>> >> +
>> >> + /* low power configuration */
>> >> + cfg_data |=3D VF610_ADC_ADLPC_EN;
>> >> +
>> >> + /* high speed operation */
>> >> + cfg_data |=3D VF610_ADC_ADHSC_EN;
>> >> +
>> >> + /* voltage reference*/
>> >
>> >blank before */
>>
>> You are very careful, thanks!
>>
>> >
>> >> + switch (adc_feature->vol_ref) {
>> >> + case VF610_ADCIOC_VR_VREF_SET:
>> >> + break;
>> >> + case VF610_ADCIOC_VR_VALT_SET:
>> >> + cfg_data |=3D VF610_ADC_REFSEL_VALT;
>> >> + break;
>> >> + case VF610_ADCIOC_VR_VBG_SET:
>> >> + cfg_data |=3D VF610_ADC_REFSEL_VBG;
>> >> + break;
>> >> + default:
>> >> + dev_err(info->dev, "error voltage reference\n");
>> >> + }
>> >> +
>> >> + /* trigger select */
>> >> + if (adc_feature->tri_hw)
>> >> + cfg_data |=3D VF610_ADC_ADTRG_HARD;
>> >> +
>> >> + /* hardware average select */
>> >> + if (adc_feature->hw_average) {
>> >> + switch (adc_feature->hw_sam) {
>> >> + case 4:
>> >> + break;
>> >> + case 8:
>> >> + cfg_data |=3D VF610_ADC_AVGS_8;
>> >> + break;
>> >> + case 16:
>> >> + cfg_data |=3D VF610_ADC_AVGS_16;
>> >> + break;
>> >> + case 32:
>> >> + cfg_data |=3D VF610_ADC_AVGS_32;
>> >> + break;
>> >> + default:
>> >> + dev_err(info->dev,
>> >> + "error hardware sample average select\n");
>> >> + }
>> >> +
>> >> + gc_data |=3D VF610_ADC_AVGEN;
>> >> + }
>> >> +
>> >> + /* data overwrite enable */
>> >> + if (adc_feature->dataov_en)
>> >> + cfg_data |=3D VF610_ADC_OVWREN;
>> >> +
>> >> + /* Asynchronous clock output enable */
>> >> + if (adc_feature->ac_clk_en)
>> >> + gc_data |=3D VF610_ADC_ADACKEN;
>> >> +
>> >> + /* dma enable */
>> >> + if (adc_feature->dma_en)
>> >> + gc_data |=3D VF610_ADC_DMAEN;
>> >> +
>> >> + /* continue function enable */
>> >> + if (adc_feature->cc_en)
>> >> + gc_data |=3D VF610_ADC_ADCON;
>> >> +
>> >> + /* compare function enable */
>> >> + if (adc_feature->cmp_func_en)
>> >> + gc_data |=3D VF610_ADC_ACFE;
>> >> +
>> >> + /* greater than enable */
>> >> + if (adc_feature->cmp_greater_en)
>> >> + gc_data |=3D VF610_ADC_ACFGT;
>> >> +
>> >> + /* range enable */
>> >> + if (adc_feature->cmp_range_en)
>> >> + gc_data |=3D VF610_ADC_ACREN;
>> >> +
>> >> + writel(cfg_data, info->regs + ADC_CFG);
>> >> + writel(gc_data, info->regs + ADC_GC); }
>> >> +
>> >> +static void vf610_adc_calibration(struct vf610_adc *info) {
>> >> + int adc_gc, hc_cfg;
>> >> + int timeout;
>> >> +
>> >> + if (!info->adc_feature.calibration)
>> >> + return;
>> >> +
>> >> + /* enable calibration interrupt */
>> >> + hc_cfg =3D VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE;
>> >> + writel(hc_cfg, info->regs + ADC_HC0);
>> >> +
>> >> + adc_gc =3D readl(info->regs + ADC_GC);
>> >> + writel(adc_gc | VF610_ADC_CAL, info->regs + ADC_GC);
>> >> +
>> >> + timeout =3D wait_for_completion_interruptible_timeout
>> >> + (&info->completion, VF610_ADC_TIMEOUT);
>> >> + if (timeout =3D=3D 0)
>> >> + dev_err(info->dev, "Timeout for adc calibration\n");
>> >
>> >error ignored
>> >
>> Got it, thanks!
>> There cannot be interrupted for calibration.
>> So use wait_for_completion_timeout() instead of
>wait_for_completion_interruptible_timeout().
>>
>> >> +
>> >> + adc_gc =3D readl(info->regs + ADC_GS);
>> >> + if (adc_gc & VF610_ADC_CALF)
>> >> + dev_err(info->dev, "ADC calibration failed\n");
>> >> +
>> >> + info->adc_feature.calibration =3D false; }
>> >> +
>> >> +static inline void vf610_adc_cfg_set(struct vf610_adc *info) {
>> >
>> >why are some functions marked inline and others not?
>> >this function does not seem time critical
>>
>> Agree, I will remove the inline.
>> >
>> >> + struct vf610_adc_feature *adc_feature =3D &(info->adc_feature);
>> >> + int cfg_data =3D 0;
>> >> +
>> >> + cfg_data =3D readl(info->regs + ADC_CFG);
>> >> +
>> >> + /* low power configuration */
>> >> + if (!adc_feature->lpm)
>> >> + cfg_data &=3D ~VF610_ADC_ADLPC_EN;
>> >> +
>> >> + /* high speed operation */
>> >> + if (!adc_feature->hs_oper)
>> >> + cfg_data &=3D ~VF610_ADC_ADHSC_EN;
>> >> +
>> >> + /* trigger select */
>> >> + if (adc_feature->tri_hw)
>> >> + cfg_data |=3D VF610_ADC_ADTRG_HARD;
>> >> +
>> >> + writel(cfg_data, info->regs + ADC_CFG); }
>> >> +
>> >> +static void vf610_adc_hw_init(struct vf610_adc *info) {
>> >> + /* pin control for Sliding rheostat */
>> >> + writel(info->adc_feature.pctl, info->regs + ADC_PCTL);
>> >> +
>> >> + /* CFG: Feature set */
>> >> + vf610_adc_cfg_post_set(info);
>> >> +
>> >> + /* adc calibration */
>> >> + vf610_adc_calibration(info);
>> >> +
>> >> + /* CFG: power and speed set */
>> >> + vf610_adc_cfg_set(info);
>> >> +}
>> >> +
>> >> +static inline int vf610_adc_read_data(struct vf610_adc *info) {
>> >> + int result;
>> >> +
>> >> + result =3D readl(info->regs + ADC_R0);
>> >> +
>> >> + switch (info->adc_feature.res_mode) {
>> >
>> >maybe a lookup table could be used; value of res_mode is trusted at
>> >this point, no error checking needed
>>
>> Yes, there don't need to check error.
>> The entry number is only three, so lookup table is not valuable.
>>
>> >
>> >> + case 8:
>> >> + result &=3D VF610_ADC_DATA_BIT8_MASK;
>> >> + break;
>> >> + case 10:
>> >> + result &=3D VF610_ADC_DATA_BIT10_MASK;
>> >> + break;
>> >> + case 12:
>> >> + result &=3D VF610_ADC_DATA_BIT12_MASK;
>> >> + break;
>> >> + default:
>> >> + dev_err(info->dev, "error resolution mode\n");
>> >> + break;
>> >> + }
>> >> +
>> >> + return result;
>> >> +}
>> >> +
>> >> +static irqreturn_t vf610_adc_isr(int irq, void *dev_id) {
>> >> + struct vf610_adc *info =3D (struct vf610_adc *)dev_id;
>> >> + int coco;
>> >> +
>> >> + coco =3D readl(info->regs + ADC_HS);
>> >> + if (coco & VF610_ADC_HS_COCO0) {
>> >> + info->value =3D vf610_adc_read_data(info);
>> >> + complete(&info->completion);
>> >> + }
>> >> +
>> >> + return IRQ_HANDLED;
>> >> +}
>> >> +
>> >> +static int vf610_read_raw(struct iio_dev *indio_dev,
>> >> + struct iio_chan_spec const *chan,
>> >> + int *val,
>> >> + int *val2,
>> >> + long mask)
>> >> +{
>> >> + struct vf610_adc *info =3D iio_priv(indio_dev);
>> >> + unsigned int hc_cfg;
>> >> + unsigned long timeout;
>> >> +
>> >> + /* Check for invalid channel */
>> >> + if (chan->channel > VF610_ADC_MAX_CHANS_NUM)
>> >> + return -EINVAL;
>> >
>> >should never happen
>> >
>> Got it. Thanks!
>>
>> >> +
>> >> + switch (mask) {
>> >> + case IIO_CHAN_INFO_RAW:
>> >> + mutex_lock(&indio_dev->mlock);
>> >> +
>> >> + hc_cfg =3D VF610_ADC_ADCHC(chan->channel);
>> >> + hc_cfg |=3D VF610_ADC_AIEN;
>> >> + writel(hc_cfg, info->regs + ADC_HC0);
>> >> + timeout =3D wait_for_completion_interruptible_timeout
>> >> + (&info->completion, VF610_ADC_TIMEOUT);
>> >> + *val =3D info->value;
>> >> +
>> >> + mutex_unlock(&indio_dev->mlock);
>> >> +
>> >> + if (timeout =3D=3D 0)
>> >> + return -ETIMEDOUT;
>> >> +
>> >> + return IIO_VAL_INT;
>> >> +
>> >> + case IIO_CHAN_INFO_SCALE:
>> >> + *val =3D info->vref_uv >> info->adc_feature.res_mode;
>> >> + *val2 =3D 0;
>> >
>> >use IIO_VAL_FRACTIONAL_LOG2 probably
>>
>> Yes, it is better to use IIO_VAL_FRACTIONAL_LOG2, thanks!
>> >
>> >> + return IIO_VAL_INT_PLUS_MICRO;
>> >> + default:
>> >> + break;
>> >> + }
>> >> +
>> >> + return -EINVAL;
>> >> +}
>> >> +
>> >> +static int vf610_adc_reg_access(struct iio_dev *indio_dev,
>> >> + unsigned reg, unsigned writeval,
>> >> + unsigned *readval)
>> >> +{
>> >> + struct vf610_adc *info =3D iio_priv(indio_dev);
>> >> +
>> >> + if (readval =3D=3D NULL)
>> >> + return -EINVAL;
>> >
>> >can this ever happen?
>>
>> No, it never happen. Thanks.
>
>see Lars comment; I was wrong
I have seen Lars comment.
>
>I think reg should be range checked in addition
>
>> static ssize_t iio_debugfs_read_reg(struct file *file, char __user *user=
buf,
>> size_t count, loff_t *ppos) {
>> struct iio_dev *indio_dev =3D file->private_data;
>> char buf[20];
>> unsigned val =3D 0;
>> ssize_t len;
>> int ret;
>>
>> ret =3D indio_dev->info->debugfs_reg_access(indio_dev,
>> indio_dev->cached_reg_=
addr,
>> 0, &val);
>> ...
>> }
>>
>> >
>> >I'd rather check the value of reg
>> >> +
>> >> + *readval =3D readl(info->regs + reg);
>> >> +
>> >> + return 0;
>> >> +}
>> >> +
>> >> +static const struct iio_info vf610_adc_iio_info =3D {
>> >> + .read_raw =3D &vf610_read_raw,
>> >> + .debugfs_reg_access =3D &vf610_adc_reg_access,
>> >> + .driver_module =3D THIS_MODULE,
>> >> +};
>> >> +
>> >> +static const struct of_device_id vf610_adc_match[] =3D {
>> >> + { .compatible =3D "fsl,vf610-adc", },
>> >> + { /* sentinel */ }
>> >> +};
>> >> +MODULE_DEVICE_TABLE(of, vf610_adc_dt_ids);
>> >> +
>> >> +static int vf610_adc_probe(struct platform_device *pdev) {
>> >> + struct vf610_adc *info =3D NULL;
>> >> + struct iio_dev *indio_dev;
>> >> + struct resource *mem;
>> >> + int ret =3D -ENODEV;
>> >
>> >initialization of ret and info not needed
>>
>> Got it.
>> >
>> >> +
>> >> + indio_dev =3D devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610=
_adc));
>> >> + if (!indio_dev) {
>> >> + dev_err(&pdev->dev, "Failed allocating iio device\n");
>> >> + return -ENOMEM;
>> >> + }
>> >> +
>> >> + info =3D iio_priv(indio_dev);
>> >> + info->dev =3D &pdev->dev;
>> >> +
>> >> + mem =3D platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> >> + info->regs =3D devm_ioremap_resource(&pdev->dev, mem);
>> >> + if (IS_ERR(info->regs))
>> >> + return PTR_ERR(info->regs);
>> >> +
>> >> + info->irq =3D platform_get_irq(pdev, 0);
>> >> + if (info->irq < 0) {
>> >> + dev_err(&pdev->dev, "no irq resource?\n");
>> >> + return -EINVAL;
>> >> + }
>> >> +
>> >> + ret =3D devm_request_irq(info->dev, info->irq,
>> >> + vf610_adc_isr, 0,
>> >> + dev_name(&pdev->dev), info);
>> >> + if (ret < 0) {
>> >> + dev_err(&pdev->dev, "failed requesting irq, irq =3D %d\n",
>> >> + info->irq);
>> >> + return ret;
>> >> + }
>> >> +
>> >> + info->clk =3D devm_clk_get(&pdev->dev, "adc");
>> >> + if (IS_ERR(info->clk)) {
>> >> + dev_err(&pdev->dev, "failed getting clock, err =3D %ld\n",
>> >> + PTR_ERR(info->clk));
>> >> + ret =3D PTR_ERR(info->clk);
>> >> + return ret;
>> >> + }
>> >> +
>> >> + info->vref =3D devm_regulator_get(&pdev->dev, "vref");
>> >> + if (!IS_ERR(info->vref)) {
>> >> + ret =3D regulator_enable(info->vref);
>> >> + if (ret) {
>> >> + dev_err(&pdev->dev,
>> >> + "Failed to enable vref regulator: %d\n", ret);
>> >> + return ret;
>> >> + }
>> >> + } else {
>> >> + info->vref =3D NULL;
>> >> + }
>> >> +
>> >> + platform_set_drvdata(pdev, indio_dev);
>> >> +
>> >> + init_completion(&info->completion);
>> >> +
>> >> + indio_dev->name =3D dev_name(&pdev->dev);
>> >> + indio_dev->dev.parent =3D &pdev->dev;
>> >> + indio_dev->dev.of_node =3D pdev->dev.of_node;
>> >> + indio_dev->info =3D &vf610_adc_iio_info;
>> >> + indio_dev->modes =3D INDIO_DIRECT_MODE;
>> >> + indio_dev->channels =3D vf610_adc_iio_channels;
>> >> + indio_dev->num_channels =3D ARRAY_SIZE(vf610_adc_iio_channels);
>> >> +
>> >> + ret =3D devm_iio_device_register(&pdev->dev, indio_dev);
>> >> + if (ret)
>> >
>> >regulator stays enabled
>> >
>> >> + return ret;
>> >> +
>> >> + ret =3D clk_prepare_enable(info->clk);
>> >> + if (ret) {
>> >> + dev_err(&pdev->dev,
>> >> + "Could not prepare or enable the clock.\n");
>> >> + return ret;
>> >> + }
>> >> +
>> >> + vf610_adc_cfg_of_init(info);
>> >> + vf610_adc_hw_init(info);
>> >> +
>> >> + return 0;
>> >> +}
>> >> +
>> >> +static int vf610_adc_remove(struct platform_device *pdev) {
>> >> + struct iio_dev *indio_dev =3D platform_get_drvdata(pdev);
>> >> + struct vf610_adc *info =3D iio_priv(indio_dev);
>> >> +
>> >> + if (info->vref)
>> >> + regulator_disable(info->vref);
>> >> + clk_disable_unprepare(info->clk);
>> >> +
>> >> + return 0;
>> >> +}
>> >> +
>> >> +#ifdef CONFIG_PM_SLEEP
>> >> +static int vf610_adc_suspend(struct device *dev) {
>> >> + struct iio_dev *indio_dev =3D dev_get_drvdata(dev);
>> >> + struct vf610_adc *info =3D iio_priv(indio_dev);
>> >> + int hc_cfg;
>> >> +
>> >> + /* enter to stop mode */
>> >
>> >comment does not add to clarity of the code, remove?
>>
>> Below code let ADC enter to stop mode when suspend.
>
>yes; VF610_ADC_CONV_DISABLE is pretty descriptive on its own :)
>
>> >
>> >> + hc_cfg =3D readl(info->regs + ADC_HC0);
>> >> + hc_cfg |=3D VF610_ADC_CONV_DISABLE;
>> >> + writel(hc_cfg, info->regs + ADC_HC0);
>> >> +
>> >> + clk_disable_unprepare(info->clk);
>> >> + if (info->vref)
>> >> + regulator_disable(info->vref);
>> >> +
>> >> + return 0;
>> >> +}
>> >> +
>> >> +static int vf610_adc_resume(struct device *dev) {
>> >> + struct iio_dev *indio_dev =3D dev_get_drvdata(dev);
>> >> + struct vf610_adc *info =3D iio_priv(indio_dev);
>> >> + int ret;
>> >> +
>> >> + if (info->vref) {
>> >> + ret =3D regulator_enable(info->vref);
>> >> + if (ret)
>> >> + return ret;
>> >> + }
>> >> +
>> >> + ret =3D clk_prepare_enable(info->clk);
>> >> + if (ret)
>> >> + return ret;
>> >> +
>> >> + vf610_adc_hw_init(info);
>> >> +
>> >> + return 0;
>> >> +}
>> >> +#endif
>> >> +
>> >> +static SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops,
>> >> + vf610_adc_suspend,
>> >> + vf610_adc_resume);
>> >> +
>> >> +static struct platform_driver vf610_adc_driver =3D {
>> >> + .probe =3D vf610_adc_probe,
>> >> + .remove =3D vf610_adc_remove,
>> >> + .driver =3D {
>> >> + .name =3D DRIVER_NAME,
>> >> + .owner =3D THIS_MODULE,
>> >> + .of_match_table =3D vf610_adc_match,
>> >> + .pm =3D &vf610_adc_pm_ops,
>> >> + },
>> >> +};
>> >> +
>> >> +module_platform_driver(vf610_adc_driver);
>> >> +
>> >> +MODULE_AUTHOR("Fugang Duan <B38611@freescale.com>");
>> >> +MODULE_DESCRIPTION("Freescale VF610 ADC driver");
>> >> +MODULE_LICENSE("GPL v2");
>> >>
>>
>> Peter,
>> thanks for your review.
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-iio"
>> in the body of a message to majordomo@vger.kernel.org More majordomo
>> info at http://vger.kernel.org/majordomo-info.html
>>
>
>--
>
>Peter Meerwald
>+43-664-2444418 (mobile)
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-26 14:25 ` Mark Rutland
0 siblings, 0 replies; 41+ messages in thread
From: Mark Rutland @ 2013-11-26 14:25 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23@kernel.org, sachin.kamat@linaro.org,
devicetree@vger.kernel.org, shawn.guo@linaro.org,
b20596@freescale.com, linux-iio@vger.kernel.org
On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
> Add Freescale Vybrid vf610 adc driver. The driver only support
> ADC software trigger.
>
> Signed-off-by: Fugang Duan <B38611@freescale.com>
> ---
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/vf610_adc.c | 744 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 756 insertions(+), 0 deletions(-)
[...]
> +static void vf610_adc_cfg_of_init(struct vf610_adc *info)
> +{
> + struct device_node *np = info->dev->of_node;
> +
> + /*
> + * set default Configuration for ADC controller
> + * This config may upgrade to require from DT
> + */
> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> + info->adc_feature.calibration = true;
> + info->adc_feature.tri_hw = false;
> + info->adc_feature.dataov_en = true;
> + info->adc_feature.ac_clk_en = false;
> + info->adc_feature.dma_en = false;
> + info->adc_feature.cc_en = false;
> + info->adc_feature.cmp_func_en = false;
> + info->adc_feature.cmp_range_en = false;
> + info->adc_feature.cmp_greater_en = false;
> +
> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> + &info->adc_feature.pctl)) {
> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> + dev_info(info->dev,
> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
> + }
Could you elaborate on what this means? What is this doing when pinctrl
is not specified in the DT?
> +
> + if (info->vref)
> + info->vref_uv = regulator_get_voltage(info->vref);
> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> + dev_err(info->dev,
> + "Miss adc-vref property or vref regulator in the DT.\n");
Query the regulator instead.
> +
> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> + &info->adc_feature.clk_div_num)) {
> + info->adc_feature.clk_div_num = 2;
> + dev_info(info->dev,
> + "Miss adc-clk-div in dt, set divider to 2.\n");
> + }
Why do you need this?
I'd expect the use to request a frequency they wanted to sample at, and
the driver would try its best by fiddling with this and the clock input.
> +
> + if (of_property_read_u32(np, "fsl,adc-res",
> + &info->adc_feature.res_mode)) {
> + info->adc_feature.res_mode = 12;
> + dev_info(info->dev,
> + "Miss adc-res property in dt, use 8bit mode.\n");
This message does not seem to match the code above it.
Is this a hardware limitation in some instances? Or is it always
possible to select any of the resolutions?
> +
> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> + &info->adc_feature.sam_time)) {
> + info->adc_feature.sam_time = 4;
> + dev_info(info->dev,
> + "Miss adc-sam-time property in dt, set to 4.\n");
> + }
This looks like it should be handled at runtime.
> +
> + info->adc_feature.hw_average = of_property_read_bool(np,
> + "fsl,adc-hw-aver-en");
Is hw average support not always present?
> + if (info->adc_feature.hw_average &&
> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> + &info->adc_feature.hw_sam)) {
> + info->adc_feature.hw_sam = 4;
> + dev_info(info->dev,
> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> + }
Why not configure this at runtime instead?
> +
> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-mode");
> + info->adc_feature.hs_oper = of_property_read_bool(np, "fsl,adc-high-speed-conv");
Please elaborate on what these mean, and why they need to be in the DT.
Thanks,
Mark.
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-26 14:25 ` Mark Rutland
0 siblings, 0 replies; 41+ messages in thread
From: Mark Rutland @ 2013-11-26 14:25 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
b20596-KZfg59tc24xl57MIdRCFDg@public.gmane.org,
linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
> Add Freescale Vybrid vf610 adc driver. The driver only support
> ADC software trigger.
>
> Signed-off-by: Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
> ---
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/vf610_adc.c | 744 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 756 insertions(+), 0 deletions(-)
[...]
> +static void vf610_adc_cfg_of_init(struct vf610_adc *info)
> +{
> + struct device_node *np = info->dev->of_node;
> +
> + /*
> + * set default Configuration for ADC controller
> + * This config may upgrade to require from DT
> + */
> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> + info->adc_feature.calibration = true;
> + info->adc_feature.tri_hw = false;
> + info->adc_feature.dataov_en = true;
> + info->adc_feature.ac_clk_en = false;
> + info->adc_feature.dma_en = false;
> + info->adc_feature.cc_en = false;
> + info->adc_feature.cmp_func_en = false;
> + info->adc_feature.cmp_range_en = false;
> + info->adc_feature.cmp_greater_en = false;
> +
> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> + &info->adc_feature.pctl)) {
> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> + dev_info(info->dev,
> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
> + }
Could you elaborate on what this means? What is this doing when pinctrl
is not specified in the DT?
> +
> + if (info->vref)
> + info->vref_uv = regulator_get_voltage(info->vref);
> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> + dev_err(info->dev,
> + "Miss adc-vref property or vref regulator in the DT.\n");
Query the regulator instead.
> +
> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> + &info->adc_feature.clk_div_num)) {
> + info->adc_feature.clk_div_num = 2;
> + dev_info(info->dev,
> + "Miss adc-clk-div in dt, set divider to 2.\n");
> + }
Why do you need this?
I'd expect the use to request a frequency they wanted to sample at, and
the driver would try its best by fiddling with this and the clock input.
> +
> + if (of_property_read_u32(np, "fsl,adc-res",
> + &info->adc_feature.res_mode)) {
> + info->adc_feature.res_mode = 12;
> + dev_info(info->dev,
> + "Miss adc-res property in dt, use 8bit mode.\n");
This message does not seem to match the code above it.
Is this a hardware limitation in some instances? Or is it always
possible to select any of the resolutions?
> +
> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> + &info->adc_feature.sam_time)) {
> + info->adc_feature.sam_time = 4;
> + dev_info(info->dev,
> + "Miss adc-sam-time property in dt, set to 4.\n");
> + }
This looks like it should be handled at runtime.
> +
> + info->adc_feature.hw_average = of_property_read_bool(np,
> + "fsl,adc-hw-aver-en");
Is hw average support not always present?
> + if (info->adc_feature.hw_average &&
> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> + &info->adc_feature.hw_sam)) {
> + info->adc_feature.hw_sam = 4;
> + dev_info(info->dev,
> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> + }
Why not configure this at runtime instead?
> +
> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-mode");
> + info->adc_feature.hs_oper = of_property_read_bool(np, "fsl,adc-high-speed-conv");
Please elaborate on what these mean, and why they need to be in the DT.
Thanks,
Mark.
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 5:37 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 5:37 UTC (permalink / raw)
To: Mark Rutland
Cc: jic23@kernel.org, sachin.kamat@linaro.org,
devicetree@vger.kernel.org, shawn.guo@linaro.org, Frank Li,
linux-iio@vger.kernel.org
From: Mark Rutland <mark.rutland@arm.com>
Data: Tuesday, November 26, 2013 10:26 PM
>To: Duan Fugang-B38611
>Cc: jic23@kernel.org; sachin.kamat@linaro.org; devicetree@vger.kernel.org;
>shawn.guo@linaro.org; Li Frank-B20596; linux-iio@vger.kernel.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc dr=
iver
>
>On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
>> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> software trigger.
>>
>> Signed-off-by: Fugang Duan <B38611@freescale.com>
>> ---
>> drivers/iio/adc/Kconfig | 11 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/vf610_adc.c | 744
>> +++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 756 insertions(+), 0 deletions(-)
>
>[...]
>
>> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> + struct device_node *np =3D info->dev->of_node;
>> +
>> + /*
>> + * set default Configuration for ADC controller
>> + * This config may upgrade to require from DT
>> + */
>> + info->adc_feature.clk_sel =3D ADCIOC_BUSCLK_SET;
>> + info->adc_feature.vol_ref =3D VF610_ADCIOC_VR_VREF_SET;
>> + info->adc_feature.calibration =3D true;
>> + info->adc_feature.tri_hw =3D false;
>> + info->adc_feature.dataov_en =3D true;
>> + info->adc_feature.ac_clk_en =3D false;
>> + info->adc_feature.dma_en =3D false;
>> + info->adc_feature.cc_en =3D false;
>> + info->adc_feature.cmp_func_en =3D false;
>> + info->adc_feature.cmp_range_en =3D false;
>> + info->adc_feature.cmp_greater_en =3D false;
>> +
>> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> + &info->adc_feature.pctl)) {
>> + info->adc_feature.pctl =3D VF610_ADC_IOPCTL5;
>> + dev_info(info->dev,
>> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>> + }
>
>Could you elaborate on what this means? What is this doing when pinctrl is=
not
>specified in the DT?
>
As vf610 reference manually describe ADC pin control:
37.8.2 Input Select and Pin Control
The pin control registers (ADC_PC) disable the I/O port control of the pins=
used as
analog inputs. When a pin control register bit is set, the following condit=
ions are forced
for the associated MCU pin:
* The output buffer is forced to its high impedance state.
* The input buffer is disabled. A read of the I/O port returns a zero for a=
ny pin with its
input buffer disabled.
* The pullup is disabled.=20
So, when the relative ADC pin is used as Sliding rheostat test, disable the=
pin control.
>> +
>> + if (info->vref)
>> + info->vref_uv =3D regulator_get_voltage(info->vref);
>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv=
))
>> + dev_err(info->dev,
>> + "Miss adc-vref property or vref regulator in
>> + the DT.\n");
>
>Query the regulator instead.
When the ADC reference voltage is not controlled by regulator, just by fixe=
d voltage from MCU digital power,
We get the reference voltage from DT.
=20
>
>> +
>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> + &info->adc_feature.clk_div_num)) {
>> + info->adc_feature.clk_div_num =3D 2;
>> + dev_info(info->dev,
>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> + }
>
>Why do you need this?
>
>I'd expect the use to request a frequency they wanted to sample at, and th=
e
>driver would try its best by fiddling with this and the clock input.
>
>> +
>> + if (of_property_read_u32(np, "fsl,adc-res",
>> + &info->adc_feature.res_mode)) {
>> + info->adc_feature.res_mode =3D 12;
>> + dev_info(info->dev,
>> + "Miss adc-res property in dt, use 8bit
>> + mode.\n");
>
>This message does not seem to match the code above it.
>
>Is this a hardware limitation in some instances? Or is it always possible =
to
>select any of the resolutions?
No limitation, just mistake for it. I have tested 8-bit, 10-bit, 12-bit mod=
e, all pass.
>
>> +
>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> + &info->adc_feature.sam_time)) {
>> + info->adc_feature.sam_time =3D 4;
>> + dev_info(info->dev,
>> + "Miss adc-sam-time property in dt, set to 4.\n")=
;
>> + }
>
>This looks like it should be handled at runtime.
Driver can get the ADC sample time duration fro DT.
fsl,adc-sam-time: ADC sample time duration, number of ADC clocks, such as 2=
, 4, 6, 8, 12, 16, 20, 24
How to handled at runtime ?=20
Use ioctl ?
For the ADC, there have many setting, some of setting are defined staticall=
y, some of them get from DT.
If let the setting handled at runtime, it need to introduce ioctl interface=
for the driver.
>
>> +
>> + info->adc_feature.hw_average =3D of_property_read_bool(np,
>> + "fsl,adc-hw-aver-en");
>
>Is hw average support not always present?
Vf610 RM describe:
37.8.5.7 Hardware Average Function
After the selected input is sampled and converted, the result is placed in =
an accumulator
from which an average is calculated once the selected number of conversions=
has been
completed. When hardware averaging is selected the completion of a single c=
onversion
will not set the COCOn bit.
In general, hw average function is useful to reduce cpu loading.
The function is optional for user.
>
>> + if (info->adc_feature.hw_average &&
>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> + &info->adc_feature.hw_sam)) {
>> + info->adc_feature.hw_sam =3D 4;
>> + dev_info(info->dev,
>> + "Miss adc-aver-sam-sel property in dt, set to 4.=
\n");
>> + }
>
>Why not configure this at runtime instead?
>
As above said, if let the configuration set at runtime, we need to introduc=
e ioctl interface for user.
How do you think ?
>> +
>> + info->adc_feature.lpm =3D of_property_read_bool(np, "fsl,adc-low=
-power-
>mode");
>> + info->adc_feature.hs_oper =3D of_property_read_bool(np,
>> + "fsl,adc-high-speed-conv");
>
>Please elaborate on what these mean, and why they need to be in the DT.
>
Yes, there have some configuration have been set in default, some of them =
from DT.
I think the hw function/feature enable/disable are judged from DT.
Some general configurations are set in default.
Of course, let all of them are set at runtime is better. But need to introd=
uce ioctl interface for user.=20
Mark,=20
Thanks for your review.
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 5:37 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-27 5:37 UTC (permalink / raw)
To: Mark Rutland
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org, Frank Li,
linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
From: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
Data: Tuesday, November 26, 2013 10:26 PM
>To: Duan Fugang-B38611
>Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org; sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org;
>shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; Li Frank-B20596; linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
>
>On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
>> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> software trigger.
>>
>> Signed-off-by: Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
>> ---
>> drivers/iio/adc/Kconfig | 11 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/vf610_adc.c | 744
>> +++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 756 insertions(+), 0 deletions(-)
>
>[...]
>
>> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> + struct device_node *np = info->dev->of_node;
>> +
>> + /*
>> + * set default Configuration for ADC controller
>> + * This config may upgrade to require from DT
>> + */
>> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
>> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
>> + info->adc_feature.calibration = true;
>> + info->adc_feature.tri_hw = false;
>> + info->adc_feature.dataov_en = true;
>> + info->adc_feature.ac_clk_en = false;
>> + info->adc_feature.dma_en = false;
>> + info->adc_feature.cc_en = false;
>> + info->adc_feature.cmp_func_en = false;
>> + info->adc_feature.cmp_range_en = false;
>> + info->adc_feature.cmp_greater_en = false;
>> +
>> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> + &info->adc_feature.pctl)) {
>> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
>> + dev_info(info->dev,
>> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>> + }
>
>Could you elaborate on what this means? What is this doing when pinctrl is not
>specified in the DT?
>
As vf610 reference manually describe ADC pin control:
37.8.2 Input Select and Pin Control
The pin control registers (ADC_PC) disable the I/O port control of the pins used as
analog inputs. When a pin control register bit is set, the following conditions are forced
for the associated MCU pin:
* The output buffer is forced to its high impedance state.
* The input buffer is disabled. A read of the I/O port returns a zero for any pin with its
input buffer disabled.
* The pullup is disabled.
So, when the relative ADC pin is used as Sliding rheostat test, disable the pin control.
>> +
>> + if (info->vref)
>> + info->vref_uv = regulator_get_voltage(info->vref);
>> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>> + dev_err(info->dev,
>> + "Miss adc-vref property or vref regulator in
>> + the DT.\n");
>
>Query the regulator instead.
When the ADC reference voltage is not controlled by regulator, just by fixed voltage from MCU digital power,
We get the reference voltage from DT.
>
>> +
>> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> + &info->adc_feature.clk_div_num)) {
>> + info->adc_feature.clk_div_num = 2;
>> + dev_info(info->dev,
>> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> + }
>
>Why do you need this?
>
>I'd expect the use to request a frequency they wanted to sample at, and the
>driver would try its best by fiddling with this and the clock input.
>
>> +
>> + if (of_property_read_u32(np, "fsl,adc-res",
>> + &info->adc_feature.res_mode)) {
>> + info->adc_feature.res_mode = 12;
>> + dev_info(info->dev,
>> + "Miss adc-res property in dt, use 8bit
>> + mode.\n");
>
>This message does not seem to match the code above it.
>
>Is this a hardware limitation in some instances? Or is it always possible to
>select any of the resolutions?
No limitation, just mistake for it. I have tested 8-bit, 10-bit, 12-bit mode, all pass.
>
>> +
>> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> + &info->adc_feature.sam_time)) {
>> + info->adc_feature.sam_time = 4;
>> + dev_info(info->dev,
>> + "Miss adc-sam-time property in dt, set to 4.\n");
>> + }
>
>This looks like it should be handled at runtime.
Driver can get the ADC sample time duration fro DT.
fsl,adc-sam-time: ADC sample time duration, number of ADC clocks, such as 2, 4, 6, 8, 12, 16, 20, 24
How to handled at runtime ?
Use ioctl ?
For the ADC, there have many setting, some of setting are defined statically, some of them get from DT.
If let the setting handled at runtime, it need to introduce ioctl interface for the driver.
>
>> +
>> + info->adc_feature.hw_average = of_property_read_bool(np,
>> + "fsl,adc-hw-aver-en");
>
>Is hw average support not always present?
Vf610 RM describe:
37.8.5.7 Hardware Average Function
After the selected input is sampled and converted, the result is placed in an accumulator
from which an average is calculated once the selected number of conversions has been
completed. When hardware averaging is selected the completion of a single conversion
will not set the COCOn bit.
In general, hw average function is useful to reduce cpu loading.
The function is optional for user.
>
>> + if (info->adc_feature.hw_average &&
>> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> + &info->adc_feature.hw_sam)) {
>> + info->adc_feature.hw_sam = 4;
>> + dev_info(info->dev,
>> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
>> + }
>
>Why not configure this at runtime instead?
>
As above said, if let the configuration set at runtime, we need to introduce ioctl interface for user.
How do you think ?
>> +
>> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
>mode");
>> + info->adc_feature.hs_oper = of_property_read_bool(np,
>> + "fsl,adc-high-speed-conv");
>
>Please elaborate on what these mean, and why they need to be in the DT.
>
Yes, there have some configuration have been set in default, some of them from DT.
I think the hw function/feature enable/disable are judged from DT.
Some general configurations are set in default.
Of course, let all of them are set at runtime is better. But need to introduce ioctl interface for user.
Mark,
Thanks for your review.
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 14:18 ` Mark Rutland
0 siblings, 0 replies; 41+ messages in thread
From: Mark Rutland @ 2013-11-27 14:18 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23@kernel.org, sachin.kamat@linaro.org,
devicetree@vger.kernel.org, shawn.guo@linaro.org, Frank Li,
linux-iio@vger.kernel.org
On Wed, Nov 27, 2013 at 05:37:24AM +0000, Fugang Duan wrote:
> From: Mark Rutland <mark.rutland@arm.com>
> Data: Tuesday, November 26, 2013 10:26 PM
>
> >To: Duan Fugang-B38611
> >Cc: jic23@kernel.org; sachin.kamat@linaro.org; devicetree@vger.kernel.org;
> >shawn.guo@linaro.org; Li Frank-B20596; linux-iio@vger.kernel.org
> >Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
> >
> >On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
> >> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
> >> software trigger.
> >>
> >> Signed-off-by: Fugang Duan <B38611@freescale.com>
> >> ---
> >> drivers/iio/adc/Kconfig | 11 +
> >> drivers/iio/adc/Makefile | 1 +
> >> drivers/iio/adc/vf610_adc.c | 744
> >> +++++++++++++++++++++++++++++++++++++++++++
> >> 3 files changed, 756 insertions(+), 0 deletions(-)
> >
> >[...]
> >
> >> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
> >> + struct device_node *np = info->dev->of_node;
> >> +
> >> + /*
> >> + * set default Configuration for ADC controller
> >> + * This config may upgrade to require from DT
> >> + */
> >> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> >> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> >> + info->adc_feature.calibration = true;
> >> + info->adc_feature.tri_hw = false;
> >> + info->adc_feature.dataov_en = true;
> >> + info->adc_feature.ac_clk_en = false;
> >> + info->adc_feature.dma_en = false;
> >> + info->adc_feature.cc_en = false;
> >> + info->adc_feature.cmp_func_en = false;
> >> + info->adc_feature.cmp_range_en = false;
> >> + info->adc_feature.cmp_greater_en = false;
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> >> + &info->adc_feature.pctl)) {
> >> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> >> + dev_info(info->dev,
> >> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
> >> + }
> >
> >Could you elaborate on what this means? What is this doing when pinctrl is not
> >specified in the DT?
> >
>
> As vf610 reference manually describe ADC pin control:
>
> 37.8.2 Input Select and Pin Control
> The pin control registers (ADC_PC) disable the I/O port control of the pins used as
> analog inputs. When a pin control register bit is set, the following conditions are forced
> for the associated MCU pin:
> * The output buffer is forced to its high impedance state.
> * The input buffer is disabled. A read of the I/O port returns a zero for any pin with its
> input buffer disabled.
> * The pullup is disabled.
>
> So, when the relative ADC pin is used as Sliding rheostat test, disable the pin control.
>
>
> >> +
> >> + if (info->vref)
> >> + info->vref_uv = regulator_get_voltage(info->vref);
> >> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> >> + dev_err(info->dev,
> >> + "Miss adc-vref property or vref regulator in
> >> + the DT.\n");
> >
> >Query the regulator instead.
>
> When the ADC reference voltage is not controlled by regulator, just by
> fixed voltage from MCU digital power, We get the reference voltage
> from DT.
You could use a fixed-regulator in that case in the DT, and query the
voltage through the regulator framework. There is no need for this
property.
>
> >
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> >> + &info->adc_feature.clk_div_num)) {
> >> + info->adc_feature.clk_div_num = 2;
> >> + dev_info(info->dev,
> >> + "Miss adc-clk-div in dt, set divider to 2.\n");
> >> + }
> >
> >Why do you need this?
> >
> >I'd expect the use to request a frequency they wanted to sample at, and the
> >driver would try its best by fiddling with this and the clock input.
> >
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-res",
> >> + &info->adc_feature.res_mode)) {
> >> + info->adc_feature.res_mode = 12;
> >> + dev_info(info->dev,
> >> + "Miss adc-res property in dt, use 8bit
> >> + mode.\n");
> >
> >This message does not seem to match the code above it.
> >
> >Is this a hardware limitation in some instances? Or is it always possible to
> >select any of the resolutions?
>
> No limitation, just mistake for it. I have tested 8-bit, 10-bit, 12-bit mode, all pass.
Ok. So this is not necessary?
> >
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> >> + &info->adc_feature.sam_time)) {
> >> + info->adc_feature.sam_time = 4;
> >> + dev_info(info->dev,
> >> + "Miss adc-sam-time property in dt, set to 4.\n");
> >> + }
> >
> >This looks like it should be handled at runtime.
>
> Driver can get the ADC sample time duration fro DT.
> fsl,adc-sam-time: ADC sample time duration, number of ADC clocks, such
> as 2, 4, 6, 8, 12, 16, 20, 24
If this is not a fixed property of the hardware, and there isn't some
limitation such that one of these values _must_ be used, I do not
believe it should be in the device tree.
Just because it is easier to put it there doesn't make putting it there
correct.
>
> How to handled at runtime ?
> Use ioctl ?
Something of that sort. Hopefully someone more familiar with the
subsystem can describe what's preferred.
> For the ADC, there have many setting, some of setting are defined
> statically, some of them get from DT. If let the setting handled at
> runtime, it need to introduce ioctl interface for the driver.
If it's a static property of the HW that cannot be probed, it should be
in the DT. If it's a limitation of the HW, that should be in the DT.
If it's an arbitrary value that does not have a significant effect on
performance or correctness, I do not see why it should be in the DT.
As far as I can see, this does not belong in the DT. The fact that you'd
need an ioctl to configure it dynamically is an orthogonal issue.
>
> >
> >> +
> >> + info->adc_feature.hw_average = of_property_read_bool(np,
> >> + "fsl,adc-hw-aver-en");
> >
> >Is hw average support not always present?
>
> Vf610 RM describe:
> 37.8.5.7 Hardware Average Function
> After the selected input is sampled and converted, the result is placed in an accumulator
> from which an average is calculated once the selected number of conversions has been
> completed. When hardware averaging is selected the completion of a single conversion
> will not set the COCOn bit.
>
> In general, hw average function is useful to reduce cpu loading.
> The function is optional for user.
Ok. If this is always possible to use, as your description implies, then
I do not see why this needs to be in the DT. Whether you choose to
enable or disable it in the driver by default, or whether you expose an
interface to userspace to configure this is an orthogonal issue.
>
> >
> >> + if (info->adc_feature.hw_average &&
> >> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> >> + &info->adc_feature.hw_sam)) {
> >> + info->adc_feature.hw_sam = 4;
> >> + dev_info(info->dev,
> >> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> >> + }
> >
> >Why not configure this at runtime instead?
> >
> As above said, if let the configuration set at runtime, we need to
> introduce ioctl interface for user.
> How do you think ?
An ioctl or sysfs interface sounds like the correct solution. I don't
have any strong opinion on the particular interface used, but DT is not
the right place to expose this configuration option.
>
>
> >> +
> >> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
> >mode");
> >> + info->adc_feature.hs_oper = of_property_read_bool(np,
> >> + "fsl,adc-high-speed-conv");
> >
> >Please elaborate on what these mean, and why they need to be in the DT.
> >
> Yes, there have some configuration have been set in default, some of them from DT.
> I think the hw function/feature enable/disable are judged from DT.
> Some general configurations are set in default.
Sorry, but that does not answer my questions:
* What do these two properties mean?
* Why must they be in the DT?
As far as I can tell, they do not need to be in the DT.
>
> Of course, let all of them are set at runtime is better. But need to
> introduce ioctl interface for user.
I don't see this as an argument for placing these in the DT. Describe
the hardware in the DT. Anything that is expected to be changed by the
user is clearly not a property of the hardware and should not be in the
DT.
There are grey areas with some devices where a specific configuration is
far more optimal for a given platform (e.g. reserving specific portions
of memory for frame buffers). As far as I can see, none of the
properties proposed in this patch fall into that area, and sane defaults
can be selected for all of them which could later be overridden with a
userspace interface of some sort.
Thanks,
Mark.
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-27 14:18 ` Mark Rutland
0 siblings, 0 replies; 41+ messages in thread
From: Mark Rutland @ 2013-11-27 14:18 UTC (permalink / raw)
To: Fugang Duan
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org, Frank Li,
linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
On Wed, Nov 27, 2013 at 05:37:24AM +0000, Fugang Duan wrote:
> From: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
> Data: Tuesday, November 26, 2013 10:26 PM
>
> >To: Duan Fugang-B38611
> >Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org; sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org;
> >shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; Li Frank-B20596; linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> >Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
> >
> >On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
> >> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
> >> software trigger.
> >>
> >> Signed-off-by: Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
> >> ---
> >> drivers/iio/adc/Kconfig | 11 +
> >> drivers/iio/adc/Makefile | 1 +
> >> drivers/iio/adc/vf610_adc.c | 744
> >> +++++++++++++++++++++++++++++++++++++++++++
> >> 3 files changed, 756 insertions(+), 0 deletions(-)
> >
> >[...]
> >
> >> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
> >> + struct device_node *np = info->dev->of_node;
> >> +
> >> + /*
> >> + * set default Configuration for ADC controller
> >> + * This config may upgrade to require from DT
> >> + */
> >> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
> >> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
> >> + info->adc_feature.calibration = true;
> >> + info->adc_feature.tri_hw = false;
> >> + info->adc_feature.dataov_en = true;
> >> + info->adc_feature.ac_clk_en = false;
> >> + info->adc_feature.dma_en = false;
> >> + info->adc_feature.cc_en = false;
> >> + info->adc_feature.cmp_func_en = false;
> >> + info->adc_feature.cmp_range_en = false;
> >> + info->adc_feature.cmp_greater_en = false;
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
> >> + &info->adc_feature.pctl)) {
> >> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
> >> + dev_info(info->dev,
> >> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
> >> + }
> >
> >Could you elaborate on what this means? What is this doing when pinctrl is not
> >specified in the DT?
> >
>
> As vf610 reference manually describe ADC pin control:
>
> 37.8.2 Input Select and Pin Control
> The pin control registers (ADC_PC) disable the I/O port control of the pins used as
> analog inputs. When a pin control register bit is set, the following conditions are forced
> for the associated MCU pin:
> * The output buffer is forced to its high impedance state.
> * The input buffer is disabled. A read of the I/O port returns a zero for any pin with its
> input buffer disabled.
> * The pullup is disabled.
>
> So, when the relative ADC pin is used as Sliding rheostat test, disable the pin control.
>
>
> >> +
> >> + if (info->vref)
> >> + info->vref_uv = regulator_get_voltage(info->vref);
> >> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
> >> + dev_err(info->dev,
> >> + "Miss adc-vref property or vref regulator in
> >> + the DT.\n");
> >
> >Query the regulator instead.
>
> When the ADC reference voltage is not controlled by regulator, just by
> fixed voltage from MCU digital power, We get the reference voltage
> from DT.
You could use a fixed-regulator in that case in the DT, and query the
voltage through the regulator framework. There is no need for this
property.
>
> >
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-clk-div",
> >> + &info->adc_feature.clk_div_num)) {
> >> + info->adc_feature.clk_div_num = 2;
> >> + dev_info(info->dev,
> >> + "Miss adc-clk-div in dt, set divider to 2.\n");
> >> + }
> >
> >Why do you need this?
> >
> >I'd expect the use to request a frequency they wanted to sample at, and the
> >driver would try its best by fiddling with this and the clock input.
> >
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-res",
> >> + &info->adc_feature.res_mode)) {
> >> + info->adc_feature.res_mode = 12;
> >> + dev_info(info->dev,
> >> + "Miss adc-res property in dt, use 8bit
> >> + mode.\n");
> >
> >This message does not seem to match the code above it.
> >
> >Is this a hardware limitation in some instances? Or is it always possible to
> >select any of the resolutions?
>
> No limitation, just mistake for it. I have tested 8-bit, 10-bit, 12-bit mode, all pass.
Ok. So this is not necessary?
> >
> >> +
> >> + if (of_property_read_u32(np, "fsl,adc-sam-time",
> >> + &info->adc_feature.sam_time)) {
> >> + info->adc_feature.sam_time = 4;
> >> + dev_info(info->dev,
> >> + "Miss adc-sam-time property in dt, set to 4.\n");
> >> + }
> >
> >This looks like it should be handled at runtime.
>
> Driver can get the ADC sample time duration fro DT.
> fsl,adc-sam-time: ADC sample time duration, number of ADC clocks, such
> as 2, 4, 6, 8, 12, 16, 20, 24
If this is not a fixed property of the hardware, and there isn't some
limitation such that one of these values _must_ be used, I do not
believe it should be in the device tree.
Just because it is easier to put it there doesn't make putting it there
correct.
>
> How to handled at runtime ?
> Use ioctl ?
Something of that sort. Hopefully someone more familiar with the
subsystem can describe what's preferred.
> For the ADC, there have many setting, some of setting are defined
> statically, some of them get from DT. If let the setting handled at
> runtime, it need to introduce ioctl interface for the driver.
If it's a static property of the HW that cannot be probed, it should be
in the DT. If it's a limitation of the HW, that should be in the DT.
If it's an arbitrary value that does not have a significant effect on
performance or correctness, I do not see why it should be in the DT.
As far as I can see, this does not belong in the DT. The fact that you'd
need an ioctl to configure it dynamically is an orthogonal issue.
>
> >
> >> +
> >> + info->adc_feature.hw_average = of_property_read_bool(np,
> >> + "fsl,adc-hw-aver-en");
> >
> >Is hw average support not always present?
>
> Vf610 RM describe:
> 37.8.5.7 Hardware Average Function
> After the selected input is sampled and converted, the result is placed in an accumulator
> from which an average is calculated once the selected number of conversions has been
> completed. When hardware averaging is selected the completion of a single conversion
> will not set the COCOn bit.
>
> In general, hw average function is useful to reduce cpu loading.
> The function is optional for user.
Ok. If this is always possible to use, as your description implies, then
I do not see why this needs to be in the DT. Whether you choose to
enable or disable it in the driver by default, or whether you expose an
interface to userspace to configure this is an orthogonal issue.
>
> >
> >> + if (info->adc_feature.hw_average &&
> >> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
> >> + &info->adc_feature.hw_sam)) {
> >> + info->adc_feature.hw_sam = 4;
> >> + dev_info(info->dev,
> >> + "Miss adc-aver-sam-sel property in dt, set to 4.\n");
> >> + }
> >
> >Why not configure this at runtime instead?
> >
> As above said, if let the configuration set at runtime, we need to
> introduce ioctl interface for user.
> How do you think ?
An ioctl or sysfs interface sounds like the correct solution. I don't
have any strong opinion on the particular interface used, but DT is not
the right place to expose this configuration option.
>
>
> >> +
> >> + info->adc_feature.lpm = of_property_read_bool(np, "fsl,adc-low-power-
> >mode");
> >> + info->adc_feature.hs_oper = of_property_read_bool(np,
> >> + "fsl,adc-high-speed-conv");
> >
> >Please elaborate on what these mean, and why they need to be in the DT.
> >
> Yes, there have some configuration have been set in default, some of them from DT.
> I think the hw function/feature enable/disable are judged from DT.
> Some general configurations are set in default.
Sorry, but that does not answer my questions:
* What do these two properties mean?
* Why must they be in the DT?
As far as I can tell, they do not need to be in the DT.
>
> Of course, let all of them are set at runtime is better. But need to
> introduce ioctl interface for user.
I don't see this as an argument for placing these in the DT. Describe
the hardware in the DT. Anything that is expected to be changed by the
user is clearly not a property of the hardware and should not be in the
DT.
There are grey areas with some devices where a specific configuration is
far more optimal for a given platform (e.g. reserving specific portions
of memory for frame buffers). As far as I can see, none of the
properties proposed in this patch fall into that area, and sane defaults
can be selected for all of them which could later be overridden with a
userspace interface of some sort.
Thanks,
Mark.
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-28 1:25 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-28 1:25 UTC (permalink / raw)
To: Mark Rutland
Cc: jic23@kernel.org, sachin.kamat@linaro.org,
devicetree@vger.kernel.org, shawn.guo@linaro.org, Frank Li,
linux-iio@vger.kernel.org
From: Mark Rutland [mailto:mark.rutland@arm.com]
Data: Wednesday, November 27, 2013 10:18 PM
>To: Duan Fugang-B38611
>Cc: jic23@kernel.org; sachin.kamat@linaro.org; devicetree@vger.kernel.org;
>shawn.guo@linaro.org; Li Frank-B20596; linux-iio@vger.kernel.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
>
>On Wed, Nov 27, 2013 at 05:37:24AM +0000, Fugang Duan wrote:
>> From: Mark Rutland <mark.rutland@arm.com>
>> Data: Tuesday, November 26, 2013 10:26 PM
>>
>> >To: Duan Fugang-B38611
>> >Cc: jic23@kernel.org; sachin.kamat@linaro.org;
>> >devicetree@vger.kernel.org; shawn.guo@linaro.org; Li Frank-B20596;
>> >linux-iio@vger.kernel.org
>> >Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610
>> >adc driver
>> >
>> >On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
>> >> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> >> software trigger.
>> >>
>> >> Signed-off-by: Fugang Duan <B38611@freescale.com>
>> >> ---
>> >> drivers/iio/adc/Kconfig | 11 +
>> >> drivers/iio/adc/Makefile | 1 +
>> >> drivers/iio/adc/vf610_adc.c | 744
>> >> +++++++++++++++++++++++++++++++++++++++++++
>> >> 3 files changed, 756 insertions(+), 0 deletions(-)
>> >
>> >[...]
>> >
>> >> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> >> + struct device_node *np = info->dev->of_node;
>> >> +
>> >> + /*
>> >> + * set default Configuration for ADC controller
>> >> + * This config may upgrade to require from DT
>> >> + */
>> >> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
>> >> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
>> >> + info->adc_feature.calibration = true;
>> >> + info->adc_feature.tri_hw = false;
>> >> + info->adc_feature.dataov_en = true;
>> >> + info->adc_feature.ac_clk_en = false;
>> >> + info->adc_feature.dma_en = false;
>> >> + info->adc_feature.cc_en = false;
>> >> + info->adc_feature.cmp_func_en = false;
>> >> + info->adc_feature.cmp_range_en = false;
>> >> + info->adc_feature.cmp_greater_en = false;
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> >> + &info->adc_feature.pctl)) {
>> >> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>> >> + }
>> >
>> >Could you elaborate on what this means? What is this doing when
>> >pinctrl is not specified in the DT?
>> >
>>
>> As vf610 reference manually describe ADC pin control:
>>
>> 37.8.2 Input Select and Pin Control
>> The pin control registers (ADC_PC) disable the I/O port control of the
>> pins used as analog inputs. When a pin control register bit is set,
>> the following conditions are forced for the associated MCU pin:
>> * The output buffer is forced to its high impedance state.
>> * The input buffer is disabled. A read of the I/O port returns a zero
>> for any pin with its input buffer disabled.
>> * The pullup is disabled.
>>
>> So, when the relative ADC pin is used as Sliding rheostat test, disable the
>pin control.
>>
>>
>> >> +
>> >> + if (info->vref)
>> >> + info->vref_uv = regulator_get_voltage(info->vref);
>> >> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>> >> + dev_err(info->dev,
>> >> + "Miss adc-vref property or vref regulator
>> >> + in the DT.\n");
>> >
>> >Query the regulator instead.
>>
>> When the ADC reference voltage is not controlled by regulator, just by
>> fixed voltage from MCU digital power, We get the reference voltage
>> from DT.
>
>You could use a fixed-regulator in that case in the DT, and query the voltage
>through the regulator framework. There is no need for this property.
>
>>
>> >
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> >> + &info->adc_feature.clk_div_num)) {
>> >> + info->adc_feature.clk_div_num = 2;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> >> + }
>> >
>> >Why do you need this?
>> >
>> >I'd expect the use to request a frequency they wanted to sample at,
>> >and the driver would try its best by fiddling with this and the clock input.
>> >
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-res",
>> >> + &info->adc_feature.res_mode)) {
>> >> + info->adc_feature.res_mode = 12;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-res property in dt, use 8bit
>> >> + mode.\n");
>> >
>> >This message does not seem to match the code above it.
>> >
>> >Is this a hardware limitation in some instances? Or is it always
>> >possible to select any of the resolutions?
>>
>> No limitation, just mistake for it. I have tested 8-bit, 10-bit, 12-bit mode,
>all pass.
>
>Ok. So this is not necessary?
>
>> >
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> >> + &info->adc_feature.sam_time)) {
>> >> + info->adc_feature.sam_time = 4;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-sam-time property in dt, set to 4.\n");
>> >> + }
>> >
>> >This looks like it should be handled at runtime.
>>
>> Driver can get the ADC sample time duration fro DT.
>> fsl,adc-sam-time: ADC sample time duration, number of ADC clocks, such
>> as 2, 4, 6, 8, 12, 16, 20, 24
>
>If this is not a fixed property of the hardware, and there isn't some
>limitation such that one of these values _must_ be used, I do not believe it
>should be in the device tree.
>
>Just because it is easier to put it there doesn't make putting it there correct.
>
>>
>> How to handled at runtime ?
>> Use ioctl ?
>
>Something of that sort. Hopefully someone more familiar with the subsystem can
>describe what's preferred.
>
>> For the ADC, there have many setting, some of setting are defined
>> statically, some of them get from DT. If let the setting handled at
>> runtime, it need to introduce ioctl interface for the driver.
>
>If it's a static property of the HW that cannot be probed, it should be in the
>DT. If it's a limitation of the HW, that should be in the DT.
>
>If it's an arbitrary value that does not have a significant effect on
>performance or correctness, I do not see why it should be in the DT.
>
>As far as I can see, this does not belong in the DT. The fact that you'd need
>an ioctl to configure it dynamically is an orthogonal issue.
>
>>
>> >
>> >> +
>> >> + info->adc_feature.hw_average = of_property_read_bool(np,
>> >> + "fsl,adc-hw-aver-en");
>> >
>> >Is hw average support not always present?
>>
>> Vf610 RM describe:
>> 37.8.5.7 Hardware Average Function
>> After the selected input is sampled and converted, the result is
>> placed in an accumulator from which an average is calculated once the
>> selected number of conversions has been completed. When hardware
>> averaging is selected the completion of a single conversion will not set the
>COCOn bit.
>>
>> In general, hw average function is useful to reduce cpu loading.
>> The function is optional for user.
>
>Ok. If this is always possible to use, as your description implies, then I do
>not see why this needs to be in the DT. Whether you choose to enable or disable
>it in the driver by default, or whether you expose an interface to userspace to
>configure this is an orthogonal issue.
>
>>
>> >
>> >> + if (info->adc_feature.hw_average &&
>> >> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> >> + &info->adc_feature.hw_sam)) {
>> >> + info->adc_feature.hw_sam = 4;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-aver-sam-sel property in dt, set to
>4.\n");
>> >> + }
>> >
>> >Why not configure this at runtime instead?
>> >
>> As above said, if let the configuration set at runtime, we need to
>> introduce ioctl interface for user.
>> How do you think ?
>
>An ioctl or sysfs interface sounds like the correct solution. I don't have any
>strong opinion on the particular interface used, but DT is not the right place
>to expose this configuration option.
>
>>
>>
>> >> +
>> >> + info->adc_feature.lpm = of_property_read_bool(np,
>> >> + "fsl,adc-low-power-
>> >mode");
>> >> + info->adc_feature.hs_oper = of_property_read_bool(np,
>> >> + "fsl,adc-high-speed-conv");
>> >
>> >Please elaborate on what these mean, and why they need to be in the DT.
>> >
>> Yes, there have some configuration have been set in default, some of them
>from DT.
>> I think the hw function/feature enable/disable are judged from DT.
>> Some general configurations are set in default.
>
>Sorry, but that does not answer my questions:
>
>* What do these two properties mean?
>* Why must they be in the DT?
>
>As far as I can tell, they do not need to be in the DT.
>
>>
>> Of course, let all of them are set at runtime is better. But need to
>> introduce ioctl interface for user.
>
>I don't see this as an argument for placing these in the DT. Describe the
>hardware in the DT. Anything that is expected to be changed by the user is
>clearly not a property of the hardware and should not be in the DT.
>
>There are grey areas with some devices where a specific configuration is far
>more optimal for a given platform (e.g. reserving specific portions of memory
>for frame buffers). As far as I can see, none of the properties proposed in
>this patch fall into that area, and sane defaults can be selected for all of
>them which could later be overridden with a userspace interface of some sort.
>
>Thanks,
>Mark.
Thanks, Mark.
Anyway, I need to re-architect the ADC configuration, as you suggestion, move some setting from DT, add it for runtime.
For iio subsystem, there have some sysfs interface for related ADC setting, but other runtime setting need ioctl interface.
Thanks very much!
Andy
^ permalink raw reply [flat|nested] 41+ messages in thread* RE: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
@ 2013-11-28 1:25 ` Fugang Duan
0 siblings, 0 replies; 41+ messages in thread
From: Fugang Duan @ 2013-11-28 1:25 UTC (permalink / raw)
To: Mark Rutland
Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org, Frank Li,
linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
From: Mark Rutland [mailto:mark.rutland-5wv7dgnIgG8@public.gmane.org]
Data: Wednesday, November 27, 2013 10:18 PM
>To: Duan Fugang-B38611
>Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org; sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org;
>shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; Li Frank-B20596; linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610 adc driver
>
>On Wed, Nov 27, 2013 at 05:37:24AM +0000, Fugang Duan wrote:
>> From: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
>> Data: Tuesday, November 26, 2013 10:26 PM
>>
>> >To: Duan Fugang-B38611
>> >Cc: jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org; sachin.kamat-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org;
>> >devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; shawn.guo-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org; Li Frank-B20596;
>> >linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>> >Subject: Re: [PATCH v3 2/3] iio:adc:imx: add Freescale Vybrid vf610
>> >adc driver
>> >
>> >On Tue, Nov 26, 2013 at 10:56:33AM +0000, Fugang Duan wrote:
>> >> Add Freescale Vybrid vf610 adc driver. The driver only support ADC
>> >> software trigger.
>> >>
>> >> Signed-off-by: Fugang Duan <B38611-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
>> >> ---
>> >> drivers/iio/adc/Kconfig | 11 +
>> >> drivers/iio/adc/Makefile | 1 +
>> >> drivers/iio/adc/vf610_adc.c | 744
>> >> +++++++++++++++++++++++++++++++++++++++++++
>> >> 3 files changed, 756 insertions(+), 0 deletions(-)
>> >
>> >[...]
>> >
>> >> +static void vf610_adc_cfg_of_init(struct vf610_adc *info) {
>> >> + struct device_node *np = info->dev->of_node;
>> >> +
>> >> + /*
>> >> + * set default Configuration for ADC controller
>> >> + * This config may upgrade to require from DT
>> >> + */
>> >> + info->adc_feature.clk_sel = ADCIOC_BUSCLK_SET;
>> >> + info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET;
>> >> + info->adc_feature.calibration = true;
>> >> + info->adc_feature.tri_hw = false;
>> >> + info->adc_feature.dataov_en = true;
>> >> + info->adc_feature.ac_clk_en = false;
>> >> + info->adc_feature.dma_en = false;
>> >> + info->adc_feature.cc_en = false;
>> >> + info->adc_feature.cmp_func_en = false;
>> >> + info->adc_feature.cmp_range_en = false;
>> >> + info->adc_feature.cmp_greater_en = false;
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-io-pinctl",
>> >> + &info->adc_feature.pctl)) {
>> >> + info->adc_feature.pctl = VF610_ADC_IOPCTL5;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-io-pinctl in dt, enable pin SE5.\n");
>> >> + }
>> >
>> >Could you elaborate on what this means? What is this doing when
>> >pinctrl is not specified in the DT?
>> >
>>
>> As vf610 reference manually describe ADC pin control:
>>
>> 37.8.2 Input Select and Pin Control
>> The pin control registers (ADC_PC) disable the I/O port control of the
>> pins used as analog inputs. When a pin control register bit is set,
>> the following conditions are forced for the associated MCU pin:
>> * The output buffer is forced to its high impedance state.
>> * The input buffer is disabled. A read of the I/O port returns a zero
>> for any pin with its input buffer disabled.
>> * The pullup is disabled.
>>
>> So, when the relative ADC pin is used as Sliding rheostat test, disable the
>pin control.
>>
>>
>> >> +
>> >> + if (info->vref)
>> >> + info->vref_uv = regulator_get_voltage(info->vref);
>> >> + else if (of_property_read_u32(np, "fsl,adc-vref", &info->vref_uv))
>> >> + dev_err(info->dev,
>> >> + "Miss adc-vref property or vref regulator
>> >> + in the DT.\n");
>> >
>> >Query the regulator instead.
>>
>> When the ADC reference voltage is not controlled by regulator, just by
>> fixed voltage from MCU digital power, We get the reference voltage
>> from DT.
>
>You could use a fixed-regulator in that case in the DT, and query the voltage
>through the regulator framework. There is no need for this property.
>
>>
>> >
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-clk-div",
>> >> + &info->adc_feature.clk_div_num)) {
>> >> + info->adc_feature.clk_div_num = 2;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-clk-div in dt, set divider to 2.\n");
>> >> + }
>> >
>> >Why do you need this?
>> >
>> >I'd expect the use to request a frequency they wanted to sample at,
>> >and the driver would try its best by fiddling with this and the clock input.
>> >
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-res",
>> >> + &info->adc_feature.res_mode)) {
>> >> + info->adc_feature.res_mode = 12;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-res property in dt, use 8bit
>> >> + mode.\n");
>> >
>> >This message does not seem to match the code above it.
>> >
>> >Is this a hardware limitation in some instances? Or is it always
>> >possible to select any of the resolutions?
>>
>> No limitation, just mistake for it. I have tested 8-bit, 10-bit, 12-bit mode,
>all pass.
>
>Ok. So this is not necessary?
>
>> >
>> >> +
>> >> + if (of_property_read_u32(np, "fsl,adc-sam-time",
>> >> + &info->adc_feature.sam_time)) {
>> >> + info->adc_feature.sam_time = 4;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-sam-time property in dt, set to 4.\n");
>> >> + }
>> >
>> >This looks like it should be handled at runtime.
>>
>> Driver can get the ADC sample time duration fro DT.
>> fsl,adc-sam-time: ADC sample time duration, number of ADC clocks, such
>> as 2, 4, 6, 8, 12, 16, 20, 24
>
>If this is not a fixed property of the hardware, and there isn't some
>limitation such that one of these values _must_ be used, I do not believe it
>should be in the device tree.
>
>Just because it is easier to put it there doesn't make putting it there correct.
>
>>
>> How to handled at runtime ?
>> Use ioctl ?
>
>Something of that sort. Hopefully someone more familiar with the subsystem can
>describe what's preferred.
>
>> For the ADC, there have many setting, some of setting are defined
>> statically, some of them get from DT. If let the setting handled at
>> runtime, it need to introduce ioctl interface for the driver.
>
>If it's a static property of the HW that cannot be probed, it should be in the
>DT. If it's a limitation of the HW, that should be in the DT.
>
>If it's an arbitrary value that does not have a significant effect on
>performance or correctness, I do not see why it should be in the DT.
>
>As far as I can see, this does not belong in the DT. The fact that you'd need
>an ioctl to configure it dynamically is an orthogonal issue.
>
>>
>> >
>> >> +
>> >> + info->adc_feature.hw_average = of_property_read_bool(np,
>> >> + "fsl,adc-hw-aver-en");
>> >
>> >Is hw average support not always present?
>>
>> Vf610 RM describe:
>> 37.8.5.7 Hardware Average Function
>> After the selected input is sampled and converted, the result is
>> placed in an accumulator from which an average is calculated once the
>> selected number of conversions has been completed. When hardware
>> averaging is selected the completion of a single conversion will not set the
>COCOn bit.
>>
>> In general, hw average function is useful to reduce cpu loading.
>> The function is optional for user.
>
>Ok. If this is always possible to use, as your description implies, then I do
>not see why this needs to be in the DT. Whether you choose to enable or disable
>it in the driver by default, or whether you expose an interface to userspace to
>configure this is an orthogonal issue.
>
>>
>> >
>> >> + if (info->adc_feature.hw_average &&
>> >> + of_property_read_u32(np, "fsl,adc-aver-sam-sel",
>> >> + &info->adc_feature.hw_sam)) {
>> >> + info->adc_feature.hw_sam = 4;
>> >> + dev_info(info->dev,
>> >> + "Miss adc-aver-sam-sel property in dt, set to
>4.\n");
>> >> + }
>> >
>> >Why not configure this at runtime instead?
>> >
>> As above said, if let the configuration set at runtime, we need to
>> introduce ioctl interface for user.
>> How do you think ?
>
>An ioctl or sysfs interface sounds like the correct solution. I don't have any
>strong opinion on the particular interface used, but DT is not the right place
>to expose this configuration option.
>
>>
>>
>> >> +
>> >> + info->adc_feature.lpm = of_property_read_bool(np,
>> >> + "fsl,adc-low-power-
>> >mode");
>> >> + info->adc_feature.hs_oper = of_property_read_bool(np,
>> >> + "fsl,adc-high-speed-conv");
>> >
>> >Please elaborate on what these mean, and why they need to be in the DT.
>> >
>> Yes, there have some configuration have been set in default, some of them
>from DT.
>> I think the hw function/feature enable/disable are judged from DT.
>> Some general configurations are set in default.
>
>Sorry, but that does not answer my questions:
>
>* What do these two properties mean?
>* Why must they be in the DT?
>
>As far as I can tell, they do not need to be in the DT.
>
>>
>> Of course, let all of them are set at runtime is better. But need to
>> introduce ioctl interface for user.
>
>I don't see this as an argument for placing these in the DT. Describe the
>hardware in the DT. Anything that is expected to be changed by the user is
>clearly not a property of the hardware and should not be in the DT.
>
>There are grey areas with some devices where a specific configuration is far
>more optimal for a given platform (e.g. reserving specific portions of memory
>for frame buffers). As far as I can see, none of the properties proposed in
>this patch fall into that area, and sane defaults can be selected for all of
>them which could later be overridden with a userspace interface of some sort.
>
>Thanks,
>Mark.
Thanks, Mark.
Anyway, I need to re-architect the ADC configuration, as you suggestion, move some setting from DT, add it for runtime.
For iio subsystem, there have some sysfs interface for related ADC setting, but other runtime setting need ioctl interface.
Thanks very much!
Andy
^ permalink raw reply [flat|nested] 41+ messages in thread