linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/3] Add the Renesas IDT821034 codec support
@ 2023-01-26  8:51 Herve Codina
  2023-01-26  8:51 ` [PATCH v3 1/3] dt-bindings: sound: Add Renesas IDT821034 codec Herve Codina
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Herve Codina @ 2023-01-26  8:51 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: alsa-devel, devicetree, linux-kernel, linux-gpio,
	Christophe Leroy, Thomas Petazzoni

Hi,

The Renesas IDT821034 codec is four channel PCM codec with on-chip
filters and programmable gain setting.
It also provides SLIC (Subscriber Line Interface Circuit) signals as
GPIOs.

Based on previous iteration,
  https://lore.kernel.org/linux-kernel/20230120095036.514639-1-herve.codina@bootlin.com/
this v3 series mainly:
  - Fixes _put() methods
  - Introduces and uses idt821034_2x8bit_write()
  - Removes the '#if IS_ENABLED(CONFIG_GPIOLIB)' conditional

Best regards,
Herve Codina

Changes v2 -> v3:
  - Patch 1
    Fix the example node name.
    Add 'Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>'

  - Patch 2
    Fix _put() methods return code.
    Remove 'select REGMAP_SPI' from Kconfig
    Introduce and use idt821034_2x8bit_write()
    Remove the '#if IS_ENABLED(CONFIG_GPIOLIB)' conditional
    Fix comments and other minor code simplifications

Changes v1 -> v2:
  - All patches
    Reformat commit log

  - Patch 1
    Remove '$ref: /schemas/gpio/gpio.yaml#'
    Use 'unevaluatedProperties: false'
    Update the node name and remove the sound node in the example

  - Patch 2
    Change the file header comment format
    Rework in order to remove the regmap virtual registers

Herve Codina (3):
  dt-bindings: sound: Add Renesas IDT821034 codec
  ASoC: codecs: Add support for the Renesas IDT821034 codec
  MAINTAINERS: add the Renesas IDT821034 codec entry

 .../bindings/sound/renesas,idt821034.yaml     |   75 ++
 MAINTAINERS                                   |    7 +
 sound/soc/codecs/Kconfig                      |   11 +
 sound/soc/codecs/Makefile                     |    2 +
 sound/soc/codecs/idt821034.c                  | 1180 +++++++++++++++++
 5 files changed, 1275 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/renesas,idt821034.yaml
 create mode 100644 sound/soc/codecs/idt821034.c

-- 
2.39.0


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

* [PATCH v3 1/3] dt-bindings: sound: Add Renesas IDT821034 codec
  2023-01-26  8:51 [PATCH v3 0/3] Add the Renesas IDT821034 codec support Herve Codina
@ 2023-01-26  8:51 ` Herve Codina
  2023-01-26  8:51 ` [PATCH v3 2/3] ASoC: codecs: Add support for the " Herve Codina
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Herve Codina @ 2023-01-26  8:51 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: alsa-devel, devicetree, linux-kernel, linux-gpio,
	Christophe Leroy, Thomas Petazzoni, Krzysztof Kozlowski

The Renesas IDT821034 codec is a quad PCM codec with programmable
gain.

Signed-off-by: Herve Codina <herve.codina@bootlin.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
 .../bindings/sound/renesas,idt821034.yaml     | 75 +++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/renesas,idt821034.yaml

diff --git a/Documentation/devicetree/bindings/sound/renesas,idt821034.yaml b/Documentation/devicetree/bindings/sound/renesas,idt821034.yaml
new file mode 100644
index 000000000000..a2b92dba5529
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/renesas,idt821034.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/renesas,idt821034.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas IDT821034 codec device
+
+maintainers:
+  - Herve Codina <herve.codina@bootlin.com>
+
+description: |
+  The IDT821034 codec is a four channel PCM codec with onchip filters and
+  programmable gain setting.
+
+  The time-slots used by the codec must be set and so, the properties
+  'dai-tdm-slot-num', 'dai-tdm-slot-width', 'dai-tdm-slot-tx-mask' and
+  'dai-tdm-slot-rx-mask' must be present in the ALSA sound card node for
+  sub-nodes that involve the codec. The codec uses one 8bit time-slot per
+  channel.
+  'dai-tdm-tdm-slot-with' must be set to 8.
+
+  The IDT821034 codec also supports 5 gpios (SLIC signals) per channel.
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    const: renesas,idt821034
+
+  reg:
+    description:
+      SPI device address.
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 8192000
+
+  spi-cpha: true
+
+  '#sound-dai-cells':
+    const: 0
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+required:
+  - compatible
+  - reg
+  - spi-cpha
+  - '#sound-dai-cells'
+  - gpio-controller
+  - '#gpio-cells'
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        audio-codec@0 {
+            compatible = "renesas,idt821034";
+            reg = <0>;
+            spi-max-frequency = <8192000>;
+            spi-cpha;
+            #sound-dai-cells = <0>;
+            gpio-controller;
+            #gpio-cells = <2>;
+        };
+    };
-- 
2.39.0


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

* [PATCH v3 2/3] ASoC: codecs: Add support for the Renesas IDT821034 codec
  2023-01-26  8:51 [PATCH v3 0/3] Add the Renesas IDT821034 codec support Herve Codina
  2023-01-26  8:51 ` [PATCH v3 1/3] dt-bindings: sound: Add Renesas IDT821034 codec Herve Codina
