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