[RFC PATCH 3/3] ASoC: stm32: sai: Add support of S/PDIF playback

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add support of S/PDIF iec60958 playback on STM32 SAI.

Signed-off-by: olivier moysan <olivier.moysan@xxxxxx>
---
 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;
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux