2807 lines
84 KiB
Diff
2807 lines
84 KiB
Diff
From f3a6e1e545c6a1810b2560ea821a3ed61b751d60 Mon Sep 17 00:00:00 2001
|
|
From: Romuald JEANNE <romuald.jeanne@st.com>
|
|
Date: Mon, 20 Jan 2020 18:09:29 +0100
|
|
Subject: [PATCH 28/31] ARM stm32mp1 r3 SOUND
|
|
|
|
---
|
|
sound/soc/codecs/Kconfig | 4 +-
|
|
sound/soc/codecs/cs42l51-i2c.c | 34 ++-
|
|
sound/soc/codecs/cs42l51.c | 231 +++++++++++++++--
|
|
sound/soc/codecs/cs42l51.h | 21 ++
|
|
sound/soc/codecs/wm8994.c | 80 +++++-
|
|
sound/soc/stm/Kconfig | 1 +
|
|
sound/soc/stm/stm32_adfsdm.c | 52 +++-
|
|
sound/soc/stm/stm32_i2s.c | 186 +++++++++-----
|
|
sound/soc/stm/stm32_sai.c | 134 ++++++++--
|
|
sound/soc/stm/stm32_sai.h | 59 ++++-
|
|
sound/soc/stm/stm32_sai_sub.c | 562 +++++++++++++++++++++++++++++++++--------
|
|
sound/soc/stm/stm32_spdifrx.c | 132 ++++++++--
|
|
12 files changed, 1245 insertions(+), 251 deletions(-)
|
|
|
|
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
|
|
index efb095d..58161d1 100644
|
|
--- a/sound/soc/codecs/Kconfig
|
|
+++ b/sound/soc/codecs/Kconfig
|
|
@@ -575,7 +575,7 @@ config SND_SOC_DA9055
|
|
tristate
|
|
|
|
config SND_SOC_DMIC
|
|
- tristate
|
|
+ tristate "Generic DMIC codec"
|
|
|
|
config SND_SOC_HDMI_CODEC
|
|
tristate
|
|
@@ -1227,7 +1227,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-i2c.c b/sound/soc/codecs/cs42l51-i2c.c
|
|
index 4b5731a..9abbd98 100644
|
|
--- a/sound/soc/codecs/cs42l51-i2c.c
|
|
+++ b/sound/soc/codecs/cs42l51-i2c.c
|
|
@@ -29,18 +29,48 @@ static int cs42l51_i2c_probe(struct i2c_client *i2c,
|
|
struct regmap_config config;
|
|
|
|
config = cs42l51_regmap;
|
|
- config.val_bits = 8;
|
|
- config.reg_bits = 8;
|
|
|
|
return cs42l51_probe(&i2c->dev, devm_regmap_init_i2c(i2c, &config));
|
|
}
|
|
|
|
+static int cs42l51_i2c_remove(struct i2c_client *i2c)
|
|
+{
|
|
+ return cs42l51_remove(&i2c->dev);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int cs42l51_suspend(struct device *dev)
|
|
+{
|
|
+ struct cs42l51_private *cs42l51 = dev_get_drvdata(dev);
|
|
+
|
|
+ regcache_cache_only(cs42l51->regmap, true);
|
|
+ regcache_mark_dirty(cs42l51->regmap);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cs42l51_resume(struct device *dev)
|
|
+{
|
|
+ struct cs42l51_private *cs42l51 = dev_get_drvdata(dev);
|
|
+
|
|
+ regcache_cache_only(cs42l51->regmap, false);
|
|
+
|
|
+ return regcache_sync(cs42l51->regmap);
|
|
+}
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+static const struct dev_pm_ops cs42l51_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(cs42l51_suspend, cs42l51_resume)
|
|
+};
|
|
+
|
|
static struct i2c_driver cs42l51_i2c_driver = {
|
|
.driver = {
|
|
.name = "cs42l51",
|
|
.of_match_table = cs42l51_of_match,
|
|
+ .pm = &cs42l51_pm_ops,
|
|
},
|
|
.probe = cs42l51_i2c_probe,
|
|
+ .remove = cs42l51_i2c_remove,
|
|
.id_table = cs42l51_i2c_id,
|
|
};
|
|
|
|
diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c
|
|
index 5080d7a3..83313ec 100644
|
|
--- a/sound/soc/codecs/cs42l51.c
|
|
+++ b/sound/soc/codecs/cs42l51.c
|
|
@@ -21,6 +21,7 @@
|
|
* - master mode *NOT* supported
|
|
*/
|
|
|
|
+#include <linux/clk.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <sound/core.h>
|
|
@@ -29,20 +30,14 @@
|
|
#include <sound/initval.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/pcm.h>
|
|
-#include <linux/regmap.h>
|
|
-
|
|
+#include <linux/gpio/consumer.h>
|
|
#include "cs42l51.h"
|
|
|
|
-enum master_slave_mode {
|
|
- MODE_SLAVE,
|
|
- MODE_SLAVE_AUTO,
|
|
- MODE_MASTER,
|
|
-};
|
|
-
|
|
-struct cs42l51_private {
|
|
- unsigned int mclk;
|
|
- unsigned int audio_mode; /* The mode (I2S or left-justified) */
|
|
- enum master_slave_mode func;
|
|
+static const char *cs42l51_supply_names[CS42L51_NUM_SUPPLIES] = {
|
|
+ "VL",
|
|
+ "VD",
|
|
+ "VA",
|
|
+ "VAHP",
|
|
};
|
|
|
|
#define CS42L51_FORMATS ( \
|
|
@@ -109,6 +104,7 @@ static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0);
|
|
static const DECLARE_TLV_DB_SCALE(aout_tlv, -10200, 50, 0);
|
|
|
|
static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(adc_boost_tlv, 2000, 2000, 0);
|
|
static const char *chan_mix[] = {
|
|
"L R",
|
|
"L+R",
|
|
@@ -137,6 +133,8 @@ static const struct snd_kcontrol_new cs42l51_snd_controls[] = {
|
|
SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0),
|
|
SOC_DOUBLE_TLV("Mic Boost Volume",
|
|
CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv),
|
|
+ SOC_DOUBLE_TLV("ADC Boost Volume",
|
|
+ CS42L51_MIC_CTL, 5, 6, 1, 0, adc_boost_tlv),
|
|
SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv),
|
|
SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv),
|
|
SOC_ENUM_EXT("PCM channel mixer",
|
|
@@ -165,6 +163,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;
|
|
}
|
|
|
|
@@ -193,7 +192,8 @@ static const struct snd_kcontrol_new cs42l51_adcr_mux_controls =
|
|
SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum);
|
|
|
|
static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
|
|
- SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1),
|
|
+ SND_SOC_DAPM_SUPPLY("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1, NULL,
|
|
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0,
|
|
cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
|
|
SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0,
|
|
@@ -237,6 +237,10 @@ static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
|
|
&cs42l51_adcr_mux_controls),
|
|
};
|
|
|
|
+static const struct snd_soc_dapm_widget cs42l51_dapm_mclk_widgets[] = {
|
|
+ SND_SOC_DAPM_CLOCK_SUPPLY("MCLK")
|
|
+};
|
|
+
|
|
static const struct snd_soc_dapm_route cs42l51_routes[] = {
|
|
{"HPL", NULL, "Left DAC"},
|
|
{"HPR", NULL, "Right DAC"},
|
|
@@ -323,6 +327,19 @@ static struct cs42l51_ratios slave_auto_ratios[] = {
|
|
{ 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 },
|
|
};
|
|
|
|
+/*
|
|
+ * Master mode mclk/fs ratios.
|
|
+ * Recommended configurations are SSM for 4-50khz and DSM for 50-100kHz ranges
|
|
+ * The table below provides support of following ratios:
|
|
+ * 128: SSM (%128) with div2 disabled
|
|
+ * 256: SSM (%128) with div2 enabled
|
|
+ * In both cases, if sampling rate is above 50kHz, SSM is overridden
|
|
+ * with DSM (%128) configuration
|
|
+ */
|
|
+static struct cs42l51_ratios master_ratios[] = {
|
|
+ { 128, CS42L51_SSM_MODE, 0 }, { 256, CS42L51_SSM_MODE, 1 },
|
|
+};
|
|
+
|
|
static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
|
int clk_id, unsigned int freq, int dir)
|
|
{
|
|
@@ -345,11 +362,13 @@ static int cs42l51_hw_params(struct snd_pcm_substream *substream,
|
|
unsigned int ratio;
|
|
struct cs42l51_ratios *ratios = NULL;
|
|
int nr_ratios = 0;
|
|
- int intf_ctl, power_ctl, fmt;
|
|
+ int intf_ctl, power_ctl, fmt, mode;
|
|
|
|
switch (cs42l51->func) {
|
|
case MODE_MASTER:
|
|
- return -EINVAL;
|
|
+ ratios = master_ratios;
|
|
+ nr_ratios = ARRAY_SIZE(master_ratios);
|
|
+ break;
|
|
case MODE_SLAVE:
|
|
ratios = slave_ratios;
|
|
nr_ratios = ARRAY_SIZE(slave_ratios);
|
|
@@ -385,7 +404,16 @@ static int cs42l51_hw_params(struct snd_pcm_substream *substream,
|
|
switch (cs42l51->func) {
|
|
case MODE_MASTER:
|
|
intf_ctl |= CS42L51_INTF_CTL_MASTER;
|
|
- power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
|
|
+ mode = ratios[i].speed_mode;
|
|
+ /* Force DSM mode if sampling rate is above 50kHz */
|
|
+ if (rate > 50000)
|
|
+ mode = CS42L51_DSM_MODE;
|
|
+ power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(mode);
|
|
+ /*
|
|
+ * Auto detect mode is not applicable for master mode and has to
|
|
+ * be disabled. Otherwise SPEED[1:0] bits will be ignored.
|
|
+ */
|
|
+ power_ctl &= ~CS42L51_MIC_POWER_CTL_AUTO;
|
|
break;
|
|
case MODE_SLAVE:
|
|
power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
|
|
@@ -458,6 +486,13 @@ static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute)
|
|
return snd_soc_component_write(component, CS42L51_DAC_OUT_CTL, reg);
|
|
}
|
|
|
|
+static int cs42l51_of_xlate_dai_id(struct snd_soc_component *component,
|
|
+ struct device_node *endpoint)
|
|
+{
|
|
+ /* return dai id 0, whatever the endpoint index */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static const struct snd_soc_dai_ops cs42l51_dai_ops = {
|
|
.hw_params = cs42l51_hw_params,
|
|
.set_sysclk = cs42l51_set_dai_sysclk,
|
|
@@ -487,6 +522,14 @@ static struct snd_soc_dai_driver cs42l51_dai = {
|
|
static int cs42l51_component_probe(struct snd_soc_component *component)
|
|
{
|
|
int ret, reg;
|
|
+ struct snd_soc_dapm_context *dapm;
|
|
+ struct cs42l51_private *cs42l51;
|
|
+
|
|
+ cs42l51 = snd_soc_component_get_drvdata(component);
|
|
+ dapm = snd_soc_component_get_dapm(component);
|
|
+
|
|
+ if (cs42l51->mclk_handle)
|
|
+ snd_soc_dapm_new_controls(dapm, cs42l51_dapm_mclk_widgets, 1);
|
|
|
|
/*
|
|
* DAC configuration
|
|
@@ -512,13 +555,113 @@ static const struct snd_soc_component_driver soc_component_device_cs42l51 = {
|
|
.num_dapm_widgets = ARRAY_SIZE(cs42l51_dapm_widgets),
|
|
.dapm_routes = cs42l51_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(cs42l51_routes),
|
|
+ .of_xlate_dai_id = cs42l51_of_xlate_dai_id,
|
|
.idle_bias_on = 1,
|
|
.use_pmdown_time = 1,
|
|
.endianness = 1,
|
|
.non_legacy_dai_naming = 1,
|
|
};
|
|
|
|
+static bool cs42l51_writeable_reg(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case CS42L51_POWER_CTL1:
|
|
+ case CS42L51_MIC_POWER_CTL:
|
|
+ case CS42L51_INTF_CTL:
|
|
+ case CS42L51_MIC_CTL:
|
|
+ case CS42L51_ADC_CTL:
|
|
+ case CS42L51_ADC_INPUT:
|
|
+ case CS42L51_DAC_OUT_CTL:
|
|
+ case CS42L51_DAC_CTL:
|
|
+ case CS42L51_ALC_PGA_CTL:
|
|
+ case CS42L51_ALC_PGB_CTL:
|
|
+ case CS42L51_ADCA_ATT:
|
|
+ case CS42L51_ADCB_ATT:
|
|
+ case CS42L51_ADCA_VOL:
|
|
+ case CS42L51_ADCB_VOL:
|
|
+ case CS42L51_PCMA_VOL:
|
|
+ case CS42L51_PCMB_VOL:
|
|
+ case CS42L51_BEEP_FREQ:
|
|
+ case CS42L51_BEEP_VOL:
|
|
+ case CS42L51_BEEP_CONF:
|
|
+ case CS42L51_TONE_CTL:
|
|
+ case CS42L51_AOUTA_VOL:
|
|
+ case CS42L51_AOUTB_VOL:
|
|
+ case CS42L51_PCM_MIXER:
|
|
+ case CS42L51_LIMIT_THRES_DIS:
|
|
+ case CS42L51_LIMIT_REL:
|
|
+ case CS42L51_LIMIT_ATT:
|
|
+ case CS42L51_ALC_EN:
|
|
+ case CS42L51_ALC_REL:
|
|
+ case CS42L51_ALC_THRES:
|
|
+ case CS42L51_NOISE_CONF:
|
|
+ case CS42L51_CHARGE_FREQ:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool cs42l51_volatile_reg(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case CS42L51_STATUS:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool cs42l51_readable_reg(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case CS42L51_CHIP_REV_ID:
|
|
+ case CS42L51_POWER_CTL1:
|
|
+ case CS42L51_MIC_POWER_CTL:
|
|
+ case CS42L51_INTF_CTL:
|
|
+ case CS42L51_MIC_CTL:
|
|
+ case CS42L51_ADC_CTL:
|
|
+ case CS42L51_ADC_INPUT:
|
|
+ case CS42L51_DAC_OUT_CTL:
|
|
+ case CS42L51_DAC_CTL:
|
|
+ case CS42L51_ALC_PGA_CTL:
|
|
+ case CS42L51_ALC_PGB_CTL:
|
|
+ case CS42L51_ADCA_ATT:
|
|
+ case CS42L51_ADCB_ATT:
|
|
+ case CS42L51_ADCA_VOL:
|
|
+ case CS42L51_ADCB_VOL:
|
|
+ case CS42L51_PCMA_VOL:
|
|
+ case CS42L51_PCMB_VOL:
|
|
+ case CS42L51_BEEP_FREQ:
|
|
+ case CS42L51_BEEP_VOL:
|
|
+ case CS42L51_BEEP_CONF:
|
|
+ case CS42L51_TONE_CTL:
|
|
+ case CS42L51_AOUTA_VOL:
|
|
+ case CS42L51_AOUTB_VOL:
|
|
+ case CS42L51_PCM_MIXER:
|
|
+ case CS42L51_LIMIT_THRES_DIS:
|
|
+ case CS42L51_LIMIT_REL:
|
|
+ case CS42L51_LIMIT_ATT:
|
|
+ case CS42L51_ALC_EN:
|
|
+ case CS42L51_ALC_REL:
|
|
+ case CS42L51_ALC_THRES:
|
|
+ case CS42L51_NOISE_CONF:
|
|
+ case CS42L51_STATUS:
|
|
+ case CS42L51_CHARGE_FREQ:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
const struct regmap_config cs42l51_regmap = {
|
|
+ .reg_bits = 8,
|
|
+ .reg_stride = 1,
|
|
+ .val_bits = 8,
|
|
+ .use_single_rw = true,
|
|
+ .readable_reg = cs42l51_readable_reg,
|
|
+ .volatile_reg = cs42l51_volatile_reg,
|
|
+ .writeable_reg = cs42l51_writeable_reg,
|
|
.max_register = CS42L51_CHARGE_FREQ,
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
@@ -528,7 +671,7 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap)
|
|
{
|
|
struct cs42l51_private *cs42l51;
|
|
unsigned int val;
|
|
- int ret;
|
|
+ int ret, i;
|
|
|
|
if (IS_ERR(regmap))
|
|
return PTR_ERR(regmap);
|
|
@@ -539,6 +682,42 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(dev, cs42l51);
|
|
+ cs42l51->regmap = regmap;
|
|
+
|
|
+ cs42l51->mclk_handle = devm_clk_get(dev, "MCLK");
|
|
+ if (IS_ERR(cs42l51->mclk_handle)) {
|
|
+ if (PTR_ERR(cs42l51->mclk_handle) != -ENOENT)
|
|
+ return PTR_ERR(cs42l51->mclk_handle);
|
|
+ cs42l51->mclk_handle = NULL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(cs42l51->supplies); i++)
|
|
+ cs42l51->supplies[i].supply = cs42l51_supply_names[i];
|
|
+
|
|
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs42l51->supplies),
|
|
+ cs42l51->supplies);
|
|
+ if (ret != 0) {
|
|
+ dev_err(dev, "Failed to request supplies: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42l51->supplies),
|
|
+ cs42l51->supplies);
|
|
+ if (ret != 0) {
|
|
+ dev_err(dev, "Failed to enable supplies: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ cs42l51->reset_gpio = devm_gpiod_get_optional(dev, "reset",
|
|
+ GPIOD_OUT_LOW);
|
|
+ if (IS_ERR(cs42l51->reset_gpio))
|
|
+ return PTR_ERR(cs42l51->reset_gpio);
|
|
+
|
|
+ if (cs42l51->reset_gpio) {
|
|
+ dev_dbg(dev, "Release reset gpio\n");
|
|
+ gpiod_set_value_cansleep(cs42l51->reset_gpio, 0);
|
|
+ mdelay(2);
|
|
+ }
|
|
|
|
/* Verify that we have a CS42L51 */
|
|
ret = regmap_read(regmap, CS42L51_CHIP_REV_ID, &val);
|
|
@@ -558,11 +737,29 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap)
|
|
|
|
ret = devm_snd_soc_register_component(dev,
|
|
&soc_component_device_cs42l51, &cs42l51_dai, 1);
|
|
+ if (ret < 0)
|
|
+ goto error;
|
|
+
|
|
+ return 0;
|
|
+
|
|
error:
|
|
+ regulator_bulk_disable(ARRAY_SIZE(cs42l51->supplies),
|
|
+ cs42l51->supplies);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cs42l51_probe);
|
|
|
|
+int cs42l51_remove(struct device *dev)
|
|
+{
|
|
+ struct cs42l51_private *cs42l51 = dev_get_drvdata(dev);
|
|
+
|
|
+ gpiod_set_value_cansleep(cs42l51->reset_gpio, 1);
|
|
+
|
|
+ return regulator_bulk_disable(ARRAY_SIZE(cs42l51->supplies),
|
|
+ cs42l51->supplies);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(cs42l51_remove);
|
|
+
|
|
const struct of_device_id cs42l51_of_match[] = {
|
|
{ .compatible = "cirrus,cs42l51", },
|
|
{ }
|
|
diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h
|
|
index 0ca8054..a086f7e 100644
|
|
--- a/sound/soc/codecs/cs42l51.h
|
|
+++ b/sound/soc/codecs/cs42l51.h
|
|
@@ -18,10 +18,14 @@
|
|
#ifndef _CS42L51_H
|
|
#define _CS42L51_H
|
|
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+
|
|
struct device;
|
|
|
|
extern const struct regmap_config cs42l51_regmap;
|
|
int cs42l51_probe(struct device *dev, struct regmap *regmap);
|
|
+int cs42l51_remove(struct device *dev);
|
|
extern const struct of_device_id cs42l51_of_match[];
|
|
|
|
#define CS42L51_CHIP_ID 0x1B
|
|
@@ -164,5 +168,22 @@ extern const struct of_device_id cs42l51_of_match[];
|
|
*/
|
|
#define CS42L51_LASTREG 0x20
|
|
#define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1)
|
|
+#define CS42L51_NUM_SUPPLIES 4
|
|
+
|
|
+enum master_slave_mode {
|
|
+ MODE_SLAVE,
|
|
+ MODE_SLAVE_AUTO,
|
|
+ MODE_MASTER,
|
|
+};
|
|
+
|
|
+struct cs42l51_private {
|
|
+ unsigned int mclk;
|
|
+ struct clk *mclk_handle;
|
|
+ unsigned int audio_mode; /* The mode (I2S or left-justified) */
|
|
+ enum master_slave_mode func;
|
|
+ struct regulator_bulk_data supplies[CS42L51_NUM_SUPPLIES];
|
|
+ struct gpio_desc *reset_gpio;
|
|
+ struct regmap *regmap;
|
|
+};
|
|
|
|
#endif
|
|
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
|
|
index 01acb8d..c902ac7 100644
|
|
--- a/sound/soc/codecs/wm8994.c
|
|
+++ b/sound/soc/codecs/wm8994.c
|
|
@@ -11,6 +11,7 @@
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
+#include <linux/clk.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/init.h>
|
|
@@ -844,6 +845,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);
|
|
@@ -1161,7 +1198,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))
|
|
@@ -1780,6 +1816,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),
|
|
@@ -2004,10 +2050,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" },
|
|
@@ -2381,11 +2427,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:
|
|
@@ -3995,6 +4056,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;
|
|
@@ -4278,6 +4340,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/Kconfig b/sound/soc/stm/Kconfig
|
|
index 9b26813..c66ffa7 100644
|
|
--- a/sound/soc/stm/Kconfig
|
|
+++ b/sound/soc/stm/Kconfig
|
|
@@ -3,6 +3,7 @@ menu "STMicroelectronics STM32 SOC audio support"
|
|
config SND_SOC_STM32_SAI
|
|
tristate "STM32 SAI interface (Serial Audio Interface) support"
|
|
depends on (ARCH_STM32 && OF) || COMPILE_TEST
|
|
+ depends on COMMON_CLK
|
|
depends on SND_SOC
|
|
select SND_SOC_GENERIC_DMAENGINE_PCM
|
|
select REGMAP_MMIO
|
|
diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c
|
|
index 24948b95..33de130 100644
|
|
--- a/sound/soc/stm/stm32_adfsdm.c
|
|
+++ b/sound/soc/stm/stm32_adfsdm.c
|
|
@@ -18,6 +18,7 @@
|
|
#include <linux/iio/adc/stm32-dfsdm-adc.h>
|
|
|
|
#include <sound/pcm.h>
|
|
+#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
|
|
#define STM32_ADFSDM_DRV_NAME "stm32-adfsdm"
|
|
@@ -44,8 +45,8 @@ struct stm32_adfsdm_priv {
|
|
|
|
static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
- SNDRV_PCM_INFO_PAUSE,
|
|
- .formats = SNDRV_PCM_FMTBIT_S32_LE,
|
|
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_PAUSE,
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
|
|
|
|
.rate_min = 8000,
|
|
.rate_max = 32000,
|
|
@@ -141,7 +142,8 @@ static const struct snd_soc_dai_driver stm32_adfsdm_dai = {
|
|
.capture = {
|
|
.channels_min = 1,
|
|
.channels_max = 1,
|
|
- .formats = SNDRV_PCM_FMTBIT_S32_LE,
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
+ SNDRV_PCM_FMTBIT_S32_LE,
|
|
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
|
|
SNDRV_PCM_RATE_32000),
|
|
},
|
|
@@ -152,30 +154,58 @@ static const struct snd_soc_component_driver stm32_adfsdm_dai_component = {
|
|
.name = "stm32_dfsdm_audio",
|
|
};
|
|
|
|
+void stm32_memcpy_32to16(void *dest, const void *src, size_t n)
|
|
+{
|
|
+ unsigned int i = 0;
|
|
+ u16 *d = (u16 *)dest, *s = (u16 *)src;
|
|
+
|
|
+ s++;
|
|
+ for (i = n >> 1; i > 0; i--) {
|
|
+ *d++ = *s++;
|
|
+ s++;
|
|
+ }
|
|
+}
|
|
+
|
|
static int stm32_afsdm_pcm_cb(const void *data, size_t size, void *private)
|
|
{
|
|
struct stm32_adfsdm_priv *priv = private;
|
|
struct snd_soc_pcm_runtime *rtd = priv->substream->private_data;
|
|
u8 *pcm_buff = priv->pcm_buff;
|
|
u8 *src_buff = (u8 *)data;
|
|
- unsigned int buff_size = snd_pcm_lib_buffer_bytes(priv->substream);
|
|
- unsigned int period_size = snd_pcm_lib_period_bytes(priv->substream);
|
|
unsigned int old_pos = priv->pos;
|
|
- unsigned int cur_size = size;
|
|
+ size_t buff_size = snd_pcm_lib_buffer_bytes(priv->substream);
|
|
+ size_t period_size = snd_pcm_lib_period_bytes(priv->substream);
|
|
+ size_t cur_size, src_size = size;
|
|
+ snd_pcm_format_t format = priv->substream->runtime->format;
|
|
+
|
|
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
|
|
+ src_size >>= 1;
|
|
+ cur_size = src_size;
|
|
|
|
dev_dbg(rtd->dev, "%s: buff_add :%pK, pos = %d, size = %zu\n",
|
|
- __func__, &pcm_buff[priv->pos], priv->pos, size);
|
|
+ __func__, &pcm_buff[priv->pos], priv->pos, src_size);
|
|
|
|
- if ((priv->pos + size) > buff_size) {
|
|
- memcpy(&pcm_buff[priv->pos], src_buff, buff_size - priv->pos);
|
|
+ if ((priv->pos + src_size) > buff_size) {
|
|
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
|
|
+ stm32_memcpy_32to16(&pcm_buff[priv->pos], src_buff,
|
|
+ buff_size - priv->pos);
|
|
+ else
|
|
+ memcpy(&pcm_buff[priv->pos], src_buff,
|
|
+ buff_size - priv->pos);
|
|
cur_size -= buff_size - priv->pos;
|
|
priv->pos = 0;
|
|
}
|
|
|
|
- memcpy(&pcm_buff[priv->pos], &src_buff[size - cur_size], cur_size);
|
|
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
|
|
+ stm32_memcpy_32to16(&pcm_buff[priv->pos],
|
|
+ &src_buff[src_size - cur_size], cur_size);
|
|
+ else
|
|
+ memcpy(&pcm_buff[priv->pos], &src_buff[src_size - cur_size],
|
|
+ cur_size);
|
|
+
|
|
priv->pos = (priv->pos + cur_size) % buff_size;
|
|
|
|
- if (cur_size != size || (old_pos && (old_pos % period_size < size)))
|
|
+ if (cur_size != src_size || (old_pos && (old_pos % period_size < size)))
|
|
snd_pcm_period_elapsed(priv->substream);
|
|
|
|
return 0;
|
|
diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c
|
|
index aa2b119..490352c 100644
|
|
--- a/sound/soc/stm/stm32_i2s.c
|
|
+++ b/sound/soc/stm/stm32_i2s.c
|
|
@@ -16,6 +16,7 @@
|
|
* details.
|
|
*/
|
|
|
|
+#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
@@ -37,6 +38,10 @@
|
|
#define STM32_I2S_TXDR_REG 0X20
|
|
#define STM32_I2S_RXDR_REG 0x30
|
|
#define STM32_I2S_CGFR_REG 0X50
|
|
+#define STM32_I2S_HWCFGR_REG 0x3F0
|
|
+#define STM32_I2S_VERR_REG 0x3F4
|
|
+#define STM32_I2S_IPIDR_REG 0x3F8
|
|
+#define STM32_I2S_SIDR_REG 0x3FC
|
|
|
|
/* Bit definition for SPI2S_CR1 register */
|
|
#define I2S_CR1_SPE BIT(0)
|
|
@@ -143,6 +148,23 @@
|
|
#define I2S_CGFR_ODD BIT(I2S_CGFR_ODD_SHIFT)
|
|
#define I2S_CGFR_MCKOE BIT(25)
|
|
|
|
+/* Registers below apply to I2S version 1.1 and more */
|
|
+
|
|
+/* Bit definition for SPI_HWCFGR register */
|
|
+#define I2S_HWCFGR_I2S_SUPPORT_MASK GENMASK(15, 12)
|
|
+
|
|
+/* Bit definition for SPI_VERR register */
|
|
+#define I2S_VERR_MIN_MASK GENMASK(3, 0)
|
|
+#define I2S_VERR_MAJ_MASK GENMASK(7, 4)
|
|
+
|
|
+/* Bit definition for SPI_IPIDR register */
|
|
+#define I2S_IPIDR_ID_MASK GENMASK(31, 0)
|
|
+
|
|
+/* Bit definition for SPI_SIDR register */
|
|
+#define I2S_SIDR_ID_MASK GENMASK(31, 0)
|
|
+
|
|
+#define I2S_IPIDR_NUMBER 0x00130022
|
|
+
|
|
enum i2s_master_mode {
|
|
I2S_MS_NOT_SET,
|
|
I2S_MS_MASTER,
|
|
@@ -179,7 +201,6 @@ enum i2s_datlen {
|
|
I2S_I2SMOD_DATLEN_32,
|
|
};
|
|
|
|
-#define STM32_I2S_DAI_NAME_SIZE 20
|
|
#define STM32_I2S_FIFO_SIZE 16
|
|
|
|
#define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER)
|
|
@@ -200,7 +221,7 @@ enum i2s_datlen {
|
|
* @base: mmio register base virtual address
|
|
* @phys_addr: I2S registers physical base address
|
|
* @lock_fd: lock to manage race conditions in full duplex mode
|
|
- * @dais_name: DAI name
|
|
+ * @irq_lock_t: prevent race condition with IRQ
|
|
* @mclk_rate: master clock frequency (Hz)
|
|
* @fmt: DAI protocol
|
|
* @refcount: keep count of opened streams on I2S
|
|
@@ -221,7 +242,7 @@ struct stm32_i2s_data {
|
|
void __iomem *base;
|
|
dma_addr_t phys_addr;
|
|
spinlock_t lock_fd; /* Manage race conditions for full duplex */
|
|
- char dais_name[STM32_I2S_DAI_NAME_SIZE];
|
|
+ spinlock_t irq_lock; /* used to prevent race condition with IRQ */
|
|
unsigned int mclk_rate;
|
|
unsigned int fmt;
|
|
int refcount;
|
|
@@ -232,8 +253,8 @@ 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;
|
|
+ u32 sr, ier;
|
|
int err = 0;
|
|
|
|
regmap_read(i2s->regmap, STM32_I2S_SR_REG, &sr);
|
|
@@ -262,8 +283,10 @@ static irqreturn_t stm32_i2s_isr(int irq, void *devid)
|
|
if (flags & I2S_SR_TIFRE)
|
|
dev_dbg(&pdev->dev, "Frame error\n");
|
|
|
|
- if (err)
|
|
+ spin_lock(&i2s->irq_lock);
|
|
+ if (err && i2s->substream)
|
|
snd_pcm_stop_xrun(i2s->substream);
|
|
+ spin_unlock(&i2s->irq_lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
@@ -276,9 +299,12 @@ static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg)
|
|
case STM32_I2S_CFG2_REG:
|
|
case STM32_I2S_IER_REG:
|
|
case STM32_I2S_SR_REG:
|
|
- case STM32_I2S_TXDR_REG:
|
|
case STM32_I2S_RXDR_REG:
|
|
case STM32_I2S_CGFR_REG:
|
|
+ case STM32_I2S_HWCFGR_REG:
|
|
+ case STM32_I2S_VERR_REG:
|
|
+ case STM32_I2S_IPIDR_REG:
|
|
+ case STM32_I2S_SIDR_REG:
|
|
return true;
|
|
default:
|
|
return false;
|
|
@@ -288,7 +314,7 @@ static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg)
|
|
static bool stm32_i2s_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
- case STM32_I2S_TXDR_REG:
|
|
+ case STM32_I2S_SR_REG:
|
|
case STM32_I2S_RXDR_REG:
|
|
return true;
|
|
default:
|
|
@@ -491,12 +517,6 @@ static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai,
|
|
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;
|
|
- }
|
|
-
|
|
switch (format) {
|
|
case 16:
|
|
cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_16);
|
|
@@ -539,12 +559,22 @@ 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);
|
|
+ unsigned long flags;
|
|
+ int ret;
|
|
|
|
+ spin_lock_irqsave(&i2s->irq_lock, flags);
|
|
i2s->substream = substream;
|
|
+ spin_unlock_irqrestore(&i2s->irq_lock, flags);
|
|
|
|
- spin_lock(&i2s->lock_fd);
|
|
- i2s->refcount++;
|
|
- spin_unlock(&i2s->lock_fd);
|
|
+ if ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A)
|
|
+ snd_pcm_hw_constraint_single(substream->runtime,
|
|
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
|
|
+
|
|
+ ret = clk_prepare_enable(i2s->i2sclk);
|
|
+ if (ret < 0) {
|
|
+ dev_err(cpu_dai->dev, "Failed to enable clock: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
|
|
return regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG,
|
|
I2S_IFCR_MASK, I2S_IFCR_MASK);
|
|
@@ -582,7 +612,8 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
/* Enable i2s */
|
|
- dev_dbg(cpu_dai->dev, "start I2S\n");
|
|
+ dev_dbg(cpu_dai->dev, "start I2S %s\n",
|
|
+ playback_flg ? "playback" : "capture");
|
|
|
|
cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN;
|
|
regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG,
|
|
@@ -595,8 +626,8 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
return ret;
|
|
}
|
|
|
|
- ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG,
|
|
- I2S_CR1_CSTART, I2S_CR1_CSTART);
|
|
+ ret = regmap_write_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);
|
|
return ret;
|
|
@@ -605,18 +636,19 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG,
|
|
I2S_IFCR_MASK, I2S_IFCR_MASK);
|
|
|
|
+ spin_lock(&i2s->lock_fd);
|
|
+ i2s->refcount++;
|
|
if (playback_flg) {
|
|
ier = I2S_IER_UDRIE;
|
|
} else {
|
|
ier = I2S_IER_OVRIE;
|
|
|
|
- spin_lock(&i2s->lock_fd);
|
|
- if (i2s->refcount == 1)
|
|
- /* dummy write to trigger capture */
|
|
+ if (STM32_I2S_IS_MASTER(i2s) && (i2s->refcount == 1))
|
|
+ /* dummy write to gate bus clocks */
|
|
regmap_write(i2s->regmap,
|
|
STM32_I2S_TXDR_REG, 0);
|
|
- spin_unlock(&i2s->lock_fd);
|
|
}
|
|
+ spin_unlock(&i2s->lock_fd);
|
|
|
|
if (STM32_I2S_IS_SLAVE(i2s))
|
|
ier |= I2S_IER_TIFREIE;
|
|
@@ -626,6 +658,9 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
+ dev_dbg(cpu_dai->dev, "stop I2S %s\n",
|
|
+ playback_flg ? "playback" : "capture");
|
|
+
|
|
if (playback_flg)
|
|
regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG,
|
|
I2S_IER_UDRIE,
|
|
@@ -641,16 +676,15 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
spin_unlock(&i2s->lock_fd);
|
|
break;
|
|
}
|
|
- spin_unlock(&i2s->lock_fd);
|
|
-
|
|
- 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);
|
|
+ spin_unlock(&i2s->lock_fd);
|
|
return ret;
|
|
}
|
|
+ spin_unlock(&i2s->lock_fd);
|
|
|
|
cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN;
|
|
regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG,
|
|
@@ -667,11 +701,16 @@ 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;
|
|
+ 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);
|
|
+ i2s->substream = NULL;
|
|
+ spin_unlock_irqrestore(&i2s->irq_lock, flags);
|
|
}
|
|
|
|
static int stm32_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
|
|
@@ -697,11 +736,13 @@ 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,
|
|
+ .max_register = STM32_I2S_SIDR_REG,
|
|
.readable_reg = stm32_i2s_readable_reg,
|
|
.volatile_reg = stm32_i2s_volatile_reg,
|
|
.writeable_reg = stm32_i2s_writeable_reg,
|
|
+ .num_reg_defaults_raw = STM32_I2S_SIDR_REG / sizeof(u32) + 1,
|
|
.fast_io = true,
|
|
+ .cache_type = REGCACHE_FLAT,
|
|
};
|
|
|
|
static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = {
|
|
@@ -716,7 +757,8 @@ static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = {
|
|
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,
|
|
+ .period_bytes_min = 1024, /* 5ms at 48kHz */
|
|
+ .period_bytes_max = PAGE_SIZE,
|
|
.periods_min = 2,
|
|
.periods_max = 8,
|
|
};
|
|
@@ -752,12 +794,8 @@ static int stm32_i2s_dais_init(struct platform_device *pdev,
|
|
if (!dai_ptr)
|
|
return -ENOMEM;
|
|
|
|
- snprintf(i2s->dais_name, STM32_I2S_DAI_NAME_SIZE,
|
|
- "%s", dev_name(&pdev->dev));
|
|
-
|
|
dai_ptr->probe = stm32_i2s_dai_probe;
|
|
dai_ptr->ops = &stm32_i2s_pcm_dai_ops;
|
|
- dai_ptr->name = i2s->dais_name;
|
|
dai_ptr->id = 1;
|
|
stm32_i2s_dai_init(&dai_ptr->playback, "playback");
|
|
stm32_i2s_dai_init(&dai_ptr->capture, "capture");
|
|
@@ -827,8 +865,9 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev,
|
|
/* 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;
|
|
+ if (irq != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
|
|
+ return irq;
|
|
}
|
|
|
|
ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, IRQF_ONESHOT,
|
|
@@ -852,6 +891,7 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev,
|
|
static int stm32_i2s_probe(struct platform_device *pdev)
|
|
{
|
|
struct stm32_i2s_data *i2s;
|
|
+ u32 val;
|
|
int ret;
|
|
|
|
i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
|
|
@@ -865,76 +905,94 @@ static int stm32_i2s_probe(struct platform_device *pdev)
|
|
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_dais_init(pdev, i2s);
|
|
if (ret)
|
|
return ret;
|
|
|
|
- i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->base,
|
|
- i2s->regmap_conf);
|
|
+ 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");
|
|
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, 1);
|
|
if (ret)
|
|
- goto err_clocks_disable;
|
|
+ return ret;
|
|
|
|
ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
|
|
&stm32_i2s_pcm_config, 0);
|
|
if (ret)
|
|
- goto err_clocks_disable;
|
|
+ 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)
|
|
- goto err_clocks_disable;
|
|
+ return ret;
|
|
|
|
- return ret;
|
|
+ ret = regmap_read(i2s->regmap, STM32_I2S_IPIDR_REG, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
|
|
-err_clocks_disable:
|
|
- clk_disable_unprepare(i2s->i2sclk);
|
|
-err_pclk_disable:
|
|
- clk_disable_unprepare(i2s->pclk);
|
|
+ if (val == I2S_IPIDR_NUMBER) {
|
|
+ ret = regmap_read(i2s->regmap, STM32_I2S_HWCFGR_REG, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (!FIELD_GET(I2S_HWCFGR_I2S_SUPPORT_MASK, val)) {
|
|
+ dev_err(&pdev->dev,
|
|
+ "Device does not support i2s mode\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = regmap_read(i2s->regmap, STM32_I2S_VERR_REG, &val);
|
|
+
|
|
+ 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;
|
|
}
|
|
|
|
-static int stm32_i2s_remove(struct platform_device *pdev)
|
|
+MODULE_DEVICE_TABLE(of, stm32_i2s_ids);
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int stm32_i2s_suspend(struct device *dev)
|
|
{
|
|
- struct stm32_i2s_data *i2s = platform_get_drvdata(pdev);
|
|
+ struct stm32_i2s_data *i2s = dev_get_drvdata(dev);
|
|
|
|
- clk_disable_unprepare(i2s->i2sclk);
|
|
- clk_disable_unprepare(i2s->pclk);
|
|
+ regcache_cache_only(i2s->regmap, true);
|
|
+ regcache_mark_dirty(i2s->regmap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
-MODULE_DEVICE_TABLE(of, stm32_i2s_ids);
|
|
+static int stm32_i2s_resume(struct device *dev)
|
|
+{
|
|
+ struct stm32_i2s_data *i2s = dev_get_drvdata(dev);
|
|
+
|
|
+ regcache_cache_only(i2s->regmap, false);
|
|
+ return regcache_sync(i2s->regmap);
|
|
+}
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+static const struct dev_pm_ops stm32_i2s_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(stm32_i2s_suspend, stm32_i2s_resume)
|
|
+};
|
|
|
|
static struct platform_driver stm32_i2s_driver = {
|
|
.driver = {
|
|
.name = "st,stm32-i2s",
|
|
.of_match_table = stm32_i2s_ids,
|
|
+ .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 540c4a0..d6763a1 100644
|
|
--- a/sound/soc/stm/stm32_sai.c
|
|
+++ b/sound/soc/stm/stm32_sai.c
|
|
@@ -21,6 +21,8 @@
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_platform.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/pm_runtime.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include <sound/dmaengine_pcm.h>
|
|
@@ -29,13 +31,20 @@
|
|
#include "stm32_sai.h"
|
|
|
|
static const struct stm32_sai_conf stm32_sai_conf_f4 = {
|
|
- .version = SAI_STM32F4,
|
|
- .has_spdif = false,
|
|
+ .version = STM_SAI_STM32F4,
|
|
+ .fifo_size = 8,
|
|
+ .has_spdif_pdm = false,
|
|
};
|
|
|
|
+/*
|
|
+ * Default settings for stm32 H7 socs and next.
|
|
+ * These default settings will be overridden if the soc provides
|
|
+ * support of hardware configuration registers.
|
|
+ */
|
|
static const struct stm32_sai_conf stm32_sai_conf_h7 = {
|
|
- .version = SAI_STM32H7,
|
|
- .has_spdif = true,
|
|
+ .version = STM_SAI_STM32H7,
|
|
+ .fifo_size = 8,
|
|
+ .has_spdif_pdm = true,
|
|
};
|
|
|
|
static const struct of_device_id stm32_sai_ids[] = {
|
|
@@ -44,20 +53,39 @@ static const struct of_device_id stm32_sai_ids[] = {
|
|
{}
|
|
};
|
|
|
|
-static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci)
|
|
+static int stm32_sai_pclk_disable(struct device *dev)
|
|
{
|
|
+ struct stm32_sai_data *sai = dev_get_drvdata(dev);
|
|
+
|
|
+ clk_disable_unprepare(sai->pclk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_sai_pclk_enable(struct device *dev)
|
|
+{
|
|
+ struct stm32_sai_data *sai = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
- /* Enable peripheral clock to allow GCR register access */
|
|
ret = clk_prepare_enable(sai->pclk);
|
|
- if (ret) {
|
|
+ if (ret)
|
|
dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* Enable peripheral clock to allow GCR register access */
|
|
+ ret = stm32_sai_pclk_enable(&sai->pdev->dev);
|
|
+ if (ret)
|
|
return ret;
|
|
- }
|
|
|
|
writel_relaxed(FIELD_PREP(SAI_GCR_SYNCIN_MASK, (synci - 1)), sai->base);
|
|
|
|
- clk_disable_unprepare(sai->pclk);
|
|
+ stm32_sai_pclk_disable(&sai->pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
@@ -68,11 +96,9 @@ static int stm32_sai_sync_conf_provider(struct stm32_sai_data *sai, int synco)
|
|
int ret;
|
|
|
|
/* Enable peripheral clock to allow GCR register access */
|
|
- ret = clk_prepare_enable(sai->pclk);
|
|
- if (ret) {
|
|
- dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret);
|
|
+ ret = stm32_sai_pclk_enable(&sai->pdev->dev);
|
|
+ if (ret)
|
|
return ret;
|
|
- }
|
|
|
|
dev_dbg(&sai->pdev->dev, "Set %s%s as synchro provider\n",
|
|
sai->pdev->dev.of_node->name,
|
|
@@ -83,13 +109,13 @@ static int stm32_sai_sync_conf_provider(struct stm32_sai_data *sai, int synco)
|
|
dev_err(&sai->pdev->dev, "%s%s already set as sync provider\n",
|
|
sai->pdev->dev.of_node->name,
|
|
prev_synco == STM_SAI_SYNC_OUT_A ? "A" : "B");
|
|
- clk_disable_unprepare(sai->pclk);
|
|
+ stm32_sai_pclk_disable(&sai->pdev->dev);
|
|
return -EINVAL;
|
|
}
|
|
|
|
writel_relaxed(FIELD_PREP(SAI_GCR_SYNCOUT_MASK, synco), sai->base);
|
|
|
|
- clk_disable_unprepare(sai->pclk);
|
|
+ stm32_sai_pclk_disable(&sai->pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
@@ -113,19 +139,20 @@ static int stm32_sai_set_sync(struct stm32_sai_data *sai_client,
|
|
dev_err(&sai_client->pdev->dev,
|
|
"SAI sync provider data not found\n");
|
|
ret = -EINVAL;
|
|
- goto out_put_dev;
|
|
+ goto error;
|
|
}
|
|
|
|
/* Configure sync client */
|
|
ret = stm32_sai_sync_conf_client(sai_client, synci);
|
|
if (ret < 0)
|
|
- goto out_put_dev;
|
|
+ goto error;
|
|
|
|
/* Configure sync provider */
|
|
ret = stm32_sai_sync_conf_provider(sai_provider, synco);
|
|
|
|
-out_put_dev:
|
|
+error:
|
|
put_device(&pdev->dev);
|
|
+ of_node_put(np_provider);
|
|
return ret;
|
|
}
|
|
|
|
@@ -135,6 +162,8 @@ static int stm32_sai_probe(struct platform_device *pdev)
|
|
struct reset_control *rst;
|
|
struct resource *res;
|
|
const struct of_device_id *of_id;
|
|
+ u32 val;
|
|
+ int ret;
|
|
|
|
sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
|
|
if (!sai)
|
|
@@ -147,7 +176,8 @@ static int stm32_sai_probe(struct platform_device *pdev)
|
|
|
|
of_id = of_match_device(stm32_sai_ids, &pdev->dev);
|
|
if (of_id)
|
|
- sai->conf = (struct stm32_sai_conf *)of_id->data;
|
|
+ memcpy(&sai->conf, (const struct stm32_sai_conf *)of_id->data,
|
|
+ sizeof(struct stm32_sai_conf));
|
|
else
|
|
return -EINVAL;
|
|
|
|
@@ -186,6 +216,30 @@ static int stm32_sai_probe(struct platform_device *pdev)
|
|
reset_control_deassert(rst);
|
|
}
|
|
|
|
+ /* Enable peripheral clock to allow register access */
|
|
+ ret = clk_prepare_enable(sai->pclk);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ val = FIELD_GET(SAI_IDR_ID_MASK,
|
|
+ readl_relaxed(sai->base + STM_SAI_IDR));
|
|
+ if (val == SAI_IPIDR_NUMBER) {
|
|
+ val = readl_relaxed(sai->base + STM_SAI_HWCFGR);
|
|
+ sai->conf.fifo_size = FIELD_GET(SAI_HWCFGR_FIFO_SIZE, val);
|
|
+ sai->conf.has_spdif_pdm = !!FIELD_GET(SAI_HWCFGR_SPDIF_PDM,
|
|
+ val);
|
|
+
|
|
+ val = readl_relaxed(sai->base + STM_SAI_VERR);
|
|
+ sai->conf.version = val;
|
|
+
|
|
+ dev_dbg(&pdev->dev, "SAI version: %lu.%lu registered\n",
|
|
+ FIELD_GET(SAI_VERR_MAJ_MASK, val),
|
|
+ FIELD_GET(SAI_VERR_MIN_MASK, val));
|
|
+ }
|
|
+ clk_disable_unprepare(sai->pclk);
|
|
+
|
|
sai->pdev = pdev;
|
|
sai->set_sync = &stm32_sai_set_sync;
|
|
platform_set_drvdata(pdev, sai);
|
|
@@ -193,12 +247,54 @@ static int stm32_sai_probe(struct platform_device *pdev)
|
|
return devm_of_platform_populate(&pdev->dev);
|
|
}
|
|
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+/*
|
|
+ * When pins are shared by two sai sub instances, pins have to be defined
|
|
+ * in sai parent node. In this case, pins state is not managed by alsa fw.
|
|
+ * These pins are managed in suspend/resume callbacks.
|
|
+ */
|
|
+static int stm32_sai_suspend(struct device *dev)
|
|
+{
|
|
+ struct stm32_sai_data *sai = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ ret = stm32_sai_pclk_enable(dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ sai->gcr = readl_relaxed(sai->base);
|
|
+ stm32_sai_pclk_disable(dev);
|
|
+
|
|
+ return pinctrl_pm_select_sleep_state(dev);
|
|
+}
|
|
+
|
|
+static int stm32_sai_resume(struct device *dev)
|
|
+{
|
|
+ struct stm32_sai_data *sai = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ ret = stm32_sai_pclk_enable(dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ writel_relaxed(sai->gcr, sai->base);
|
|
+ stm32_sai_pclk_disable(dev);
|
|
+
|
|
+ return pinctrl_pm_select_default_state(dev);
|
|
+}
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+static const struct dev_pm_ops stm32_sai_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(stm32_sai_suspend, stm32_sai_resume)
|
|
+};
|
|
+
|
|
MODULE_DEVICE_TABLE(of, stm32_sai_ids);
|
|
|
|
static struct platform_driver stm32_sai_driver = {
|
|
.driver = {
|
|
.name = "st,stm32-sai",
|
|
.of_match_table = stm32_sai_ids,
|
|
+ .pm = &stm32_sai_pm_ops,
|
|
},
|
|
.probe = stm32_sai_probe,
|
|
};
|
|
diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h
|
|
index f254221..158c73f 100644
|
|
--- a/sound/soc/stm/stm32_sai.h
|
|
+++ b/sound/soc/stm/stm32_sai.h
|
|
@@ -37,6 +37,12 @@
|
|
#define STM_SAI_PDMCR_REGX 0x40
|
|
#define STM_SAI_PDMLY_REGX 0x44
|
|
|
|
+/* Hardware configuration registers */
|
|
+#define STM_SAI_HWCFGR 0x3F0
|
|
+#define STM_SAI_VERR 0x3F4
|
|
+#define STM_SAI_IDR 0x3F8
|
|
+#define STM_SAI_SIDR 0x3FC
|
|
+
|
|
/******************** Bit definition for SAI_GCR register *******************/
|
|
#define SAI_GCR_SYNCIN_SHIFT 0
|
|
#define SAI_GCR_SYNCIN_WDTH 2
|
|
@@ -82,7 +88,7 @@
|
|
#define SAI_XCR1_NODIV BIT(SAI_XCR1_NODIV_SHIFT)
|
|
|
|
#define SAI_XCR1_MCKDIV_SHIFT 20
|
|
-#define SAI_XCR1_MCKDIV_WIDTH(x) (((x) == SAI_STM32F4) ? 4 : 6)
|
|
+#define SAI_XCR1_MCKDIV_WIDTH(x) (((x) == STM_SAI_STM32F4) ? 4 : 6)
|
|
#define SAI_XCR1_MCKDIV_MASK(x) GENMASK((SAI_XCR1_MCKDIV_SHIFT + (x) - 1),\
|
|
SAI_XCR1_MCKDIV_SHIFT)
|
|
#define SAI_XCR1_MCKDIV_SET(x) ((x) << SAI_XCR1_MCKDIV_SHIFT)
|
|
@@ -91,6 +97,9 @@
|
|
#define SAI_XCR1_OSR_SHIFT 26
|
|
#define SAI_XCR1_OSR BIT(SAI_XCR1_OSR_SHIFT)
|
|
|
|
+#define SAI_XCR1_MCKEN_SHIFT 27
|
|
+#define SAI_XCR1_MCKEN BIT(SAI_XCR1_MCKEN_SHIFT)
|
|
+
|
|
/******************* Bit definition for SAI_XCR2 register *******************/
|
|
#define SAI_XCR2_FTH_SHIFT 0
|
|
#define SAI_XCR2_FTH_MASK GENMASK(2, SAI_XCR2_FTH_SHIFT)
|
|
@@ -231,8 +240,33 @@
|
|
#define SAI_PDMDLY_4R_MASK GENMASK(30, SAI_PDMDLY_4R_SHIFT)
|
|
#define SAI_PDMDLY_4R_WIDTH 3
|
|
|
|
-#define STM_SAI_IS_F4(ip) ((ip)->conf->version == SAI_STM32F4)
|
|
-#define STM_SAI_IS_H7(ip) ((ip)->conf->version == SAI_STM32H7)
|
|
+/* Registers below apply to SAI version 2.1 and more */
|
|
+
|
|
+/* Bit definition for SAI_HWCFGR register */
|
|
+#define SAI_HWCFGR_FIFO_SIZE GENMASK(7, 0)
|
|
+#define SAI_HWCFGR_SPDIF_PDM GENMASK(11, 8)
|
|
+#define SAI_HWCFGR_REGOUT GENMASK(19, 12)
|
|
+
|
|
+/* Bit definition for SAI_VERR register */
|
|
+#define SAI_VERR_MIN_MASK GENMASK(3, 0)
|
|
+#define SAI_VERR_MAJ_MASK GENMASK(7, 4)
|
|
+
|
|
+/* Bit definition for SAI_IDR register */
|
|
+#define SAI_IDR_ID_MASK GENMASK(31, 0)
|
|
+
|
|
+/* Bit definition for SAI_SIDR register */
|
|
+#define SAI_SIDR_ID_MASK GENMASK(31, 0)
|
|
+
|
|
+#define SAI_IPIDR_NUMBER 0x00130031
|
|
+
|
|
+/* SAI version numbers are 1.x for F4. Major version number set to 1 for F4 */
|
|
+#define STM_SAI_STM32F4 BIT(4)
|
|
+/* Dummy version number for H7 socs and next */
|
|
+#define STM_SAI_STM32H7 0x0
|
|
+
|
|
+#define STM_SAI_IS_F4(ip) ((ip)->conf.version == STM_SAI_STM32F4)
|
|
+#define STM_SAI_HAS_SPDIF_PDM(ip)\
|
|
+ ((ip)->pdata->conf.has_spdif_pdm)
|
|
|
|
enum stm32_sai_syncout {
|
|
STM_SAI_SYNC_OUT_NONE,
|
|
@@ -240,19 +274,16 @@ enum stm32_sai_syncout {
|
|
STM_SAI_SYNC_OUT_B,
|
|
};
|
|
|
|
-enum stm32_sai_version {
|
|
- SAI_STM32F4,
|
|
- SAI_STM32H7
|
|
-};
|
|
-
|
|
/**
|
|
* struct stm32_sai_conf - SAI configuration
|
|
* @version: SAI version
|
|
- * @has_spdif: SAI S/PDIF support flag
|
|
+ * @fifo_size: SAI fifo size as words number
|
|
+ * @has_spdif_pdm: SAI S/PDIF and PDM features support flag
|
|
*/
|
|
struct stm32_sai_conf {
|
|
- int version;
|
|
- bool has_spdif;
|
|
+ u32 version;
|
|
+ u32 fifo_size;
|
|
+ bool has_spdif_pdm;
|
|
};
|
|
|
|
/**
|
|
@@ -262,9 +293,10 @@ struct stm32_sai_conf {
|
|
* @pclk: SAI bus clock
|
|
* @clk_x8k: SAI parent clock for sampling frequencies multiple of 8kHz
|
|
* @clk_x11k: SAI parent clock for sampling frequencies multiple of 11kHz
|
|
- * @version: SOC version
|
|
+ * @conf: SAI hardware capabitilites
|
|
* @irq: SAI interrupt line
|
|
* @set_sync: pointer to synchro mode configuration callback
|
|
+ * @gcr: SAI Global Configuration Register
|
|
*/
|
|
struct stm32_sai_data {
|
|
struct platform_device *pdev;
|
|
@@ -272,8 +304,9 @@ struct stm32_sai_data {
|
|
struct clk *pclk;
|
|
struct clk *clk_x8k;
|
|
struct clk *clk_x11k;
|
|
- struct stm32_sai_conf *conf;
|
|
+ struct stm32_sai_conf conf;
|
|
int irq;
|
|
int (*set_sync)(struct stm32_sai_data *sai,
|
|
struct device_node *np_provider, int synco, int synci);
|
|
+ u32 gcr;
|
|
};
|
|
diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c
|
|
index 6c2e69e..8225665 100644
|
|
--- a/sound/soc/stm/stm32_sai_sub.c
|
|
+++ b/sound/soc/stm/stm32_sai_sub.c
|
|
@@ -17,6 +17,7 @@
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
+#include <linux/clk-provider.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_irq.h>
|
|
@@ -44,7 +45,6 @@
|
|
#define SAI_DATASIZE_24 0x6
|
|
#define SAI_DATASIZE_32 0x7
|
|
|
|
-#define STM_SAI_FIFO_SIZE 8
|
|
#define STM_SAI_DAI_NAME_SIZE 15
|
|
|
|
#define STM_SAI_IS_PLAYBACK(ip) ((ip)->dir == SNDRV_PCM_STREAM_PLAYBACK)
|
|
@@ -62,12 +62,15 @@
|
|
#define SAI_SYNC_EXTERNAL 0x2
|
|
|
|
#define STM_SAI_PROTOCOL_IS_SPDIF(ip) ((ip)->spdif)
|
|
-#define STM_SAI_HAS_SPDIF(x) ((x)->pdata->conf->has_spdif)
|
|
+#define STM_SAI_HAS_SPDIF(x) ((x)->pdata->conf.has_spdif_pdm)
|
|
+#define STM_SAI_HAS_PDM(x) ((x)->pdata->conf.has_spdif_pdm)
|
|
#define STM_SAI_HAS_EXT_SYNC(x) (!STM_SAI_IS_F4(sai->pdata))
|
|
|
|
#define SAI_IEC60958_BLOCK_FRAMES 192
|
|
#define SAI_IEC60958_STATUS_BYTES 24
|
|
|
|
+#define SAI_MCLK_NAME_LEN 32
|
|
+
|
|
/**
|
|
* struct stm32_sai_sub_data - private data of SAI sub block (block A or B)
|
|
* @pdev: device data pointer
|
|
@@ -80,6 +83,7 @@
|
|
* @pdata: SAI block parent data pointer
|
|
* @np_sync_provider: synchronization provider node
|
|
* @sai_ck: kernel clock feeding the SAI clock generator
|
|
+ * @sai_mclk: master clock from SAI mclk provider
|
|
* @phys_addr: SAI registers physical base address
|
|
* @mclk_rate: SAI block master clock frequency (Hz). set at init
|
|
* @id: SAI sub block id corresponding to sub-block A or B
|
|
@@ -98,6 +102,7 @@
|
|
* @spdif_frm_cnt: S/PDIF playback frame counter
|
|
* @iec958: iec958 data
|
|
* @ctrl_lock: control lock
|
|
+ * @spinlock_t: prevent race condition with IRQ
|
|
*/
|
|
struct stm32_sai_sub_data {
|
|
struct platform_device *pdev;
|
|
@@ -110,6 +115,7 @@ struct stm32_sai_sub_data {
|
|
struct stm32_sai_data *pdata;
|
|
struct device_node *np_sync_provider;
|
|
struct clk *sai_ck;
|
|
+ struct clk *sai_mclk;
|
|
dma_addr_t phys_addr;
|
|
unsigned int mclk_rate;
|
|
unsigned int id;
|
|
@@ -128,6 +134,7 @@ struct stm32_sai_sub_data {
|
|
unsigned int spdif_frm_cnt;
|
|
struct snd_aes_iec958 iec958;
|
|
struct mutex ctrl_lock; /* protect resources accessed by controls */
|
|
+ spinlock_t irq_lock; /* used to prevent race condition with IRQ */
|
|
};
|
|
|
|
enum stm32_sai_fifo_th {
|
|
@@ -151,6 +158,10 @@ static bool stm32_sai_sub_readable_reg(struct device *dev, unsigned int reg)
|
|
case STM_SAI_DR_REGX:
|
|
case STM_SAI_PDMCR_REGX:
|
|
case STM_SAI_PDMLY_REGX:
|
|
+ case STM_SAI_HWCFGR:
|
|
+ case STM_SAI_VERR:
|
|
+ case STM_SAI_IDR:
|
|
+ case STM_SAI_SIDR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
@@ -161,6 +172,7 @@ static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case STM_SAI_DR_REGX:
|
|
+ case STM_SAI_SR_REGX:
|
|
return true;
|
|
default:
|
|
return false;
|
|
@@ -175,7 +187,6 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg)
|
|
case STM_SAI_FRCR_REGX:
|
|
case STM_SAI_SLOTR_REGX:
|
|
case STM_SAI_IMR_REGX:
|
|
- case STM_SAI_SR_REGX:
|
|
case STM_SAI_CLRFR_REGX:
|
|
case STM_SAI_DR_REGX:
|
|
case STM_SAI_PDMCR_REGX:
|
|
@@ -186,6 +197,56 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg)
|
|
}
|
|
}
|
|
|
|
+static int stm32_sai_sub_reg_up(struct stm32_sai_sub_data *sai,
|
|
+ unsigned int reg, unsigned int mask,
|
|
+ unsigned int val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(sai->pdata->pclk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = regmap_update_bits(sai->regmap, reg, mask, val);
|
|
+
|
|
+ clk_disable(sai->pdata->pclk);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int stm32_sai_sub_reg_wr(struct stm32_sai_sub_data *sai,
|
|
+ unsigned int reg, unsigned int mask,
|
|
+ unsigned int val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(sai->pdata->pclk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = regmap_write_bits(sai->regmap, reg, mask, val);
|
|
+
|
|
+ clk_disable(sai->pdata->pclk);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int stm32_sai_sub_reg_rd(struct stm32_sai_sub_data *sai,
|
|
+ unsigned int reg, unsigned int *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(sai->pdata->pclk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = regmap_read(sai->regmap, reg, val);
|
|
+
|
|
+ clk_disable(sai->pdata->pclk);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
static const struct regmap_config stm32_sai_sub_regmap_config_f4 = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
@@ -195,6 +256,7 @@ static const struct regmap_config stm32_sai_sub_regmap_config_f4 = {
|
|
.volatile_reg = stm32_sai_sub_volatile_reg,
|
|
.writeable_reg = stm32_sai_sub_writeable_reg,
|
|
.fast_io = true,
|
|
+ .cache_type = REGCACHE_FLAT,
|
|
};
|
|
|
|
static const struct regmap_config stm32_sai_sub_regmap_config_h7 = {
|
|
@@ -206,6 +268,7 @@ static const struct regmap_config stm32_sai_sub_regmap_config_h7 = {
|
|
.volatile_reg = stm32_sai_sub_volatile_reg,
|
|
.writeable_reg = stm32_sai_sub_writeable_reg,
|
|
.fast_io = true,
|
|
+ .cache_type = REGCACHE_FLAT,
|
|
};
|
|
|
|
static int snd_pcm_iec958_info(struct snd_kcontrol *kcontrol,
|
|
@@ -251,6 +314,194 @@ static const struct snd_kcontrol_new iec958_ctls = {
|
|
.put = snd_pcm_iec958_put,
|
|
};
|
|
|
|
+struct stm32_sai_mclk_data {
|
|
+ struct clk_hw hw;
|
|
+ unsigned long freq;
|
|
+ struct stm32_sai_sub_data *sai_data;
|
|
+};
|
|
+
|
|
+#define to_mclk_data(_hw) container_of(_hw, struct stm32_sai_mclk_data, hw)
|
|
+#define STM32_SAI_MAX_CLKS 1
|
|
+
|
|
+static int stm32_sai_get_clk_div(struct stm32_sai_sub_data *sai,
|
|
+ unsigned long input_rate,
|
|
+ unsigned long output_rate)
|
|
+{
|
|
+ int version = sai->pdata->conf.version;
|
|
+ int div;
|
|
+
|
|
+ div = DIV_ROUND_CLOSEST(input_rate, output_rate);
|
|
+ if (div > SAI_XCR1_MCKDIV_MAX(version)) {
|
|
+ dev_err(&sai->pdev->dev, "Divider %d out of range\n", div);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ dev_dbg(&sai->pdev->dev, "SAI divider %d\n", div);
|
|
+
|
|
+ if (input_rate % div)
|
|
+ dev_dbg(&sai->pdev->dev,
|
|
+ "Rate not accurate. requested (%ld), actual (%ld)\n",
|
|
+ output_rate, input_rate / div);
|
|
+
|
|
+ return div;
|
|
+}
|
|
+
|
|
+static int stm32_sai_set_clk_div(struct stm32_sai_sub_data *sai,
|
|
+ unsigned int div)
|
|
+{
|
|
+ int version = sai->pdata->conf.version;
|
|
+ int ret, cr1, mask;
|
|
+
|
|
+ if (div > SAI_XCR1_MCKDIV_MAX(version)) {
|
|
+ dev_err(&sai->pdev->dev, "Divider %d out of range\n", div);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ mask = SAI_XCR1_MCKDIV_MASK(SAI_XCR1_MCKDIV_WIDTH(version));
|
|
+ cr1 = SAI_XCR1_MCKDIV_SET(div);
|
|
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, mask, cr1);
|
|
+ if (ret < 0)
|
|
+ dev_err(&sai->pdev->dev, "Failed to update CR1 register\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int stm32_sai_set_parent_clock(struct stm32_sai_sub_data *sai,
|
|
+ unsigned int rate)
|
|
+{
|
|
+ struct platform_device *pdev = sai->pdev;
|
|
+ struct clk *parent_clk = sai->pdata->clk_x8k;
|
|
+ int ret;
|
|
+
|
|
+ if (!(rate % 11025))
|
|
+ parent_clk = sai->pdata->clk_x11k;
|
|
+
|
|
+ ret = clk_set_parent(sai->sai_ck, parent_clk);
|
|
+ if (ret)
|
|
+ dev_err(&pdev->dev, " Error %d setting sai_ck parent clock. %s",
|
|
+ ret, ret == -EBUSY ?
|
|
+ "Active stream rates conflict\n" : "\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static long stm32_sai_mclk_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
+ unsigned long *prate)
|
|
+{
|
|
+ struct stm32_sai_mclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_sai_sub_data *sai = mclk->sai_data;
|
|
+ int div;
|
|
+
|
|
+ div = stm32_sai_get_clk_div(sai, *prate, rate);
|
|
+ if (div < 0)
|
|
+ return div;
|
|
+
|
|
+ mclk->freq = *prate / div;
|
|
+
|
|
+ return mclk->freq;
|
|
+}
|
|
+
|
|
+static unsigned long stm32_sai_mclk_recalc_rate(struct clk_hw *hw,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct stm32_sai_mclk_data *mclk = to_mclk_data(hw);
|
|
+
|
|
+ return mclk->freq;
|
|
+}
|
|
+
|
|
+static int stm32_sai_mclk_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct stm32_sai_mclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_sai_sub_data *sai = mclk->sai_data;
|
|
+ int div, ret;
|
|
+
|
|
+ div = stm32_sai_get_clk_div(sai, parent_rate, rate);
|
|
+ if (div < 0)
|
|
+ return div;
|
|
+
|
|
+ ret = stm32_sai_set_clk_div(sai, div);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mclk->freq = rate;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_sai_mclk_enable(struct clk_hw *hw)
|
|
+{
|
|
+ struct stm32_sai_mclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_sai_sub_data *sai = mclk->sai_data;
|
|
+
|
|
+ dev_dbg(&sai->pdev->dev, "Enable master clock\n");
|
|
+
|
|
+ return stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
|
|
+ SAI_XCR1_MCKEN, SAI_XCR1_MCKEN);
|
|
+}
|
|
+
|
|
+static void stm32_sai_mclk_disable(struct clk_hw *hw)
|
|
+{
|
|
+ struct stm32_sai_mclk_data *mclk = to_mclk_data(hw);
|
|
+ struct stm32_sai_sub_data *sai = mclk->sai_data;
|
|
+
|
|
+ dev_dbg(&sai->pdev->dev, "Disable master clock\n");
|
|
+
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, SAI_XCR1_MCKEN, 0);
|
|
+}
|
|
+
|
|
+static const struct clk_ops mclk_ops = {
|
|
+ .enable = stm32_sai_mclk_enable,
|
|
+ .disable = stm32_sai_mclk_disable,
|
|
+ .recalc_rate = stm32_sai_mclk_recalc_rate,
|
|
+ .round_rate = stm32_sai_mclk_round_rate,
|
|
+ .set_rate = stm32_sai_mclk_set_rate,
|
|
+};
|
|
+
|
|
+static int stm32_sai_add_mclk_provider(struct stm32_sai_sub_data *sai)
|
|
+{
|
|
+ struct clk_hw *hw;
|
|
+ struct stm32_sai_mclk_data *mclk;
|
|
+ struct device *dev = &sai->pdev->dev;
|
|
+ const char *pname = __clk_get_name(sai->sai_ck);
|
|
+ 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),
|
|
+ SAI_MCLK_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 < (SAI_MCLK_NAME_LEN - 7))) {
|
|
+ *p++ = *s++;
|
|
+ i++;
|
|
+ }
|
|
+ STM_SAI_IS_SUB_A(sai) ? strcat(p, "a_mclk") : strcat(p, "b_mclk");
|
|
+
|
|
+ mclk->hw.init = CLK_HW_INIT(mclk_name, pname, &mclk_ops, 0);
|
|
+ mclk->sai_data = sai;
|
|
+ hw = &mclk->hw;
|
|
+
|
|
+ dev_dbg(dev, "Register master clock %s\n", mclk_name);
|
|
+ ret = devm_clk_hw_register(&sai->pdev->dev, hw);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "mclk register returned %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ sai->sai_mclk = hw->clk;
|
|
+
|
|
+ /* register mclk provider */
|
|
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
|
|
+}
|
|
+
|
|
static irqreturn_t stm32_sai_isr(int irq, void *devid)
|
|
{
|
|
struct stm32_sai_sub_data *sai = (struct stm32_sai_sub_data *)devid;
|
|
@@ -258,15 +509,15 @@ static irqreturn_t stm32_sai_isr(int irq, void *devid)
|
|
unsigned int sr, imr, flags;
|
|
snd_pcm_state_t status = SNDRV_PCM_STATE_RUNNING;
|
|
|
|
- regmap_read(sai->regmap, STM_SAI_IMR_REGX, &imr);
|
|
- regmap_read(sai->regmap, STM_SAI_SR_REGX, &sr);
|
|
+ stm32_sai_sub_reg_rd(sai, STM_SAI_IMR_REGX, &imr);
|
|
+ stm32_sai_sub_reg_rd(sai, STM_SAI_SR_REGX, &sr);
|
|
|
|
flags = sr & imr;
|
|
if (!flags)
|
|
return IRQ_NONE;
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK,
|
|
- SAI_XCLRFR_MASK);
|
|
+ stm32_sai_sub_reg_wr(sai, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK,
|
|
+ SAI_XCLRFR_MASK);
|
|
|
|
if (!sai->substream) {
|
|
dev_err(&pdev->dev, "Device stopped. Spurious IRQ 0x%x\n", sr);
|
|
@@ -300,8 +551,10 @@ static irqreturn_t stm32_sai_isr(int irq, void *devid)
|
|
status = SNDRV_PCM_STATE_XRUN;
|
|
}
|
|
|
|
- if (status != SNDRV_PCM_STATE_RUNNING)
|
|
+ spin_lock(&sai->irq_lock);
|
|
+ if (status != SNDRV_PCM_STATE_RUNNING && sai->substream)
|
|
snd_pcm_stop_xrun(sai->substream);
|
|
+ spin_unlock(&sai->irq_lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
@@ -312,15 +565,29 @@ static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai,
|
|
struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
|
int ret;
|
|
|
|
- if ((dir == SND_SOC_CLOCK_OUT) && sai->master) {
|
|
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
|
|
- SAI_XCR1_NODIV,
|
|
- (unsigned int)~SAI_XCR1_NODIV);
|
|
+ if (dir == SND_SOC_CLOCK_OUT && sai->sai_mclk) {
|
|
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
|
|
+ SAI_XCR1_NODIV,
|
|
+ (unsigned int)~SAI_XCR1_NODIV);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
- sai->mclk_rate = freq;
|
|
+ /* If master clock is used, set parent clock now */
|
|
+ ret = stm32_sai_set_parent_clock(sai, freq);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = clk_set_rate_exclusive(sai->sai_mclk, freq);
|
|
+ if (ret) {
|
|
+ dev_err(cpu_dai->dev,
|
|
+ ret == -EBUSY ?
|
|
+ "Active streams have incompatible rates" :
|
|
+ "Could not set mclk rate\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq);
|
|
+ sai->mclk_rate = freq;
|
|
}
|
|
|
|
return 0;
|
|
@@ -369,7 +636,7 @@ static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
|
|
|
|
slotr_mask |= SAI_XSLOTR_SLOTEN_MASK;
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, slotr_mask, slotr);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_SLOTR_REGX, slotr_mask, slotr);
|
|
|
|
sai->slot_width = slot_width;
|
|
sai->slots = slots;
|
|
@@ -451,7 +718,7 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
|
|
cr1_mask |= SAI_XCR1_CKSTR;
|
|
frcr_mask |= SAI_XFRCR_FSPOL;
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_FRCR_REGX, frcr_mask, frcr);
|
|
|
|
/* DAI clock master masks */
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
@@ -479,7 +746,7 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
|
|
cr1_mask |= SAI_XCR1_SLAVE;
|
|
|
|
conf_update:
|
|
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
|
|
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1);
|
|
if (ret < 0) {
|
|
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
|
|
return ret;
|
|
@@ -495,8 +762,11 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream,
|
|
{
|
|
struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
|
int imr, cr2, ret;
|
|
+ unsigned long flags;
|
|
|
|
+ spin_lock_irqsave(&sai->irq_lock, flags);
|
|
sai->substream = substream;
|
|
+ spin_unlock_irqrestore(&sai->irq_lock, flags);
|
|
|
|
if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
|
|
snd_pcm_hw_constraint_mask64(substream->runtime,
|
|
@@ -513,13 +783,12 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream,
|
|
}
|
|
|
|
/* Enable ITs */
|
|
-
|
|
- regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX,
|
|
- SAI_XCLRFR_MASK, SAI_XCLRFR_MASK);
|
|
+ stm32_sai_sub_reg_wr(sai, STM_SAI_CLRFR_REGX,
|
|
+ SAI_XCLRFR_MASK, SAI_XCLRFR_MASK);
|
|
|
|
imr = SAI_XIMR_OVRUDRIE;
|
|
if (STM_SAI_IS_CAPTURE(sai)) {
|
|
- regmap_read(sai->regmap, STM_SAI_CR2_REGX, &cr2);
|
|
+ stm32_sai_sub_reg_rd(sai, STM_SAI_CR2_REGX, &cr2);
|
|
if (cr2 & SAI_XCR2_MUTECNT_MASK)
|
|
imr |= SAI_XIMR_MUTEDETIE;
|
|
}
|
|
@@ -529,8 +798,8 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream,
|
|
else
|
|
imr |= SAI_XIMR_AFSDETIE | SAI_XIMR_LFSDETIE;
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX,
|
|
- SAI_XIMR_MASK, imr);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_IMR_REGX,
|
|
+ SAI_XIMR_MASK, imr);
|
|
|
|
return 0;
|
|
}
|
|
@@ -547,10 +816,10 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai,
|
|
* SAI fifo threshold is set to half fifo, to keep enough space
|
|
* for DMA incoming bursts.
|
|
*/
|
|
- regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX,
|
|
- SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK,
|
|
- SAI_XCR2_FFLUSH |
|
|
- SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF));
|
|
+ stm32_sai_sub_reg_wr(sai, STM_SAI_CR2_REGX,
|
|
+ SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK,
|
|
+ SAI_XCR2_FFLUSH |
|
|
+ SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF));
|
|
|
|
/* DS bits in CR1 not set for SPDIF (size forced to 24 bits).*/
|
|
if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
|
|
@@ -579,7 +848,7 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai,
|
|
if ((sai->slots == 2) && (params_channels(params) == 1))
|
|
cr1 |= SAI_XCR1_MONO;
|
|
|
|
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
|
|
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1);
|
|
if (ret < 0) {
|
|
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
|
|
return ret;
|
|
@@ -593,7 +862,7 @@ static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai)
|
|
struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
|
int slotr, slot_sz;
|
|
|
|
- regmap_read(sai->regmap, STM_SAI_SLOTR_REGX, &slotr);
|
|
+ stm32_sai_sub_reg_rd(sai, STM_SAI_SLOTR_REGX, &slotr);
|
|
|
|
/*
|
|
* If SLOTSZ is set to auto in SLOTR, align slot width on data size
|
|
@@ -615,16 +884,16 @@ static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai)
|
|
sai->slots = 2;
|
|
|
|
/* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/
|
|
- regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
|
|
- SAI_XSLOTR_NBSLOT_MASK,
|
|
- SAI_XSLOTR_NBSLOT_SET((sai->slots - 1)));
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_SLOTR_REGX,
|
|
+ SAI_XSLOTR_NBSLOT_MASK,
|
|
+ SAI_XSLOTR_NBSLOT_SET((sai->slots - 1)));
|
|
|
|
/* Set default slots mask if not already set from DT */
|
|
if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) {
|
|
sai->slot_mask = (1 << sai->slots) - 1;
|
|
- regmap_update_bits(sai->regmap,
|
|
- STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK,
|
|
- SAI_XSLOTR_SLOTEN_SET(sai->slot_mask));
|
|
+ stm32_sai_sub_reg_up(sai,
|
|
+ STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK,
|
|
+ SAI_XSLOTR_SLOTEN_SET(sai->slot_mask));
|
|
}
|
|
|
|
dev_dbg(cpu_dai->dev, "Slots %d, slot width %d\n",
|
|
@@ -654,14 +923,14 @@ static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai)
|
|
dev_dbg(cpu_dai->dev, "Frame length %d, frame active %d\n",
|
|
sai->fs_length, fs_active);
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_FRCR_REGX, frcr_mask, frcr);
|
|
|
|
if ((sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_LSB) {
|
|
offset = sai->slot_width - sai->data_size;
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX,
|
|
- SAI_XSLOTR_FBOFF_MASK,
|
|
- SAI_XSLOTR_FBOFF_SET(offset));
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_SLOTR_REGX,
|
|
+ SAI_XSLOTR_FBOFF_MASK,
|
|
+ SAI_XSLOTR_FBOFF_SET(offset));
|
|
}
|
|
}
|
|
|
|
@@ -722,31 +991,35 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
|
- int cr1, mask, div = 0;
|
|
- int sai_clk_rate, mclk_ratio, den, ret;
|
|
- int version = sai->pdata->conf->version;
|
|
+ int div = 0;
|
|
+ int sai_clk_rate, mclk_ratio, den;
|
|
unsigned int rate = params_rate(params);
|
|
+ int ret, cr1 = 0;
|
|
|
|
- if (!sai->mclk_rate) {
|
|
- dev_err(cpu_dai->dev, "Mclk rate is null\n");
|
|
- return -EINVAL;
|
|
+ if (!sai->sai_mclk) {
|
|
+ ret = stm32_sai_set_parent_clock(sai, rate);
|
|
+ if (ret)
|
|
+ return ret;
|
|
}
|
|
-
|
|
- if (!(rate % 11025))
|
|
- clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k);
|
|
- else
|
|
- clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k);
|
|
sai_clk_rate = clk_get_rate(sai->sai_ck);
|
|
|
|
if (STM_SAI_IS_F4(sai->pdata)) {
|
|
- /*
|
|
- * mclk_rate = 256 * fs
|
|
- * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate
|
|
- * MCKDIV = sai_ck / (2 * mclk_rate) otherwise
|
|
+ /* mclk on (NODIV=0)
|
|
+ * mclk_rate = 256 * fs
|
|
+ * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate
|
|
+ * MCKDIV = sai_ck / (2 * mclk_rate) otherwise
|
|
+ * mclk off (NODIV=1)
|
|
+ * MCKDIV ignored. sck = sai_ck
|
|
*/
|
|
- if (2 * sai_clk_rate >= 3 * sai->mclk_rate)
|
|
- div = DIV_ROUND_CLOSEST(sai_clk_rate,
|
|
- 2 * sai->mclk_rate);
|
|
+ if (!sai->mclk_rate)
|
|
+ return 0;
|
|
+
|
|
+ if (2 * sai_clk_rate >= 3 * sai->mclk_rate) {
|
|
+ div = stm32_sai_get_clk_div(sai, sai_clk_rate,
|
|
+ 2 * sai->mclk_rate);
|
|
+ if (div < 0)
|
|
+ return div;
|
|
+ }
|
|
} else {
|
|
/*
|
|
* TDM mode :
|
|
@@ -758,13 +1031,14 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai,
|
|
* Note: NOMCK/NODIV correspond to same bit.
|
|
*/
|
|
if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
|
|
- div = DIV_ROUND_CLOSEST(sai_clk_rate,
|
|
- (params_rate(params) * 128));
|
|
+ div = stm32_sai_get_clk_div(sai, sai_clk_rate,
|
|
+ rate * 128);
|
|
+ if (div < 0)
|
|
+ return div;
|
|
} else {
|
|
if (sai->mclk_rate) {
|
|
mclk_ratio = sai->mclk_rate / rate;
|
|
if (mclk_ratio == 512) {
|
|
- mask = SAI_XCR1_OSR;
|
|
cr1 = SAI_XCR1_OSR;
|
|
} else if (mclk_ratio != 256) {
|
|
dev_err(cpu_dai->dev,
|
|
@@ -772,31 +1046,27 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai,
|
|
mclk_ratio);
|
|
return -EINVAL;
|
|
}
|
|
- div = DIV_ROUND_CLOSEST(sai_clk_rate,
|
|
- sai->mclk_rate);
|
|
+
|
|
+ stm32_sai_sub_reg_up(sai,
|
|
+ STM_SAI_CR1_REGX,
|
|
+ SAI_XCR1_OSR, cr1);
|
|
+
|
|
+ div = stm32_sai_get_clk_div(sai, sai_clk_rate,
|
|
+ sai->mclk_rate);
|
|
+ if (div < 0)
|
|
+ return div;
|
|
} else {
|
|
/* mclk-fs not set, master clock not active */
|
|
den = sai->fs_length * params_rate(params);
|
|
- div = DIV_ROUND_CLOSEST(sai_clk_rate, den);
|
|
+ div = stm32_sai_get_clk_div(sai, sai_clk_rate,
|
|
+ den);
|
|
+ if (div < 0)
|
|
+ return div;
|
|
}
|
|
}
|
|
}
|
|
|
|
- if (div > SAI_XCR1_MCKDIV_MAX(version)) {
|
|
- dev_err(cpu_dai->dev, "Divider %d out of range\n", div);
|
|
- return -EINVAL;
|
|
- }
|
|
- dev_dbg(cpu_dai->dev, "SAI clock %d, divider %d\n", sai_clk_rate, div);
|
|
-
|
|
- mask = SAI_XCR1_MCKDIV_MASK(SAI_XCR1_MCKDIV_WIDTH(version));
|
|
- cr1 = SAI_XCR1_MCKDIV_SET(div);
|
|
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1);
|
|
- if (ret < 0) {
|
|
- dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
|
|
- return ret;
|
|
- }
|
|
-
|
|
- return 0;
|
|
+ return stm32_sai_set_clk_div(sai, div);
|
|
}
|
|
|
|
static int stm32_sai_hw_params(struct snd_pcm_substream *substream,
|
|
@@ -841,12 +1111,12 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n");
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
|
|
- SAI_XCR1_DMAEN, SAI_XCR1_DMAEN);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
|
|
+ SAI_XCR1_DMAEN, SAI_XCR1_DMAEN);
|
|
|
|
/* Enable SAI */
|
|
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
|
|
- SAI_XCR1_SAIEN, SAI_XCR1_SAIEN);
|
|
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
|
|
+ SAI_XCR1_SAIEN, SAI_XCR1_SAIEN);
|
|
if (ret < 0)
|
|
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
|
|
break;
|
|
@@ -855,16 +1125,16 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n");
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX,
|
|
- SAI_XIMR_MASK, 0);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_IMR_REGX,
|
|
+ SAI_XIMR_MASK, 0);
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
|
|
- SAI_XCR1_SAIEN,
|
|
- (unsigned int)~SAI_XCR1_SAIEN);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
|
|
+ SAI_XCR1_SAIEN,
|
|
+ (unsigned int)~SAI_XCR1_SAIEN);
|
|
|
|
- ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX,
|
|
- SAI_XCR1_DMAEN,
|
|
- (unsigned int)~SAI_XCR1_DMAEN);
|
|
+ ret = stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX,
|
|
+ SAI_XCR1_DMAEN,
|
|
+ (unsigned int)~SAI_XCR1_DMAEN);
|
|
if (ret < 0)
|
|
dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
|
|
|
|
@@ -882,14 +1152,24 @@ static void stm32_sai_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
|
+ unsigned long flags;
|
|
+
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0);
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0);
|
|
+ stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, SAI_XCR1_NODIV,
|
|
+ SAI_XCR1_NODIV);
|
|
|
|
- regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, SAI_XCR1_NODIV,
|
|
- SAI_XCR1_NODIV);
|
|
+ /* Release mclk rate only if rate was actually set */
|
|
+ if (sai->mclk_rate) {
|
|
+ clk_rate_exclusive_put(sai->sai_mclk);
|
|
+ sai->mclk_rate = 0;
|
|
+ }
|
|
|
|
clk_disable_unprepare(sai->sai_ck);
|
|
+
|
|
+ spin_lock_irqsave(&sai->irq_lock, flags);
|
|
sai->substream = NULL;
|
|
+ spin_unlock_irqrestore(&sai->irq_lock, flags);
|
|
}
|
|
|
|
static int stm32_sai_pcm_new(struct snd_soc_pcm_runtime *rtd,
|
|
@@ -910,7 +1190,9 @@ static int stm32_sai_pcm_new(struct snd_soc_pcm_runtime *rtd,
|
|
static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev);
|
|
- int cr1 = 0, cr1_mask;
|
|
+ int cr1 = 0, cr1_mask, ret;
|
|
+
|
|
+ sai->cpu_dai = cpu_dai;
|
|
|
|
sai->dma_params.addr = (dma_addr_t)(sai->phys_addr + STM_SAI_DR_REGX);
|
|
/*
|
|
@@ -919,6 +1201,8 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai)
|
|
* constraints).
|
|
*/
|
|
sai->dma_params.maxburst = 4;
|
|
+ if (sai->pdata->conf.fifo_size < 8)
|
|
+ sai->dma_params.maxburst = 1;
|
|
/* Buswidth will be set by framework at runtime */
|
|
sai->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED;
|
|
|
|
@@ -938,14 +1222,16 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai)
|
|
/* Configure synchronization */
|
|
if (sai->sync == SAI_SYNC_EXTERNAL) {
|
|
/* Configure synchro client and provider */
|
|
- sai->pdata->set_sync(sai->pdata, sai->np_sync_provider,
|
|
- sai->synco, sai->synci);
|
|
+ ret = sai->pdata->set_sync(sai->pdata, sai->np_sync_provider,
|
|
+ sai->synco, sai->synci);
|
|
+ if (ret)
|
|
+ return ret;
|
|
}
|
|
|
|
cr1_mask |= SAI_XCR1_SYNCEN_MASK;
|
|
cr1 |= SAI_XCR1_SYNCEN_SET(sai->sync);
|
|
|
|
- return regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
|
|
+ return stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1);
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = {
|
|
@@ -1099,11 +1385,16 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev,
|
|
|
|
sai->regmap_config = &stm32_sai_sub_regmap_config_f4;
|
|
/* Note: PDM registers not available for H7 sub-block B */
|
|
- if (STM_SAI_IS_H7(sai->pdata) && STM_SAI_IS_SUB_A(sai))
|
|
+ if (STM_SAI_HAS_PDM(sai) && STM_SAI_IS_SUB_A(sai))
|
|
sai->regmap_config = &stm32_sai_sub_regmap_config_h7;
|
|
|
|
- sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "sai_ck",
|
|
- base, sai->regmap_config);
|
|
+ /*
|
|
+ * Do not manage peripheral clock through regmap framework as this
|
|
+ * can lead to circular locking issue with sai master clock provider.
|
|
+ * Manage peripheral clock directly in driver instead.
|
|
+ */
|
|
+ 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");
|
|
return PTR_ERR(sai->regmap);
|
|
@@ -1201,6 +1492,27 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev,
|
|
return PTR_ERR(sai->sai_ck);
|
|
}
|
|
|
|
+ ret = clk_prepare(sai->pdata->pclk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ if (STM_SAI_IS_F4(sai->pdata))
|
|
+ return 0;
|
|
+
|
|
+ /* Register mclk provider if requested */
|
|
+ if (of_find_property(np, "#clock-cells", NULL)) {
|
|
+ ret = stm32_sai_add_mclk_provider(sai);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ } else {
|
|
+ sai->sai_mclk = devm_clk_get(&pdev->dev, "MCLK");
|
|
+ if (IS_ERR(sai->sai_mclk)) {
|
|
+ if (PTR_ERR(sai->sai_mclk) != -ENOENT)
|
|
+ return PTR_ERR(sai->sai_mclk);
|
|
+ sai->sai_mclk = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -1245,6 +1557,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev)
|
|
|
|
sai->pdev = pdev;
|
|
mutex_init(&sai->ctrl_lock);
|
|
+ spin_lock_init(&sai->irq_lock);
|
|
platform_set_drvdata(pdev, sai);
|
|
|
|
sai->pdata = dev_get_drvdata(pdev->dev.parent);
|
|
@@ -1285,12 +1598,63 @@ static int stm32_sai_sub_probe(struct platform_device *pdev)
|
|
return 0;
|
|
}
|
|
|
|
+static int stm32_sai_sub_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct stm32_sai_sub_data *sai = dev_get_drvdata(&pdev->dev);
|
|
+
|
|
+ clk_unprepare(sai->pdata->pclk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int stm32_sai_sub_suspend(struct device *dev)
|
|
+{
|
|
+ struct stm32_sai_sub_data *sai = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(sai->pdata->pclk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ regcache_cache_only(sai->regmap, true);
|
|
+ regcache_mark_dirty(sai->regmap);
|
|
+
|
|
+ clk_disable(sai->pdata->pclk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_sai_sub_resume(struct device *dev)
|
|
+{
|
|
+ struct stm32_sai_sub_data *sai = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(sai->pdata->pclk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ regcache_cache_only(sai->regmap, false);
|
|
+ ret = regcache_sync(sai->regmap);
|
|
+
|
|
+ clk_disable(sai->pdata->pclk);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+static const struct dev_pm_ops stm32_sai_sub_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(stm32_sai_sub_suspend, stm32_sai_sub_resume)
|
|
+};
|
|
+
|
|
static struct platform_driver stm32_sai_sub_driver = {
|
|
.driver = {
|
|
.name = "st,stm32-sai-sub",
|
|
.of_match_table = stm32_sai_sub_ids,
|
|
+ .pm = &stm32_sai_sub_pm_ops,
|
|
},
|
|
.probe = stm32_sai_sub_probe,
|
|
+ .remove = stm32_sai_sub_remove,
|
|
};
|
|
|
|
module_platform_driver(stm32_sai_sub_driver);
|
|
diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c
|
|
index 373df4f..5a5094e 100644
|
|
--- a/sound/soc/stm/stm32_spdifrx.c
|
|
+++ b/sound/soc/stm/stm32_spdifrx.c
|
|
@@ -16,6 +16,7 @@
|
|
* details.
|
|
*/
|
|
|
|
+#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/delay.h>
|
|
@@ -35,6 +36,9 @@
|
|
#define STM32_SPDIFRX_DR 0x10
|
|
#define STM32_SPDIFRX_CSR 0x14
|
|
#define STM32_SPDIFRX_DIR 0x18
|
|
+#define STM32_SPDIFRX_VERR 0x3F4
|
|
+#define STM32_SPDIFRX_IDR 0x3F8
|
|
+#define STM32_SPDIFRX_SIDR 0x3FC
|
|
|
|
/* Bit definition for SPDIF_CR register */
|
|
#define SPDIFRX_CR_SPDIFEN_SHIFT 0
|
|
@@ -168,6 +172,18 @@
|
|
#define SPDIFRX_SPDIFEN_SYNC 0x1
|
|
#define SPDIFRX_SPDIFEN_ENABLE 0x3
|
|
|
|
+/* Bit definition for SPDIFRX_VERR register */
|
|
+#define SPDIFRX_VERR_MIN_MASK GENMASK(3, 0)
|
|
+#define SPDIFRX_VERR_MAJ_MASK GENMASK(7, 4)
|
|
+
|
|
+/* Bit definition for SPDIFRX_IDR register */
|
|
+#define SPDIFRX_IDR_ID_MASK GENMASK(31, 0)
|
|
+
|
|
+/* Bit definition for SPDIFRX_SIDR register */
|
|
+#define SPDIFRX_SIDR_SID_MASK GENMASK(31, 0)
|
|
+
|
|
+#define SPDIFRX_IPIDR_NUMBER 0x00130041
|
|
+
|
|
#define SPDIFRX_IN1 0x1
|
|
#define SPDIFRX_IN2 0x2
|
|
#define SPDIFRX_IN3 0x3
|
|
@@ -213,6 +229,7 @@
|
|
* @slave_config: dma slave channel runtime config pointer
|
|
* @phys_addr: SPDIFRX registers physical base address
|
|
* @lock: synchronization enabling lock
|
|
+ * @irq_lock: prevent race condition with IRQ on stream state
|
|
* @cs: channel status buffer
|
|
* @ub: user data buffer
|
|
* @irq: SPDIFRX interrupt line
|
|
@@ -233,6 +250,7 @@ struct stm32_spdifrx_data {
|
|
struct dma_slave_config slave_config;
|
|
dma_addr_t phys_addr;
|
|
spinlock_t lock; /* Sync enabling lock */
|
|
+ spinlock_t irq_lock; /* Prevent race condition on stream state */
|
|
unsigned char cs[SPDIFRX_CS_BYTES_NB];
|
|
unsigned char ub[SPDIFRX_UB_BYTES_NB];
|
|
int irq;
|
|
@@ -313,6 +331,7 @@ static void stm32_spdifrx_dma_ctrl_stop(struct stm32_spdifrx_data *spdifrx)
|
|
static int stm32_spdifrx_start_sync(struct stm32_spdifrx_data *spdifrx)
|
|
{
|
|
int cr, cr_mask, imr, ret;
|
|
+ unsigned long flags;
|
|
|
|
/* Enable IRQs */
|
|
imr = SPDIFRX_IMR_IFEIE | SPDIFRX_IMR_SYNCDIE | SPDIFRX_IMR_PERRIE;
|
|
@@ -320,7 +339,7 @@ static int stm32_spdifrx_start_sync(struct stm32_spdifrx_data *spdifrx)
|
|
if (ret)
|
|
return ret;
|
|
|
|
- spin_lock(&spdifrx->lock);
|
|
+ spin_lock_irqsave(&spdifrx->lock, flags);
|
|
|
|
spdifrx->refcount++;
|
|
|
|
@@ -344,6 +363,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,
|
|
@@ -353,7 +374,7 @@ static int stm32_spdifrx_start_sync(struct stm32_spdifrx_data *spdifrx)
|
|
"Failed to start synchronization\n");
|
|
}
|
|
|
|
- spin_unlock(&spdifrx->lock);
|
|
+ spin_unlock_irqrestore(&spdifrx->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
@@ -361,11 +382,12 @@ static int stm32_spdifrx_start_sync(struct stm32_spdifrx_data *spdifrx)
|
|
static void stm32_spdifrx_stop(struct stm32_spdifrx_data *spdifrx)
|
|
{
|
|
int cr, cr_mask, reg;
|
|
+ unsigned long flags;
|
|
|
|
- spin_lock(&spdifrx->lock);
|
|
+ spin_lock_irqsave(&spdifrx->lock, flags);
|
|
|
|
if (--spdifrx->refcount) {
|
|
- spin_unlock(&spdifrx->lock);
|
|
+ spin_unlock_irqrestore(&spdifrx->lock, flags);
|
|
return;
|
|
}
|
|
|
|
@@ -384,7 +406,7 @@ static void stm32_spdifrx_stop(struct stm32_spdifrx_data *spdifrx)
|
|
regmap_read(spdifrx->regmap, STM32_SPDIFRX_DR, ®);
|
|
regmap_read(spdifrx->regmap, STM32_SPDIFRX_CSR, ®);
|
|
|
|
- spin_unlock(&spdifrx->lock);
|
|
+ spin_unlock_irqrestore(&spdifrx->lock, flags);
|
|
}
|
|
|
|
static int stm32_spdifrx_dma_ctrl_register(struct device *dev,
|
|
@@ -493,7 +515,7 @@ static int stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx)
|
|
if (wait_for_completion_interruptible_timeout(&spdifrx->cs_completion,
|
|
msecs_to_jiffies(100))
|
|
<= 0) {
|
|
- dev_err(&spdifrx->pdev->dev, "Failed to get control data\n");
|
|
+ dev_dbg(&spdifrx->pdev->dev, "Failed to get control data\n");
|
|
ret = -EAGAIN;
|
|
}
|
|
|
|
@@ -603,6 +625,9 @@ static bool stm32_spdifrx_readable_reg(struct device *dev, unsigned int reg)
|
|
case STM32_SPDIFRX_DR:
|
|
case STM32_SPDIFRX_CSR:
|
|
case STM32_SPDIFRX_DIR:
|
|
+ case STM32_SPDIFRX_VERR:
|
|
+ case STM32_SPDIFRX_IDR:
|
|
+ case STM32_SPDIFRX_SIDR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
@@ -611,10 +636,15 @@ static bool stm32_spdifrx_readable_reg(struct device *dev, unsigned int reg)
|
|
|
|
static bool stm32_spdifrx_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
- if (reg == STM32_SPDIFRX_DR)
|
|
+ switch (reg) {
|
|
+ case STM32_SPDIFRX_DR:
|
|
+ case STM32_SPDIFRX_CSR:
|
|
+ case STM32_SPDIFRX_SR:
|
|
+ case STM32_SPDIFRX_DIR:
|
|
return true;
|
|
-
|
|
- return false;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
}
|
|
|
|
static bool stm32_spdifrx_writeable_reg(struct device *dev, unsigned int reg)
|
|
@@ -633,20 +663,21 @@ static const struct regmap_config stm32_h7_spdifrx_regmap_conf = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
- .max_register = STM32_SPDIFRX_DIR,
|
|
+ .max_register = STM32_SPDIFRX_SIDR,
|
|
.readable_reg = stm32_spdifrx_readable_reg,
|
|
.volatile_reg = stm32_spdifrx_volatile_reg,
|
|
.writeable_reg = stm32_spdifrx_writeable_reg,
|
|
+ .num_reg_defaults_raw = STM32_SPDIFRX_SIDR / sizeof(u32) + 1,
|
|
.fast_io = true,
|
|
+ .cache_type = REGCACHE_FLAT,
|
|
};
|
|
|
|
static irqreturn_t stm32_spdifrx_isr(int irq, void *devid)
|
|
{
|
|
struct stm32_spdifrx_data *spdifrx = (struct stm32_spdifrx_data *)devid;
|
|
- struct snd_pcm_substream *substream = spdifrx->substream;
|
|
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);
|
|
@@ -706,19 +737,36 @@ 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 (substream)
|
|
- snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
|
|
+ /* 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,
|
|
+ SNDRV_PCM_STATE_DISCONNECTED);
|
|
+ spin_unlock(&spdifrx->irq_lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
- if (err_xrun && substream)
|
|
- snd_pcm_stop_xrun(substream);
|
|
+ spin_lock(&spdifrx->irq_lock);
|
|
+ if (err_xrun && spdifrx->substream)
|
|
+ snd_pcm_stop_xrun(spdifrx->substream);
|
|
+ spin_unlock(&spdifrx->irq_lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
@@ -727,9 +775,12 @@ static int stm32_spdifrx_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai);
|
|
+ unsigned long flags;
|
|
int ret;
|
|
|
|
+ spin_lock_irqsave(&spdifrx->irq_lock, flags);
|
|
spdifrx->substream = substream;
|
|
+ spin_unlock_irqrestore(&spdifrx->irq_lock, flags);
|
|
|
|
ret = clk_prepare_enable(spdifrx->kclk);
|
|
if (ret)
|
|
@@ -805,8 +856,12 @@ static void stm32_spdifrx_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai);
|
|
+ unsigned long flags;
|
|
|
|
+ spin_lock_irqsave(&spdifrx->irq_lock, flags);
|
|
spdifrx->substream = NULL;
|
|
+ spin_unlock_irqrestore(&spdifrx->irq_lock, flags);
|
|
+
|
|
clk_disable_unprepare(spdifrx->kclk);
|
|
}
|
|
|
|
@@ -835,7 +890,8 @@ static struct snd_soc_dai_driver stm32_spdifrx_dai[] = {
|
|
static const struct snd_pcm_hardware stm32_spdifrx_pcm_hw = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP,
|
|
.buffer_bytes_max = 8 * PAGE_SIZE,
|
|
- .period_bytes_max = 2048, /* MDMA constraint */
|
|
+ .period_bytes_min = 1024, /* 5ms at 48kHz */
|
|
+ .period_bytes_max = PAGE_SIZE,
|
|
.periods_min = 2,
|
|
.periods_max = 8,
|
|
};
|
|
@@ -901,6 +957,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev)
|
|
struct stm32_spdifrx_data *spdifrx;
|
|
struct reset_control *rst;
|
|
const struct snd_dmaengine_pcm_config *pcm_config = NULL;
|
|
+ u32 ver, idr;
|
|
int ret;
|
|
|
|
spdifrx = devm_kzalloc(&pdev->dev, sizeof(*spdifrx), GFP_KERNEL);
|
|
@@ -910,6 +967,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev)
|
|
spdifrx->pdev = pdev;
|
|
init_completion(&spdifrx->cs_completion);
|
|
spin_lock_init(&spdifrx->lock);
|
|
+ spin_lock_init(&spdifrx->irq_lock);
|
|
|
|
platform_set_drvdata(pdev, spdifrx);
|
|
|
|
@@ -957,7 +1015,19 @@ static int stm32_spdifrx_probe(struct platform_device *pdev)
|
|
goto error;
|
|
}
|
|
|
|
- return 0;
|
|
+ ret = regmap_read(spdifrx->regmap, STM32_SPDIFRX_IDR, &idr);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+
|
|
+ if (idr == SPDIFRX_IPIDR_NUMBER) {
|
|
+ ret = regmap_read(spdifrx->regmap, STM32_SPDIFRX_VERR, &ver);
|
|
+
|
|
+ dev_dbg(&pdev->dev, "SPDIFRX version: %lu.%lu registered\n",
|
|
+ FIELD_GET(SPDIFRX_VERR_MAJ_MASK, ver),
|
|
+ FIELD_GET(SPDIFRX_VERR_MIN_MASK, ver));
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
|
|
error:
|
|
if (!IS_ERR(spdifrx->ctrl_chan))
|
|
@@ -983,10 +1053,34 @@ static int stm32_spdifrx_remove(struct platform_device *pdev)
|
|
|
|
MODULE_DEVICE_TABLE(of, stm32_spdifrx_ids);
|
|
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int stm32_spdifrx_suspend(struct device *dev)
|
|
+{
|
|
+ struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(dev);
|
|
+
|
|
+ regcache_cache_only(spdifrx->regmap, true);
|
|
+ regcache_mark_dirty(spdifrx->regmap);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_spdifrx_resume(struct device *dev)
|
|
+{
|
|
+ struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(dev);
|
|
+
|
|
+ regcache_cache_only(spdifrx->regmap, false);
|
|
+ return regcache_sync(spdifrx->regmap);
|
|
+}
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+static const struct dev_pm_ops stm32_spdifrx_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(stm32_spdifrx_suspend, stm32_spdifrx_resume)
|
|
+};
|
|
+
|
|
static struct platform_driver stm32_spdifrx_driver = {
|
|
.driver = {
|
|
.name = "st,stm32-spdifrx",
|
|
.of_match_table = stm32_spdifrx_ids,
|
|
+ .pm = &stm32_spdifrx_pm_ops,
|
|
},
|
|
.probe = stm32_spdifrx_probe,
|
|
.remove = stm32_spdifrx_remove,
|
|
--
|
|
2.7.4
|
|
|