@ 2023-01-26  8:51 ` Herve Codina
  2023-01-26  8:51 ` [PATCH v3 3/3] MAINTAINERS: add the Renesas IDT821034 codec entry Herve Codina
  2023-01-31 14:32 ` [PATCH v3 0/3] Add the Renesas IDT821034 codec support Mark Brown
  3 siblings, 0 replies; 5+ messages in thread
From: Herve Codina @ 2023-01-26  8:51 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: alsa-devel, devicetree, linux-kernel, linux-gpio,
	Christophe Leroy, Thomas Petazzoni

The Renesas IDT821034 codec is four channel PCM codec with on-chip
filters and programmable gain setting.
It also provides SLIC (Subscriber Line Interface Circuit) signals as
GPIOs.

Signed-off-by: Herve Codina <herve.codina@bootlin.com>
---
 sound/soc/codecs/Kconfig     |   11 +
 sound/soc/codecs/Makefile    |    2 +
 sound/soc/codecs/idt821034.c | 1180 ++++++++++++++++++++++++++++++++++
 3 files changed, 1193 insertions(+)
 create mode 100644 sound/soc/codecs/idt821034.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 0f9d71490075..5531bf8d4b3f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -107,6 +107,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_HDAC_HDMI
 	imply SND_SOC_HDAC_HDA
 	imply SND_SOC_ICS43432
+	imply SND_SOC_IDT821034
 	imply SND_SOC_INNO_RK3036
 	imply SND_SOC_ISABELLE
 	imply SND_SOC_JZ4740_CODEC
@@ -972,6 +973,16 @@ config SND_SOC_HDA
 config SND_SOC_ICS43432
 	tristate "ICS43423 and compatible i2s microphones"
 
+config SND_SOC_IDT821034
+	tristate "Renesas IDT821034 quad PCM codec"
+	depends on SPI
+	help
+	  Enable support for the Renesas IDT821034 quad PCM with
+	  programmable gain codec.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-soc-idt821034.
+
 config SND_SOC_INNO_RK3036
 	tristate "Inno codec driver for RK3036 SoC"
 	select REGMAP_MMIO
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 71d3ce5867e4..bcf95de654fd 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -111,6 +111,7 @@ snd-soc-hdac-hdmi-objs := hdac_hdmi.o
 snd-soc-hdac-hda-objs := hdac_hda.o
 snd-soc-hda-codec-objs := hda.o hda-dai.o
 snd-soc-ics43432-objs := ics43432.o
+snd-soc-idt821034-objs := idt821034.o
 snd-soc-inno-rk3036-objs := inno_rk3036.o
 snd-soc-isabelle-objs := isabelle.o
 snd-soc-jz4740-codec-objs := jz4740.o
@@ -472,6 +473,7 @@ obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o
 obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
 obj-$(CONFIG_SND_SOC_HDA) += snd-soc-hda-codec.o
 obj-$(CONFIG_SND_SOC_ICS43432)	+= snd-soc-ics43432.o
+obj-$(CONFIG_SND_SOC_IDT821034)	+= snd-soc-idt821034.o
 obj-$(CONFIG_SND_SOC_INNO_RK3036)	+= snd-soc-inno-rk3036.o
 obj-$(CONFIG_SND_SOC_ISABELLE)	+= snd-soc-isabelle.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)	+= snd-soc-jz4740-codec.o
diff --git a/sound/soc/codecs/idt821034.c b/sound/soc/codecs/idt821034.c
new file mode 100644
index 000000000000..5d01787b1c1f
--- /dev/null
+++ b/sound/soc/codecs/idt821034.c
@@ -0,0 +1,1180 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// IDT821034 ALSA SoC driver
+//
+// Copyright 2022 CS GROUP France
+//
+// Author: Herve Codina <herve.codina@bootlin.com>
+
+#include <linux/bitrev.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#define IDT821034_NB_CHANNEL	4
+
+struct idt821034_amp {
+	u16 gain;
+	bool is_muted;
+};
+
+struct idt821034 {
+	struct spi_device *spi;
+	struct mutex mutex;
+	u8 spi_tx_buf; /* Cannot use stack area for SPI (dma-safe memory) */
+	u8 spi_rx_buf; /* Cannot use stack area for SPI (dma-safe memory) */
+	struct {
+		u8 codec_conf;
+		struct {
+			u8 power;
+			u8 tx_slot;
+			u8 rx_slot;
+			u8 slic_conf;
+			u8 slic_control;
+		} ch[IDT821034_NB_CHANNEL];
+	} cache;
+	struct {
+		struct {
+			struct idt821034_amp amp_out;
+			struct idt821034_amp amp_in;
+		} ch[IDT821034_NB_CHANNEL];
+	} amps;
+	int max_ch_playback;
+	int max_ch_capture;
+	struct gpio_chip gpio_chip;
+};
+
+static int idt821034_8bit_write(struct idt821034 *idt821034, u8 val)
+{
+	struct spi_transfer xfer[] = {
+		{
+			.tx_buf = &idt821034->spi_tx_buf,
+			.len = 1,
+		}, {
+			.cs_off = 1,
+			.tx_buf = &idt821034->spi_tx_buf,
+			.len = 1,
+		}
+	};
+
+	idt821034->spi_tx_buf = val;
+
+	dev_vdbg(&idt821034->spi->dev, "spi xfer wr 0x%x\n", val);
+
+	return spi_sync_transfer(idt821034->spi, xfer, 2);
+}
+
+static int idt821034_2x8bit_write(struct idt821034 *idt821034, u8 val1, u8 val2)
+{
+	int ret;
+
+	ret = idt821034_8bit_write(idt821034, val1);
+	if (ret)
+		return ret;
+	return idt821034_8bit_write(idt821034, val2);
+}
+
+static int idt821034_8bit_read(struct idt821034 *idt821034, u8 valw, u8 *valr)
+{
+	struct spi_transfer xfer[] = {
+		{
+			.tx_buf = &idt821034->spi_tx_buf,
+			.rx_buf = &idt821034->spi_rx_buf,
+			.len = 1,
+		}, {
+			.cs_off = 1,
+			.tx_buf = &idt821034->spi_tx_buf,
+			.len = 1,
+		}
+	};
+	int ret;
+
+	idt821034->spi_tx_buf = valw;
+
+	ret = spi_sync_transfer(idt821034->spi, xfer, 2);
+	if (ret)
+		return ret;
+
+	*valr = idt821034->spi_rx_buf;
+
+	dev_vdbg(&idt821034->spi->dev, "spi xfer wr 0x%x, rd 0x%x\n",
+		 valw, *valr);
+
+	return 0;
+}
+
+/* Available mode for the programming sequence */
+#define IDT821034_MODE_CODEC(_ch) (0x80 | ((_ch) << 2))
+#define IDT821034_MODE_SLIC(_ch)  (0xD0 | ((_ch) << 2))
+#define IDT821034_MODE_GAIN(_ch)  (0xC0 | ((_ch) << 2))
+
+/* Power values that can be used in 'power' (can be ORed) */
+#define IDT821034_CONF_PWRUP_TX		BIT(1) /* from analog input to PCM */
+#define IDT821034_CONF_PWRUP_RX		BIT(0) /* from PCM to analog output */
+
+static int idt821034_set_channel_power(struct idt821034 *idt821034, u8 ch, u8 power)
+{
+	u8 conf;
+	int ret;
+
+	dev_dbg(&idt821034->spi->dev, "set_channel_power(%u, 0x%x)\n", ch, power);
+
+	conf = IDT821034_MODE_CODEC(ch) | idt821034->cache.codec_conf;
+
+	if (power & IDT821034_CONF_PWRUP_RX) {
+		ret = idt821034_2x8bit_write(idt821034,
+					     conf | IDT821034_CONF_PWRUP_RX,
+					     idt821034->cache.ch[ch].rx_slot);
+		if (ret)
+			return ret;
+	}
+	if (power & IDT821034_CONF_PWRUP_TX) {
+		ret = idt821034_2x8bit_write(idt821034,
+					     conf | IDT821034_CONF_PWRUP_TX,
+					     idt821034->cache.ch[ch].tx_slot);
+		if (ret)
+			return ret;
+	}
+	if (!(power & (IDT821034_CONF_PWRUP_TX | IDT821034_CONF_PWRUP_RX))) {
+		ret = idt821034_2x8bit_write(idt821034, conf, 0);
+		if (ret)
+			return ret;
+	}
+
+	idt821034->cache.ch[ch].power = power;
+
+	return 0;
+}
+
+static u8 idt821034_get_channel_power(struct idt821034 *idt821034, u8 ch)
+{
+	return idt821034->cache.ch[ch].power;
+}
+
+/* Codec configuration values that can be used in 'codec_conf' (can be ORed) */
+#define IDT821034_CONF_ALAW_MODE	BIT(5)
+#define IDT821034_CONF_DELAY_MODE	BIT(4)
+
+static int idt821034_set_codec_conf(struct idt821034 *idt821034, u8 codec_conf)
+{
+	u8 conf;
+	u8 ts;
+	int ret;
+
+	dev_dbg(&idt821034->spi->dev, "set_codec_conf(0x%x)\n", codec_conf);
+
+	/* codec conf fields are common to all channel.
+	 * Arbitrary use of channel 0 for this configuration.
+	 */
+
+	/* Set Configuration Register */
+	conf = IDT821034_MODE_CODEC(0) | codec_conf;
+
+	/* Update conf value and timeslot register value according
+	 * to cache values
+	 */
+	if (idt821034->cache.ch[0].power & IDT821034_CONF_PWRUP_RX) {
+		conf |= IDT821034_CONF_PWRUP_RX;
+		ts = idt821034->cache.ch[0].rx_slot;
+	} else if (idt821034->cache.ch[0].power & IDT821034_CONF_PWRUP_TX) {
+		conf |= IDT821034_CONF_PWRUP_TX;
+		ts = idt821034->cache.ch[0].tx_slot;
+	} else {
+		ts = 0x00;
+	}
+
+	/* Write configuration register and time-slot register */
+	ret = idt821034_2x8bit_write(idt821034, conf, ts);
+	if (ret)
+		return ret;
+
+	idt821034->cache.codec_conf = codec_conf;
+	return 0;
+}
+
+static u8 idt821034_get_codec_conf(struct idt821034 *idt821034)
+{
+	return idt821034->cache.codec_conf;
+}
+
+/* Channel direction values that can be used in 'ch_dir' (can be ORed) */
+#define IDT821034_CH_RX		BIT(0) /* from PCM to analog output */
+#define IDT821034_CH_TX		BIT(1) /* from analog input to PCM */
+
+static int idt821034_set_channel_ts(struct idt821034 *idt821034, u8 ch, u8 ch_dir, u8 ts_num)
+{
+	u8 conf;
+	int ret;
+
+	dev_dbg(&idt821034->spi->dev, "set_channel_ts(%u, 0x%x, %d)\n", ch, ch_dir, ts_num);
+
+	conf = IDT821034_MODE_CODEC(ch) | idt821034->cache.codec_conf;
+
+	if (ch_dir & IDT821034_CH_RX) {
+		if (idt821034->cache.ch[ch].power & IDT821034_CONF_PWRUP_RX) {
+			ret = idt821034_2x8bit_write(idt821034,
+						     conf | IDT821034_CONF_PWRUP_RX,
+						     ts_num);
+			if (ret)
+				return ret;
+		}
+		idt821034->cache.ch[ch].rx_slot = ts_num;
+	}
+	if (ch_dir & IDT821034_CH_TX) {
+		if (idt821034->cache.ch[ch].power & IDT821034_CONF_PWRUP_TX) {
+			ret = idt821034_2x8bit_write(idt821034,
+						     conf | IDT821034_CONF_PWRUP_TX,
+						     ts_num);
+			if (ret)
+				return ret;
+		}
+		idt821034->cache.ch[ch].tx_slot = ts_num;
+	}
+
+	return 0;
+}
+
+/* SLIC direction values that can be used in 'slic_dir' (can be ORed) */
+#define IDT821034_SLIC_IO1_IN       BIT(1)
+#define IDT821034_SLIC_IO0_IN       BIT(0)
+
+static int idt821034_set_slic_conf(struct idt821034 *idt821034, u8 ch, u8 slic_dir)
+{
+	u8 conf;
+	int ret;
+
+	dev_dbg(&idt821034->spi->dev, "set_slic_conf(%u, 0x%x)\n", ch, slic_dir);
+
+	conf = IDT821034_MODE_SLIC(ch) | slic_dir;
+	ret = idt821034_2x8bit_write(idt821034, conf, idt821034->cache.ch[ch].slic_control);
+	if (ret)
+		return ret;
+
+	idt821034->cache.ch[ch].slic_conf = slic_dir;
+
+	return 0;
+}
+
+static u8 idt821034_get_slic_conf(struct idt821034 *idt821034, u8 ch)
+{
+	return idt821034->cache.ch[ch].slic_conf;
+}
+
+static int idt821034_write_slic_raw(struct idt821034 *idt821034, u8 ch, u8 slic_raw)
+{
+	u8 conf;
+	int ret;
+
+	dev_dbg(&idt821034->spi->dev, "write_slic_raw(%u, 0x%x)\n", ch, slic_raw);
+
+	/*
+	 * On write, slic_raw is mapped as follow :
+	 *   b4: O_4
+	 *   b3: O_3
+	 *   b2: O_2
+	 *   b1: I/O_1
+	 *   b0: I/O_0
+	 */
+
+	conf = IDT821034_MODE_SLIC(ch) | idt821034->cache.ch[ch].slic_conf;
+	ret = idt821034_2x8bit_write(idt821034, conf, slic_raw);
+	if (ret)
+		return ret;
+
+	idt821034->cache.ch[ch].slic_control = slic_raw;
+	return 0;
+}
+
+static u8 idt821034_get_written_slic_raw(struct idt821034 *idt821034, u8 ch)
+{
+	return idt821034->cache.ch[ch].slic_control;
+}
+
+static int idt821034_read_slic_raw(struct idt821034 *idt821034, u8 ch, u8 *slic_raw)
+{
+	u8 val;
+	int ret;
+
+	/*
+	 * On read, slic_raw is mapped as follow :
+	 *   b7: I/O_0
+	 *   b6: I/O_1
+	 *   b5: O_2
+	 *   b4: O_3
+	 *   b3: O_4
+	 *   b2: I/O1_0, I/O_0 from channel 1 (no matter ch value)
+	 *   b1: I/O2_0, I/O_0 from channel 2 (no matter ch value)
+	 *   b2: I/O3_0, I/O_0 from channel 3 (no matter ch value)
+	 */
+
+	val = IDT821034_MODE_SLIC(ch) | idt821034->cache.ch[ch].slic_conf;
+	ret = idt821034_8bit_write(idt821034, val);
+	if (ret)
+		return ret;
+
+	ret = idt821034_8bit_read(idt821034, idt821034->cache.ch[ch].slic_control, slic_raw);
+	if (ret)
+		return ret;
+
+	dev_dbg(&idt821034->spi->dev, "read_slic_raw(%i) 0x%x\n", ch, *slic_raw);
+
+	return 0;
+}
+
+/* Gain type values that can be used in 'gain_type' (cannot be ORed) */
+#define IDT821034_GAIN_RX		(0 << 1) /* from PCM to analog output */
+#define IDT821034_GAIN_TX		(1 << 1) /* from analog input to PCM */
+
+static int idt821034_set_gain_channel(struct idt821034 *idt821034, u8 ch,
+				      u8 gain_type, u16 gain_val)
+{
+	u8 conf;
+	int ret;
+
+	dev_dbg(&idt821034->spi->dev, "set_gain_channel(%u, 0x%x, 0x%x-%d)\n",
+		ch, gain_type, gain_val, gain_val);
+
+	/*
+	 * The gain programming coefficients should be calculated as:
+	 *   Transmit : Coeff_X = round [ gain_X0dB × gain_X ]
+	 *   Receive: Coeff_R = round [ gain_R0dB × gain_R ]
+	 * where:
+	 *   gain_X0dB = 1820;
+	 *   gain_X is the target gain;
+	 *   Coeff_X should be in the range of 0 to 8192.
+	 *   gain_R0dB = 2506;
+	 *   gain_R is the target gain;
+	 *   Coeff_R should be in the range of 0 to 8192.
+	 *
+	 * A gain programming coefficient is 14-bit wide and in binary format.
+	 * The 7 Most Significant Bits of the coefficient is called
+	 * GA_MSB_Transmit for transmit path, or is called GA_MSB_Receive for
+	 * receive path; The 7 Least Significant Bits of the coefficient is
+	 * called GA_LSB_ Transmit for transmit path, or is called
+	 * GA_LSB_Receive for receive path.
+	 *
+	 * An example is given below to clarify the calculation of the
+	 * coefficient. To program a +3 dB gain in transmit path and a -3.5 dB
+	 * gain in receive path:
+	 *
+	 * Linear Code of +3dB = 10^(3/20)= 1.412537545
+	 * Coeff_X = round (1820 × 1.412537545) = 2571
+	 *                                      = 0b001010_00001011
+	 * GA_MSB_Transmit = 0b0010100
+	 * GA_LSB_Transmit = 0b0001011
+	 *
+	 * Linear Code of -3.5dB = 10^(-3.5/20) = 0.668343917
+	 * Coeff_R= round (2506 × 0.668343917) = 1675
+	 *                                     = 0b0001101_0001011
+	 * GA_MSB_Receive = 0b0001101
+	 * GA_LSB_Receive = 0b0001011
+	 */
+
+	conf = IDT821034_MODE_GAIN(ch) | gain_type;
+
+	ret = idt821034_2x8bit_write(idt821034, conf | 0x00, gain_val & 0x007F);
+	if (ret)
+		return ret;
+
+	ret = idt821034_2x8bit_write(idt821034, conf | 0x01, (gain_val >> 7) & 0x7F);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* Id helpers used in controls and dapm */
+#define IDT821034_DIR_OUT (1 << 3)
+#define IDT821034_DIR_IN  (0 << 3)
+#define IDT821034_ID(_ch, _dir) (((_ch) & 0x03) | (_dir))
+#define IDT821034_ID_OUT(_ch) IDT821034_ID(_ch, IDT821034_DIR_OUT)
+#define IDT821034_ID_IN(_ch)  IDT821034_ID(_ch, IDT821034_DIR_IN)
+
+#define IDT821034_ID_GET_CHAN(_id) ((_id) & 0x03)
+#define IDT821034_ID_GET_DIR(_id) ((_id) & (1 << 3))
+#define IDT821034_ID_IS_OUT(_id) (IDT821034_ID_GET_DIR(_id) == IDT821034_DIR_OUT)
+
+static int idt821034_kctrl_gain_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component);
+	int min = mc->min;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+	int val;
+	u8 ch;
+
+	ch = IDT821034_ID_GET_CHAN(mc->reg);
+
+	mutex_lock(&idt821034->mutex);
+	if (IDT821034_ID_IS_OUT(mc->reg))
+		val = idt821034->amps.ch[ch].amp_out.gain;
+	else
+		val = idt821034->amps.ch[ch].amp_in.gain;
+	mutex_unlock(&idt821034->mutex);
+
+	ucontrol->value.integer.value[0] = val & mask;
+	if (invert)
+		ucontrol->value.integer.value[0] = max - ucontrol->value.integer.value[0];
+	else
+		ucontrol->value.integer.value[0] = ucontrol->value.integer.value[0] - min;
+
+	return 0;
+}
+
+static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component);
+	struct idt821034_amp *amp;
+	int min = mc->min;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+	unsigned int val;
+	int ret;
+	u8 gain_type;
+	u8 ch;
+
+	val = ucontrol->value.integer.value[0];
+	if (val < 0)
+		return -EINVAL;
+	if (val > max - min)
+		return -EINVAL;
+
+	if (invert)
+		val = (max - val) & mask;
+	else
+		val = (val + min) & mask;
+
+	ch = IDT821034_ID_GET_CHAN(mc->reg);
+
+	mutex_lock(&idt821034->mutex);
+
+	if (IDT821034_ID_IS_OUT(mc->reg)) {
+		amp = &idt821034->amps.ch[ch].amp_out;
+		gain_type = IDT821034_GAIN_RX;
+	} else {
+		amp = &idt821034->amps.ch[ch].amp_in;
+		gain_type = IDT821034_GAIN_TX;
+	}
+
+	if (amp->gain == val) {
+		ret = 0;
+		goto end;
+	}
+
+	if (!amp->is_muted) {
+		ret = idt821034_set_gain_channel(idt821034, ch, gain_type, val);
+		if (ret)
+			goto end;
+	}
+
+	amp->gain = val;
+	ret = 1; /* The value changed */
+end:
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component);
+	int id = kcontrol->private_value;
+	bool is_muted;
+	u8 ch;
+
+	ch = IDT821034_ID_GET_CHAN(id);
+
+	mutex_lock(&idt821034->mutex);
+	is_muted = IDT821034_ID_IS_OUT(id) ?
+			idt821034->amps.ch[ch].amp_out.is_muted :
+			idt821034->amps.ch[ch].amp_in.is_muted;
+	mutex_unlock(&idt821034->mutex);
+
+	ucontrol->value.integer.value[0] = !is_muted;
+
+	return 0;
+}
+
+static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component);
+	int id = kcontrol->private_value;
+	struct idt821034_amp *amp;
+	bool is_mute;
+	u8 gain_type;
+	int ret;
+	u8 ch;
+
+	ch = IDT821034_ID_GET_CHAN(id);
+	is_mute = !ucontrol->value.integer.value[0];
+
+	mutex_lock(&idt821034->mutex);
+
+	if (IDT821034_ID_IS_OUT(id)) {
+		amp = &idt821034->amps.ch[ch].amp_out;
+		gain_type = IDT821034_GAIN_RX;
+	} else {
+		amp = &idt821034->amps.ch[ch].amp_in;
+		gain_type = IDT821034_GAIN_TX;
+	}
+
+	if (amp->is_muted == is_mute) {
+		ret = 0;
+		goto end;
+	}
+
+	ret = idt821034_set_gain_channel(idt821034, ch, gain_type,
+					 is_mute ? 0 : amp->gain);
+	if (ret)
+		goto end;
+
+	amp->is_muted = is_mute;
+	ret = 1; /* The value changed */
+end:
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static const DECLARE_TLV_DB_LINEAR(idt821034_gain_in, -6520, 1306);
+#define IDT821034_GAIN_IN_MIN_RAW	1 /* -65.20 dB -> 10^(-65.2/20.0) * 1820 = 1 */
+#define IDT821034_GAIN_IN_MAX_RAW	8191 /* 13.06 dB -> 10^(13.06/20.0) * 1820 = 8191 */
+#define IDT821034_GAIN_IN_INIT_RAW	1820 /* 0dB -> 10^(0/20) * 1820 = 1820 */
+
+static const DECLARE_TLV_DB_LINEAR(idt821034_gain_out, -6798, 1029);
+#define IDT821034_GAIN_OUT_MIN_RAW	1 /* -67.98 dB -> 10^(-67.98/20.0) * 2506 = 1*/
+#define IDT821034_GAIN_OUT_MAX_RAW	8191 /* 10.29 dB -> 10^(10.29/20.0) * 2506 = 8191 */
+#define IDT821034_GAIN_OUT_INIT_RAW	2506 /* 0dB -> 10^(0/20) * 2506 = 2506 */
+
+static const struct snd_kcontrol_new idt821034_controls[] = {
+	/* DAC volume control */
+	SOC_SINGLE_RANGE_EXT_TLV("DAC0 Playback Volume", IDT821034_ID_OUT(0), 0,
+				 IDT821034_GAIN_OUT_MIN_RAW, IDT821034_GAIN_OUT_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_out),
+	SOC_SINGLE_RANGE_EXT_TLV("DAC1 Playback Volume", IDT821034_ID_OUT(1), 0,
+				 IDT821034_GAIN_OUT_MIN_RAW, IDT821034_GAIN_OUT_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_out),
+	SOC_SINGLE_RANGE_EXT_TLV("DAC2 Playback Volume", IDT821034_ID_OUT(2), 0,
+				 IDT821034_GAIN_OUT_MIN_RAW, IDT821034_GAIN_OUT_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_out),
+	SOC_SINGLE_RANGE_EXT_TLV("DAC3 Playback Volume", IDT821034_ID_OUT(3), 0,
+				 IDT821034_GAIN_OUT_MIN_RAW, IDT821034_GAIN_OUT_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_out),
+
+	/* DAC mute control */
+	SOC_SINGLE_BOOL_EXT("DAC0 Playback Switch", IDT821034_ID_OUT(0),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+	SOC_SINGLE_BOOL_EXT("DAC1 Playback Switch", IDT821034_ID_OUT(1),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+	SOC_SINGLE_BOOL_EXT("DAC2 Playback Switch", IDT821034_ID_OUT(2),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+	SOC_SINGLE_BOOL_EXT("DAC3 Playback Switch", IDT821034_ID_OUT(3),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+
+	/* ADC volume control */
+	SOC_SINGLE_RANGE_EXT_TLV("ADC0 Capture Volume", IDT821034_ID_IN(0), 0,
+				 IDT821034_GAIN_IN_MIN_RAW, IDT821034_GAIN_IN_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_in),
+	SOC_SINGLE_RANGE_EXT_TLV("ADC1 Capture Volume", IDT821034_ID_IN(1), 0,
+				 IDT821034_GAIN_IN_MIN_RAW, IDT821034_GAIN_IN_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_in),
+	SOC_SINGLE_RANGE_EXT_TLV("ADC2 Capture Volume", IDT821034_ID_IN(2), 0,
+				 IDT821034_GAIN_IN_MIN_RAW, IDT821034_GAIN_IN_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_in),
+	SOC_SINGLE_RANGE_EXT_TLV("ADC3 Capture Volume", IDT821034_ID_IN(3), 0,
+				 IDT821034_GAIN_IN_MIN_RAW, IDT821034_GAIN_IN_MAX_RAW,
+				 0, idt821034_kctrl_gain_get, idt821034_kctrl_gain_put,
+				 idt821034_gain_in),
+
+	/* ADC mute control */
+	SOC_SINGLE_BOOL_EXT("ADC0 Capture Switch", IDT821034_ID_IN(0),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+	SOC_SINGLE_BOOL_EXT("ADC1 Capture Switch", IDT821034_ID_IN(1),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+	SOC_SINGLE_BOOL_EXT("ADC2 Capture Switch", IDT821034_ID_IN(2),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+	SOC_SINGLE_BOOL_EXT("ADC3 Capture Switch", IDT821034_ID_IN(3),
+			    idt821034_kctrl_mute_get, idt821034_kctrl_mute_put),
+};
+
+static int idt821034_power_event(struct snd_soc_dapm_widget *w,
+				 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component);
+	unsigned int id = w->shift;
+	u8 power, mask;
+	int ret;
+	u8 ch;
+
+	ch = IDT821034_ID_GET_CHAN(id);
+	mask = IDT821034_ID_IS_OUT(id) ? IDT821034_CONF_PWRUP_RX : IDT821034_CONF_PWRUP_TX;
+
+	mutex_lock(&idt821034->mutex);
+
+	power = idt821034_get_channel_power(idt821034, ch);
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		power |= mask;
+	else
+		power &= ~mask;
+	ret = idt821034_set_channel_power(idt821034, ch, power);
+
+	mutex_unlock(&idt821034->mutex);
+
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget idt821034_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC_E("DAC0", "Playback", SND_SOC_NOPM, IDT821034_ID_OUT(0), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("DAC1", "Playback", SND_SOC_NOPM, IDT821034_ID_OUT(1), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("DAC2", "Playback", SND_SOC_NOPM, IDT821034_ID_OUT(2), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("DAC3", "Playback", SND_SOC_NOPM, IDT821034_ID_OUT(3), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_OUTPUT("OUT0"),
+	SND_SOC_DAPM_OUTPUT("OUT1"),
+	SND_SOC_DAPM_OUTPUT("OUT2"),
+	SND_SOC_DAPM_OUTPUT("OUT3"),
+
+	SND_SOC_DAPM_DAC_E("ADC0", "Capture", SND_SOC_NOPM, IDT821034_ID_IN(0), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("ADC1", "Capture", SND_SOC_NOPM, IDT821034_ID_IN(1), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("ADC2", "Capture", SND_SOC_NOPM, IDT821034_ID_IN(2), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("ADC3", "Capture", SND_SOC_NOPM, IDT821034_ID_IN(3), 0,
+			   idt821034_power_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_INPUT("IN0"),
+	SND_SOC_DAPM_INPUT("IN1"),
+	SND_SOC_DAPM_INPUT("IN2"),
+	SND_SOC_DAPM_INPUT("IN3"),
+};
+
+static const struct snd_soc_dapm_route idt821034_dapm_routes[] = {
+	{ "OUT0", NULL, "DAC0" },
+	{ "OUT1", NULL, "DAC1" },
+	{ "OUT2", NULL, "DAC2" },
+	{ "OUT3", NULL, "DAC3" },
+
+	{ "ADC0", NULL, "IN0" },
+	{ "ADC1", NULL, "IN1" },
+	{ "ADC2", NULL, "IN2" },
+	{ "ADC3", NULL, "IN3" },
+};
+
+static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai,
+				      unsigned int tx_mask, unsigned int rx_mask,
+				      int slots, int width)
+{
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int mask;
+	u8 slot;
+	int ret;
+	u8 ch;
+
+	switch (width) {
+	case 0: /* Not set -> default 8 */
+	case 8:
+		break;
+	default:
+		dev_err(dai->dev, "tdm slot width %d not supported\n", width);
+		return -EINVAL;
+	}
+
+	mask = tx_mask;
+	slot = 0;
+	ch = 0;
+	while (mask && ch < IDT821034_NB_CHANNEL) {
+		if (mask & 0x1) {
+			mutex_lock(&idt821034->mutex);
+			ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_RX, slot);
+			mutex_unlock(&idt821034->mutex);
+			if (ret) {
+				dev_err(dai->dev, "ch%u set tx tdm slot failed (%d)\n",
+					ch, ret);
+				return ret;
+			}
+			ch++;
+		}
+		mask >>= 1;
+		slot++;
+	}
+	if (mask) {
+		dev_err(dai->dev, "too much tx slots defined (mask = 0x%x) support max %d\n",
+			tx_mask, IDT821034_NB_CHANNEL);
+		return -EINVAL;
+	}
+	idt821034->max_ch_playback = ch;
+
+	mask = rx_mask;
+	slot = 0;
+	ch = 0;
+	while (mask && ch < IDT821034_NB_CHANNEL) {
+		if (mask & 0x1) {
+			mutex_lock(&idt821034->mutex);
+			ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_TX, slot);
+			mutex_unlock(&idt821034->mutex);
+			if (ret) {
+				dev_err(dai->dev, "ch%u set rx tdm slot failed (%d)\n",
+					ch, ret);
+				return ret;
+			}
+			ch++;
+		}
+		mask >>= 1;
+		slot++;
+	}
+	if (mask) {
+		dev_err(dai->dev, "too much rx slots defined (mask = 0x%x) support max %d\n",
+			rx_mask, IDT821034_NB_CHANNEL);
+		return -EINVAL;
+	}
+	idt821034->max_ch_capture = ch;
+
+	return 0;
+}
+
+static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component);
+	u8 conf;
+	int ret;
+
+	mutex_lock(&idt821034->mutex);
+
+	conf = idt821034_get_codec_conf(idt821034);
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		conf |= IDT821034_CONF_DELAY_MODE;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		conf &= ~IDT821034_CONF_DELAY_MODE;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported DAI format 0x%x\n",
+			fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		ret = -EINVAL;
+		goto end;
+	}
+	ret = idt821034_set_codec_conf(idt821034, conf);
+end:
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static int idt821034_dai_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component);
+	u8 conf;
+	int ret;
+
+	mutex_lock(&idt821034->mutex);
+
+	conf = idt821034_get_codec_conf(idt821034);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_A_LAW:
+		conf |= IDT821034_CONF_ALAW_MODE;
+		break;
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		conf &= ~IDT821034_CONF_ALAW_MODE;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported PCM format 0x%x\n",
+			params_format(params));
+		ret = -EINVAL;
+		goto end;
+	}
+	ret = idt821034_set_codec_conf(idt821034, conf);
+end:
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static const unsigned int idt821034_sample_bits[] = {8};
+
+static struct snd_pcm_hw_constraint_list idt821034_sample_bits_constr = {
+	.list = idt821034_sample_bits,
+	.count = ARRAY_SIZE(idt821034_sample_bits),
+};
+
+static int idt821034_dai_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int max_ch = 0;
+	int ret;
+
+	max_ch = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		idt821034->max_ch_playback : idt821034->max_ch_capture;
+
+	/*
+	 * Disable stream support (min = 0, max = 0) if no timeslots were
+	 * configured otherwise, limit the number of channels to those
+	 * configured.
+	 */
+	ret = snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+					   max_ch ? 1 : 0, max_ch);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					 &idt821034_sample_bits_constr);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static u64 idt821034_dai_formats[] = {
+	SND_SOC_POSSIBLE_DAIFMT_DSP_A	|
+	SND_SOC_POSSIBLE_DAIFMT_DSP_B,
+};
+
+static const struct snd_soc_dai_ops idt821034_dai_ops = {
+	.startup      = idt821034_dai_startup,
+	.hw_params    = idt821034_dai_hw_params,
+	.set_tdm_slot = idt821034_dai_set_tdm_slot,
+	.set_fmt      = idt821034_dai_set_fmt,
+	.auto_selectable_formats     = idt821034_dai_formats,
+	.num_auto_selectable_formats = ARRAY_SIZE(idt821034_dai_formats),
+};
+
+static struct snd_soc_dai_driver idt821034_dai_driver = {
+	.name = "idt821034",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = IDT821034_NB_CHANNEL,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = IDT821034_NB_CHANNEL,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
+	},
+	.ops = &idt821034_dai_ops,
+};
+
+static int idt821034_reset_audio(struct idt821034 *idt821034)
+{
+	int ret;
+	u8 i;
+
+	mutex_lock(&idt821034->mutex);
+
+	ret = idt821034_set_codec_conf(idt821034, 0);
+	if (ret)
+		goto end;
+
+	for (i = 0; i < IDT821034_NB_CHANNEL; i++) {
+		idt821034->amps.ch[i].amp_out.gain = IDT821034_GAIN_OUT_INIT_RAW;
+		idt821034->amps.ch[i].amp_out.is_muted = false;
+		ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_RX,
+						 idt821034->amps.ch[i].amp_out.gain);
+		if (ret)
+			goto end;
+
+		idt821034->amps.ch[i].amp_in.gain = IDT821034_GAIN_IN_INIT_RAW;
+		idt821034->amps.ch[i].amp_in.is_muted = false;
+		ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_TX,
+						 idt821034->amps.ch[i].amp_in.gain);
+		if (ret)
+			goto end;
+
+		ret = idt821034_set_channel_power(idt821034, i, 0);
+		if (ret)
+			goto end;
+	}
+
+	ret = 0;
+end:
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static int idt821034_component_probe(struct snd_soc_component *component)
+{
+	struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	/* reset idt821034 audio part*/
+	ret = idt821034_reset_audio(idt821034);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver idt821034_component_driver = {
+	.probe			= idt821034_component_probe,
+	.controls		= idt821034_controls,
+	.num_controls		= ARRAY_SIZE(idt821034_controls),
+	.dapm_widgets		= idt821034_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(idt821034_dapm_widgets),
+	.dapm_routes		= idt821034_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(idt821034_dapm_routes),
+	.endianness		= 1,
+};
+
+#define IDT821034_GPIO_OFFSET_TO_SLIC_CHANNEL(_offset) (((_offset) / 5) % 4)
+#define IDT821034_GPIO_OFFSET_TO_SLIC_MASK(_offset)    BIT((_offset) % 5)
+
+static void idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, int val)
+{
+	u8 ch = IDT821034_GPIO_OFFSET_TO_SLIC_CHANNEL(offset);
+	u8 mask = IDT821034_GPIO_OFFSET_TO_SLIC_MASK(offset);
+	struct idt821034 *idt821034 = gpiochip_get_data(c);
+	u8 slic_raw;
+	int ret;
+
+	mutex_lock(&idt821034->mutex);
+
+	slic_raw = idt821034_get_written_slic_raw(idt821034, ch);
+	if (val)
+		slic_raw |= mask;
+	else
+		slic_raw &= ~mask;
+	ret = idt821034_write_slic_raw(idt821034, ch, slic_raw);
+	if (ret) {
+		dev_err(&idt821034->spi->dev, "set gpio %d (%u, 0x%x) failed (%d)\n",
+			offset, ch, mask, ret);
+	}
+
+	mutex_unlock(&idt821034->mutex);
+}
+
+static int idt821034_chip_gpio_get(struct gpio_chip *c, unsigned int offset)
+{
+	u8 ch = IDT821034_GPIO_OFFSET_TO_SLIC_CHANNEL(offset);
+	u8 mask = IDT821034_GPIO_OFFSET_TO_SLIC_MASK(offset);
+	struct idt821034 *idt821034 = gpiochip_get_data(c);
+	u8 slic_raw;
+	int ret;
+
+	mutex_lock(&idt821034->mutex);
+	ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw);
+	mutex_unlock(&idt821034->mutex);
+	if (ret) {
+		dev_err(&idt821034->spi->dev, "get gpio %d (%u, 0x%x) failed (%d)\n",
+			offset, ch, mask, ret);
+		return ret;
+	}
+
+	/*
+	 * SLIC IOs are read in reverse order compared to write.
+	 * Reverse the read value here in order to have IO0 at lsb (ie same
+	 * order as write)
+	 */
+	return !!(bitrev8(slic_raw) & mask);
+}
+
+static int idt821034_chip_get_direction(struct gpio_chip *c, unsigned int offset)
+{
+	u8 ch = IDT821034_GPIO_OFFSET_TO_SLIC_CHANNEL(offset);
+	u8 mask = IDT821034_GPIO_OFFSET_TO_SLIC_MASK(offset);
+	struct idt821034 *idt821034 = gpiochip_get_data(c);
+	u8 slic_dir;
+
+	mutex_lock(&idt821034->mutex);
+	slic_dir = idt821034_get_slic_conf(idt821034, ch);
+	mutex_unlock(&idt821034->mutex);
+
+	return slic_dir & mask ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
+}
+
+static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offset)
+{
+	u8 ch = IDT821034_GPIO_OFFSET_TO_SLIC_CHANNEL(offset);
+	u8 mask = IDT821034_GPIO_OFFSET_TO_SLIC_MASK(offset);
+	struct idt821034 *idt821034 = gpiochip_get_data(c);
+	u8 slic_conf;
+	int ret;
+
+	/* Only IO0 and IO1 can be set as input */
+	if (mask & ~(IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN))
+		return -EPERM;
+
+	mutex_lock(&idt821034->mutex);
+
+	slic_conf = idt821034_get_slic_conf(idt821034, ch) | mask;
+
+	ret = idt821034_set_slic_conf(idt821034, ch, slic_conf);
+	if (ret) {
+		dev_err(&idt821034->spi->dev, "dir in gpio %d (%u, 0x%x) failed (%d)\n",
+			offset, ch, mask, ret);
+	}
+
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int offset, int val)
+{
+	u8 ch = IDT821034_GPIO_OFFSET_TO_SLIC_CHANNEL(offset);
+	u8 mask = IDT821034_GPIO_OFFSET_TO_SLIC_MASK(offset);
+	struct idt821034 *idt821034 = gpiochip_get_data(c);
+	u8 slic_conf;
+	int ret;
+
+	idt821034_chip_gpio_set(c, offset, val);
+
+	mutex_lock(&idt821034->mutex);
+
+	slic_conf = idt821034_get_slic_conf(idt821034, ch) & ~mask;
+
+	ret = idt821034_set_slic_conf(idt821034, ch, slic_conf);
+	if (ret) {
+		dev_err(&idt821034->spi->dev, "dir in gpio %d (%u, 0x%x) failed (%d)\n",
+			offset, ch, mask, ret);
+	}
+
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static int idt821034_reset_gpio(struct idt821034 *idt821034)
+{
+	int ret;
+	u8 i;
+
+	mutex_lock(&idt821034->mutex);
+
+	/* IO0 and IO1 as input for all channels and output IO set to 0 */
+	for (i = 0; i < IDT821034_NB_CHANNEL; i++) {
+		ret = idt821034_set_slic_conf(idt821034, i,
+					      IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN);
+		if (ret)
+			goto end;
+
+		ret = idt821034_write_slic_raw(idt821034, i, 0);
+		if (ret)
+			goto end;
+
+	}
+	ret = 0;
+end:
+	mutex_unlock(&idt821034->mutex);
+	return ret;
+}
+
+static int idt821034_gpio_init(struct idt821034 *idt821034)
+{
+	int ret;
+
+	ret = idt821034_reset_gpio(idt821034);
+	if (ret)
+		return ret;
+
+	idt821034->gpio_chip.owner = THIS_MODULE;
+	idt821034->gpio_chip.label = dev_name(&idt821034->spi->dev);
+	idt821034->gpio_chip.parent = &idt821034->spi->dev;
+	idt821034->gpio_chip.base = -1;
+	idt821034->gpio_chip.ngpio = 5 * 4; /* 5 GPIOs on 4 channels */
+	idt821034->gpio_chip.get_direction = idt821034_chip_get_direction;
+	idt821034->gpio_chip.direction_input = idt821034_chip_direction_input;
+	idt821034->gpio_chip.direction_output = idt821034_chip_direction_output;
+	idt821034->gpio_chip.get = idt821034_chip_gpio_get;
+	idt821034->gpio_chip.set = idt821034_chip_gpio_set;
+	idt821034->gpio_chip.can_sleep = true;
+
+	return devm_gpiochip_add_data(&idt821034->spi->dev, &idt821034->gpio_chip,
+				      idt821034);
+}
+
+static int idt821034_spi_probe(struct spi_device *spi)
+{
+	struct idt821034 *idt821034;
+	int ret;
+
+	spi->bits_per_word = 8;
+	ret = spi_setup(spi);
+	if (ret < 0)
+		return ret;
+
+	idt821034 = devm_kzalloc(&spi->dev, sizeof(*idt821034), GFP_KERNEL);
+	if (!idt821034)
+		return -ENOMEM;
+
+	idt821034->spi = spi;
+
+	mutex_init(&idt821034->mutex);
+
+	spi_set_drvdata(spi, idt821034);
+
+	ret = devm_snd_soc_register_component(&spi->dev, &idt821034_component_driver,
+					      &idt821034_dai_driver, 1);
+	if (ret)
+		return ret;
+
+	if (IS_ENABLED(CONFIG_GPIOLIB))
+		return idt821034_gpio_init(idt821034);
+
+	return 0;
+}
+
+static const struct of_device_id idt821034_of_match[] = {
+	{ .compatible = "renesas,idt821034", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, idt821034_of_match);
+
+static const struct spi_device_id idt821034_id_table[] = {
+	{ "idt821034", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, idt821034_id_table);
+
+static struct spi_driver idt821034_spi_driver = {
+	.driver  = {
+		.name   = "idt821034",
+		.of_match_table = idt821034_of_match,
+	},
+	.id_table = idt821034_id_table,
+	.probe  = idt821034_spi_probe,
+};
+
+module_spi_driver(idt821034_spi_driver);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("IDT821034 ALSA SoC driver");
+MODULE_LICENSE("GPL");
-- 
2.39.0


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

* [PATCH v3 3/3] MAINTAINERS: add the Renesas IDT821034 codec entry
  2023-01-26  8:51 [PATCH v3 0/3] Add the Renesas IDT821034 codec support Herve Codina
  2023-01-26  8:51 ` [PATCH v3 1/3] dt-bindings: sound: Add Renesas IDT821034 codec Herve Codina
  2023-01-26  8:51 ` [PATCH v3 2/3] ASoC: codecs: Add support for the " Herve Codina
@ 2023-01-26  8:51 ` Herve Codina
  2023-01-31 14:32 ` [PATCH v3 0/3] Add the Renesas IDT821034 codec support Mark Brown
  3 siblings, 0 replies; 5+ messages in thread
From: Herve Codina @ 2023-01-26  8:51 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: alsa-devel, devicetree, linux-kernel, linux-gpio,
	Christophe Leroy, Thomas Petazzoni

After contributing the driver, add myself as the maintainer for the
Renesas IDT821034 codec.

Signed-off-by: Herve Codina <herve.codina@bootlin.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 9dcfadec5aa3..31115a7e01c1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17809,6 +17809,13 @@ F:	Documentation/devicetree/bindings/net/renesas,*.yaml
 F:	drivers/net/ethernet/renesas/
 F:	include/linux/sh_eth.h
 
+RENESAS IDT821034 ASoC CODEC
+M:	Herve Codina <herve.codina@bootlin.com>
+L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
+S:	Maintained
+F:	Documentation/devicetree/bindings/sound/renesas,idt821034.yaml
+F:	sound/soc/codecs/idt821034.c
+
 RENESAS R-CAR GYROADC DRIVER
 M:	Marek Vasut <marek.vasut@gmail.com>
 L:	linux-iio@vger.kernel.org
-- 
2.39.0


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

* Re: [PATCH v3 0/3] Add the Renesas IDT821034 codec support
  2023-01-26  8:51 [PATCH v3 0/3] Add the Renesas IDT821034 codec support Herve Codina
                   ` (2 preceding siblings ...)
  2023-01-26  8:51 ` [PATCH v3 3/3] MAINTAINERS: add the Renesas IDT821034 codec entry Herve Codina
@ 2023-01-31 14:32 ` Mark Brown
  3 siblings, 0 replies; 5+ messages in thread
