#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>
#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
#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
* @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)
* @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;
unsigned int id;
int dir;
bool master;
+ bool spdif;
int fmt;
int sync;
int synco;
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 {
}
}
+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,
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);
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 */
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");
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)) {
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);
* 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);
}
}
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)
(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;
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);
.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,
};
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 = {
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);
{
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);
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;