* Re: [PATCH] [media] imx: csi: retain current field order and colorimetry setting as default
From: Philipp Zabel @ 2017-04-06 15:25 UTC (permalink / raw)
To: Russell King - ARM Linux
Cc: Steve Longerbeam, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A,
kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc,
mchehab-DgEjT+Ai2ygdnm+yROfE0A, hverkuil-qWit8jRvyhVmR6Xm/wNWPw,
nick-gcszYUEDH4VrovVCs/uTlw, markus.heiser-O6JHGLzbNUwb1SvskN2V4Q,
laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw,
bparrot-l0cyMroinI0, geert-Td1EMuHUCqxL1ZNQvxDV9g,
arnd-r2nGTMty4D4, sudipm.mukherjee-Re5JQEeQqe8AvxtiuMwx3w,
minghsiu.tsai-NuS5LvNUpcJWk0Htik3J/w,
tiffany.lin-NuS5LvNUpcJWk0Htik3J/w,
jean-christophe.trotin-qxv4g6HH51o,
horms+renesas-/R6kz+dDXgpPR4JQBCEnsQ,
niklas.soderlund+renesas-1zkq55x86MTxsAP9Fp7wbw,
robert.jarzmik-GANU6spQydw, songjun.wu-UWL1GkI3JZL3oGB3hsPCZA,
andrew-ct.chen-NuS5LvNUpcJWk0Htik3J/w,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r,
shuah-DgEjT+Ai2ygdnm+yROfE0A, sakari.ailus-VuQAYsv1563Yd54FQh9/CA,
pavel-+ZI9xUNit7I, devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-media
In-Reply-To: <20170406151032.GH17774-l+eeeJia6m9URfEZ8mYm6t73F7V6hmMc@public.gmane.org>
On Thu, 2017-04-06 at 16:10 +0100, Russell King - ARM Linux wrote:
> On Thu, Apr 06, 2017 at 05:01:52PM +0200, Philipp Zabel wrote:
> > On Thu, 2017-04-06 at 15:05 +0100, Russell King - ARM Linux wrote:
> > > On Thu, Apr 06, 2017 at 03:55:29PM +0200, Philipp Zabel wrote:
> > > > +
> > > > + /* Retain current field setting as default */
> > > > + if (sdformat->format.field == V4L2_FIELD_ANY)
> > > > + sdformat->format.field = fmt->field;
> > > > +
> > > > + /* Retain current colorspace setting as default */
> > > > + if (sdformat->format.colorspace == V4L2_COLORSPACE_DEFAULT) {
> > > > + sdformat->format.colorspace = fmt->colorspace;
> > > > + if (sdformat->format.xfer_func == V4L2_XFER_FUNC_DEFAULT)
> > > > + sdformat->format.xfer_func = fmt->xfer_func;
> > > > + if (sdformat->format.ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
> > > > + sdformat->format.ycbcr_enc = fmt->ycbcr_enc;
> > > > + if (sdformat->format.quantization == V4L2_QUANTIZATION_DEFAULT)
> > > > + sdformat->format.quantization = fmt->quantization;
> > > > + } else {
> > > > + if (sdformat->format.xfer_func == V4L2_XFER_FUNC_DEFAULT) {
> > > > + sdformat->format.xfer_func =
> > > > + V4L2_MAP_XFER_FUNC_DEFAULT(
> > > > + sdformat->format.colorspace);
> > > > + }
> > > > + if (sdformat->format.ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) {
> > > > + sdformat->format.ycbcr_enc =
> > > > + V4L2_MAP_YCBCR_ENC_DEFAULT(
> > > > + sdformat->format.colorspace);
> > > > + }
> > > > + if (sdformat->format.quantization == V4L2_QUANTIZATION_DEFAULT) {
> > > > + sdformat->format.quantization =
> > > > + V4L2_MAP_QUANTIZATION_DEFAULT(
> > > > + cc->cs != IPUV3_COLORSPACE_YUV,
> > > > + sdformat->format.colorspace,
> > > > + sdformat->format.ycbcr_enc);
> > > > + }
> > > > + }
> > >
> > > Would it make sense for this to be a helper function?
> >
> > Quite possible, the next subdev that has to set frame_interval on both
> > pads manually because its upstream source pad doesn't suport
> > frame_interval might want to do the same.
>
> Hmm. I'm not sure I agree with this approach. If a subdev hardware
> does not support any modification of the colourspace or field, then
> it should not be modifyable at the source pad - it should retain the
> propagated settings from the sink pad.
This new code is only relevant for the CSI_SINK_PAD.
> I thought I had already sent a patch doing exactly that.
Yes. Right above the modification there is a call to csi_try_fmt which
will already fix up sdformat->format for the source pads. So for the
CSI_SRC_PAD_DIRECT and CSI_SRC_PAD_IDMAC this should amount to a no-op.
If might be better to move this into a separate function and only call
it if sdformat->pad == CSI_SINK_PAD.
regards
Philipp
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH V3 3/4] pwm: tegra: Add DT binding details to configure pin in suspends/resume
From: Jon Hunter @ 2017-04-06 15:26 UTC (permalink / raw)
To: Laxman Dewangan, thierry.reding, robh+dt
Cc: mark.rutland, linux-pwm, devicetree, linux-tegra, linux-kernel
In-Reply-To: <1491488461-24621-4-git-send-email-ldewangan@nvidia.com>
On 06/04/17 15:21, Laxman Dewangan wrote:
> In some of NVIDIA Tegra's platform, PWM controller is used to
> control the PWM controlled regulators. PWM signal is connected to
> the VID pin of the regulator where duty cycle of PWM signal decide
> the voltage level of the regulator output.
>
> The tristate (high impedance of PWM pin form Tegra) also define
s/form/from/
s/define/defines/
> one of the state of PWM regulator which needs to be configure in
> suspend state of system.
It maybe clearer to say that when the system enters suspend the
regulator requires the pwm output to be tristated.
> Add DT binding details to provide the pin configuration state
> from PWM and pinctrl DT node in suspend and active state of
> the system.
>
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> ---
> Changes from v1:
> - Use standard pinctrl names for sleep and active state.
>
> .../devicetree/bindings/pwm/nvidia,tegra20-pwm.txt | 43 ++++++++++++++++++++++
> 1 file changed, 43 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt
> index b4e7377..4128cdc 100644
> --- a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt
> +++ b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt
> @@ -19,6 +19,19 @@ Required properties:
> - reset-names: Must include the following entries:
> - pwm
>
> +Optional properties:
> +============================
> +In some of the interface like PWM based regulator device, it is required
> +to configure the pins differently in different states, especially in suspend
> +state of the system. The configuration of pin is provided via the pinctrl
> +DT node as detailed in the pinctrl DT binding document
> + Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
> +
> +The PWM node will have following optional properties.
> +pinctrl-names: Pin state names. Must be "default" and "sleep".
> +pinctrl-0: Node handle for the default/active state of pi configurations.
s/pi/pin/
s/Node handle/phandle/
> +pinctrl-1: Node handle for the sleep state of pin configurations.
> +
> Example:
>
> pwm: pwm@7000a000 {
> @@ -29,3 +42,33 @@ Example:
> resets = <&tegra_car 17>;
> reset-names = "pwm";
> };
> +
> +
> +Example with the pin configuration for suspend and resume:
> +=========================================================
> +Pin PE7 is used as PWM interface.
Nit-pick. On what devices? Sounds like this is verbatim. Maybe state
what device this is an example for.
Jon
--
nvpublic
^ permalink raw reply
* [PATCH 0/2] ASoC: stm32: Add I2S driver
From: olivier moysan @ 2017-04-06 15:40 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, mcoquelin.stm32,
alexandre.torgue, alsa-devel, robh, mark.rutland, devicetree,
linux-arm-kernel, olivier.moysan
Cc: arnaud.pouliquen, benjamin.gaignard
This patch-set handles the SPI/I2S IP on STM32 platforms.
It applies to STM32 platforms implementing version 2 of SPI/I2S IP like STM32H7 series.
The SPI/I2S block is a serial peripheral interface (SPI),
which can also be configured to work on I2S/PCM mode.
ASoC STM32 I2S driver only supports this I2S/PCM mode.
The I2S/PCM interface can either support full duplex or half-duplex communication
(transmitter or receiver only).
I2S driver exhibits 3 DAIs corresponding to a playback, capture or full duplex PCM device.
When the interface is configured as full duplex, the related PCM device must be opened twice.
The PCM streams are started when both playback and capture devices are opened.
The relevant DAI can be selected through Device Tree using sound-dai-cells parameter.
The I2S/PCM interface supports four audio standards:
I2S Philips standard, MSB justified standard, LSB justified standard, PCM standard
PCM standard is declined in two versions, PCM short and long,
according frame synchronization duration.
These standards can be mapped to ASoC standards as follows:
- I2S: i2s
- MSB justified standard: left_j
- LSB justified standard: right_j
- PCM short: dsp_a
PCM long format is not exposed as it does not match supported ASoC standards.
olivier moysan (2):
dt-bindings: Document STM32 I2S bindings
ASoC: stm32: Add I2S driver
.../devicetree/bindings/sound/st,stm32h7-i2s.txt | 71 ++
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/stm/Kconfig | 8 +
sound/soc/stm/Makefile | 2 +
sound/soc/stm/stm32_i2s.c | 1069 ++++++++++++++++++++
6 files changed, 1152 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/st,stm32h7-i2s.txt
create mode 100644 sound/soc/stm/Kconfig
create mode 100644 sound/soc/stm/Makefile
create mode 100644 sound/soc/stm/stm32_i2s.c
--
1.9.1
^ permalink raw reply
* [PATCH 1/2] dt-bindings: Document STM32 I2S bindings
From: olivier moysan @ 2017-04-06 15:40 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, mcoquelin.stm32,
alexandre.torgue, alsa-devel, robh, mark.rutland, devicetree,
linux-arm-kernel, olivier.moysan
Cc: arnaud.pouliquen, benjamin.gaignard
In-Reply-To: <1491493236-2574-1-git-send-email-olivier.moysan@st.com>
Add documentation of device tree bindings for STM32 SPI/I2S.
Signed-off-by: olivier moysan <olivier.moysan@st.com>
---
.../devicetree/bindings/sound/st,stm32h7-i2s.txt | 71 ++++++++++++++++++++++
1 file changed, 71 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/st,stm32h7-i2s.txt
diff --git a/Documentation/devicetree/bindings/sound/st,stm32h7-i2s.txt b/Documentation/devicetree/bindings/sound/st,stm32h7-i2s.txt
new file mode 100644
index 0000000..b99467a
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/st,stm32h7-i2s.txt
@@ -0,0 +1,71 @@
+STMicroelectronics STM32 SPI/I2S Controller
+
+The SPI/I2S block supports I2S/PCM protocols when configured on I2S mode.
+Only some SPI instances support I2S.
+
+Required properties:
+ - compatible: Must be "st,stm32h7-i2s"
+ - #sound-dai-cells: Must be 1. (one parameter)
+ This parameter allows to specify CPU DAI index in soundcard CPU dai link.
+ index 0: playback DAI
+ index 1: capture DAI
+ index 2: full duplex DAI
+ - reg: Offset and length of the device's register set.
+ - interrupts: Must contain the interrupt line id.
+ - clocks: Must contain phandle and clock specifier pairs for each entry
+ in clock-names.
+ - clock-names: Must contain "i2sclk", "pclk", "x8k" and "x11k".
+ "i2sclk": clock which feeds the internal clock generator
+ "pclk": clock which feeds the peripheral bus interface
+ "x8k": I2S parent clock for sampling rates multiple of 8kHz.
+ "x11k": I2S parent clock for sampling rates multiple of 11.025kHz.
+ - dmas: DMA specifiers for tx and rx dma.
+ See Documentation/devicetree/bindings/dma/stm32-dma.txt.
+ - dma-names: Identifier for each DMA request line. Must be "tx" and "rx".
+ - pinctrl-names: should contain only value "default"
+ - pinctrl-0: see Documentation/devicetree/bindings/pinctrl/pinctrl-stm32.txt
+
+Optional properties:
+ - resets: Reference to a reset controller asserting the reset controller
+
+Example:
+sound_card {
+ compatible = "audio-graph-card";
+ dais = <&i2s2_port 0>;
+};
+
+i2s2: audio-controller@40003800 {
+ compatible = "st,stm32h7-i2s";
+ #sound-dai-cells = <1>;
+ reg = <0x40003800 0x400>;
+ interrupts = <36>;
+ clocks = <&rcc PCLK1>, <&rcc SPI2_CK>, <&rcc PLL1_Q>, <&rcc PLL2_P>;
+ clock-names = "pclk", "i2sclk", "x8k", "x11k";
+ dmas = <&dmamux2 2 39 0x400 0x1>,
+ <&dmamux2 3 40 0x400 0x1>;
+ dma-names = "rx", "tx";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i2s2>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2s2_port: port@0 {
+ reg = <0>;
+ cpu_endpoint: endpoint {
+ remote-endpoint = <&codec_endpoint>;
+ audio-graph-card,format = "i2s";
+ audio-graph-card,bitclock-master = <&codec_endpoint>;
+ audio-graph-card,frame-master = <&codec_endpoint>;
+ };
+ };
+};
+
+audio-codec {
+ codec_port: port {
+ codec_endpoint: endpoint {
+ remote-endpoint = <&cpu_endpoint>;
+ };
+ };
+};
--
1.9.1
^ permalink raw reply related
* [PATCH 2/2] ASoC: stm32: Add I2S driver
From: olivier moysan @ 2017-04-06 15:40 UTC (permalink / raw)
To: lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
perex-/Fr2/VpizcU, tiwai-IBi9RG/b67k,
mcoquelin.stm32-Re5JQEeQqe8AvxtiuMwx3w,
alexandre.torgue-qxv4g6HH51o, alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw,
robh-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
olivier.moysan-qxv4g6HH51o
Cc: arnaud.pouliquen-qxv4g6HH51o, benjamin.gaignard-qxv4g6HH51o
In-Reply-To: <1491493236-2574-1-git-send-email-olivier.moysan-qxv4g6HH51o@public.gmane.org>
Add I2S ASoC driver for STM32.
Signed-off-by: olivier moysan <olivier.moysan-qxv4g6HH51o@public.gmane.org>
---
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/stm/Kconfig | 8 +
sound/soc/stm/Makefile | 2 +
sound/soc/stm/stm32_i2s.c | 1069 +++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1081 insertions(+)
create mode 100644 sound/soc/stm/Kconfig
create mode 100644 sound/soc/stm/Makefile
create mode 100644 sound/soc/stm/stm32_i2s.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 182d92e..3836ebe 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -63,6 +63,7 @@ source "sound/soc/sh/Kconfig"
source "sound/soc/sirf/Kconfig"
source "sound/soc/spear/Kconfig"
source "sound/soc/sti/Kconfig"
+source "sound/soc/stm/Kconfig"
source "sound/soc/sunxi/Kconfig"
source "sound/soc/tegra/Kconfig"
source "sound/soc/txx9/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 9a30f21..5440cf7 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += sirf/
obj-$(CONFIG_SND_SOC) += spear/
obj-$(CONFIG_SND_SOC) += sti/
+obj-$(CONFIG_SND_SOC) += stm/
obj-$(CONFIG_SND_SOC) += sunxi/
obj-$(CONFIG_SND_SOC) += tegra/
obj-$(CONFIG_SND_SOC) += txx9/
diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig
new file mode 100644
index 0000000..972970f
--- /dev/null
+++ b/sound/soc/stm/Kconfig
@@ -0,0 +1,8 @@
+menuconfig SND_SOC_STM32
+ tristate "STMicroelectronics STM32 SOC audio support"
+ depends on ARCH_STM32 || COMPILE_TEST
+ depends on SND_SOC
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y if you want to enable ASoC-support for STM32
diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile
new file mode 100644
index 0000000..090836b
--- /dev/null
+++ b/sound/soc/stm/Makefile
@@ -0,0 +1,2 @@
+snd-soc-stm32-i2s-objs := stm32_i2s.o
+obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-i2s.o
diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c
new file mode 100644
index 0000000..2ca9719
--- /dev/null
+++ b/sound/soc/stm/stm32_i2s.c
@@ -0,0 +1,1069 @@
+/*
+ * STM32 ALSA SoC Digital Audio Interface (I2S) driver.
+ *
+ * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
+ * Author(s): Olivier Moysan <olivier.moysan-qxv4g6HH51o@public.gmane.org> for STMicroelectronics.
+ *
+ * License terms: GPL V2.0.
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#define STM32_I2S_CR1_REG 0x0
+#define STM32_I2S_CFG1_REG 0x08
+#define STM32_I2S_CFG2_REG 0x0C
+#define STM32_I2S_IER_REG 0x10
+#define STM32_I2S_SR_REG 0x14
+#define STM32_I2S_IFCR_REG 0x18
+#define STM32_I2S_TXDR_REG 0X20
+#define STM32_I2S_RXDR_REG 0x30
+#define STM32_I2S_CGFR_REG 0X50
+
+/* Bit definition for SPI2S_CR1 register */
+#define I2S_CR1_SPE BIT(0)
+#define I2S_CR1_CSTART BIT(9)
+#define I2S_CR1_CSUSP BIT(10)
+#define I2S_CR1_HDDIR BIT(11)
+#define I2S_CR1_SSI BIT(12)
+#define I2S_CR1_CRC33_17 BIT(13)
+#define I2S_CR1_RCRCI BIT(14)
+#define I2S_CR1_TCRCI BIT(15)
+
+/* Bit definition for SPI_CFG2 register */
+#define I2S_CFG2_IOSWP_SHIFT 15
+#define I2S_CFG2_IOSWP BIT(I2S_CFG2_IOSWP_SHIFT)
+#define I2S_CFG2_LSBFRST BIT(23)
+#define I2S_CFG2_AFCNTR BIT(31)
+
+/* Bit definition for SPI_CFG1 register */
+#define I2S_CFG1_FTHVL_SHIFT 5
+#define I2S_CFG1_FTHVL_MASK GENMASK(8, I2S_CFG1_FTHVL_SHIFT)
+#define I2S_CFG1_FTHVL_SET(x) ((x) << I2S_CFG1_FTHVL_SHIFT)
+
+#define I2S_CFG1_TXDMAEN BIT(15)
+#define I2S_CFG1_RXDMAEN BIT(14)
+
+/* Bit definition for SPI2S_IER register */
+#define I2S_IER_RXPIE BIT(0)
+#define I2S_IER_TXPIE BIT(1)
+#define I2S_IER_DPXPIE BIT(2)
+#define I2S_IER_EOTIE BIT(3)
+#define I2S_IER_TXTFIE BIT(4)
+#define I2S_IER_UDRIE BIT(5)
+#define I2S_IER_OVRIE BIT(6)
+#define I2S_IER_CRCEIE BIT(7)
+#define I2S_IER_TIFREIE BIT(8)
+#define I2S_IER_MODFIE BIT(9)
+#define I2S_IER_TSERFIE BIT(10)
+
+/* Bit definition for SPI2S_SR register */
+#define I2S_SR_RXP BIT(0)
+#define I2S_SR_TXP BIT(1)
+#define I2S_SR_DPXP BIT(2)
+#define I2S_SR_EOT BIT(3)
+#define I2S_SR_TXTF BIT(4)
+#define I2S_SR_UDR BIT(5)
+#define I2S_SR_OVR BIT(6)
+#define I2S_SR_CRCERR BIT(7)
+#define I2S_SR_TIFRE BIT(8)
+#define I2S_SR_MODF BIT(9)
+#define I2S_SR_TSERF BIT(10)
+#define I2S_SR_SUSP BIT(11)
+#define I2S_SR_TXC BIT(12)
+#define I2S_SR_RXPLVL GENMASK(14, 13)
+#define I2S_SR_RXWNE BIT(15)
+
+#define I2S_SR_MASK GENMASK(15, 0)
+
+/* Bit definition for SPI_IFCR register */
+#define I2S_IFCR_EOTC BIT(3)
+#define I2S_IFCR_TXTFC BIT(4)
+#define I2S_IFCR_UDRC BIT(5)
+#define I2S_IFCR_OVRC BIT(6)
+#define I2S_IFCR_CRCEC BIT(7)
+#define I2S_IFCR_TIFREC BIT(8)
+#define I2S_IFCR_MODFC BIT(9)
+#define I2S_IFCR_TSERFC BIT(10)
+#define I2S_IFCR_SUSPC BIT(11)
+
+#define I2S_IFCR_MASK GENMASK(11, 3)
+
+/* Bit definition for SPI_I2SCGFR register */
+#define I2S_CGFR_I2SMOD BIT(0)
+
+#define I2S_CGFR_I2SCFG_SHIFT 1
+#define I2S_CGFR_I2SCFG_MASK GENMASK(3, I2S_CGFR_I2SCFG_SHIFT)
+#define I2S_CGFR_I2SCFG_SET(x) ((x) << I2S_CGFR_I2SCFG_SHIFT)
+
+#define I2S_CGFR_I2SSTD_SHIFT 4
+#define I2S_CGFR_I2SSTD_MASK GENMASK(5, I2S_CGFR_I2SSTD_SHIFT)
+#define I2S_CGFR_I2SSTD_SET(x) ((x) << I2S_CGFR_I2SSTD_SHIFT)
+
+#define I2S_CGFR_PCMSYNC BIT(7)
+
+#define I2S_CGFR_DATLEN_SHIFT 8
+#define I2S_CGFR_DATLEN_MASK GENMASK(9, I2S_CGFR_DATLEN_SHIFT)
+#define I2S_CGFR_DATLEN_SET(x) ((x) << I2S_CGFR_DATLEN_SHIFT)
+
+#define I2S_CGFR_CHLEN_SHIFT 10
+#define I2S_CGFR_CHLEN BIT(I2S_CGFR_CHLEN_SHIFT)
+#define I2S_CGFR_CKPOL BIT(11)
+#define I2S_CGFR_FIXCH BIT(12)
+#define I2S_CGFR_WSINV BIT(13)
+#define I2S_CGFR_DATFMT BIT(14)
+
+#define I2S_CGFR_I2SDIV_SHIFT 16
+#define I2S_CGFR_I2SDIV_BIT_H 23
+#define I2S_CGFR_I2SDIV_MASK GENMASK(I2S_CGFR_I2SDIV_BIT_H,\
+ I2S_CGFR_I2SDIV_SHIFT)
+#define I2S_CGFR_I2SDIV_SET(x) ((x) << I2S_CGFR_I2SDIV_SHIFT)
+#define I2S_CGFR_I2SDIV_MAX ((1 << (I2S_CGFR_I2SDIV_BIT_H -\
+ I2S_CGFR_I2SDIV_SHIFT)) - 1)
+
+#define I2S_CGFR_ODD_SHIFT 24
+#define I2S_CGFR_ODD BIT(I2S_CGFR_ODD_SHIFT)
+#define I2S_CGFR_MCKOE BIT(25)
+
+enum i2s_master_mode {
+ I2S_MS_NOT_SET,
+ I2S_MS_MASTER,
+ I2S_MS_SLAVE,
+};
+
+enum i2s_mode {
+ I2S_I2SMOD_TX_SLAVE,
+ I2S_I2SMOD_RX_SLAVE,
+ I2S_I2SMOD_TX_MASTER,
+ I2S_I2SMOD_RX_MASTER,
+ I2S_I2SMOD_FD_SLAVE,
+ I2S_I2SMOD_FD_MASTER,
+};
+
+enum i2s_fifo_th {
+ I2S_FIFO_TH_NONE,
+ I2S_FIFO_TH_ONE_QUARTER,
+ I2S_FIFO_TH_HALF,
+ I2S_FIFO_TH_THREE_QUARTER,
+ I2S_FIFO_TH_FULL,
+};
+
+enum i2s_std {
+ I2S_STD_I2S,
+ I2S_STD_LEFT_J,
+ I2S_STD_RIGHT_J,
+ I2S_STD_DSP,
+};
+
+enum i2s_dir {
+ I2S_DIR_TX,
+ I2S_DIR_RX,
+ I2S_DIR_FD,
+};
+
+enum i2s_datlen {
+ I2S_I2SMOD_DATLEN_16,
+ I2S_I2SMOD_DATLEN_24,
+ I2S_I2SMOD_DATLEN_32,
+};
+
+#define STM32_I2S_DAI_NAME_SIZE 20
+#define STM32_I2S_DAIS_NB 3
+#define STM32_I2S_IP_NAME_LENGTH 5
+#define STM32_I2S_FIFO_SIZE 16
+
+#define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER)
+#define STM32_I2S_IS_SLAVE(x) ((x)->ms_flg == I2S_MS_SLAVE)
+
+#define STM32_I2S_IS_PLAYBACK(x) ((x)->dir == I2S_DIR_TX)
+#define STM32_I2S_IS_CAPTURE(x) ((x)->dir == I2S_DIR_RX)
+#define STM32_I2S_IS_FULL_DUPLEX(x) ((x)->dir == I2S_DIR_FD)
+
+/**
+ * @regmap_conf: I2S register map configuration pointer
+ * @egmap: I2S register map pointer
+ * @pdev: device data pointer
+ * @dai_drv: DAI driver pointer
+ * @dma_data_tx: dma configuration data for tx channel
+ * @dma_data_rx: dma configuration data for tx channel
+ * @substream: PCM substream data pointer
+ * @i2sclk: kernel clock feeding the I2S clock generator
+ * @pclk: peripheral clock driving bus interface
+ * @x8kclk: I2S parent clock for sampling frequencies multiple of 8kHz
+ * @x11kclk: I2S parent clock for sampling frequencies multiple of 11kHz
+ * @base: mmio register base virtual address
+ * @phys_addr: I2S registers physical base address
+ * @lock_fd: lock to manage race conditions in full duplex mode
+ * @ip_name: I2S name
+ * @dais_name: playback, capture and fd DAI names
+ * @fifo_th: fifo threshold setting
+ * @mclk_rate: master clock frequency (Hz)
+ * @fmt: DAI protocol
+ * @refcount: keep count of opened streams on I2S
+ * @startcount: keep count of started streams on I2S
+ * @dir: I2S direction. tx, rx or full duplex.
+ * @ms_flg: master mode flag.
+ * @format: pcm stream width (16/32 bits), used for consistency check in fd mode
+ * @rate: pcm stream rate, used for consistency check in fd mode
+ */
+struct stm32_i2s_data {
+ const struct regmap_config *regmap_conf;
+ struct regmap *regmap;
+ struct platform_device *pdev;
+ struct snd_soc_dai_driver *dai_drv;
+ struct snd_dmaengine_dai_dma_data dma_data_tx;
+ struct snd_dmaengine_dai_dma_data dma_data_rx;
+ struct snd_pcm_substream *substream;
+ struct clk *i2sclk;
+ struct clk *pclk;
+ struct clk *x8kclk;
+ struct clk *x11kclk;
+ void __iomem *base;
+ dma_addr_t phys_addr;
+ spinlock_t lock_fd; /* Manage race conditions for full duplex */
+ const char *ip_name;
+ char dais_name[STM32_I2S_DAIS_NB][STM32_I2S_DAI_NAME_SIZE];
+ unsigned int fifo_th;
+ unsigned int mclk_rate;
+ unsigned int fmt;
+ int refcount;
+ int startcount;
+ int dir;
+ int ms_flg;
+ int format;
+ unsigned int rate;
+};
+
+static irqreturn_t stm32_i2s_isr(int irq, void *devid)
+{
+ struct stm32_i2s_data *i2s = (struct stm32_i2s_data *)devid;
+ struct platform_device *pdev = i2s->pdev;
+ u32 sr, ier;
+ unsigned long flags;
+ int err = 0;
+
+ regmap_read(i2s->regmap, STM32_I2S_SR_REG, &sr);
+ regmap_read(i2s->regmap, STM32_I2S_IER_REG, &ier);
+
+ flags = sr & ier;
+ if (!flags) {
+ dev_dbg(&pdev->dev, "Spurious IT sr=0x%08x, ier=0x%08x\n",
+ sr, ier);
+ return IRQ_NONE;
+ }
+
+ /* Clear ITs */
+ regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG,
+ I2S_IFCR_MASK, flags);
+
+ if (flags & I2S_SR_OVR) {
+ dev_dbg(&pdev->dev, "Overrun: received value discarded\n");
+ err = 1;
+ }
+
+ if (flags & I2S_SR_UDR) {
+ dev_dbg(&pdev->dev, "Underrun\n");
+ err = 1;
+ }
+
+ if (flags & I2S_SR_TIFRE)
+ dev_dbg(&pdev->dev, "Frame error\n");
+
+ if (err)
+ snd_pcm_stop_xrun(i2s->substream);
+
+ return IRQ_HANDLED;
+}
+
+static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case STM32_I2S_CR1_REG:
+ case STM32_I2S_CFG1_REG:
+ case STM32_I2S_CFG2_REG:
+ case STM32_I2S_IER_REG:
+ case STM32_I2S_SR_REG:
+ case STM32_I2S_IFCR_REG:
+ case STM32_I2S_TXDR_REG:
+ case STM32_I2S_RXDR_REG:
+ case STM32_I2S_CGFR_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool stm32_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case STM32_I2S_TXDR_REG:
+ case STM32_I2S_RXDR_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool stm32_i2s_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case STM32_I2S_CR1_REG:
+ case STM32_I2S_CFG1_REG:
+ case STM32_I2S_CFG2_REG:
+ case STM32_I2S_IER_REG:
+ case STM32_I2S_IFCR_REG:
+ case STM32_I2S_TXDR_REG:
+ case STM32_I2S_CGFR_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int stm32_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 cgfr;
+ u32 cgfr_mask = I2S_CGFR_I2SSTD_MASK | I2S_CGFR_CKPOL |
+ I2S_CGFR_WSINV | I2S_CGFR_I2SCFG_MASK;
+
+ dev_dbg(cpu_dai->dev, "fmt %x\n", fmt);
+
+ /*
+ * winv = 0 : default behavior (high/low) for all standards
+ * ckpol 0 for all standards.
+ */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_I2S);
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_LEFT_J);
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_RIGHT_J);
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_DSP);
+ break;
+ /* DSP_B not mapped on I2S PCM long format. 1 bit offset does not fit */
+ default:
+ dev_err(cpu_dai->dev, "Unsupported protocol %#x\n",
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ return -EINVAL;
+ }
+
+ /* DAI clock strobing */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ cgfr |= I2S_CGFR_CKPOL;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ cgfr |= I2S_CGFR_WSINV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ cgfr |= I2S_CGFR_CKPOL;
+ cgfr |= I2S_CGFR_WSINV;
+ break;
+ default:
+ dev_err(cpu_dai->dev, "Unsupported strobing %#x\n",
+ fmt & SND_SOC_DAIFMT_INV_MASK);
+ return -EINVAL;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ if (STM32_I2S_IS_MASTER(i2s)) {
+ dev_err(cpu_dai->dev, "previous DAI set master mode\n");
+ return -EINVAL;
+ }
+ i2s->ms_flg = I2S_MS_SLAVE;
+
+ if (STM32_I2S_IS_FULL_DUPLEX(i2s))
+ cgfr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_FD_SLAVE);
+ else if (STM32_I2S_IS_PLAYBACK(i2s))
+ cgfr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_TX_SLAVE);
+ else
+ cgfr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_RX_SLAVE);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ if (STM32_I2S_IS_SLAVE(i2s)) {
+ dev_err(cpu_dai->dev, "previous DAI set slave mode\n");
+ return -EINVAL;
+ }
+ i2s->ms_flg = I2S_MS_MASTER;
+
+ if (STM32_I2S_IS_FULL_DUPLEX(i2s))
+ cgfr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_FD_MASTER);
+ else if (STM32_I2S_IS_PLAYBACK(i2s))
+ cgfr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_TX_MASTER);
+ else
+ cgfr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_RX_MASTER);
+ break;
+ default:
+ dev_err(cpu_dai->dev, "Unsupported mode %#x\n",
+ fmt & SND_SOC_DAIFMT_MASTER_MASK);
+ return -EINVAL;
+ }
+
+ i2s->fmt = fmt;
+ return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ cgfr_mask, cgfr);
+}
+
+static int stm32_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+
+ dev_dbg(cpu_dai->dev, "I2S MCLK frequency is %uHz\n", freq);
+
+ if ((dir == SND_SOC_CLOCK_OUT) && STM32_I2S_IS_MASTER(i2s)) {
+ i2s->mclk_rate = freq;
+
+ /* Enable master clock if master mode and mclk-fs are set */
+ return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ I2S_CGFR_MCKOE, I2S_CGFR_MCKOE);
+ }
+
+ return 0;
+}
+
+static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_hw_params *params)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned long i2s_clock_rate;
+ unsigned int tmp, div, real_div, nb_bits, frame_len;
+ unsigned int rate = params_rate(params);
+ int ret;
+ u32 cgfr, cgfr_mask;
+ bool odd;
+
+ if (i2s->refcount > 1) {
+ if (rate != i2s->rate) {
+ dev_err(cpu_dai->dev,
+ "rate not compatible with active stream");
+ return -EINVAL;
+ }
+ return 0;
+ }
+ i2s->rate = rate;
+
+ if (!(rate % 11025))
+ clk_set_parent(i2s->i2sclk, i2s->x11kclk);
+ else
+ clk_set_parent(i2s->i2sclk, i2s->x8kclk);
+ i2s_clock_rate = clk_get_rate(i2s->i2sclk);
+
+ /*
+ * mckl = mclk_ratio x ws
+ * i2s mode : mclk_ratio = 256
+ * dsp mode : mclk_ratio = 128
+ *
+ * mclk on
+ * i2s mode : div = i2s_clk / (mclk_ratio * ws)
+ * dsp mode : div = i2s_clk / (mclk_ratio * ws)
+ * mclk off
+ * i2s mode : div = i2s_clk / (nb_bits x ws)
+ * dsp mode : div = i2s_clk / (nb_bits x ws)
+ */
+ if (i2s->mclk_rate) {
+ tmp = DIV_ROUND_CLOSEST(i2s_clock_rate, i2s->mclk_rate);
+ } else {
+ frame_len = 32;
+ if ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) ==
+ SND_SOC_DAIFMT_DSP_A)
+ frame_len = 16;
+
+ /* master clock not enabled */
+ ret = regmap_read(i2s->regmap, STM32_I2S_CGFR_REG, &cgfr);
+ if (ret < 0)
+ return ret;
+
+ nb_bits = frame_len * ((cgfr & I2S_CGFR_CHLEN) + 1);
+ tmp = DIV_ROUND_CLOSEST(i2s_clock_rate, (nb_bits * rate));
+ }
+
+ /* Check the parity of the divider */
+ odd = tmp & 0x1;
+
+ /* Compute the div prescaler */
+ div = tmp >> 1;
+
+ cgfr = I2S_CGFR_I2SDIV_SET(div) | (odd << I2S_CGFR_ODD_SHIFT);
+ cgfr_mask = I2S_CGFR_I2SDIV_MASK | I2S_CGFR_ODD;
+
+ real_div = ((2 * div) + odd);
+ dev_dbg(cpu_dai->dev, "I2S clk: %ld, SCLK: %d\n",
+ i2s_clock_rate, rate);
+ dev_dbg(cpu_dai->dev, "Divider: 2*%d(div)+%d(odd) = %d\n",
+ div, odd, real_div);
+
+ if (((div == 1) && odd) || (div > I2S_CGFR_I2SDIV_MAX)) {
+ dev_err(cpu_dai->dev, "Wrong divider setting\n");
+ return -EINVAL;
+ }
+
+ if (!div && !odd)
+ dev_warn(cpu_dai->dev, "real divider forced to 1\n");
+
+ ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ cgfr_mask, cgfr);
+ if (ret < 0)
+ return ret;
+
+ /* Set bitclock and frameclock to their inactive state */
+ return regmap_update_bits(i2s->regmap, STM32_I2S_CFG2_REG,
+ I2S_CFG2_AFCNTR, I2S_CFG2_AFCNTR);
+}
+
+static int stm32_i2s_configure_channel(struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_hw_params *params)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ int format = params_width(params);
+ u32 cfgr, cfgr_mask;
+
+ if (i2s->refcount > 1) {
+ if (format != i2s->format) {
+ dev_err(cpu_dai->dev,
+ "format not compatible with active stream");
+ return -EINVAL;
+ }
+ return 0;
+ }
+ i2s->format = format;
+
+ switch (format) {
+ case 16:
+ cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_16);
+ cfgr_mask = I2S_CGFR_DATLEN_MASK;
+ break;
+ case 32:
+ cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_32) |
+ I2S_CGFR_CHLEN;
+ cfgr_mask = I2S_CGFR_DATLEN_MASK | I2S_CGFR_CHLEN;
+ break;
+ default:
+ dev_err(cpu_dai->dev, "Unexpected format %d", format);
+ return -EINVAL;
+ }
+
+ if (STM32_I2S_IS_SLAVE(i2s)) {
+ /* As data length is either 16 or 32 bits, fixch always set */
+ cfgr |= I2S_CGFR_FIXCH;
+ cfgr_mask |= I2S_CGFR_FIXCH;
+ }
+
+ return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ cfgr_mask, cfgr);
+}
+
+static int stm32_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret, ier;
+
+ i2s->substream = substream;
+
+ spin_lock(&i2s->lock_fd);
+ i2s->refcount++;
+ spin_unlock(&i2s->lock_fd);
+
+ ret = regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG,
+ I2S_IFCR_MASK, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Enable ITs */
+ ier = I2S_IER_OVRIE | I2S_IER_UDRIE;
+ if (STM32_I2S_IS_SLAVE(i2s))
+ ier |= I2S_IER_TIFREIE;
+
+ return regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, ier, ier);
+}
+
+static int stm32_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ int format = params_width(params);
+ unsigned int fthlv;
+ int ret;
+
+ if ((params_channels(params) == 1) &&
+ ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A)) {
+ dev_err(cpu_dai->dev, "Mono mode supported only by DSP_A\n");
+ return -EINVAL;
+ }
+
+ /*
+ * fthlv is fifo threshold expressed as sample number
+ * fthlv = fifo size * threshold_ratio / (format / 8)
+ * where threshold_ratio = fifo_th / 4
+ */
+ fthlv = STM32_I2S_FIFO_SIZE * 8 * i2s->fifo_th / (format * 4);
+
+ regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, I2S_CFG1_FTHVL_MASK,
+ I2S_CFG1_FTHVL_SET(fthlv - 1));
+
+ spin_lock(&i2s->lock_fd);
+
+ ret = stm32_i2s_configure_channel(cpu_dai, params);
+ if (ret < 0) {
+ spin_unlock(&i2s->lock_fd);
+ return ret;
+ }
+
+ if (STM32_I2S_IS_MASTER(i2s)) {
+ ret = stm32_i2s_configure_clock(cpu_dai, params);
+ if (ret < 0) {
+ spin_unlock(&i2s->lock_fd);
+ return ret;
+ }
+ }
+
+ spin_unlock(&i2s->lock_fd);
+
+ return 0;
+}
+
+static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret;
+ bool stream_is_playback = false;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream_is_playback = true;
+
+ /* This lock protects both start flag and SPE bit accesses */
+ spin_lock(&i2s->lock_fd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (stream_is_playback) {
+ ret = regmap_update_bits(i2s->regmap,
+ STM32_I2S_CFG1_REG,
+ I2S_CFG1_TXDMAEN,
+ I2S_CFG1_TXDMAEN);
+ } else {
+ ret = regmap_update_bits(i2s->regmap,
+ STM32_I2S_CFG1_REG,
+ I2S_CFG1_RXDMAEN,
+ I2S_CFG1_RXDMAEN);
+ }
+ if (ret < 0)
+ goto err_trigger;
+
+ i2s->startcount++;
+ if (STM32_I2S_IS_FULL_DUPLEX(i2s) && (i2s->startcount < 2)) {
+ dev_dbg(cpu_dai->dev,
+ "Full duplex device waiting for %s stream",
+ stream_is_playback ? "capture" : "playback");
+ spin_unlock(&i2s->lock_fd);
+ return 0;
+ }
+
+ /* Enable i2s */
+ dev_dbg(cpu_dai->dev, "start I2S\n");
+
+ ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG,
+ I2S_CR1_SPE, I2S_CR1_SPE);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "Error %d enabling I2S\n", ret);
+ goto err_trigger;
+ }
+
+ ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG,
+ I2S_CR1_CSTART, I2S_CR1_CSTART);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "Error %d starting I2S\n", ret);
+ goto err_trigger;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dev_dbg(cpu_dai->dev, "stop I2S\n");
+
+ ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG,
+ I2S_CR1_SPE, 0);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "Error %d disabling I2S\n", ret);
+ goto err_trigger;
+ }
+ i2s->startcount = 0;
+
+ if (stream_is_playback) {
+ ret = regmap_update_bits(i2s->regmap,
+ STM32_I2S_CFG1_REG,
+ I2S_CFG1_TXDMAEN, 0);
+ } else {
+ ret = regmap_update_bits(i2s->regmap,
+ STM32_I2S_CFG1_REG,
+ I2S_CFG1_RXDMAEN, 0);
+ }
+ if (ret < 0)
+ goto err_trigger;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_unlock(&i2s->lock_fd);
+ return 0;
+
+err_trigger:
+ spin_unlock(&i2s->lock_fd);
+ return ret;
+}
+
+static void stm32_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+
+ i2s->substream = NULL;
+
+ spin_lock(&i2s->lock_fd);
+ if (i2s->refcount > 0)
+ i2s->refcount--;
+ spin_unlock(&i2s->lock_fd);
+
+ regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ I2S_CGFR_MCKOE, (unsigned int)~I2S_CGFR_MCKOE);
+}
+
+static int stm32_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct stm32_i2s_data *i2s = dev_get_drvdata(cpu_dai->dev);
+ struct snd_dmaengine_dai_dma_data *dma_data_tx = &i2s->dma_data_tx;
+ struct snd_dmaengine_dai_dma_data *dma_data_rx = &i2s->dma_data_rx;
+
+ /* Buswidth will be set by framework */
+ dma_data_tx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+ dma_data_tx->addr = (dma_addr_t)(i2s->phys_addr) + STM32_I2S_TXDR_REG;
+ dma_data_tx->maxburst = 1;
+ dma_data_rx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
+ dma_data_rx->addr = (dma_addr_t)(i2s->phys_addr) + STM32_I2S_RXDR_REG;
+ dma_data_rx->maxburst = 1;
+
+ i2s->dir = cpu_dai->id;
+ switch (i2s->dir) {
+ case I2S_DIR_TX:
+ i2s->fifo_th = I2S_FIFO_TH_FULL;
+ snd_soc_dai_init_dma_data(cpu_dai, dma_data_tx, NULL);
+ break;
+ case I2S_DIR_RX:
+ i2s->fifo_th = I2S_FIFO_TH_HALF;
+ snd_soc_dai_init_dma_data(cpu_dai, NULL, dma_data_rx);
+ break;
+ case I2S_DIR_FD:
+ i2s->fifo_th = I2S_FIFO_TH_NONE;
+ snd_soc_dai_init_dma_data(cpu_dai, dma_data_tx, dma_data_rx);
+ break;
+ }
+
+ return 0;
+}
+
+static const struct regmap_config stm32_h7_i2s_regmap_conf = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = STM32_I2S_CGFR_REG,
+ .readable_reg = stm32_i2s_readable_reg,
+ .volatile_reg = stm32_i2s_volatile_reg,
+ .writeable_reg = stm32_i2s_writeable_reg,
+ .fast_io = true,
+};
+
+static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = {
+ .set_sysclk = stm32_i2s_set_sysclk,
+ .set_fmt = stm32_i2s_set_dai_fmt,
+ .startup = stm32_i2s_startup,
+ .hw_params = stm32_i2s_hw_params,
+ .trigger = stm32_i2s_trigger,
+ .shutdown = stm32_i2s_shutdown,
+};
+
+static const struct snd_pcm_hardware stm32_i2s_pcm_hw = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP,
+ .buffer_bytes_max = 8 * PAGE_SIZE,
+ .period_bytes_max = 2048,
+ .periods_min = 2,
+ .periods_max = 8,
+};
+
+static const struct snd_dmaengine_pcm_config stm32_i2s_pcm_config = {
+ .pcm_hardware = &stm32_i2s_pcm_hw,
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+ .prealloc_buffer_size = PAGE_SIZE * 8,
+};
+
+static const struct snd_soc_component_driver stm32_i2s_component = {
+ .name = "stm32-i2s",
+};
+
+static void stm32_i2s_dai_init(struct snd_soc_pcm_stream *stream,
+ char *stream_name)
+{
+ stream->stream_name = stream_name;
+ stream->channels_min = 1;
+ stream->channels_max = 2;
+ stream->rates = SNDRV_PCM_RATE_8000_192000;
+ stream->formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE;
+}
+
+static int stm32_i2s_dais_init(struct platform_device *pdev,
+ struct stm32_i2s_data *i2s)
+{
+ struct snd_soc_dai_driver *dai_ptr;
+ int i;
+
+ dai_ptr = devm_kzalloc(&pdev->dev, STM32_I2S_DAIS_NB *
+ sizeof(struct snd_soc_dai_driver), GFP_KERNEL);
+ if (!dai_ptr)
+ return -ENOMEM;
+
+ i2s->dai_drv = dai_ptr;
+ for (i = 0; i < STM32_I2S_DAIS_NB; i++) {
+ dai_ptr->probe = stm32_i2s_dai_probe;
+ dai_ptr->ops = &stm32_i2s_pcm_dai_ops;
+ if (i == 0) {
+ snprintf(i2s->dais_name[i], STM32_I2S_DAI_NAME_SIZE,
+ "%s-playback", i2s->ip_name);
+ dai_ptr->id = I2S_DIR_TX;
+ stm32_i2s_dai_init(&dai_ptr->playback, "CPU-Playback");
+ }
+ if (i == 1) {
+ snprintf(i2s->dais_name[i], STM32_I2S_DAI_NAME_SIZE,
+ "%s-capture", i2s->ip_name);
+ dai_ptr->id = I2S_DIR_RX;
+ stm32_i2s_dai_init(&dai_ptr->capture, "CPU-Capture");
+ }
+ if (i == 2) {
+ snprintf(i2s->dais_name[i], STM32_I2S_DAI_NAME_SIZE,
+ "%s-full-duplex", i2s->ip_name);
+ dai_ptr->id = I2S_DIR_FD;
+ stm32_i2s_dai_init(&dai_ptr->playback,
+ "CPU-FD-Playback");
+ stm32_i2s_dai_init(&dai_ptr->capture,
+ "CPU-FD-Capture");
+ }
+ dai_ptr->name = i2s->dais_name[i];
+ dai_ptr++;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id stm32_i2s_ids[] = {
+ {
+ .compatible = "st,stm32h7-i2s",
+ .data = &stm32_h7_i2s_regmap_conf
+ },
+ {},
+};
+
+static int stm32_i2s_parse_dt(struct platform_device *pdev,
+ struct stm32_i2s_data *i2s)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id;
+ struct reset_control *rst;
+ struct resource *res;
+ int irq, ret;
+
+ if (!np)
+ return -ENODEV;
+
+ of_id = of_match_device(stm32_i2s_ids, &pdev->dev);
+ if (of_id)
+ i2s->regmap_conf = (const struct regmap_config *)of_id->data;
+ else
+ return -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2s->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(i2s->base))
+ return PTR_ERR(i2s->base);
+
+ i2s->phys_addr = res->start;
+
+ /* Get clocks */
+ i2s->pclk = devm_clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(i2s->pclk)) {
+ dev_err(&pdev->dev, "Could not get pclk\n");
+ return PTR_ERR(i2s->pclk);
+ }
+
+ i2s->i2sclk = devm_clk_get(&pdev->dev, "i2sclk");
+ if (IS_ERR(i2s->i2sclk)) {
+ dev_err(&pdev->dev, "Could not get i2sclk\n");
+ return PTR_ERR(i2s->i2sclk);
+ }
+
+ i2s->x8kclk = devm_clk_get(&pdev->dev, "x8k");
+ if (IS_ERR(i2s->x8kclk)) {
+ dev_err(&pdev->dev, "missing x8k parent clock\n");
+ return PTR_ERR(i2s->x8kclk);
+ }
+
+ i2s->x11kclk = devm_clk_get(&pdev->dev, "x11k");
+ if (IS_ERR(i2s->x11kclk)) {
+ dev_err(&pdev->dev, "missing x11k parent clock\n");
+ return PTR_ERR(i2s->x11kclk);
+ }
+
+ /* Get irqs */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+ return -ENOENT;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, IRQF_ONESHOT,
+ dev_name(&pdev->dev), i2s);
+ if (ret) {
+ dev_err(&pdev->dev, "irq request returned %d\n", ret);
+ return ret;
+ }
+
+ /* Reset */
+ rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (!IS_ERR(rst)) {
+ reset_control_assert(rst);
+ udelay(2);
+ reset_control_deassert(rst);
+ }
+
+ return 0;
+}
+
+static int stm32_i2s_probe(struct platform_device *pdev)
+{
+ struct stm32_i2s_data *i2s;
+ int ret;
+
+ i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
+ if (!i2s)
+ return -ENOMEM;
+
+ i2s->pdev = pdev;
+ i2s->ms_flg = I2S_MS_NOT_SET;
+ i2s->ip_name = strstr(dev_name(&pdev->dev), ".") + 1;
+ spin_lock_init(&i2s->lock_fd);
+ platform_set_drvdata(pdev, i2s);
+
+ ret = stm32_i2s_parse_dt(pdev, i2s);
+ if (ret)
+ return ret;
+
+ ret = stm32_i2s_dais_init(pdev, i2s);
+ if (ret)
+ return ret;
+
+ i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->base,
+ i2s->regmap_conf);
+ if (IS_ERR(i2s->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ return PTR_ERR(i2s->regmap);
+ }
+
+ ret = clk_prepare_enable(i2s->pclk);
+ if (ret) {
+ dev_err(&pdev->dev, "Enable pclk failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(i2s->i2sclk);
+ if (ret) {
+ dev_err(&pdev->dev, "Enable i2sclk failed: %d\n", ret);
+ goto err_pclk_disable;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &stm32_i2s_component,
+ i2s->dai_drv, STM32_I2S_DAIS_NB);
+ if (ret)
+ goto err_clocks_disable;
+
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
+ &stm32_i2s_pcm_config, 0);
+ if (ret)
+ goto err_clocks_disable;
+
+ /* Set SPI/I2S in i2s mode */
+ ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
+ I2S_CGFR_I2SMOD, I2S_CGFR_I2SMOD);
+ if (ret)
+ goto err_clocks_disable;
+
+ return ret;
+
+err_clocks_disable:
+ clk_disable_unprepare(i2s->i2sclk);
+err_pclk_disable:
+ clk_disable_unprepare(i2s->pclk);
+
+ return ret;
+}
+
+static int stm32_i2s_remove(struct platform_device *pdev)
+{
+ struct stm32_i2s_data *i2s = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(i2s->i2sclk);
+ clk_disable_unprepare(i2s->pclk);
+
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(of, stm32_i2s_ids);
+
+static struct platform_driver stm32_i2s_driver = {
+ .driver = {
+ .name = "st,stm32-i2s",
+ .of_match_table = stm32_i2s_ids,
+ },
+ .probe = stm32_i2s_probe,
+ .remove = stm32_i2s_remove,
+};
+
+module_platform_driver(stm32_i2s_driver);
+
+MODULE_DESCRIPTION("STM32 Soc i2s Interface");
+MODULE_AUTHOR("Olivier Moysan, <olivier.moysan-qxv4g6HH51o@public.gmane.org>");
+MODULE_ALIAS("platform:stm32-i2s");
+MODULE_LICENSE("GPL v2");
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* Re: [PATCH] [media] imx: csi: retain current field order and colorimetry setting as default
From: Hans Verkuil @ 2017-04-06 15:43 UTC (permalink / raw)
To: Philipp Zabel
Cc: mark.rutland, andrew-ct.chen, minghsiu.tsai, nick, songjun.wu,
Steve Longerbeam, pavel, robert.jarzmik, devel, markus.heiser,
laurent.pinchart+renesas, shuah, linux, geert, Steve Longerbeam,
linux-media, devicetree, sakari.ailus, arnd, mchehab, bparrot,
robh+dt, horms+renesas, tiffany.lin, linux-arm-kernel,
niklas.soderlund+renesas, gregkh, linux-kernel,
jean-christophe.trotin, kernel, fabio.estevam, shawnguo,
sudipm.mukherjee
In-Reply-To: <1491490451.2392.70.camel@pengutronix.de>
On 04/06/2017 04:54 PM, Philipp Zabel wrote:
> On Thu, 2017-04-06 at 16:20 +0200, Hans Verkuil wrote:
>> On 04/06/2017 03:55 PM, Philipp Zabel wrote:
>>> If the the field order is set to ANY in set_fmt, choose the currently
>>> set field order. If the colorspace is set to DEFAULT, choose the current
>>> colorspace. If any of xfer_func, ycbcr_enc or quantization are set to
>>> DEFAULT, either choose the current setting, or the default setting for the
>>> new colorspace, if non-DEFAULT colorspace was given.
>>>
>>> This allows to let field order and colorimetry settings be propagated
>>> from upstream by calling media-ctl on the upstream entity source pad,
>>> and then call media-ctl on the sink pad to manually set the input frame
>>> interval, without changing the already set field order and colorimetry
>>> information.
>>>
>>> Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
>>> ---
>>> This is based on imx-media-staging-md-v14, and it is supposed to allow
>>> configuring the pipeline with media-ctl like this:
>>>
>>> 1) media-ctl --set-v4l2 "'tc358743 1-000f':0[fmt:UYVY8_1X16/1920x1080]"
>>> 2) media-ctl --set-v4l2 "'imx6-mipi-csi2':1[fmt:UYVY8_1X16/1920x108]"
>>> 3) media-ctl --set-v4l2 "'ipu1_csi0_mux':2[fmt:UYVY8_1X16/1920x1080]"
>>> 4) media-ctl --set-v4l2 "'ipu1_csi0':0[fmt:UYVY8_1X16/1920x1080@1/60]"
>>> 5) media-ctl --set-v4l2 "'ipu1_csi0':2[fmt:AYUV32/1920x1080@1/30]"
>>>
>>> Without having step 4) overwrite the colorspace and field order set on
>>> 'ipu1_csi0':0 by the propagation in step 3).
>>> ---
>>> drivers/staging/media/imx/imx-media-csi.c | 34 +++++++++++++++++++++++++++++++
>>> 1 file changed, 34 insertions(+)
>>>
>>> diff --git a/drivers/staging/media/imx/imx-media-csi.c b/drivers/staging/media/imx/imx-media-csi.c
>>> index 64dc454f6b371..d94ce1de2bf05 100644
>>> --- a/drivers/staging/media/imx/imx-media-csi.c
>>> +++ b/drivers/staging/media/imx/imx-media-csi.c
>>> @@ -1325,6 +1325,40 @@ static int csi_set_fmt(struct v4l2_subdev *sd,
>>> csi_try_fmt(priv, sensor, cfg, sdformat, crop, compose, &cc);
>>>
>>> fmt = __csi_get_fmt(priv, cfg, sdformat->pad, sdformat->which);
>>> +
>>> + /* Retain current field setting as default */
>>> + if (sdformat->format.field == V4L2_FIELD_ANY)
>>> + sdformat->format.field = fmt->field;
>>
>> sdformat->format.field should never be FIELD_ANY. If it is, then that's a
>> subdev bug and I'm pretty sure FIELD_NONE was intended.
>
> This is the subdev. sdformat is passed in from userspace, so we have to
> deal with it being set to ANY. I'm trying hard right now not to return
> ANY though. The values in sdformat->format are applied to fmt down
> below.
Do you have a git tree with this patch? It is really hard to review without
having the full imx-media-csi.c source.
I think one problem is that it is not clearly defined how subdevs and colorspace
information should work.
Regards,
Hans
>
>>> +
>>> + /* Retain current colorspace setting as default */
>>> + if (sdformat->format.colorspace == V4L2_COLORSPACE_DEFAULT) {
>>> + sdformat->format.colorspace = fmt->colorspace;
>>
>> No! Subdevs should never return COLORSPACE_DEFAULT. If they do, then fix
>> them. If this happens a lot (I'm not sure how reliably subdevs fill this
>> in) you could set it to COLORSPACE_RAW. Perhaps with a WARN_ON_ONCE.
>
> Same here, if userspace calls VIDIOC_SUBDEV_S_FMT with DEFAULT
> colorspace, I don't want to return that unchanged, but overwrite it with
> the currently set value.
>
>>> + if (sdformat->format.xfer_func == V4L2_XFER_FUNC_DEFAULT)
>>> + sdformat->format.xfer_func = fmt->xfer_func;
>>> + if (sdformat->format.ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
>>> + sdformat->format.ycbcr_enc = fmt->ycbcr_enc;
>>> + if (sdformat->format.quantization == V4L2_QUANTIZATION_DEFAULT)
>>> + sdformat->format.quantization = fmt->quantization;
>>
>> Nack. This is meaningless.
>
> It isn't. It may be wrong, but it is not without effect. If the
> colorimetry info (in fmt) is currently set to something that is
> non-standard, calling VIDIOC_SUBDEV_S_FMT with DEFAULT in xfer_func,
> ycbcr_enc or quantization will cause those old values to be retained,
> instead of overwriting them to DEFAULT.
>
>>> + } else {
>>> + if (sdformat->format.xfer_func == V4L2_XFER_FUNC_DEFAULT) {
>>> + sdformat->format.xfer_func =
>>> + V4L2_MAP_XFER_FUNC_DEFAULT(
>>> + sdformat->format.colorspace);
>>> + }
>>> + if (sdformat->format.ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) {
>>> + sdformat->format.ycbcr_enc =
>>> + V4L2_MAP_YCBCR_ENC_DEFAULT(
>>> + sdformat->format.colorspace);
>>> + }
>>> + if (sdformat->format.quantization == V4L2_QUANTIZATION_DEFAULT) {
>>> + sdformat->format.quantization =
>>> + V4L2_MAP_QUANTIZATION_DEFAULT(
>>> + cc->cs != IPUV3_COLORSPACE_YUV,
>>> + sdformat->format.colorspace,
>>> + sdformat->format.ycbcr_enc);
>>> + }
>>
>> This isn't wrong, but it is perfectly fine to keep the DEFAULT here and let
>> the application call V4L2_MAP_.
>>
>> I get the feeling this patch is a workaround for subdev errors. Either that,
>> or the commit log doesn't give me enough information to really understand the
>> problem that's being addressed here.
>
> It's the latter. I should have written VIDIOC_SUBDEV_S_FMT instead of
> just set_fmt in the comment.
>
>> Regards,
>>
>> Hans
>>
>>> + }
>>> +
>>> *fmt = sdformat->format;
>
> Here sdformat is applied to the subdev pad.
>
> regards
> Philipp
>
^ permalink raw reply
* Re: [PATCH v3 1/3] clk: vc5: Add structure to describe particular chip features
From: Alexey Firago @ 2017-04-06 15:45 UTC (permalink / raw)
To: Marek Vasut, mturquette, sboyd, robh+dt, geert, linux-clk,
devicetree
In-Reply-To: <d461ac7d-4081-4444-6875-de4d8013dec3@gmail.com>
On 06.04.2017 17:54, Marek Vasut wrote:
> On 04/06/2017 11:15 AM, Alexey Firago wrote:
>> Introduce vc5_chip_info structure to describe features of a particular
>> VC5 chip (id, number of FODs, number of outputs, flags).
>> For now flags are only used to indicate if chip has internal XTAL.
>> vc5_chip_info is set on probe from the matched of_device_id->data.
>>
>> Also add defines to specify maximum number of FODs and clock outputs
>> supported by the driver.
>>
>> With these changes it should be easier to extend driver to support
>> more VC5 models.
>>
>> Signed-off-by: Alexey Firago <alexey_firago@mentor.com>
>
> Reviewed-by: Marek Vasut <marek.vasut@gmail.com>
>
>> ---
>> drivers/clk/clk-versaclock5.c | 65 +++++++++++++++++++++++++++++++------------
>> 1 file changed, 47 insertions(+), 18 deletions(-)
>>
>> diff --git a/drivers/clk/clk-versaclock5.c b/drivers/clk/clk-versaclock5.c
>> index 56741f3..4e81fb1 100644
>> --- a/drivers/clk/clk-versaclock5.c
>> +++ b/drivers/clk/clk-versaclock5.c
>> @@ -113,12 +113,30 @@
>> #define VC5_MUX_IN_XIN BIT(0)
>> #define VC5_MUX_IN_CLKIN BIT(1)
>>
>> +/* Maximum number of clk_out supported by this driver */
>> +#define VC5_MAX_CLK_OUT_NUM 3
>> +
>> +/* Maximum number of FODs supported by this driver */
>> +#define VC5_MAX_FOD_NUM 2
>> +
>> +/* flags to describe chip features */
>> +/* chip has built-in oscilator */
>> +#define VC5_HAS_INTERNAL_XTAL BIT(0)
>> +
>> /* Supported IDT VC5 models. */
>> enum vc5_model {
>> IDT_VC5_5P49V5923,
>> IDT_VC5_5P49V5933,
>> };
>>
>> +/* Structure to describe features of a particular VC5 model */
>> +struct vc5_chip_info {
>> + const enum vc5_model model;
>> + const unsigned int clk_fod_cnt;
>> + const unsigned int clk_out_cnt;
>> + u32 flags;
>
> Shouldn't this also be const ?
It probably should, since we are not changing flags at runtime
currently. Will resend.
Thanks,
Alexey
^ permalink raw reply
* Re: [PATCH v2 3/4] dt-bindings: display: add support for ZTE VGA device
From: Rob Herring @ 2017-04-06 15:46 UTC (permalink / raw)
To: Shawn Guo
Cc: devicetree@vger.kernel.org, Xin Zhou, Daniel Vetter, Baoyou Xie,
dri-devel, Jun Nie
In-Reply-To: <1491490870-6330-4-git-send-email-shawnguo@kernel.org>
On Thu, Apr 6, 2017 at 10:01 AM, Shawn Guo <shawnguo@kernel.org> wrote:
> From: Shawn Guo <shawn.guo@linaro.org>
>
> It adds bindings doc for ZTE VOU VGA output device.
>
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
> .../devicetree/bindings/display/zte,vou.txt | 21 +++++++++++++++++++++
> 1 file changed, 21 insertions(+)
Acked-by: Rob Herring <robh@kernel.org>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel
^ permalink raw reply
* Re: [PATCH v2] Extend pca9532 device tree support
From: Pavel Machek @ 2017-04-06 15:50 UTC (permalink / raw)
To: Jacek Anaszewski
Cc: Felix Brack, rpurdie-Fm38FmjxZ/leoWH0uzbU5w,
mark.rutland-5wv7dgnIgG8, riku.voipio-X3B1VOXEql0,
linux-leds-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <b54c77d5-b4f7-9517-8c5b-9200c766204a-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 2495 bytes --]
Hi!
> > diff --git a/Documentation/devicetree/bindings/leds/leds-pca9532.txt b/Documentation/devicetree/bindings/leds/leds-pca9532.txt
> > index 198f3ba..8374075 100644
> > --- a/Documentation/devicetree/bindings/leds/leds-pca9532.txt
> > +++ b/Documentation/devicetree/bindings/leds/leds-pca9532.txt
> > @@ -17,6 +17,8 @@ Optional sub-node properties:
> > - label: see Documentation/devicetree/bindings/leds/common.txt
> > - type: Output configuration, see dt-bindings/leds/leds-pca9532.h (default NONE)
> > - linux,default-trigger: see Documentation/devicetree/bindings/leds/common.txt
> > + - default-state: see Documentation/devicetree/bindings/leds/common.txt
> > + This property is only valid for sub-nodes of type <PCA9532_TYPE_LED>.
> >
> > Example:
> > #include <dt-bindings/leds/leds-pca9532.h>
> > @@ -33,6 +35,14 @@ Example:
> > label = "pca:green:power";
> > type = <PCA9532_TYPE_LED>;
> > };
> > + kernel-booting {
> > + type = <PCA9532_TYPE_LED>;
> > + default-state = "on";
> > + };
> > + sys-stat {
> > + type = <PCA9532_TYPE_LED>;
> > + default-state = "keep"; // don't touch, was set by U-Boot
> > + };
>
> Adjusted above indentation to match the preceding lines.
> > @@ -475,6 +494,16 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
> > of_property_read_u32(child, "type", &pdata->leds[i].type);
> > of_property_read_string(child, "linux,default-trigger",
> > &pdata->leds[i].default_trigger);
> > + if (!of_property_read_string(child, "default-state", &state)) {
> > + if (!strcmp(state, "on"))
> > + pdata->leds[i].state = PCA9532_ON;
> > + else if (!strcmp(state, "keep"))
> > + pdata->leds[i].state = PCA9532_KEEP;
> > + else if (!strcmp(state, "pwm0"))
> > + pdata->leds[i].state = PCA9532_PWM0;
> > + else if (!strcmp(state, "pwm1"))
> > + pdata->leds[i].state = PCA9532_PWM1;
> > + }
> > if (++i >= maxleds) {
> > of_node_put(child);
> > break;
This seems to look for "pwm0" and "pwm1" strings, which do not seem to
be documented.
Plus... is it useful to have default-state? We already have default
trigger. If we keep the value by default (on PC, we do something like
that) this patch should not be neccessary?
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply
* Re: [PATCH] [media] imx: csi: retain current field order and colorimetry setting as default
From: Philipp Zabel @ 2017-04-06 16:01 UTC (permalink / raw)
To: Hans Verkuil
Cc: Steve Longerbeam, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A,
kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc,
linux-I+IVW8TIWO2tmTQ+vhA3Yw, mchehab-DgEjT+Ai2ygdnm+yROfE0A,
nick-gcszYUEDH4VrovVCs/uTlw, markus.heiser-O6JHGLzbNUwb1SvskN2V4Q,
laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw,
bparrot-l0cyMroinI0, geert-Td1EMuHUCqxL1ZNQvxDV9g,
arnd-r2nGTMty4D4, sudipm.mukherjee-Re5JQEeQqe8AvxtiuMwx3w,
minghsiu.tsai-NuS5LvNUpcJWk0Htik3J/w,
tiffany.lin-NuS5LvNUpcJWk0Htik3J/w,
jean-christophe.trotin-qxv4g6HH51o,
horms+renesas-/R6kz+dDXgpPR4JQBCEnsQ,
niklas.soderlund+renesas-1zkq55x86MTxsAP9Fp7wbw,
robert.jarzmik-GANU6spQydw, songjun.wu-UWL1GkI3JZL3oGB3hsPCZA,
andrew-ct.chen-NuS5LvNUpcJWk0Htik3J/w,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r,
shuah-DgEjT+Ai2ygdnm+yROfE0A, sakari.ailus-VuQAYsv1563Yd54FQh9/CA,
pavel-+ZI9xUNit7I, devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-medi
In-Reply-To: <59e72974-bfb0-6061-8b13-5f13f8723ba6-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org>
On Thu, 2017-04-06 at 17:43 +0200, Hans Verkuil wrote:
> On 04/06/2017 04:54 PM, Philipp Zabel wrote:
> > On Thu, 2017-04-06 at 16:20 +0200, Hans Verkuil wrote:
> >> On 04/06/2017 03:55 PM, Philipp Zabel wrote:
> >>> If the the field order is set to ANY in set_fmt, choose the currently
> >>> set field order. If the colorspace is set to DEFAULT, choose the current
> >>> colorspace. If any of xfer_func, ycbcr_enc or quantization are set to
> >>> DEFAULT, either choose the current setting, or the default setting for the
> >>> new colorspace, if non-DEFAULT colorspace was given.
> >>>
> >>> This allows to let field order and colorimetry settings be propagated
> >>> from upstream by calling media-ctl on the upstream entity source pad,
> >>> and then call media-ctl on the sink pad to manually set the input frame
> >>> interval, without changing the already set field order and colorimetry
> >>> information.
> >>>
> >>> Signed-off-by: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> >>> ---
> >>> This is based on imx-media-staging-md-v14, and it is supposed to allow
> >>> configuring the pipeline with media-ctl like this:
> >>>
> >>> 1) media-ctl --set-v4l2 "'tc358743 1-000f':0[fmt:UYVY8_1X16/1920x1080]"
> >>> 2) media-ctl --set-v4l2 "'imx6-mipi-csi2':1[fmt:UYVY8_1X16/1920x108]"
> >>> 3) media-ctl --set-v4l2 "'ipu1_csi0_mux':2[fmt:UYVY8_1X16/1920x1080]"
> >>> 4) media-ctl --set-v4l2 "'ipu1_csi0':0[fmt:UYVY8_1X16/1920x1080@1/60]"
> >>> 5) media-ctl --set-v4l2 "'ipu1_csi0':2[fmt:AYUV32/1920x1080@1/30]"
> >>>
> >>> Without having step 4) overwrite the colorspace and field order set on
> >>> 'ipu1_csi0':0 by the propagation in step 3).
> >>> ---
> >>> drivers/staging/media/imx/imx-media-csi.c | 34 +++++++++++++++++++++++++++++++
> >>> 1 file changed, 34 insertions(+)
> >>>
> >>> diff --git a/drivers/staging/media/imx/imx-media-csi.c b/drivers/staging/media/imx/imx-media-csi.c
> >>> index 64dc454f6b371..d94ce1de2bf05 100644
> >>> --- a/drivers/staging/media/imx/imx-media-csi.c
> >>> +++ b/drivers/staging/media/imx/imx-media-csi.c
> >>> @@ -1325,6 +1325,40 @@ static int csi_set_fmt(struct v4l2_subdev *sd,
> >>> csi_try_fmt(priv, sensor, cfg, sdformat, crop, compose, &cc);
> >>>
> >>> fmt = __csi_get_fmt(priv, cfg, sdformat->pad, sdformat->which);
> >>> +
> >>> + /* Retain current field setting as default */
> >>> + if (sdformat->format.field == V4L2_FIELD_ANY)
> >>> + sdformat->format.field = fmt->field;
> >>
> >> sdformat->format.field should never be FIELD_ANY. If it is, then that's a
> >> subdev bug and I'm pretty sure FIELD_NONE was intended.
> >
> > This is the subdev. sdformat is passed in from userspace, so we have to
> > deal with it being set to ANY. I'm trying hard right now not to return
> > ANY though. The values in sdformat->format are applied to fmt down
> > below.
>
> Do you have a git tree with this patch? It is really hard to review without
> having the full imx-media-csi.c source.
The patch applies on top of
https://github.com/slongerbeam/mediatree.git imx-media-staging-md-v14
I have uploaded a branch
git://git.pengutronix.de/git/pza/linux imx-media-staging-md-v14+color
with the patch applied on top.
> I think one problem is that it is not clearly defined how subdevs and colorspace
> information should work.
regards
Philipp
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 1/1] gpio: core: Decouple open drain/source flag with active low/high
From: Andy Shevchenko @ 2017-04-06 16:10 UTC (permalink / raw)
To: Laxman Dewangan
Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
Frank Rowand, linux-gpio@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree
In-Reply-To: <1491485752-28030-1-git-send-email-ldewangan@nvidia.com>
On Thu, Apr 6, 2017 at 4:35 PM, Laxman Dewangan <ldewangan@nvidia.com> wrote:
> Currently, the GPIO interface is said to Open Drain if it is Single
> Ended and active LOW. Similarly, it is said as Open Source if it is
> Single Ended and active HIGH.
>
> The active HIGH/LOW is used in the interface for setting the pin
> state to HIGH or LOW when enabling/disabling the interface.
>
> In Open Drain interface, pin is set to HIGH by putting pin in
> high impedance and LOW by driving to the LOW.
>
> In Open Source interface, pin is set to HIGH by driving pin to
> HIGH and set to LOW by putting pin in high impedance.
>
> With above, the Open Drain/Source is unrelated to the active LOW/HIGH
> in interface. There is interface where the enable/disable of interface
> is ether active LOW or HIGH but it is Open Drain type.
>
> Hence decouple the Open Drain with Single Ended + Active LOW and
> Open Source with Single Ended + Active HIGH.
>
> Adding different flag for the Open Drain/Open Source which is valid
> only when Single ended flag is enabled.
> if (single_ended) {
> - if (active_low)
> + if (open_drain)
This breaks ACPI case, right?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH v2 0/5] Add STM32H7 DAC driver
From: Fabrice Gasnier @ 2017-04-06 16:11 UTC (permalink / raw)
To: jic23, linux, robh+dt, linux-arm-kernel, devicetree, linux-kernel
Cc: mark.rutland, benjamin.gaignard, lars, alexandre.torgue,
linux-iio, pmeerw, mcoquelin.stm32, knaack.h, fabrice.gasnier,
benjamin.gaignard
This patchset adds support for the STM32H7 DAC controller
It's a 12-bit, voltage output digital-to-analog converter. It has two
output channels, each with its own converter, trigger sources and
waveform generator.
Each channel can be used independently, so common resources are managed
in stm32-dac-core driver (e.g. clock, reset, regulator, registers).
One IIO device is instantiated per DAC output channel, in stm32-dac
driver, so each channel can have its own trigger.
Please find bellow basic examples, using this driver to:
- generate DC voltage output on channel1
- generate a triangle waveform on channel2
# set max DC voltage / enable / min DC voltage / disable on out1:
cd /sys/bus/iio/devices/iio\:device0
echo 4095 > out_voltage1_raw
echo 1 > out_voltage1_powerdown
echo 0 > out_voltage1_raw
echo 0 > out_voltage1_powerdown
cat out_voltage_powerdown_mode_available
Hi-Z enable
# configure timer trigger, and set triangle waveform with half
# amplitude and DC offset on out2:
cd /sys/bus/iio/devices/trigger9
cat name
tim2_trgo
echo 10000 > sampling_frequency
cd /sys/bus/iio/devices/iio\:device1
echo triangle > out_voltage2_wavetype
echo 2047 > out_voltage2_amplitude
echo 1024 > out_voltage2_offset
echo tim2_trgo > trigger/current_trigger
echo 1 > out_voltage2_powerdown
---
Changes in v2:
- Update dt binding, use 'reg' property to select channel
- Use 'powerdown' attribute instead of 'enable'
- Added set_trigger callback
- Use 'offset' attribute in waveform generation mode to add DC offset
- rework ABI for waveform generation mode
- Various typos, comments
Fabrice Gasnier (5):
dt-bindings: iio: stm32-dac: Add support for STM32 DAC
iio: dac: add support for stm32 DAC
iio: trigger: add set_trigger callback to notify device
iio: dac: stm32: add support for trigger events
iio: dac: stm32: add support for waveform generator
Documentation/ABI/testing/sysfs-bus-iio-dac-stm32 | 16 +
.../devicetree/bindings/iio/dac/st,stm32-dac.txt | 61 ++
drivers/iio/dac/Kconfig | 18 +
drivers/iio/dac/Makefile | 2 +
drivers/iio/dac/stm32-dac-core.c | 180 ++++++
drivers/iio/dac/stm32-dac-core.h | 63 ++
drivers/iio/dac/stm32-dac.c | 631 +++++++++++++++++++++
drivers/iio/industrialio-trigger.c | 6 +
include/linux/iio/iio.h | 2 +
9 files changed, 979 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-dac-stm32
create mode 100644 Documentation/devicetree/bindings/iio/dac/st,stm32-dac.txt
create mode 100644 drivers/iio/dac/stm32-dac-core.c
create mode 100644 drivers/iio/dac/stm32-dac-core.h
create mode 100644 drivers/iio/dac/stm32-dac.c
--
1.9.1
^ permalink raw reply
* [PATCH v2 1/5] dt-bindings: iio: stm32-dac: Add support for STM32 DAC
From: Fabrice Gasnier @ 2017-04-06 16:11 UTC (permalink / raw)
To: jic23-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-iio-u79uwXL29TY76Z2rM5mHXA, mark.rutland-5wv7dgnIgG8,
mcoquelin.stm32-Re5JQEeQqe8AvxtiuMwx3w,
alexandre.torgue-qxv4g6HH51o, lars-Qo5EllUWu/uELgA04lAiVw,
knaack.h-Mmb7MZpHnFY, pmeerw-jW+XmwGofnusTnJN9+BGXg,
fabrice.gasnier-qxv4g6HH51o,
benjamin.gaignard-QSEj5FYQhm4dnm+yROfE0A,
benjamin.gaignard-qxv4g6HH51o
In-Reply-To: <1491495116-7209-1-git-send-email-fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
Document STMicroelectronics STM32 DAC (digital-to-analog converter).
Signed-off-by: Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
---
Changes in v2:
- use 'reg' instead of 'st,dac-channel' property
- remove alignment from description
---
.../devicetree/bindings/iio/dac/st,stm32-dac.txt | 61 ++++++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/dac/st,stm32-dac.txt
diff --git a/Documentation/devicetree/bindings/iio/dac/st,stm32-dac.txt b/Documentation/devicetree/bindings/iio/dac/st,stm32-dac.txt
new file mode 100644
index 0000000..bcee71f
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/dac/st,stm32-dac.txt
@@ -0,0 +1,61 @@
+STMicroelectronics STM32 DAC
+
+The STM32 DAC is a 12-bit voltage output digital-to-analog converter. The DAC
+may be configured in 8 or 12-bit mode. It has two output channels, each with
+its own converter.
+It has built-in noise and triangle waveform generator and supports external
+triggers for conversions. The DAC's output buffer allows a high drive output
+current.
+
+Contents of a stm32 dac root node:
+-----------------------------------
+Required properties:
+- compatible: Must be "st,stm32h7-dac-core".
+- reg: Offset and length of the device's register set.
+- clocks: Must contain an entry for pclk (which feeds the peripheral bus
+ interface)
+- clock-names: Must be "pclk".
+- vref-supply: Phandle to the vref+ input analog reference supply.
+- #address-cells = <1>;
+- #size-cells = <0>;
+
+Optional properties:
+- resets: Must contain the phandle to the reset controller.
+- A pinctrl state named "default" for each DAC channel may be defined to set
+ DAC_OUTx pin in mode of operation for analog output on external pin.
+
+Contents of a stm32 dac child node:
+-----------------------------------
+DAC core node should contain at least one subnode, representing a
+DAC instance/channel available on the machine.
+
+Required properties:
+- compatible: Must be "st,stm32-dac".
+- reg: Must be either 1 or 2, to define (single) channel in use
+- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers" in
+ Documentation/devicetree/bindings/iio/iio-bindings.txt
+
+Example:
+ dac: dac@40007400 {
+ compatible = "st,stm32h7-dac-core";
+ reg = <0x40007400 0x400>;
+ clocks = <&clk>;
+ clock-names = "pclk";
+ vref-supply = <®_vref>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&dac_out1 &dac_out2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dac1: dac@1 {
+ compatible = "st,stm32-dac";
+ #io-channels-cells = <1>;
+ reg = <1>;
+ };
+
+ dac2: dac@2 {
+ compatible = "st,stm32-dac";
+ #io-channels-cells = <1>;
+ reg = <2>;
+ };
+ };
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 2/5] iio: dac: add support for stm32 DAC
From: Fabrice Gasnier @ 2017-04-06 16:11 UTC (permalink / raw)
To: jic23-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-iio-u79uwXL29TY76Z2rM5mHXA, mark.rutland-5wv7dgnIgG8,
mcoquelin.stm32-Re5JQEeQqe8AvxtiuMwx3w,
alexandre.torgue-qxv4g6HH51o, lars-Qo5EllUWu/uELgA04lAiVw,
knaack.h-Mmb7MZpHnFY, pmeerw-jW+XmwGofnusTnJN9+BGXg,
fabrice.gasnier-qxv4g6HH51o,
benjamin.gaignard-QSEj5FYQhm4dnm+yROfE0A,
benjamin.gaignard-qxv4g6HH51o
In-Reply-To: <1491495116-7209-1-git-send-email-fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
Add support for STMicroelectronics STM32 DAC. It's a 12-bit, voltage
output digital-to-analog converter. It has two output channels, each
with its own converter.
It supports 8 bits or 12bits left/right aligned data format. Only
12bits right-aligned is used here. It has built-in noise or
triangle waveform generator, and supports external triggers for
conversions.
Each channel can be used independently, with separate trigger, then
separate IIO devices are used to handle this. Core driver is intended
to share common resources such as clock, reset, reference voltage and
registers.
Signed-off-by: Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
---
Changes in v2:
- Define 'Hi-Z'/'enable' powerdown modes instead of using 'enable'
attribute normally not used for DACs.
- use 'reg' instead of 'st,dac-channel' property
- Use macro to differentiate channels
- Fix typos, remove leading '&' for functions
- Add comments on single channel per device
- Use devm_iio_device_register variant, removes need for .remove
---
drivers/iio/dac/Kconfig | 15 ++
drivers/iio/dac/Makefile | 2 +
drivers/iio/dac/stm32-dac-core.c | 180 ++++++++++++++++++++
drivers/iio/dac/stm32-dac-core.h | 51 ++++++
drivers/iio/dac/stm32-dac.c | 350 +++++++++++++++++++++++++++++++++++++++
5 files changed, 598 insertions(+)
create mode 100644 drivers/iio/dac/stm32-dac-core.c
create mode 100644 drivers/iio/dac/stm32-dac-core.h
create mode 100644 drivers/iio/dac/stm32-dac.c
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index d3084028..7198648 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -274,6 +274,21 @@ config MCP4922
To compile this driver as a module, choose M here: the module
will be called mcp4922.
+config STM32_DAC
+ tristate "STMicroelectronics STM32 DAC"
+ depends on (ARCH_STM32 && OF) || COMPILE_TEST
+ depends on REGULATOR
+ select STM32_DAC_CORE
+ help
+ Say yes here to build support for STMicroelectronics STM32 Digital
+ to Analog Converter (DAC).
+
+ This driver can also be built as a module. If so, the module
+ will be called stm32-dac.
+
+config STM32_DAC_CORE
+ tristate
+
config VF610_DAC
tristate "Vybrid vf610 DAC driver"
depends on OF
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index f01bf4a..afe8ae7 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -29,4 +29,6 @@ obj-$(CONFIG_MAX517) += max517.o
obj-$(CONFIG_MAX5821) += max5821.o
obj-$(CONFIG_MCP4725) += mcp4725.o
obj-$(CONFIG_MCP4922) += mcp4922.o
+obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o
+obj-$(CONFIG_STM32_DAC) += stm32-dac.o
obj-$(CONFIG_VF610_DAC) += vf610_dac.o
diff --git a/drivers/iio/dac/stm32-dac-core.c b/drivers/iio/dac/stm32-dac-core.c
new file mode 100644
index 0000000..75e4878
--- /dev/null
+++ b/drivers/iio/dac/stm32-dac-core.c
@@ -0,0 +1,180 @@
+/*
+ * This file is part of STM32 DAC driver
+ *
+ * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
+ * Author: Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>.
+ *
+ * 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/module.h>
+#include <linux/of_platform.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+
+#include "stm32-dac-core.h"
+
+/**
+ * struct stm32_dac_priv - stm32 DAC core private data
+ * @pclk: peripheral clock common for all DACs
+ * @rst: peripheral reset control
+ * @vref: regulator reference
+ * @common: Common data for all DAC instances
+ */
+struct stm32_dac_priv {
+ struct clk *pclk;
+ struct reset_control *rst;
+ struct regulator *vref;
+ struct stm32_dac_common common;
+};
+
+static struct stm32_dac_priv *to_stm32_dac_priv(struct stm32_dac_common *com)
+{
+ return container_of(com, struct stm32_dac_priv, common);
+}
+
+static const struct regmap_config stm32_dac_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = sizeof(u32),
+ .max_register = 0x3fc,
+};
+
+static int stm32_dac_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct stm32_dac_priv *priv;
+ struct regmap *regmap;
+ struct resource *res;
+ void __iomem *mmio;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(mmio))
+ return PTR_ERR(mmio);
+
+ regmap = devm_regmap_init_mmio(dev, mmio, &stm32_dac_regmap_cfg);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ priv->common.regmap = regmap;
+
+ priv->vref = devm_regulator_get(dev, "vref");
+ if (IS_ERR(priv->vref)) {
+ ret = PTR_ERR(priv->vref);
+ dev_err(dev, "vref get failed, %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_enable(priv->vref);
+ if (ret < 0) {
+ dev_err(dev, "vref enable failed\n");
+ return ret;
+ }
+
+ ret = regulator_get_voltage(priv->vref);
+ if (ret < 0) {
+ dev_err(dev, "vref get voltage failed, %d\n", ret);
+ goto err_vref;
+ }
+ priv->common.vref_mv = ret / 1000;
+ dev_dbg(dev, "vref+=%dmV\n", priv->common.vref_mv);
+
+ priv->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(priv->pclk)) {
+ ret = PTR_ERR(priv->pclk);
+ dev_err(dev, "pclk get failed\n");
+ goto err_vref;
+ }
+
+ ret = clk_prepare_enable(priv->pclk);
+ if (ret < 0) {
+ dev_err(dev, "pclk enable failed\n");
+ goto err_vref;
+ }
+
+ priv->rst = devm_reset_control_get(dev, NULL);
+ if (!IS_ERR(priv->rst)) {
+ reset_control_assert(priv->rst);
+ udelay(2);
+ reset_control_deassert(priv->rst);
+ }
+
+ /* When clock speed is higher than 80MHz, set HFSEL */
+ priv->common.hfsel = (clk_get_rate(priv->pclk) > 80000000UL);
+ ret = regmap_update_bits(regmap, STM32_DAC_CR, STM32H7_DAC_CR_HFSEL,
+ priv->common.hfsel ? STM32H7_DAC_CR_HFSEL : 0);
+ if (ret)
+ goto err_pclk;
+
+ platform_set_drvdata(pdev, &priv->common);
+
+ ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to populate DT children\n");
+ goto err_pclk;
+ }
+
+ return 0;
+
+err_pclk:
+ clk_disable_unprepare(priv->pclk);
+err_vref:
+ regulator_disable(priv->vref);
+
+ return ret;
+}
+
+static int stm32_dac_remove(struct platform_device *pdev)
+{
+ struct stm32_dac_common *common = platform_get_drvdata(pdev);
+ struct stm32_dac_priv *priv = to_stm32_dac_priv(common);
+
+ of_platform_depopulate(&pdev->dev);
+ clk_disable_unprepare(priv->pclk);
+ regulator_disable(priv->vref);
+
+ return 0;
+}
+
+static const struct of_device_id stm32_dac_of_match[] = {
+ { .compatible = "st,stm32h7-dac-core", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, stm32_dac_of_match);
+
+static struct platform_driver stm32_dac_driver = {
+ .probe = stm32_dac_probe,
+ .remove = stm32_dac_remove,
+ .driver = {
+ .name = "stm32-dac-core",
+ .of_match_table = stm32_dac_of_match,
+ },
+};
+module_platform_driver(stm32_dac_driver);
+
+MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 DAC core driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:stm32-dac-core");
diff --git a/drivers/iio/dac/stm32-dac-core.h b/drivers/iio/dac/stm32-dac-core.h
new file mode 100644
index 0000000..daf0993
--- /dev/null
+++ b/drivers/iio/dac/stm32-dac-core.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of STM32 DAC driver
+ *
+ * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
+ * Author: Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>.
+ *
+ * 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/>.
+ */
+
+#ifndef __STM32_DAC_CORE_H
+#define __STM32_DAC_CORE_H
+
+#include <linux/regmap.h>
+
+/* STM32 DAC registers */
+#define STM32_DAC_CR 0x00
+#define STM32_DAC_DHR12R1 0x08
+#define STM32_DAC_DHR12R2 0x14
+#define STM32_DAC_DOR1 0x2C
+#define STM32_DAC_DOR2 0x30
+
+/* STM32_DAC_CR bit fields */
+#define STM32_DAC_CR_EN1 BIT(0)
+#define STM32H7_DAC_CR_HFSEL BIT(15)
+#define STM32_DAC_CR_EN2 BIT(16)
+
+/**
+ * struct stm32_dac_common - stm32 DAC driver common data (for all instances)
+ * @regmap: DAC registers shared via regmap
+ * @vref_mv: reference voltage (mv)
+ * @hfsel: high speed bus clock selected
+ */
+struct stm32_dac_common {
+ struct regmap *regmap;
+ int vref_mv;
+ bool hfsel;
+};
+
+#endif
diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c
new file mode 100644
index 0000000..c0d993a
--- /dev/null
+++ b/drivers/iio/dac/stm32-dac.c
@@ -0,0 +1,350 @@
+/*
+ * This file is part of STM32 DAC driver
+ *
+ * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
+ * Authors: Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>
+ * Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
+ *
+ * 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/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "stm32-dac-core.h"
+
+#define STM32_DAC_CHANNEL_1 1
+#define STM32_DAC_CHANNEL_2 2
+#define STM32_DAC_IS_CHAN_1(ch) ((ch) & STM32_DAC_CHANNEL_1)
+
+/**
+ * struct stm32_dac - private data of DAC driver
+ * @common: reference to DAC common data
+ */
+struct stm32_dac {
+ struct stm32_dac_common *common;
+};
+
+static int stm32_dac_is_enabled(struct iio_dev *indio_dev, int channel)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ u32 en, val;
+ int ret;
+
+ ret = regmap_read(dac->common->regmap, STM32_DAC_CR, &val);
+ if (ret < 0)
+ return ret;
+ if (STM32_DAC_IS_CHAN_1(channel))
+ en = FIELD_GET(STM32_DAC_CR_EN1, val);
+ else
+ en = FIELD_GET(STM32_DAC_CR_EN2, val);
+
+ return !!en;
+}
+
+static int stm32_dac_enable(struct iio_dev *indio_dev, int channel)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ u32 en = STM32_DAC_IS_CHAN_1(channel) ?
+ STM32_DAC_CR_EN1 : STM32_DAC_CR_EN2;
+ int ret;
+
+ ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, en, en);
+ if (ret < 0) {
+ dev_err(&indio_dev->dev, "Enable failed\n");
+ return ret;
+ }
+
+ /*
+ * When HFSEL is set, it is not allowed to write the DHRx register
+ * during 8 clock cycles after the ENx bit is set. It is not allowed
+ * to make software/hardware trigger during this period either.
+ */
+ if (dac->common->hfsel)
+ udelay(1);
+
+ return 0;
+}
+
+static int stm32_dac_disable(struct iio_dev *indio_dev, int channel)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ u32 en = STM32_DAC_IS_CHAN_1(channel) ?
+ STM32_DAC_CR_EN1 : STM32_DAC_CR_EN2;
+ int ret;
+
+ ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, en, 0);
+ if (ret)
+ dev_err(&indio_dev->dev, "Disable failed\n");
+
+ return ret;
+}
+
+static int stm32_dac_get_value(struct stm32_dac *dac, int channel, int *val)
+{
+ int ret;
+
+ if (STM32_DAC_IS_CHAN_1(channel))
+ ret = regmap_read(dac->common->regmap, STM32_DAC_DOR1, val);
+ else
+ ret = regmap_read(dac->common->regmap, STM32_DAC_DOR2, val);
+
+ return ret ? ret : IIO_VAL_INT;
+}
+
+static int stm32_dac_set_value(struct stm32_dac *dac, int channel, int val)
+{
+ int ret;
+
+ if (STM32_DAC_IS_CHAN_1(channel))
+ ret = regmap_write(dac->common->regmap, STM32_DAC_DHR12R1, val);
+ else
+ ret = regmap_write(dac->common->regmap, STM32_DAC_DHR12R2, val);
+
+ return ret;
+}
+
+static int stm32_dac_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return stm32_dac_get_value(dac, chan->channel, val);
+ case IIO_CHAN_INFO_SCALE:
+ *val = dac->common->vref_mv;
+ *val2 = chan->scan_type.realbits;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int stm32_dac_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return stm32_dac_set_value(dac, chan->channel, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int stm32_dac_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned reg, unsigned writeval,
+ unsigned *readval)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+
+ if (!readval)
+ return regmap_write(dac->common->regmap, reg, writeval);
+ else
+ return regmap_read(dac->common->regmap, reg, readval);
+}
+
+static const struct iio_info stm32_dac_iio_info = {
+ .read_raw = stm32_dac_read_raw,
+ .write_raw = stm32_dac_write_raw,
+ .debugfs_reg_access = stm32_dac_debugfs_reg_access,
+ .driver_module = THIS_MODULE,
+};
+
+static const char * const stm32_dac_powerdown_modes[] = {
+ "Hi-Z",
+ "enable",
+};
+
+static int stm32_dac_get_powerdown_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ return stm32_dac_is_enabled(indio_dev, chan->channel);
+}
+
+static int stm32_dac_set_powerdown_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int type)
+{
+ if (type)
+ return stm32_dac_enable(indio_dev, chan->channel);
+ else
+ return stm32_dac_disable(indio_dev, chan->channel);
+}
+
+static ssize_t stm32_dac_read_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ int ret = stm32_dac_is_enabled(indio_dev, chan->channel);
+
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t stm32_dac_write_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ unsigned int en;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &en);
+ if (ret)
+ return ret;
+
+ ret = stm32_dac_set_powerdown_mode(indio_dev, chan, en);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+static const struct iio_enum stm32_dac_powerdown_mode_en = {
+ .items = stm32_dac_powerdown_modes,
+ .num_items = ARRAY_SIZE(stm32_dac_powerdown_modes),
+ .get = stm32_dac_get_powerdown_mode,
+ .set = stm32_dac_set_powerdown_mode,
+};
+
+static const struct iio_chan_spec_ext_info stm32_dac_ext_info[] = {
+ {
+ .name = "powerdown",
+ .read = stm32_dac_read_powerdown,
+ .write = stm32_dac_write_powerdown,
+ .shared = IIO_SEPARATE,
+ },
+ IIO_ENUM("powerdown_mode", IIO_SEPARATE, &stm32_dac_powerdown_mode_en),
+ IIO_ENUM_AVAILABLE("powerdown_mode", &stm32_dac_powerdown_mode_en),
+ {},
+};
+
+#define STM32_DAC_CHANNEL(chan, name) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = chan, \
+ .info_mask_separate = \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ /* scan_index is always 0 as num_channels is 1 */ \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ }, \
+ .datasheet_name = name, \
+ .ext_info = stm32_dac_ext_info \
+}
+
+static const struct iio_chan_spec stm32_dac_channels[] = {
+ STM32_DAC_CHANNEL(STM32_DAC_CHANNEL_1, "out1"),
+ STM32_DAC_CHANNEL(STM32_DAC_CHANNEL_2, "out2"),
+};
+
+static int stm32_dac_chan_of_init(struct iio_dev *indio_dev)
+{
+ struct device_node *np = indio_dev->dev.of_node;
+ unsigned int i;
+ u32 channel;
+ int ret;
+
+ ret = of_property_read_u32(np, "reg", &channel);
+ if (ret) {
+ dev_err(&indio_dev->dev, "Failed to read reg property\n");
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(stm32_dac_channels); i++) {
+ if (stm32_dac_channels[i].channel == channel)
+ break;
+ }
+ if (i >= ARRAY_SIZE(stm32_dac_channels)) {
+ dev_err(&indio_dev->dev, "Invalid st,dac-channel\n");
+ return -EINVAL;
+ }
+
+ indio_dev->channels = &stm32_dac_channels[i];
+ /*
+ * Expose only one channel here, as they can be used independently,
+ * with separate trigger. Then separate IIO devices are instantiated
+ * to manage this.
+ */
+ indio_dev->num_channels = 1;
+
+ return 0;
+};
+
+static int stm32_dac_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct iio_dev *indio_dev;
+ struct stm32_dac *dac;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*dac));
+ if (!indio_dev)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, indio_dev);
+
+ dac = iio_priv(indio_dev);
+ dac->common = dev_get_drvdata(pdev->dev.parent);
+ 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_dac_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = stm32_dac_chan_of_init(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static const struct of_device_id stm32_dac_of_match[] = {
+ { .compatible = "st,stm32-dac", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, stm32_dac_of_match);
+
+static struct platform_driver stm32_dac_driver = {
+ .probe = stm32_dac_probe,
+ .driver = {
+ .name = "stm32-dac",
+ .of_match_table = stm32_dac_of_match,
+ },
+};
+module_platform_driver(stm32_dac_driver);
+
+MODULE_ALIAS("platform:stm32-dac");
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay-qxv4g6HH51o@public.gmane.org>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 DAC driver");
+MODULE_LICENSE("GPL v2");
--
1.9.1
^ permalink raw reply related
* [PATCH v2 3/5] iio: trigger: add set_trigger callback to notify device
From: Fabrice Gasnier @ 2017-04-06 16:11 UTC (permalink / raw)
To: jic23, linux, robh+dt, linux-arm-kernel, devicetree, linux-kernel
Cc: mark.rutland, benjamin.gaignard, lars, alexandre.torgue,
linux-iio, pmeerw, mcoquelin.stm32, knaack.h, fabrice.gasnier,
benjamin.gaignard
In-Reply-To: <1491495116-7209-1-git-send-email-fabrice.gasnier@st.com>
Add 'set_trigger' callback to iio info structure. This allows device
to be notified when a trigger (or no trigger) has been assigned. This
maybe useful for instance in non buffered mode (e.g. event triggered).
This is called, after trigger and device side validate callbacks have
been called.
Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
---
Changes in v2:
- Added set_trigger callback used in remaining patches
---
drivers/iio/industrialio-trigger.c | 6 ++++++
include/linux/iio/iio.h | 2 ++
2 files changed, 8 insertions(+)
diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c
index 978e1592..010bdf2 100644
--- a/drivers/iio/industrialio-trigger.c
+++ b/drivers/iio/industrialio-trigger.c
@@ -434,6 +434,12 @@ static ssize_t iio_trigger_write_current(struct device *dev,
goto out_trigger_put;
}
+ if (indio_dev->info->set_trigger) {
+ ret = indio_dev->info->set_trigger(indio_dev, trig);
+ if (ret)
+ goto out_trigger_put;
+ }
+
indio_dev->trig = trig;
if (oldtrig) {
diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h
index 3f5ea2e..9f51065 100644
--- a/include/linux/iio/iio.h
+++ b/include/linux/iio/iio.h
@@ -399,6 +399,7 @@ static inline bool iio_channel_has_available(const struct iio_chan_spec *chan,
* @write_event_value: write a configuration value for the event.
* @validate_trigger: function to validate the trigger when the
* current trigger gets changed.
+ * @set_trigger: function to notify current trigger gets changed.
* @update_scan_mode: function to configure device and scan buffer when
* channels have changed
* @debugfs_reg_access: function to read or write register value of device
@@ -478,6 +479,7 @@ struct iio_info {
int (*validate_trigger)(struct iio_dev *indio_dev,
struct iio_trigger *trig);
+ int (*set_trigger)(struct iio_dev *indio_dev, struct iio_trigger *trig);
int (*update_scan_mode)(struct iio_dev *indio_dev,
const unsigned long *scan_mask);
int (*debugfs_reg_access)(struct iio_dev *indio_dev,
--
1.9.1
^ permalink raw reply related
* [PATCH v2 4/5] iio: dac: stm32: add support for trigger events
From: Fabrice Gasnier @ 2017-04-06 16:11 UTC (permalink / raw)
To: jic23-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-iio-u79uwXL29TY76Z2rM5mHXA, mark.rutland-5wv7dgnIgG8,
mcoquelin.stm32-Re5JQEeQqe8AvxtiuMwx3w,
alexandre.torgue-qxv4g6HH51o, lars-Qo5EllUWu/uELgA04lAiVw,
knaack.h-Mmb7MZpHnFY, pmeerw-jW+XmwGofnusTnJN9+BGXg,
fabrice.gasnier-qxv4g6HH51o,
benjamin.gaignard-QSEj5FYQhm4dnm+yROfE0A,
benjamin.gaignard-qxv4g6HH51o
In-Reply-To: <1491495116-7209-1-git-send-email-fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
STM32 DAC supports triggers to synchronize conversions. When trigger
occurs, data is transferred from DHR (data holding register) to DOR
(data output register) so output voltage is updated.
Both hardware and software triggers are supported.
Signed-off-by: Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
---
Changes in v2:
- Fix issue with trigger, by using set_trigger callback
- trigger can now be assigned, no matters powerdown state
---
drivers/iio/dac/Kconfig | 3 +
drivers/iio/dac/stm32-dac-core.h | 8 +++
drivers/iio/dac/stm32-dac.c | 127 ++++++++++++++++++++++++++++++++++++++-
3 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 7198648..786c38b 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -278,6 +278,9 @@ config STM32_DAC
tristate "STMicroelectronics STM32 DAC"
depends on (ARCH_STM32 && OF) || COMPILE_TEST
depends on REGULATOR
+ select IIO_TRIGGERED_EVENT
+ select IIO_STM32_TIMER_TRIGGER
+ select MFD_STM32_TIMERS
select STM32_DAC_CORE
help
Say yes here to build support for STMicroelectronics STM32 Digital
diff --git a/drivers/iio/dac/stm32-dac-core.h b/drivers/iio/dac/stm32-dac-core.h
index daf0993..e51a468 100644
--- a/drivers/iio/dac/stm32-dac-core.h
+++ b/drivers/iio/dac/stm32-dac-core.h
@@ -26,6 +26,7 @@
/* STM32 DAC registers */
#define STM32_DAC_CR 0x00
+#define STM32_DAC_SWTRIGR 0x04
#define STM32_DAC_DHR12R1 0x08
#define STM32_DAC_DHR12R2 0x14
#define STM32_DAC_DOR1 0x2C
@@ -33,9 +34,16 @@
/* STM32_DAC_CR bit fields */
#define STM32_DAC_CR_EN1 BIT(0)
+#define STM32H7_DAC_CR_TEN1 BIT(1)
+#define STM32H7_DAC_CR_TSEL1_SHIFT 2
+#define STM32H7_DAC_CR_TSEL1 GENMASK(5, 2)
#define STM32H7_DAC_CR_HFSEL BIT(15)
#define STM32_DAC_CR_EN2 BIT(16)
+/* STM32_DAC_SWTRIGR bit fields */
+#define STM32_DAC_SWTRIGR_SWTRIG1 BIT(0)
+#define STM32_DAC_SWTRIGR_SWTRIG2 BIT(1)
+
/**
* struct stm32_dac_common - stm32 DAC driver common data (for all instances)
* @regmap: DAC registers shared via regmap
diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c
index c0d993a..a7a078e 100644
--- a/drivers/iio/dac/stm32-dac.c
+++ b/drivers/iio/dac/stm32-dac.c
@@ -23,6 +23,10 @@
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/iio/iio.h>
+#include <linux/iio/timer/stm32-timer-trigger.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_event.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -32,15 +36,113 @@
#define STM32_DAC_CHANNEL_1 1
#define STM32_DAC_CHANNEL_2 2
#define STM32_DAC_IS_CHAN_1(ch) ((ch) & STM32_DAC_CHANNEL_1)
+/* channel2 shift */
+#define STM32_DAC_CHAN2_SHIFT 16
/**
* struct stm32_dac - private data of DAC driver
* @common: reference to DAC common data
+ * @swtrig: Using software trigger
*/
struct stm32_dac {
struct stm32_dac_common *common;
+ bool swtrig;
};
+/**
+ * struct stm32_dac_trig_info - DAC trigger info
+ * @name: name of the trigger, corresponding to its source
+ * @tsel: trigger selection, value to be configured in DAC_CR.TSELx
+ */
+struct stm32_dac_trig_info {
+ const char *name;
+ u32 tsel;
+};
+
+static const struct stm32_dac_trig_info stm32h7_dac_trinfo[] = {
+ { "swtrig", 0 },
+ { TIM1_TRGO, 1 },
+ { TIM2_TRGO, 2 },
+ { TIM4_TRGO, 3 },
+ { TIM5_TRGO, 4 },
+ { TIM6_TRGO, 5 },
+ { TIM7_TRGO, 6 },
+ { TIM8_TRGO, 7 },
+ {},
+};
+
+static irqreturn_t stm32_dac_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ int channel = indio_dev->channels[0].channel;
+
+ /* Using software trigger? Then, trigger it now */
+ if (dac->swtrig) {
+ u32 swtrig;
+
+ if (STM32_DAC_IS_CHAN_1(channel))
+ swtrig = STM32_DAC_SWTRIGR_SWTRIG1;
+ else
+ swtrig = STM32_DAC_SWTRIGR_SWTRIG2;
+ regmap_update_bits(dac->common->regmap, STM32_DAC_SWTRIGR,
+ swtrig, swtrig);
+ }
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static unsigned int stm32_dac_get_trig_tsel(struct stm32_dac *dac,
+ struct iio_trigger *trig)
+{
+ unsigned int i;
+
+ /* skip 1st trigger that should be swtrig */
+ for (i = 1; stm32h7_dac_trinfo[i].name; i++) {
+ /*
+ * Checking both stm32 timer trigger type and trig name
+ * should be safe against arbitrary trigger names.
+ */
+ if (is_stm32_timer_trigger(trig) &&
+ !strcmp(stm32h7_dac_trinfo[i].name, trig->name)) {
+ return stm32h7_dac_trinfo[i].tsel;
+ }
+ }
+
+ /* When no trigger has been found, default to software trigger */
+ dac->swtrig = true;
+
+ return stm32h7_dac_trinfo[0].tsel;
+}
+
+static int stm32_dac_set_trigger(struct iio_dev *indio_dev,
+ struct iio_trigger *trig)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ int channel = indio_dev->channels[0].channel;
+ u32 shift = STM32_DAC_IS_CHAN_1(channel) ? 0 : STM32_DAC_CHAN2_SHIFT;
+ u32 val = 0, tsel;
+ u32 msk = (STM32H7_DAC_CR_TEN1 | STM32H7_DAC_CR_TSEL1) << shift;
+
+ dac->swtrig = false;
+ if (trig) {
+ /* select & enable trigger (tsel / ten) */
+ tsel = stm32_dac_get_trig_tsel(dac, trig);
+ val = tsel << STM32H7_DAC_CR_TSEL1_SHIFT;
+ val = (val | STM32H7_DAC_CR_TEN1) << shift;
+ }
+
+ if (trig)
+ dev_dbg(&indio_dev->dev, "enable trigger: %s\n", trig->name);
+ else
+ dev_dbg(&indio_dev->dev, "disable trigger\n");
+
+ return regmap_update_bits(dac->common->regmap, STM32_DAC_CR, msk, val);
+}
+
static int stm32_dac_is_enabled(struct iio_dev *indio_dev, int channel)
{
struct stm32_dac *dac = iio_priv(indio_dev);
@@ -167,6 +269,7 @@ static int stm32_dac_debugfs_reg_access(struct iio_dev *indio_dev,
static const struct iio_info stm32_dac_iio_info = {
.read_raw = stm32_dac_read_raw,
.write_raw = stm32_dac_write_raw,
+ .set_trigger = stm32_dac_set_trigger,
.debugfs_reg_access = stm32_dac_debugfs_reg_access,
.driver_module = THIS_MODULE,
};
@@ -326,7 +429,28 @@ static int stm32_dac_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
- return devm_iio_device_register(&pdev->dev, indio_dev);
+ ret = iio_triggered_event_setup(indio_dev, NULL,
+ stm32_dac_trigger_handler);
+ if (ret)
+ return ret;
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ iio_triggered_event_cleanup(indio_dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stm32_dac_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+
+ iio_triggered_event_cleanup(indio_dev);
+ iio_device_unregister(indio_dev);
+
+ return 0;
}
static const struct of_device_id stm32_dac_of_match[] = {
@@ -337,6 +461,7 @@ static int stm32_dac_probe(struct platform_device *pdev)
static struct platform_driver stm32_dac_driver = {
.probe = stm32_dac_probe,
+ .remove = stm32_dac_remove,
.driver = {
.name = "stm32-dac",
.of_match_table = stm32_dac_of_match,
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 5/5] iio: dac: stm32: add support for waveform generator
From: Fabrice Gasnier @ 2017-04-06 16:11 UTC (permalink / raw)
To: jic23, linux, robh+dt, linux-arm-kernel, devicetree, linux-kernel
Cc: mark.rutland, benjamin.gaignard, lars, alexandre.torgue,
linux-iio, pmeerw, mcoquelin.stm32, knaack.h, fabrice.gasnier,
benjamin.gaignard
In-Reply-To: <1491495116-7209-1-git-send-email-fabrice.gasnier@st.com>
STM32 DAC has built-in noise or triangle waveform generator.
- "wavetype" extended attribute selects noise or triangle.
- "amplitude" extended attribute selects amplitude for waveform generator
A DC offset can be added to waveform generator output. This can be done
using out_voltage[1/2]_offset
Waveform generator requires a trigger to be configured, to increment /
decrement internal counter in case of triangle generator. Noise
generator is a bit different, but also requires a trigger to generate
samples.
Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
---
Changes in v2:
- use _offset parameter to add DC offset to waveform generator
- Rework ABI to better fit existing DDS ABI: use out_voltageY_wavetype,
out_voltage_wavetype_available, out_voltageY_amplitude,
out_voltage_amplitude_available
- Better explain trigger usage in case of waveform generator.
---
Documentation/ABI/testing/sysfs-bus-iio-dac-stm32 | 16 +++
drivers/iio/dac/stm32-dac-core.h | 4 +
drivers/iio/dac/stm32-dac.c | 158 +++++++++++++++++++++-
3 files changed, 177 insertions(+), 1 deletion(-)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-dac-stm32
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-dac-stm32 b/Documentation/ABI/testing/sysfs-bus-iio-dac-stm32
new file mode 100644
index 0000000..8f1fa009
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-dac-stm32
@@ -0,0 +1,16 @@
+What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_wavetype
+What: /sys/bus/iio/devices/iio:deviceX/out_voltage_wavetype_available
+KernelVersion: 4.12
+Contact: fabrice.gasnier@st.com
+Description:
+ List and/or select waveform generation provided by STM32 DAC:
+ - "flat": waveform generator disabled (default)
+ - "noise": select noise waveform
+ - "triangle": select triangle waveform
+
+What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_amplitude
+What: /sys/bus/iio/devices/iio:deviceX/out_voltage_amplitude_available
+KernelVersion: 4.12
+Contact: fabrice.gasnier@st.com
+Description:
+ List and/or select amplitude used for waveform generator
diff --git a/drivers/iio/dac/stm32-dac-core.h b/drivers/iio/dac/stm32-dac-core.h
index e51a468..0f02975 100644
--- a/drivers/iio/dac/stm32-dac-core.h
+++ b/drivers/iio/dac/stm32-dac-core.h
@@ -37,8 +37,12 @@
#define STM32H7_DAC_CR_TEN1 BIT(1)
#define STM32H7_DAC_CR_TSEL1_SHIFT 2
#define STM32H7_DAC_CR_TSEL1 GENMASK(5, 2)
+#define STM32_DAC_CR_WAVE1 GENMASK(7, 6)
+#define STM32_DAC_CR_MAMP1 GENMASK(11, 8)
#define STM32H7_DAC_CR_HFSEL BIT(15)
#define STM32_DAC_CR_EN2 BIT(16)
+#define STM32_DAC_CR_WAVE2 GENMASK(23, 22)
+#define STM32_DAC_CR_MAMP2 GENMASK(27, 24)
/* STM32_DAC_SWTRIGR bit fields */
#define STM32_DAC_SWTRIGR_SWTRIG1 BIT(0)
diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c
index a7a078e..2ed75db 100644
--- a/drivers/iio/dac/stm32-dac.c
+++ b/drivers/iio/dac/stm32-dac.c
@@ -42,10 +42,12 @@
/**
* struct stm32_dac - private data of DAC driver
* @common: reference to DAC common data
+ * @wavetype: waveform generator
* @swtrig: Using software trigger
*/
struct stm32_dac {
struct stm32_dac_common *common;
+ u32 wavetype;
bool swtrig;
};
@@ -222,6 +224,29 @@ static int stm32_dac_set_value(struct stm32_dac *dac, int channel, int val)
return ret;
}
+static int stm32_dac_get_offset(struct stm32_dac *dac, int channel, int *val)
+{
+ int ret;
+
+ /* Offset is only relevant in waveform generation mode. */
+ if (!dac->wavetype) {
+ *val = 0;
+ return IIO_VAL_INT;
+ }
+
+ /*
+ * In waveform generation mode, DC offset in DHR is added to waveform
+ * generator output, then stored to DOR (data output register).
+ * Read offset from DHR.
+ */
+ if (STM32_DAC_IS_CHAN_1(channel))
+ ret = regmap_read(dac->common->regmap, STM32_DAC_DHR12R1, val);
+ else
+ ret = regmap_read(dac->common->regmap, STM32_DAC_DHR12R2, val);
+
+ return ret ? ret : IIO_VAL_INT;
+}
+
static int stm32_dac_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -231,6 +256,8 @@ static int stm32_dac_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
return stm32_dac_get_value(dac, chan->channel, val);
+ case IIO_CHAN_INFO_OFFSET:
+ return stm32_dac_get_offset(dac, chan->channel, val);
case IIO_CHAN_INFO_SCALE:
*val = dac->common->vref_mv;
*val2 = chan->scan_type.realbits;
@@ -247,8 +274,16 @@ static int stm32_dac_write_raw(struct iio_dev *indio_dev,
struct stm32_dac *dac = iio_priv(indio_dev);
switch (mask) {
+ case IIO_CHAN_INFO_OFFSET:
+ /* Offset only makes sense in waveform generation mode */
+ if (dac->wavetype)
+ return stm32_dac_set_value(dac, chan->channel, val);
+ return -EBUSY;
case IIO_CHAN_INFO_RAW:
- return stm32_dac_set_value(dac, chan->channel, val);
+ if (!dac->wavetype)
+ return stm32_dac_set_value(dac, chan->channel, val);
+ /* raw value is read only in waveform generation mode */
+ return -EBUSY;
default:
return -EINVAL;
}
@@ -334,6 +369,122 @@ static ssize_t stm32_dac_write_powerdown(struct iio_dev *indio_dev,
.set = stm32_dac_set_powerdown_mode,
};
+/* waveform generator wave selection */
+static const char * const stm32_dac_wavetype_desc[] = {
+ "flat",
+ "noise",
+ "triangle",
+};
+
+static int stm32_dac_set_wavetype(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int wavetype)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ u32 mask, val;
+ int ret;
+
+ /*
+ * Waveform generator requires a trigger to be configured, to increment
+ * or decrement internal counter in case of triangle generator. Noise
+ * generator is a bit different, but also requires a trigger to
+ * generate samples.
+ */
+ if (wavetype && !indio_dev->trig)
+ dev_dbg(&indio_dev->dev, "Wavegen requires a trigger\n");
+
+ if (STM32_DAC_IS_CHAN_1(chan->channel)) {
+ val = FIELD_PREP(STM32_DAC_CR_WAVE1, wavetype);
+ mask = STM32_DAC_CR_WAVE1;
+ } else {
+ val = FIELD_PREP(STM32_DAC_CR_WAVE2, wavetype);
+ mask = STM32_DAC_CR_WAVE2;
+ }
+
+ ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, mask, val);
+ if (ret)
+ return ret;
+ dac->wavetype = wavetype;
+
+ return 0;
+}
+
+static int stm32_dac_get_wavetype(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ u32 val;
+ int ret;
+
+ ret = regmap_read(dac->common->regmap, STM32_DAC_CR, &val);
+ if (ret < 0)
+ return ret;
+
+ if (STM32_DAC_IS_CHAN_1(chan->channel))
+ return FIELD_GET(STM32_DAC_CR_WAVE1, val);
+ else
+ return FIELD_GET(STM32_DAC_CR_WAVE2, val);
+}
+
+static const struct iio_enum stm32_dac_wavetype_enum = {
+ .items = stm32_dac_wavetype_desc,
+ .num_items = ARRAY_SIZE(stm32_dac_wavetype_desc),
+ .get = stm32_dac_get_wavetype,
+ .set = stm32_dac_set_wavetype,
+};
+
+/*
+ * waveform generator mamp selection: mask/amplitude
+ * - noise: LFSR mask (linear feedback shift register, umasks bit 0, [1:0]...)
+ * - triangle: amplitude (equal to 1, 3, 5, 7... 4095)
+ */
+static const char * const stm32_dac_amplitude_desc[] = {
+ "1", "3", "7", "15", "31", "63", "127", "255", "511", "1023", "2047",
+ "4095",
+};
+
+static int stm32_dac_set_amplitude(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int amplitude)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ u32 mask, val;
+
+ if (STM32_DAC_IS_CHAN_1(chan->channel)) {
+ val = FIELD_PREP(STM32_DAC_CR_MAMP1, amplitude);
+ mask = STM32_DAC_CR_MAMP1;
+ } else {
+ val = FIELD_PREP(STM32_DAC_CR_MAMP2, amplitude);
+ mask = STM32_DAC_CR_MAMP2;
+ }
+
+ return regmap_update_bits(dac->common->regmap, STM32_DAC_CR, mask, val);
+}
+
+static int stm32_dac_get_amplitude(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct stm32_dac *dac = iio_priv(indio_dev);
+ u32 val;
+ int ret;
+
+ ret = regmap_read(dac->common->regmap, STM32_DAC_CR, &val);
+ if (ret < 0)
+ return ret;
+
+ if (STM32_DAC_IS_CHAN_1(chan->channel))
+ return FIELD_GET(STM32_DAC_CR_MAMP1, val);
+ else
+ return FIELD_GET(STM32_DAC_CR_MAMP2, val);
+}
+
+static const struct iio_enum stm32_dac_amplitude_enum = {
+ .items = stm32_dac_amplitude_desc,
+ .num_items = ARRAY_SIZE(stm32_dac_amplitude_desc),
+ .get = stm32_dac_get_amplitude,
+ .set = stm32_dac_set_amplitude,
+};
+
static const struct iio_chan_spec_ext_info stm32_dac_ext_info[] = {
{
.name = "powerdown",
@@ -343,6 +494,10 @@ static ssize_t stm32_dac_write_powerdown(struct iio_dev *indio_dev,
},
IIO_ENUM("powerdown_mode", IIO_SEPARATE, &stm32_dac_powerdown_mode_en),
IIO_ENUM_AVAILABLE("powerdown_mode", &stm32_dac_powerdown_mode_en),
+ IIO_ENUM("wavetype", IIO_SEPARATE, &stm32_dac_wavetype_enum),
+ IIO_ENUM_AVAILABLE("wavetype", &stm32_dac_wavetype_enum),
+ IIO_ENUM("amplitude", IIO_SEPARATE, &stm32_dac_amplitude_enum),
+ IIO_ENUM_AVAILABLE("amplitude", &stm32_dac_amplitude_enum),
{},
};
@@ -352,6 +507,7 @@ static ssize_t stm32_dac_write_powerdown(struct iio_dev *indio_dev,
.output = 1, \
.channel = chan, \
.info_mask_separate = \
+ BIT(IIO_CHAN_INFO_OFFSET) | \
BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_SCALE), \
/* scan_index is always 0 as num_channels is 1 */ \
--
1.9.1
^ permalink raw reply related
* Re: [PATCH V2 2/4] pwm: tegra: Increase precision in pwm rate calculation
From: Thierry Reding @ 2017-04-06 16:24 UTC (permalink / raw)
To: Laxman Dewangan
Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, jonathanh-DDmLM1+adcrQT0dZR+AlfA,
mark.rutland-5wv7dgnIgG8, linux-pwm-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-tegra-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1491488461-24621-3-git-send-email-ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 2584 bytes --]
On Thu, Apr 06, 2017 at 07:50:59PM +0530, Laxman Dewangan wrote:
> The rate of the PWM calculated as follows:
> hz = NSEC_PER_SEC / period_ns;
> rate = (rate + (hz / 2)) / hz;
>
> This has the precision loss in lower PWM rate.
> Changing this to have more precision as:
> hz = DIV_ROUND_CLOSE(NSEC_PER_SEC * 100, period_ns);
> rate = DIV_ROUND_CLOSE(rate * 100, hz)
DIV_ROUND_CLOSEST(). And I much prefer this to the actual code below. I
don't think it's necessary to have a local variable for the precision.
Thierry
> Example:
> 1. period_ns = 16672000, PWM clock rate is 200KHz.
> Based on old formula
> hz = NSEC_PER_SEC / period_ns
> = 1000000000ul/16672000
> = 59 (59.98)
> rate = (200K + 59/2)/59 = 3390
>
> Based on new method:
> hz = 5998
> rate = DIV_ROUND_CLOSE(200000*100, 5998) = 3334
>
> If we measure the PWM signal rate, we will get more accurate period
> with rate value of 3334 instead of 3390.
>
> 2. period_ns = 16803898, PWM clock rate is 200KHz.
> Based on old formula:
> hz = 60, rate = 3333
> Based on new formula:
> hz = 5951, rate = 3360
>
> The rate of 3360 is more near to requested period then the 3333.
>
> Signed-off-by: Laxman Dewangan <ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> ---
> Changes from V1:
> - None
>
> drivers/pwm/pwm-tegra.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
> index 0a688da..e9c4de5 100644
> --- a/drivers/pwm/pwm-tegra.c
> +++ b/drivers/pwm/pwm-tegra.c
> @@ -76,6 +76,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
> unsigned long long c = duty_ns;
> unsigned long rate, hz;
> + unsigned long long ns100 = NSEC_PER_SEC;
> + unsigned long precision = 100; /* Consider 2 digit precision */
> u32 val = 0;
> int err;
>
> @@ -94,9 +96,11 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> * cycles at the PWM clock rate will take period_ns nanoseconds.
> */
> rate = clk_get_rate(pc->clk) >> PWM_DUTY_WIDTH;
> - hz = NSEC_PER_SEC / period_ns;
>
> - rate = (rate + (hz / 2)) / hz;
> + /* Consider precision in PWM_SCALE_WIDTH rate calculation */
> + ns100 *= precision;
> + hz = DIV_ROUND_CLOSEST_ULL(ns100, period_ns);
> + rate = DIV_ROUND_CLOSEST(rate * precision, hz);
>
> /*
> * Since the actual PWM divider is the register's frequency divider
> --
> 2.1.4
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH V2 2/4] pwm: tegra: Increase precision in pwm rate calculation
From: Thierry Reding @ 2017-04-06 16:28 UTC (permalink / raw)
To: Laxman Dewangan
Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, jonathanh-DDmLM1+adcrQT0dZR+AlfA,
mark.rutland-5wv7dgnIgG8, linux-pwm-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-tegra-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1491488461-24621-3-git-send-email-ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 2591 bytes --]
On Thu, Apr 06, 2017 at 07:50:59PM +0530, Laxman Dewangan wrote:
> The rate of the PWM calculated as follows:
> hz = NSEC_PER_SEC / period_ns;
> rate = (rate + (hz / 2)) / hz;
>
> This has the precision loss in lower PWM rate.
> Changing this to have more precision as:
> hz = DIV_ROUND_CLOSE(NSEC_PER_SEC * 100, period_ns);
> rate = DIV_ROUND_CLOSE(rate * 100, hz)
DIV_ROUND_CLOSEST(). Also I very much prefer this notation over the
actual code below. I don't think we need a local variable to hold the
precision.
Thierry
> Example:
> 1. period_ns = 16672000, PWM clock rate is 200KHz.
> Based on old formula
> hz = NSEC_PER_SEC / period_ns
> = 1000000000ul/16672000
> = 59 (59.98)
> rate = (200K + 59/2)/59 = 3390
>
> Based on new method:
> hz = 5998
> rate = DIV_ROUND_CLOSE(200000*100, 5998) = 3334
>
> If we measure the PWM signal rate, we will get more accurate period
> with rate value of 3334 instead of 3390.
>
> 2. period_ns = 16803898, PWM clock rate is 200KHz.
> Based on old formula:
> hz = 60, rate = 3333
> Based on new formula:
> hz = 5951, rate = 3360
>
> The rate of 3360 is more near to requested period then the 3333.
>
> Signed-off-by: Laxman Dewangan <ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> ---
> Changes from V1:
> - None
>
> drivers/pwm/pwm-tegra.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
> index 0a688da..e9c4de5 100644
> --- a/drivers/pwm/pwm-tegra.c
> +++ b/drivers/pwm/pwm-tegra.c
> @@ -76,6 +76,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
> unsigned long long c = duty_ns;
> unsigned long rate, hz;
> + unsigned long long ns100 = NSEC_PER_SEC;
> + unsigned long precision = 100; /* Consider 2 digit precision */
> u32 val = 0;
> int err;
>
> @@ -94,9 +96,11 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> * cycles at the PWM clock rate will take period_ns nanoseconds.
> */
> rate = clk_get_rate(pc->clk) >> PWM_DUTY_WIDTH;
> - hz = NSEC_PER_SEC / period_ns;
>
> - rate = (rate + (hz / 2)) / hz;
> + /* Consider precision in PWM_SCALE_WIDTH rate calculation */
> + ns100 *= precision;
> + hz = DIV_ROUND_CLOSEST_ULL(ns100, period_ns);
> + rate = DIV_ROUND_CLOSEST(rate * precision, hz);
>
> /*
> * Since the actual PWM divider is the register's frequency divider
> --
> 2.1.4
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH V2 1/4] pwm: tegra: Use DIV_ROUND_CLOSEST_ULL() instead of local implementation
From: Thierry Reding @ 2017-04-06 16:28 UTC (permalink / raw)
To: Laxman Dewangan
Cc: robh+dt, jonathanh, mark.rutland, linux-pwm, devicetree,
linux-tegra, linux-kernel
In-Reply-To: <1491488461-24621-2-git-send-email-ldewangan@nvidia.com>
[-- Attachment #1: Type: text/plain, Size: 934 bytes --]
On Thu, Apr 06, 2017 at 07:50:58PM +0530, Laxman Dewangan wrote:
> Use macro DIV_ROUND_CLOSEST_ULL() for 64bit division to closet one
"closest"
Thierry
> instead of implementing the same locally. This increase readability.
>
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> ---
> Changes from V1:
> None
>
> drivers/pwm/pwm-tegra.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
> index e464784..0a688da 100644
> --- a/drivers/pwm/pwm-tegra.c
> +++ b/drivers/pwm/pwm-tegra.c
> @@ -85,8 +85,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> * nearest integer during division.
> */
> c *= (1 << PWM_DUTY_WIDTH);
> - c += period_ns / 2;
> - do_div(c, period_ns);
> + c = DIV_ROUND_CLOSEST_ULL(c, period_ns);
>
> val = (u32)c << PWM_DUTY_SHIFT;
>
> --
> 2.1.4
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH V4 4/4] pwm: tegra: Add support to configure pin state in suspends/resume
From: Laxman Dewangan @ 2017-04-06 16:40 UTC (permalink / raw)
To: Jon Hunter, thierry.reding, robh+dt
Cc: mark.rutland, linux-pwm, devicetree, linux-tegra, linux-kernel
In-Reply-To: <e6eef15a-45e4-002b-dd45-38c268070241@nvidia.com>
Oops, it was actually v2.
On Thursday 06 April 2017 08:47 PM, Jon Hunter wrote:
> On 06/04/17 15:21, Laxman Dewangan wrote:
>> In some of NVIDIA Tegra's platform, PWM controller is used to
>> control the PWM controlled regulators. PWM signal is connected to
>> the VID pin of the regulator where duty cycle of PWM signal decide
>> the voltage level of the regulator output.
>>
>> The tristate (high impedance of PWM pin form Tegra) also define
>> one of the state of PWM regulator which needs to be configure in
>> suspend state of system.
>>
>> Add support to configure the pin state via pinctrl frameworks in
>> suspend and active state of the system.
>>
>> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
>> ---
>> Changes from v1:
>> - Use standard pinctrl names for sleep and active state.
>> - Use API pinctrl_pm_select_*()
>>
>> drivers/pwm/pwm-tegra.c | 22 ++++++++++++++++++++++
>> 1 file changed, 22 insertions(+)
>>
>> diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
>> index e9c4de5..af1bd4f 100644
>> --- a/drivers/pwm/pwm-tegra.c
>> +++ b/drivers/pwm/pwm-tegra.c
>> @@ -29,6 +29,7 @@
>> #include <linux/of_device.h>
>> #include <linux/pwm.h>
>> #include <linux/platform_device.h>
>> +#include <linux/pinctrl/consumer.h>
>> #include <linux/slab.h>
>> #include <linux/reset.h>
>>
>> @@ -256,6 +257,22 @@ static int tegra_pwm_remove(struct platform_device *pdev)
>> return pwmchip_remove(&pc->chip);
>> }
>>
>> +#ifdef CONFIG_PM_SLEEP
>> +static int tegra_pwm_suspend(struct device *dev)
>> +{
>> + pinctrl_pm_select_sleep_state(dev);
> Why not return the error code here?
As the pin state in suspend is optional, I dont want to return error if
the sleep state is not available.
However, it seems pinctrl take care of retuning success if there is no
sleep state. By seeing code.
Let me test this on different condition and it it works fine then we can
return the return of pinctrl_pm_select_*()
BTW, it should be OK to have pwm_tegra_resume/suspend wrapper, not
directly use the pinctrl_pm_select_* in pm ops suspend/resume. The
prototype matches.
>
> By the way, do you plan to include patches to populate the bindings for
> the pwm devices?
I am planning to populate the GPU regulator which is PWM based. This
will only populate the regulator.
^ permalink raw reply
* Re: [PATCH V3 3/4] pwm: tegra: Add DT binding details to configure pin in suspends/resume
From: Laxman Dewangan @ 2017-04-06 16:48 UTC (permalink / raw)
To: Jon Hunter, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A
Cc: mark.rutland-5wv7dgnIgG8, linux-pwm-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-tegra-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <f43c83a9-8ae0-73b0-d41d-97d3bc6c253e-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
On Thursday 06 April 2017 08:56 PM, Jon Hunter wrote:
> On 06/04/17 15:21, Laxman Dewangan wrote:
>> In some of NVIDIA Tegra's platform, PWM controller is used to
>> control the PWM controlled regulators. PWM signal is connected to
>> the VID pin of the regulator where duty cycle of PWM signal decide
>> the voltage level of the regulator output.
>>
>> The tristate (high impedance of PWM pin form Tegra) also define
> s/form/from/
> s/define/defines/
>
>> one of the state of PWM regulator which needs to be configure in
>> suspend state of system.
> It maybe clearer to say that when the system enters suspend the
> regulator requires the pwm output to be tristated.
Not necessarily that every PWM regulator interfaces needs it. It
depends on the devices.
So I will say:
When system enters suspend, in some of PWM regulator interface, it is
required to to set the PWM output to be tristated.
> pwm: pwm@7000a000 {
> @@ -29,3 +42,33 @@ Example:
> resets = <&tegra_car 17>;
> reset-names = "pwm";
> };
> +
> +
> +Example with the pin configuration for suspend and resume:
> +=========================================================
> +Pin PE7 is used as PWM interface.
> Nit-pick. On what devices? Sounds like this is verbatim. Maybe state
> what device this is an example for.
Let me phrase it as:
Suppose pin PE7 (On tegra210) interfaced with the regulator device and
this requires PWM output to be tristated when system enters suspend.
Following will be DT binding to achieve this:
^ permalink raw reply
* Re: [PATCH 1/1] gpio: core: Decouple open drain/source flag with active low/high
From: Laxman Dewangan @ 2017-04-06 16:56 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
Frank Rowand, linux-gpio@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree
In-Reply-To: <CAHp75Ver7n=Xoer0YzA0sVVrcXKOB1bDz0ygSm0V0YoyYbe3pg@mail.gmail.com>
On Thursday 06 April 2017 09:40 PM, Andy Shevchenko wrote:
> On Thu, Apr 6, 2017 at 4:35 PM, Laxman Dewangan <ldewangan@nvidia.com> wrote:
>> Currently, the GPIO interface is said to Open Drain if it is Single
>> Ended and active LOW. Similarly, it is said as Open Source if it is
>> Single Ended and active HIGH.
>>
>> The active HIGH/LOW is used in the interface for setting the pin
>> state to HIGH or LOW when enabling/disabling the interface.
>>
>> In Open Drain interface, pin is set to HIGH by putting pin in
>> high impedance and LOW by driving to the LOW.
>>
>> In Open Source interface, pin is set to HIGH by driving pin to
>> HIGH and set to LOW by putting pin in high impedance.
>>
>> With above, the Open Drain/Source is unrelated to the active LOW/HIGH
>> in interface. There is interface where the enable/disable of interface
>> is ether active LOW or HIGH but it is Open Drain type.
>>
>> Hence decouple the Open Drain with Single Ended + Active LOW and
>> Open Source with Single Ended + Active HIGH.
>>
>> Adding different flag for the Open Drain/Open Source which is valid
>> only when Single ended flag is enabled.
>> if (single_ended) {
>> - if (active_low)
>> + if (open_drain)
> This breaks ACPI case, right?
>
In acpi case, single_ended is not handled. It only handles the active LOW.
From code:
bool active_low = false;
bool single_ended = false;
int ret;
if (!fwnode)
return ERR_PTR(-EINVAL);
if (is_of_node(fwnode)) {
enum of_gpio_flags flags;
desc = of_get_named_gpiod_flags(to_of_node(fwnode),
propname,
index, &flags);
if (!IS_ERR(desc)) {
active_low = flags & OF_GPIO_ACTIVE_LOW;
single_ended = flags & OF_GPIO_SINGLE_ENDED;
}
} else if (is_acpi_node(fwnode)) {
struct acpi_gpio_info info;
desc = acpi_node_get_gpiod(fwnode, propname, index, &info);
if (!IS_ERR(desc))
active_low = info.polarity == GPIO_ACTIVE_LOW;
}
^ permalink raw reply
* [PATCH] ARM: dts: aspeed: add a fastread property
From: Cédric Le Goater @ 2017-04-06 17:02 UTC (permalink / raw)
To: Joel Stanley
Cc: Mark Rutland, devicetree, Russell King, Rob Herring,
Cédric Le Goater, linux-arm-kernel
All chips on OpenPOWER platforms support the fastread SPI command.
Signed-off-by: Cédric Le Goater <clg@kaod.org>
---
arch/arm/boot/dts/aspeed-ast2500-evb.dts | 2 ++
arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts | 2 ++
arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts | 2 ++
3 files changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/aspeed-ast2500-evb.dts b/arch/arm/boot/dts/aspeed-ast2500-evb.dts
index 8f82e8ab1875..7c90dac99822 100644
--- a/arch/arm/boot/dts/aspeed-ast2500-evb.dts
+++ b/arch/arm/boot/dts/aspeed-ast2500-evb.dts
@@ -24,6 +24,7 @@
status = "okay";
flash@0 {
status = "okay";
+ m25p,fast-read;
label = "bmc";
};
};
@@ -32,6 +33,7 @@
status = "okay";
flash@0 {
status = "okay";
+ m25p,fast-read;
label = "pnor";
};
};
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
index aab1889f702f..112551766275 100644
--- a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
@@ -35,6 +35,7 @@
status = "okay";
flash@0 {
status = "okay";
+ m25p,fast-read;
label = "bmc";
};
};
@@ -43,6 +44,7 @@
status = "okay";
flash@0 {
status = "okay";
+ m25p,fast-read;
label = "pnor";
};
};
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts b/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts
index e3c6358bc7d9..b82f67eb3dae 100644
--- a/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts
@@ -35,6 +35,7 @@
status = "okay";
flash@0 {
status = "okay";
+ m25p,fast-read;
label = "bmc";
};
};
@@ -43,6 +44,7 @@
status = "okay";
flash@0 {
status = "okay";
+ m25p,fast-read;
label = "pnor";
};
};
--
2.7.4
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* Re: [PATCH V2 2/4] pwm: tegra: Increase precision in pwm rate calculation
From: Laxman Dewangan @ 2017-04-06 17:03 UTC (permalink / raw)
To: Thierry Reding
Cc: robh+dt, jonathanh, mark.rutland, linux-pwm, devicetree,
linux-tegra, linux-kernel
In-Reply-To: <20170406162417.GB19312@ulmo.ba.sec>
[-- Attachment #1: Type: text/plain, Size: 783 bytes --]
On Thursday 06 April 2017 09:54 PM, Thierry Reding wrote:
> * PGP Signed by an unknown key
>
> On Thu, Apr 06, 2017 at 07:50:59PM +0530, Laxman Dewangan wrote:
>> The rate of the PWM calculated as follows:
>> hz = NSEC_PER_SEC / period_ns;
>> rate = (rate + (hz / 2)) / hz;
>>
>> This has the precision loss in lower PWM rate.
>> Changing this to have more precision as:
>> hz = DIV_ROUND_CLOSE(NSEC_PER_SEC * 100, period_ns);
>> rate = DIV_ROUND_CLOSE(rate * 100, hz)
> DIV_ROUND_CLOSEST(). And I much prefer this to the actual code below. I
> don't think it's necessary to have a local variable for the precision.
Do you suggest to use DIV_ROUND_CLOSEST() instead of
DIV_ROUND_CLOSEST_ULL()?
This is ULL division and so we need to use this one.
Or am I missing something
[-- Attachment #2: Type: text/html, Size: 1288 bytes --]
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox