From b97a67a14e5c5904b84e52150e0d13da794fc9d7 Mon Sep 17 00:00:00 2001
From: gtrainavicius <gtrainavicius@users.noreply.github.com>
Date: Tue, 10 Jan 2017 21:59:39 +0200
Subject: [PATCH] bcm2835-i2s: Changes for allowing asymmetric sample formats.
 (#1783)

This is achieved by making changes only to the requested
stream direction format, keeping the other stream direction
configuration intact.

Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>
---
 sound/soc/bcm/bcm2835-i2s.c | 54 +++++++++++++++++++++++++++++++--------------
 1 file changed, 38 insertions(+), 16 deletions(-)

--- a/sound/soc/bcm/bcm2835-i2s.c
+++ b/sound/soc/bcm/bcm2835-i2s.c
@@ -310,6 +310,7 @@ static int bcm2835_i2s_hw_params(struct
 	unsigned int sampling_rate = params_rate(params);
 	unsigned int data_length, data_delay, bclk_ratio;
 	unsigned int ch1pos, ch2pos, mode, format;
+	unsigned int previous_ftxp, previous_frxp;
 	unsigned int mash = BCM2835_CLK_MASH_1;
 	unsigned int divi, divf, target_frequency;
 	int clk_src = -1;
@@ -320,6 +321,7 @@ static int bcm2835_i2s_hw_params(struct
 	bool frame_master =	(master == SND_SOC_DAIFMT_CBS_CFS
 					|| master == SND_SOC_DAIFMT_CBM_CFS);
 	uint32_t csreg;
+	bool packed;
 
 	/*
 	 * If a stream is already enabled,
@@ -465,26 +467,46 @@ static int bcm2835_i2s_hw_params(struct
 		return -EINVAL;
 	}
 
-	/*
-	 * Set format for both streams.
-	 * We cannot set another frame length
-	 * (and therefore word length) anyway,
-	 * so the format will be the same.
-	 */
-	regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG, format);
-	regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG, format);
+	/* Set the format for the matching stream direction. */
+	switch (substream->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG, format);
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG, format);
+		break;
+	default:
+		return -EINVAL;
+	}
 
 	/* Setup the I2S mode */
+	/* Keep existing FTXP and FRXP values. */
+	regmap_read(dev->i2s_regmap, BCM2835_I2S_MODE_A_REG, &mode);
+
+	previous_ftxp = mode & BCM2835_I2S_FTXP;
+	previous_frxp = mode & BCM2835_I2S_FRXP;
+
 	mode = 0;
 
-	if (data_length <= 16) {
-		/*
-		 * Use frame packed mode (2 channels per 32 bit word)
-		 * We cannot set another frame length in the second stream
-		 * (and therefore word length) anyway,
-		 * so the format will be the same.
-		 */
-		mode |= BCM2835_I2S_FTXP | BCM2835_I2S_FRXP;
+	/*
+	 * Retain the frame packed mode (2 channels per 32 bit word)
+	 * of the other direction stream intact. The formats of each
+	 * direction can be different as long as the frame length is
+	 * shared for both.
+	 */
+	packed = data_length <= 16;
+
+	switch (substream->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		mode |= previous_frxp;
+		mode |= packed ? BCM2835_I2S_FTXP : 0;
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		mode |= previous_ftxp;
+		mode |= packed ? BCM2835_I2S_FRXP : 0;
+		break;
+	default:
+		return -EINVAL;
 	}
 
 	mode |= BCM2835_I2S_FLEN(bclk_ratio - 1);
