Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 6eb17d70 authored by Olivier Moysan's avatar Olivier Moysan Committed by Mark Brown
Browse files

ASoC: stm32: sai: Add support of S/PDIF playback



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

Signed-off-by: default avatarolivier moysan <olivier.moysan@st.com>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 994f4661
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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[] = {
+2 −0
Original line number Diff line number Diff line
@@ -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;
};

/**
+129 −24
Original line number Diff line number Diff line
@@ -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,26 +658,30 @@ 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 (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 / params_rate(params);
			if (mclk_ratio != 256) {
				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);
				div = DIV_ROUND_CLOSEST(sai_clk_rate,
							sai->mclk_rate);
			} else {
			/* mclk-fs not set, master clock not active. NOMCK=1 */
				/* mclk-fs not set, master clock not active */
				den = sai->fs_length * params_rate(params);
				div = DIV_ROUND_CLOSEST(sai_clk_rate, den);
			}
		}
	}

	if (div > SAI_XCR1_MCKDIV_MAX(version)) {
		dev_err(cpu_dai->dev, "Divider %d out of range\n", div);
@@ -670,10 +709,12 @@ static int stm32_sai_hw_params(struct snd_pcm_substream *substream,

	sai->data_size = params_width(params);

	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,
@@ -846,6 +930,12 @@ static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = {
	.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 = {
	.name = "stm32-sai",
};
@@ -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;