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 = { static const struct stm32_sai_conf stm32_sai_conf_f4 = {
.version = SAI_STM32F4, .version = SAI_STM32F4,
.has_spdif = false,
}; };
static const struct stm32_sai_conf stm32_sai_conf_h7 = { static const struct stm32_sai_conf stm32_sai_conf_h7 = {
.version = SAI_STM32H7, .version = SAI_STM32H7,
.has_spdif = true,
}; };
static const struct of_device_id stm32_sai_ids[] = { 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 * struct stm32_sai_conf - SAI configuration
* @version: SAI version * @version: SAI version
* @has_spdif: SAI S/PDIF support flag
*/ */
struct stm32_sai_conf { struct stm32_sai_conf {
int version; int version;
bool has_spdif;
}; };
/** /**

View File

@ -23,6 +23,7 @@
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <sound/asoundef.h>
#include <sound/core.h> #include <sound/core.h>
#include <sound/dmaengine_pcm.h> #include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
@ -30,6 +31,7 @@
#include "stm32_sai.h" #include "stm32_sai.h"
#define SAI_FREE_PROTOCOL 0x0 #define SAI_FREE_PROTOCOL 0x0
#define SAI_SPDIF_PROTOCOL 0x1
#define SAI_SLOT_SIZE_AUTO 0x0 #define SAI_SLOT_SIZE_AUTO 0x0
#define SAI_SLOT_SIZE_16 0x1 #define SAI_SLOT_SIZE_16 0x1
@ -59,8 +61,13 @@
#define SAI_SYNC_INTERNAL 0x1 #define SAI_SYNC_INTERNAL 0x1
#define SAI_SYNC_EXTERNAL 0x2 #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 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) * struct stm32_sai_sub_data - private data of SAI sub block (block A or B)
* @pdev: device data pointer * @pdev: device data pointer
@ -78,6 +85,7 @@
* @id: SAI sub block id corresponding to sub-block A or B * @id: SAI sub block id corresponding to sub-block A or B
* @dir: SAI block direction (playback or capture). set at init * @dir: SAI block direction (playback or capture). set at init
* @master: SAI block mode flag. (true=master, false=slave) 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 * @fmt: SAI block format. relevant only for custom protocols. set at init
* @sync: SAI block synchronization mode. (none, internal or external) * @sync: SAI block synchronization mode. (none, internal or external)
* @synco: SAI block ext sync source (provider setting). (none, sub-block A/B) * @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_width: rx or tx slot width in bits
* @slot_mask: rx or tx active slots mask. set at init or at runtime * @slot_mask: rx or tx active slots mask. set at init or at runtime
* @data_size: PCM data width. corresponds to PCM substream width. * @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 stm32_sai_sub_data {
struct platform_device *pdev; struct platform_device *pdev;
@ -104,6 +114,7 @@ struct stm32_sai_sub_data {
unsigned int id; unsigned int id;
int dir; int dir;
bool master; bool master;
bool spdif;
int fmt; int fmt;
int sync; int sync;
int synco; int synco;
@ -113,6 +124,8 @@ struct stm32_sai_sub_data {
int slot_width; int slot_width;
int slot_mask; int slot_mask;
int data_size; int data_size;
unsigned int spdif_frm_cnt;
unsigned char spdif_status_bits[SAI_IEC60958_STATUS_BYTES];
}; };
enum stm32_sai_fifo_th { 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 = { static const struct regmap_config stm32_sai_sub_regmap_config_f4 = {
.reg_bits = 32, .reg_bits = 32,
.reg_stride = 4, .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); struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai);
int slotr, slotr_mask, slot_size; 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", dev_dbg(cpu_dai->dev, "Masks tx/rx:%#x/%#x, slots:%d, width:%d\n",
tx_mask, rx_mask, slots, slot_width); 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); dev_dbg(cpu_dai->dev, "fmt %x\n", fmt);
cr1_mask = SAI_XCR1_PRTCFG_MASK; /* Do not generate master by default */
cr1 = SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL); 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) { switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
/* SCK active high for all protocols */ /* 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; cr1_mask |= SAI_XCR1_SLAVE;
/* do not generate master by default */ conf_update:
cr1 |= SAI_XCR1_NODIV;
cr1_mask |= SAI_XCR1_NODIV;
ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1);
if (ret < 0) { if (ret < 0) {
dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); 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_FFLUSH |
SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF)); 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 */ /* Mode, data format and channel config */
cr1_mask = SAI_XCR1_DS_MASK; cr1_mask = SAI_XCR1_DS_MASK;
switch (params_format(params)) { 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 cr1, mask, div = 0;
int sai_clk_rate, mclk_ratio, den, ret; int sai_clk_rate, mclk_ratio, den, ret;
int version = sai->pdata->conf->version; int version = sai->pdata->conf->version;
unsigned int rate = params_rate(params);
if (!sai->mclk_rate) { if (!sai->mclk_rate) {
dev_err(cpu_dai->dev, "Mclk rate is null\n"); dev_err(cpu_dai->dev, "Mclk rate is null\n");
return -EINVAL; return -EINVAL;
} }
if (!(params_rate(params) % 11025)) if (!(rate % 11025))
clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k); clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k);
else else
clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k); 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) * MCKDIV = sai_ck / (frl x ws) (NOMCK=1)
* Note: NOMCK/NODIV correspond to same bit. * Note: NOMCK/NODIV correspond to same bit.
*/ */
if (sai->mclk_rate) { if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
mclk_ratio = sai->mclk_rate / params_rate(params); div = DIV_ROUND_CLOSEST(sai_clk_rate,
if (mclk_ratio != 256) { (params_rate(params) * 128));
} else {
if (sai->mclk_rate) {
mclk_ratio = sai->mclk_rate / rate;
if (mclk_ratio == 512) { if (mclk_ratio == 512) {
mask = SAI_XCR1_OSR; mask = SAI_XCR1_OSR;
cr1 = SAI_XCR1_OSR; cr1 = SAI_XCR1_OSR;
} else { } else if (mclk_ratio != 256) {
dev_err(cpu_dai->dev, dev_err(cpu_dai->dev,
"Wrong mclk ratio %d\n", "Wrong mclk ratio %d\n",
mclk_ratio); mclk_ratio);
return -EINVAL; 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); sai->data_size = params_width(params);
ret = stm32_sai_set_slots(cpu_dai); if (!STM_SAI_PROTOCOL_IS_SPDIF(sai)) {
if (ret < 0) ret = stm32_sai_set_slots(cpu_dai);
return ret; if (ret < 0)
stm32_sai_set_frame(cpu_dai); return ret;
stm32_sai_set_frame(cpu_dai);
}
ret = stm32_sai_set_config(cpu_dai, substream, params); ret = stm32_sai_set_config(cpu_dai, substream, params);
if (ret) if (ret)
@ -723,6 +764,9 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd,
(unsigned int)~SAI_XCR1_DMAEN); (unsigned int)~SAI_XCR1_DMAEN);
if (ret < 0) if (ret < 0)
dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); dev_err(cpu_dai->dev, "Failed to update CR1 register\n");
if (STM_SAI_PROTOCOL_IS_SPDIF(sai))
sai->spdif_frm_cnt = 0;
break; break;
default: default:
return -EINVAL; return -EINVAL;
@ -776,6 +820,10 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai)
sai->synco, sai->synci); 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_mask |= SAI_XCR1_SYNCEN_MASK;
cr1 |= SAI_XCR1_SYNCEN_SET(sai->sync); 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, .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 = { static const struct snd_pcm_hardware stm32_sai_pcm_hw = {
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP,
.buffer_bytes_max = 8 * PAGE_SIZE, .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 = { static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = {
.pcm_hardware = &stm32_sai_pcm_hw, .pcm_hardware = &stm32_sai_pcm_hw,
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_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 = { 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; 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 */ /* Get synchronization property */
args.np = NULL; args.np = NULL;
ret = of_parse_phandle_with_fixed_args(np, "st,sync", 1, 0, &args); 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; struct stm32_sai_sub_data *sai;
const struct of_device_id *of_id; const struct of_device_id *of_id;
const struct snd_dmaengine_pcm_config *conf = &stm32_sai_pcm_config;
int ret; int ret;
sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); 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) if (ret)
return ret; return ret;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, if (STM_SAI_PROTOCOL_IS_SPDIF(sai))
&stm32_sai_pcm_config, 0); conf = &stm32_sai_pcm_config_spdif;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, conf, 0);
if (ret) { if (ret) {
dev_err(&pdev->dev, "Could not register pcm dma\n"); dev_err(&pdev->dev, "Could not register pcm dma\n");
return ret; return ret;