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.hiki...@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

Reply via email to