Browse Source

asoc: lpass-cdc: Add support for HiFi FIR filter

Enable HiFi FIR PCM filter on digital codec to support HiFi audio playback on headset.

Change-Id: I5bc03ed45a3fd149c93dc04f33be0a581b519d44
Signed-off-by: Junkai Cai <[email protected]>
Junkai Cai 4 years ago
parent
commit
b7e259a57b

+ 24 - 0
asoc/codecs/lpass-cdc/lpass-cdc-regmap.c

@@ -1208,6 +1208,30 @@ static bool lpass_cdc_is_volatile_register(struct device *dev,
 	case LPASS_CDC_RX_SIDETONE_IIR0_IIR_COEF_B2_CTL:
 	case LPASS_CDC_RX_SIDETONE_IIR1_IIR_COEF_B1_CTL:
 	case LPASS_CDC_RX_SIDETONE_IIR1_IIR_COEF_B2_CTL:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_ADDR:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA0:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA1:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA2:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA3:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA4:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA5:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA6:
+	case LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA7:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_ADDR:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA0:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA1:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA2:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA3:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA4:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA5:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA6:
+	case LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA7:
+	case LPASS_CDC_RX_RX0_RX_FIR_CTL:
+	case LPASS_CDC_RX_RX1_RX_FIR_CTL:
+	case LPASS_CDC_RX_RX0_RX_FIR_CFG:
+	case LPASS_CDC_RX_RX1_RX_FIR_CFG:
+	case LPASS_CDC_RX_RX0_RX_PATH_CTL:
+	case LPASS_CDC_RX_RX1_RX_PATH_CTL:
 		return true;
 	}
 	return false;

+ 493 - 1
asoc/codecs/lpass-cdc/lpass-cdc-rx-macro.c

@@ -54,6 +54,13 @@
 
 #define LPASS_CDC_RX_MACRO_INTERP_MUX_NUM_INPUTS 3
 #define LPASS_CDC_RX_MACRO_SIDETONE_IIR_COEFF_MAX 5
+#define LPASS_CDC_RX_MACRO_FIR_COEFF_MAX 100
+#define LPASS_CDC_RX_MACRO_FIR_COEFF_ARRAY_MAX \
+	(LPASS_CDC_RX_MACRO_FIR_COEFF_MAX + 1)
+/* first value represent number of coefficients in each 100 integer group */
+#define LPASS_CDC_RX_MACRO_FIR_FILTER_BYTES \
+	(sizeof(u32) * LPASS_CDC_RX_MACRO_FIR_COEFF_ARRAY_MAX)
+
 
 #define STRING(name) #name
 #define LPASS_CDC_RX_MACRO_DAPM_ENUM(name, reg, offset, text) \
@@ -360,6 +367,37 @@ struct lpass_cdc_rx_macro_iir_filter_ctl {
 	} \
 }
 
