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,
 };