[alsa-devel] [PATCH 6/9] ASoc: cygnus: Fix problems with multichannel transfers
Lori Hikichi
lori.hikichi at broadcom.com
Tue Aug 15 00:06:54 CEST 2017
Problems were found with multi-channel (4+) TDM transfers. The alignment
of the channels within the frame could shift when starting a new transfer.
In order to implement a fix the register programming sequence needed to
be revised.
Signed-off-by: Lori Hikichi <lori.hikichi at broadcom.com>
---
sound/soc/bcm/cygnus-ssp.c | 539 ++++++++++++++++++++++++++++++++-------------
sound/soc/bcm/cygnus-ssp.h | 14 +-
2 files changed, 394 insertions(+), 159 deletions(-)
diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c
index 5b6e345..5292c04 100644
--- a/sound/soc/bcm/cygnus-ssp.c
+++ b/sound/soc/bcm/cygnus-ssp.c
@@ -121,6 +121,7 @@
#define I2S_OUT_STREAM_ENA 31
#define I2S_OUT_STREAM_CFG_GROUP_ID 20
#define I2S_OUT_STREAM_CFG_CHANNEL_GROUPING 24
+#define I2S_OUT_STREAM_CFG_FCI_ID_MASK 0x3ff
/* AUD_FMM_IOP_IN_I2S_x_CAP */
#define I2S_IN_STREAM_CFG_CAP_ENA 31
@@ -129,7 +130,11 @@
/* AUD_FMM_IOP_OUT_I2S_x_I2S_CFG_REG */
#define I2S_OUT_CFGX_CLK_ENA 0
#define I2S_OUT_CFGX_DATA_ENABLE 1
+#define I2S_OUT_CFGX_LRCK_POLARITY 4
+#define I2S_OUT_CFGX_SCLK_POLARITY 5
#define I2S_OUT_CFGX_DATA_ALIGNMENT 6
+#define I2S_OUT_CFGX_BITS_PER_SAMPLE 8
+#define I2S_OUT_CFGX_BIT_PER_SAMPLE_MASK 0x1f
#define I2S_OUT_CFGX_BITS_PER_SLOT 13
#define I2S_OUT_CFGX_VALID_SLOT 14
#define I2S_OUT_CFGX_FSYNC_WIDTH 18
@@ -137,14 +142,27 @@
#define I2S_OUT_CFGX_SLAVE_MODE 30
#define I2S_OUT_CFGX_TDM_MODE 31
+#define I2S_IN_CFGX_DATA_ALIGNMENT 6
+#define I2S_IN_CFGX_BITS_PER_SAMPLE 8
+#define I2S_IN_CFGX_BIT_PER_SAMPLE_MASK 0x1f
+#define I2S_IN_CFGX_BITS_PER_SLOT 13
+#define I2S_IN_CFGX_VALID_SLOT 14
+#define I2S_IN_CFGX_SLAVE_MODE 30
+#define I2S_IN_CFGX_TDM_MODE 31
+
/* AUD_FMM_BF_CTRL_SOURCECH_CFGx_REG */
#define BF_SRC_CFGX_SFIFO_ENA 0
#define BF_SRC_CFGX_BUFFER_PAIR_ENABLE 1
#define BF_SRC_CFGX_SAMPLE_CH_MODE 2
#define BF_SRC_CFGX_SFIFO_SZ_DOUBLE 5
#define BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY 10
+#define BF_SRC_CFGX_SAMPLE_REPEAT_ENABLE 11
#define BF_SRC_CFGX_BIT_RES 20
#define BF_SRC_CFGX_PROCESS_SEQ_ID_VALID 31
+#define BF_SRC_CFGX_BITRES_MASK 0x1f
+
+/* AUD_FMM_BF_CTRL_SOURCECH_CTRLx_REG */
+#define BF_SOURCECH_CTRL_PLAY_RUN 0
/* AUD_FMM_BF_CTRL_DESTCH_CFGx_REG */
#define BF_DST_CFGX_CAP_ENA 0
@@ -154,11 +172,16 @@
#define BF_DST_CFGX_FCI_ID 12
#define BF_DST_CFGX_CAP_MODE 24
#define BF_DST_CFGX_PROC_SEQ_ID_VALID 31
+#define BF_DST_CFGX_BITRES_MASK 0x1f
+
+/* AUD_FMM_BF_CTRL_DESTCH_CTRLX */
+#define BF_DESTCH_CTRLX_CAP_RUN 0x1
/* AUD_FMM_IOP_OUT_SPDIF_xxx */
#define SPDIF_0_OUT_DITHER_ENA 3
#define SPDIF_0_OUT_STREAM_ENA 31
+#define IOP_LOGIC_RESET_IN_OFFSET(x) ((x) + 7) /* Capture ports offset by 7 */
#define INIT_SSP_REGS(num) (struct cygnus_ssp_regs){ \
.i2s_stream_cfg = OUT_I2S_ ##num## _STREAM_CFG_OFFSET, \
@@ -169,8 +192,7 @@
.bf_destch_ctrl = BF_DST_CTRL ##num## _OFFSET, \
.bf_destch_cfg = BF_DST_CFG ##num## _OFFSET, \
.bf_sourcech_ctrl = BF_SRC_CTRL ##num## _OFFSET, \
- .bf_sourcech_cfg = BF_SRC_CFG ##num## _OFFSET, \
- .bf_sourcech_grp = BF_SRC_GRP ##num## _OFFSET \
+ .bf_sourcech_cfg = BF_SRC_CFG ##num## _OFFSET \
}
#define CYGNUS_RATE_MIN 8000
@@ -189,6 +211,8 @@
.list = cygnus_rates,
};
+static void update_ssp_cfg(struct cygnus_aio_port *aio);
+
static struct cygnus_aio_port *cygnus_dai_get_portinfo(struct snd_soc_dai *dai)
{
struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai);
@@ -201,15 +225,17 @@ static int audio_ssp_init_portregs(struct cygnus_aio_port *aio)
u32 value, fci_id;
int status = 0;
+ /* Set Group ID */
+ writel(0, aio->cygaud->audio + BF_SRC_GRP0_OFFSET);
+ writel(1, aio->cygaud->audio + BF_SRC_GRP1_OFFSET);
+ writel(2, aio->cygaud->audio + BF_SRC_GRP2_OFFSET);
+ writel(3, aio->cygaud->audio + BF_SRC_GRP3_OFFSET);
+
switch (aio->port_type) {
case PORT_TDM:
value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
value &= ~I2S_STREAM_CFG_MASK;
- /* Set Group ID */
- writel(aio->portnum,
- aio->cygaud->audio + aio->regs.bf_sourcech_grp);
-
/* Configure the AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG reg */
value |= aio->portnum << I2S_OUT_STREAM_CFG_GROUP_ID;
value |= aio->portnum; /* FCI ID is the port num */
@@ -219,6 +245,7 @@ static int audio_ssp_init_portregs(struct cygnus_aio_port *aio)
/* Configure the AUD_FMM_BF_CTRL_SOURCECH_CFGX reg */
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY);
+ value &= ~BIT(BF_SRC_CFGX_SAMPLE_REPEAT_ENABLE);
value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE);
value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
@@ -247,8 +274,6 @@ static int audio_ssp_init_portregs(struct cygnus_aio_port *aio)
writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
break;
case PORT_SPDIF:
- writel(aio->portnum, aio->cygaud->audio + BF_SRC_GRP3_OFFSET);
-
value = readl(aio->cygaud->audio + SPDIF_CTRL_OFFSET);
value |= BIT(SPDIF_0_OUT_DITHER_ENA);
writel(value, aio->cygaud->audio + SPDIF_CTRL_OFFSET);
@@ -287,17 +312,35 @@ static void audio_ssp_in_enable(struct cygnus_aio_port *aio)
value |= BIT(BF_DST_CFGX_CAP_ENA);
writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg);
- writel(0x1, aio->cygaud->audio + aio->regs.bf_destch_ctrl);
-
- value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
- value |= BIT(I2S_OUT_CFGX_CLK_ENA);
- value |= BIT(I2S_OUT_CFGX_DATA_ENABLE);
- writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+ /*
+ * DATA_ENABLE need to be set even if doing capture.
+ * Subsequent Tx will fail if this is not done.
+ */
+ if (!aio->streams_on) {
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value |= BIT(I2S_OUT_CFGX_CLK_ENA);
+ value |= BIT(I2S_OUT_CFGX_DATA_ENABLE);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+ }
value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
value |= BIT(I2S_IN_STREAM_CFG_CAP_ENA);
writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
+ /* Enable input portion of block */
+ udelay(10);
+
+ /*
+ * The input port may or may not be held in reset. Always clear
+ * the reset. This will be benign if the port is not in reset.
+ */
+ value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value &= ~BIT(IOP_LOGIC_RESET_IN_OFFSET(aio->portnum));
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+
+ writel(BF_DESTCH_CTRLX_CAP_RUN,
+ aio->cygaud->audio + aio->regs.bf_destch_ctrl);
+
aio->streams_on |= CAPTURE_STREAM_MASK;
}
@@ -305,25 +348,50 @@ static void audio_ssp_in_disable(struct cygnus_aio_port *aio)
{
u32 value;
+ value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg);
+ value &= ~BIT(BF_DST_CFGX_CAP_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg);
+
value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
value &= ~BIT(I2S_IN_STREAM_CFG_CAP_ENA);
writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
aio->streams_on &= ~CAPTURE_STREAM_MASK;
+ writel(0x0, aio->cygaud->audio + aio->regs.bf_destch_ctrl);
+
+ /*
+ * Put input portion of port in reset.
+ * Clears residual data (32 bits) from internal formatter buffer
+ * BIT_CLOCK must be present for this to take effect. For Cygnus
+ * in slave mode this means we must master after Cygnus port
+ */
+ /*
+ * TDM Slave Rx needs to toggle this reset.
+ * See comment in cygnus_ssp_hw_params() about JIRA-1312.
+ * TDM Master Rx also needs this fix
+ * 32 bit transfers of fully populated TDM frames will have every
+ * transfer after the first with misaligned channels.
+ *
+ */
+ if (aio->mode == CYGNUS_SSPMODE_TDM) {
+ value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value |= BIT(IOP_LOGIC_RESET_IN_OFFSET(aio->portnum));
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ }
+
/* If both playback and capture are off */
if (!aio->streams_on) {
+ /*
+ * Add small delay before turning off clock
+ * Need 1 bit clock tick for INIT_LOGIC to activate
+ */
+ udelay(10);
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value &= ~BIT(I2S_OUT_CFGX_CLK_ENA);
value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE);
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
}
-
- writel(0x0, aio->cygaud->audio + aio->regs.bf_destch_ctrl);
-
- value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg);
- value &= ~BIT(BF_DST_CFGX_CAP_ENA);
- writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg);
}
static int audio_ssp_out_enable(struct cygnus_aio_port *aio)
@@ -334,19 +402,33 @@ static int audio_ssp_out_enable(struct cygnus_aio_port *aio)
switch (aio->port_type) {
case PORT_TDM:
value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
- value |= BIT(I2S_OUT_STREAM_ENA);
+ value &= ~(I2S_OUT_STREAM_CFG_FCI_ID_MASK);
+ value |= aio->portnum;
writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
- writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value |= BIT(BF_SRC_CFGX_SFIFO_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+
+ writel(BIT(BF_SOURCECH_CTRL_PLAY_RUN),
+ aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
+
+ value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+ value |= BIT(I2S_OUT_STREAM_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
value |= BIT(I2S_OUT_CFGX_CLK_ENA);
value |= BIT(I2S_OUT_CFGX_DATA_ENABLE);
writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
- value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
- value |= BIT(BF_SRC_CFGX_SFIFO_ENA);
- writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ /*
+ * The output port may or may not be in reset. Always clear
+ * the reset. This will be benign if the port is not in reset.
+ */
+ value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value &= ~BIT(aio->portnum);
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
aio->streams_on |= PLAYBACK_STREAM_MASK;
break;
@@ -355,7 +437,8 @@ static int audio_ssp_out_enable(struct cygnus_aio_port *aio)
value |= 0x3;
writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
- writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
+ writel(BIT(BF_SOURCECH_CTRL_PLAY_RUN),
+ aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value |= BIT(BF_SRC_CFGX_SFIFO_ENA);
@@ -379,13 +462,17 @@ static int audio_ssp_out_disable(struct cygnus_aio_port *aio)
case PORT_TDM:
aio->streams_on &= ~PLAYBACK_STREAM_MASK;
- /* If both playback and capture are off */
- if (!aio->streams_on) {
- value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
- value &= ~BIT(I2S_OUT_CFGX_CLK_ENA);
- value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE);
- writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
- }
+ /* Set the FCI ID to INVALID */
+ value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+ value |= 0x3ff;
+ writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+
+ /*
+ * We want to wait enough time for 2 LRCLK.
+ * At 8 kHz framerate, this would be 250 us.
+ * Set delay to 300 us to be safe.
+ */
+ udelay(300);
/* set group_sync_dis = 1 */
value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
@@ -403,16 +490,27 @@ static int audio_ssp_out_disable(struct cygnus_aio_port *aio)
value &= ~BIT(aio->portnum);
writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
- value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
- value &= ~BIT(I2S_OUT_STREAM_ENA);
- writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+ /*
+ * We want to wait enough time for 1 LRCLK.
+ * At 8 kHz framerate, this would be 125 us.
+ * Set delay to 175 us to be safe.
+ */
+ udelay(175);
+
+ if (aio->is_slave && (aio->mode == CYGNUS_SSPMODE_TDM)) {
+ value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value |= BIT(aio->portnum);
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ }
+
+ /* If both playback and capture are off */
+ if (aio->streams_on == 0) {
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE);
+ value &= ~BIT(I2S_OUT_CFGX_CLK_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+ }
- /* IOP SW INIT on OUT_I2S_x */
- value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
- value |= BIT(aio->portnum);
- writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
- value &= ~BIT(aio->portnum);
- writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
break;
case PORT_SPDIF:
value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
@@ -439,10 +537,12 @@ static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio)
u32 mask = 0xf;
u32 sclk;
u32 mclk_rate;
+ unsigned int bits_per_frame;
unsigned int bit_rate;
unsigned int ratio;
- bit_rate = aio->bit_per_frame * aio->lrclk;
+ bits_per_frame = aio->slots_per_frame * aio->slot_width;
+ bit_rate = bits_per_frame * aio->lrclk;
/*
* Check if the bit clock can be generated from the given MCLK.
@@ -468,14 +568,14 @@ static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio)
dev_err(aio->cygaud->dev,
"Invalid combination of MCLK and BCLK\n");
dev_err(aio->cygaud->dev, "lrclk = %u, bits/frame = %u, mclk = %u\n",
- aio->lrclk, aio->bit_per_frame, aio->mclk);
+ aio->lrclk, bits_per_frame, aio->mclk);
return -EINVAL;
}
/* Set sclk rate */
switch (aio->port_type) {
case PORT_TDM:
- sclk = aio->bit_per_frame;
+ sclk = bits_per_frame;
if (sclk == 512)
sclk = 0;
@@ -505,7 +605,7 @@ static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio)
dev_dbg(aio->cygaud->dev, "mclk cfg reg = 0x%x\n", value);
dev_dbg(aio->cygaud->dev, "bits per frame = %u, mclk = %u Hz, lrclk = %u Hz\n",
- aio->bit_per_frame, aio->mclk, aio->lrclk);
+ bits_per_frame, aio->mclk, aio->lrclk);
return 0;
}
@@ -514,9 +614,9 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai);
- int rate, bitres;
+ int rate, bitres, bits_per_sample;
u32 value;
- u32 mask = 0x1f;
+ u32 mask;
int ret = 0;
dev_dbg(aio->cygaud->dev, "%s port = %d\n", __func__, aio->portnum);
@@ -529,6 +629,7 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream,
switch (aio->mode) {
case CYGNUS_SSPMODE_TDM:
+ /* It's expected that set_dai_tdm_slot has been called */
if ((rate == 192000) && (params_channels(params) > 4)) {
dev_err(aio->cygaud->dev, "Cannot run %d channels at %dHz\n",
params_channels(params), rate);
@@ -536,7 +637,15 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream,
}
break;
case CYGNUS_SSPMODE_I2S:
- aio->bit_per_frame = 64; /* I2S must be 64 bit per frame */
+ if (params_channels(params) != 2) {
+ dev_err(aio->cygaud->dev, "i2s mode must use 2 channels\n");
+ return -EINVAL;
+ }
+
+ aio->active_slots = 2;
+ aio->slots_per_frame = 2;
+ aio->slot_width = 32; /* Use 64Fs */
+
break;
default:
dev_err(aio->cygaud->dev,
@@ -553,26 +662,36 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream,
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
bitres = 16;
+ bits_per_sample = 16;
break;
case SNDRV_PCM_FORMAT_S32_LE:
- /* 32 bit mode is coded as 0 */
- bitres = 0;
+ bitres = 0; /* 32 bit mode is coded as 0 */
+ bits_per_sample = 24; /* Only 24 valid bits */
break;
default:
return -EINVAL;
}
+ mask = BF_SRC_CFGX_BITRES_MASK;
value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
value &= ~(mask << BF_SRC_CFGX_BIT_RES);
value |= (bitres << BF_SRC_CFGX_BIT_RES);
writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ /* Only needed for LSB mode, ignored for MSB */
+ mask = I2S_OUT_CFGX_BIT_PER_SAMPLE_MASK;
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value &= ~(mask << I2S_OUT_CFGX_BITS_PER_SAMPLE);
+ value |= (bits_per_sample << I2S_OUT_CFGX_BITS_PER_SAMPLE);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
} else {
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
+ bits_per_sample = 16;
+
value = readl(aio->cygaud->audio +
aio->regs.bf_destch_cfg);
value |= BIT(BF_DST_CFGX_CAP_MODE);
@@ -581,6 +700,8 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream,
break;
case SNDRV_PCM_FORMAT_S32_LE:
+ bits_per_sample = 24; /* Only 24 valid bits */
+
value = readl(aio->cygaud->audio +
aio->regs.bf_destch_cfg);
value &= ~BIT(BF_DST_CFGX_CAP_MODE);
@@ -591,12 +712,52 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream,
default:
return -EINVAL;
}
+
+ /* Used for both LSB and MSB modes */
+ mask = I2S_IN_CFGX_BIT_PER_SAMPLE_MASK;
+ value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+ value &= ~(mask << I2S_IN_CFGX_BITS_PER_SAMPLE);
+ value |= (bits_per_sample << I2S_IN_CFGX_BITS_PER_SAMPLE);
+ writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
}
- aio->lrclk = rate;
+ /* Put output port into reset prior to configuring.
+ * This action is a workaround for couple situations:
+ * 1) JIRA-1312: 16-bit TDM Slave Tx problem
+ * If the port is configured as 16-bit slave and
+ * both CLK_ENA and DATA_ENABLE bits are off then the port will
+ * fail to Tx. Therefore, we hold port in reset until the
+ * we are ready to enable
+ * 2) The TDM Slave Tx stream will be misaligned on the first
+ * transfer after boot/reset (both 16 and 32 bit modes).
+ * Applying reset will workaround this problem.
+ */
+ if (aio->streams_on == 0) {
+ if (aio->is_slave && (aio->mode == CYGNUS_SSPMODE_TDM)) {
+ /*
+ * Need to do this LOGIC reset after boot/reset
+ * because it puts the logic in a slightly different
+ * than hard reset. In this way the chip logic will be
+ * in the same state for our first transfer as it is
+ * every transfer.
+ */
+ value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value |= BIT(aio->portnum);
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+
+ value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value |= BIT(IOP_LOGIC_RESET_IN_OFFSET(aio->portnum));
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ }
+
+ if (aio->port_type != PORT_SPDIF)
+ update_ssp_cfg(aio);
- if (!aio->is_slave)
- ret = cygnus_ssp_set_clocks(aio);
+ aio->lrclk = rate;
+
+ if (!aio->is_slave)
+ ret = cygnus_ssp_set_clocks(aio);
+ }
return ret;
}
@@ -703,28 +864,6 @@ static void cygnus_ssp_shutdown(struct snd_pcm_substream *substream,
clk_disable_unprepare(aio->clk_info.audio_clk);
}
-/*
- * Bit Update Notes
- * 31 Yes TDM Mode (1 = TDM, 0 = i2s)
- * 30 Yes Slave Mode (1 = Slave, 0 = Master)
- * 29:26 No Sclks per frame
- * 25:18 Yes FS Width
- * 17:14 No Valid Slots
- * 13 No Bits (1 = 16 bits, 0 = 32 bits)
- * 12:08 No Bits per samp
- * 07 Yes Justifcation (1 = LSB, 0 = MSB)
- * 06 Yes Alignment (1 = Delay 1 clk, 0 = no delay
- * 05 Yes SCLK polarity (1 = Rising, 0 = Falling)
- * 04 Yes LRCLK Polarity (1 = High for left, 0 = Low for left)
- * 03:02 Yes Reserved - write as zero
- * 01 No Data Enable
- * 00 No CLK Enable
- */
-#define I2S_OUT_CFG_REG_UPDATE_MASK 0x3C03FF03
-
-/* Input cfg is same as output, but the FS width is not a valid field */
-#define I2S_IN_CFG_REG_UPDATE_MASK (I2S_OUT_CFG_REG_UPDATE_MASK | 0x03FC0000)
-
int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, int len)
{
struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
@@ -740,10 +879,6 @@ int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, int len)
static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
- u32 ssp_curcfg;
- u32 ssp_newcfg;
- u32 ssp_outcfg;
- u32 ssp_incfg;
u32 val;
u32 mask;
@@ -752,15 +887,11 @@ static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
if (aio->port_type == PORT_SPDIF)
return -EINVAL;
- ssp_newcfg = 0;
-
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
- ssp_newcfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE);
aio->is_slave = 1;
break;
case SND_SOC_DAIFMT_CBS_CFS:
- ssp_newcfg &= ~BIT(I2S_OUT_CFGX_SLAVE_MODE);
aio->is_slave = 0;
break;
default:
@@ -769,25 +900,24 @@ static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
- ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
- ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH);
+ aio->fs_delay = 1;
aio->mode = CYGNUS_SSPMODE_I2S;
break;
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
- ssp_newcfg |= BIT(I2S_OUT_CFGX_TDM_MODE);
-
/* DSP_A = data after FS, DSP_B = data during FS */
if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A)
- ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
-
- if ((aio->fsync_width > 0) && (aio->fsync_width < 256))
- ssp_newcfg |=
- (aio->fsync_width << I2S_OUT_CFGX_FSYNC_WIDTH);
- else
- ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH);
-
+ aio->fs_delay = 1;
+ else {
+ if (aio->is_slave) {
+ dev_err(aio->cygaud->dev,
+ "%s DSP_B mode not supported while slave.\n",
+ __func__);
+ return -EINVAL;
+ }
+ aio->fs_delay = 0;
+ }
aio->mode = CYGNUS_SSPMODE_TDM;
break;
@@ -795,21 +925,36 @@ static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
return -EINVAL;
}
- /*
- * SSP out cfg.
- * Retain bits we do not want to update, then OR in new bits
- */
- ssp_curcfg = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
- ssp_outcfg = (ssp_curcfg & I2S_OUT_CFG_REG_UPDATE_MASK) | ssp_newcfg;
- writel(ssp_outcfg, aio->cygaud->audio + aio->regs.i2s_cfg);
+ /* We must be i2s master to invert any clock */
+ if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
+ if (aio->is_slave || (aio->mode == CYGNUS_SSPMODE_TDM)) {
+ dev_err(aio->cygaud->dev,
+ "%s Can only invert clocks in i2s master mode\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
- /*
- * SSP in cfg.
- * Retain bits we do not want to update, then OR in new bits
- */
- ssp_curcfg = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
- ssp_incfg = (ssp_curcfg & I2S_IN_CFG_REG_UPDATE_MASK) | ssp_newcfg;
- writel(ssp_incfg, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ aio->invert_bclk = true;
+ aio->invert_fs = false;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aio->invert_bclk = false;
+ aio->invert_fs = true;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aio->invert_bclk = true;
+ aio->invert_fs = true;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ aio->invert_bclk = false;
+ aio->invert_fs = false;
+ break;
+ default:
+ return -EINVAL;
+ }
val = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
@@ -871,12 +1016,10 @@ static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
{
struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
- u32 value;
- int bits_per_slot = 0; /* default to 32-bits per slot */
- int frame_bits;
unsigned int active_slots;
+ unsigned int bits_per_frame;
bool found = false;
- int i;
+ unsigned int i;
if (tx_mask != rx_mask) {
dev_err(aio->cygaud->dev,
@@ -886,35 +1029,20 @@ static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
active_slots = hweight32(tx_mask);
- if ((active_slots < 0) || (active_slots > 16))
+ if ((active_slots == 0) || (active_slots > 16))
return -EINVAL;
/* Slot value must be even */
if (active_slots % 2)
return -EINVAL;
- /* We encode 16 slots as 0 in the reg */
- if (active_slots == 16)
- active_slots = 0;
-
- /* Slot Width is either 16 or 32 */
- switch (slot_width) {
- case 16:
- bits_per_slot = 1;
- break;
- case 32:
- bits_per_slot = 0;
- break;
- default:
- bits_per_slot = 0;
- dev_warn(aio->cygaud->dev,
- "%s Defaulting Slot Width to 32\n", __func__);
- }
+ if ((slot_width != 16) && (slot_width != 32))
+ return -EINVAL;
- frame_bits = slots * slot_width;
+ bits_per_frame = slots * slot_width;
for (i = 0; i < ARRAY_SIZE(ssp_valid_tdm_framesize); i++) {
- if (ssp_valid_tdm_framesize[i] == frame_bits) {
+ if (ssp_valid_tdm_framesize[i] == bits_per_frame) {
found = true;
break;
}
@@ -923,31 +1051,16 @@ static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
if (!found) {
dev_err(aio->cygaud->dev,
"%s In TDM mode, frame bits INVALID (%d)\n",
- __func__, frame_bits);
+ __func__, bits_per_frame);
return -EINVAL;
}
- aio->bit_per_frame = frame_bits;
+ aio->active_slots = active_slots;
+ aio->slot_width = slot_width;
+ aio->slots_per_frame = slots;
dev_dbg(aio->cygaud->dev, "%s active_slots %u, bits per frame %d\n",
- __func__, active_slots, frame_bits);
-
- /* Set capture side of ssp port */
- value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
- value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
- value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT);
- value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
- value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT);
- writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
-
- /* Set playback side of ssp port */
- value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
- value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
- value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT);
- value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
- value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT);
- writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
-
+ __func__, aio->active_slots, bits_per_frame);
return 0;
}
@@ -978,6 +1091,124 @@ static int cygnus_ssp_set_pll(struct snd_soc_dai *cpu_dai, int pll_id,
return ret;
}
+/*
+ * Bit Update Notes
+ * 31 Yes TDM Mode (1 = TDM, 0 = i2s)
+ * 30 Yes Slave Mode (1 = Slave, 0 = Master)
+ * 29:26 No Sclks per frame
+ * 25:18 Yes FS Width
+ * 17:14 No Valid Slots
+ * 13 No Bits (1 = 16 bits, 0 = 32 bits)
+ * 12:08 No Bits per samp
+ * 07 Yes Justifcation (1 = LSB, 0 = MSB)
+ * 06 Yes Alignment (1 = Delay 1 clk, 0 = no delay
+ * 05 Yes SCLK polarity (1 = Rising, 0 = Falling)
+ * 04 Yes LRCLK Polarity (1 = High for left, 0 = Low for left)
+ * 03:02 Yes Reserved - write as zero
+ * 01 No Data Enable
+ * 00 No CLK Enable
+ */
+#define I2S_OUT_CFG_REG_UPDATE_MASK 0x3c03ff03 /* set bit = do not modify */
+
+/* Input cfg is same as output, but the FS width is not a valid field */
+#define I2S_IN_CFG_REG_UPDATE_MASK (I2S_OUT_CFG_REG_UPDATE_MASK | 0x03fc0000)
+
+static void update_ssp_cfg(struct cygnus_aio_port *aio)
+{
+ u32 valid_slots; /* reg val to program */
+ int bits_per_slot_cmn;
+ int bits_per_slot_in;
+ int bits_per_slot_out;
+
+ u32 ssp_newcfg;
+ u32 ssp_curcfg;
+ u32 ssp_outcfg;
+ u32 ssp_incfg;
+ u32 fsync_width;
+
+ if (aio->port_type == PORT_SPDIF)
+ return;
+
+ /* We encode 16 slots as 0 in the reg */
+ valid_slots = aio->active_slots;
+ if (aio->active_slots == 16)
+ valid_slots = 0;
+
+ /* Slot Width is either 16 or 32 */
+ bits_per_slot_cmn = 0; /* Default to 32 bits */
+ if (aio->slot_width <= 16)
+ bits_per_slot_cmn = 1;
+
+ bits_per_slot_in = bits_per_slot_cmn;
+ bits_per_slot_out = bits_per_slot_cmn;
+
+ ssp_newcfg = 0;
+
+ if (aio->mode == CYGNUS_SSPMODE_TDM) {
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_TDM_MODE);
+ if (aio->fsync_width == -1)
+ fsync_width = 1;
+ else
+ fsync_width = aio->fsync_width;
+
+ ssp_newcfg |= (fsync_width << I2S_OUT_CFGX_FSYNC_WIDTH);
+ }
+
+ if (aio->is_slave)
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE);
+ else
+ ssp_newcfg &= ~BIT(I2S_OUT_CFGX_SLAVE_MODE);
+
+ if (aio->mode == CYGNUS_SSPMODE_I2S) {
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH);
+ } else {
+ if (aio->fs_delay == 0)
+ ssp_newcfg &= ~BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
+ else
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
+ }
+
+ if (aio->invert_bclk)
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_SCLK_POLARITY);
+
+ if (aio->invert_fs)
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_LRCK_POLARITY);
+
+ /*
+ * SSP in cfg.
+ * Retain bits we do not want to update, then OR in new bits
+ * Always set slave mode for Rx formatter.
+ * The Rx formatter's Slave Mode bit controls if it uses its own
+ * internal clock or the clock signal that comes from the Slave Mode
+ * bit set in the Tx formatter (which would be the Tx Formatters
+ * internal clock or signal from external pin).
+ */
+ ssp_curcfg = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+ ssp_incfg = (ssp_curcfg & I2S_IN_CFG_REG_UPDATE_MASK) | ssp_newcfg;
+ ssp_incfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE);
+
+ ssp_incfg &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
+ ssp_incfg |= (valid_slots << I2S_OUT_CFGX_VALID_SLOT);
+ ssp_incfg &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
+ ssp_incfg |= (bits_per_slot_in << I2S_OUT_CFGX_BITS_PER_SLOT);
+
+ writel(ssp_incfg, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+
+ /*
+ * SSP out cfg.
+ * Retain bits we do not want to update, then OR in new bits
+ */
+ ssp_curcfg = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ ssp_outcfg = (ssp_curcfg & I2S_OUT_CFG_REG_UPDATE_MASK) | ssp_newcfg;
+
+ ssp_outcfg &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
+ ssp_outcfg |= (valid_slots << I2S_OUT_CFGX_VALID_SLOT);
+ ssp_outcfg &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
+ ssp_outcfg |= (bits_per_slot_out << I2S_OUT_CFGX_BITS_PER_SLOT);
+
+ writel(ssp_outcfg, aio->cygaud->audio + aio->regs.i2s_cfg);
+}
static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = {
diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h
index ad15a00..648321d 100644
--- a/sound/soc/bcm/cygnus-ssp.h
+++ b/sound/soc/bcm/cygnus-ssp.h
@@ -77,7 +77,6 @@ struct cygnus_ssp_regs {
u32 bf_destch_cfg;
u32 bf_sourcech_ctrl;
u32 bf_sourcech_cfg;
- u32 bf_sourcech_grp;
};
struct cygnus_audio_clkinfo {
@@ -90,14 +89,21 @@ struct cygnus_aio_port {
int mode;
bool is_slave;
int streams_on; /* will be 0 if both capture and play are off */
- int fsync_width;
int port_type;
+ unsigned int fsync_width;
+ unsigned int fs_delay;
+ bool invert_bclk;
+ bool invert_fs;
+
u32 mclk;
u32 lrclk;
- u32 bit_per_frame;
u32 pll_clk_num;
+ unsigned int slot_width;
+ unsigned int slots_per_frame;
+ unsigned int active_slots;
+
struct cygnus_audio *cygaud;
struct cygnus_ssp_regs regs;
@@ -120,8 +126,6 @@ struct cygnus_audio {
void __iomem *i2s_in;
};
-extern int cygnus_ssp_get_mode(struct snd_soc_dai *cpu_dai);
-extern int cygnus_ssp_add_pll_tweak_controls(struct snd_soc_pcm_runtime *rtd);
extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai,
int len);
extern int cygnus_soc_platform_register(struct device *dev,
--
1.9.1
More information about the Alsa-devel
mailing list