+/* Codec supports 2 FIR filters Path */
+enum {
+	RX0_PATH = 0,
+	RX1_PATH,
+	FIR_PATH_MAX,
+};
+
+/* Each RX Path has 2 group of coefficients */
+enum {
+	GRP0 = 0,
+	GRP1,
+	GRP_MAX,
+};
+
+struct lpass_cdc_rx_macro_fir_filter_ctl {
+	unsigned int path_idx;
+	unsigned int grp_idx;
+	struct soc_bytes_ext bytes_ext;
+};
+
+#define LPASS_CDC_RX_MACRO_FIR_FILTER_CTL(xname, pidx, gidx) \
+{	 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	 .info = lpass_cdc_rx_macro_fir_filter_info, \
+	 .get = lpass_cdc_rx_macro_fir_audio_mixer_get, \
+	 .put = lpass_cdc_rx_macro_fir_audio_mixer_put, \
+	 .private_value = (unsigned long)&(struct lpass_cdc_rx_macro_fir_filter_ctl) { \
+		.path_idx = pidx, \
+		.grp_idx = gidx, \
+		.bytes_ext = {.max = LPASS_CDC_RX_MACRO_FIR_FILTER_BYTES, }, \
+	} \
+}
 
 struct lpass_cdc_rx_macro_idle_detect_config {
 	u8 hph_idle_thr;
@@ -459,6 +497,9 @@ struct lpass_cdc_rx_macro_priv {
 	int rx_mclk_cnt;
 	bool is_native_on;
 	bool is_ear_mode_on;
+	bool is_fir_filter_on;
+	bool is_fir_coeff_ready[FIR_PATH_MAX][GRP_MAX];
+	bool is_fir_capable;
 	bool dev_up;
 	bool hph_pwr_mode;
 	bool hph_hd2_mode;
@@ -477,7 +518,9 @@ struct lpass_cdc_rx_macro_priv {
 	struct lpass_cdc_rx_macro_idle_detect_config idle_det_cfg;
 	u8 sidetone_coeff_array[IIR_MAX][BAND_MAX]
 		[LPASS_CDC_RX_MACRO_SIDETONE_IIR_COEFF_MAX * 4];
-
+	u32 fir_coeff_array[FIR_PATH_MAX][GRP_MAX]
+		[LPASS_CDC_RX_MACRO_FIR_COEFF_MAX];
+	u32 num_fir_coeff[FIR_PATH_MAX][GRP_MAX];
 	struct platform_device *pdev_child_devices
 			[LPASS_CDC_RX_MACRO_CHILD_DEVICES_MAX];
 	int child_count;
@@ -486,6 +529,7 @@ struct lpass_cdc_rx_macro_priv {
 	int softclip_clk_users;
 	u16 clk_id;
 	u16 default_clk_id;
+	struct clk *hifi_fir_clk;
 	int8_t rx0_gain_val;
 	int8_t rx1_gain_val;
 };
@@ -559,6 +603,10 @@ static const char * const lpass_cdc_rx_macro_vbat_bcl_gsm_mode_text[] = {"OFF",
 static const struct soc_enum lpass_cdc_rx_macro_vbat_bcl_gsm_mode_enum =
 	SOC_ENUM_SINGLE_EXT(2, lpass_cdc_rx_macro_vbat_bcl_gsm_mode_text);
 
+static const char *const lpass_cdc_rx_macro_fir_filter_text[] = {"OFF", "ON"};
+static const struct soc_enum lpass_cdc_rx_macro_fir_filter_enum =
+	SOC_ENUM_SINGLE_EXT(2, lpass_cdc_rx_macro_fir_filter_text);
+
 static const struct snd_kcontrol_new rx_int2_1_vbat_mix_switch[] = {
 	SOC_DAPM_SINGLE("RX AUX VBAT Enable", SND_SOC_NOPM, 0, 1, 0)
 };
@@ -3119,6 +3167,405 @@ static int lpass_cdc_rx_macro_set_iir_gain(struct snd_soc_dapm_widget *w,
 	return 0;
 }
 
+static int lpass_cdc_rx_macro_fir_filter_enable_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+				snd_soc_kcontrol_component(kcontrol);
+	struct device *rx_dev = NULL;
+	struct lpass_cdc_rx_macro_priv *rx_priv = NULL;
+
+	if (!component) {
+		pr_err("%s: component is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!lpass_cdc_rx_macro_get_data(component, &rx_dev, &rx_priv, __func__))
+		return -EINVAL;
+
+	ucontrol->value.bytes.data[0] = (unsigned char)rx_priv->is_fir_filter_on;
+	return 0;
+}
+
+static int lpass_cdc_rx_macro_fir_filter_enable_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+				snd_soc_kcontrol_component(kcontrol);
+	struct device *rx_dev = NULL;
+	struct lpass_cdc_rx_macro_priv *rx_priv = NULL;
+	int ret = 0;
+
+	if (!component) {
+		pr_err("%s: component is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!lpass_cdc_rx_macro_get_data(component, &rx_dev, &rx_priv, __func__))
+		return -EINVAL;
+
+	if (!rx_priv->hifi_fir_clk) {
+		dev_dbg(rx_priv->dev, "%s: Undefined HIFI FIR Clock.\n",
+				__func__);
+		return 0;
+	}
+
+	if (!rx_priv->is_fir_capable) {
+		dev_dbg(rx_priv->dev, "%s: HIFI FIR is not supported.\n",
+				__func__);
+		return 0;
+	}
+
+	rx_priv->is_fir_filter_on =
+			(!ucontrol->value.bytes.data[0] ? false : true);
+
+	dev_dbg(rx_priv->dev, "%s:is_fir_filter_on=%d\n",
+				__func__, rx_priv->is_fir_filter_on);
+
+	if (rx_priv->is_fir_filter_on) {
+		ret = clk_prepare_enable(rx_priv->hifi_fir_clk);
+		if (ret < 0) {
+			dev_err_ratelimited(rx_priv->dev, "%s:hifi_fir_clk enable failed\n",
+						__func__);
+			return ret;
+		}
+
+		/* Enable HIFI_FEAT_EN bit */
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_TOP_TOP_CFG1, 0x01, 0x01);
+		/* Enable FIR_CLK_EN */
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX0_RX_PATH_CTL, 0x80, 0x80);
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX1_RX_PATH_CTL, 0x80, 0x80);
+		/* Start the FIR filter */
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX0_RX_FIR_CTL, 0x0D, 0x05);
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX1_RX_FIR_CTL, 0x0D, 0x05);
+	} else {
+		/* Stop the FIR filter */
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX0_RX_FIR_CTL, 0x0D, 0x00);
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX1_RX_FIR_CTL, 0x0D, 0x00);
+		/* Disable FIR_CLK_EN */
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX0_RX_PATH_CTL, 0x80, 0x00);
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_RX1_RX_PATH_CTL, 0x80, 0x00);
+		/* Disable HIFI_FEAT_EN bit */
+		snd_soc_component_update_bits(component, LPASS_CDC_RX_TOP_TOP_CFG1, 0x01, 0x00);
+
+		clk_disable_unprepare(rx_priv->hifi_fir_clk);
+	}
+
+	return 0;
+}
+
+static int lpass_cdc_rx_macro_fir_filter_info(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *ucontrol)
+{
+	struct lpass_cdc_rx_macro_fir_filter_ctl *ctl =
+		(struct lpass_cdc_rx_macro_fir_filter_ctl *)kcontrol->private_value;
+	struct soc_bytes_ext *params = &ctl->bytes_ext;
+
+	ucontrol->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	ucontrol->count = params->max;
+	return 0;
+}
+
+static int lpass_cdc_rx_macro_fir_audio_mixer_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+				snd_soc_kcontrol_component(kcontrol);
+	struct lpass_cdc_rx_macro_fir_filter_ctl *ctl =
+		(struct lpass_cdc_rx_macro_fir_filter_ctl *)kcontrol->private_value;
+	unsigned int path_idx = ctl->path_idx;
+	unsigned int grp_idx = ctl->grp_idx;
+	u32 num_coeff_grp = 0;
+	u32 readArray[LPASS_CDC_RX_MACRO_FIR_COEFF_ARRAY_MAX];
+
+	unsigned int coeff_idx = 0, array_idx = 0;
+	unsigned int copy_size;
+	struct device *rx_dev = NULL;
+	struct lpass_cdc_rx_macro_priv *rx_priv = NULL;
+
+	if (!component) {
+		pr_err("%s: component is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!lpass_cdc_rx_macro_get_data(component, &rx_dev, &rx_priv, __func__))
+		return -EINVAL;
+
+	num_coeff_grp = rx_priv->num_fir_coeff[path_idx][grp_idx];
+	readArray[array_idx++] = num_coeff_grp;
+
+	for (coeff_idx = 0; coeff_idx < num_coeff_grp; coeff_idx++) {
+		readArray[array_idx++] =
+				rx_priv->fir_coeff_array[path_idx][grp_idx][coeff_idx];
+	}
+	copy_size = array_idx;
+
+	memcpy(ucontrol->value.bytes.data, &readArray[0], sizeof(readArray[0]) * copy_size);
+
+	return 0;
+}
+
+static int set_fir_filter_coeff(struct snd_soc_component *component,
+				struct lpass_cdc_rx_macro_priv *rx_priv,
+				unsigned int path_idx)
+{
+	int grp_idx = 0, coeff_idx = 0;
+	unsigned int ret = 0;
+	unsigned int sum_num_coeff, max_coeff_num, num_coeff_grp;
+	unsigned int path_ctl_addr, wdata0_addr, coeff_addr;
+	unsigned int fir_ctl_addr, num_coeff_addr;
+
+	switch (path_idx) {
+	case RX0_PATH:
+		path_ctl_addr = LPASS_CDC_RX_RX0_RX_PATH_CTL;
+		wdata0_addr = LPASS_CDC_RX_RX0_RX_FIR_COEFF_WDATA0;
+		coeff_addr = LPASS_CDC_RX_RX0_RX_FIR_COEFF_ADDR;
+		fir_ctl_addr = LPASS_CDC_RX_RX0_RX_FIR_CTL;
+		num_coeff_addr = LPASS_CDC_RX_RX0_RX_FIR_CFG;
+		break;
+	case RX1_PATH:
+		path_ctl_addr = LPASS_CDC_RX_RX1_RX_PATH_CTL;
+		wdata0_addr = LPASS_CDC_RX_RX1_RX_FIR_COEFF_WDATA0;
+		coeff_addr = LPASS_CDC_RX_RX1_RX_FIR_COEFF_ADDR;
+		fir_ctl_addr = LPASS_CDC_RX_RX1_RX_FIR_CTL;
+		num_coeff_addr = LPASS_CDC_RX_RX1_RX_FIR_CFG;
+		break;
+	default:
+		dev_err(rx_priv->dev,
+			"%s: inavlid FIR ID: %d\n", __func__, path_idx);
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	max_coeff_num = LPASS_CDC_RX_MACRO_FIR_COEFF_MAX;
+	sum_num_coeff = 0;
+	for (grp_idx = 0; grp_idx < GRP_MAX; grp_idx++) {
+		sum_num_coeff += rx_priv->num_fir_coeff[path_idx][grp_idx];
+	}
+
+	ret = lpass_cdc_rx_macro_mclk_enable(rx_priv, 1, false);
+	if (ret < 0) {
+		dev_err_ratelimited(rx_priv->dev, "%s:rx_macro_mclk enable failed\n",
+					__func__);
+		goto exit;
+	}
+
+	ret = clk_prepare_enable(rx_priv->hifi_fir_clk);
+	if (ret < 0) {
+		dev_err_ratelimited(rx_priv->dev, "%s:hifi_fir_clk enable failed\n",
+					__func__);
+		goto disable_mclk_block;
+	}
+
+	/* Enable HIFI_FEAT_EN bit */
+	snd_soc_component_update_bits(component, LPASS_CDC_RX_TOP_TOP_CFG1, 0x01, 0x01);
+	/* Enable FIR_CLK_EN, datapath reset */
+	snd_soc_component_update_bits(component, path_ctl_addr, 0xC0, 0xC0);
+	/* Enable FIR_CLK_EN, Release Reset */
+	snd_soc_component_update_bits(component, path_ctl_addr, 0xC0, 0x80);
+
+	/* wait for data ram initialization after enabling clock  */
+	usleep_range(10, 11);
+	snd_soc_component_write(component, num_coeff_addr, sum_num_coeff);
+	dev_dbg(rx_priv->dev, "TEST: %s: sum_num_coeff:0x%x\n",
+				__func__, sum_num_coeff);
+
+	for (grp_idx = 0; grp_idx < GRP_MAX; grp_idx++) {
+		unsigned int coeff_idx_start = 0, array_idx = 0;
+
+		num_coeff_grp = rx_priv->num_fir_coeff[path_idx][grp_idx];
+		if (num_coeff_grp > max_coeff_num) {
+			dev_err(rx_priv->dev,
+				"%s: inavlid number of RX_FIR coefficients:%d"
+				" in path:%d, group:%d\n",
+				__func__, num_coeff_grp, path_idx, grp_idx);
+			ret = -EINVAL;
+			goto disable_FIR;
+		}
+		coeff_idx_start = grp_idx * max_coeff_num;
+
+		for (coeff_idx = coeff_idx_start;
+			coeff_idx < coeff_idx_start + num_coeff_grp / 2 * 2;
+			coeff_idx += 2) {
+
+			unsigned int addr_offset = coeff_idx / 2;
+
+			/* First coefficient in pair */
+			u32 value = rx_priv->fir_coeff_array[path_idx][grp_idx][array_idx++];
+			dev_dbg(rx_priv->dev, "TEST: %s: val of coeff_idx:%d, COEFF:0x%x\n",
+						__func__, coeff_idx, value);
+			snd_soc_component_write(component, wdata0_addr,
+					value & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0x4,
+					(value >> 8) & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0x8,
+					(value >> 16) & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0xC,
+					(value >> 24) & 0xFF);
+
+			/* Second coefficient in pair */
+			value = rx_priv->fir_coeff_array[path_idx][grp_idx][array_idx++];
+			dev_dbg(rx_priv->dev, "TEST: %s: val of coeff_idx:%d, COEFF:0x%x\n",
+						__func__, coeff_idx, value);
+			snd_soc_component_write(component, wdata0_addr + 0x10,
+					value & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0x14,
+					(value >> 8) & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0x18,
+					(value >> 16) & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0x1C,
+					(value >> 24) & 0xFF);
+
+			snd_soc_component_write(component, coeff_addr, addr_offset);
+			snd_soc_component_update_bits(component, fir_ctl_addr, 0x02, 0x02);
+			usleep_range(13, 15);
+			snd_soc_component_update_bits(component, fir_ctl_addr, 0x02, 0x00);
+		}
+
+		/* odd number of coefficients in this group, handle last one */
+		if (num_coeff_grp % 2 != 0) {
+			int addr_offset = coeff_idx / 2;
+
+			/* First coefficient in pair */
+			u32 value = rx_priv->fir_coeff_array[path_idx][grp_idx][array_idx++];
+			dev_dbg(rx_priv->dev, "TEST: %s: val of coeff_idx:%d, COEFF:0x%x\n",
+						__func__, coeff_idx, value);
+			snd_soc_component_write(component, wdata0_addr,
+					value & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0x4,
+					(value >> 8) & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0x8,
+					(value >> 16) & 0xFF);
+			snd_soc_component_write(component, wdata0_addr + 0xC,
+					(value >> 24) & 0xFF);
+
+			/* Second coefficient in pair */
+			dev_dbg(rx_priv->dev, "TEST: %s: val of coeff_idx:%d, COEFF:0x%x\n",
+						__func__, coeff_idx, 0x0);
+			snd_soc_component_write(component, wdata0_addr + 0x10, 0x0);
+			snd_soc_component_write(component, wdata0_addr + 0x14, 0x0);
+			snd_soc_component_write(component, wdata0_addr + 0x18, 0x0);
+			snd_soc_component_write(component, wdata0_addr + 0x1C, 0x0);
+
+			snd_soc_component_write(component, coeff_addr, addr_offset);
+			snd_soc_component_update_bits(component, fir_ctl_addr, 0x02, 0x02);
+			usleep_range(13, 15);
+			snd_soc_component_update_bits(component, fir_ctl_addr, 0x02, 0x00);
+		}
+
+	}
+
+disable_FIR:
+	/* disable FIR_CLK_EN */
+	snd_soc_component_update_bits(component, path_ctl_addr, 0x80, 0x00);
+
+	/* Disable HIFI_FEAT_EN bit */
+	snd_soc_component_update_bits(component, LPASS_CDC_RX_TOP_TOP_CFG1, 0x01, 0x00);
+
+	clk_disable_unprepare(rx_priv->hifi_fir_clk);
+
+disable_mclk_block:
+	ret = lpass_cdc_rx_macro_mclk_enable(rx_priv, 0, false);
+
+exit:
+	for (grp_idx = 0; grp_idx < GRP_MAX; grp_idx++) {
+		rx_priv->is_fir_coeff_ready[path_idx][grp_idx] = false;
+	}
+	return ret;
+}
+
+static int lpass_cdc_rx_macro_fir_audio_mixer_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component =
+				snd_soc_kcontrol_component(kcontrol);
+	struct lpass_cdc_rx_macro_fir_filter_ctl *ctl =
+		(struct lpass_cdc_rx_macro_fir_filter_ctl *)kcontrol->private_value;
+	unsigned int path_idx = ctl->path_idx;
+	unsigned int grp_idx = ctl->grp_idx;
+	u32 ele_size = 0, num_coeff_grp = 0;
+	u32 coeff[LPASS_CDC_RX_MACRO_FIR_COEFF_ARRAY_MAX];
+
+	int ret = 0;
+	bool coeff_ready = true;
+	unsigned int grp_iidx = 0, coeff_idx = 0, array_idx = 0;
+	struct device *rx_dev = NULL;
+	struct lpass_cdc_rx_macro_priv *rx_priv = NULL;
+
+	if (!component) {
+		pr_err("%s: component is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!lpass_cdc_rx_macro_get_data(component, &rx_dev, &rx_priv, __func__))
+		return -EINVAL;
+
+	if (!rx_priv->hifi_fir_clk) {
+		dev_dbg(rx_priv->dev, "%s: Undefined HIFI FIR Clock.\n",
+				__func__);
+		return 0;
+	}
+
+	if (!rx_priv->is_fir_capable) {
+		dev_dbg(rx_priv->dev, "%s: HIFI FIR is not supported.\n",
+				__func__);
+		return 0;
+	}
+
+	ele_size = sizeof(coeff[0]);
+	memcpy(&coeff[0], ucontrol->value.bytes.data, ele_size);
+	num_coeff_grp = coeff[0];
+
+	dev_dbg(rx_priv->dev, "%s: bytes.data: path:%d, grp:%d, num_coeff_grp:%d\n",
+		__func__, path_idx, grp_idx, num_coeff_grp);
+
+	if (num_coeff_grp > LPASS_CDC_RX_MACRO_FIR_COEFF_MAX) {
+		dev_err(rx_priv->dev,
+			"%s: inavlid number of RX_FIR coefficients:%d in path:%d, group:%d\n",
+				 __func__, num_coeff_grp, path_idx, grp_idx);
+		rx_priv->num_fir_coeff[path_idx][grp_idx] = 0;
+		return -EINVAL;
+	} else {
+		rx_priv->num_fir_coeff[path_idx][grp_idx] = num_coeff_grp;
+	}
+
+	memcpy(&coeff[1], &(ucontrol->value.bytes.data[ele_size]), ele_size * num_coeff_grp);
+
+	/* Store the coefficients in FIR coeff array */
+	array_idx = 1;
+	for (coeff_idx = 0; coeff_idx < num_coeff_grp; coeff_idx++)
+		rx_priv->fir_coeff_array[path_idx][grp_idx][coeff_idx] = coeff[array_idx++];
+
+	/*
+	 * Set all of followed groups flag to ready if one group is not full(last group)
+	 * to ensure all followed groups ready flag set even all-zero group presents
+	 * Only last group is expected to have unfilled coefficients
+	 */
+	if (num_coeff_grp < LPASS_CDC_RX_MACRO_FIR_COEFF_MAX) {
+		for (grp_iidx = grp_idx; grp_iidx < GRP_MAX; grp_iidx++) {
+			rx_priv->is_fir_coeff_ready[path_idx][grp_iidx] = true;
+			if (grp_iidx != grp_idx)
+				rx_priv->num_fir_coeff[path_idx][grp_iidx] = 0;
+		}
+	} else {
+		rx_priv->is_fir_coeff_ready[path_idx][grp_idx] = true;
+	}
+
+	for (grp_iidx = 0; grp_iidx < GRP_MAX; grp_iidx++) {
+		coeff_ready &= rx_priv->is_fir_coeff_ready[path_idx][grp_iidx];
+	}
+
+	if (coeff_ready) {
+		ret = set_fir_filter_coeff(component, rx_priv, path_idx);
+		if (ret < 0) {
+			rx_priv->num_fir_coeff[path_idx][grp_idx] = 0;
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
 static const struct snd_kcontrol_new lpass_cdc_rx_macro_snd_controls[] = {
 	SOC_SINGLE_S8_TLV("RX_RX0 Digital Volume",
 			  LPASS_CDC_RX_RX0_RX_VOL_CTL,
@@ -3150,6 +3597,9 @@ static const struct snd_kcontrol_new lpass_cdc_rx_macro_snd_controls[] = {
 	SOC_ENUM_EXT("RX_EAR Mode", lpass_cdc_rx_macro_ear_mode_enum,
 		lpass_cdc_rx_macro_get_ear_mode, lpass_cdc_rx_macro_put_ear_mode),
 
+	SOC_ENUM_EXT("RX_FIR Filter", lpass_cdc_rx_macro_fir_filter_enum,
+		lpass_cdc_rx_macro_fir_filter_enable_get, lpass_cdc_rx_macro_fir_filter_enable_put),
+
 	SOC_ENUM_EXT("RX_HPH HD2 Mode", lpass_cdc_rx_macro_hph_hd2_mode_enum,
 		lpass_cdc_rx_macro_get_hph_hd2_mode, lpass_cdc_rx_macro_put_hph_hd2_mode),
 
@@ -3232,6 +3682,11 @@ static const struct snd_kcontrol_new lpass_cdc_rx_macro_snd_controls[] = {
 	LPASS_CDC_RX_MACRO_IIR_FILTER_CTL("IIR1 Band3", IIR1, BAND3),
 	LPASS_CDC_RX_MACRO_IIR_FILTER_CTL("IIR1 Band4", IIR1, BAND4),
 	LPASS_CDC_RX_MACRO_IIR_FILTER_CTL("IIR1 Band5", IIR1, BAND5),
+
+	LPASS_CDC_RX_MACRO_FIR_FILTER_CTL("RX0 FIR Coeff Group0", RX0_PATH, GRP0),
+	LPASS_CDC_RX_MACRO_FIR_FILTER_CTL("RX0 FIR Coeff Group1", RX0_PATH, GRP1),
+	LPASS_CDC_RX_MACRO_FIR_FILTER_CTL("RX1 FIR Coeff Group0", RX1_PATH, GRP0),
+	LPASS_CDC_RX_MACRO_FIR_FILTER_CTL("RX1 FIR Coeff Group1", RX1_PATH, GRP1),
 };
 
 static int lpass_cdc_rx_macro_enable_echo(struct snd_soc_dapm_widget *w,
@@ -3863,6 +4318,33 @@ exit:
 	return ret;
 }
 
+/**
+ * lpass_cdc_rx_set_fir_capability - Set RX HIFI FIR Filter capability
+ *
+ * @component: Codec component ptr.
+ * @capable: if the target have RX HIFI FIR available.
+ *
+ * Set RX HIFI FIR capability, stored the capability into RX macro private data.
+ */
+int lpass_cdc_rx_set_fir_capability(struct snd_soc_component *component, bool capable)
+{
+	struct device *rx_dev = NULL;
+	struct lpass_cdc_rx_macro_priv *rx_priv = NULL;
+
+	if (!component) {
+		pr_err("%s: component is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!lpass_cdc_rx_macro_get_data(component, &rx_dev, &rx_priv, __func__))
+		return -EINVAL;
+
+	rx_priv->is_fir_capable = capable;
+
+	return 0;
+}
+EXPORT_SYMBOL(lpass_cdc_rx_set_fir_capability);
+
 static const struct lpass_cdc_rx_macro_reg_mask_val
 				lpass_cdc_rx_macro_reg_init[] = {
 	{LPASS_CDC_RX_RX0_RX_PATH_SEC7, 0x07, 0x02},
@@ -4088,6 +4570,7 @@ static int lpass_cdc_rx_macro_probe(struct platform_device *pdev)
 	char __iomem *rx_io_base = NULL, *muxsel_io = NULL;
 	int ret = 0;
 	u32 default_clk_id = 0;
+	struct clk *hifi_fir_clk = NULL;
 	u32 is_used_rx_swr_gpio = 1;
 	const char *is_used_rx_swr_gpio_dt = "qcom,is-used-swr-gpio";
 
@@ -4181,6 +4664,15 @@ static int lpass_cdc_rx_macro_probe(struct platform_device *pdev)
 	ops.clk_id_req = rx_priv->clk_id;
 	ops.default_clk_id = default_clk_id;
 
+	hifi_fir_clk = devm_clk_get(&pdev->dev, "rx_mclk2_2x_clk");
+	if (IS_ERR(hifi_fir_clk)) {
+		ret = PTR_ERR(hifi_fir_clk);
+		dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n",
+			__func__, "rx_mclk2_2x_clk", ret);
+		hifi_fir_clk = NULL;
+	}
+	rx_priv->hifi_fir_clk = hifi_fir_clk;
+
 	rx_priv->is_aux_hpf_on = 1;
 
 	dev_set_drvdata(&pdev->dev, rx_priv);

+ 11 - 1
asoc/codecs/lpass-cdc/lpass-cdc.h

@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
-/* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
  */
 
 #ifndef LPASS_CDC_H
@@ -110,6 +110,10 @@ int lpass_cdc_tx_mclk_enable(struct snd_soc_component *c, bool enable);
 int lpass_cdc_get_version(struct device *dev);
 int lpass_cdc_dmic_clk_enable(struct snd_soc_component *component,
 			   u32 dmic, u32 tx_mode, bool enable);
+
+/* RX MACRO utilities */
+int lpass_cdc_rx_set_fir_capability(struct snd_soc_component *component,
+					bool capable);
 #else
 static inline int lpass_cdc_register_res_clk(struct device *dev, rsc_clk_cb_t cb)
 {
@@ -204,5 +208,11 @@ static int lpass_cdc_tx_mclk_enable(struct snd_soc_component *c, bool enable)
 {
 	return 0;
 }
+/* RX MACRO utilities */
+static int lpass_cdc_rx_set_fir_capability(struct snd_soc_component *component,
+						bool capable)
+{
+	return 0;
+}
 #endif /* CONFIG_SND_SOC_LPASS_CDC */
 #endif /* LPASS_CDC_H */

+ 14 - 16
asoc/waipio.c

@@ -1351,7 +1351,9 @@ static int msm_int_wsa_init(struct snd_soc_pcm_runtime *rtd)
 
 static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd)
 {
+	int codec_variant = -1;
 	struct snd_soc_component *component = NULL;
+	struct snd_soc_component *lpass_cdc_component = NULL;
 	struct snd_soc_dapm_context *dapm = NULL;
 	struct snd_info_entry *entry = NULL;
 	struct snd_card *card = NULL;
@@ -1359,14 +1361,14 @@ static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd)
 				snd_soc_card_get_drvdata(rtd->card);
 	int ret = 0;
 
-	component = snd_soc_rtdcom_lookup(rtd, "lpass-cdc");
-	if (!component) {
+	lpass_cdc_component = snd_soc_rtdcom_lookup(rtd, "lpass-cdc");
+	if (!lpass_cdc_component) {
 		pr_err("%s: could not find component for lpass-cdc\n",
 			__func__);
 		return ret;
 	}
 
-	dapm = snd_soc_component_get_dapm(component);
+	dapm = snd_soc_component_get_dapm(lpass_cdc_component);
 
 	snd_soc_dapm_new_controls(dapm, msm_int_dapm_widgets,
 				ARRAY_SIZE(msm_int_dapm_widgets));
@@ -1386,7 +1388,7 @@ static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd)
 	snd_soc_dapm_ignore_suspend(dapm, "Analog Mic4");
 	snd_soc_dapm_ignore_suspend(dapm, "Analog Mic5");
 
-	lpass_cdc_set_port_map(component, ARRAY_SIZE(sm_port_map), sm_port_map);
+	lpass_cdc_set_port_map(lpass_cdc_component, ARRAY_SIZE(sm_port_map), sm_port_map);
 
 	card = rtd->card->snd_card;
 	if (!pdata->codec_root) {
@@ -1407,7 +1409,8 @@ static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd)
 
 	component = snd_soc_rtdcom_lookup(rtd, WCD938X_DRV_NAME);
 	if (!component) {
-		pr_err("%s component is NULL\n", __func__);
+		pr_err("%s could not find component for %s\n",
+			__func__, WCD938X_DRV_NAME);
 		return -EINVAL;
 	}
 	dapm = snd_soc_component_get_dapm(component);
@@ -1435,24 +1438,19 @@ static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd)
 		pdata->codec_root = entry;
 	}
 	wcd938x_info_create_codec_entry(pdata->codec_root, component);
-#if 0
+
 	codec_variant = wcd938x_get_codec_variant(component);
 	dev_dbg(component->dev, "%s: variant %d\n", __func__, codec_variant);
-	if (codec_variant == WCD9380)
-		ret = snd_soc_add_component_controls(component,
-					msm_int_wcd9380_snd_controls,
-					ARRAY_SIZE(msm_int_wcd9380_snd_controls));
-	else if (codec_variant == WCD9385)
-		ret = snd_soc_add_component_controls(component,
-					msm_int_wcd9385_snd_controls,
-					ARRAY_SIZE(msm_int_wcd9385_snd_controls));
+	if (codec_variant == WCD9385)
+		ret = lpass_cdc_rx_set_fir_capability(lpass_cdc_component, true);
+	else
+		ret = lpass_cdc_rx_set_fir_capability(lpass_cdc_component, false);
 
 	if (ret < 0) {
-		dev_err(component->dev, "%s: add codec specific snd controls failed: %d\n",
+		dev_err(component->dev, "%s: set fir capability failed: %d\n",
 			__func__, ret);
 		return ret;
 	}
-#endif
 done:
 	codec_reg_done = true;
 	msm_common_dai_link_init(rtd);