Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1522843 > unrolled thread

[PATCH v3 0/6] Add support for STM32 ADC

Started byFabrice Gasnier <fabrice.gasnier@st.com>
First post2016-11-15 16:40 +0100
Last post2016-11-19 13:30 +0100
Articles 7 — 2 participants

Back to article view | Back to linux.kernel


Contents

  [PATCH v3 0/6] Add support for STM32 ADC Fabrice Gasnier <fabrice.gasnier@st.com> - 2016-11-15 16:40 +0100
    [PATCH v3 6/6] ARM: dts: stm32f429: enable adc on eval board Fabrice Gasnier <fabrice.gasnier@st.com> - 2016-11-15 16:40 +0100
    [PATCH v3 5/6] ARM: dts: stm32f429: Add adc support Fabrice Gasnier <fabrice.gasnier@st.com> - 2016-11-15 16:40 +0100
    [PATCH v3 3/6] iio: adc: Add support for STM32 ADC Fabrice Gasnier <fabrice.gasnier@st.com> - 2016-11-15 16:40 +0100
      Re: [PATCH v3 3/6] iio: adc: Add support for STM32 ADC Jonathan Cameron <jic23@kernel.org> - 2016-11-19 13:20 +0100
    [PATCH v3 4/6] ARM: configs: stm32: enable ADC driver Fabrice Gasnier <fabrice.gasnier@st.com> - 2016-11-15 16:40 +0100
      Re: [PATCH v3 4/6] ARM: configs: stm32: enable ADC driver Jonathan Cameron <jic23@kernel.org> - 2016-11-19 13:30 +0100

#1522843 — [PATCH v3 0/6] Add support for STM32 ADC

FromFabrice Gasnier <fabrice.gasnier@st.com>
Date2016-11-15 16:40 +0100
Subject[PATCH v3 0/6] Add support for STM32 ADC
Message-ID<sDO01-6JS-5@gated-at.bofh.it>
This series adds support for STM32F4 ADC into IIO framework.
STM32F4 ADC is a 12-bit successive approximation analog-to-digital
converter with multiplexed input channels. Conversions can
be performed in single, continuous, scan or discontinuous mode.
Conversions can be launched in software or using hardware triggers.

This driver has been developed and tested on STM32F429 eval board.
It consist of a core driver, to manage common resources shared
between up to 3 ADC instances and an ADC driver to manage each adc
instance.

Changes in v3:
- Core driver moved to iio/adc.
- Build fix.
- Updates following Jonathan's and Lars's remarks.
- Binding: adc child clock is mandatory.

Changes in v2:
- Replace single driver model by MFD approach, to handle up to 3 ADCs
  as separate devices. Each ADC device then registers a unique IIO
  device.
- Make driver as simple as possible for the first instance, to ease
  review. For now, I dropped complexity by removing injected support,
  triggered buffer mode, dmas.
- Removed abstraction layer (indirection routines, ops) as only stm32f4
  is supported.

Fabrice Gasnier (6):
  Documentation: dt-bindings: Document STM32 ADC DT bindings
  iio: adc: Add support for STM32 ADC core
  iio: adc: Add support for STM32 ADC
  ARM: configs: stm32: enable ADC driver
  ARM: dts: stm32f429: Add adc support
  ARM: dts: stm32f429: enable adc on eval board

 .../devicetree/bindings/iio/adc/st,stm32-adc.txt   |  83 ++++
 arch/arm/boot/dts/stm32429i-eval.dts               |  25 +
 arch/arm/boot/dts/stm32f429.dtsi                   |  49 ++
 arch/arm/configs/stm32_defconfig                   |   3 +
 drivers/iio/adc/Kconfig                            |  23 +
 drivers/iio/adc/Makefile                           |   2 +
 drivers/iio/adc/stm32-adc-core.c                   | 303 ++++++++++++
 drivers/iio/adc/stm32-adc-core.h                   |  52 +++
 drivers/iio/adc/stm32-adc.c                        | 518 +++++++++++++++++++++
 9 files changed, 1058 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/st,stm32-adc.txt
 create mode 100644 drivers/iio/adc/stm32-adc-core.c
 create mode 100644 drivers/iio/adc/stm32-adc-core.h
 create mode 100644 drivers/iio/adc/stm32-adc.c

-- 
1.9.1

[toc] | [next] | [standalone]


#1522845 — [PATCH v3 6/6] ARM: dts: stm32f429: enable adc on eval board

FromFabrice Gasnier <fabrice.gasnier@st.com>
Date2016-11-15 16:40 +0100
Subject[PATCH v3 6/6] ARM: dts: stm32f429: enable adc on eval board
Message-ID<sDO02-6JS-33@gated-at.bofh.it>
In reply to#1522843
Enable analog to digital converter on stm32f429i-eval board.

Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
---
 arch/arm/boot/dts/stm32429i-eval.dts | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/arch/arm/boot/dts/stm32429i-eval.dts b/arch/arm/boot/dts/stm32429i-eval.dts
index 6bfc595..c144735 100644
--- a/arch/arm/boot/dts/stm32429i-eval.dts
+++ b/arch/arm/boot/dts/stm32429i-eval.dts
@@ -65,6 +65,20 @@
 		serial0 = &usart1;
 	};
 