From: Mark Brown @ 2023-01-31 14:32 UTC (permalink / raw)
  To: Liam Girdwood, Rob Herring, Krzysztof Kozlowski, Linus Walleij,
	Bartosz Golaszewski, Jaroslav Kysela, Takashi Iwai, Herve Codina
  Cc: alsa-devel, devicetree, linux-kernel, linux-gpio,
	Christophe Leroy, Thomas Petazzoni

On Thu, 26 Jan 2023 09:51:34 +0100, Herve Codina wrote:
> The Renesas IDT821034 codec is four channel PCM codec with on-chip
> filters and programmable gain setting.
> It also provides SLIC (Subscriber Line Interface Circuit) signals as
> GPIOs.
> 
> Based on previous iteration,
>   https://lore.kernel.org/linux-kernel/20230120095036.514639-1-herve.codina@bootlin.com/
> this v3 series mainly:
>   - Fixes _put() methods
>   - Introduces and uses idt821034_2x8bit_write()
>   - Removes the '#if IS_ENABLED(CONFIG_GPIOLIB)' conditional
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next

Thanks!

[1/3] dt-bindings: sound: Add Renesas IDT821034 codec
      commit: 545679837eef1819aee3868fbb9dfced9a56de02
[2/3] ASoC: codecs: Add support for the Renesas IDT821034 codec
      commit: e51166990e81754d2cd30593558c3ff47fa49f15
[3/3] MAINTAINERS: add the Renesas IDT821034 codec entry
      commit: e60259f77c46a6b4733b20f1fb44547d67302c67

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark


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

end of thread, other threads:[~2023-01-31 14:32 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-26  8:51 [PATCH v3 0/3] Add the Renesas IDT821034 codec support Herve Codina
2023-01-26  8:51 ` [PATCH v3 1/3] dt-bindings: sound: Add Renesas IDT821034 codec Herve Codina
2023-01-26  8:51 ` [PATCH v3 2/3] ASoC: codecs: Add support for the " Herve Codina
2023-01-26  8:51 ` [PATCH v3 3/3] MAINTAINERS: add the Renesas IDT821034 codec entry Herve Codina
2023-01-31 14:32 ` [PATCH v3 0/3] Add the Renesas IDT821034 codec support Mark Brown

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).