ASOC: msm: Add the MI2S CPU driver.
Add MI2S CPU driver which configures QDSP6 AFE to start MI2S port.
Change-Id: Idc12325d83607d60e66636c3fba052204905a629
Signed-off-by: Kuirong Wang <kuirongw@codeaurora.org>
diff --git a/include/sound/msm-dai-q6.h b/include/sound/msm-dai-q6.h
index cec7e65..89a6a47 100644
--- a/include/sound/msm-dai-q6.h
+++ b/include/sound/msm-dai-q6.h
@@ -14,6 +14,13 @@
#define __MSM_DAI_Q6_PDATA_H__
+#define MSM_MI2S_SD0 (1 << 0)
+#define MSM_MI2S_SD1 (1 << 1)
+#define MSM_MI2S_SD2 (1 << 2)
+#define MSM_MI2S_SD3 (1 << 3)
+#define MSM_MI2S_CAP_RX 0
+#define MSM_MI2S_CAP_TX 1
+
struct msm_dai_auxpcm_pdata {
const char *clk;
u16 mode;
@@ -25,4 +32,8 @@
int pcm_clk_rate;
};
+struct msm_mi2s_data {
+ u32 capability; /* RX or TX */
+ u16 sd_lines;
+};
#endif
diff --git a/include/sound/q6afe.h b/include/sound/q6afe.h
index 4be1abe..6206838 100644
--- a/include/sound/q6afe.h
+++ b/include/sound/q6afe.h
@@ -13,10 +13,13 @@
#define __Q6AFE_H__
#include <sound/apr_audio.h>
-#define MSM_AFE_MONO 0
-#define MSM_AFE_MONO_RIGHT 1
-#define MSM_AFE_MONO_LEFT 2
-#define MSM_AFE_STEREO 3
+#define MSM_AFE_MONO 0
+#define MSM_AFE_MONO_RIGHT 1
+#define MSM_AFE_MONO_LEFT 2
+#define MSM_AFE_STEREO 3
+#define MSM_AFE_4CHANNELS 4
+#define MSM_AFE_6CHANNELS 6
+#define MSM_AFE_8CHANNELS 8
#define MSM_AFE_PORT_TYPE_RX 0
#define MSM_AFE_PORT_TYPE_TX 1
diff --git a/sound/soc/msm/msm-dai-q6.c b/sound/soc/msm/msm-dai-q6.c
index 4af4f06..b2adf5d 100644
--- a/sound/soc/msm/msm-dai-q6.c
+++ b/sound/soc/msm/msm-dai-q6.c
@@ -41,6 +41,17 @@
static struct clk *pcm_clk;
+static u8 num_of_bits_set(u8 sd_line_mask)
+{
+ u8 num_bits_set = 0;
+
+ while (sd_line_mask) {
+ num_bits_set++;
+ sd_line_mask = sd_line_mask & (sd_line_mask - 1);
+ }
+ return num_bits_set;
+}
+
static int msm_dai_q6_cdc_hw_params(struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai, int stream)
{
@@ -66,10 +77,118 @@
/* Q6 only supports 16 as now */
dai_data->port_config.mi2s.bitwidth = 16;
dai_data->port_config.mi2s.line = 1;
+ return 0;
+}
+
+static int msm_dai_q6_mi2s_hw_params(struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai, int stream)
+{
+ struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct msm_mi2s_data *mi2s_pdata =
+ (struct msm_mi2s_data *) dai->dev->platform_data;
+
+ dai_data->channels = params_channels(params);
+ if (num_of_bits_set(mi2s_pdata->sd_lines) == 1) {
+ switch (dai_data->channels) {
+ case 2:
+ dai_data->port_config.mi2s.channel = MSM_AFE_STEREO;
+ break;
+ case 1:
+ dai_data->port_config.mi2s.channel = MSM_AFE_MONO;
+ break;
+ default:
+ pr_warn("greater than stereo has not been validated");
+ break;
+ }
+ }
+ /* Q6 only supports 16 as now */
+ dai_data->port_config.mi2s.bitwidth = 16;
return 0;
}
+static int msm_dai_q6_mi2s_platform_data_validation(
+ struct snd_soc_dai *dai)
+{
+ u8 num_of_sd_lines;
+ struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct msm_mi2s_data *mi2s_pdata =
+ (struct msm_mi2s_data *)dai->dev->platform_data;
+ struct snd_soc_dai_driver *dai_driver =
+ (struct snd_soc_dai_driver *)dai->driver;
+
+ num_of_sd_lines = num_of_bits_set(mi2s_pdata->sd_lines);
+
+ switch (num_of_sd_lines) {
+ case 1:
+ switch (mi2s_pdata->sd_lines) {
+ case MSM_MI2S_SD0:
+ dai_data->port_config.mi2s.line = AFE_I2S_SD0;
+ break;
+ case MSM_MI2S_SD1:
+ dai_data->port_config.mi2s.line = AFE_I2S_SD1;
+ break;
+ case MSM_MI2S_SD2:
+ dai_data->port_config.mi2s.line = AFE_I2S_SD2;
+ break;
+ case MSM_MI2S_SD3:
+ dai_data->port_config.mi2s.line = AFE_I2S_SD3;
+ break;
+ default:
+ pr_err("%s: invalid SD line\n",
+ __func__);
+ goto error_invalid_data;
+ }
+ break;
+ case 2:
+ switch (mi2s_pdata->sd_lines) {
+ case MSM_MI2S_SD0 | MSM_MI2S_SD1:
+ dai_data->port_config.mi2s.line = AFE_I2S_QUAD01;
+ break;
+ case MSM_MI2S_SD2 | MSM_MI2S_SD3:
+ dai_data->port_config.mi2s.line = AFE_I2S_QUAD23;
+ break;
+ default:
+ pr_err("%s: invalid SD line\n",
+ __func__);
+ goto error_invalid_data;
+ }
+ break;
+ case 3:
+ switch (mi2s_pdata->sd_lines) {
+ case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2:
+ dai_data->port_config.mi2s.line = AFE_I2S_6CHS;
+ break;
+ default:
+ pr_err("%s: invalid SD lines\n",
+ __func__);
+ goto error_invalid_data;
+ }
+ break;
+ case 4:
+ switch (mi2s_pdata->sd_lines) {
+ case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2 | MSM_MI2S_SD3:
+ dai_data->port_config.mi2s.line = AFE_I2S_8CHS;
+ break;
+ default:
+ pr_err("%s: invalid SD lines\n",
+ __func__);
+ goto error_invalid_data;
+ }
+ break;
+ default:
+ pr_err("%s: invalid SD lines\n", __func__);
+ goto error_invalid_data;
+ }
+ if (mi2s_pdata->capability == MSM_MI2S_CAP_RX)
+ dai_driver->playback.channels_max = num_of_sd_lines << 1;
+
+ return 0;
+
+error_invalid_data:
+ return -EINVAL;
+}
+
static int msm_dai_q6_cdc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev);
@@ -280,6 +399,9 @@
case PRIMARY_I2S_RX:
rc = msm_dai_q6_cdc_hw_params(params, dai, substream->stream);
break;
+ case MI2S_RX:
+ rc = msm_dai_q6_mi2s_hw_params(params, dai, substream->stream);
+ break;
case HDMI_RX:
rc = msm_dai_q6_hdmi_hw_params(params, dai);
break;
@@ -592,6 +714,31 @@
return 0;
}
+static int msm_dai_q6_dai_mi2s_probe(struct snd_soc_dai *dai)
+{
+ struct msm_dai_q6_dai_data *dai_data;
+ int rc = 0;
+
+ dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data),
+ GFP_KERNEL);
+
+ if (!dai_data) {
+ dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n",
+ dai->id);
+ rc = -ENOMEM;
+ goto rtn;
+ } else
+ dev_set_drvdata(dai->dev, dai_data);
+
+ rc = msm_dai_q6_mi2s_platform_data_validation(dai);
+ if (rc != 0) {
+ pr_err("%s: The msm_dai_q6_mi2s_platform_data_validation failed\n",
+ __func__);
+ kfree(dai_data);
+ }
+rtn:
+ return rc;
+}
static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai)
{
@@ -649,6 +796,7 @@
switch (dai->id) {
case PRIMARY_I2S_TX:
case PRIMARY_I2S_RX:
+ case MI2S_RX:
rc = msm_dai_q6_cdc_set_fmt(dai, fmt);
break;
default:
@@ -892,6 +1040,20 @@
},
};
+static struct snd_soc_dai_driver msm_dai_q6_mi2s_rx_dai = {
+ .playback = {
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .ops = &msm_dai_q6_ops,
+ .probe = msm_dai_q6_dai_mi2s_probe,
+ .remove = msm_dai_q6_dai_probe,
+};
+
/* To do: change to register DAIs as batch */
static __devinit int msm_dai_q6_dev_probe(struct platform_device *pdev)
{
@@ -914,6 +1076,10 @@
rc = snd_soc_register_dai(&pdev->dev,
&msm_dai_q6_aux_pcm_tx_dai);
break;
+ case MI2S_RX:
+ rc = snd_soc_register_dai(&pdev->dev,
+ &msm_dai_q6_mi2s_rx_dai);
+ break;
case HDMI_RX:
rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_hdmi_rx_dai);
break;
diff --git a/sound/soc/msm/msm-pcm-routing.c b/sound/soc/msm/msm-pcm-routing.c
index e7c2694..2815daa 100644
--- a/sound/soc/msm/msm-pcm-routing.c
+++ b/sound/soc/msm/msm-pcm-routing.c
@@ -117,6 +117,7 @@
{ VOICE_PLAYBACK_TX, 0, NULL, 0, 0},
{ VOICE_RECORD_RX, 0, NULL, 0, 0},
{ VOICE_RECORD_TX, 0, NULL, 0, 0},
+ { MI2S_RX, 0, NULL, 0, 0},
};
@@ -682,6 +683,21 @@
msm_routing_put_audio_mixer),
};
+static const struct snd_kcontrol_new mi2s_rx_mixer_controls[] = {
+ SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_MI2S_RX ,
+ MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,
+ msm_routing_put_audio_mixer),
+ SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_MI2S_RX,
+ MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer,
+ msm_routing_put_audio_mixer),
+ SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_MI2S_RX,
+ MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer,
+ msm_routing_put_audio_mixer),
+ SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_MI2S_RX,
+ MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer,
+ msm_routing_put_audio_mixer),
+};
+
static const struct snd_kcontrol_new hdmi_mixer_controls[] = {
SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_HDMI_RX,
MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,
@@ -1118,6 +1134,7 @@
SND_SOC_DAPM_AIF_OUT("PRI_I2S_RX", "Primary I2S Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_RX", "Slimbus Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_OUT("HDMI", "HDMI Playback", 0, 0, 0 , 0),
+ SND_SOC_DAPM_AIF_OUT("MI2S_RX", "MI2S Playback", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("PRI_I2S_TX", "Primary I2S Capture", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_IN("SLIMBUS_0_TX", "Slimbus Capture", 0, 0, 0, 0),
SND_SOC_DAPM_AIF_OUT("INT_BT_SCO_RX", "Internal BT-SCO Playback",
@@ -1151,6 +1168,8 @@
slimbus_rx_mixer_controls, ARRAY_SIZE(slimbus_rx_mixer_controls)),
SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0,
hdmi_mixer_controls, ARRAY_SIZE(hdmi_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ mi2s_rx_mixer_controls, ARRAY_SIZE(mi2s_rx_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0,
mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)),
SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0,
@@ -1228,6 +1247,11 @@
{"MultiMedia1 Mixer", "VOC_REC_UL", "INCALL_RECORD_TX"},
{"MultiMedia1 Mixer", "VOC_REC_DL", "INCALL_RECORD_RX"},
+ {"MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"},
+ {"MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"},
+ {"MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"},
+ {"MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"},
+ {"MI2S_RX", NULL, "MI2S_RX Audio Mixer"},
{"MultiMedia1 Mixer", "PRI_TX", "PRI_I2S_TX"},
{"MultiMedia1 Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"},
diff --git a/sound/soc/msm/msm-pcm-routing.h b/sound/soc/msm/msm-pcm-routing.h
index 2020939..a8d2d91 100644
--- a/sound/soc/msm/msm-pcm-routing.h
+++ b/sound/soc/msm/msm-pcm-routing.h
@@ -30,6 +30,7 @@
#define LPASS_BE_INCALL_RECORD_RX "(Backend) INCALL_RECORD_TX"
#define LPASS_BE_INCALL_RECORD_TX "(Backend) INCALL_RECORD_RX"
+#define LPASS_BE_MI2S_RX "(Backend) MI2S_RX"
/* For multimedia front-ends, asm session is allocated dynamically.
* Hence, asm session/multimedia front-end mapping has to be maintained.
@@ -69,6 +70,7 @@
MSM_BACKEND_DAI_VOICE_PLAYBACK_TX,
MSM_BACKEND_DAI_INCALL_RECORD_RX,
MSM_BACKEND_DAI_INCALL_RECORD_TX,
+ MSM_BACKEND_DAI_MI2S_RX,
MSM_BACKEND_DAI_MAX,
};