From: Marcus Cooper codekipper@gmail.com
The i2s block can be used to pass PCM data over multiple channels and is sometimes used for the audio side of an HDMI connection.
Signed-off-by: Marcus Cooper codekipper@gmail.com --- sound/soc/sunxi/sun4i-i2s.c | 93 +++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 30 deletions(-)
diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index a020c3b372a8..a71969167053 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -617,41 +617,74 @@ static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream, int lines;
channels = params_channels(params); - if (channels != 2) { - dev_err(dai->dev, "Unsupported number of channels: %d\n", - channels); - return -EINVAL; - } - - lines = (channels + 1) / 2; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if ((channels > dai->driver->playback.channels_max) || + (channels < dai->driver->playback.channels_min)) { + dev_err(dai->dev, "Unsupported number of channels: %d\n", + channels); + return -EINVAL; + }
- /* Enable the required output lines */ - regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, - SUN4I_I2S_CTRL_SDO_EN_MASK, - SUN4I_I2S_CTRL_SDO_EN(lines)); - - if (i2s->variant->has_chcfg) { - regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG, - SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM_MASK, - SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM(channels)); - regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG, - SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM_MASK, - SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM(channels)); - } + lines = (channels + 1) / 2;
- /* Map the channels for playback and capture */ - i2s->variant->set_txchanmap(i2s, 0, 0x76543210); - i2s->variant->set_rxchanmap(i2s, 0x00003210); + /* Enable the required output lines */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_SDO_EN_MASK, + SUN4I_I2S_CTRL_SDO_EN(lines)); + + i2s->variant->set_txchanmap(i2s, 0, 0x10); + i2s->variant->set_txchansel(i2s, 0, channels > 1 ? 2:1); + + if (i2s->variant->set_txchanen) + i2s->variant->set_txchanen(i2s, 0, 2); + + if (i2s->variant->has_chcfg) { + regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG, + SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM_MASK, + SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM(channels)); + + if (channels > 2) { + i2s->variant->set_txchanmap(i2s, 1, 0x32); + i2s->variant->set_txchanoffset(i2s, 1); + i2s->variant->set_txchansel(i2s, 1, + channels > 3 ? 2:1); + i2s->variant->set_txchanen(i2s, 1, 2); + } + if (channels > 4) { + i2s->variant->set_txchanmap(i2s, 2, 0x54); + i2s->variant->set_txchanoffset(i2s, 2); + i2s->variant->set_txchansel(i2s, 2, + channels > 5 ? 2:1); + i2s->variant->set_txchanen(i2s, 2, 2); + } + if (channels > 6) { + i2s->variant->set_txchanmap(i2s, 3, 0x76); + i2s->variant->set_txchanoffset(i2s, 3); + i2s->variant->set_txchansel(i2s, 3, + channels > 6 ? 2:1); + i2s->variant->set_txchanen(i2s, 3, 2); + } + } + } else { + if ((channels > dai->driver->capture.channels_max) || + (channels < dai->driver->capture.channels_min)) { + dev_err(dai->dev, "Unsupported number of channels: %d\n", + channels); + return -EINVAL; + }
- /* Configure the channels */ - i2s->variant->set_txchansel(i2s, 0, channels); - i2s->variant->set_rxchansel(i2s, channels); + /* Map the channels for capture */ + i2s->variant->set_rxchanmap(i2s, 0x10); + i2s->variant->set_rxchansel(i2s, channels);
- if (i2s->variant->set_txchanen) - i2s->variant->set_txchanen(i2s, 0, channels); + if (i2s->variant->set_rxchanen) + i2s->variant->set_rxchanen(i2s, channels);
- if (i2s->variant->set_rxchanen) - i2s->variant->set_rxchanen(i2s, channels); + if (i2s->variant->has_chcfg) + regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG, + SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM_MASK, + SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM(channels)); + }
switch (params_physical_width(params)) { case 16: