linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code
@ 2024-01-26  3:58 Shenghao Ding
  2024-01-26  3:58 ` [PATCH v2 2/4] ASoc: PCM6240: Create header file for " Shenghao Ding
                   ` (4 more replies)
  0 siblings, 5 replies; 13+ messages in thread
From: Shenghao Ding @ 2024-01-26  3:58 UTC (permalink / raw)
  To: broonie, conor+dt, krzysztof.kozlowski
  Cc: robh+dt, andriy.shevchenko, kevin-lu, baojun.xu, devicetree,
	v-po, lgirdwood, perex, pierre-louis.bossart, 13916275206,
	mohit.chawla, linux-sound, linux-kernel, liam.r.girdwood, soyer,
	jkhuang3, tiwai, pdjuandi, j-mcpherson, navada, Shenghao Ding

PCM6240 driver implements a flexible and configurable setting for register
and filter coefficients, to one, two or even multiple PCM6240 Family Audio
chips.

Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>

---
Change in v2:
 - All these chips have only a portion of the functionality of codec,
   such as ADC or DAC, and so on, but their audio performance is far
   superior to the codec's, and cost is lower than codec, and much easier
   to program than codec.
---
 sound/soc/codecs/pcm6240.c | 2062 ++++++++++++++++++++++++++++++++++++
 1 file changed, 2062 insertions(+)
 create mode 100644 sound/soc/codecs/pcm6240.c

diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c
new file mode 100644
index 000000000000..d9e4d57dc115
--- /dev/null
+++ b/sound/soc/codecs/pcm6240.c
@@ -0,0 +1,2062 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Texas Instruments PCM6240 Family Audio ADC/DAC/Router
+//
+// Copyright (C) 2022 - 2024 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// The PCM6240 driver implements a flexible and configurable
+// algo coefficient setting for one, two, or even multiple
+// PCM6240 Family chips.
+//
+// Author: Shenghao Ding <shenghao-ding@ti.com>
+//
+
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "pcm6240.h"
+
+static const struct i2c_device_id pcmdevice_i2c_id[] = {
+	{ "adc3120",  ADC3120  },
+	{ "adc5120",  ADC5120  },
+	{ "adc6120",  ADC6120  },
+	{ "dix4192",  DIX4192  },
+	{ "pcm1690",  PCM1690  },
+	{ "pcm3120",  PCM3120  },
+	{ "pcm3140",  PCM3140  },
+	{ "pcm5120",  PCM5120  },
+	{ "pcm5140",  PCM5140  },
+	{ "pcm6120",  PCM6120  },
+	{ "pcm6140",  PCM6140  },
+	{ "pcm6240",  PCM6240  },
+	{ "pcm6260",  PCM6260  },
+	{ "pcm9211",  PCM9211  },
+	{ "pcmd3140", PCMD3140 },
+	{ "pcmd3180", PCMD3180 },
+	{ "pcmd512x", PCMD512X },
+	{ "taa5212",  TAA5212  },
+	{ "taa5412",  TAA5412  },
+	{ "tad5212",  TAD5212  },
+	{ "tad5412",  TAD5412  },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, pcmdevice_i2c_id);
+
+static struct pcmdevice_mixer_control adc5120_analog_gain_ctl[] = {
+	{
+		.shift = 1,
+		.reg = ADC5120_REG_CH1_ANALOG_GAIN,
+		.max = 0x54,
+		.invert = 0,
+	},
+	{
+		.shift = 1,
+		.reg = ADC5120_REG_CH2_ANALOG_GAIN,
+		.max = 0x54,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control adc5120_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = ADC5120_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = ADC5120_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm1690_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH5_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH6_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH7_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH8_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6240_analog_gain_ctl[] = {
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH1_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH2_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH3_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH4_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6240_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6260_analog_gain_ctl[] = {
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH1_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH2_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH3_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH4_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH5_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH6_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6260_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH5_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH6_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm9211_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM9211_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM9211_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcmd3140_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcmd3180_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH5_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH6_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH7_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH8_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control taa5412_digital_volume_ctl[] = {
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH1_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH2_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH3_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH4_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+};
+
+static struct pcmdevice_mixer_control taa5412_fine_gain_ctl[] = {
+	{
+		.shift = 4,
+		.reg = TAA5412_REG_CH1_FINE_GAIN,
+		.max = 0xf,
+		.invert = 0,
+	},
+	{
+		.shift = 4,
+		.reg = TAA5412_REG_CH2_FINE_GAIN,
+		.max = 0xf,
+		.invert = 0,
+	},
+	{
+		.shift = 4,
+		.reg = TAA5412_REG_CH3_FINE_GAIN,
+		.max = 0xf,
+		.invert = 4,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH4_FINE_GAIN,
+		.max = 0xf,
+		.invert = 4,
+	},
+};
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcmd3180_dig_gain_tlv,
+	-10000, 2700);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcmd3140_dig_gain_tlv,
+	-10000, 2700);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_fine_dig_gain_tlv,
+	-12750, 0);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_dig_gain_tlv,
+	-25500, 0);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm9211_dig_gain_tlv,
+	-11450, 2000);
+static const DECLARE_TLV_DB_MINMAX_MUTE(adc5120_fgain_tlv,
+	-10050, 2700);
+static const DECLARE_TLV_DB_LINEAR(adc5120_chgain_tlv, 0, 4200);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm6260_fgain_tlv,
+	-10000, 2700);
+static const DECLARE_TLV_DB_LINEAR(pcm6260_chgain_tlv, 0, 4200);
+static const DECLARE_TLV_DB_MINMAX_MUTE(taa5412_dig_vol_tlv,
+	-8050, 4700);
+static const DECLARE_TLV_DB_LINEAR(taa5412_fine_gain_tlv,
+	-80, 70);
+
+static int pcmdev_change_dev(struct pcmdevice_priv *pcm_priv,
+	unsigned short dev_no)
+{
+	struct i2c_client *client = (struct i2c_client *)pcm_priv->client;
+	struct regmap *map = pcm_priv->regmap;
+	int ret = 0;
+
+	if (client->addr != pcm_priv->addr[dev_no]) {
+		client->addr = pcm_priv->addr[dev_no];
+		/* All pcmdevices share the same regmap, clear the page
+		 * inside regmap once switching to another pcmdevice.
+		 * Register 0 at any pages inside pcmdevice is the same
+		 * one for page-switching.
+		 */
+		ret = regmap_write(map, PCMDEVICE_PAGE_SELECT, 0);
+		if (ret < 0)
+			dev_err(pcm_priv->dev, "%s, E=%d\n", __func__, ret);
+	}
+
+	return ret;
+}
+
+static int pcmdev_dev_read(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned int *val)
+{
+	int ret = -EINVAL;
+
+	if (dev_no < pcm_dev->ndev) {
+		struct regmap *map = pcm_dev->regmap;
+
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+			goto out;
+		}
+
+		ret = regmap_read(map, reg, val);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, no such channel(%d)\n", __func__,
+			dev_no);
+
+
+out:
+	return ret;
+}
+
+static int pcmdev_dev_bulk_write(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned char *data,
+	unsigned int len)
+{
+	int ret = -EINVAL;
+
+	if (dev_no < pcm_dev->ndev) {
+		struct regmap *map = pcm_dev->regmap;
+
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+			goto out;
+		}
+
+		ret = regmap_bulk_write(map, reg, data, len);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "bulk_write ERROR, E=%d\n",
+				ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, ERROR: no such channel(%d)\n",
+			__func__, dev_no);
+
+out:
+	return ret;
+}
+
+static int pcmdev_dev_update_bits(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned int mask,
+	unsigned int value)
+{
+	struct regmap *map = pcm_dev->regmap;
+	int ret =  -EINVAL;
+
+	if (dev_no < pcm_dev->ndev) {
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n",
+				__func__, ret);
+			goto out;
+		}
+
+		ret = regmap_update_bits(map, reg, mask, value);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "update_bits ERROR, E=%d\n",
+				ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, ERROR: no such device(%d)\n",
+			__func__, dev_no);
+
+out:
+	return ret;
+}
+
+static int pcmdev_dev_write(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned int value)
+{
+	struct regmap *map = pcm_dev->regmap;
+	int ret = 0;
+
+	if (dev_no < pcm_dev->ndev) {
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n",
+				__func__, ret);
+			goto out;
+		}
+
+		ret = regmap_write(map, reg, value);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, ERROR: no such device(%d)\n",
+			__func__, dev_no);
+
+out:
+	return ret;
+}
+
+static int pcmdevice_info_profile(
+	struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_soc_component *codec
+		= snd_soc_kcontrol_component(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(codec);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = max(0, pcm_dev->regbin.ncfgs - 1);
+
+	return 0;
+}
+
+static int pcmdevice_get_profile_id(
+	struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec
+		= snd_soc_kcontrol_component(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = pcm_dev->cur_conf;
+
+	return 0;
+}
+
+static int pcmdevice_set_profile_id(
+	struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec
+		= snd_soc_kcontrol_component(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(codec);
+	int ret = 0;
+
+	if (pcm_dev->cur_conf != ucontrol->value.integer.value[0]) {
+		pcm_dev->cur_conf = ucontrol->value.integer.value[0];
+		ret = 1;
+	}
+
+	return ret;
+}
+
+static int pcmdevice_info_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mc->max;
+	return 0;
+}
+
+static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	int rc = 0;
+	unsigned int val;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+
+	mutex_lock(&pcm_dev->codec_lock);
+	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+
+	val = (val >> shift) & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	ucontrol->value.integer.value[0] = val;
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return rc;
+}
+
+static int pcmdevice_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	int err = 0;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	unsigned int val, val_mask;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	val = ucontrol->value.integer.value[0] & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	val_mask = mask << shift;
+	val = val << shift;
+	err = pcmdev_dev_update_bits(pcm_dev, dev_no, reg, val_mask,
+		val);
+	if (err) {
+		dev_err(pcm_dev->dev, "%s:update_bits, ERROR, E=%d\n",
+			__func__, err);
+		goto out;
+	}
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return err;
+}
+
+static int pcm1690_get_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	int rc = 0;
+	unsigned int val;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	rc = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL,
+		&val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read mode, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+	if (!(val & PCM1690_REG_MODE_CTRL_DAMS_MSK)) {
+		dev_info(pcm_dev->dev,
+			"%s: set to wide-range mode, before using this ctrl\n",
+			__func__);
+		ucontrol->value.integer.value[0] = -25500;
+		goto out;
+	}
+	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+
+	val = (val >> shift) & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	ucontrol->value.integer.value[0] = val;
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return rc;
+}
+
+static int pcm1690_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	int err = 0;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	unsigned int val, val_mask;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	val = ucontrol->value.integer.value[0] & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	val_mask = mask << shift;
+	val = val << shift;
+	err = pcmdev_dev_update_bits(pcm_dev, dev_no, reg,
+		PCM1690_REG_MODE_CTRL_DAMS_MSK | val_mask,
+		PCM1690_REG_MODE_CTRL_DAMS_WIDE_RANGE | val);
+	if (err) {
+		dev_err(pcm_dev->dev, "%s:update_bits, ERROR, E=%d\n",
+			__func__, err);
+		goto out;
+	}
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return err;
+}
+
+static int pcm1690_get_finevolsw(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	int rc = 0;
+	unsigned int val;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	rc = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL,
+		&val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read mode, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+	if (val & PCM1690_REG_MODE_CTRL_DAMS_MSK) {
+		dev_info(pcm_dev->dev,
+			"%s: Set to fine mode, before using this ctrl\n",
+			__func__);
+		ucontrol->value.integer.value[0] = -12750;
+		goto out;
+	}
+	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+
+	val = (val >> shift) & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	ucontrol->value.integer.value[0] = val;
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return rc;
+}
+
+static int pcm1690_put_finevolsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	int err = 0;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	unsigned int val, val_mask;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	val = ucontrol->value.integer.value[0] & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	val_mask = mask << shift;
+	val = val << shift;
+	err = pcmdev_dev_update_bits(pcm_dev, dev_no, reg,
+		PCM1690_REG_MODE_CTRL_DAMS_MSK | val_mask,
+		PCM1690_REG_MODE_CTRL_DAMS_FINE_STEP | val);
+	if (err) {
+		dev_err(pcm_dev->dev, "%s:update_bits, ERROR, E=%d\n",
+			__func__, err);
+		goto out;
+	}
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return err;
+}
+
+static int pcm1690_ctrl_add(struct pcmdevice_priv *pcm_dev)
+{
+	struct i2c_adapter *adap = pcm_dev->client->adapter;
+	struct pcmdev_ctrl_info fine_dig_ctl_info = {0};
+	struct pcmdev_ctrl_info dig_ctl_info = {0};
+	struct snd_kcontrol_new *pcmdev_controls;
+	int nr_controls = 1, ret = 0, mix_index = 0, i, chn;
+	char *name;
+
+	dig_ctl_info.gain = pcm1690_dig_gain_tlv;
+	dig_ctl_info.pcmdev_ctrl = pcm1690_digital_gain_ctl;
+	dig_ctl_info.ctrl_array_size =
+		ARRAY_SIZE(pcm1690_digital_gain_ctl);
+
+	fine_dig_ctl_info.gain = pcm1690_fine_dig_gain_tlv;
+	fine_dig_ctl_info.pcmdev_ctrl = pcm1690_digital_gain_ctl;
+	fine_dig_ctl_info.ctrl_array_size =
+		ARRAY_SIZE(pcm1690_digital_gain_ctl);
+	nr_controls += pcm_dev->ndev * (dig_ctl_info.ctrl_array_size +
+		fine_dig_ctl_info.ctrl_array_size);
+
+	pcmdev_controls = devm_kzalloc(pcm_dev->dev,
+		nr_controls * sizeof(*pcmdev_controls),
+		GFP_KERNEL);
+	if (!pcmdev_controls) {
+		pcm_dev->pcm_ctrl.pcmdevice_profile_controls = NULL;
+		ret = -ENOMEM;
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.pcmdevice_profile_controls = pcmdev_controls;
+	/* Create a mixer item for selecting the active profile */
+	name = devm_kzalloc(pcm_dev->dev,
+		SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "PCMDEVICE Profile id");
+	pcmdev_controls[mix_index].name = name;
+	pcmdev_controls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	pcmdev_controls[mix_index].info = pcmdevice_info_profile;
+	pcmdev_controls[mix_index].get = pcmdevice_get_profile_id;
+	pcmdev_controls[mix_index].put = pcmdevice_set_profile_id;
+	mix_index++;
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev,
+				"%s: mix_index: %d nr_controls: %d\n",
+				__func__, mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= dig_ctl_info.ctrl_array_size;
+			chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx = %d nr_controls = %d\n",
+					__func__, mix_index,
+					nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				"%s-i2c-%d-dev%d-ch%d-digi-gain",
+				pcm_dev->dev_name, adap->nr, i, chn);
+			pcmdev_controls[mix_index].tlv.p = dig_ctl_info.gain;
+			dig_ctl_info.pcmdev_ctrl[chn - 1].dev_no = i;
+			pcmdev_controls[mix_index].private_value =
+				(unsigned long)
+				&dig_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get = pcm1690_get_volsw;
+			pcmdev_controls[mix_index].put = pcm1690_put_volsw;
+			mix_index++;
+		}
+	}
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev,
+				"%s: mix_index = %d nr_controls = %d\n",
+				__func__, mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= fine_dig_ctl_info.ctrl_array_size;
+			chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx = %d nr_controls = %d\n",
+					__func__, mix_index, nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				"%s-i2c-%d-dev%d-ch%d-fine-digi-gain",
+				pcm_dev->dev_name, adap->nr, i, chn);
+			pcmdev_controls[mix_index].tlv.p =
+				fine_dig_ctl_info.gain;
+			fine_dig_ctl_info.pcmdev_ctrl[chn - 1].dev_no = i;
+			pcmdev_controls[mix_index].private_value =
+				(unsigned long)
+				&fine_dig_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get =
+				pcm1690_get_finevolsw;
+			pcmdev_controls[mix_index].put =
+				pcm1690_put_finevolsw;
+			mix_index++;
+		}
+	}
+
+	ret = snd_soc_add_component_controls(pcm_dev->component,
+		pcmdev_controls,
+		nr_controls < mix_index ? nr_controls : mix_index);
+	if (ret) {
+		dev_err(pcm_dev->dev,
+			"%s: add_component_controls error = %d\n",
+			__func__, ret);
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.nr_controls =
+		nr_controls < mix_index ? nr_controls : mix_index;
+out:
+	return ret;
+}
+
+static void pcm9211_sw_rst(struct pcmdevice_priv *pcm_dev)
+{
+	int ret, i;
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		ret = pcmdev_dev_update_bits(pcm_dev, i,
+			PCM9211_REG_SW_CTRL, PCM9211_REG_SW_CTRL_MRST_MSK,
+			PCM9211_REG_SW_CTRL_MRST);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s: dev %d swreset fail, %d\n",
+				__func__, i, ret);
+	}
+}
+
+static void pcmdevice_sw_rst(struct pcmdevice_priv *pcm_dev)
+{
+	int ret, i;
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		ret = pcmdev_dev_write(pcm_dev, i, PCMDEVICE_REG_SWRESET,
+			PCMDEVICE_REG_SWRESET_RESET);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s: dev %d swreset fail, %d\n",
+				__func__, i, ret);
+	}
+}
+
+static struct pcmdevice_config_info *pcmdevice_add_config(void *ctxt,
+	const unsigned char *config_data, unsigned int config_size,
+	int *status)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
+	struct pcmdevice_config_info *cfg_info;
+	struct pcmdevice_block_data **bk_da;
+	unsigned int config_offset = 0, i;
+
+	cfg_info = kzalloc(sizeof(struct pcmdevice_config_info), GFP_KERNEL);
+	if (!cfg_info) {
+		*status = -ENOMEM;
+		dev_err(pcm_dev->dev, "add config: cfg_info alloc failed!\n");
+		goto out;
+	}
+
+	if (pcm_dev->regbin.fw_hdr.binary_version_num >= 0x105) {
+		if (config_offset + 64 > (int)config_size) {
+			*status = -EINVAL;
+			dev_err(pcm_dev->dev, "add config: Out of boundary\n");
+			goto out;
+		}
+		memcpy(cfg_info->cfg_name, &config_data[config_offset], 64);
+		config_offset += 64;
+	}
+
+	if (config_offset + 4 > config_size) {
+		*status = -EINVAL;
+		dev_err(pcm_dev->dev, "add config: Out of boundary\n");
+		goto out;
+	}
+	cfg_info->nblocks =
+		be32_to_cpup((__be32 *)&config_data[config_offset]);
+	config_offset += 4;
+
+	bk_da = cfg_info->blk_data = kcalloc(cfg_info->nblocks,
+		sizeof(struct pcmdevice_block_data *), GFP_KERNEL);
+	if (!bk_da) {
+		*status = -ENOMEM;
+		goto out;
+	}
+	cfg_info->real_nblocks = 0;
+	for (i = 0; i < cfg_info->nblocks; i++) {
+		if (config_offset + 12 > config_size) {
+			*status = -EINVAL;
+			dev_err(pcm_dev->dev,
+				"%s: Out of boundary: i = %d nblocks = %u!\n",
+				__func__, i, cfg_info->nblocks);
+			break;
+		}
+		bk_da[i] = kzalloc(sizeof(struct pcmdevice_block_data),
+			GFP_KERNEL);
+		if (!bk_da[i]) {
+			*status = -ENOMEM;
+			break;
+		}
+		bk_da[i]->dev_idx = config_data[config_offset];
+		config_offset++;
+
+		bk_da[i]->block_type = config_data[config_offset];
+		config_offset++;
+
+		if (bk_da[i]->block_type == PCMDEVICE_BIN_BLK_PRE_POWER_UP) {
+			if (bk_da[i]->dev_idx == 0)
+				cfg_info->active_dev =
+					(1 << pcm_dev->ndev) - 1;
+			else
+				cfg_info->active_dev =
+					1 << (bk_da[i]->dev_idx - 1);
+		}
+
+		bk_da[i]->yram_checksum =
+			be16_to_cpup((__be16 *)&config_data[config_offset]);
+		config_offset += 2;
+		bk_da[i]->block_size =
+			be32_to_cpup((__be32 *)&config_data[config_offset]);
+		config_offset += 4;
+
+		bk_da[i]->n_subblks =
+			be32_to_cpup((__be32 *)&config_data[config_offset]);
+
+		config_offset += 4;
+
+		if (config_offset + bk_da[i]->block_size > config_size) {
+			*status = -EINVAL;
+			dev_err(pcm_dev->dev,
+				"%s: Out of boundary: i = %d blks = %u!\n",
+				__func__, i, cfg_info->nblocks);
+			break;
+		}
+
+		bk_da[i]->regdata = kmemdup(&config_data[config_offset],
+			bk_da[i]->block_size, GFP_KERNEL);
+		if (!bk_da[i]->regdata) {
+			*status = -ENOMEM;
+			goto out;
+		}
+		config_offset += bk_da[i]->block_size;
+		cfg_info->real_nblocks += 1;
+	}
+out:
+	return cfg_info;
+}
+
+static int pcmdev_ctrl_add(struct pcmdevice_priv *pcm_dev)
+{
+	struct i2c_adapter *adap = pcm_dev->client->adapter;
+	struct pcmdev_ctrl_info analog_ctl_info = {0};
+	struct pcmdev_ctrl_info dig_ctl_info = {0};
+	struct snd_kcontrol_new *pcmdev_controls;
+	int nr_controls = 1, ret = 0;
+	int mix_index = 0, dev, chn;
+	char *name;
+
+	switch (pcm_dev->chip_id) {
+	case ADC3120:
+	case ADC5120:
+	case ADC6120:
+	case PCM3120:
+	case PCM5120:
+	case PCM6120:
+		dig_ctl_info.gain = adc5120_fgain_tlv;
+		analog_ctl_info.gain = adc5120_chgain_tlv;
+		analog_ctl_info.pcmdev_ctrl = adc5120_analog_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(adc5120_analog_gain_ctl);
+		dig_ctl_info.pcmdev_ctrl = adc5120_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(adc5120_digital_gain_ctl);
+		break;
+	case PCM3140:
+	case PCM5140:
+	case PCM6140:
+	case PCM6240:
+		dig_ctl_info.gain = pcm6260_fgain_tlv;
+		analog_ctl_info.gain = pcm6260_chgain_tlv;
+		analog_ctl_info.pcmdev_ctrl = pcm6240_analog_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6240_analog_gain_ctl);
+		dig_ctl_info.pcmdev_ctrl = pcm6240_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6240_digital_gain_ctl);
+		break;
+	case PCM6260:
+		dig_ctl_info.gain = pcm6260_fgain_tlv;
+		analog_ctl_info.gain = pcm6260_chgain_tlv;
+		analog_ctl_info.pcmdev_ctrl = pcm6260_analog_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6260_analog_gain_ctl);
+		dig_ctl_info.pcmdev_ctrl = pcm6260_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6260_digital_gain_ctl);
+		break;
+	case PCM9211:
+		dig_ctl_info.gain = pcm9211_dig_gain_tlv;
+		dig_ctl_info.pcmdev_ctrl = pcm9211_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm9211_digital_gain_ctl);
+		break;
+	case PCMD3140:
+		dig_ctl_info.gain = pcmd3140_dig_gain_tlv;
+		dig_ctl_info.pcmdev_ctrl = pcmd3140_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcmd3140_digital_gain_ctl);
+		break;
+	case PCMD3180:
+		dig_ctl_info.gain = pcmd3180_dig_gain_tlv;
+		dig_ctl_info.pcmdev_ctrl = pcmd3180_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcmd3180_digital_gain_ctl);
+		break;
+	case TAA5212:
+	case TAA5412:
+		analog_ctl_info.gain = taa5412_fine_gain_tlv;
+		analog_ctl_info.pcmdev_ctrl = taa5412_fine_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(taa5412_fine_gain_ctl);
+		dig_ctl_info.gain = taa5412_dig_vol_tlv;
+		dig_ctl_info.pcmdev_ctrl = taa5412_digital_volume_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(taa5412_digital_volume_ctl);
+		break;
+	}
+
+	nr_controls += pcm_dev->ndev * (dig_ctl_info.ctrl_array_size +
+		analog_ctl_info.ctrl_array_size);
+	pcmdev_controls = devm_kzalloc(pcm_dev->dev,
+		nr_controls * sizeof(struct snd_kcontrol_new),
+		GFP_KERNEL);
+	if (!pcmdev_controls) {
+		ret = -ENOMEM;
+		pcm_dev->pcm_ctrl.pcmdevice_profile_controls = NULL;
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.pcmdevice_profile_controls = pcmdev_controls;
+	/* Create a mixer item for selecting the active profile */
+	name = devm_kzalloc(pcm_dev->dev, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+		GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "PCMDEVICE Profile id");
+	pcmdev_controls[mix_index].name = name;
+	pcmdev_controls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	pcmdev_controls[mix_index].info = pcmdevice_info_profile;
+	pcmdev_controls[mix_index].get = pcmdevice_get_profile_id;
+	pcmdev_controls[mix_index].put = pcmdevice_set_profile_id;
+	mix_index++;
+
+	if (analog_ctl_info.ctrl_array_size == 0)
+		goto dig_ctrl;
+
+	for (dev = 0; dev < pcm_dev->ndev; dev++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev, "mix_idx: %d nr_controls = %d\n",
+				mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= analog_ctl_info.ctrl_array_size;
+			chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx = %d nr_controls = %d\n",
+					__func__, mix_index,
+					nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				(pcm_dev->chip_id != TAA5412) ?
+				"%s-i2c-%d-dev%d-ch%d-ana-gain" :
+				"%s-i2c-%d-dev%d-ch%d-fine-gain",
+				pcm_dev->dev_name, adap->nr, dev, chn);
+			pcmdev_controls[mix_index].tlv.p =
+				analog_ctl_info.gain;
+			analog_ctl_info.pcmdev_ctrl[chn - 1].dev_no = dev;
+			pcmdev_controls[mix_index].private_value =
+				(unsigned long)
+				&analog_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get = pcmdevice_get_volsw;
+			pcmdev_controls[mix_index].put = pcmdevice_put_volsw;
+			mix_index++;
+		}
+	}
+
+dig_ctrl:
+	if (dig_ctl_info.ctrl_array_size == 0)
+		goto add_ctrls;
+
+	for (dev = 0; dev < pcm_dev->ndev; dev++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev, "%s: mix_idx: %d nr_ctrls: %d\n",
+				__func__, mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= dig_ctl_info.ctrl_array_size; chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx: %d nr_ctrls: %d\n",
+					__func__, mix_index, nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				"%s-i2c-%d-dev%d-ch%d-digi-gain",
+				pcm_dev->dev_name, adap->nr, dev, chn);
+			pcmdev_controls[mix_index].tlv.p = dig_ctl_info.gain;
+			dig_ctl_info.pcmdev_ctrl[chn - 1].dev_no = dev;
+			pcmdev_controls[mix_index].private_value =
+					(unsigned long)
+					&dig_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get = pcmdevice_get_volsw;
+			pcmdev_controls[mix_index].put = pcmdevice_put_volsw;
+			mix_index++;
+		}
+	}
+
+add_ctrls:
+	ret = snd_soc_add_component_controls(pcm_dev->component,
+		pcmdev_controls,
+		nr_controls < mix_index ? nr_controls : mix_index);
+	if (ret) {
+		dev_err(pcm_dev->dev, "%s: add_controls error = %d\n",
+			__func__, ret);
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.nr_controls =
+		nr_controls < mix_index ? nr_controls : mix_index;
+out:
+	return ret;
+}
+
+static int pcmdevice_create_controls(struct pcmdevice_priv *pcm_dev)
+{
+	int ret;
+
+	if (pcm_dev->chip_id == PCM1690)
+		ret = pcm1690_ctrl_add(pcm_dev);
+	else
+		ret = pcmdev_ctrl_add(pcm_dev);
+
+	return ret;
+}
+
+static void pcmdevice_config_info_remove(void *pContext)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *) pContext;
+	struct pcmdevice_regbin *regbin = &(pcm_dev->regbin);
+	struct pcmdevice_config_info **cfg_info = regbin->cfg_info;
+	int i, j;
+
+	if (!cfg_info)
+		return;
+	for (i = 0; i < regbin->ncfgs; i++) {
+		if (!cfg_info[i])
+			continue;
+		if (cfg_info[i]->blk_data) {
+			for (j = 0; j < (int)cfg_info[i]->real_nblocks; j++) {
+				if (!cfg_info[i]->blk_data[j])
+					continue;
+				kfree(cfg_info[i]->blk_data[j]->regdata);
+				kfree(cfg_info[i]->blk_data[j]);
+			}
+			kfree(cfg_info[i]->blk_data);
+		}
+		kfree(cfg_info[i]);
+	}
+	kfree(cfg_info);
+}
+
+static void pcmdev_regbin_ready(const struct firmware *fmw, void *ctxt)
+{
+	struct pcmdevice_config_info **cfg_info;
+	struct pcmdevice_priv *pcm_dev = ctxt;
+	struct pcmdevice_regbin_hdr *fw_hdr;
+	struct pcmdevice_regbin *regbin;
+	unsigned int total_config_sz = 0;
+	int offset = 0, ret = 0, i;
+	unsigned char *buf;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	regbin = &(pcm_dev->regbin);
+	fw_hdr = &(regbin->fw_hdr);
+	if (!fmw || !fmw->data) {
+		dev_err(pcm_dev->dev, "Failed to read %s\n",
+			pcm_dev->regbin_name);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	buf = (unsigned char *)fmw->data;
+
+	fw_hdr->img_sz = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 4;
+	if (fw_hdr->img_sz != fmw->size) {
+		dev_err(pcm_dev->dev, "File size not match, %d %u",
+			(int)fmw->size, fw_hdr->img_sz);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+
+	fw_hdr->checksum = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 4;
+	fw_hdr->binary_version_num = be32_to_cpup((__be32 *)&buf[offset]);
+	if (fw_hdr->binary_version_num < 0x103) {
+		dev_err(pcm_dev->dev, "Bin version 0x%04x is out of date",
+			fw_hdr->binary_version_num);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	offset += 4;
+	fw_hdr->drv_fw_version = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 8;
+	fw_hdr->plat_type = buf[offset];
+	offset += 1;
+	fw_hdr->dev_family = buf[offset];
+	offset += 1;
+	fw_hdr->reserve = buf[offset];
+	offset += 1;
+	fw_hdr->ndev = buf[offset];
+	offset += 1;
+	if (fw_hdr->ndev != pcm_dev->ndev) {
+		dev_err(pcm_dev->dev, "Invalid ndev(%u)\n", fw_hdr->ndev);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+
+	if (offset + PCMDEVICE_DEVICE_SUM > fw_hdr->img_sz) {
+		dev_err(pcm_dev->dev, "regbin_ready: Out of boundary!\n");
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+
+	for (i = 0; i < PCMDEVICE_DEVICE_SUM; i++, offset++)
+		fw_hdr->devs[i] = buf[offset];
+
+	fw_hdr->nconfig = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 4;
+
+	for (i = 0; i < PCMDEVICE_CONFIG_SUM; i++) {
+		fw_hdr->config_size[i] = be32_to_cpup((__be32 *)&buf[offset]);
+		offset += 4;
+		total_config_sz += fw_hdr->config_size[i];
+	}
+
+	if (fw_hdr->img_sz - total_config_sz != (unsigned int)offset) {
+		dev_err(pcm_dev->dev, "Bin file error!\n");
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	cfg_info = kcalloc(fw_hdr->nconfig, sizeof(*cfg_info), GFP_KERNEL);
+	if (!cfg_info) {
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	regbin->cfg_info = cfg_info;
+	regbin->ncfgs = 0;
+	for (i = 0; i < (int)fw_hdr->nconfig; i++) {
+		cfg_info[i] = pcmdevice_add_config(ctxt, &buf[offset],
+				fw_hdr->config_size[i], &ret);
+		if (ret) {
+			/* In case the bin file is partially destroyed. */
+			if (regbin->ncfgs == 0)
+				pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+			break;
+		}
+		offset += (int)fw_hdr->config_size[i];
+		regbin->ncfgs += 1;
+	}
+	if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_OK)
+		pcmdevice_create_controls(pcm_dev);
+out:
+	if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED)
+		pcmdevice_config_info_remove(pcm_dev);
+
+	mutex_unlock(&pcm_dev->codec_lock);
+	if (fmw)
+		release_firmware(fmw);
+}
+
+static int pcmdevice_codec_probe(struct snd_soc_component *codec)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
+	struct i2c_adapter *adap = pcm_dev->client->adapter;
+	int ret;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	pcm_dev->component = codec;
+	pcm_dev->fw_state = PCMDEVICE_FW_LOAD_OK;
+	strscpy(pcm_dev->dev_name, pcmdevice_i2c_id[pcm_dev->chip_id].name,
+		sizeof(pcm_dev->dev_name));
+
+	/* device-name[defined in pcmdevice_i2c_id]-i2c-bus_id[0,1,...,N]-
+	 * sum[1,2,...,4]dev-reg.bin stores the firmware including register
+	 * setting and params for different filters inside chips, it must be
+	 * copied into firmware folder. The same types of pcmdevices sitting
+	 * on the same i2c bus will be aggregated as one single codec,
+	 * all of them share the same bin file.
+	 */
+	scnprintf(pcm_dev->regbin_name, PCMDEVICE_REGBIN_FILENAME_LEN,
+		"%s-i2c-%d-%udev-reg.bin", pcm_dev->dev_name, adap->nr,
+		pcm_dev->ndev);
+
+	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+		pcm_dev->regbin_name, pcm_dev->dev, GFP_KERNEL, pcm_dev,
+		pcmdev_regbin_ready);
+	if (ret) {
+		dev_err(pcm_dev->dev, "load %s error = %d\n",
+			pcm_dev->regbin_name, ret);
+		goto out;
+	}
+
+	if (pcm_dev->hw_rst) {
+		gpiod_set_value_cansleep(pcm_dev->hw_rst, 0);
+		usleep_range(500, 1000);
+		gpiod_set_value_cansleep(pcm_dev->hw_rst, 1);
+	} else {
+		if (pcm_dev->sw_rst)
+			pcm_dev->sw_rst(pcm_dev);
+	}
+
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return ret;
+}
+
+
+static void pcmdevice_codec_remove(struct snd_soc_component *codec)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
+
+	if (!pcm_dev)
+		return;
+	mutex_lock(&pcm_dev->codec_lock);
+	pcmdevice_config_info_remove(pcm_dev);
+	mutex_unlock(&pcm_dev->codec_lock);
+}
+
+static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("ASI1 OUT", "ASI1 Capture",
+		0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_OUTPUT("OUT"),
+	SND_SOC_DAPM_INPUT("MIC"),
+};
+
+static const struct snd_soc_dapm_route pcmdevice_audio_map[] = {
+	{"OUT", NULL, "ASI"},
+	{"ASI1 OUT", NULL, "MIC"},
+};
+
+static const struct snd_soc_component_driver
+	soc_codec_driver_pcmdevice = {
+	.probe			= pcmdevice_codec_probe,
+	.remove			= pcmdevice_codec_remove,
+	.dapm_widgets		= pcmdevice_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(pcmdevice_dapm_widgets),
+	.dapm_routes		= pcmdevice_audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(pcmdevice_audio_map),
+	.suspend_bias_off	= 1,
+	.idle_bias_on		= 0,
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static int pcmdevice_process_block(void *ctxt, unsigned char *data,
+	unsigned char dev_idx, int sublocksize)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
+	int subblk_offset = 2, chn, chnend, rc;
+	unsigned char subblk_typ = data[1];
+
+	if (dev_idx) {
+		chn = dev_idx - 1;
+		chnend = dev_idx;
+	} else {
+		chn = 0;
+		chnend = pcm_dev->ndev;
+	}
+
+	for (; chn < chnend; chn++) {
+		switch (subblk_typ) {
+		case PCMDEVICE_CMD_SING_W: {
+			unsigned short len = be16_to_cpup((__be16 *)&data[2]);
+			int i = 0;
+
+			subblk_offset += 2;
+			if (subblk_offset + 4 * len > sublocksize) {
+				dev_err(pcm_dev->dev,
+					"process_block: Out of boundary\n");
+				break;
+			}
+
+			for (i = 0; i < len; i++) {
+				rc = pcmdev_dev_write(pcm_dev, chn,
+					PCMDEVICE_REG(data[subblk_offset + 1],
+						data[subblk_offset + 2]),
+					data[subblk_offset + 3]);
+				if (rc < 0)
+					dev_err(pcm_dev->dev,
+						"single write error\n");
+
+				subblk_offset += 4;
+			}
+		}
+		break;
+		case PCMDEVICE_CMD_BURST: {
+			unsigned short len = be16_to_cpup((__be16 *)&data[2]);
+
+			subblk_offset += 2;
+			if (subblk_offset + 4 + len > sublocksize) {
+				dev_err(pcm_dev->dev,
+					"BURST Out of boundary\n");
+				break;
+			}
+			if (len % 4) {
+				dev_err(pcm_dev->dev,
+					"Bst-len(%u)not div by 4\n", len);
+				break;
+			}
+			rc = pcmdev_dev_bulk_write(pcm_dev, chn,
+				PCMDEVICE_REG(data[subblk_offset + 1],
+				data[subblk_offset + 2]),
+				&(data[subblk_offset + 4]), len);
+			if (rc < 0)
+				dev_err(pcm_dev->dev,
+					"bulk_write error = %d\n", rc);
+
+			subblk_offset += (len + 4);
+		}
+			break;
+		case PCMDEVICE_CMD_DELAY: {
+			unsigned int delay_time = 0;
+
+			if (subblk_offset + 2 > sublocksize) {
+				dev_err(pcm_dev->dev,
+					"deley Out of boundary\n");
+				break;
+			}
+			delay_time = be16_to_cpup((__be16 *)&data[2]) * 1000;
+			usleep_range(delay_time, delay_time + 50);
+			subblk_offset += 2;
+		}
+			break;
+		case PCMDEVICE_CMD_FIELD_W:
+		if (subblk_offset + 6 > sublocksize) {
+			dev_err(pcm_dev->dev,
+				"process_block: bit write Out of memory\n");
+			break;
+		}
+			rc = pcmdev_dev_update_bits(pcm_dev, chn,
+				PCMDEVICE_REG(data[subblk_offset + 3],
+				data[subblk_offset + 4]),
+				data[subblk_offset + 1],
+				data[subblk_offset + 5]);
+		if (rc < 0)
+			dev_err(pcm_dev->dev,
+				"process_block: update_bits error = %d\n", rc);
+
+		subblk_offset += 6;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return subblk_offset;
+}
+
+
+static void pcmdevice_select_cfg_blk(void *ctxt, int conf_no,
+	unsigned char block_type)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
+	struct pcmdevice_regbin *regbin = &(pcm_dev->regbin);
+	struct pcmdevice_config_info **cfg_info = regbin->cfg_info;
+	struct pcmdevice_block_data **blk_data;
+	int j, k, chn, chnend;
+
+	if (conf_no >= regbin->ncfgs || conf_no < 0 || NULL == cfg_info) {
+		dev_err(pcm_dev->dev, "conf_no should be less than %u\n",
+			regbin->ncfgs);
+		goto out;
+	}
+	blk_data = cfg_info[conf_no]->blk_data;
+
+	for (j = 0; j < (int)cfg_info[conf_no]->real_nblocks; j++) {
+		unsigned int length = 0, rc = 0;
+
+		if (block_type > 5 || block_type < 2) {
+			dev_err(pcm_dev->dev,
+				"block_type should be in range from 2 to 5\n");
+			goto out;
+		}
+		if (block_type != blk_data[j]->block_type)
+			continue;
+
+		for (k = 0; k < (int)blk_data[j]->n_subblks; k++) {
+			if (blk_data[j]->dev_idx) {
+				chn = blk_data[j]->dev_idx - 1;
+				chnend = blk_data[j]->dev_idx;
+			} else {
+				chn = 0;
+				chnend = pcm_dev->ndev;
+			}
+
+			rc = pcmdevice_process_block(pcm_dev,
+				blk_data[j]->regdata + length,
+				blk_data[j]->dev_idx,
+				blk_data[j]->block_size - length);
+			length += rc;
+			if (blk_data[j]->block_size < length) {
+				dev_err(pcm_dev->dev,
+					"%s: %u %u out of boundary\n",
+					__func__, length,
+					blk_data[j]->block_size);
+				break;
+			}
+		}
+		if (length != blk_data[j]->block_size)
+			dev_err(pcm_dev->dev, "%s: %u %u size is not same\n",
+				__func__, length, blk_data[j]->block_size);
+	}
+
+out:
+	return;
+}
+
+static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream)
+{
+	struct snd_soc_component *codec = dai->component;
+	struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
+	unsigned char block_type;
+
+	if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED) {
+		dev_err(pcm_dev->dev, "DSP bin file not loaded\n");
+		return -EINVAL;
+	}
+
+	if (mute)
+		block_type = PCMDEVICE_BIN_BLK_PRE_SHUTDOWN;
+	else
+		block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type);
+	mutex_unlock(&pcm_dev->codec_lock);
+	return 0;
+}
+
+static int pcmdevice_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_dai_get_drvdata(codec_dai);
+
+	pcm_dev->sysclk = freq;
+
+	return 0;
+}
+
+static int pcmdevice_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_dai_get_drvdata(dai);
+	unsigned int fsrate;
+	unsigned int slot_width;
+	int bclk_rate;
+	int rc = 0;
+
+	fsrate = params_rate(params);
+	switch (fsrate) {
+	case 48000:
+		break;
+	case 44100:
+		break;
+	default:
+		dev_err(pcm_dev->dev,
+			"%s: incorrect sample rate = %u\n",
+			__func__, fsrate);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	slot_width = params_width(params);
+	switch (slot_width) {
+	case 16:
+		break;
+	case 20:
+		break;
+	case 24:
+		break;
+	case 32:
+		break;
+	default:
+		dev_err(pcm_dev->dev, "%s: incorrect slot width = %u\n",
+			__func__, slot_width);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	bclk_rate = snd_soc_params_to_bclk(params);
+	if (bclk_rate < 0) {
+		dev_err(pcm_dev->dev, "%s: incorrect bclk rate = %d\n",
+			__func__, bclk_rate);
+		rc = bclk_rate;
+		goto out;
+	}
+
+out:
+	return rc;
+}
+
+static int pcmdevice_startup(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *codec = dai->component;
+	struct pcmdevice_priv *pcm_priv = snd_soc_component_get_drvdata(codec);
+	int ret = 0;
+
+	if (pcm_priv->fw_state != PCMDEVICE_FW_LOAD_OK) {
+		dev_err(pcm_priv->dev, "DSP bin file not loaded\n");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dai_ops pcmdevice_dai_ops = {
+	.mute_stream = pcmdevice_mute,
+	.startup = pcmdevice_startup,
+	.hw_params = pcmdevice_hw_params,
+	.set_sysclk = pcmdevice_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver pcmdevice_dai_driver[] = {
+	{
+		.name = "pcmdevice-codec",
+		.capture = {
+			.stream_name	 = "Capture",
+			.channels_min	 = 2,
+			.channels_max	 = PCMDEVICE_MAX_CHANNELS,
+			.rates		 = PCMDEVICE_RATES,
+			.formats	 = PCMDEVICE_FORMATS,
+		},
+		.playback = {
+			.stream_name	 = "Playback",
+			.channels_min	 = 2,
+			.channels_max	 = PCMDEVICE_MAX_CHANNELS,
+			.rates		 = PCMDEVICE_RATES,
+			.formats	 = PCMDEVICE_FORMATS,
+		},
+		.ops = &pcmdevice_dai_ops,
+		.symmetric_rate = 1,
+	}
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id pcmdevice_of_match[] = {
+	{ .compatible = "ti,adc3120"  },
+	{ .compatible = "ti,adc5120"  },
+	{ .compatible = "ti,adc6120"  },
+	{ .compatible = "ti,dix4192"  },
+	{ .compatible = "ti,pcm1690"  },
+	{ .compatible = "ti,pcm3120"  },
+	{ .compatible = "ti,pcm3140"  },
+	{ .compatible = "ti,pcm5120"  },
+	{ .compatible = "ti,pcm5140"  },
+	{ .compatible = "ti,pcm6120"  },
+	{ .compatible = "ti,pcm6140"  },
+	{ .compatible = "ti,pcm6240"  },
+	{ .compatible = "ti,pcm6260"  },
+	{ .compatible = "ti,pcm9211"  },
+	{ .compatible = "ti,pcmd3140" },
+	{ .compatible = "ti,pcmd3180" },
+	{ .compatible = "ti,pcmd512x" },
+	{ .compatible = "ti,taa5212"  },
+	{ .compatible = "ti,taa5412"  },
+	{ .compatible = "ti,tad5212"  },
+	{ .compatible = "ti,tad5412"  },
+	{},
+};
+MODULE_DEVICE_TABLE(of, pcmdevice_of_match);
+#endif
+
+static const struct regmap_range_cfg pcmdevice_ranges[] = {
+	{
+		.range_min = 0,
+		.range_max = 256 * 128,
+		.selector_reg = PCMDEVICE_PAGE_SELECT,
+		.selector_mask = 0xff,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 128,
+	},
+};
+
+static const struct regmap_config pcmdevice_i2c_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.ranges = pcmdevice_ranges,
+	.num_ranges = ARRAY_SIZE(pcmdevice_ranges),
+	.max_register = 256 * 128,
+};
+
+static void pcmdevice_remove(struct pcmdevice_priv *pcm_dev)
+{
+	if (gpio_is_valid(pcm_dev->irq_info.gpio)) {
+		gpio_free(pcm_dev->irq_info.gpio);
+		free_irq(pcm_dev->irq_info.nmb, pcm_dev);
+	}
+	mutex_destroy(&pcm_dev->codec_lock);
+}
+
+static int pcmdevice_i2c_probe(struct i2c_client *i2c)
+{
+	const struct i2c_device_id *id = i2c_match_id(pcmdevice_i2c_id, i2c);
+	struct pcmdevice_priv *pcm_dev;
+	struct device_node *np;
+	unsigned int dev_addrs[PCMDEVICE_MAX_DEVICES];
+	int ret = 0, i = 0, ndev = 0;
+#ifdef CONFIG_OF
+	const __be32 *reg, *reg_end;
+	int len, sw, aw;
+#endif
+
+	pcm_dev = devm_kzalloc(&i2c->dev, sizeof(*pcm_dev), GFP_KERNEL);
+	if (!pcm_dev) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	pcm_dev->chip_id = (id != NULL) ? id->driver_data : 0;
+
+	pcm_dev->dev = &i2c->dev;
+	pcm_dev->client = i2c;
+
+	if (pcm_dev->chip_id >= MAX_DEVICE)
+		pcm_dev->chip_id = 0;
+
+	pcm_dev->regmap = devm_regmap_init_i2c(i2c,
+		&pcmdevice_i2c_regmap);
+	if (IS_ERR(pcm_dev->regmap)) {
+		ret = PTR_ERR(pcm_dev->regmap);
+		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
+			ret);
+		goto out;
+	}
+
+	np = pcm_dev->dev->of_node;
+#ifdef CONFIG_OF
+	aw = of_n_addr_cells(np);
+	sw = of_n_size_cells(np);
+	if (sw == 0) {
+		reg = (const __be32 *)of_get_property(np,
+			"reg", &len);
+		reg_end = reg + len/sizeof(*reg);
+		ndev = 0;
+		do {
+			dev_addrs[ndev] = of_read_number(reg, aw);
+			reg += aw;
+			ndev++;
+		} while (reg < reg_end);
+	} else {
+		ndev = 1;
+		dev_addrs[0] = i2c->addr;
+	}
+#else
+	ndev = 1;
+	dev_addrs[0] = i2c->addr;
+#endif
+	pcm_dev->irq_info.gpio = of_irq_get(np, 0);
+
+	for (i = 0; i < ndev; i++)
+		pcm_dev->addr[i] = dev_addrs[i];
+
+	pcm_dev->ndev = ndev;
+
+	pcm_dev->hw_rst = devm_gpiod_get_optional(&i2c->dev,
+			"reset-gpios", GPIOD_OUT_HIGH);
+	if (IS_ERR(pcm_dev->hw_rst))
+		dev_dbg(&i2c->dev, "%s: No reset GPIO, no side-effect\n",
+			__func__);
+
+	if (pcm_dev->chip_id == PCM9211 || pcm_dev->chip_id == PCM1690)
+		pcm_dev->sw_rst = pcm9211_sw_rst;
+	else
+		pcm_dev->sw_rst = pcmdevice_sw_rst;
+
+	if (pcm_dev->chip_id == PCM1690)
+		goto skip_interrupt;
+	if (gpio_is_valid(pcm_dev->irq_info.gpio)) {
+		dev_dbg(pcm_dev->dev, "irq-gpio = %d",
+			pcm_dev->irq_info.gpio);
+
+		ret = gpio_request(pcm_dev->irq_info.gpio, "PCMDEV-IRQ");
+		if (!ret) {
+			int gpio = pcm_dev->irq_info.gpio;
+
+			gpio_direction_input(gpio);
+			pcm_dev->irq_info.nmb = gpio_to_irq(gpio);
+
+		} else
+			dev_err(pcm_dev->dev, "%s: GPIO %d request error\n",
+				__func__, pcm_dev->irq_info.gpio);
+	} else
+		dev_err(pcm_dev->dev, "Looking up irq-gpio failed %d\n",
+			pcm_dev->irq_info.gpio);
+
+
+skip_interrupt:
+	mutex_init(&pcm_dev->codec_lock);
+
+	i2c_set_clientdata(i2c, pcm_dev);
+
+	ret = devm_snd_soc_register_component(&i2c->dev,
+		&soc_codec_driver_pcmdevice, pcmdevice_dai_driver,
+		ARRAY_SIZE(pcmdevice_dai_driver));
+	if (ret < 0)
+		dev_err(&i2c->dev, "probe register comp failed %d\n", ret);
+
+out:
+	if (ret < 0)
+		pcmdevice_remove(pcm_dev);
+	return ret;
+}
+
+static void pcmdevice_i2c_remove(struct i2c_client *i2c)
+{
+	struct pcmdevice_priv *pcm_dev = i2c_get_clientdata(i2c);
+
+	pcmdevice_remove(pcm_dev);
+}
+
+static struct i2c_driver pcmdevice_i2c_driver = {
+	.driver = {
+		.name = "pcmdevice-codec",
+		.of_match_table = of_match_ptr(pcmdevice_of_match),
+	},
+	.probe	= pcmdevice_i2c_probe,
+	.remove = pcmdevice_i2c_remove,
+	.id_table = pcmdevice_i2c_id,
+};
+
+module_i2c_driver(pcmdevice_i2c_driver);
+
+MODULE_AUTHOR("Shenghao Ding <shenghao-ding@ti.com>");
+MODULE_DESCRIPTION("ASoC PCM6240 Family Audio ADC/DAC/Router Driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 2/4] ASoc: PCM6240: Create header file for PCM6240 Family driver code
  2024-01-26  3:58 [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Shenghao Ding
@ 2024-01-26  3:58 ` Shenghao Ding
  2024-01-26  3:58 ` [PATCH v2 3/4] ASoc: PCM6240: Add compile item for PCM6240 Family driver Shenghao Ding
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 13+ messages in thread
From: Shenghao Ding @ 2024-01-26  3:58 UTC (permalink / raw)
  To: broonie, conor+dt, krzysztof.kozlowski
  Cc: robh+dt, andriy.shevchenko, kevin-lu, baojun.xu, devicetree,
	v-po, lgirdwood, perex, pierre-louis.bossart, 13916275206,
	mohit.chawla, linux-sound, linux-kernel, liam.r.girdwood, soyer,
	jkhuang3, tiwai, pdjuandi, j-mcpherson, navada, Shenghao Ding

PCM6240 driver implements a flexible and configurable setting for register
and filter coefficients, to one, two or even multiple PCM6240 Family Audio
chips.

Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>

---
Change in v2:
 - All these chips have only a portion of the functionality of codec,
   such as ADC or DAC, and so on, but their audio performance is far
   superior to the codec's, and cost is lower than codec, and much easier
   to program than codec.
---
 sound/soc/codecs/pcm6240.h | 235 +++++++++++++++++++++++++++++++++++++
 1 file changed, 235 insertions(+)
 create mode 100644 sound/soc/codecs/pcm6240.h

diff --git a/sound/soc/codecs/pcm6240.h b/sound/soc/codecs/pcm6240.h
new file mode 100644
index 000000000000..2d077bd7fc41
--- /dev/null
+++ b/sound/soc/codecs/pcm6240.h
@@ -0,0 +1,235 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+//
+// ALSA SoC Texas Instruments PCM6240 Family Audio ADC/DAC/Router
+//
+// Copyright (C) 2022 - 2024 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// The PCM6240 driver implements a flexible and configurable
+// algo coefficient setting for one, two, or even multiple
+// PCM6240 Family Audio chips.
+//
+// Author: Shenghao Ding <shenghao-ding@ti.com>
+//
+
+#ifndef __PCM6240_H__
+#define __PCM6240_H__
+
+enum pcm_device {
+	ADC3120,
+	ADC5120,
+	ADC6120,
+	DIX4192,
+	PCM1690,
+	PCM3120,
+	PCM3140,
+	PCM5120,
+	PCM5140,
+	PCM6120,
+	PCM6140,
+	PCM6240,
+	PCM6260,
+	PCM9211,
+	PCMD3140,
+	PCMD3180,
+	PCMD512X,
+	TAA5212,
+	TAA5412,
+	TAD5212,
+	TAD5412,
+	MAX_DEVICE,
+};
+
+#define PCMDEVICE_MAX_DEVICES			4
+#define PCMDEVICE_DEVICE_SUM			8
+#define PCMDEVICE_CONFIG_SUM			64
+#define PCMDEVICE_REGBIN_FILENAME_LEN		64
+
+#define PCMDEVICE_RATES	(SNDRV_PCM_RATE_44100 | \
+	SNDRV_PCM_RATE_48000)
+#define PCMDEVICE_MAX_CHANNELS			8
+#define PCMDEVICE_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | \
+	SNDRV_PCM_FMTBIT_S20_3LE | \
+	SNDRV_PCM_FMTBIT_S24_3LE | \
+	SNDRV_PCM_FMTBIT_S24_LE | \
+	SNDRV_PCM_FMTBIT_S32_LE)
+
+/* PAGE Control Register (available in page0 of each book) */
+#define PCMDEVICE_PAGE_SELECT			0x00
+#define PCMDEVICE_REG(page, reg)		((page * 128) + reg)
+#define PCMDEVICE_REG_SWRESET			PCMDEVICE_REG(0X0, 0x01)
+#define PCMDEVICE_REG_SWRESET_RESET		BIT(0)
+
+#define ADC5120_REG_CH1_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x3d)
+#define ADC5120_REG_CH1_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x3e)
+#define ADC5120_REG_CH2_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x42)
+#define ADC5120_REG_CH2_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x43)
+
+#define PCM1690_REG_MODE_CTRL			PCMDEVICE_REG(0X0, 0x46)
+#define PCM1690_REG_MODE_CTRL_DAMS_MSK		BIT(7)
+#define PCM1690_REG_MODE_CTRL_DAMS_FINE_STEP	0x0
+#define PCM1690_REG_MODE_CTRL_DAMS_WIDE_RANGE	0x80
+
+#define PCM1690_REG_CH1_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x48)
+#define PCM1690_REG_CH2_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x49)
+#define PCM1690_REG_CH3_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4a)
+#define PCM1690_REG_CH4_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4b)
+#define PCM1690_REG_CH5_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4c)
+#define PCM1690_REG_CH6_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4d)
+#define PCM1690_REG_CH7_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4e)
+#define PCM1690_REG_CH8_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4f)
+
+#define PCM6240_REG_CH1_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x3d)
+#define PCM6240_REG_CH1_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x3e)
+#define PCM6240_REG_CH2_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x42)
+#define PCM6240_REG_CH2_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x43)
+#define PCM6240_REG_CH3_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x47)
+#define PCM6240_REG_CH3_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x48)
+#define PCM6240_REG_CH4_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x4c)
+#define PCM6240_REG_CH4_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4d)
+
+#define PCM6260_REG_CH1_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x3d)
+#define PCM6260_REG_CH1_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x3e)
+#define PCM6260_REG_CH2_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x42)
+#define PCM6260_REG_CH2_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x43)
+#define PCM6260_REG_CH3_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x47)
+#define PCM6260_REG_CH3_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x48)
+#define PCM6260_REG_CH4_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x4c)
+#define PCM6260_REG_CH4_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4d)
+#define PCM6260_REG_CH5_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x51)
+#define PCM6260_REG_CH5_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x52)
+#define PCM6260_REG_CH6_ANALOG_GAIN		PCMDEVICE_REG(0X0, 0x56)
+#define PCM6260_REG_CH6_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x57)
+
+#define PCM9211_REG_SW_CTRL			PCMDEVICE_REG(0X0, 0x40)
+#define PCM9211_REG_SW_CTRL_MRST_MSK		BIT(7)
+#define PCM9211_REG_SW_CTRL_MRST		0x0
+
+#define PCM9211_REG_CH1_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x46)
+#define PCM9211_REG_CH2_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x47)
+
+#define PCMD3140_REG_CH1_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x3E)
+#define PCMD3140_REG_CH2_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x43)
+#define PCMD3140_REG_CH3_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x48)
+#define PCMD3140_REG_CH4_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4D)
+
+#define PCMD3180_REG_CH1_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x3E)
+#define PCMD3180_REG_CH2_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x43)
+#define PCMD3180_REG_CH3_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x48)
+#define PCMD3180_REG_CH4_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x4D)
+#define PCMD3180_REG_CH5_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x52)
+#define PCMD3180_REG_CH6_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x57)
+#define PCMD3180_REG_CH7_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x5C)
+#define PCMD3180_REG_CH8_DIGITAL_GAIN		PCMDEVICE_REG(0X0, 0x61)
+
+#define TAA5412_REG_CH1_DIGITAL_VOLUME		PCMDEVICE_REG(0X0, 0x52)
+#define TAA5412_REG_CH2_DIGITAL_VOLUME		PCMDEVICE_REG(0X0, 0x57)
+#define TAA5412_REG_CH3_DIGITAL_VOLUME		PCMDEVICE_REG(0X0, 0x5B)
+#define TAA5412_REG_CH4_DIGITAL_VOLUME		PCMDEVICE_REG(0X0, 0x5F)
+
+#define TAA5412_REG_CH1_FINE_GAIN		PCMDEVICE_REG(0X0, 0x53)
+#define TAA5412_REG_CH2_FINE_GAIN		PCMDEVICE_REG(0X0, 0x58)
+#define TAA5412_REG_CH3_FINE_GAIN		PCMDEVICE_REG(0X0, 0x5C)
+#define TAA5412_REG_CH4_FINE_GAIN		PCMDEVICE_REG(0X0, 0x60)
+
+#define PCMDEVICE_CMD_SING_W		0x1
+#define PCMDEVICE_CMD_BURST		0x2
+#define PCMDEVICE_CMD_DELAY		0x3
+#define PCMDEVICE_CMD_FIELD_W		0x4
+
+enum pcmdevice_bin_blk_type {
+	PCMDEVICE_BIN_BLK_COEFF = 1,
+	PCMDEVICE_BIN_BLK_POST_POWER_UP,
+	PCMDEVICE_BIN_BLK_PRE_SHUTDOWN,
+	PCMDEVICE_BIN_BLK_PRE_POWER_UP,
+	PCMDEVICE_BIN_BLK_POST_SHUTDOWN
+};
+
+enum pcmdevice_fw_state {
+	PCMDEVICE_FW_LOAD_OK = 0,
+	PCMDEVICE_FW_LOAD_FAILED
+};
+
+struct pcmdevice_regbin_hdr {
+	unsigned int img_sz;
+	unsigned int checksum;
+	unsigned int binary_version_num;
+	unsigned int drv_fw_version;
+	unsigned int timestamp;
+	unsigned char plat_type;
+	unsigned char dev_family;
+	unsigned char reserve;
+	unsigned char ndev;
+	unsigned char devs[PCMDEVICE_MAX_DEVICES];
+	unsigned int nconfig;
+	unsigned int config_size[PCMDEVICE_CONFIG_SUM];
+};
+
+struct pcmdevice_block_data {
+	unsigned char dev_idx;
+	unsigned char block_type;
+	unsigned short yram_checksum;
+	unsigned int block_size;
+	unsigned int n_subblks;
+	unsigned char *regdata;
+};
+
+struct pcmdevice_config_info {
+	char cfg_name[64];
+	unsigned int nblocks;
+	unsigned int real_nblocks;
+	unsigned char active_dev;
+	struct pcmdevice_block_data **blk_data;
+};
+
+struct pcmdevice_regbin {
+	struct pcmdevice_regbin_hdr fw_hdr;
+	int ncfgs;
+	struct pcmdevice_config_info **cfg_info;
+};
+
+struct pcm_control {
+	struct snd_kcontrol_new *pcmdevice_profile_controls;
+	int nr_controls;
+};
+
+struct pcmdevice_irqinfo {
+	int gpio;
+	int nmb;
+};
+
+struct pcmdevice_priv {
+	struct snd_soc_component *component;
+	struct i2c_client *client;
+	struct device *dev;
+	struct mutex codec_lock;
+	struct gpio_desc *hw_rst;
+	struct regmap *regmap;
+	struct pcmdevice_regbin regbin;
+	struct pcm_control pcm_ctrl;
+	struct pcmdevice_irqinfo irq_info;
+	unsigned int addr[PCMDEVICE_MAX_DEVICES];
+	unsigned int chip_id;
+	unsigned int sysclk;
+	int cur_conf;
+	int fw_state;
+	int ndev;
+	unsigned char regbin_name[PCMDEVICE_REGBIN_FILENAME_LEN];
+	unsigned char dev_name[I2C_NAME_SIZE];
+	void (*sw_rst)(struct pcmdevice_priv *pcm_dev);
+};
+
+/* mixer control */
+struct pcmdevice_mixer_control {
+	int max;
+	int reg;
+	unsigned int dev_no;
+	unsigned int shift;
+	unsigned int invert;
+};
+struct pcmdev_ctrl_info {
+	const unsigned int *gain;
+	struct pcmdevice_mixer_control *pcmdev_ctrl;
+	unsigned int ctrl_array_size;
+};
+#endif /* __PCM6240_H__ */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 3/4] ASoc: PCM6240: Add compile item for PCM6240 Family driver
  2024-01-26  3:58 [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Shenghao Ding
  2024-01-26  3:58 ` [PATCH v2 2/4] ASoc: PCM6240: Create header file for " Shenghao Ding
@ 2024-01-26  3:58 ` Shenghao Ding
  2024-01-26  3:58 ` [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding Shenghao Ding
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 13+ messages in thread
From: Shenghao Ding @ 2024-01-26  3:58 UTC (permalink / raw)
  To: broonie, conor+dt, krzysztof.kozlowski
  Cc: robh+dt, andriy.shevchenko, kevin-lu, baojun.xu, devicetree,
	v-po, lgirdwood, perex, pierre-louis.bossart, 13916275206,
	mohit.chawla, linux-sound, linux-kernel, liam.r.girdwood, soyer,
	jkhuang3, tiwai, pdjuandi, j-mcpherson, navada, Shenghao Ding

PCM6240 driver implements a flexible and configurable setting for register
and filter coefficients, to one, two or even multiple PCM6240 Family Audio
chips.

Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>

---
Change in v2:
 - All these chips have only a portion of the functionality of codec,
   such as ADC or DAC, and so on, but their audio performance is far
   superior to the codec's, and cost is lower than codec, and much easier
   to program than codec.
---
 sound/soc/codecs/Kconfig  | 10 ++++++++++
 sound/soc/codecs/Makefile |  2 ++
 2 files changed, 12 insertions(+)

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 59f9742e9ff4..bab0ed032b5d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -178,6 +178,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_PCM5102A
 	imply SND_SOC_PCM512x_I2C
 	imply SND_SOC_PCM512x_SPI
+	imply SND_SOC_PCM6240
 	imply SND_SOC_PEB2466
 	imply SND_SOC_RK3328
 	imply SND_SOC_RK817
@@ -1389,6 +1390,15 @@ config SND_SOC_PCM512x_SPI
 	select SND_SOC_PCM512x
 	select REGMAP_SPI
 
+config SND_SOC_PCM6240
+	tristate "Texas Instruments PCM6240 Family Audio chips based on I2C"
+	depends on I2C
+	help
+	  Enable support for Texas Instruments PCM6240 Family Audio chips.
+	  Note the PCM6240 driver implements a flexible and configurable
+	  setting for register and filter coefficients, to one, two or
+	  even multiple PCM6240 Family Audio chips.
+
 config SND_SOC_PEB2466
 	tristate "Infineon PEB2466 quad PCM codec"
 	depends on SPI
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index f53baa2b9565..c2ae573b62dd 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -201,6 +201,7 @@ snd-soc-pcm5102a-objs := pcm5102a.o
 snd-soc-pcm512x-objs := pcm512x.o
 snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o
 snd-soc-pcm512x-spi-objs := pcm512x-spi.o
+snd-soc-pcm6240-objs := pcm6240.o
 snd-soc-peb2466-objs := peb2466.o
 snd-soc-rk3328-objs := rk3328_codec.o
 snd-soc-rk817-objs := rk817_codec.o
@@ -586,6 +587,7 @@ obj-$(CONFIG_SND_SOC_PCM5102A)	+= snd-soc-pcm5102a.o
 obj-$(CONFIG_SND_SOC_PCM512x)	+= snd-soc-pcm512x.o
 obj-$(CONFIG_SND_SOC_PCM512x_I2C)	+= snd-soc-pcm512x-i2c.o
 obj-$(CONFIG_SND_SOC_PCM512x_SPI)	+= snd-soc-pcm512x-spi.o
+obj-$(CONFIG_SND_SOC_PCM6240)	+= snd-soc-pcm6240.o
 obj-$(CONFIG_SND_SOC_PEB2466)	+= snd-soc-peb2466.o
 obj-$(CONFIG_SND_SOC_RK3328)	+= snd-soc-rk3328.o
 obj-$(CONFIG_SND_SOC_RK817)	+= snd-soc-rk817.o
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding
  2024-01-26  3:58 [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Shenghao Ding
  2024-01-26  3:58 ` [PATCH v2 2/4] ASoc: PCM6240: Create header file for " Shenghao Ding
  2024-01-26  3:58 ` [PATCH v2 3/4] ASoc: PCM6240: Add compile item for PCM6240 Family driver Shenghao Ding
@ 2024-01-26  3:58 ` Shenghao Ding
  2024-01-26  8:27   ` Krzysztof Kozlowski
  2024-01-26 14:33 ` [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Mark Brown
  2024-01-28 15:23 ` Andy Shevchenko
  4 siblings, 1 reply; 13+ messages in thread
From: Shenghao Ding @ 2024-01-26  3:58 UTC (permalink / raw)
  To: broonie, conor+dt, krzysztof.kozlowski
  Cc: robh+dt, andriy.shevchenko, kevin-lu, baojun.xu, devicetree,
	v-po, lgirdwood, perex, pierre-louis.bossart, 13916275206,
	mohit.chawla, linux-sound, linux-kernel, liam.r.girdwood, soyer,
	jkhuang3, tiwai, pdjuandi, j-mcpherson, navada, Shenghao Ding

PCM6240 driver implements a flexible and configurable setting for register
and filter coefficients, to one, two or even multiple PCM6240 Family Audio
chips.

Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>

---
Change in v2:
 - Rewrite the subject to match something similar to other commits.
 - And something to be compatible with.
 - minItems, then maxItems.
 - Drop reset-gpios description
 - Remove the repeated reg descriptions and reg constraints.
 - Drop redundant spaces.
 - Add missing line breaks between blocks and additionalProperties.
 - All these chips have only a portion of the functionality of codec,
   such as ADC or DAC, and so on, but their audio performance is far
   superior to the codec's, and cost is lower than codec, and much easier
   to program than codec. Simply one or two register settings can enable
   them to work. Init for these chips are hardware reset or software reset.
   As to some audio filter params for internal filters, it is up to the
   special user cases, which can be saved into the bin file. The default
   value also can work well.
---
 .../devicetree/bindings/sound/ti,pcm6240.yaml | 276 ++++++++++++++++++
 1 file changed, 276 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/ti,pcm6240.yaml

diff --git a/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml b/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
new file mode 100644
index 000000000000..f06264d81b46
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
@@ -0,0 +1,276 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2022 - 2024 Texas Instruments Incorporated
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/ti,pcm6240.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments PCM6240 Family Audio ADC/DAC
+
+maintainers:
+  - Shenghao Ding <shenghao-ding@ti.com>
+
+description: |
+  The PCM6240 Family is a big family of Audio ADC/DAC for
+  different Specifications, range from Personal Electric
+  to Automotive Electric, even some professional fields.
+
+  Specifications about the audio chip can be found at:
+    https://www.ti.com/lit/gpn/tlv320adc3120
+    https://www.ti.com/lit/gpn/tlv320adc5120
+    https://www.ti.com/lit/gpn/tlv320adc6120
+    https://www.ti.com/lit/gpn/dix4192
+    https://www.ti.com/lit/gpn/pcm1690
+    https://www.ti.com/lit/gpn/pcm3120-q1
+    https://www.ti.com/lit/gpn/pcm3140-q1
+    https://www.ti.com/lit/gpn/pcm5120-q1
+    https://www.ti.com/lit/gpn/pcm6120-q1
+    https://www.ti.com/lit/gpn/pcm6260-q1
+    https://www.ti.com/lit/gpn/pcm9211
+    https://www.ti.com/lit/gpn/pcmd3140
+    https://www.ti.com/lit/gpn/pcmd3180
+    https://www.ti.com/lit/gpn/taa5212
+    https://www.ti.com/lit/gpn/tad5212
+
+properties:
+  compatible:
+    description: |
+      ti,adc3120: Stereo-channel, 768-kHz, Burr-Brown™ audio analog-to-
+      digital converter (ADC) with 106-dB SNR.
+
+      ti,adc5120: 2-Channel, 768-kHz, Burr-BrownTM Audio ADC with 120-dB SNR.
+
+      ti,adc6120: Stereo-channel, 768-kHz, Burr-Brown™ audio analog-to-
+      digital converter (ADC) with 123-dB SNR.
+
+      ti,pcm1690: 113dB SNR, 24-Bit, 192-kHz Sampling, Enhanced Multi-Level
+      ?S, Eight-Channel Audio Digital-to-Analog Converter with Differential
+      Outputs.
+
+      ti,pcm3120: Automotive, stereo, 106-dB SNR, 768-kHz, low-power
+      software-controlled audio ADC.
+
+      ti,pcm3140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM Audio ADC
+      with 106-dB SNR.
+
+      ti,pcm5120: Automotive, stereo, 120-dB SNR, 768-kHz, low-power
+      software-controlled audio ADC.
+
+      ti,pcm5140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM Audio ADC
+      with 120-dB SNR.
+
+      ti,pcm6120: Automotive, stereo, 123-dB SNR, 768-kHz, low-power
+      software-controlled audio ADC.
+
+      ti,pcm6140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM Audio ADC
+      with 123-dB SNR.
+
+      ti,pcm6240: Automotive 4-ch audio ADC with integrated programmable mic
+      bias, boost and input diagnostics.
+
+      ti,pcm6260: Automotive 6-ch audio ADC with integrated programmable mic
+      bias, boost and input diagnostics.
+
+      ti,pcm9211: 216-kHz Digital Audio Interface Transceiver (DIX)
+      With Stereo ADC and Routing.
+
+      ti,pcmd3140: Four-channel PDM-input to TDM or I�S output converter.
+
+      ti,pcmd3180: Eight-channel pulse-density-modulation input to TDM or
+      I�S output converter.
+
+      ti,taa5212: Low-power high-performance stereo audio ADC with 118-dB
+      dynamic range.
+
+      ti,tad5212: Low-power stereo audio DAC with 120-dB dynamic range.
+    oneOf:
+      - items:
+          - enum:
+              - ti,adc3120
+              - ti,adc5120
+              - ti,pcm3120
+              - ti,pcm5120
+              - ti,pcm6120
+          - const: ti,adc6120
+      - items:
+          - enum:
+              - ti,pcm6260
+              - ti,pcm6140
+              - ti,pcm3140
+              - ti,pcm5140
+          - const: ti,pcm6240
+      - items:
+          - const: ti,dix4192
+          - const: ti,pcm6240
+          - const: ti,adc6120
+      - items:
+          - const: ti,pcm1690
+          - const: ti,pcm9211
+          - const: ti,pcmd512x
+      - items:
+          - enum:
+              - ti,pcmd3180
+          - const: ti,pcmd3140
+      - items:
+          - enum:
+              - taa5412
+          - const: ti,taa5212
+      - items:
+          - enum:
+              - tad5412
+          - const: ti,tad5212
+      - items:
+          - enum:
+              - ti,pcm6240
+              - ti,pcmd3140
+              - ti,taa5212
+              - ti,tad5212
+              - ti,pcmd3180
+  reg:
+    description:
+      I2C address, in multiple pcmdevices case, all the i2c address
+      aggregate as one Audio Device to support multiple audio slots.
+    minItems: 1
+    maxItems: 4
+
+  reset-gpios:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+    description:
+      Invalid only for ti,pcm1690 because of no INT pin.
+
+  '#sound-dai-cells':
+    const: 0
+
+required:
+  - compatible
+  - reg
+
+allOf:
+  - $ref: dai-common.yaml#
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - ti,pcm1690
+    then:
+      properties:
+        reg:
+          items:
+            minimum: 0x4c
+            maximum: 0x4f
+        interrupts: false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - ti,adc3120
+              - ti,adc5120
+              - ti,adc6120
+              - ti,pcm3120
+              - ti,pcm5120
+              - ti,pcm6120
+              - ti,pcmd3140
+    then:
+      properties:
+        reg:
+          maxItems: 1
+          items:
+            maximum: 0x4e
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - ti,dix4192
+    then:
+      properties:
+        reg:
+          items:
+            minimum: 0x70
+            maximum: 0x73
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - ti,pcm6240
+              - ti,pcm6260
+    then:
+      properties:
+        reg:
+          minItems: 1
+          maxItems: 4
+          items:
+            minimum: 0x48
+            maximum: 0x4b
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - ti,pcm9211
+    then:
+      properties:
+        reg:
+          items:
+            minimum: 0x40
+            maximum: 0x43
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - ti,taa5212
+              - ti,taa5412
+              - ti,tad5212
+              - ti,tad5412
+    then:
+      properties:
+        reg:
+          items:
+            minimum: 0x50
+            maximum: 0x53
+
+additionalProperties: false
+
+examples:
+  - |
+   #include <dt-bindings/gpio/gpio.h>
+   i2c {
+     /* example for two devices with interrupt support */
+     #address-cells = <1>;
+     #size-cells = <0>;
+     pcm6240: audio-codec@48 {
+       compatible = "ti,pcm6240";
+       reg = <0x48>, /* primary-device */
+            <0x4b>; /* secondary-device */
+       #sound-dai-cells = <0>;
+       reset-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>;
+       interrupt-parent = <&gpio1>;
+       interrupts = <15>;
+     };
+   };
+  - |
+   #include <dt-bindings/gpio/gpio.h>
+   i2c {
+     /* example for one device without interrupt support*/
+     #address-cells = <1>;
+     #size-cells = <0>;
+     pcmd3180: audio-codec@4c {
+       compatible = "ti,pcmd3180";
+       reg = <0x4c>;
+       #sound-dai-cells = <0>;
+       reset-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>;
+     };
+   };
+...
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding
  2024-01-26  3:58 ` [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding Shenghao Ding
@ 2024-01-26  8:27   ` Krzysztof Kozlowski
  2024-01-26 13:49     ` Mark Brown
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Kozlowski @ 2024-01-26  8:27 UTC (permalink / raw)
  To: Shenghao Ding, broonie, conor+dt
  Cc: robh+dt, andriy.shevchenko, kevin-lu, baojun.xu, devicetree,
	v-po, lgirdwood, perex, pierre-louis.bossart, 13916275206,
	mohit.chawla, linux-sound, linux-kernel, liam.r.girdwood, soyer,
	jkhuang3, tiwai, pdjuandi, j-mcpherson, navada

On 26/01/2024 04:58, Shenghao Ding wrote:
> PCM6240 driver implements a flexible and configurable setting for register
> and filter coefficients, to one, two or even multiple PCM6240 Family Audio
> chips.
> 

Subject: you just ignored my comment...

Please use scripts/get_maintainers.pl to get a list of necessary people
and lists to CC (and consider --no-git-fallback argument). It might
happen, that command when run on an older kernel, gives you outdated
entries. Therefore please be sure you base your patches on recent Linux
kernel.

Tools like b4 or scripts_getmaintainer.pl provide you proper list of
people, so fix your workflow. Tools might also fail if you work on some
ancient tree (don't, use mainline), work on fork of kernel (don't, use
mainline) or you ignore some maintainers (really don't). Just use b4 and
all the problems go away.

...

> +      ti,pcmd3140: Four-channel PDM-input to TDM or I�S output converter.

This does not look like proper encoding.

> +
> +      ti,pcmd3180: Eight-channel pulse-density-modulation input to TDM or
> +      I�S output converter.
> +
> +      ti,taa5212: Low-power high-performance stereo audio ADC with 118-dB
> +      dynamic range.
> +
> +      ti,tad5212: Low-power stereo audio DAC with 120-dB dynamic range.
> +    oneOf:
> +      - items:
> +          - enum:
> +              - ti,adc3120
> +              - ti,adc5120
> +              - ti,pcm3120
> +              - ti,pcm5120
> +              - ti,pcm6120
> +          - const: ti,adc6120
> +      - items:
> +          - enum:
> +              - ti,pcm6260
> +              - ti,pcm6140
> +              - ti,pcm3140
> +              - ti,pcm5140
> +          - const: ti,pcm6240
> +      - items:
> +          - const: ti,dix4192
> +          - const: ti,pcm6240
> +          - const: ti,adc6120

It does not make sense. You said above adc6120 is not compatible with
pcm6240.

> +      - items:
> +          - const: ti,pcm1690
> +          - const: ti,pcm9211
> +          - const: ti,pcmd512x
> +      - items:
> +          - enum:
> +              - ti,pcmd3180
> +          - const: ti,pcmd3140
> +      - items:
> +          - enum:
> +              - taa5412
> +          - const: ti,taa5212
> +      - items:
> +          - enum:
> +              - tad5412
> +          - const: ti,tad5212
> +      - items:

No need for items.

> +          - enum:
> +              - ti,pcm6240
> +              - ti,pcmd3140
> +              - ti,taa5212
> +              - ti,tad5212
> +              - ti,pcmd3180

Where is adc6120 and others?

Missing blank line.

> +  reg:
> +    description:
> +      I2C address, in multiple pcmdevices case, all the i2c address
> +      aggregate as one Audio Device to support multiple audio slots.
> +    minItems: 1
> +    maxItems: 4
> +
> +  reset-gpios:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +    description:
> +      Invalid only for ti,pcm1690 because of no INT pin.
> +
> +  '#sound-dai-cells':
> +    const: 0
> +
> +required:
> +  - compatible
> +  - reg
> +
> +allOf:
> +  - $ref: dai-common.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,pcm1690
> +    then:
> +      properties:
> +        reg:
> +          items:
> +            minimum: 0x4c
> +            maximum: 0x4f

Nothing improved.

> +        interrupts: false
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,adc3120
> +              - ti,adc5120
> +              - ti,adc6120
> +              - ti,pcm3120
> +              - ti,pcm5120
> +              - ti,pcm6120
> +              - ti,pcmd3140
> +    then:
> +      properties:
> +        reg:
> +          maxItems: 1
> +          items:
> +            maximum: 0x4e
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,dix4192
> +    then:
> +      properties:
> +        reg:
> +          items:
> +            minimum: 0x70
> +            maximum: 0x73

Drop entire if

> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,pcm6240
> +              - ti,pcm6260
> +    then:
> +      properties:
> +        reg:
> +          minItems: 1
> +          maxItems: 4
> +          items:
> +            minimum: 0x48
> +            maximum: 0x4b

Drop entire if

> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,pcm9211
> +    then:
> +      properties:
> +        reg:
> +          items:
> +            minimum: 0x40
> +            maximum: 0x43

Drop entire if


> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,taa5212
> +              - ti,taa5412
> +              - ti,tad5212
> +              - ti,tad5412
> +    then:
> +      properties:
> +        reg:
> +          items:
> +            minimum: 0x50
> +            maximum: 0x53

Drop entire if


> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +   #include <dt-bindings/gpio/gpio.h>
> +   i2c {
> +     /* example for two devices with interrupt support */
> +     #address-cells = <1>;
> +     #size-cells = <0>;
> +     pcm6240: audio-codec@48 {
> +       compatible = "ti,pcm6240";
> +       reg = <0x48>, /* primary-device */
> +            <0x4b>; /* secondary-device */

Looks misaligned.



Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding
  2024-01-26  8:27   ` Krzysztof Kozlowski
@ 2024-01-26 13:49     ` Mark Brown
  2024-01-29  4:43       ` [EXTERNAL] " Ding, Shenghao
  0 siblings, 1 reply; 13+ messages in thread
From: Mark Brown @ 2024-01-26 13:49 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Shenghao Ding, conor+dt, robh+dt, andriy.shevchenko, kevin-lu,
	baojun.xu, devicetree, v-po, lgirdwood, perex,
	pierre-louis.bossart, 13916275206, mohit.chawla, linux-sound,
	linux-kernel, liam.r.girdwood, soyer, jkhuang3, tiwai, pdjuandi,
	j-mcpherson, navada

[-- Attachment #1: Type: text/plain, Size: 566 bytes --]

On Fri, Jan 26, 2024 at 09:27:47AM +0100, Krzysztof Kozlowski wrote:
> On 26/01/2024 04:58, Shenghao Ding wrote:

> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            enum:
> > +              - ti,pcm1690
> > +    then:
> > +      properties:
> > +        reg:
> > +          items:
> > +            minimum: 0x4c
> > +            maximum: 0x4f

> Nothing improved.

Shenghao explained what what this is doing - I'm not sure what the
actual problem is here?  It's an actual restriction on the values that
are valid.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code
  2024-01-26  3:58 [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Shenghao Ding
                   ` (2 preceding siblings ...)
  2024-01-26  3:58 ` [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding Shenghao Ding
@ 2024-01-26 14:33 ` Mark Brown
  2024-01-29  5:03   ` [EXTERNAL] " Ding, Shenghao
  2024-01-28 15:23 ` Andy Shevchenko
  4 siblings, 1 reply; 13+ messages in thread
From: Mark Brown @ 2024-01-26 14:33 UTC (permalink / raw)
  To: Shenghao Ding
  Cc: conor+dt, krzysztof.kozlowski, robh+dt, andriy.shevchenko,
	kevin-lu, baojun.xu, devicetree, v-po, lgirdwood, perex,
	pierre-louis.bossart, 13916275206, mohit.chawla, linux-sound,
	linux-kernel, liam.r.girdwood, soyer, jkhuang3, tiwai, pdjuandi,
	j-mcpherson, navada

[-- Attachment #1: Type: text/plain, Size: 4118 bytes --]

On Fri, Jan 26, 2024 at 11:58:51AM +0800, Shenghao Ding wrote:

This looks mostly good - I've got a few comments that are mainly
stylistic or otherwise very minor, there's one issue with validation of
profile IDs that does look like it's important to fix though.

> +static int pcmdev_dev_read(struct pcmdevice_priv *pcm_dev,
> +	unsigned int dev_no, unsigned int reg, unsigned int *val)
> +{
> +	int ret = -EINVAL;
> +
> +	if (dev_no < pcm_dev->ndev) {

You could write all these functions a bit more simply if you rewrote
these error checks to return immediately on error, that way there's less
indentation and fewer paths later on.

	if (dev_no >= pcm_dev->ndev)
		return -EINVAL;

and so on.  For the ones dealing with locking it can help to have a
single exit path but functions like this don't deal directly with the
locks.

> +
> +		ret = regmap_read(map, reg, val);
> +		if (ret < 0)
> +			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
> +	} else
> +		dev_err(pcm_dev->dev, "%s, no such channel(%d)\n", __func__,
> +			dev_no);
> +

The kernel coding style is that if one side of an if/else has { } both
should.

> +static int pcmdevice_set_profile_id(
> +	struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *codec
> +		= snd_soc_kcontrol_component(kcontrol);
> +	struct pcmdevice_priv *pcm_dev =
> +		snd_soc_component_get_drvdata(codec);
> +	int ret = 0;
> +
> +	if (pcm_dev->cur_conf != ucontrol->value.integer.value[0]) {
> +		pcm_dev->cur_conf = ucontrol->value.integer.value[0];
> +		ret = 1;
> +	}
> +
> +	return ret;
> +}

This will accept any configuration number, shouldn't there be some
validation here?  The put functions doing regmap_update_bits() have
some limiting of values in the regmap_update_bits() but this just stores
the value directly.

> +static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{

> +	mutex_lock(&pcm_dev->codec_lock);
> +	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
> +	if (rc) {
> +		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
> +			__func__, rc);
> +		goto out;
> +	}

It would be kind of nice if the device switching could be hidden inside
a custom regmap and we didn't have all this code duplication but I'm not
thinking of a way of doing that which doesn't just create complications
so probably this is fine.

> +	val = (val >> shift) & mask;
> +	val = (val > max) ? max : val;
> +	val = mc->invert ? max - val : val;
> +	ucontrol->value.integer.value[0] = val;

There's the FIELD_GET() macro (and FIELD_SET() for writing values) - the
core predates them and hence doesn't use them, we might want to update
some time.

> +static int pcmdevice_codec_probe(struct snd_soc_component *codec)
> +{

> +	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
> +		pcm_dev->regbin_name, pcm_dev->dev, GFP_KERNEL, pcm_dev,
> +		pcmdev_regbin_ready);
> +	if (ret) {
> +		dev_err(pcm_dev->dev, "load %s error = %d\n",
> +			pcm_dev->regbin_name, ret);
> +		goto out;
> +	}

It might be better to request the firmware in the I2C probe rather than
in the ASoC level probe, that way there's more time for the firmware to
be loaded before we actually need it.  That does mean you can't register
the controls immediately though so it may be more trouble than it's
worth.

Similarly for the reset, if we reset as early as possible that seems
better.

> +static int pcmdevice_startup(struct snd_pcm_substream *substream,
> +	struct snd_soc_dai *dai)
> +{
> +	struct snd_soc_component *codec = dai->component;
> +	struct pcmdevice_priv *pcm_priv = snd_soc_component_get_drvdata(codec);
> +	int ret = 0;
> +
> +	if (pcm_priv->fw_state != PCMDEVICE_FW_LOAD_OK) {
> +		dev_err(pcm_priv->dev, "DSP bin file not loaded\n");
> +		ret = -EINVAL;
> +	}

Perhaps -EBUSY instead?  What the user is doing is valid.

> +static const struct regmap_config pcmdevice_i2c_regmap = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.cache_type = REGCACHE_RBTREE,

Use _MAPLE for new devices, it's a more modern design with tradeoffs
that work better for most current systems.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code
  2024-01-26  3:58 [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Shenghao Ding
                   ` (3 preceding siblings ...)
  2024-01-26 14:33 ` [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Mark Brown
@ 2024-01-28 15:23 ` Andy Shevchenko
  4 siblings, 0 replies; 13+ messages in thread
From: Andy Shevchenko @ 2024-01-28 15:23 UTC (permalink / raw)
  To: Shenghao Ding
  Cc: broonie, conor+dt, krzysztof.kozlowski, robh+dt, kevin-lu,
	baojun.xu, devicetree, v-po, lgirdwood, perex,
	pierre-louis.bossart, 13916275206, mohit.chawla, linux-sound,
	linux-kernel, liam.r.girdwood, soyer, jkhuang3, tiwai, pdjuandi,
	j-mcpherson, navada

On Fri, Jan 26, 2024 at 11:58:51AM +0800, Shenghao Ding wrote:
> PCM6240 driver implements a flexible and configurable setting for register
> and filter coefficients, to one, two or even multiple PCM6240 Family Audio
> chips.

Same comments applied as per previous version, please read my email and address
them.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 13+ messages in thread

* RE: [EXTERNAL] Re: [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding
  2024-01-26 13:49     ` Mark Brown
@ 2024-01-29  4:43       ` Ding, Shenghao
  2024-01-30 16:18         ` Krzysztof Kozlowski
  0 siblings, 1 reply; 13+ messages in thread
From: Ding, Shenghao @ 2024-01-29  4:43 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: conor+dt, robh+dt, andriy.shevchenko, Lu, Kevin, Xu, Baojun,
	devicetree, P O, Vijeth, lgirdwood, perex, pierre-louis.bossart,
	13916275206, Chawla, Mohit, linux-sound, linux-kernel,
	liam.r.girdwood, soyer, Huang, Jonathan, tiwai, Djuandi, Peter,
	McPherson, Jeff, Navada Kanyana, Mukund, Mark Brown



> -----Original Message-----
> From: Mark Brown <broonie@kernel.org>
> Sent: Friday, January 26, 2024 9:50 PM
> To: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> Cc: Ding, Shenghao <shenghao-ding@ti.com>; conor+dt@kernel.org;
> robh+dt@kernel.org; andriy.shevchenko@linux.intel.com; Lu, Kevin <kevin-
> lu@ti.com>; Xu, Baojun <baojun.xu@ti.com>; devicetree@vger.kernel.org; P
> O, Vijeth <v-po@ti.com>; lgirdwood@gmail.com; perex@perex.cz; pierre-
> louis.bossart@linux.intel.com; 13916275206@139.com; Chawla, Mohit
> <mohit.chawla@ti.com>; linux-sound@vger.kernel.org; linux-
> kernel@vger.kernel.org; liam.r.girdwood@intel.com; soyer@irl.hu; Huang,
> Jonathan <jkhuang3@ti.com>; tiwai@suse.de; Djuandi, Peter
> <pdjuandi@ti.com>; McPherson, Jeff <j-mcpherson@ti.com>; Navada
> Kanyana, Mukund <navada@ti.com>
> Subject: [EXTERNAL] Re: [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add
> initial DT binding
> 
> On Fri, Jan 26, 2024 at 09:27:47AM +0100, Krzysztof Kozlowski wrote:
> > On 26/01/2024 04:58, Shenghao Ding wrote:
> 
> > > +  - if:
> > > +      properties:
> > > +        compatible:
> > > +          contains:
> > > +            enum:
> > > +              - ti,pcm1690
> > > +    then:
> > > +      properties:
> > > +        reg:
> > > +          items:
> > > +            minimum: 0x4c
> > > +            maximum: 0x4f
> 
> > Nothing improved.
> 
> Shenghao explained what what this is doing - I'm not sure what the actual
> problem is here?  It's an actual restriction on the values that are valid.

Hi, Krzysztof. May I have the privilege to petition on behalf of my customers? They 
want to keep these if branches and the i2c address in yaml file. As you know, most 
of my customers used to make mistakes and confuse with the i2c address. Listing 
them here can help them to get the information easily.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* RE: [EXTERNAL] Re: [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code
  2024-01-26 14:33 ` [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Mark Brown
@ 2024-01-29  5:03   ` Ding, Shenghao
  2024-01-29 13:40     ` Mark Brown
  0 siblings, 1 reply; 13+ messages in thread
From: Ding, Shenghao @ 2024-01-29  5:03 UTC (permalink / raw)
  To: Mark Brown
  Cc: conor+dt, krzysztof.kozlowski, robh+dt, andriy.shevchenko, Lu,
	Kevin, Xu, Baojun, devicetree, P O, Vijeth, lgirdwood, perex,
	pierre-louis.bossart, 13916275206, Chawla, Mohit, linux-sound,
	linux-kernel, liam.r.girdwood, soyer, Huang, Jonathan, tiwai,
	Djuandi, Peter, McPherson, Jeff, Navada Kanyana, Mukund



> -----Original Message-----
> From: Mark Brown <broonie@kernel.org>
> Sent: Friday, January 26, 2024 10:33 PM
> To: Ding, Shenghao <shenghao-ding@ti.com>
> Cc: conor+dt@kernel.org; krzysztof.kozlowski@linaro.org;
> robh+dt@kernel.org; andriy.shevchenko@linux.intel.com; Lu, Kevin <kevin-
> lu@ti.com>; Xu, Baojun <baojun.xu@ti.com>; devicetree@vger.kernel.org; P
> O, Vijeth <v-po@ti.com>; lgirdwood@gmail.com; perex@perex.cz; pierre-
> louis.bossart@linux.intel.com; 13916275206@139.com; Chawla, Mohit
> <mohit.chawla@ti.com>; linux-sound@vger.kernel.org; linux-
> kernel@vger.kernel.org; liam.r.girdwood@intel.com; soyer@irl.hu; Huang,
> Jonathan <jkhuang3@ti.com>; tiwai@suse.de; Djuandi, Peter
> <pdjuandi@ti.com>; McPherson, Jeff <j-mcpherson@ti.com>; Navada
> Kanyana, Mukund <navada@ti.com>
> Subject: [EXTERNAL] Re: [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240
> Family driver code
> 
> On Fri, Jan 26, 2024 at 11:58:51AM +0800, Shenghao Ding wrote:
> 
> This looks mostly good - I've got a few comments that are mainly stylistic or
> otherwise very minor, there's one issue with validation of profile IDs that
> does look like it's important to fix though.
..............................
> 
> > +	val = (val >> shift) & mask;
> > +	val = (val > max) ? max : val;
> > +	val = mc->invert ? max - val : val;
> > +	ucontrol->value.integer.value[0] = val;
> 
> There's the FIELD_GET() macro (and FIELD_SET() for writing values) - the core
> predates them and hence doesn't use them, we might want to update some
> time.
Hi, Mark. FIELD_GET seemed not suitable in this, because mask in not the const. 
it will cause compile error.
> 
> > +static int pcmdevice_codec_probe(struct snd_soc_component *codec) {
> 
> > +	ret = request_firmware_nowait(THIS_MODULE,
> FW_ACTION_UEVENT,
> > +		pcm_dev->regbin_name, pcm_dev->dev, GFP_KERNEL,
> pcm_dev,
> > +		pcmdev_regbin_ready);
> > +	if (ret) {
> > +		dev_err(pcm_dev->dev, "load %s error = %d\n",
> > +			pcm_dev->regbin_name, ret);
> > +		goto out;
> > +	}
> 
> It might be better to request the firmware in the I2C probe rather than in the
> ASoC level probe, that way there's more time for the firmware to be loaded
> before we actually need it.  That does mean you can't register the controls
> immediately though so it may be more trouble than it's worth.

I once put request_firmware_nowait in i2c_probe, but it sometimes returned 
error in some platforms. So my customer suggest that it would be moved into 
the codec_probe. It seemed filesystem is not completely ready in some 
platform during calling the i2c_probe.
> 
> Similarly for the reset, if we reset as early as possible that seems better.

As to reset, it is also from my customers' suggestion. they found the issue that 
i2c access error in i2c_probe in some platform. So they put it into codec_probe.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [EXTERNAL] Re: [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code
  2024-01-29  5:03   ` [EXTERNAL] " Ding, Shenghao
@ 2024-01-29 13:40     ` Mark Brown
  0 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2024-01-29 13:40 UTC (permalink / raw)
  To: Ding, Shenghao
  Cc: conor+dt, krzysztof.kozlowski, robh+dt, andriy.shevchenko, Lu,
	Kevin, Xu, Baojun, devicetree, P O, Vijeth, lgirdwood, perex,
	pierre-louis.bossart, 13916275206, Chawla, Mohit, linux-sound,
	linux-kernel, liam.r.girdwood, soyer, Huang, Jonathan, tiwai,
	Djuandi, Peter, McPherson, Jeff, Navada Kanyana, Mukund

[-- Attachment #1: Type: text/plain, Size: 1473 bytes --]

On Mon, Jan 29, 2024 at 05:03:48AM +0000, Ding, Shenghao wrote:

> > It might be better to request the firmware in the I2C probe rather than in the
> > ASoC level probe, that way there's more time for the firmware to be loaded
> > before we actually need it.  That does mean you can't register the controls
> > immediately though so it may be more trouble than it's worth.

> I once put request_firmware_nowait in i2c_probe, but it sometimes returned 
> error in some platforms. So my customer suggest that it would be moved into 
> the codec_probe. It seemed filesystem is not completely ready in some 
> platform during calling the i2c_probe.

That indicates that this is still racy - shuffling things around has
papered over a timing issue on their particular system but it's still
possible to have the card come up before the filesystems are fully ready
(especially if all the drivers are built in).  If the DSP firmware is
essential to the device's operation the driver should defer registration
with the core until the firmware has loaded.  wm8958 should have an
example of this IIRC.

> > Similarly for the reset, if we reset as early as possible that seems better.

> As to reset, it is also from my customers' suggestion. they found the issue that 
> i2c access error in i2c_probe in some platform. So they put it into codec_probe.

That suggests the reset might be missing some delays or something and
again there might be some issues.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [EXTERNAL] Re: [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding
  2024-01-29  4:43       ` [EXTERNAL] " Ding, Shenghao
@ 2024-01-30 16:18         ` Krzysztof Kozlowski
  2024-01-31 12:17           ` Ding, Shenghao
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Kozlowski @ 2024-01-30 16:18 UTC (permalink / raw)
  To: Ding, Shenghao
  Cc: conor+dt, robh+dt, andriy.shevchenko, Lu, Kevin, Xu, Baojun,
	devicetree, P O, Vijeth, lgirdwood, perex, pierre-louis.bossart,
	13916275206, Chawla, Mohit, linux-sound, linux-kernel,
	liam.r.girdwood, soyer, Huang, Jonathan, tiwai, Djuandi, Peter,
	McPherson, Jeff, Navada Kanyana, Mukund, Mark Brown

On 29/01/2024 05:43, Ding, Shenghao wrote:
>>
>> On Fri, Jan 26, 2024 at 09:27:47AM +0100, Krzysztof Kozlowski wrote:
>>> On 26/01/2024 04:58, Shenghao Ding wrote:
>>
>>>> +  - if:
>>>> +      properties:
>>>> +        compatible:
>>>> +          contains:
>>>> +            enum:
>>>> +              - ti,pcm1690
>>>> +    then:
>>>> +      properties:
>>>> +        reg:
>>>> +          items:
>>>> +            minimum: 0x4c
>>>> +            maximum: 0x4f
>>
>>> Nothing improved.
>>
>> Shenghao explained what what this is doing - I'm not sure what the actual
>> problem is here?  It's an actual restriction on the values that are valid.
> 
> Hi, Krzysztof. May I have the privilege to petition on behalf of my customers? They 
> want to keep these if branches and the i2c address in yaml file. As you know, most 
> of my customers used to make mistakes and confuse with the i2c address. Listing 
> them here can help them to get the information easily.

To which cases this exception will apply? Your patches, all TI?

Bindings are not supposed to be so detailed to create DTS out if it,
because it makes them more difficult to maintain. Such amount of
if:then: makes it trickier, for no real gain.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 13+ messages in thread

* RE: [EXTERNAL] Re: [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding
  2024-01-30 16:18         ` Krzysztof Kozlowski
@ 2024-01-31 12:17           ` Ding, Shenghao
  0 siblings, 0 replies; 13+ messages in thread
From: Ding, Shenghao @ 2024-01-31 12:17 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: conor+dt, robh+dt, andriy.shevchenko, Lu, Kevin, Xu, Baojun,
	devicetree, P O, Vijeth, lgirdwood, perex, pierre-louis.bossart,
	13916275206, Chawla, Mohit, linux-sound, linux-kernel,
	liam.r.girdwood, soyer, Huang, Jonathan, tiwai, Djuandi, Peter,
	McPherson, Jeff, Navada Kanyana, Mukund, Mark Brown



> -----Original Message-----
> From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> Sent: Wednesday, January 31, 2024 12:18 AM
> To: Ding, Shenghao <shenghao-ding@ti.com>
> Cc: conor+dt@kernel.org; robh+dt@kernel.org;
> andriy.shevchenko@linux.intel.com; Lu, Kevin <kevin-lu@ti.com>; Xu, Baojun
> <baojun.xu@ti.com>; devicetree@vger.kernel.org; P O, Vijeth <v-
> po@ti.com>; lgirdwood@gmail.com; perex@perex.cz; pierre-
> louis.bossart@linux.intel.com; 13916275206@139.com; Chawla, Mohit
> <mohit.chawla@ti.com>; linux-sound@vger.kernel.org; linux-
> kernel@vger.kernel.org; liam.r.girdwood@intel.com; soyer@irl.hu; Huang,
> Jonathan <jkhuang3@ti.com>; tiwai@suse.de; Djuandi, Peter
> <pdjuandi@ti.com>; McPherson, Jeff <j-mcpherson@ti.com>; Navada
> Kanyana, Mukund <navada@ti.com>; Mark Brown <broonie@kernel.org>
> Subject: Re: [EXTERNAL] Re: [PATCH v2 4/4] ASoc: dt-bindings: PCM6240:
> Add initial DT binding
> 
> On 29/01/2024 05: 43, Ding, Shenghao wrote: >> >> On Fri, Jan 26, 2024 at
> 09: 27: 47AM +0100, Krzysztof Kozlowski wrote: >>> On 26/01/2024 04: 58,
> Shenghao Ding wrote: >> >>>> + - if: >>>> + properties:
> ZjQcmQRYFpfptBannerStart This message was sent from outside of Texas
> Instruments.
> Do not click links or open attachments unless you recognize the source of
> this email and know the content is safe.
> 
> ZjQcmQRYFpfptBannerEnd
> On 29/01/2024 05:43, Ding, Shenghao wrote:
> >>
> >> On Fri, Jan 26, 2024 at 09:27:47AM +0100, Krzysztof Kozlowski wrote:
> >>> On 26/01/2024 04:58, Shenghao Ding wrote:
> >>
> >>>> +  - if:
> >>>> +      properties:
> >>>> +        compatible:
> >>>> +          contains:
> >>>> +            enum:
> >>>> +              - ti,pcm1690
> >>>> +    then:
> >>>> +      properties:
> >>>> +        reg:
> >>>> +          items:
> >>>> +            minimum: 0x4c
> >>>> +            maximum: 0x4f
> >>
> >>> Nothing improved.
> >>
> >> Shenghao explained what what this is doing - I'm not sure what the
> >> actual problem is here?  It's an actual restriction on the values that are
> valid.
> >
> > Hi, Krzysztof. May I have the privilege to petition on behalf of my
> > customers? They want to keep these if branches and the i2c address in
> > yaml file. As you know, most of my customers used to make mistakes and
> > confuse with the i2c address. Listing them here can help them to get the
> information easily.
> 
> To which cases this exception will apply? Your patches, all TI?
> 
Some AI customers usually like to try some innovative projects, such as their multiple-slot TDM 
devices with serval different ADCs or DAC on different i2c buses. Traditional driver code really 
can't meet their requirements.
> Bindings are not supposed to be so detailed to create DTS out if it, because it
> makes them more difficult to maintain. Such amount of
> if:then: makes it trickier, for no real gain.
> 
Accept

 Best regards,
Shenghao Ding


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2024-01-31 12:18 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-26  3:58 [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Shenghao Ding
2024-01-26  3:58 ` [PATCH v2 2/4] ASoc: PCM6240: Create header file for " Shenghao Ding
2024-01-26  3:58 ` [PATCH v2 3/4] ASoc: PCM6240: Add compile item for PCM6240 Family driver Shenghao Ding
2024-01-26  3:58 ` [PATCH v2 4/4] ASoc: dt-bindings: PCM6240: Add initial DT binding Shenghao Ding
2024-01-26  8:27   ` Krzysztof Kozlowski
2024-01-26 13:49     ` Mark Brown
2024-01-29  4:43       ` [EXTERNAL] " Ding, Shenghao
2024-01-30 16:18         ` Krzysztof Kozlowski
2024-01-31 12:17           ` Ding, Shenghao
2024-01-26 14:33 ` [PATCH v2 1/4] ASoc: PCM6240: Create PCM6240 Family driver code Mark Brown
2024-01-29  5:03   ` [EXTERNAL] " Ding, Shenghao
2024-01-29 13:40     ` Mark Brown
2024-01-28 15:23 ` Andy Shevchenko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).