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@xxxxxxxxxxxx> --- 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 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html