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>
This commit is contained in:
Olivier Moysan 2018-02-19 16:00:37 +01:00 committed by Mark Brown
parent 994f46610b
commit 6eb17d7093
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
3 changed files with 133 additions and 24 deletions

View File

@ -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[] = {

View File

@ -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;
};
/**

View File

@ -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;