1269 lines
38 KiB
Diff
1269 lines
38 KiB
Diff
From 69629ada3651978075924c825648de57ad2dab11 Mon Sep 17 00:00:00 2001
|
|
From: Lionel VITTE <lionel.vitte@st.com>
|
|
Date: Mon, 5 Oct 2020 13:19:51 +0200
|
|
Subject: [PATCH 18/22] ARM-stm32mp1-r2-rc8-SOUND
|
|
|
|
---
|
|
.../bindings/sound/st,stm32-i2s.txt | 62 ---
|
|
.../bindings/sound/st,stm32-i2s.yaml | 91 +++++
|
|
sound/soc/codecs/Kconfig | 2 +-
|
|
sound/soc/codecs/cs42l51.c | 17 +-
|
|
sound/soc/codecs/wm8994.c | 80 +++-
|
|
sound/soc/stm/stm32_i2s.c | 385 +++++++++++++++---
|
|
sound/soc/stm/stm32_sai.c | 26 +-
|
|
sound/soc/stm/stm32_sai_sub.c | 21 +-
|
|
sound/soc/stm/stm32_spdifrx.c | 105 +++--
|
|
9 files changed, 599 insertions(+), 190 deletions(-)
|
|
delete mode 100644 Documentation/devicetree/bindings/sound/st,stm32-i2s.txt
|
|
create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-i2s.yaml
|
|
|
|
diff --git a/Documentation/devicetree/bindings/sound/st,stm32-i2s.txt b/Documentation/devicetree/bindings/sound/st,stm32-i2s.txt
|
|
deleted file mode 100644
|
|
index cbf24bcd1b8d3..0000000000000
|
|
--- a/Documentation/devicetree/bindings/sound/st,stm32-i2s.txt
|
|
+++ /dev/null
|
|
@@ -1,62 +0,0 @@
|
|
-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"
|
|
- - 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/st,stm32-pinctrl.yaml
|
|
-
|
|
-Optional properties:
|
|
- - resets: Reference to a reset controller asserting the reset controller
|
|
-
|
|
-The device node should contain one 'port' child node with one child 'endpoint'
|
|
-node, according to the bindings defined in Documentation/devicetree/bindings/
|
|
-graph.txt.
|
|
-
|
|
-Example:
|
|
-sound_card {
|
|
- compatible = "audio-graph-card";
|
|
- dais = <&i2s2_port>;
|
|
-};
|
|
-
|
|
-i2s2: audio-controller@40003800 {
|
|
- compatible = "st,stm32h7-i2s";
|
|
- 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>;
|
|
-
|
|
- i2s2_port: port@0 {
|
|
- cpu_endpoint: endpoint {
|
|
- remote-endpoint = <&codec_endpoint>;
|
|
- format = "i2s";
|
|
- };
|
|
- };
|
|
-};
|
|
-
|
|
-audio-codec {
|
|
- codec_port: port@0 {
|
|
- codec_endpoint: endpoint {
|
|
- remote-endpoint = <&cpu_endpoint>;
|
|
- };
|
|
- };
|
|
-};
|
|
diff --git a/Documentation/devicetree/bindings/sound/st,stm32-i2s.yaml b/Documentation/devicetree/bindings/sound/st,stm32-i2s.yaml
|
|
new file mode 100644
|
|
index 0000000000000..6feb5a09c184e
|
|
--- /dev/null
|
|
+++ b/Documentation/devicetree/bindings/sound/st,stm32-i2s.yaml
|
|
@@ -0,0 +1,91 @@
|
|
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
|
+%YAML 1.2
|
|
+---
|
|
+$id: http://devicetree.org/schemas/sound/st,stm32-i2s.yaml#
|
|
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
|
+
|
|
+title: STMicroelectronics STM32 SPI/I2S Controller
|
|
+
|
|
+maintainers:
|
|
+ - Olivier Moysan <olivier.moysan@st.com>
|
|
+
|
|
+description:
|
|
+ The SPI/I2S block supports I2S/PCM protocols when configured on I2S mode.
|
|
+ Only some SPI instances support I2S.
|
|
+
|
|
+properties:
|
|
+ compatible:
|
|
+ enum:
|
|
+ - st,stm32h7-i2s
|
|
+
|
|
+ "#sound-dai-cells":
|
|
+ const: 0
|
|
+
|
|
+ reg:
|
|
+ maxItems: 1
|
|
+
|
|
+ clocks:
|
|
+ items:
|
|
+ - description: clock feeding the peripheral bus interface.
|
|
+ - description: clock feeding the internal clock generator.
|
|
+ - description: I2S parent clock for sampling rates multiple of 8kHz.
|
|
+ - description: I2S parent clock for sampling rates multiple of 11.025kHz.
|
|
+
|
|
+ clock-names:
|
|
+ items:
|
|
+ - const: pclk
|
|
+ - const: i2sclk
|
|
+ - const: x8k
|
|
+ - const: x11k
|
|
+
|
|
+ interrupts:
|
|
+ maxItems: 1
|
|
+
|
|
+ dmas:
|
|
+ items:
|
|
+ - description: audio capture DMA.
|
|
+ - description: audio playback DMA.
|
|
+
|
|
+ dma-names:
|
|
+ items:
|
|
+ - const: rx
|
|
+ - const: tx
|
|
+
|
|
+ resets:
|
|
+ maxItems: 1
|
|
+
|
|
+ "#clock-cells":
|
|
+ description: Configure the I2S device as MCLK clock provider.
|
|
+ const: 0
|
|
+
|
|
+required:
|
|
+ - compatible
|
|
+ - "#sound-dai-cells"
|
|
+ - reg
|
|
+ - clocks
|
|
+ - clock-names
|
|
+ - interrupts
|
|
+ - dmas
|
|
+ - dma-names
|
|
+
|
|
+additionalProperties: false
|
|
+
|
|
+examples:
|
|
+ - |
|
|
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
|
|
+ #include <dt-bindings/clock/stm32mp1-clks.h>
|
|
+ i2s2: audio-controller@4000b000 {
|
|
+ compatible = "st,stm32h7-i2s";
|
|
+ #sound-dai-cells = <0>;
|
|
+ reg = <0x4000b000 0x400>;
|
|
+ clocks = <&rcc SPI2>, <&rcc SPI2_K>, <&rcc PLL3_Q>, <&rcc PLL3_R>;
|
|
+ clock-names = "pclk", "i2sclk", "x8k", "x11k";
|
|
+ interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ dmas = <&dmamux1 39 0x400 0x01>,
|
|
+ <&dmamux1 40 0x400 0x01>;
|
|
+ dma-names = "rx", "tx";
|
|
+ pinctrl-names = "default";
|
|
+ pinctrl-0 = <&i2s2_pins_a>;
|
|
+ };
|
|
+
|
|
+...
|
|
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
|
|
index 229cc89f8c5a5..e5d231a6331b4 100644
|
|
--- a/sound/soc/codecs/Kconfig
|
|
+++ b/sound/soc/codecs/Kconfig
|
|
@@ -1395,7 +1395,7 @@ config SND_SOC_WM8993
|
|
tristate
|
|
|
|
config SND_SOC_WM8994
|
|
- tristate
|
|
+ tristate "Wolfson Microelectronics WM8994 codec"
|
|
|
|
config SND_SOC_WM8995
|
|
tristate
|
|
diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c
|
|
index 55408c8fcb4e3..01a4d93e11dd8 100644
|
|
--- a/sound/soc/codecs/cs42l51.c
|
|
+++ b/sound/soc/codecs/cs42l51.c
|
|
@@ -174,6 +174,7 @@ static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w,
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
snd_soc_component_update_bits(component, CS42L51_POWER_CTL1,
|
|
CS42L51_POWER_CTL1_PDN, 0);
|
|
+ msleep(20);
|
|
break;
|
|
}
|
|
|
|
@@ -214,12 +215,10 @@ static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
|
|
SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture",
|
|
CS42L51_POWER_CTL1, 2, 1,
|
|
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
|
- SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback",
|
|
- CS42L51_POWER_CTL1, 5, 1,
|
|
- cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
|
- SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback",
|
|
- CS42L51_POWER_CTL1, 6, 1,
|
|
- cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
|
+ SND_SOC_DAPM_DAC_E("Left DAC", NULL, CS42L51_POWER_CTL1, 5, 1,
|
|
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
|
+ SND_SOC_DAPM_DAC_E("Right DAC", NULL, CS42L51_POWER_CTL1, 6, 1,
|
|
+ cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
|
|
|
/* analog/mic */
|
|
SND_SOC_DAPM_INPUT("AIN1L"),
|
|
@@ -255,6 +254,12 @@ static const struct snd_soc_dapm_route cs42l51_routes[] = {
|
|
{"HPL", NULL, "Left DAC"},
|
|
{"HPR", NULL, "Right DAC"},
|
|
|
|
+ {"Right DAC", NULL, "DAC Mux"},
|
|
+ {"Left DAC", NULL, "DAC Mux"},
|
|
+
|
|
+ {"DAC Mux", "Direct PCM", "Playback"},
|
|
+ {"DAC Mux", "DSP PCM", "Playback"},
|
|
+
|
|
{"Left ADC", NULL, "Left PGA"},
|
|
{"Right ADC", NULL, "Right PGA"},
|
|
|
|
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
|
|
index d5fb7f5dd551c..a166ab1f34e1e 100644
|
|
--- a/sound/soc/codecs/wm8994.c
|
|
+++ b/sound/soc/codecs/wm8994.c
|
|
@@ -7,6 +7,7 @@
|
|
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
*/
|
|
|
|
+#include <linux/clk.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/init.h>
|
|
@@ -840,6 +841,42 @@ static int clk_sys_event(struct snd_soc_dapm_widget *w,
|
|
return 0;
|
|
}
|
|
|
|
+static int mclk_event(struct snd_soc_dapm_widget *w,
|
|
+ struct snd_kcontrol *kcontrol, int event)
|
|
+{
|
|
+ struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm);
|
|
+ struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(comp);
|
|
+ struct wm8994 *control = wm8994->wm8994;
|
|
+ struct wm8994_pdata *pdata = &control->pdata;
|
|
+ struct clk *mclk = pdata->mclk1;
|
|
+ int ret, mclk_id = 0;
|
|
+
|
|
+ if (!strncmp(w->name, "MCLK2", 5)) {
|
|
+ mclk_id = 1;
|
|
+ mclk = pdata->mclk2;
|
|
+ }
|
|
+
|
|
+ switch (event) {
|
|
+ case SND_SOC_DAPM_PRE_PMU:
|
|
+ dev_dbg(comp->dev, "Enable master clock %s\n",
|
|
+ mclk_id ? "MCLK2" : "MCLK1");
|
|
+
|
|
+ ret = clk_prepare_enable(mclk);
|
|
+ if (ret < 0) {
|
|
+ dev_err(comp->dev, "Failed to enable clock: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ break;
|
|
+ case SND_SOC_DAPM_POST_PMD:
|
|
+ dev_dbg(comp->dev, "Disable master clock %s\n",
|
|
+ mclk_id ? "MCLK2" : "MCLK1");
|
|
+ clk_disable_unprepare(mclk);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static void vmid_reference(struct snd_soc_component *component)
|
|
{
|
|
struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component);
|
|
@@ -1157,7 +1194,6 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w,
|
|
else
|
|
adc = WM8994_AIF2ADCL_ENA | WM8994_AIF2ADCR_ENA;
|
|
|
|
-
|
|
val = snd_soc_component_read32(component, WM8994_AIF2_CONTROL_2);
|
|
if ((val & WM8994_AIF2DACL_SRC) &&
|
|
(val & WM8994_AIF2DACR_SRC))
|
|
@@ -1776,6 +1812,16 @@ static const struct snd_soc_dapm_widget wm8994_specific_dapm_widgets[] = {
|
|
SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8994_aif3adc_mux),
|
|
};
|
|
|
|
+static const struct snd_soc_dapm_widget wm8994_mclk1_dapm_widgets[] = {
|
|
+SND_SOC_DAPM_SUPPLY("MCLK1", SND_SOC_NOPM, 0, 0, mclk_event,
|
|
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_widget wm8994_mclk2_dapm_widgets[] = {
|
|
+SND_SOC_DAPM_SUPPLY("MCLK2", SND_SOC_NOPM, 0, 0, mclk_event,
|
|
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
+};
|
|
+
|
|
static const struct snd_soc_dapm_widget wm8958_dapm_widgets[] = {
|
|
SND_SOC_DAPM_SUPPLY("AIF3", WM8994_POWER_MANAGEMENT_6, 5, 1, NULL, 0),
|
|
SND_SOC_DAPM_MUX("Mono PCM Out Mux", SND_SOC_NOPM, 0, 0, &mono_pcm_out_mux),
|
|
@@ -2000,10 +2046,10 @@ static const struct snd_soc_dapm_route wm8994_lateclk_intercon[] = {
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route wm8994_revd_intercon[] = {
|
|
- { "AIF1DACDAT", NULL, "AIF2DACDAT" },
|
|
- { "AIF2DACDAT", NULL, "AIF1DACDAT" },
|
|
- { "AIF1ADCDAT", NULL, "AIF2ADCDAT" },
|
|
- { "AIF2ADCDAT", NULL, "AIF1ADCDAT" },
|
|
+// { "AIF1DACDAT", NULL, "AIF2DACDAT" },
|
|
+// { "AIF2DACDAT", NULL, "AIF1DACDAT" },
|
|
+// { "AIF1ADCDAT", NULL, "AIF2ADCDAT" },
|
|
+// { "AIF2ADCDAT", NULL, "AIF1ADCDAT" },
|
|
{ "MICBIAS1", NULL, "CLK_SYS" },
|
|
{ "MICBIAS1", NULL, "MICBIAS Supply" },
|
|
{ "MICBIAS2", NULL, "CLK_SYS" },
|
|
@@ -2377,11 +2423,26 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component);
|
|
+ struct wm8994 *control = wm8994->wm8994;
|
|
+ struct wm8994_pdata *pdata = &control->pdata;
|
|
int i;
|
|
|
|
+ /*
|
|
+ * Simple card provides unconditionnaly clock_id = 0.
|
|
+ * Workaround to select master clock for aif1/2
|
|
+ */
|
|
switch (dai->id) {
|
|
case 1:
|
|
+ if (pdata->mclk1)
|
|
+ clk_id = WM8994_SYSCLK_MCLK1;
|
|
+ else if (pdata->mclk2)
|
|
+ clk_id = WM8994_SYSCLK_MCLK2;
|
|
+ break;
|
|
case 2:
|
|
+ if (pdata->mclk2)
|
|
+ clk_id = WM8994_SYSCLK_MCLK2;
|
|
+ else if (pdata->mclk1)
|
|
+ clk_id = WM8994_SYSCLK_MCLK1;
|
|
break;
|
|
|
|
default:
|
|
@@ -3991,6 +4052,7 @@ static int wm8994_component_probe(struct snd_soc_component *component)
|
|
{
|
|
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
|
|
struct wm8994 *control = dev_get_drvdata(component->dev->parent);
|
|
+ struct wm8994_pdata *pdata = &control->pdata;
|
|
struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component);
|
|
unsigned int reg;
|
|
int ret, i;
|
|
@@ -4274,6 +4336,14 @@ static int wm8994_component_probe(struct snd_soc_component *component)
|
|
ARRAY_SIZE(wm8994_snd_controls));
|
|
snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets,
|
|
ARRAY_SIZE(wm8994_specific_dapm_widgets));
|
|
+ if (pdata->mclk1)
|
|
+ snd_soc_dapm_new_controls(dapm, wm8994_mclk1_dapm_widgets,
|
|
+ ARRAY_SIZE(wm8994_mclk1_dapm_widgets));
|
|
+
|
|
+ if (pdata->mclk2)
|
|
+ snd_soc_dapm_new_controls(dapm, wm8994_mclk2_dapm_widgets,
|
|
+ ARRAY_SIZE(wm8994_mclk2_dapm_widgets));
|
|
+
|
|
if (control->revision < 4) {
|
|
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets,
|
|
ARRAY_SIZE(wm8994_lateclk_revd_widgets));
|
|
diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c
|
|
index 3e7226a53e53a..7d1672cf78cc5 100644
|
|
--- a/sound/soc/stm/stm32_i2s.c
|
|
+++ b/sound/soc/stm/stm32_i2s.c
|
|
@@ -8,6 +8,7 @@
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
+#include <linux/clk-provider.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_irq.h>
|
|
@@ -196,6 +197,9 @@ enum i2s_datlen {
|
|
#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_NAME_LEN 32
|
|
+#define STM32_I2S_RATE_11K 11025
|
|
+
|
|
/**
|
|
* struct stm32_i2s_data - private data of I2S
|
|
* @regmap_conf: I2S register map configuration pointer
|
|
@@ -206,6 +210,7 @@ enum i2s_datlen {
|
|
* @dma_data_rx: dma configuration data for tx channel
|
|
* @substream: PCM substream data pointer
|
|
* @i2sclk: kernel clock feeding the I2S clock generator
|
|
+ * @i2smclk: master clock from I2S mclk provider
|
|
* @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
|
|
@@ -215,6 +220,9 @@ enum i2s_datlen {
|
|
* @irq_lock: prevent race condition with IRQ
|
|
* @mclk_rate: master clock frequency (Hz)
|
|
* @fmt: DAI protocol
|
|
+ * @divider: prescaler division ratio
|
|
+ * @div: prescaler div field
|
|
+ * @odd: prescaler odd field
|
|
* @refcount: keep count of opened streams on I2S
|
|
* @ms_flg: master mode flag.
|
|
*/
|
|
@@ -227,6 +235,7 @@ struct stm32_i2s_data {
|
|
struct snd_dmaengine_dai_dma_data dma_data_rx;
|
|
struct snd_pcm_substream *substream;
|
|
struct clk *i2sclk;
|
|
+ struct clk *i2smclk;
|
|
struct clk *pclk;
|
|
struct clk *x8kclk;
|
|
struct clk *x11kclk;
|
|
@@ -236,10 +245,210 @@ struct stm32_i2s_data {
|
|
spinlock_t irq_lock; /* used to prevent race condition with IRQ */
|
|
unsigned int mclk_rate;
|
|
unsigned int fmt;
|
|
+ unsigned int divider;
|
|
+ unsigned int div;
|
|
+ bool odd;
|
|
int refcount;
|
|
int ms_flg;
|
|
};
|
|
|
|
+struct stm32_i2smclk_data {
|
|
+ struct clk_hw hw;
|
|
+ unsigned long freq;
|
|
+ struct stm32_i2s_data *i2s_data;
|
|
+};
|
|
+
|
|
+#define to_mclk_data(_hw) container_of(_hw, struct stm32_i2smclk_data, hw)
|
|
+
|
|
+static int stm32_i2s_calc_clk_div(struct stm32_i2s_data *i2s,
|
|
+ unsigned long input_rate,
|
|
+ unsigned long output_rate)
|
|
+{
|
|
+ unsigned int ratio, div, divider = 1;
|
|
+ bool odd;
|
|
+
|
|
+ ratio = DIV_ROUND_CLOSEST(input_rate, output_rate);
|
|
+
|
|
+ /* Check the parity of the divider */
|
|
+ odd = ratio & 0x1;
|
|
+
|
|
+ /* Compute the div prescaler */
|
|
+ div = ratio >> 1;
|
|
+
|
|
+ /* If div is 0 actual divider is 1 */
|
|
+ if (div) {
|
|
+ divider = ((2 * div) + odd);
|
|
+ dev_dbg(&i2s->pdev->dev, "Divider: 2*%d(div)+%d(odd) = %d\n",
|
|
+ div, odd, divider);
|
|
+ }
|
|
+
|
|
+ /* Division by three is not allowed by I2S prescaler */
|
|
+ if ((div == 1 && odd) || div > I2S_CGFR_I2SDIV_MAX) {
|
|
+ dev_err(&i2s->pdev->dev, "Wrong divider setting\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (input_rate % divider)
|
|
+ dev_dbg(&i2s->pdev->dev,
|
|
+ "Rate not accurate. requested (%ld), actual (%ld)\n",
|
|
+ output_rate, input_rate / divider);
|
|
+
|
|
+ i2s->div = div;
|
|
+ i2s->odd = odd;
|
|
+ i2s->divider = divider;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_i2s_set_clk_div(struct stm32_i2s_data *i2s)
|
|
+{
|
|
+ u32 cgfr, cgfr_mask;
|
|
+
|
|
+ cgfr = I2S_CGFR_I2SDIV_SET(i2s->div) | (i2s->odd << I2S_CGFR_ODD_SHIFT);
|
|
+ cgfr_mask = I2S_CGFR_I2SDIV_MASK | I2S_CGFR_ODD;
|
|
+
|
|
+ return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
|
|
+ cgfr_mask, cgfr);
|
|
+}
|
|
+
|
|
+static int stm32_i2s_set_parent_clock(struct stm32_i2s_data *i2s,
|
|
+ unsigned int rate)
|
|
+{
|
|
+ struct platform_device *pdev = i2s->pdev;
|
|
+ struct clk *parent_clk;
|
|
+ int ret;
|
|
+
|
|
+ if (!(rate % STM32_I2S_RATE_11K))
|
|
+ parent_clk = i2s->x11kclk;
|
|
+ else
|
|
+ parent_clk = i2s->x8kclk;
|
|
+
|
|
+ ret = clk_set_parent(i2s->i2sclk, parent_clk);
|
|
+ if (ret)
|
|
+ dev_err(&pdev->dev,
|
|
+ "Error %d setting i2sclk parent clock\n", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static long stm32_i2smclk_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
+ unsigned long *prate)
|
|
+{
|
|
+ struct stm32_i2smclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_i2s_data *i2s = mclk->i2s_data;
|
|
+ int ret;
|
|
+
|
|
+ ret = stm32_i2s_calc_clk_div(i2s, *prate, rate);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mclk->freq = *prate / i2s->divider;
|
|
+
|
|
+ return mclk->freq;
|
|
+}
|
|
+
|
|
+static unsigned long stm32_i2smclk_recalc_rate(struct clk_hw *hw,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct stm32_i2smclk_data *mclk = to_mclk_data(hw);
|
|
+
|
|
+ return mclk->freq;
|
|
+}
|
|
+
|
|
+static int stm32_i2smclk_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct stm32_i2smclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_i2s_data *i2s = mclk->i2s_data;
|
|
+ int ret;
|
|
+
|
|
+ ret = stm32_i2s_calc_clk_div(i2s, parent_rate, rate);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = stm32_i2s_set_clk_div(i2s);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mclk->freq = rate;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_i2smclk_enable(struct clk_hw *hw)
|
|
+{
|
|
+ struct stm32_i2smclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_i2s_data *i2s = mclk->i2s_data;
|
|
+
|
|
+ dev_dbg(&i2s->pdev->dev, "Enable master clock\n");
|
|
+
|
|
+ return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
|
|
+ I2S_CGFR_MCKOE, I2S_CGFR_MCKOE);
|
|
+}
|
|
+
|
|
+static void stm32_i2smclk_disable(struct clk_hw *hw)
|
|
+{
|
|
+ struct stm32_i2smclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_i2s_data *i2s = mclk->i2s_data;
|
|
+
|
|
+ dev_dbg(&i2s->pdev->dev, "Disable master clock\n");
|
|
+
|
|
+ regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, I2S_CGFR_MCKOE, 0);
|
|
+}
|
|
+
|
|
+static const struct clk_ops mclk_ops = {
|
|
+ .enable = stm32_i2smclk_enable,
|
|
+ .disable = stm32_i2smclk_disable,
|
|
+ .recalc_rate = stm32_i2smclk_recalc_rate,
|
|
+ .round_rate = stm32_i2smclk_round_rate,
|
|
+ .set_rate = stm32_i2smclk_set_rate,
|
|
+};
|
|
+
|
|
+static int stm32_i2s_add_mclk_provider(struct stm32_i2s_data *i2s)
|
|
+{
|
|
+ struct clk_hw *hw;
|
|
+ struct stm32_i2smclk_data *mclk;
|
|
+ struct device *dev = &i2s->pdev->dev;
|
|
+ const char *pname = __clk_get_name(i2s->i2sclk);
|
|
+ char *mclk_name, *p, *s = (char *)pname;
|
|
+ int ret, i = 0;
|
|
+
|
|
+ mclk = devm_kzalloc(dev, sizeof(*mclk), GFP_KERNEL);
|
|
+ if (!mclk)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ mclk_name = devm_kcalloc(dev, sizeof(char),
|
|
+ STM32_I2S_NAME_LEN, GFP_KERNEL);
|
|
+ if (!mclk_name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /*
|
|
+ * Forge mclk clock name from parent clock name and suffix.
|
|
+ * String after "_" char is stripped in parent name.
|
|
+ */
|
|
+ p = mclk_name;
|
|
+ while (*s && *s != '_' && (i < (STM32_I2S_NAME_LEN - 7))) {
|
|
+ *p++ = *s++;
|
|
+ i++;
|
|
+ }
|
|
+ strcat(p, "_mclk");
|
|
+
|
|
+ mclk->hw.init = CLK_HW_INIT(mclk_name, pname, &mclk_ops, 0);
|
|
+ mclk->i2s_data = i2s;
|
|
+ hw = &mclk->hw;
|
|
+
|
|
+ dev_dbg(dev, "Register master clock %s\n", mclk_name);
|
|
+ ret = devm_clk_hw_register(&i2s->pdev->dev, hw);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "mclk register fails with error %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ i2s->i2smclk = hw->clk;
|
|
+
|
|
+ /* register mclk provider */
|
|
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
|
|
+}
|
|
+
|
|
static irqreturn_t stm32_i2s_isr(int irq, void *devid)
|
|
{
|
|
struct stm32_i2s_data *i2s = (struct stm32_i2s_data *)devid;
|
|
@@ -405,18 +614,46 @@ 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);
|
|
+ int ret = 0;
|
|
|
|
- dev_dbg(cpu_dai->dev, "I2S MCLK frequency is %uHz\n", freq);
|
|
+ dev_dbg(cpu_dai->dev, "I2S MCLK frequency is %uHz. mode: %s, dir: %s\n",
|
|
+ freq, STM32_I2S_IS_MASTER(i2s) ? "master" : "slave",
|
|
+ dir ? "output" : "input");
|
|
|
|
- if ((dir == SND_SOC_CLOCK_OUT) && STM32_I2S_IS_MASTER(i2s)) {
|
|
- i2s->mclk_rate = freq;
|
|
+ /* MCLK generation is available only in master mode */
|
|
+ if (dir == SND_SOC_CLOCK_OUT && STM32_I2S_IS_MASTER(i2s)) {
|
|
+ if (!i2s->i2smclk) {
|
|
+ dev_dbg(cpu_dai->dev, "No MCLK registered\n");
|
|
+ return 0;
|
|
+ }
|
|
|
|
- /* 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);
|
|
+ /* Assume shutdown if requested frequency is 0Hz */
|
|
+ if (!freq) {
|
|
+ /* Release mclk rate only if rate was actually set */
|
|
+ if (i2s->mclk_rate) {
|
|
+ clk_rate_exclusive_put(i2s->i2smclk);
|
|
+ i2s->mclk_rate = 0;
|
|
+ }
|
|
+ return regmap_update_bits(i2s->regmap,
|
|
+ STM32_I2S_CGFR_REG,
|
|
+ I2S_CGFR_MCKOE, 0);
|
|
+ }
|
|
+ /* If master clock is used, set parent clock now */
|
|
+ ret = stm32_i2s_set_parent_clock(i2s, freq);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = clk_set_rate_exclusive(i2s->i2smclk, freq);
|
|
+ if (ret) {
|
|
+ dev_err(cpu_dai->dev, "Could not set mclk rate\n");
|
|
+ return ret;
|
|
+ }
|
|
+ ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
|
|
+ I2S_CGFR_MCKOE, I2S_CGFR_MCKOE);
|
|
+ if (!ret)
|
|
+ i2s->mclk_rate = freq;
|
|
}
|
|
|
|
- return 0;
|
|
+ return ret;
|
|
}
|
|
|
|
static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai,
|
|
@@ -424,11 +661,10 @@ static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai,
|
|
{
|
|
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 nb_bits, frame_len;
|
|
unsigned int rate = params_rate(params);
|
|
+ u32 cgfr;
|
|
int ret;
|
|
- u32 cgfr, cgfr_mask;
|
|
- bool odd;
|
|
|
|
if (!(rate % 11025))
|
|
clk_set_parent(i2s->i2sclk, i2s->x11kclk);
|
|
@@ -449,7 +685,10 @@ static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai,
|
|
* dsp mode : div = i2s_clk / (nb_bits x ws)
|
|
*/
|
|
if (i2s->mclk_rate) {
|
|
- tmp = DIV_ROUND_CLOSEST(i2s_clock_rate, i2s->mclk_rate);
|
|
+ ret = stm32_i2s_calc_clk_div(i2s, i2s_clock_rate,
|
|
+ i2s->mclk_rate);
|
|
+ if (ret)
|
|
+ return ret;
|
|
} else {
|
|
frame_len = 32;
|
|
if ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) ==
|
|
@@ -462,34 +701,13 @@ static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai,
|
|
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;
|
|
+ ret = stm32_i2s_calc_clk_div(i2s, i2s_clock_rate,
|
|
+ (nb_bits * rate));
|
|
+ if (ret)
|
|
+ return ret;
|
|
}
|
|
|
|
- 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);
|
|
+ ret = stm32_i2s_set_clk_div(i2s);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
@@ -694,9 +912,6 @@ static void stm32_i2s_shutdown(struct snd_pcm_substream *substream,
|
|
struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);
|
|
unsigned long flags;
|
|
|
|
- regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
|
|
- I2S_CGFR_MCKOE, (unsigned int)~I2S_CGFR_MCKOE);
|
|
-
|
|
clk_disable_unprepare(i2s->i2sclk);
|
|
|
|
spin_lock_irqsave(&i2s->irq_lock, flags);
|
|
@@ -831,28 +1046,43 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev,
|
|
/* Get clocks */
|
|
i2s->pclk = devm_clk_get(&pdev->dev, "pclk");
|
|
if (IS_ERR(i2s->pclk)) {
|
|
- dev_err(&pdev->dev, "Could not get pclk\n");
|
|
+ if (PTR_ERR(i2s->pclk) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Could not get pclk: %ld\n",
|
|
+ PTR_ERR(i2s->pclk));
|
|
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");
|
|
+ if (PTR_ERR(i2s->i2sclk) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Could not get i2sclk: %ld\n",
|
|
+ PTR_ERR(i2s->i2sclk));
|
|
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");
|
|
+ if (PTR_ERR(i2s->x8kclk) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Could not get x8k parent clock: %ld\n",
|
|
+ PTR_ERR(i2s->x8kclk));
|
|
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");
|
|
+ if (PTR_ERR(i2s->x11kclk) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Could not get x11k parent clock: %ld\n",
|
|
+ PTR_ERR(i2s->x11kclk));
|
|
return PTR_ERR(i2s->x11kclk);
|
|
}
|
|
|
|
+ /* Register mclk provider if requested */
|
|
+ if (of_find_property(np, "#clock-cells", NULL)) {
|
|
+ ret = stm32_i2s_add_mclk_provider(i2s);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
/* Get irqs */
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
@@ -866,12 +1096,24 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev,
|
|
}
|
|
|
|
/* Reset */
|
|
- rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
|
- if (!IS_ERR(rst)) {
|
|
- reset_control_assert(rst);
|
|
- udelay(2);
|
|
- reset_control_deassert(rst);
|
|
+ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
|
|
+ if (IS_ERR(rst)) {
|
|
+ if (PTR_ERR(rst) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Reset controller error %ld\n",
|
|
+ PTR_ERR(rst));
|
|
+ return PTR_ERR(rst);
|
|
}
|
|
+ reset_control_assert(rst);
|
|
+ udelay(2);
|
|
+ reset_control_deassert(rst);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_i2s_remove(struct platform_device *pdev)
|
|
+{
|
|
+ snd_dmaengine_pcm_unregister(&pdev->dev);
|
|
+ snd_soc_unregister_component(&pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
@@ -886,16 +1128,16 @@ static int stm32_i2s_probe(struct platform_device *pdev)
|
|
if (!i2s)
|
|
return -ENOMEM;
|
|
|
|
- ret = stm32_i2s_parse_dt(pdev, i2s);
|
|
- if (ret)
|
|
- return ret;
|
|
-
|
|
i2s->pdev = pdev;
|
|
i2s->ms_flg = I2S_MS_NOT_SET;
|
|
spin_lock_init(&i2s->lock_fd);
|
|
spin_lock_init(&i2s->irq_lock);
|
|
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;
|
|
@@ -903,48 +1145,62 @@ static int stm32_i2s_probe(struct platform_device *pdev)
|
|
i2s->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "pclk",
|
|
i2s->base, i2s->regmap_conf);
|
|
if (IS_ERR(i2s->regmap)) {
|
|
- dev_err(&pdev->dev, "regmap init failed\n");
|
|
+ if (PTR_ERR(i2s->regmap) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Regmap init error %ld\n",
|
|
+ PTR_ERR(i2s->regmap));
|
|
return PTR_ERR(i2s->regmap);
|
|
}
|
|
|
|
- ret = devm_snd_soc_register_component(&pdev->dev, &stm32_i2s_component,
|
|
- i2s->dai_drv, 1);
|
|
- if (ret)
|
|
+ ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0);
|
|
+ if (ret) {
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "PCM DMA register error %d\n", ret);
|
|
return ret;
|
|
+ }
|
|
|
|
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
|
|
- &stm32_i2s_pcm_config, 0);
|
|
- if (ret)
|
|
+ ret = snd_soc_register_component(&pdev->dev, &stm32_i2s_component,
|
|
+ i2s->dai_drv, 1);
|
|
+ if (ret) {
|
|
+ snd_dmaengine_pcm_unregister(&pdev->dev);
|
|
return ret;
|
|
+ }
|
|
|
|
/* Set SPI/I2S in i2s mode */
|
|
ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,
|
|
I2S_CGFR_I2SMOD, I2S_CGFR_I2SMOD);
|
|
if (ret)
|
|
- return ret;
|
|
+ goto error;
|
|
|
|
ret = regmap_read(i2s->regmap, STM32_I2S_IPIDR_REG, &val);
|
|
if (ret)
|
|
- return ret;
|
|
+ goto error;
|
|
|
|
if (val == I2S_IPIDR_NUMBER) {
|
|
ret = regmap_read(i2s->regmap, STM32_I2S_HWCFGR_REG, &val);
|
|
if (ret)
|
|
- return ret;
|
|
+ goto error;
|
|
|
|
if (!FIELD_GET(I2S_HWCFGR_I2S_SUPPORT_MASK, val)) {
|
|
dev_err(&pdev->dev,
|
|
"Device does not support i2s mode\n");
|
|
- return -EPERM;
|
|
+ ret = -EPERM;
|
|
+ goto error;
|
|
}
|
|
|
|
ret = regmap_read(i2s->regmap, STM32_I2S_VERR_REG, &val);
|
|
+ if (ret)
|
|
+ goto error;
|
|
|
|
dev_dbg(&pdev->dev, "I2S version: %lu.%lu registered\n",
|
|
FIELD_GET(I2S_VERR_MAJ_MASK, val),
|
|
FIELD_GET(I2S_VERR_MIN_MASK, val));
|
|
}
|
|
|
|
+ return ret;
|
|
+
|
|
+error:
|
|
+ stm32_i2s_remove(pdev);
|
|
+
|
|
return ret;
|
|
}
|
|
|
|
@@ -981,6 +1237,7 @@ static struct platform_driver stm32_i2s_driver = {
|
|
.pm = &stm32_i2s_pm_ops,
|
|
},
|
|
.probe = stm32_i2s_probe,
|
|
+ .remove = stm32_i2s_remove,
|
|
};
|
|
|
|
module_platform_driver(stm32_i2s_driver);
|
|
diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c
|
|
index ef4273361d0d8..820ae27e7e2e9 100644
|
|
--- a/sound/soc/stm/stm32_sai.c
|
|
+++ b/sound/soc/stm/stm32_sai.c
|
|
@@ -174,20 +174,26 @@ static int stm32_sai_probe(struct platform_device *pdev)
|
|
if (!STM_SAI_IS_F4(sai)) {
|
|
sai->pclk = devm_clk_get(&pdev->dev, "pclk");
|
|
if (IS_ERR(sai->pclk)) {
|
|
- dev_err(&pdev->dev, "missing bus clock pclk\n");
|
|
+ if (PTR_ERR(sai->pclk) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "missing bus clock pclk: %ld\n",
|
|
+ PTR_ERR(sai->pclk));
|
|
return PTR_ERR(sai->pclk);
|
|
}
|
|
}
|
|
|
|
sai->clk_x8k = devm_clk_get(&pdev->dev, "x8k");
|
|
if (IS_ERR(sai->clk_x8k)) {
|
|
- dev_err(&pdev->dev, "missing x8k parent clock\n");
|
|
+ if (PTR_ERR(sai->clk_x8k) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "missing x8k parent clock: %ld\n",
|
|
+ PTR_ERR(sai->clk_x8k));
|
|
return PTR_ERR(sai->clk_x8k);
|
|
}
|
|
|
|
sai->clk_x11k = devm_clk_get(&pdev->dev, "x11k");
|
|
if (IS_ERR(sai->clk_x11k)) {
|
|
- dev_err(&pdev->dev, "missing x11k parent clock\n");
|
|
+ if (PTR_ERR(sai->clk_x11k) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "missing x11k parent clock: %ld\n",
|
|
+ PTR_ERR(sai->clk_x11k));
|
|
return PTR_ERR(sai->clk_x11k);
|
|
}
|
|
|
|
@@ -197,12 +203,16 @@ static int stm32_sai_probe(struct platform_device *pdev)
|
|
return sai->irq;
|
|
|
|
/* reset */
|
|
- rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
|
- if (!IS_ERR(rst)) {
|
|
- reset_control_assert(rst);
|
|
- udelay(2);
|
|
- reset_control_deassert(rst);
|
|
+ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
|
|
+ if (IS_ERR(rst)) {
|
|
+ if (PTR_ERR(rst) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Reset controller error %ld\n",
|
|
+ PTR_ERR(rst));
|
|
+ return PTR_ERR(rst);
|
|
}
|
|
+ reset_control_assert(rst);
|
|
+ udelay(2);
|
|
+ reset_control_deassert(rst);
|
|
|
|
/* Enable peripheral clock to allow register access */
|
|
ret = clk_prepare_enable(sai->pclk);
|
|
diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c
|
|
index 7e965848796c3..a7c39d1794a7b 100644
|
|
--- a/sound/soc/stm/stm32_sai_sub.c
|
|
+++ b/sound/soc/stm/stm32_sai_sub.c
|
|
@@ -12,6 +12,7 @@
|
|
#include <linux/module.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_platform.h>
|
|
+#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include <sound/asoundef.h>
|
|
@@ -1380,7 +1381,9 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev,
|
|
sai->regmap = devm_regmap_init_mmio(&pdev->dev, base,
|
|
sai->regmap_config);
|
|
if (IS_ERR(sai->regmap)) {
|
|
- dev_err(&pdev->dev, "Failed to initialize MMIO\n");
|
|
+ if (PTR_ERR(sai->regmap) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Regmap init error %ld\n",
|
|
+ PTR_ERR(sai->regmap));
|
|
return PTR_ERR(sai->regmap);
|
|
}
|
|
|
|
@@ -1471,7 +1474,9 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev,
|
|
of_node_put(args.np);
|
|
sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck");
|
|
if (IS_ERR(sai->sai_ck)) {
|
|
- dev_err(&pdev->dev, "Missing kernel clock sai_ck\n");
|
|
+ if (PTR_ERR(sai->sai_ck) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Missing kernel clock sai_ck: %ld\n",
|
|
+ PTR_ERR(sai->sai_ck));
|
|
return PTR_ERR(sai->sai_ck);
|
|
}
|
|
|
|
@@ -1548,16 +1553,21 @@ static int stm32_sai_sub_probe(struct platform_device *pdev)
|
|
|
|
ret = snd_dmaengine_pcm_register(&pdev->dev, conf, 0);
|
|
if (ret) {
|
|
- dev_err(&pdev->dev, "Could not register pcm dma\n");
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "PCM DMA register error %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = snd_soc_register_component(&pdev->dev, &stm32_component,
|
|
&sai->cpu_dai_drv, 1);
|
|
- if (ret)
|
|
+ if (ret) {
|
|
snd_dmaengine_pcm_unregister(&pdev->dev);
|
|
+ return ret;
|
|
+ }
|
|
|
|
- return ret;
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static int stm32_sai_sub_remove(struct platform_device *pdev)
|
|
@@ -1567,6 +1577,7 @@ static int stm32_sai_sub_remove(struct platform_device *pdev)
|
|
clk_unprepare(sai->pdata->pclk);
|
|
snd_dmaengine_pcm_unregister(&pdev->dev);
|
|
snd_soc_unregister_component(&pdev->dev);
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c
|
|
index 9fc2a1767eb1d..1bfa3b2ba9744 100644
|
|
--- a/sound/soc/stm/stm32_spdifrx.c
|
|
+++ b/sound/soc/stm/stm32_spdifrx.c
|
|
@@ -353,6 +353,8 @@ static int stm32_spdifrx_start_sync(struct stm32_spdifrx_data *spdifrx)
|
|
SPDIFRX_CR_CUMSK | SPDIFRX_CR_PTMSK | SPDIFRX_CR_RXSTEO;
|
|
cr_mask = cr;
|
|
|
|
+ cr |= SPDIFRX_CR_NBTRSET(SPDIFRX_NBTR_63);
|
|
+ cr_mask |= SPDIFRX_CR_NBTR_MASK;
|
|
cr |= SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_SYNC);
|
|
cr_mask |= SPDIFRX_CR_SPDIFEN_MASK;
|
|
ret = regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR,
|
|
@@ -404,7 +406,9 @@ static int stm32_spdifrx_dma_ctrl_register(struct device *dev,
|
|
|
|
spdifrx->ctrl_chan = dma_request_chan(dev, "rx-ctrl");
|
|
if (IS_ERR(spdifrx->ctrl_chan)) {
|
|
- dev_err(dev, "dma_request_slave_channel failed\n");
|
|
+ if (PTR_ERR(spdifrx->ctrl_chan) != -EPROBE_DEFER)
|
|
+ dev_err(dev, "dma_request_slave_channel error %ld\n",
|
|
+ PTR_ERR(spdifrx->ctrl_chan));
|
|
return PTR_ERR(spdifrx->ctrl_chan);
|
|
}
|
|
|
|
@@ -665,7 +669,7 @@ static irqreturn_t stm32_spdifrx_isr(int irq, void *devid)
|
|
struct stm32_spdifrx_data *spdifrx = (struct stm32_spdifrx_data *)devid;
|
|
struct platform_device *pdev = spdifrx->pdev;
|
|
unsigned int cr, mask, sr, imr;
|
|
- unsigned int flags;
|
|
+ unsigned int flags, sync_state;
|
|
int err = 0, err_xrun = 0;
|
|
|
|
regmap_read(spdifrx->regmap, STM32_SPDIFRX_SR, &sr);
|
|
@@ -725,11 +729,23 @@ static irqreturn_t stm32_spdifrx_isr(int irq, void *devid)
|
|
}
|
|
|
|
if (err) {
|
|
- /* SPDIFRX in STATE_STOP. Disable SPDIFRX to clear errors */
|
|
+ regmap_read(spdifrx->regmap, STM32_SPDIFRX_CR, &cr);
|
|
+ sync_state = FIELD_GET(SPDIFRX_CR_SPDIFEN_MASK, cr) &&
|
|
+ SPDIFRX_SPDIFEN_SYNC;
|
|
+
|
|
+ /* SPDIFRX is in STATE_STOP. Disable SPDIFRX to clear errors */
|
|
cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_DISABLE);
|
|
regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR,
|
|
SPDIFRX_CR_SPDIFEN_MASK, cr);
|
|
|
|
+ /* If SPDIFRX was in STATE_SYNC, retry synchro */
|
|
+ if (sync_state) {
|
|
+ cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_SYNC);
|
|
+ regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR,
|
|
+ SPDIFRX_CR_SPDIFEN_MASK, cr);
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
spin_lock(&spdifrx->irq_lock);
|
|
if (spdifrx->substream)
|
|
snd_pcm_stop(spdifrx->substream,
|
|
@@ -915,7 +931,9 @@ static int stm32_spdifrx_parse_of(struct platform_device *pdev,
|
|
|
|
spdifrx->kclk = devm_clk_get(&pdev->dev, "kclk");
|
|
if (IS_ERR(spdifrx->kclk)) {
|
|
- dev_err(&pdev->dev, "Could not get kclk\n");
|
|
+ if (PTR_ERR(spdifrx->kclk) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Could not get kclk: %ld\n",
|
|
+ PTR_ERR(spdifrx->kclk));
|
|
return PTR_ERR(spdifrx->kclk);
|
|
}
|
|
|
|
@@ -926,6 +944,22 @@ static int stm32_spdifrx_parse_of(struct platform_device *pdev,
|
|
return 0;
|
|
}
|
|
|
|
+static int stm32_spdifrx_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct stm32_spdifrx_data *spdifrx = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (spdifrx->ctrl_chan)
|
|
+ dma_release_channel(spdifrx->ctrl_chan);
|
|
+
|
|
+ if (spdifrx->dmab)
|
|
+ snd_dma_free_pages(spdifrx->dmab);
|
|
+
|
|
+ snd_dmaengine_pcm_unregister(&pdev->dev);
|
|
+ snd_soc_unregister_component(&pdev->dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int stm32_spdifrx_probe(struct platform_device *pdev)
|
|
{
|
|
struct stm32_spdifrx_data *spdifrx;
|
|
@@ -953,7 +987,9 @@ static int stm32_spdifrx_probe(struct platform_device *pdev)
|
|
spdifrx->base,
|
|
spdifrx->regmap_conf);
|
|
if (IS_ERR(spdifrx->regmap)) {
|
|
- dev_err(&pdev->dev, "Regmap init failed\n");
|
|
+ if (PTR_ERR(spdifrx->regmap) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Regmap init error %ld\n",
|
|
+ PTR_ERR(spdifrx->regmap));
|
|
return PTR_ERR(spdifrx->regmap);
|
|
}
|
|
|
|
@@ -964,31 +1000,38 @@ static int stm32_spdifrx_probe(struct platform_device *pdev)
|
|
return ret;
|
|
}
|
|
|
|
- rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
|
- if (!IS_ERR(rst)) {
|
|
- reset_control_assert(rst);
|
|
- udelay(2);
|
|
- reset_control_deassert(rst);
|
|
+ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
|
|
+ if (IS_ERR(rst)) {
|
|
+ if (PTR_ERR(rst) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Reset controller error %ld\n",
|
|
+ PTR_ERR(rst));
|
|
+ return PTR_ERR(rst);
|
|
}
|
|
+ reset_control_assert(rst);
|
|
+ udelay(2);
|
|
+ reset_control_deassert(rst);
|
|
|
|
- ret = devm_snd_soc_register_component(&pdev->dev,
|
|
- &stm32_spdifrx_component,
|
|
- stm32_spdifrx_dai,
|
|
- ARRAY_SIZE(stm32_spdifrx_dai));
|
|
- if (ret)
|
|
+ pcm_config = &stm32_spdifrx_pcm_config;
|
|
+ ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0);
|
|
+ if (ret) {
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "PCM DMA register error %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = snd_soc_register_component(&pdev->dev,
|
|
+ &stm32_spdifrx_component,
|
|
+ stm32_spdifrx_dai,
|
|
+ ARRAY_SIZE(stm32_spdifrx_dai));
|
|
+ if (ret) {
|
|
+ snd_dmaengine_pcm_unregister(&pdev->dev);
|
|
return ret;
|
|
+ }
|
|
|
|
ret = stm32_spdifrx_dma_ctrl_register(&pdev->dev, spdifrx);
|
|
if (ret)
|
|
goto error;
|
|
|
|
- pcm_config = &stm32_spdifrx_pcm_config;
|
|
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0);
|
|
- if (ret) {
|
|
- dev_err(&pdev->dev, "PCM DMA register returned %d\n", ret);
|
|
- goto error;
|
|
- }
|
|
-
|
|
ret = regmap_read(spdifrx->regmap, STM32_SPDIFRX_IDR, &idr);
|
|
if (ret)
|
|
goto error;
|
|
@@ -1006,27 +1049,11 @@ static int stm32_spdifrx_probe(struct platform_device *pdev)
|
|
return ret;
|
|
|
|
error:
|
|
- if (!IS_ERR(spdifrx->ctrl_chan))
|
|
- dma_release_channel(spdifrx->ctrl_chan);
|
|
- if (spdifrx->dmab)
|
|
- snd_dma_free_pages(spdifrx->dmab);
|
|
+ stm32_spdifrx_remove(pdev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
-static int stm32_spdifrx_remove(struct platform_device *pdev)
|
|
-{
|
|
- struct stm32_spdifrx_data *spdifrx = platform_get_drvdata(pdev);
|
|
-
|
|
- if (spdifrx->ctrl_chan)
|
|
- dma_release_channel(spdifrx->ctrl_chan);
|
|
-
|
|
- if (spdifrx->dmab)
|
|
- snd_dma_free_pages(spdifrx->dmab);
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
MODULE_DEVICE_TABLE(of, stm32_spdifrx_ids);
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
--
|
|
2.17.1
|
|
|