+	regulators {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg_vref: regulator@0 {
+			compatible = "regulator-fixed";
+			reg = <0>;
+			regulator-name = "vref";
+			regulator-min-microvolt = <3300000>;
+			regulator-max-microvolt = <3300000>;
+		};
+	};
+
 	leds {
 		compatible = "gpio-leds";
 		green {
@@ -123,3 +137,14 @@
 	pinctrl-names = "default";
 	status = "okay";
 };
+
+&adc {
+	pinctrl-names = "default";
+	pinctrl-0 = <&adc3_in8_pin>;
+	vref-supply = <&reg_vref>;
+	status = "okay";
+	adc3: adc@200 {
+		st,adc-channels = <8>;
+		status = "okay";
+	};
+};
-- 
1.9.1

[toc] | [prev] | [next] | [standalone]


#1522847 — [PATCH v3 5/6] ARM: dts: stm32f429: Add adc support

FromFabrice Gasnier <fabrice.gasnier@st.com>
Date2016-11-15 16:40 +0100
Subject[PATCH v3 5/6] ARM: dts: stm32f429: Add adc support
Message-ID<sDO03-6JS-59@gated-at.bofh.it>
In reply to#1522843
Add adc support & pinctrl analog phandle (adc3_in8) to stm32f429.

Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
---
 arch/arm/boot/dts/stm32f429.dtsi | 49 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/arch/arm/boot/dts/stm32f429.dtsi b/arch/arm/boot/dts/stm32f429.dtsi
index 336ee4f..f198132 100644
--- a/arch/arm/boot/dts/stm32f429.dtsi
+++ b/arch/arm/boot/dts/stm32f429.dtsi
@@ -172,6 +172,49 @@
 			status = "disabled";
 		};
 
+		adc: adc@40012000 {
+			compatible = "st,stm32f4-adc-core";
+			reg = <0x40012000 0x400>;
+			interrupts = <18>;
+			clocks = <&rcc 0 168>;
+			clock-names = "adc";
+			interrupt-controller;
+			#interrupt-cells = <1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+
+			adc1: adc@0 {
+				compatible = "st,stm32f4-adc";
+				#io-channel-cells = <1>;
+				reg = <0x0>;
+				clocks = <&rcc 0 168>;
+				interrupt-parent = <&adc>;
+				interrupts = <0>;
+				status = "disabled";
+			};
+
+			adc2: adc@100 {
+				compatible = "st,stm32f4-adc";
+				#io-channel-cells = <1>;
+				reg = <0x100>;
+				clocks = <&rcc 0 169>;
+				interrupt-parent = <&adc>;
+				interrupts = <1>;
+				status = "disabled";
+			};
+
+			adc3: adc@200 {
+				compatible = "st,stm32f4-adc";
+				#io-channel-cells = <1>;
+				reg = <0x200>;
+				clocks = <&rcc 0 170>;
+				interrupt-parent = <&adc>;
+				interrupts = <2>;
+				status = "disabled";
+			};
+		};
+
 		syscfg: system-config@40013800 {
 			compatible = "syscon";
 			reg = <0x40013800 0x400>;
@@ -332,6 +375,12 @@
 					slew-rate = <2>;
 				};
 			};
+
+			adc3_in8_pin: adc@200 {
+				pins {
+					pinmux = <STM32F429_PF10_FUNC_ANALOG>;
+				};
+			};
 		};
 
 		rcc: rcc@40023810 {
-- 
1.9.1

[toc] | [prev] | [next] | [standalone]


#1522849 — [PATCH v3 3/6] iio: adc: Add support for STM32 ADC

FromFabrice Gasnier <fabrice.gasnier@st.com>
Date2016-11-15 16:40 +0100
Subject[PATCH v3 3/6] iio: adc: Add support for STM32 ADC
Message-ID<sDO03-6JS-57@gated-at.bofh.it>
In reply to#1522843
This patch adds support for STMicroelectronics STM32 MCU's analog to
digital converter.

Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
---
 drivers/iio/adc/Kconfig     |  10 +
 drivers/iio/adc/Makefile    |   1 +
 drivers/iio/adc/stm32-adc.c | 518 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 529 insertions(+)
 create mode 100644 drivers/iio/adc/stm32-adc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index ff30239..f93b990 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -432,6 +432,16 @@ config STM32_ADC_CORE
 	  This driver can also be built as a module.  If so, the module
 	  will be called stm32-adc-core.
 
+config STM32_ADC
+	tristate "STMicroelectronics STM32 adc"
+	depends on STM32_ADC_CORE
+	help
+	  Say yes here to build support for STMicroelectronics stm32 Analog
+	  to Digital Converter (ADC).
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called stm32-adc.
+
 config STX104
 	tristate "Apex Embedded Systems STX104 driver"
 	depends on X86 && ISA_BUS_API
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index a1e8f44..8e02a94 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
 obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
 obj-$(CONFIG_STX104) += stx104.o
 obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
+obj-$(CONFIG_STM32_ADC) += stm32-adc.o
 obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
 obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
 obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
new file mode 100644
index 0000000..5715e79
--- /dev/null
+++ b/drivers/iio/adc/stm32-adc.c
@@ -0,0 +1,518 @@
+/*
+ * This file is part of STM32 ADC driver
+ *
+ * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
+ * Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
+ *
+ * License type: GPLv2
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+
+#include "stm32-adc-core.h"
+
+/* STM32F4 - Registers for each ADC instance */
+#define STM32F4_ADC_SR			0x00
+#define STM32F4_ADC_CR1			0x04
+#define STM32F4_ADC_CR2			0x08
+#define STM32F4_ADC_SMPR1		0x0C
+#define STM32F4_ADC_SMPR2		0x10
+#define STM32F4_ADC_HTR			0x24
+#define STM32F4_ADC_LTR			0x28
+#define STM32F4_ADC_SQR1		0x2C
+#define STM32F4_ADC_SQR2		0x30
+#define STM32F4_ADC_SQR3		0x34
+#define STM32F4_ADC_JSQR		0x38
+#define STM32F4_ADC_JDR1		0x3C
+#define STM32F4_ADC_JDR2		0x40
+#define STM32F4_ADC_JDR3		0x44
+#define STM32F4_ADC_JDR4		0x48
+#define STM32F4_ADC_DR			0x4C
+
+/* STM32F4_ADC_SR - bit fields */
+#define STM32F4_STRT			BIT(4)
+#define STM32F4_EOC			BIT(1)
+
+/* STM32F4_ADC_CR1 - bit fields */
+#define STM32F4_SCAN			BIT(8)
+#define STM32F4_EOCIE			BIT(5)
+
+/* STM32F4_ADC_CR2 - bit fields */
+#define STM32F4_SWSTART			BIT(30)
+#define STM32F4_EXTEN_MASK		GENMASK(29, 28)
+#define STM32F4_EOCS			BIT(10)
+#define STM32F4_ADON			BIT(0)
+
+/* STM32F4_ADC_SQR1 - bit fields */
+#define STM32F4_L_SHIFT			20
+#define STM32F4_L_MASK			GENMASK(23, 20)
+
+/* STM32F4_ADC_SQR3 - bit fields */
+#define STM32F4_SQ1_SHIFT		0
+#define STM32F4_SQ1_MASK		GENMASK(4, 0)
+
+#define STM32_ADC_TIMEOUT_US		100000
+#define STM32_ADC_TIMEOUT	(msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000))
+
+/**
+ * struct stm32_adc - private data of each ADC IIO instance
+ * @common:		reference to ADC block common data
+ * @offset:		ADC instance register offset in ADC block
+ * @completion:		end of single conversion completion
+ * @buffer:		data buffer
+ * @clk:		clock for this adc instance
+ * @irq:		interrupt for this adc instance
+ * @lock:		spinlock
+ */
+struct stm32_adc {
+	struct stm32_adc_common	*common;
+	u32			offset;
+	struct completion	completion;
+	u16			*buffer;
+	struct clk		*clk;
+	int			irq;
+	spinlock_t		lock;		/* interrupt lock */
+};
+
+/**
+ * struct stm32_adc_chan_spec - specification of stm32 adc channel
+ * @type:	IIO channel type
+ * @channel:	channel number (single ended)
+ * @name:	channel name (single ended)
+ */
+struct stm32_adc_chan_spec {
+	enum iio_chan_type	type;
+	int			channel;
+	const char		*name;
+};
+
+/* Input definitions common for all STM32F4 instances */
+static const struct stm32_adc_chan_spec stm32f4_adc123_channels[] = {
+	{ IIO_VOLTAGE, 0, "in0" },
+	{ IIO_VOLTAGE, 1, "in1" },
+	{ IIO_VOLTAGE, 2, "in2" },
+	{ IIO_VOLTAGE, 3, "in3" },
+	{ IIO_VOLTAGE, 4, "in4" },
+	{ IIO_VOLTAGE, 5, "in5" },
+	{ IIO_VOLTAGE, 6, "in6" },
+	{ IIO_VOLTAGE, 7, "in7" },
+	{ IIO_VOLTAGE, 8, "in8" },
+	{ IIO_VOLTAGE, 9, "in9" },
+	{ IIO_VOLTAGE, 10, "in10" },
+	{ IIO_VOLTAGE, 11, "in11" },
+	{ IIO_VOLTAGE, 12, "in12" },
+	{ IIO_VOLTAGE, 13, "in13" },
+	{ IIO_VOLTAGE, 14, "in14" },
+	{ IIO_VOLTAGE, 15, "in15" },
+};
+
+/**
+ * STM32 ADC registers access routines
+ * @adc: stm32 adc instance
+ * @reg: reg offset in adc instance
+ *
+ * Note: All instances share same base, with 0x0, 0x100 or 0x200 offset resp.
+ * for adc1, adc2 and adc3.
+ */
+static u32 stm32_adc_readl(struct stm32_adc *adc, u32 reg)
+{
+	return readl_relaxed(adc->common->base + adc->offset + reg);
+}
+
+static u16 stm32_adc_readw(struct stm32_adc *adc, u32 reg)
+{
+	return readw_relaxed(adc->common->base + adc->offset + reg);
+}
+
+static void stm32_adc_writel(struct stm32_adc *adc, u32 reg, u32 val)
+{
+	writel_relaxed(val, adc->common->base + adc->offset + reg);
+}
+
+static void stm32_adc_set_bits(struct stm32_adc *adc, u32 reg, u32 bits)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&adc->lock, flags);
+	stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) | bits);
+	spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+static void stm32_adc_clr_bits(struct stm32_adc *adc, u32 reg, u32 bits)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&adc->lock, flags);
+	stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) & ~bits);
+	spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+/**
+ * stm32_adc_conv_irq_enable() - Enable end of conversion interrupt
+ * @adc: stm32 adc instance
+ */
+static void stm32_adc_conv_irq_enable(struct stm32_adc *adc)
+{
+	stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_EOCIE);
+};
+
+/**
+ * stm32_adc_conv_irq_disable() - Disable end of conversion interrupt
+ * @adc: stm32 adc instance
+ */
+static void stm32_adc_conv_irq_disable(struct stm32_adc *adc)
+{
+	stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_EOCIE);
+}
+
+/**
+ * stm32_adc_start_conv() - Start conversions for regular channels.
+ * @adc: stm32 adc instance
+ */
+static void stm32_adc_start_conv(struct stm32_adc *adc)
+{
+	stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN);
+	stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_EOCS | STM32F4_ADON);
+
+	/* Wait for Power-up time (tSTAB from datasheet) */
+	usleep_range(2, 3);
+
+	/* Software start ? (e.g. trigger detection disabled ?) */
+	if (!(stm32_adc_readl(adc, STM32F4_ADC_CR2) & STM32F4_EXTEN_MASK))
+		stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_SWSTART);
+}
+
+static void stm32_adc_stop_conv(struct stm32_adc *adc)
+{
+	stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK);
+	stm32_adc_clr_bits(adc, STM32F4_ADC_SR, STM32F4_STRT);
+
+	stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN);
+	stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_ADON);
+}
+
+/**
+ * stm32_adc_single_conv() - Performs a single conversion
+ * @indio_dev: IIO device
+ * @chan: IIO channel
+ * @res: conversion result
+ *
+ * The function performs a single conversion on a given channel:
+ * - Program sequencer with one channel (e.g. in SQ1 with len = 1)
+ * - Use SW trigger
+ * - Start conversion, then wait for interrupt completion.
+ */
+static int stm32_adc_single_conv(struct iio_dev *indio_dev,
+				 const struct iio_chan_spec *chan,
+				 int *res)
+{
+	struct stm32_adc *adc = iio_priv(indio_dev);
+	long timeout;
+	u32 val;
+	u16 result;
+	int ret;
+
+	reinit_completion(&adc->completion);
+
+	adc->buffer = &result;
+
+	/* Program chan number in regular sequence */
+	val = stm32_adc_readl(adc, STM32F4_ADC_SQR3);
+	val &= ~STM32F4_SQ1_MASK;
+	val |= chan->channel << STM32F4_SQ1_SHIFT;
+	stm32_adc_writel(adc, STM32F4_ADC_SQR3, val);
+
+	/* Set regular sequence len (0 for 1 conversion) */
+	stm32_adc_clr_bits(adc, STM32F4_ADC_SQR1, STM32F4_L_MASK);
+
+	/* Trigger detection disabled (conversion can be launched in SW) */
+	stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK);
+
+	stm32_adc_conv_irq_enable(adc);
+
+	stm32_adc_start_conv(adc);
+
+	timeout = wait_for_completion_interruptible_timeout(
+					&adc->completion, STM32_ADC_TIMEOUT);
+	if (timeout == 0) {
+		ret = -ETIMEDOUT;
+	} else if (timeout < 0) {
+		ret = timeout;
+	} else {
+		*res = result;
+		ret = IIO_VAL_INT;
+	}
+
+	stm32_adc_stop_conv(adc);
+
+	stm32_adc_conv_irq_disable(adc);
+
+	return ret;
+}
+
+static int stm32_adc_read_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int *val, int *val2, long mask)
+{
+	struct stm32_adc *adc = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			return ret;
+		if (chan->type == IIO_VOLTAGE)
+			ret = stm32_adc_single_conv(indio_dev, chan, val);
+		else
+			ret = -EINVAL;
+		iio_device_release_direct_mode(indio_dev);
+		return ret;
+
+	case IIO_CHAN_INFO_SCALE:
+		*val = adc->common->vref_mv;
+		*val2 = chan->scan_type.realbits;
+		return IIO_VAL_FRACTIONAL_LOG2;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static irqreturn_t stm32_adc_isr(int irq, void *data)
+{
+	struct stm32_adc *adc = data;
+	u32 status = stm32_adc_readl(adc, STM32F4_ADC_SR);
+
+	if (status & STM32F4_EOC) {
+		*adc->buffer = stm32_adc_readw(adc, STM32F4_ADC_DR);
+		complete(&adc->completion);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int stm32_adc_of_xlate(struct iio_dev *indio_dev,
+			      const struct of_phandle_args *iiospec)
+{
+	int i;
+
+	for (i = 0; i < indio_dev->num_channels; i++)
+		if (indio_dev->channels[i].channel == iiospec->args[0])
+			return i;
+
+	return -EINVAL;
+}
+
+/**
+ * stm32_adc_debugfs_reg_access - read or write register value
+ *
+ * To read a value from an ADC register:
+ *   echo [ADC reg offset] > direct_reg_access
+ *   cat direct_reg_access
+ *
+ * To write a value in a ADC register:
+ *   echo [ADC_reg_offset] [value] > direct_reg_access
+ */
+static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev,
+					unsigned reg, unsigned writeval,
+					unsigned *readval)
+{
+	struct stm32_adc *adc = iio_priv(indio_dev);
+
+	if (!readval)
+		stm32_adc_writel(adc, reg, writeval);
+	else
+		*readval = stm32_adc_readl(adc, reg);
+
+	return 0;
+}
+
+static const struct iio_info stm32_adc_iio_info = {
+	.read_raw = stm32_adc_read_raw,
+	.debugfs_reg_access = stm32_adc_debugfs_reg_access,
+	.of_xlate = stm32_adc_of_xlate,
+	.driver_module = THIS_MODULE,
+};
+
+static void stm32_adc_chan_init_one(struct iio_dev *indio_dev,
+				    struct iio_chan_spec *chan,
+				    const struct stm32_adc_chan_spec *channel,
+				    int scan_index)
+{
+	chan->type = channel->type;
+	chan->channel = channel->channel;
+	chan->datasheet_name = channel->name;
+	chan->scan_index = scan_index;
+	chan->indexed = 1;
+	chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
+	chan->scan_type.sign = 'u';
+	chan->scan_type.realbits = 12;
+	chan->scan_type.storagebits = 16;
+}
+
+static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
+{
+	struct device_node *node = indio_dev->dev.of_node;
+	struct property *prop;
+	const __be32 *cur;
+	struct iio_chan_spec *channels;
+	int scan_index = 0, num_channels;
+	u32 val;
+
+	num_channels = of_property_count_u32_elems(node, "st,adc-channels");
+	if (num_channels < 0 ||
+	    num_channels >= ARRAY_SIZE(stm32f4_adc123_channels)) {
+		dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
+		return num_channels < 0 ? num_channels : -EINVAL;
+	}
+
+	channels = devm_kcalloc(&indio_dev->dev, num_channels,
+				sizeof(struct iio_chan_spec), GFP_KERNEL);
+	if (!channels)
+		return -ENOMEM;
+
+	of_property_for_each_u32(node, "st,adc-channels", prop, cur, val) {
+		if (val >= ARRAY_SIZE(stm32f4_adc123_channels)) {
+			dev_err(&indio_dev->dev, "Invalid channel %d\n", val);
+			return -EINVAL;
+		}
+		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
+					&stm32f4_adc123_channels[val],
+					scan_index);
+		scan_index++;
+	}
+
+	indio_dev->num_channels = scan_index;
+	indio_dev->channels = channels;
+
+	return 0;
+}
+
+static int stm32_adc_probe(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev;
+	struct stm32_adc *adc;
+	int ret;
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	adc = iio_priv(indio_dev);
+	adc->common = dev_get_drvdata(pdev->dev.parent);
+	spin_lock_init(&adc->lock);
+	init_completion(&adc->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 = &stm32_adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	platform_set_drvdata(pdev, adc);
+
+	ret = of_property_read_u32(pdev->dev.of_node, "reg", &adc->offset);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "missing reg property\n");
+		return -EINVAL;
+	}
+
+	adc->irq = platform_get_irq(pdev, 0);
+	if (adc->irq < 0) {
+		dev_err(&pdev->dev, "failed to get irq\n");
+		return adc->irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, adc->irq, stm32_adc_isr,
+			       0, pdev->name, adc);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request IRQ\n");
+		return ret;
+	}
+
+	adc->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(adc->clk)) {
+		dev_err(&pdev->dev, "Can't get clock\n");
+		return PTR_ERR(adc->clk);
+	}
+
+	ret = clk_prepare_enable(adc->clk);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "clk enable failed\n");
+		return ret;
+	}
+
+	ret = stm32_adc_chan_of_init(indio_dev);
+	if (ret < 0)
+		goto err_clk_disable;
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "iio dev register failed\n");
+		goto err_clk_disable;
+	}
+
+	return 0;
+
+err_clk_disable:
+	clk_disable_unprepare(adc->clk);
+
+	return ret;
+}
+
+static int stm32_adc_remove(struct platform_device *pdev)
+{
+	struct stm32_adc *adc = platform_get_drvdata(pdev);
+	struct iio_dev *indio_dev = iio_priv_to_dev(adc);
+
+	iio_device_unregister(indio_dev);
+	clk_disable_unprepare(adc->clk);
+
+	return 0;
+}
+
+static const struct of_device_id stm32_adc_of_match[] = {
+	{ .compatible = "st,stm32f4-adc" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, stm32_adc_of_match);
+
+static struct platform_driver stm32_adc_driver = {
+	.probe = stm32_adc_probe,
+	.remove = stm32_adc_remove,
+	.driver = {
+		.name = "stm32-adc",
+		.of_match_table = stm32_adc_of_match,
+	},
+};
+module_platform_driver(stm32_adc_driver);
+
+MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 ADC IIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:stm32-adc");
-- 
1.9.1

[toc] | [prev] | [next] | [standalone]


#1525983 — Re: [PATCH v3 3/6] iio: adc: Add support for STM32 ADC

FromJonathan Cameron <jic23@kernel.org>
Date2016-11-19 13:20 +0100
SubjectRe: [PATCH v3 3/6] iio: adc: Add support for STM32 ADC
Message-ID<sFcMG-4Qx-7@gated-at.bofh.it>
In reply to#1522849
On 15/11/16 15:30, Fabrice Gasnier wrote:
> This patch adds support for STMicroelectronics STM32 MCU's analog to
> digital converter.
> 
> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
Applied to the togreg branch of iio.git and pushed out as testing
for the autobuilders to play with it.

Very nice driver!

Thanks,

Jonathan
> ---
>  drivers/iio/adc/Kconfig     |  10 +
>  drivers/iio/adc/Makefile    |   1 +
>  drivers/iio/adc/stm32-adc.c | 518 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 529 insertions(+)
>  create mode 100644 drivers/iio/adc/stm32-adc.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index ff30239..f93b990 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -432,6 +432,16 @@ config STM32_ADC_CORE
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called stm32-adc-core.
>  
> +config STM32_ADC
> +	tristate "STMicroelectronics STM32 adc"
> +	depends on STM32_ADC_CORE
> +	help
> +	  Say yes here to build support for STMicroelectronics stm32 Analog
> +	  to Digital Converter (ADC).
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called stm32-adc.
> +
>  config STX104
>  	tristate "Apex Embedded Systems STX104 driver"
>  	depends on X86 && ISA_BUS_API
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index a1e8f44..8e02a94 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -42,6 +42,7 @@ obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>  obj-$(CONFIG_STX104) += stx104.o
>  obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
> +obj-$(CONFIG_STM32_ADC) += stm32-adc.o
>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>  obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
> diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
> new file mode 100644
> index 0000000..5715e79
> --- /dev/null
> +++ b/drivers/iio/adc/stm32-adc.c
> @@ -0,0 +1,518 @@
> +/*
> + * This file is part of STM32 ADC driver
> + *
> + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
> + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
> + *
> + * License type: GPLv2
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + * 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, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/iio/iio.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +
> +#include "stm32-adc-core.h"
> +
> +/* STM32F4 - Registers for each ADC instance */
> +#define STM32F4_ADC_SR			0x00
> +#define STM32F4_ADC_CR1			0x04
> +#define STM32F4_ADC_CR2			0x08
> +#define STM32F4_ADC_SMPR1		0x0C
> +#define STM32F4_ADC_SMPR2		0x10
> +#define STM32F4_ADC_HTR			0x24
> +#define STM32F4_ADC_LTR			0x28
> +#define STM32F4_ADC_SQR1		0x2C
> +#define STM32F4_ADC_SQR2		0x30
> +#define STM32F4_ADC_SQR3		0x34
> +#define STM32F4_ADC_JSQR		0x38
> +#define STM32F4_ADC_JDR1		0x3C
> +#define STM32F4_ADC_JDR2		0x40
> +#define STM32F4_ADC_JDR3		0x44
> +#define STM32F4_ADC_JDR4		0x48
> +#define STM32F4_ADC_DR			0x4C
> +
> +/* STM32F4_ADC_SR - bit fields */
> +#define STM32F4_STRT			BIT(4)
> +#define STM32F4_EOC			BIT(1)
> +
> +/* STM32F4_ADC_CR1 - bit fields */
> +#define STM32F4_SCAN			BIT(8)
> +#define STM32F4_EOCIE			BIT(5)
> +
> +/* STM32F4_ADC_CR2 - bit fields */
> +#define STM32F4_SWSTART			BIT(30)
> +#define STM32F4_EXTEN_MASK		GENMASK(29, 28)
> +#define STM32F4_EOCS			BIT(10)
> +#define STM32F4_ADON			BIT(0)
> +
> +/* STM32F4_ADC_SQR1 - bit fields */
> +#define STM32F4_L_SHIFT			20
> +#define STM32F4_L_MASK			GENMASK(23, 20)
> +
> +/* STM32F4_ADC_SQR3 - bit fields */
> +#define STM32F4_SQ1_SHIFT		0
> +#define STM32F4_SQ1_MASK		GENMASK(4, 0)
> +
> +#define STM32_ADC_TIMEOUT_US		100000
> +#define STM32_ADC_TIMEOUT	(msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000))
> +
> +/**
> + * struct stm32_adc - private data of each ADC IIO instance
> + * @common:		reference to ADC block common data
> + * @offset:		ADC instance register offset in ADC block
> + * @completion:		end of single conversion completion
> + * @buffer:		data buffer
> + * @clk:		clock for this adc instance
> + * @irq:		interrupt for this adc instance
> + * @lock:		spinlock
> + */
> +struct stm32_adc {
> +	struct stm32_adc_common	*common;
> +	u32			offset;
> +	struct completion	completion;
> +	u16			*buffer;
> +	struct clk		*clk;
> +	int			irq;
> +	spinlock_t		lock;		/* interrupt lock */
> +};
> +
> +/**
> + * struct stm32_adc_chan_spec - specification of stm32 adc channel
> + * @type:	IIO channel type
> + * @channel:	channel number (single ended)
> + * @name:	channel name (single ended)
> + */
> +struct stm32_adc_chan_spec {
> +	enum iio_chan_type	type;
> +	int			channel;
> +	const char		*name;
> +};
> +
> +/* Input definitions common for all STM32F4 instances */
> +static const struct stm32_adc_chan_spec stm32f4_adc123_channels[] = {
> +	{ IIO_VOLTAGE, 0, "in0" },
> +	{ IIO_VOLTAGE, 1, "in1" },
> +	{ IIO_VOLTAGE, 2, "in2" },
> +	{ IIO_VOLTAGE, 3, "in3" },
> +	{ IIO_VOLTAGE, 4, "in4" },
> +	{ IIO_VOLTAGE, 5, "in5" },
> +	{ IIO_VOLTAGE, 6, "in6" },
> +	{ IIO_VOLTAGE, 7, "in7" },
> +	{ IIO_VOLTAGE, 8, "in8" },
> +	{ IIO_VOLTAGE, 9, "in9" },
> +	{ IIO_VOLTAGE, 10, "in10" },
> +	{ IIO_VOLTAGE, 11, "in11" },
> +	{ IIO_VOLTAGE, 12, "in12" },
> +	{ IIO_VOLTAGE, 13, "in13" },
> +	{ IIO_VOLTAGE, 14, "in14" },
> +	{ IIO_VOLTAGE, 15, "in15" },
> +};
> +
> +/**
> + * STM32 ADC registers access routines
> + * @adc: stm32 adc instance
> + * @reg: reg offset in adc instance
> + *
> + * Note: All instances share same base, with 0x0, 0x100 or 0x200 offset resp.
> + * for adc1, adc2 and adc3.
> + */
> +static u32 stm32_adc_readl(struct stm32_adc *adc, u32 reg)
> +{
> +	return readl_relaxed(adc->common->base + adc->offset + reg);
> +}
> +
> +static u16 stm32_adc_readw(struct stm32_adc *adc, u32 reg)
> +{
> +	return readw_relaxed(adc->common->base + adc->offset + reg);
> +}
> +
> +static void stm32_adc_writel(struct stm32_adc *adc, u32 reg, u32 val)
> +{
> +	writel_relaxed(val, adc->common->base + adc->offset + reg);
> +}
> +
> +static void stm32_adc_set_bits(struct stm32_adc *adc, u32 reg, u32 bits)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&adc->lock, flags);
> +	stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) | bits);
> +	spin_unlock_irqrestore(&adc->lock, flags);
> +}
> +
> +static void stm32_adc_clr_bits(struct stm32_adc *adc, u32 reg, u32 bits)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&adc->lock, flags);
> +	stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) & ~bits);
> +	spin_unlock_irqrestore(&adc->lock, flags);
> +}
> +
> +/**
> + * stm32_adc_conv_irq_enable() - Enable end of conversion interrupt
> + * @adc: stm32 adc instance
> + */
> +static void stm32_adc_conv_irq_enable(struct stm32_adc *adc)
> +{
> +	stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_EOCIE);
> +};
> +
> +/**
> + * stm32_adc_conv_irq_disable() - Disable end of conversion interrupt
> + * @adc: stm32 adc instance
> + */
> +static void stm32_adc_conv_irq_disable(struct stm32_adc *adc)
> +{
> +	stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_EOCIE);
> +}
> +
> +/**
> + * stm32_adc_start_conv() - Start conversions for regular channels.
> + * @adc: stm32 adc instance
> + */
> +static void stm32_adc_start_conv(struct stm32_adc *adc)
> +{
> +	stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN);
> +	stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_EOCS | STM32F4_ADON);
> +
> +	/* Wait for Power-up time (tSTAB from datasheet) */
> +	usleep_range(2, 3);
> +
> +	/* Software start ? (e.g. trigger detection disabled ?) */
> +	if (!(stm32_adc_readl(adc, STM32F4_ADC_CR2) & STM32F4_EXTEN_MASK))
> +		stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_SWSTART);
> +}
> +
> +static void stm32_adc_stop_conv(struct stm32_adc *adc)
> +{
> +	stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK);
> +	stm32_adc_clr_bits(adc, STM32F4_ADC_SR, STM32F4_STRT);
> +
> +	stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN);
> +	stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_ADON);
> +}
> +
> +/**
> + * stm32_adc_single_conv() - Performs a single conversion
> + * @indio_dev: IIO device
> + * @chan: IIO channel
> + * @res: conversion result
> + *
> + * The function performs a single conversion on a given channel:
> + * - Program sequencer with one channel (e.g. in SQ1 with len = 1)
> + * - Use SW trigger
> + * - Start conversion, then wait for interrupt completion.
> + */
> +static int stm32_adc_single_conv(struct iio_dev *indio_dev,
> +				 const struct iio_chan_spec *chan,
> +				 int *res)
> +{
> +	struct stm32_adc *adc = iio_priv(indio_dev);
> +	long timeout;
> +	u32 val;
> +	u16 result;
> +	int ret;
> +
> +	reinit_completion(&adc->completion);
> +
> +	adc->buffer = &result;
> +
> +	/* Program chan number in regular sequence */
> +	val = stm32_adc_readl(adc, STM32F4_ADC_SQR3);
> +	val &= ~STM32F4_SQ1_MASK;
> +	val |= chan->channel << STM32F4_SQ1_SHIFT;
> +	stm32_adc_writel(adc, STM32F4_ADC_SQR3, val);
> +
> +	/* Set regular sequence len (0 for 1 conversion) */
> +	stm32_adc_clr_bits(adc, STM32F4_ADC_SQR1, STM32F4_L_MASK);
> +
> +	/* Trigger detection disabled (conversion can be launched in SW) */
> +	stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK);
> +
> +	stm32_adc_conv_irq_enable(adc);
> +
> +	stm32_adc_start_conv(adc);
> +
> +	timeout = wait_for_completion_interruptible_timeout(
> +					&adc->completion, STM32_ADC_TIMEOUT);
> +	if (timeout == 0) {
> +		ret = -ETIMEDOUT;
> +	} else if (timeout < 0) {
> +		ret = timeout;
> +	} else {
> +		*res = result;
> +		ret = IIO_VAL_INT;
> +	}
> +
> +	stm32_adc_stop_conv(adc);
> +
> +	stm32_adc_conv_irq_disable(adc);
> +
> +	return ret;
> +}
> +
> +static int stm32_adc_read_raw(struct iio_dev *indio_dev,
> +			      struct iio_chan_spec const *chan,
> +			      int *val, int *val2, long mask)
> +{
> +	struct stm32_adc *adc = iio_priv(indio_dev);
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +		if (chan->type == IIO_VOLTAGE)
> +			ret = stm32_adc_single_conv(indio_dev, chan, val);
> +		else
> +			ret = -EINVAL;
> +		iio_device_release_direct_mode(indio_dev);
> +		return ret;
> +
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = adc->common->vref_mv;
> +		*val2 = chan->scan_type.realbits;
> +		return IIO_VAL_FRACTIONAL_LOG2;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static irqreturn_t stm32_adc_isr(int irq, void *data)
> +{
> +	struct stm32_adc *adc = data;
> +	u32 status = stm32_adc_readl(adc, STM32F4_ADC_SR);
> +
> +	if (status & STM32F4_EOC) {
> +		*adc->buffer = stm32_adc_readw(adc, STM32F4_ADC_DR);
> +		complete(&adc->completion);
> +		return IRQ_HANDLED;
> +	}
> +
> +	return IRQ_NONE;
> +}
> +
> +static int stm32_adc_of_xlate(struct iio_dev *indio_dev,
> +			      const struct of_phandle_args *iiospec)
> +{
> +	int i;
> +
> +	for (i = 0; i < indio_dev->num_channels; i++)
> +		if (indio_dev->channels[i].channel == iiospec->args[0])
> +			return i;
> +
> +	return -EINVAL;
> +}
> +
> +/**
> + * stm32_adc_debugfs_reg_access - read or write register value
> + *
> + * To read a value from an ADC register:
> + *   echo [ADC reg offset] > direct_reg_access
> + *   cat direct_reg_access
> + *
> + * To write a value in a ADC register:
> + *   echo [ADC_reg_offset] [value] > direct_reg_access
> + */
> +static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev,
> +					unsigned reg, unsigned writeval,
> +					unsigned *readval)
> +{
> +	struct stm32_adc *adc = iio_priv(indio_dev);
> +
> +	if (!readval)
> +		stm32_adc_writel(adc, reg, writeval);
> +	else
> +		*readval = stm32_adc_readl(adc, reg);
> +
> +	return 0;
> +}
> +
> +static const struct iio_info stm32_adc_iio_info = {
> +	.read_raw = stm32_adc_read_raw,
> +	.debugfs_reg_access = stm32_adc_debugfs_reg_access,
> +	.of_xlate = stm32_adc_of_xlate,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static void stm32_adc_chan_init_one(struct iio_dev *indio_dev,
> +				    struct iio_chan_spec *chan,
> +				    const struct stm32_adc_chan_spec *channel,
> +				    int scan_index)
> +{
> +	chan->type = channel->type;
> +	chan->channel = channel->channel;
> +	chan->datasheet_name = channel->name;
> +	chan->scan_index = scan_index;
> +	chan->indexed = 1;
> +	chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> +	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
> +	chan->scan_type.sign = 'u';
> +	chan->scan_type.realbits = 12;
> +	chan->scan_type.storagebits = 16;
> +}
> +
> +static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
> +{
> +	struct device_node *node = indio_dev->dev.of_node;
> +	struct property *prop;
> +	const __be32 *cur;
> +	struct iio_chan_spec *channels;
> +	int scan_index = 0, num_channels;
> +	u32 val;
> +
> +	num_channels = of_property_count_u32_elems(node, "st,adc-channels");
> +	if (num_channels < 0 ||
> +	    num_channels >= ARRAY_SIZE(stm32f4_adc123_channels)) {
> +		dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
> +		return num_channels < 0 ? num_channels : -EINVAL;
> +	}
> +
> +	channels = devm_kcalloc(&indio_dev->dev, num_channels,
> +				sizeof(struct iio_chan_spec), GFP_KERNEL);
> +	if (!channels)
> +		return -ENOMEM;
> +
> +	of_property_for_each_u32(node, "st,adc-channels", prop, cur, val) {
> +		if (val >= ARRAY_SIZE(stm32f4_adc123_channels)) {
> +			dev_err(&indio_dev->dev, "Invalid channel %d\n", val);
> +			return -EINVAL;
> +		}
> +		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
> +					&stm32f4_adc123_channels[val],
> +					scan_index);
> +		scan_index++;
> +	}
> +
> +	indio_dev->num_channels = scan_index;
> +	indio_dev->channels = channels;
> +
> +	return 0;
> +}
> +
> +static int stm32_adc_probe(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev;
> +	struct stm32_adc *adc;
> +	int ret;
> +
> +	if (!pdev->dev.of_node)
> +		return -ENODEV;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	adc = iio_priv(indio_dev);
> +	adc->common = dev_get_drvdata(pdev->dev.parent);
> +	spin_lock_init(&adc->lock);
> +	init_completion(&adc->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 = &stm32_adc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	platform_set_drvdata(pdev, adc);
> +
> +	ret = of_property_read_u32(pdev->dev.of_node, "reg", &adc->offset);
> +	if (ret != 0) {
> +		dev_err(&pdev->dev, "missing reg property\n");
> +		return -EINVAL;
> +	}
> +
> +	adc->irq = platform_get_irq(pdev, 0);
> +	if (adc->irq < 0) {
> +		dev_err(&pdev->dev, "failed to get irq\n");
> +		return adc->irq;
> +	}
> +
> +	ret = devm_request_irq(&pdev->dev, adc->irq, stm32_adc_isr,
> +			       0, pdev->name, adc);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to request IRQ\n");
> +		return ret;
> +	}
> +
> +	adc->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(adc->clk)) {
> +		dev_err(&pdev->dev, "Can't get clock\n");
> +		return PTR_ERR(adc->clk);
> +	}
> +
> +	ret = clk_prepare_enable(adc->clk);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "clk enable failed\n");
> +		return ret;
> +	}
> +
> +	ret = stm32_adc_chan_of_init(indio_dev);
> +	if (ret < 0)
> +		goto err_clk_disable;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "iio dev register failed\n");
> +		goto err_clk_disable;
> +	}
> +
> +	return 0;
> +
> +err_clk_disable:
> +	clk_disable_unprepare(adc->clk);
> +
> +	return ret;
> +}
> +
> +static int stm32_adc_remove(struct platform_device *pdev)
> +{
> +	struct stm32_adc *adc = platform_get_drvdata(pdev);
> +	struct iio_dev *indio_dev = iio_priv_to_dev(adc);
> +
> +	iio_device_unregister(indio_dev);
> +	clk_disable_unprepare(adc->clk);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id stm32_adc_of_match[] = {
> +	{ .compatible = "st,stm32f4-adc" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, stm32_adc_of_match);
> +
> +static struct platform_driver stm32_adc_driver = {
> +	.probe = stm32_adc_probe,
> +	.remove = stm32_adc_remove,
> +	.driver = {
> +		.name = "stm32-adc",
> +		.of_match_table = stm32_adc_of_match,
> +	},
> +};
> +module_platform_driver(stm32_adc_driver);
> +
> +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
> +MODULE_DESCRIPTION("STMicroelectronics STM32 ADC IIO driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:stm32-adc");
> 

[toc] | [prev] | [next] | [standalone]


#1522851 — [PATCH v3 4/6] ARM: configs: stm32: enable ADC driver

FromFabrice Gasnier <fabrice.gasnier@st.com>
Date2016-11-15 16:40 +0100
Subject[PATCH v3 4/6] ARM: configs: stm32: enable ADC driver
Message-ID<sDO03-6JS-63@gated-at.bofh.it>
In reply to#1522843
Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
---
 arch/arm/configs/stm32_defconfig | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm/configs/stm32_defconfig b/arch/arm/configs/stm32_defconfig
index 1e5ec2a..5d241e0 100644
--- a/arch/arm/configs/stm32_defconfig
+++ b/arch/arm/configs/stm32_defconfig
@@ -57,6 +57,9 @@ CONFIG_LEDS_TRIGGERS=y
 CONFIG_LEDS_TRIGGER_HEARTBEAT=y
 CONFIG_DMADEVICES=y
 CONFIG_STM32_DMA=y
+CONFIG_IIO=y
+CONFIG_STM32_ADC_CORE=y
+CONFIG_STM32_ADC=y
 # CONFIG_FILE_LOCKING is not set
 # CONFIG_DNOTIFY is not set
 # CONFIG_INOTIFY_USER is not set
-- 
1.9.1

[toc] | [prev] | [next] | [standalone]


#1525984 — Re: [PATCH v3 4/6] ARM: configs: stm32: enable ADC driver

FromJonathan Cameron <jic23@kernel.org>
Date2016-11-19 13:30 +0100
SubjectRe: [PATCH v3 4/6] ARM: configs: stm32: enable ADC driver
Message-ID<sFcWm-4Tx-9@gated-at.bofh.it>
In reply to#1522851
On 15/11/16 15:30, Fabrice Gasnier wrote:
> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
The driver is now on it's way in.  I'm assuming this and the two device tree patches
will go via the relevant route to arm-soc.

Thanks,

Jonathan
> ---
>  arch/arm/configs/stm32_defconfig | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/arch/arm/configs/stm32_defconfig b/arch/arm/configs/stm32_defconfig
> index 1e5ec2a..5d241e0 100644
> --- a/arch/arm/configs/stm32_defconfig
> +++ b/arch/arm/configs/stm32_defconfig
> @@ -57,6 +57,9 @@ CONFIG_LEDS_TRIGGERS=y
>  CONFIG_LEDS_TRIGGER_HEARTBEAT=y
>  CONFIG_DMADEVICES=y
>  CONFIG_STM32_DMA=y
> +CONFIG_IIO=y
> +CONFIG_STM32_ADC_CORE=y
> +CONFIG_STM32_ADC=y
>  # CONFIG_FILE_LOCKING is not set
>  # CONFIG_DNOTIFY is not set
>  # CONFIG_INOTIFY_USER is not set
> 

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web