[alsa-devel] [RFC PATCH 0/3] ASoC: stm32: add S/PDIF support to SAI
This patch-set adds S/PDIF IEC6958 protocol support to STM32 SAI. Only S/PDIF playback is supported here, as SAI peripheral does not support S/PDIF capture.
SAI peripheral does not take as input full IEC60958 frames as it generates preamble and parity bit. However, it cannot handle raw PCM data, as it is not designed to insert channel status and user data in the generated IEC frames.
Moreover, SAI data register format does not match exactly IEC60958 subframe format. (see figure below). Channel status (C) and User data (U) are at 26-25 bits position, in data register, instead of 30-29 bits position.
IEC60958 subframe bits: 31-28 27-4 3-0 P C U V data (16|24 bits) preamble
SAI data register format: 27-24 23-0 P C U V data (16|24 bits)
The impact in software is that the data have to be processed before DMA transfer. These constraints should be managed at driver level to minimize the impact on application. This means that a callback is necessary to perform data processing (shift and channel status insertion)
This patch-set introduces a callback to allow processing on samples. The implementation is based on previous discussions available here: https://patchwork.kernel.org/patch/9570255/
BRs Olivier
Olivier Moysan (3): ASoC: stm32: Add S/PDIF to SAI bindings ASoC: dmaengine_pcm: add processing support ASoC: stm32: sai: Add support of S/PDIF playback
.../devicetree/bindings/sound/st,stm32-sai.txt | 6 + include/sound/dmaengine_pcm.h | 3 + sound/soc/soc-generic-dmaengine-pcm.c | 62 ++++++++- sound/soc/stm/stm32_sai.c | 2 + sound/soc/stm/stm32_sai.h | 2 + sound/soc/stm/stm32_sai_sub.c | 153 +++++++++++++++++---- 6 files changed, 202 insertions(+), 26 deletions(-)
Add S/PDIF IEC6958 protocol support to STM32 SAI bindings.
Signed-off-by: olivier moysan olivier.moysan@st.com --- Documentation/devicetree/bindings/sound/st,stm32-sai.txt | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt index b1acc1a..f301cdf 100644 --- a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt +++ b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt @@ -45,6 +45,12 @@ SAI subnodes Optional properties: This property sets SAI sub-block as slave of another SAI sub-block. Must contain the phandle and index of the sai sub-block providing the synchronization. + - st,iec60958: support S/PDIF IEC6958 protocol for playback + IEC60958 protocol is not available for capture. + By default, custom protocol is assumed, meaning that protocol is + configured according to protocol defined in related DAI link node, + such as i2s, left justified, right justified, dsp and pdm protocols. + Note: ac97 protocol is not supported by SAI driver
The device node should contain one 'port' child node with one child 'endpoint' node, according to the bindings defined in Documentation/devicetree/bindings/
The patch
ASoC: stm32: Add S/PDIF to SAI bindings
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 994f46610bafd75a5881544bce9dd3acdbe3626a Mon Sep 17 00:00:00 2001
From: Olivier Moysan olivier.moysan@st.com Date: Mon, 19 Feb 2018 16:00:35 +0100 Subject: [PATCH] ASoC: stm32: Add S/PDIF to SAI bindings
Add S/PDIF IEC6958 protocol support to STM32 SAI bindings.
Signed-off-by: olivier moysan olivier.moysan@st.com Signed-off-by: Mark Brown broonie@kernel.org --- Documentation/devicetree/bindings/sound/st,stm32-sai.txt | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt index b1acc1a256ba..f301cdf0b7e6 100644 --- a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt +++ b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt @@ -45,6 +45,12 @@ SAI subnodes Optional properties: This property sets SAI sub-block as slave of another SAI sub-block. Must contain the phandle and index of the sai sub-block providing the synchronization. + - st,iec60958: support S/PDIF IEC6958 protocol for playback + IEC60958 protocol is not available for capture. + By default, custom protocol is assumed, meaning that protocol is + configured according to protocol defined in related DAI link node, + such as i2s, left justified, right justified, dsp and pdm protocols. + Note: ac97 protocol is not supported by SAI driver
The device node should contain one 'port' child node with one child 'endpoint' node, according to the bindings defined in Documentation/devicetree/bindings/
Allow dmaengine client to optionally register a processing callback. This callback is intended to apply processing on samples in buffer copied from/to user space, before/after DMA transfer.
Signed-off-by: Olivier Moysan olivier.moysan@st.com --- include/sound/dmaengine_pcm.h | 3 ++ sound/soc/soc-generic-dmaengine-pcm.c | 62 +++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index 8a5a840..47ef486 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -140,6 +140,9 @@ struct snd_dmaengine_pcm_config { struct dma_chan *(*compat_request_channel)( struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes); dma_filter_fn compat_filter_fn; struct device *dma_dev; const char *chan_names[SNDRV_PCM_STREAM_LAST + 1]; diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index c07d5c7..1c1d838 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -340,6 +340,41 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( return snd_dmaengine_pcm_pointer(substream); }
+static int dmaengine_copy_user(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct snd_pcm_runtime *runtime = substream->runtime; + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) = pcm->config->process; + bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + void *dma_ptr = runtime->dma_area + hwoff + + channel * (runtime->dma_bytes / runtime->channels); + int ret; + + if (is_playback) + if (copy_from_user(dma_ptr, (void __user *)buf, bytes)) + return -EFAULT; + + if (process) { + ret = process(substream, channel, hwoff, + (void __user *)buf, bytes); + if (ret < 0) + return ret; + } + + if (!is_playback) + if (copy_to_user((void __user *)buf, dma_ptr, bytes)) + return -EFAULT; + + return 0; +} + static const struct snd_pcm_ops dmaengine_pcm_ops = { .open = dmaengine_pcm_open, .close = snd_dmaengine_pcm_close, @@ -350,6 +385,17 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( .pointer = dmaengine_pcm_pointer, };
+static const struct snd_pcm_ops dmaengine_pcm_process_ops = { + .open = dmaengine_pcm_open, + .close = snd_dmaengine_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dmaengine_pcm_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = dmaengine_pcm_pointer, + .copy_user = dmaengine_copy_user, +}; + static const struct snd_soc_component_driver dmaengine_pcm_component = { .name = SND_DMAENGINE_PCM_DRV_NAME, .probe_order = SND_SOC_COMP_ORDER_LATE, @@ -357,6 +403,13 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( .pcm_new = dmaengine_pcm_new, };
+static const struct snd_soc_component_driver dmaengine_pcm_component_process = { + .name = SND_DMAENGINE_PCM_DRV_NAME, + .probe_order = SND_SOC_COMP_ORDER_LATE, + .ops = &dmaengine_pcm_process_ops, + .pcm_new = dmaengine_pcm_new, +}; + static const char * const dmaengine_pcm_dma_channel_names[] = { [SNDRV_PCM_STREAM_PLAYBACK] = "tx", [SNDRV_PCM_STREAM_CAPTURE] = "rx", @@ -449,8 +502,13 @@ int snd_dmaengine_pcm_register(struct device *dev, if (ret) goto err_free_dma;
- ret = snd_soc_add_component(dev, &pcm->component, - &dmaengine_pcm_component, NULL, 0); + if (config && config->process) + ret = snd_soc_add_component(dev, &pcm->component, + &dmaengine_pcm_component_process, + NULL, 0); + else + ret = snd_soc_add_component(dev, &pcm->component, + &dmaengine_pcm_component, NULL, 0); if (ret) goto err_free_dma;
Add support of S/PDIF iec60958 playback on STM32 SAI.
Signed-off-by: olivier moysan olivier.moysan@st.com --- sound/soc/stm/stm32_sai.c | 2 + sound/soc/stm/stm32_sai.h | 2 + sound/soc/stm/stm32_sai_sub.c | 153 +++++++++++++++++++++++++++++++++++------- 3 files changed, 133 insertions(+), 24 deletions(-)
diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index d743b7d..f226542 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -30,10 +30,12 @@
static const struct stm32_sai_conf stm32_sai_conf_f4 = { .version = SAI_STM32F4, + .has_spdif = false, };
static const struct stm32_sai_conf stm32_sai_conf_h7 = { .version = SAI_STM32H7, + .has_spdif = true, };
static const struct of_device_id stm32_sai_ids[] = { diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h index bb062e7..f254221 100644 --- a/sound/soc/stm/stm32_sai.h +++ b/sound/soc/stm/stm32_sai.h @@ -248,9 +248,11 @@ enum stm32_sai_version { /** * struct stm32_sai_conf - SAI configuration * @version: SAI version + * @has_spdif: SAI S/PDIF support flag */ struct stm32_sai_conf { int version; + bool has_spdif; };
/** diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index 08583b9..cfeb219 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -23,6 +23,7 @@ #include <linux/of_platform.h> #include <linux/regmap.h>
+#include <sound/asoundef.h> #include <sound/core.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> @@ -30,6 +31,7 @@ #include "stm32_sai.h"
#define SAI_FREE_PROTOCOL 0x0 +#define SAI_SPDIF_PROTOCOL 0x1
#define SAI_SLOT_SIZE_AUTO 0x0 #define SAI_SLOT_SIZE_16 0x1 @@ -59,8 +61,13 @@ #define SAI_SYNC_INTERNAL 0x1 #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_EXT_SYNC(x) (!STM_SAI_IS_F4(sai->pdata))
+#define SAI_IEC60958_BLOCK_FRAMES 192 +#define SAI_IEC60958_STATUS_BYTES 24 + /** * struct stm32_sai_sub_data - private data of SAI sub block (block A or B) * @pdev: device data pointer @@ -78,6 +85,7 @@ * @id: SAI sub block id corresponding to sub-block A or B * @dir: SAI block direction (playback or capture). set at init * @master: SAI block mode flag. (true=master, false=slave) set at init + * @spdif: SAI S/PDIF iec60958 mode flag. set at init * @fmt: SAI block format. relevant only for custom protocols. set at init * @sync: SAI block synchronization mode. (none, internal or external) * @synco: SAI block ext sync source (provider setting). (none, sub-block A/B) @@ -87,6 +95,8 @@ * @slot_width: rx or tx slot width in bits * @slot_mask: rx or tx active slots mask. set at init or at runtime * @data_size: PCM data width. corresponds to PCM substream width. + * @spdif_frm_cnt: S/PDIF playback frame counter + * @spdif_status_bits: S/PDIF status bits */ struct stm32_sai_sub_data { struct platform_device *pdev; @@ -104,6 +114,7 @@ struct stm32_sai_sub_data { unsigned int id; int dir; bool master; + bool spdif; int fmt; int sync; int synco; @@ -113,6 +124,8 @@ struct stm32_sai_sub_data { int slot_width; int slot_mask; int data_size; + unsigned int spdif_frm_cnt; + unsigned char spdif_status_bits[SAI_IEC60958_STATUS_BYTES]; };
enum stm32_sai_fifo_th { @@ -171,6 +184,10 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) } }
+static const unsigned char default_status_bits[SAI_IEC60958_STATUS_BYTES] = { + 0, 0, 0, IEC958_AES3_CON_FS_48000, +}; + static const struct regmap_config stm32_sai_sub_regmap_config_f4 = { .reg_bits = 32, .reg_stride = 4, @@ -277,6 +294,11 @@ static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int slotr, slotr_mask, slot_size;
+ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + dev_warn(cpu_dai->dev, "Slot setting relevant only for TDM\n"); + return 0; + } + dev_dbg(cpu_dai->dev, "Masks tx/rx:%#x/%#x, slots:%d, width:%d\n", tx_mask, rx_mask, slots, slot_width);
@@ -326,8 +348,17 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
dev_dbg(cpu_dai->dev, "fmt %x\n", fmt);
- cr1_mask = SAI_XCR1_PRTCFG_MASK; - cr1 = SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL); + /* Do not generate master by default */ + cr1 = SAI_XCR1_NODIV; + cr1_mask = SAI_XCR1_NODIV; + + cr1_mask |= SAI_XCR1_PRTCFG_MASK; + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + cr1 |= SAI_XCR1_PRTCFG_SET(SAI_SPDIF_PROTOCOL); + goto conf_update; + } + + cr1 |= SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { /* SCK active high for all protocols */ @@ -409,10 +440,7 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
cr1_mask |= SAI_XCR1_SLAVE;
- /* do not generate master by default */ - cr1 |= SAI_XCR1_NODIV; - cr1_mask |= SAI_XCR1_NODIV; - +conf_update: ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); if (ret < 0) { dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); @@ -478,6 +506,12 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai, 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)) { + sai->spdif_frm_cnt = 0; + return 0; + } + /* Mode, data format and channel config */ cr1_mask = SAI_XCR1_DS_MASK; switch (params_format(params)) { @@ -592,13 +626,14 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, int cr1, mask, div = 0; int sai_clk_rate, mclk_ratio, den, ret; int version = sai->pdata->conf->version; + unsigned int rate = params_rate(params);
if (!sai->mclk_rate) { dev_err(cpu_dai->dev, "Mclk rate is null\n"); return -EINVAL; }
- if (!(params_rate(params) % 11025)) + if (!(rate % 11025)) clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k); else clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k); @@ -623,24 +658,28 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, * MCKDIV = sai_ck / (frl x ws) (NOMCK=1) * Note: NOMCK/NODIV correspond to same bit. */ - if (sai->mclk_rate) { - mclk_ratio = sai->mclk_rate / params_rate(params); - if (mclk_ratio != 256) { + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + div = DIV_ROUND_CLOSEST(sai_clk_rate, + (params_rate(params) * 128)); + } else { + if (sai->mclk_rate) { + mclk_ratio = sai->mclk_rate / rate; if (mclk_ratio == 512) { mask = SAI_XCR1_OSR; cr1 = SAI_XCR1_OSR; - } else { + } else if (mclk_ratio != 256) { dev_err(cpu_dai->dev, "Wrong mclk ratio %d\n", mclk_ratio); return -EINVAL; } + div = DIV_ROUND_CLOSEST(sai_clk_rate, + sai->mclk_rate); + } 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 = DIV_ROUND_CLOSEST(sai_clk_rate, sai->mclk_rate); - } else { - /* mclk-fs not set, master clock not active. NOMCK=1 */ - den = sai->fs_length * params_rate(params); - div = DIV_ROUND_CLOSEST(sai_clk_rate, den); } }
@@ -670,10 +709,12 @@ static int stm32_sai_hw_params(struct snd_pcm_substream *substream,
sai->data_size = params_width(params);
- ret = stm32_sai_set_slots(cpu_dai); - if (ret < 0) - return ret; - stm32_sai_set_frame(cpu_dai); + if (!STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + ret = stm32_sai_set_slots(cpu_dai); + if (ret < 0) + return ret; + stm32_sai_set_frame(cpu_dai); + }
ret = stm32_sai_set_config(cpu_dai, substream, params); if (ret) @@ -723,6 +764,9 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd, (unsigned int)~SAI_XCR1_DMAEN); if (ret < 0) dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + sai->spdif_frm_cnt = 0; break; default: return -EINVAL; @@ -776,6 +820,10 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) sai->synco, sai->synci); }
+ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + memcpy(sai->spdif_status_bits, default_status_bits, + sizeof(default_status_bits)); + cr1_mask |= SAI_XCR1_SYNCEN_MASK; cr1 |= SAI_XCR1_SYNCEN_SET(sai->sync);
@@ -792,6 +840,42 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) .shutdown = stm32_sai_shutdown, };
+static int stm32_sai_pcm_process_spdif(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + int *ptr = (int *)(runtime->dma_area + hwoff + + channel * (runtime->dma_bytes / runtime->channels)); + ssize_t cnt = bytes_to_samples(runtime, bytes); + unsigned int frm_cnt = sai->spdif_frm_cnt; + unsigned int byte; + unsigned int mask; + + do { + *ptr = ((*ptr >> 8) & 0x00ffffff); + + /* Set channel status bit */ + byte = frm_cnt >> 3; + mask = 1 << (frm_cnt - (byte << 3)); + if (sai->spdif_status_bits[byte] & mask) + *ptr |= 0x04000000; + ptr++; + + if (!(cnt % 2)) + frm_cnt++; + + if (frm_cnt == SAI_IEC60958_BLOCK_FRAMES) + frm_cnt = 0; + } while (--cnt); + sai->spdif_frm_cnt = frm_cnt; + + return 0; +} + static const struct snd_pcm_hardware stm32_sai_pcm_hw = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, .buffer_bytes_max = 8 * PAGE_SIZE, @@ -842,8 +926,14 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) };
static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = { - .pcm_hardware = &stm32_sai_pcm_hw, - .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .pcm_hardware = &stm32_sai_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config_spdif = { + .pcm_hardware = &stm32_sai_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .process = stm32_sai_pcm_process_spdif, };
static const struct snd_soc_component_driver stm32_component = { @@ -900,6 +990,18 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, return -EINVAL; }
+ /* Get spdif iec60958 property */ + sai->spdif = false; + if (of_get_property(np, "st,iec60958", NULL)) { + if (!STM_SAI_HAS_SPDIF(sai) || + sai->dir == SNDRV_PCM_STREAM_CAPTURE) { + dev_err(&pdev->dev, "S/PDIF IEC60958 not supported\n"); + return -EINVAL; + } + sai->spdif = true; + sai->master = true; + } + /* Get synchronization property */ args.np = NULL; ret = of_parse_phandle_with_fixed_args(np, "st,sync", 1, 0, &args); @@ -999,6 +1101,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) { struct stm32_sai_sub_data *sai; const struct of_device_id *of_id; + const struct snd_dmaengine_pcm_config *conf = &stm32_sai_pcm_config; int ret;
sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); @@ -1039,8 +1142,10 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) if (ret) return ret;
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, - &stm32_sai_pcm_config, 0); + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, conf, 0); if (ret) { dev_err(&pdev->dev, "Could not register pcm dma\n"); return ret;
The patch
ASoC: stm32: sai: Add support of S/PDIF playback
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 6eb17d70932b5fbe25d9e7fc6d45d29d7a6cdf94 Mon Sep 17 00:00:00 2001
From: Olivier Moysan olivier.moysan@st.com Date: Mon, 19 Feb 2018 16:00:37 +0100 Subject: [PATCH] ASoC: stm32: sai: Add support of S/PDIF playback
Add support of S/PDIF iec60958 playback on STM32 SAI.
Signed-off-by: olivier moysan olivier.moysan@st.com Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/stm/stm32_sai.c | 2 + sound/soc/stm/stm32_sai.h | 2 + sound/soc/stm/stm32_sai_sub.c | 153 +++++++++++++++++++++++++++++++++++------- 3 files changed, 133 insertions(+), 24 deletions(-)
diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index d743b7dd52fb..f22654253c43 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -30,10 +30,12 @@
static const struct stm32_sai_conf stm32_sai_conf_f4 = { .version = SAI_STM32F4, + .has_spdif = false, };
static const struct stm32_sai_conf stm32_sai_conf_h7 = { .version = SAI_STM32H7, + .has_spdif = true, };
static const struct of_device_id stm32_sai_ids[] = { diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h index bb062e70de63..f25422174909 100644 --- a/sound/soc/stm/stm32_sai.h +++ b/sound/soc/stm/stm32_sai.h @@ -248,9 +248,11 @@ enum stm32_sai_version { /** * struct stm32_sai_conf - SAI configuration * @version: SAI version + * @has_spdif: SAI S/PDIF support flag */ struct stm32_sai_conf { int version; + bool has_spdif; };
/** diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index 08583b958430..cfeb219e1d78 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -23,6 +23,7 @@ #include <linux/of_platform.h> #include <linux/regmap.h>
+#include <sound/asoundef.h> #include <sound/core.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> @@ -30,6 +31,7 @@ #include "stm32_sai.h"
#define SAI_FREE_PROTOCOL 0x0 +#define SAI_SPDIF_PROTOCOL 0x1
#define SAI_SLOT_SIZE_AUTO 0x0 #define SAI_SLOT_SIZE_16 0x1 @@ -59,8 +61,13 @@ #define SAI_SYNC_INTERNAL 0x1 #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_EXT_SYNC(x) (!STM_SAI_IS_F4(sai->pdata))
+#define SAI_IEC60958_BLOCK_FRAMES 192 +#define SAI_IEC60958_STATUS_BYTES 24 + /** * struct stm32_sai_sub_data - private data of SAI sub block (block A or B) * @pdev: device data pointer @@ -78,6 +85,7 @@ * @id: SAI sub block id corresponding to sub-block A or B * @dir: SAI block direction (playback or capture). set at init * @master: SAI block mode flag. (true=master, false=slave) set at init + * @spdif: SAI S/PDIF iec60958 mode flag. set at init * @fmt: SAI block format. relevant only for custom protocols. set at init * @sync: SAI block synchronization mode. (none, internal or external) * @synco: SAI block ext sync source (provider setting). (none, sub-block A/B) @@ -87,6 +95,8 @@ * @slot_width: rx or tx slot width in bits * @slot_mask: rx or tx active slots mask. set at init or at runtime * @data_size: PCM data width. corresponds to PCM substream width. + * @spdif_frm_cnt: S/PDIF playback frame counter + * @spdif_status_bits: S/PDIF status bits */ struct stm32_sai_sub_data { struct platform_device *pdev; @@ -104,6 +114,7 @@ struct stm32_sai_sub_data { unsigned int id; int dir; bool master; + bool spdif; int fmt; int sync; int synco; @@ -113,6 +124,8 @@ struct stm32_sai_sub_data { int slot_width; int slot_mask; int data_size; + unsigned int spdif_frm_cnt; + unsigned char spdif_status_bits[SAI_IEC60958_STATUS_BYTES]; };
enum stm32_sai_fifo_th { @@ -171,6 +184,10 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) } }
+static const unsigned char default_status_bits[SAI_IEC60958_STATUS_BYTES] = { + 0, 0, 0, IEC958_AES3_CON_FS_48000, +}; + static const struct regmap_config stm32_sai_sub_regmap_config_f4 = { .reg_bits = 32, .reg_stride = 4, @@ -277,6 +294,11 @@ static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int slotr, slotr_mask, slot_size;
+ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + dev_warn(cpu_dai->dev, "Slot setting relevant only for TDM\n"); + return 0; + } + dev_dbg(cpu_dai->dev, "Masks tx/rx:%#x/%#x, slots:%d, width:%d\n", tx_mask, rx_mask, slots, slot_width);
@@ -326,8 +348,17 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
dev_dbg(cpu_dai->dev, "fmt %x\n", fmt);
- cr1_mask = SAI_XCR1_PRTCFG_MASK; - cr1 = SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL); + /* Do not generate master by default */ + cr1 = SAI_XCR1_NODIV; + cr1_mask = SAI_XCR1_NODIV; + + cr1_mask |= SAI_XCR1_PRTCFG_MASK; + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + cr1 |= SAI_XCR1_PRTCFG_SET(SAI_SPDIF_PROTOCOL); + goto conf_update; + } + + cr1 |= SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { /* SCK active high for all protocols */ @@ -409,10 +440,7 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
cr1_mask |= SAI_XCR1_SLAVE;
- /* do not generate master by default */ - cr1 |= SAI_XCR1_NODIV; - cr1_mask |= SAI_XCR1_NODIV; - +conf_update: ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); if (ret < 0) { dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); @@ -478,6 +506,12 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai, 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)) { + sai->spdif_frm_cnt = 0; + return 0; + } + /* Mode, data format and channel config */ cr1_mask = SAI_XCR1_DS_MASK; switch (params_format(params)) { @@ -592,13 +626,14 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, int cr1, mask, div = 0; int sai_clk_rate, mclk_ratio, den, ret; int version = sai->pdata->conf->version; + unsigned int rate = params_rate(params);
if (!sai->mclk_rate) { dev_err(cpu_dai->dev, "Mclk rate is null\n"); return -EINVAL; }
- if (!(params_rate(params) % 11025)) + if (!(rate % 11025)) clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k); else clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k); @@ -623,24 +658,28 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, * MCKDIV = sai_ck / (frl x ws) (NOMCK=1) * Note: NOMCK/NODIV correspond to same bit. */ - if (sai->mclk_rate) { - mclk_ratio = sai->mclk_rate / params_rate(params); - if (mclk_ratio != 256) { + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + div = DIV_ROUND_CLOSEST(sai_clk_rate, + (params_rate(params) * 128)); + } else { + if (sai->mclk_rate) { + mclk_ratio = sai->mclk_rate / rate; if (mclk_ratio == 512) { mask = SAI_XCR1_OSR; cr1 = SAI_XCR1_OSR; - } else { + } else if (mclk_ratio != 256) { dev_err(cpu_dai->dev, "Wrong mclk ratio %d\n", mclk_ratio); return -EINVAL; } + div = DIV_ROUND_CLOSEST(sai_clk_rate, + sai->mclk_rate); + } 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 = DIV_ROUND_CLOSEST(sai_clk_rate, sai->mclk_rate); - } else { - /* mclk-fs not set, master clock not active. NOMCK=1 */ - den = sai->fs_length * params_rate(params); - div = DIV_ROUND_CLOSEST(sai_clk_rate, den); } }
@@ -670,10 +709,12 @@ static int stm32_sai_hw_params(struct snd_pcm_substream *substream,
sai->data_size = params_width(params);
- ret = stm32_sai_set_slots(cpu_dai); - if (ret < 0) - return ret; - stm32_sai_set_frame(cpu_dai); + if (!STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + ret = stm32_sai_set_slots(cpu_dai); + if (ret < 0) + return ret; + stm32_sai_set_frame(cpu_dai); + }
ret = stm32_sai_set_config(cpu_dai, substream, params); if (ret) @@ -723,6 +764,9 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd, (unsigned int)~SAI_XCR1_DMAEN); if (ret < 0) dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + sai->spdif_frm_cnt = 0; break; default: return -EINVAL; @@ -776,6 +820,10 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) sai->synco, sai->synci); }
+ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + memcpy(sai->spdif_status_bits, default_status_bits, + sizeof(default_status_bits)); + cr1_mask |= SAI_XCR1_SYNCEN_MASK; cr1 |= SAI_XCR1_SYNCEN_SET(sai->sync);
@@ -792,6 +840,42 @@ static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { .shutdown = stm32_sai_shutdown, };
+static int stm32_sai_pcm_process_spdif(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + int *ptr = (int *)(runtime->dma_area + hwoff + + channel * (runtime->dma_bytes / runtime->channels)); + ssize_t cnt = bytes_to_samples(runtime, bytes); + unsigned int frm_cnt = sai->spdif_frm_cnt; + unsigned int byte; + unsigned int mask; + + do { + *ptr = ((*ptr >> 8) & 0x00ffffff); + + /* Set channel status bit */ + byte = frm_cnt >> 3; + mask = 1 << (frm_cnt - (byte << 3)); + if (sai->spdif_status_bits[byte] & mask) + *ptr |= 0x04000000; + ptr++; + + if (!(cnt % 2)) + frm_cnt++; + + if (frm_cnt == SAI_IEC60958_BLOCK_FRAMES) + frm_cnt = 0; + } while (--cnt); + sai->spdif_frm_cnt = frm_cnt; + + return 0; +} + static const struct snd_pcm_hardware stm32_sai_pcm_hw = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, .buffer_bytes_max = 8 * PAGE_SIZE, @@ -842,8 +926,14 @@ static struct snd_soc_dai_driver stm32_sai_capture_dai[] = { };
static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = { - .pcm_hardware = &stm32_sai_pcm_hw, - .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .pcm_hardware = &stm32_sai_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config_spdif = { + .pcm_hardware = &stm32_sai_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .process = stm32_sai_pcm_process_spdif, };
static const struct snd_soc_component_driver stm32_component = { @@ -900,6 +990,18 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, return -EINVAL; }
+ /* Get spdif iec60958 property */ + sai->spdif = false; + if (of_get_property(np, "st,iec60958", NULL)) { + if (!STM_SAI_HAS_SPDIF(sai) || + sai->dir == SNDRV_PCM_STREAM_CAPTURE) { + dev_err(&pdev->dev, "S/PDIF IEC60958 not supported\n"); + return -EINVAL; + } + sai->spdif = true; + sai->master = true; + } + /* Get synchronization property */ args.np = NULL; ret = of_parse_phandle_with_fixed_args(np, "st,sync", 1, 0, &args); @@ -999,6 +1101,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) { struct stm32_sai_sub_data *sai; const struct of_device_id *of_id; + const struct snd_dmaengine_pcm_config *conf = &stm32_sai_pcm_config; int ret;
sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); @@ -1039,8 +1142,10 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) if (ret) return ret;
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, - &stm32_sai_pcm_config, 0); + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, conf, 0); if (ret) { dev_err(&pdev->dev, "Could not register pcm dma\n"); return ret;
participants (2)
-
Mark Brown
-
Olivier Moysan