All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] Add the Infineon PEB2466 codec support
@ 2023-02-06 14:49 ` Herve Codina
  0 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 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 Infineon PEB2466 codec is a programmable DSP-based four channels
codec with filters capabilities.
It also provides signals as GPIOs.

Best regards,
Herve Codina

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

 .../bindings/sound/infineon,peb2466.yaml      |   91 +
 MAINTAINERS                                   |    7 +
 sound/soc/codecs/Kconfig                      |   12 +
 sound/soc/codecs/Makefile                     |    2 +
 sound/soc/codecs/peb2466.c                    | 2071 +++++++++++++++++
 5 files changed, 2183 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
 create mode 100644 sound/soc/codecs/peb2466.c

-- 
2.39.1


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

* [PATCH 0/3] Add the Infineon PEB2466 codec support
@ 2023-02-06 14:49 ` Herve Codina
  0 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: devicetree, alsa-devel, linux-kernel, Christophe Leroy,
	linux-gpio, Thomas Petazzoni

Hi,

The Infineon PEB2466 codec is a programmable DSP-based four channels
codec with filters capabilities.
It also provides signals as GPIOs.

Best regards,
Herve Codina

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

 .../bindings/sound/infineon,peb2466.yaml      |   91 +
 MAINTAINERS                                   |    7 +
 sound/soc/codecs/Kconfig                      |   12 +
 sound/soc/codecs/Makefile                     |    2 +
 sound/soc/codecs/peb2466.c                    | 2071 +++++++++++++++++
 5 files changed, 2183 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
 create mode 100644 sound/soc/codecs/peb2466.c

-- 
2.39.1


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

* [PATCH 1/3] dt-bindings: sound: Add the Infineon PEB2466 codec
  2023-02-06 14:49 ` Herve Codina
@ 2023-02-06 14:49   ` Herve Codina
  -1 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 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 Infineon PEB2466 codec is a programmable DSP-based four channels
codec with filters capabilities.

Signed-off-by: Herve Codina <herve.codina@bootlin.com>
---
 .../bindings/sound/infineon,peb2466.yaml      | 91 +++++++++++++++++++
 1 file changed, 91 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/infineon,peb2466.yaml

diff --git a/Documentation/devicetree/bindings/sound/infineon,peb2466.yaml b/Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
new file mode 100644
index 000000000000..66993d378aaf
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/infineon,peb2466.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Infineon PEB2466 codec
+
+maintainers:
+  - Herve Codina <herve.codina@bootlin.com>
+
+description: |
+  The Infineon PEB2466 codec is a programmable DSP-based four channels codec
+  with filters capabilities.
+
+  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 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 PEB2466 codec also supports 28 gpios (signaling pins).
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    const: infineon,peb2466
+
+  reg:
+    description:
+      SPI device address.
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Master clock
+
+  clock-names:
+    items:
+      - const: mclk
+
+  spi-max-frequency:
+    maximum: 8192000
+
+  reset-gpios:
+    description:
+      GPIO used to reset the device.
+    maxItems: 1
+
+  firmware-name:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Filters coefficients file to load. If this property is omitted, internal
+      filters are disabled.
+
+  '#sound-dai-cells':
+    const: 0
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+  - gpio-controller
+  - '#gpio-cells'
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        audio-codec@0 {
+            compatible = "infineon,peb2466";
+            reg = <0>;
+            spi-max-frequency = <8192000>;
+            reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
+            #sound-dai-cells = <0>;
+            gpio-controller;
+            #gpio-cells = <2>;
+        };
+    };
-- 
2.39.1


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

* [PATCH 1/3] dt-bindings: sound: Add the Infineon PEB2466 codec
@ 2023-02-06 14:49   ` Herve Codina
  0 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: devicetree, alsa-devel, linux-kernel, Christophe Leroy,
	linux-gpio, Thomas Petazzoni

The Infineon PEB2466 codec is a programmable DSP-based four channels
codec with filters capabilities.

Signed-off-by: Herve Codina <herve.codina@bootlin.com>
---
 .../bindings/sound/infineon,peb2466.yaml      | 91 +++++++++++++++++++
 1 file changed, 91 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/infineon,peb2466.yaml

diff --git a/Documentation/devicetree/bindings/sound/infineon,peb2466.yaml b/Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
new file mode 100644
index 000000000000..66993d378aaf
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/infineon,peb2466.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Infineon PEB2466 codec
+
+maintainers:
+  - Herve Codina <herve.codina@bootlin.com>
+
+description: |
+  The Infineon PEB2466 codec is a programmable DSP-based four channels codec
+  with filters capabilities.
+
+  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 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 PEB2466 codec also supports 28 gpios (signaling pins).
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    const: infineon,peb2466
+
+  reg:
+    description:
+      SPI device address.
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Master clock
+
+  clock-names:
+    items:
+      - const: mclk
+
+  spi-max-frequency:
+    maximum: 8192000
+
+  reset-gpios:
+    description:
+      GPIO used to reset the device.
+    maxItems: 1
+
+  firmware-name:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Filters coefficients file to load. If this property is omitted, internal
+      filters are disabled.
+
+  '#sound-dai-cells':
+    const: 0
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+  - gpio-controller
+  - '#gpio-cells'
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        audio-codec@0 {
+            compatible = "infineon,peb2466";
+            reg = <0>;
+            spi-max-frequency = <8192000>;
+            reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
+            #sound-dai-cells = <0>;
+            gpio-controller;
+            #gpio-cells = <2>;
+        };
+    };
-- 
2.39.1


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

* [PATCH 2/3] ASoC: codecs: Add support for the Infineon PEB2466 codec
  2023-02-06 14:49 ` Herve Codina
@ 2023-02-06 14:49   ` Herve Codina
  -1 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 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 Infineon PEB2466 codec is a programmable DSP-based four channels
codec with filters capabilities.
It also provides signals as GPIOs.

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

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 19afa0067305..80dd454695b6 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -165,6 +165,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_PCM5102A
 	imply SND_SOC_PCM512x_I2C
 	imply SND_SOC_PCM512x_SPI
+	imply SND_SOC_PEB2466
 	imply SND_SOC_RK3328
 	imply SND_SOC_RK817
 	imply SND_SOC_RT274
@@ -1217,6 +1218,17 @@ config SND_SOC_PCM512x_SPI
 	select SND_SOC_PCM512x
 	select REGMAP_SPI
 
+config SND_SOC_PEB2466
+	tristate "Infineon PEB2466 quad PCM codec"
+	depends on SPI
+	select REGMAP_SPI
+	help
+	  Enable support for the Infineon PEB2466 quad PCM codec,
+	  also named SICOFI 4-uC.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-soc-peb2466.
+
 config SND_SOC_RK3328
 	tristate "Rockchip RK3328 audio CODEC"
 	select REGMAP_MMIO
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index a1d6de6a18f6..a48fec03703d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -184,6 +184,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-peb2466-objs := peb2466.o
 snd-soc-rk3328-objs := rk3328_codec.o
 snd-soc-rk817-objs := rk817_codec.o
 snd-soc-rl6231-objs := rl6231.o
@@ -542,6 +543,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_PEB2466)	+= snd-soc-peb2466.o
 obj-$(CONFIG_SND_SOC_RK3328)	+= snd-soc-rk3328.o
 obj-$(CONFIG_SND_SOC_RK817)	+= snd-soc-rk817.o
 obj-$(CONFIG_SND_SOC_RL6231)	+= snd-soc-rl6231.o
diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c
new file mode 100644
index 000000000000..4640b1860312
--- /dev/null
+++ b/sound/soc/codecs/peb2466.c
@@ -0,0 +1,2071 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// peb2466.c  --  Infineon PEB2466 ALSA SoC driver
+//
+// Copyright 2023 CS GROUP France
+//
+// Author: Herve Codina <herve.codina@bootlin.com>
+
+#include <asm/unaligned.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#define PEB2466_NB_CHANNEL	4
+
+struct peb2466_lookup {
+	u8 (*table)[4];
+	unsigned int count;
+};
+
+#define PEB2466_TLV_SIZE  (sizeof((unsigned int []){TLV_DB_SCALE_ITEM(0, 0, 0)}) / \
+			   sizeof(unsigned int))
+
+struct peb2466_lkup_ctrl {
+	int reg;
+	unsigned int index;
+	const struct peb2466_lookup *lookup;
+	unsigned int tlv_array[PEB2466_TLV_SIZE];
+};
+
+struct peb2466 {
+	struct spi_device *spi;
+	struct clk *mclk;
+	struct gpio_desc *reset_gpio;
+	u8 spi_tx_buf[2 + 8]; /* Cannot use stack area for SPI (dma-safe memory) */
+	u8 spi_rx_buf[2 + 8]; /* Cannot use stack area for SPI (dma-safe memory) */
+	struct regmap *regmap;
+	struct {
+		struct peb2466_lookup ax_lookup;
+		struct peb2466_lookup ar_lookup;
+		struct peb2466_lkup_ctrl ax_lkup_ctrl;
+		struct peb2466_lkup_ctrl ar_lkup_ctrl;
+		unsigned int tg1_freq_item;
+		unsigned int tg2_freq_item;
+	} ch[PEB2466_NB_CHANNEL];
+	int max_chan_playback;
+	int max_chan_capture;
+	struct {
+		struct gpio_chip gpio_chip;
+		struct mutex lock;
+		struct {
+			unsigned int xr0;
+			unsigned int xr1;
+			unsigned int xr2;
+			unsigned int xr3;
+		} cache;
+	} gpio;
+};
+
+#define PEB2466_CMD_R	(1 << 5)
+#define PEB2466_CMD_W	(0 << 5)
+
+#define PEB2466_CMD_MASK 0x18
+#define PEB2466_CMD_XOP  0x18  /* XOP is 0bxxx11xxx */
+#define PEB2466_CMD_SOP  0x10  /* SOP is 0bxxx10xxx */
+#define PEB2466_CMD_COP  0x00  /* COP is 0bxxx0xxxx, handle 0bxxx00xxx */
+#define PEB2466_CMD_COP1 0x08  /* COP is 0bxxx0xxxx, handle 0bxxx01xxx */
+
+#define PEB2466_MAKE_XOP(_lsel)      (PEB2466_CMD_XOP | (_lsel))
+#define PEB2466_MAKE_SOP(_ad, _lsel) (PEB2466_CMD_SOP | ((_ad) << 6) | (_lsel))
+#define PEB2466_MAKE_COP(_ad, _code) (PEB2466_CMD_COP | ((_ad) << 6) | (_code))
+
+#define PEB2466_CR0(_ch)	PEB2466_MAKE_SOP(_ch, 0x0)
+#define   PEB2466_CR0_TH		(1 << 7)
+#define   PEB2466_CR0_IMR1		(1 << 6)
+#define   PEB2466_CR0_FRX		(1 << 5)
+#define   PEB2466_CR0_FRR		(1 << 4)
+#define   PEB2466_CR0_AX		(1 << 3)
+#define   PEB2466_CR0_AR		(1 << 2)
+#define   PEB2466_CR0_THSEL_MASK	(0x3 << 0)
+#define   PEB2466_CR0_THSEL(_set)	((_set) << 0)
+
+#define PEB2466_CR1(_ch)	PEB2466_MAKE_SOP(_ch, 0x1)
+#define   PEB2466_CR1_ETG2		(1 << 7)
+#define   PEB2466_CR1_ETG1		(1 << 6)
+#define   PEB2466_CR1_PTG2		(1 << 5)
+#define   PEB2466_CR1_PTG1		(1 << 4)
+#define   PEB2466_CR1_LAW_MASK		(1 << 3)
+#define   PEB2466_CR1_LAW_ALAW		(0 << 3)
+#define   PEB2466_CR1_LAW_MULAW		(1 << 3)
+#define   PEB2466_CR1_PU		(1 << 0)
+
+#define PEB2466_CR2(_ch)	PEB2466_MAKE_SOP(_ch, 0x2)
+#define PEB2466_CR3(_ch)	PEB2466_MAKE_SOP(_ch, 0x3)
+#define PEB2466_CR4(_ch)	PEB2466_MAKE_SOP(_ch, 0x4)
+#define PEB2466_CR5(_ch)	PEB2466_MAKE_SOP(_ch, 0x5)
+
+#define PEB2466_XR0		PEB2466_MAKE_XOP(0x0)
+#define PEB2466_XR1		PEB2466_MAKE_XOP(0x1)
+#define PEB2466_XR2		PEB2466_MAKE_XOP(0x2)
+#define PEB2466_XR3		PEB2466_MAKE_XOP(0x3)
+#define PEB2466_XR4		PEB2466_MAKE_XOP(0x4)
+#define PEB2466_XR5		PEB2466_MAKE_XOP(0x5)
+#define   PEB2466_XR5_MCLK_1536		(0x0 << 6)
+#define   PEB2466_XR5_MCLK_2048		(0x1 << 6)
+#define   PEB2466_XR5_MCLK_4096		(0x2 << 6)
+#define   PEB2466_XR5_MCLK_8192		(0x3 << 6)
+
+#define PEB2466_XR6		PEB2466_MAKE_XOP(0x6)
+#define   PEB2466_XR6_PCM_OFFSET(_off)	((_off) << 0)
+
+#define PEB2466_XR7		PEB2466_MAKE_XOP(0x7)
+
+#define PEB2466_TH_FILTER_P1(_ch)	PEB2466_MAKE_COP(_ch, 0x0)
+#define PEB2466_TH_FILTER_P2(_ch)	PEB2466_MAKE_COP(_ch, 0x1)
+#define PEB2466_TH_FILTER_P3(_ch)	PEB2466_MAKE_COP(_ch, 0x2)
+#define PEB2466_IMR1_FILTER_P1(_ch)	PEB2466_MAKE_COP(_ch, 0x4)
+#define PEB2466_IMR1_FILTER_P2(_ch)	PEB2466_MAKE_COP(_ch, 0x5)
+#define PEB2466_FRX_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x6)
+#define PEB2466_FRR_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x7)
+#define PEB2466_AX_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x8)
+#define PEB2466_AR_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x9)
+#define PEB2466_TG1(_ch)		PEB2466_MAKE_COP(_ch, 0xc)
+#define PEB2466_TG2(_ch)		PEB2466_MAKE_COP(_ch, 0xd)
+
+static int peb2466_write_byte(struct peb2466 *peb2466, u8 cmd, u8 val)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = &peb2466->spi_tx_buf,
+		.len = 2,
+	};
+
+	peb2466->spi_tx_buf[0] = cmd | PEB2466_CMD_W;
+	peb2466->spi_tx_buf[1] = val;
+
+	dev_dbg(&peb2466->spi->dev, "write byte (cmd %02x) %02x\n",
+		peb2466->spi_tx_buf[0], peb2466->spi_tx_buf[1]);
+
+	return spi_sync_transfer(peb2466->spi, &xfer, 1);
+}
+
+static int peb2466_read_byte(struct peb2466 *peb2466, u8 cmd, u8 *val)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = &peb2466->spi_tx_buf,
+		.rx_buf = &peb2466->spi_rx_buf,
+		.len = 3,
+	};
+	int ret;
+
+	peb2466->spi_tx_buf[0] = cmd | PEB2466_CMD_R;
+
+	ret = spi_sync_transfer(peb2466->spi, &xfer, 1);
+	if (ret)
+		return ret;
+
+	if (peb2466->spi_rx_buf[1] != 0x81) {
+		dev_err(&peb2466->spi->dev,
+			"spi xfer rd (cmd %02x) invalid ident byte (0x%02x)\n",
+			peb2466->spi_tx_buf[0], peb2466->spi_rx_buf[1]);
+		return -EILSEQ;
+	}
+
+	*val = peb2466->spi_rx_buf[2];
+
+	dev_dbg(&peb2466->spi->dev, "read byte (cmd %02x) %02x\n",
+		peb2466->spi_tx_buf[0], *val);
+
+	return 0;
+}
+
+static int peb2466_write_buf(struct peb2466 *peb2466, u8 cmd, const u8 *buf, unsigned int len)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = &peb2466->spi_tx_buf,
+		.len = len + 1,
+	};
+
+	if (len > 8)
+		return -EINVAL;
+
+	peb2466->spi_tx_buf[0] = cmd | PEB2466_CMD_W;
+	memcpy(&peb2466->spi_tx_buf[1], buf, len);
+
+	dev_dbg(&peb2466->spi->dev, "write buf (cmd %02x, %u) %*ph\n",
+		peb2466->spi_tx_buf[0], len, len, &peb2466->spi_tx_buf[1]);
+
+	return spi_sync_transfer(peb2466->spi, &xfer, 1);
+}
+
+static int peb2466_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct peb2466 *peb2466 = context;
+	int ret;
+
+	/*
+	 * Only XOP and SOP commands can be handled as registers.
+	 * COP commands are handled using direct peb2466_write_buf() calls.
+	 */
+	switch (reg & PEB2466_CMD_MASK) {
+	case PEB2466_CMD_XOP:
+	case PEB2466_CMD_SOP:
+		ret = peb2466_write_byte(peb2466, reg, val);
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Not a XOP or SOP command\n");
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int peb2466_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct peb2466 *peb2466 = context;
+	int ret;
+	u8 tmp;
+
+	/* Only XOP and SOP commands can be handled as registers */
+	switch (reg & PEB2466_CMD_MASK) {
+	case PEB2466_CMD_XOP:
+	case PEB2466_CMD_SOP:
+		ret = peb2466_read_byte(peb2466, reg, &tmp);
+		*val = tmp;
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Not a XOP or SOP command\n");
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static const struct regmap_config peb2466_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0xFF,
+	.reg_write = peb2466_reg_write,
+	.reg_read = peb2466_reg_read,
+	.cache_type = REGCACHE_NONE,
+};
+
+static int peb2466_lkup_ctrl_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct peb2466_lkup_ctrl *lkup_ctrl =
+		(struct peb2466_lkup_ctrl *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = lkup_ctrl->lookup->count - 1;
+	return 0;
+}
+
+static int peb2466_lkup_ctrl_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct peb2466_lkup_ctrl *lkup_ctrl =
+		(struct peb2466_lkup_ctrl *)kcontrol->private_value;
+
+	ucontrol->value.integer.value[0] = lkup_ctrl->index;
+	return 0;
+}
+
+static int peb2466_lkup_ctrl_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct peb2466_lkup_ctrl *lkup_ctrl =
+		(struct peb2466_lkup_ctrl *)kcontrol->private_value;
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	unsigned int index;
+	int ret;
+
+	index = ucontrol->value.integer.value[0];
+	if (index >= lkup_ctrl->lookup->count)
+		return -EINVAL;
+
+	if (index == lkup_ctrl->index)
+		return 0;
+
+	ret = peb2466_write_buf(peb2466, lkup_ctrl->reg,
+				lkup_ctrl->lookup->table[index], 4);
+	if (ret)
+		return ret;
+
+	lkup_ctrl->index = index;
+	return 1; /* The value changed */
+}
+
+static int peb2466_add_lkup_ctrl(struct snd_soc_component *component,
+				 struct peb2466_lkup_ctrl *lkup_ctrl,
+				 const char *name, int min_val, int step)
+{
+	DECLARE_TLV_DB_SCALE(tlv_array, min_val, step, 0);
+	struct snd_kcontrol_new control = {0};
+
+	BUILD_BUG_ON(sizeof(lkup_ctrl->tlv_array) < sizeof(tlv_array));
+	memcpy(lkup_ctrl->tlv_array, tlv_array, sizeof(tlv_array));
+
+	control.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	control.name = name;
+	control.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+			 SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	control.tlv.p = lkup_ctrl->tlv_array;
+	control.info = peb2466_lkup_ctrl_info;
+	control.get = peb2466_lkup_ctrl_get;
+	control.put = peb2466_lkup_ctrl_put;
+	control.private_value = (unsigned long)lkup_ctrl;
+
+	return snd_soc_add_component_controls(component, &control, 1);
+}
+
+enum peb2466_tone_freq {
+	PEB2466_TONE_697HZ,
+	PEB2466_TONE_800HZ,
+	PEB2466_TONE_950HZ,
+	PEB2466_TONE_1000HZ,
+	PEB2466_TONE_1008HZ,
+	PEB2466_TONE_2000HZ,
+};
+
+static const u8 peb2466_tone_lookup[][4] = {
+	[PEB2466_TONE_697HZ] = {0x0a, 0x33, 0x5a, 0x2c},
+	[PEB2466_TONE_800HZ] = {0x12, 0xD6, 0x5a, 0xc0},
+	[PEB2466_TONE_950HZ] = {0x1c, 0xf0, 0x5c, 0xc0},
+	[PEB2466_TONE_1000HZ] = {0}, /* lookup value not used for 1000Hz */
+	[PEB2466_TONE_1008HZ] = {0x1a, 0xae, 0x57, 0x70},
+	[PEB2466_TONE_2000HZ] = {0x00, 0x80, 0x50, 0x09},
+};
+
+static const char * const peb2466_tone_freq_txt[] = {
+	[PEB2466_TONE_697HZ] = "697Hz",
+	[PEB2466_TONE_800HZ] = "800Hz",
+	[PEB2466_TONE_950HZ] = "950Hz",
+	[PEB2466_TONE_1000HZ] = "1000Hz",
+	[PEB2466_TONE_1008HZ] = "1008Hz",
+	[PEB2466_TONE_2000HZ] = "2000Hz"
+};
+
+static const struct soc_enum peb2466_tg_freq[][2] = {
+	[0] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(0), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(0), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	},
+	[1] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(1), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(1), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	},
+	[2] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(2), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(2), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	},
+	[3] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(3), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(3), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	}
+};
+
+static int peb2466_tg_freq_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+	switch (e->reg) {
+	case PEB2466_TG1(0):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[0].tg1_freq_item;
+		break;
+	case PEB2466_TG2(0):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[0].tg2_freq_item;
+		break;
+	case PEB2466_TG1(1):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[1].tg1_freq_item;
+		break;
+	case PEB2466_TG2(1):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[1].tg2_freq_item;
+		break;
+	case PEB2466_TG1(2):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[2].tg1_freq_item;
+		break;
+	case PEB2466_TG2(2):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[2].tg2_freq_item;
+		break;
+	case PEB2466_TG1(3):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[3].tg1_freq_item;
+		break;
+	case PEB2466_TG2(3):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[3].tg2_freq_item;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int peb2466_tg_freq_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int *tg_freq_item;
+	u8 cr1_reg, cr1_mask;
+	unsigned int index;
+	int ret;
+
+	index = ucontrol->value.enumerated.item[0];
+
+	if (index >= ARRAY_SIZE(peb2466_tone_lookup))
+		return -EINVAL;
+
+	switch (e->reg) {
+	case PEB2466_TG1(0):
+		tg_freq_item = &peb2466->ch[0].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(0);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(0):
+		tg_freq_item = &peb2466->ch[0].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(0);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	case PEB2466_TG1(1):
+		tg_freq_item = &peb2466->ch[1].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(1);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(1):
+		tg_freq_item = &peb2466->ch[1].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(1);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	case PEB2466_TG1(2):
+		tg_freq_item = &peb2466->ch[2].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(2);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(2):
+		tg_freq_item = &peb2466->ch[2].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(2);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	case PEB2466_TG1(3):
+		tg_freq_item = &peb2466->ch[3].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(3);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(3):
+		tg_freq_item = &peb2466->ch[3].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(3);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (index == *tg_freq_item)
+		return 0;
+
+	if (index == PEB2466_TONE_1000HZ) {
+		ret = regmap_update_bits(peb2466->regmap, cr1_reg, cr1_mask, 0);
+		if (ret)
+			return ret;
+	} else {
+		ret = peb2466_write_buf(peb2466, e->reg, peb2466_tone_lookup[index], 4);
+		if (ret)
+			return ret;
+		ret = regmap_update_bits(peb2466->regmap, cr1_reg, cr1_mask, cr1_mask);
+		if (ret)
+			return ret;
+	}
+
+	*tg_freq_item = index;
+	return 1; /* The value changed */
+}
+
+static const struct snd_kcontrol_new peb2466_ch0_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(0), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(0), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(0), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_ch1_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(1), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(1), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(1), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_ch2_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(2), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(2), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(2), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_ch3_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(3), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(3), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(3), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_controls[] = {
+	/* Attenuators */
+	SOC_SINGLE("DAC0 -6dB Playback Switch", PEB2466_CR3(0), 2, 1, 0),
+	SOC_SINGLE("DAC1 -6dB Playback Switch", PEB2466_CR3(1), 2, 1, 0),
+	SOC_SINGLE("DAC2 -6dB Playback Switch", PEB2466_CR3(2), 2, 1, 0),
+	SOC_SINGLE("DAC3 -6dB Playback Switch", PEB2466_CR3(3), 2, 1, 0),
+
+	/* Amplifiers */
+	SOC_SINGLE("ADC0 +6dB Capture Switch", PEB2466_CR3(0), 3, 1, 0),
+	SOC_SINGLE("ADC1 +6dB Capture Switch", PEB2466_CR3(1), 3, 1, 0),
+	SOC_SINGLE("ADC2 +6dB Capture Switch", PEB2466_CR3(2), 3, 1, 0),
+	SOC_SINGLE("ADC3 +6dB Capture Switch", PEB2466_CR3(3), 3, 1, 0),
+
+	/* Tone generators */
+	SOC_ENUM_EXT("DAC0 TG1 Freq", peb2466_tg_freq[0][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC1 TG1 Freq", peb2466_tg_freq[1][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC2 TG1 Freq", peb2466_tg_freq[2][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC3 TG1 Freq", peb2466_tg_freq[3][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+
+	SOC_ENUM_EXT("DAC0 TG2 Freq", peb2466_tg_freq[0][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC1 TG2 Freq", peb2466_tg_freq[1][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC2 TG2 Freq", peb2466_tg_freq[2][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC3 TG2 Freq", peb2466_tg_freq[3][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+};
+
+static const struct snd_soc_dapm_widget peb2466_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("CH0 PWR", PEB2466_CR1(0), 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("CH1 PWR", PEB2466_CR1(1), 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("CH2 PWR", PEB2466_CR1(2), 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("CH3 PWR", PEB2466_CR1(3), 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("CH0 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("CH1 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("CH2 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("CH3 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_SIGGEN("CH0 TG1"),
+	SND_SOC_DAPM_SIGGEN("CH1 TG1"),
+	SND_SOC_DAPM_SIGGEN("CH2 TG1"),
+	SND_SOC_DAPM_SIGGEN("CH3 TG1"),
+
+	SND_SOC_DAPM_SIGGEN("CH0 TG2"),
+	SND_SOC_DAPM_SIGGEN("CH1 TG2"),
+	SND_SOC_DAPM_SIGGEN("CH2 TG2"),
+	SND_SOC_DAPM_SIGGEN("CH3 TG2"),
+
+	SND_SOC_DAPM_MIXER("DAC0 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch0_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch0_out_mix_controls)),
+	SND_SOC_DAPM_MIXER("DAC1 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch1_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch1_out_mix_controls)),
+	SND_SOC_DAPM_MIXER("DAC2 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch2_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch2_out_mix_controls)),
+	SND_SOC_DAPM_MIXER("DAC3 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch3_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch3_out_mix_controls)),
+
+	SND_SOC_DAPM_PGA("DAC0 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("DAC1 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("DAC2 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("DAC3 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_OUTPUT("OUT0"),
+	SND_SOC_DAPM_OUTPUT("OUT1"),
+	SND_SOC_DAPM_OUTPUT("OUT2"),
+	SND_SOC_DAPM_OUTPUT("OUT3"),
+
+	SND_SOC_DAPM_INPUT("IN0"),
+	SND_SOC_DAPM_INPUT("IN1"),
+	SND_SOC_DAPM_INPUT("IN2"),
+	SND_SOC_DAPM_INPUT("IN3"),
+
+	SND_SOC_DAPM_DAC("ADC0", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ADC1", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ADC2", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ADC3", "Capture", SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route peb2466_dapm_routes[] = {
+	{ "CH0 DIN", NULL, "CH0 PWR" },
+	{ "CH1 DIN", NULL, "CH1 PWR" },
+	{ "CH2 DIN", NULL, "CH2 PWR" },
+	{ "CH3 DIN", NULL, "CH3 PWR" },
+
+	{ "CH0 TG1", NULL, "CH0 PWR" },
+	{ "CH1 TG1", NULL, "CH1 PWR" },
+	{ "CH2 TG1", NULL, "CH2 PWR" },
+	{ "CH3 TG1", NULL, "CH3 PWR" },
+
+	{ "CH0 TG2", NULL, "CH0 PWR" },
+	{ "CH1 TG2", NULL, "CH1 PWR" },
+	{ "CH2 TG2", NULL, "CH2 PWR" },
+	{ "CH3 TG2", NULL, "CH3 PWR" },
+
+	{ "DAC0 Mixer", "TG1 Switch", "CH0 TG1" },
+	{ "DAC0 Mixer", "TG2 Switch", "CH0 TG2" },
+	{ "DAC0 Mixer", "Voice Switch", "CH0 DIN" },
+	{ "DAC0 Mixer", NULL, "CH0 DIN" },
+
+	{ "DAC1 Mixer", "TG1 Switch", "CH1 TG1" },
+	{ "DAC1 Mixer", "TG2 Switch", "CH1 TG2" },
+	{ "DAC1 Mixer", "Voice Switch", "CH1 DIN" },
+	{ "DAC1 Mixer", NULL, "CH1 DIN" },
+
+	{ "DAC2 Mixer", "TG1 Switch", "CH2 TG1" },
+	{ "DAC2 Mixer", "TG2 Switch", "CH2 TG2" },
+	{ "DAC2 Mixer", "Voice Switch", "CH2 DIN" },
+	{ "DAC2 Mixer", NULL, "CH2 DIN" },
+
+	{ "DAC3 Mixer", "TG1 Switch", "CH3 TG1" },
+	{ "DAC3 Mixer", "TG2 Switch", "CH3 TG2" },
+	{ "DAC3 Mixer", "Voice Switch", "CH3 DIN" },
+	{ "DAC3 Mixer", NULL, "CH3 DIN" },
+
+	{ "DAC0 PGA", NULL, "DAC0 Mixer" },
+	{ "DAC1 PGA", NULL, "DAC1 Mixer" },
+	{ "DAC2 PGA", NULL, "DAC2 Mixer" },
+	{ "DAC3 PGA", NULL, "DAC3 Mixer" },
+
+	{ "OUT0", NULL, "DAC0 PGA" },
+	{ "OUT1", NULL, "DAC1 PGA" },
+	{ "OUT2", NULL, "DAC2 PGA" },
+	{ "OUT3", NULL, "DAC3 PGA" },
+
+	{ "ADC0", NULL, "IN0" },
+	{ "ADC1", NULL, "IN1" },
+	{ "ADC2", NULL, "IN2" },
+	{ "ADC3", NULL, "IN3" },
+
+	{ "ADC0", NULL, "CH0 PWR" },
+	{ "ADC1", NULL, "CH1 PWR" },
+	{ "ADC2", NULL, "CH2 PWR" },
+	{ "ADC3", NULL, "CH3 PWR" },
+};
+
+static int peb2466_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+				    unsigned int rx_mask, int slots, int width)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int chan;
+	unsigned int mask;
+	u8 slot;
+	int ret;
+
+	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;
+	chan = 0;
+	while (mask && chan < PEB2466_NB_CHANNEL) {
+		if (mask & 0x1) {
+			ret = regmap_write(peb2466->regmap, PEB2466_CR5(chan), slot);
+			if (ret) {
+				dev_err(dai->dev, "chan %d set tx tdm slot failed (%d)\n",
+					chan, ret);
+				return ret;
+			}
+			chan++;
+		}
+		mask >>= 1;
+		slot++;
+	}
+	if (mask) {
+		dev_err(dai->dev, "too much tx slots defined (mask = 0x%x) support max %d\n",
+			tx_mask, PEB2466_NB_CHANNEL);
+		return -EINVAL;
+	}
+	peb2466->max_chan_playback = chan;
+
+	mask = rx_mask;
+	slot = 0;
+	chan = 0;
+	while (mask && chan < PEB2466_NB_CHANNEL) {
+		if (mask & 0x1) {
+			ret = regmap_write(peb2466->regmap, PEB2466_CR4(chan), slot);
+			if (ret) {
+				dev_err(dai->dev, "chan %d set rx tdm slot failed (%d)\n",
+					chan, ret);
+				return ret;
+			}
+			chan++;
+		}
+		mask >>= 1;
+		slot++;
+	}
+	if (mask) {
+		dev_err(dai->dev, "too much rx slots defined (mask = 0x%x) support max %d\n",
+			rx_mask, PEB2466_NB_CHANNEL);
+		return -EINVAL;
+	}
+	peb2466->max_chan_capture = chan;
+
+	return 0;
+}
+
+static int peb2466_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	u8 xr6;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		xr6 = PEB2466_XR6_PCM_OFFSET(1);
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		xr6 = PEB2466_XR6_PCM_OFFSET(0);
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported format 0x%x\n",
+			fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+	return regmap_write(peb2466->regmap, PEB2466_XR6, xr6);
+}
+
+static int peb2466_dai_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int ch;
+	int ret;
+	u8 cr1;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		cr1 = PEB2466_CR1_LAW_MULAW;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		cr1 = PEB2466_CR1_LAW_ALAW;
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Unsupported format 0x%x\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	for (ch = 0; ch < PEB2466_NB_CHANNEL; ch++) {
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR1(ch),
+					 PEB2466_CR1_LAW_MASK, cr1);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const unsigned int peb2466_sample_bits[] = {8};
+
+static struct snd_pcm_hw_constraint_list peb2466_sample_bits_constr = {
+	.list = peb2466_sample_bits,
+	.count = ARRAY_SIZE(peb2466_sample_bits),
+};
+
+static int peb2466_dai_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int max_ch;
+	int ret;
+
+	max_ch = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		peb2466->max_chan_playback : peb2466->max_chan_capture;
+
+	/*
+	 * Disable stream support (min = 0, max = 0) if no timeslots were
+	 * 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;
+
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+					  SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					  &peb2466_sample_bits_constr);
+}
+
+static u64 peb2466_dai_formats[] = {
+	SND_SOC_POSSIBLE_DAIFMT_DSP_A	|
+	SND_SOC_POSSIBLE_DAIFMT_DSP_B,
+};
+
+static const struct snd_soc_dai_ops peb2466_dai_ops = {
+	.startup = peb2466_dai_startup,
+	.hw_params = peb2466_dai_hw_params,
+	.set_tdm_slot = peb2466_dai_set_tdm_slot,
+	.set_fmt = peb2466_dai_set_fmt,
+	.auto_selectable_formats     = peb2466_dai_formats,
+	.num_auto_selectable_formats = ARRAY_SIZE(peb2466_dai_formats),
+};
+
+static struct snd_soc_dai_driver peb2466_dai_driver = {
+	.name = "peb2466",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = PEB2466_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 = PEB2466_NB_CHANNEL,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
+	},
+	.ops = &peb2466_dai_ops,
+};
+
+static int peb2466_reset_audio(struct peb2466 *peb2466)
+{
+	static const struct reg_sequence reg_reset[] = {
+		{  .reg = PEB2466_XR6,    .def = 0x00 },
+
+		{  .reg = PEB2466_CR5(0), .def = 0x00 },
+		{  .reg = PEB2466_CR4(0), .def = 0x00 },
+		{  .reg = PEB2466_CR3(0), .def = 0x00 },
+		{  .reg = PEB2466_CR2(0), .def = 0x00 },
+		{  .reg = PEB2466_CR1(0), .def = 0x00 },
+		{  .reg = PEB2466_CR0(0), .def = PEB2466_CR0_IMR1 },
+
+		{  .reg = PEB2466_CR5(1), .def = 0x00 },
+		{  .reg = PEB2466_CR4(1), .def = 0x00 },
+		{  .reg = PEB2466_CR3(1), .def = 0x00 },
+		{  .reg = PEB2466_CR2(1), .def = 0x00 },
+		{  .reg = PEB2466_CR1(1), .def = 0x00 },
+		{  .reg = PEB2466_CR0(1), .def = PEB2466_CR0_IMR1 },
+
+		{  .reg = PEB2466_CR5(2), .def = 0x00 },
+		{  .reg = PEB2466_CR4(2), .def = 0x00 },
+		{  .reg = PEB2466_CR3(2), .def = 0x00 },
+		{  .reg = PEB2466_CR2(2), .def = 0x00 },
+		{  .reg = PEB2466_CR1(2), .def = 0x00 },
+		{  .reg = PEB2466_CR0(2), .def = PEB2466_CR0_IMR1 },
+
+		{  .reg = PEB2466_CR5(3), .def = 0x00 },
+		{  .reg = PEB2466_CR4(3), .def = 0x00 },
+		{  .reg = PEB2466_CR3(3), .def = 0x00 },
+		{  .reg = PEB2466_CR2(3), .def = 0x00 },
+		{  .reg = PEB2466_CR1(3), .def = 0x00 },
+		{  .reg = PEB2466_CR0(3), .def = PEB2466_CR0_IMR1 },
+	};
+	static const u8 imr1_p1[8] = {0x00, 0x90, 0x09, 0x00, 0x90, 0x09, 0x00, 0x00};
+	static const u8 imr1_p2[8] = {0x7F, 0xFF, 0x00, 0x00, 0x90, 0x14, 0x40, 0x08};
+	static const u8 zero[8] = {0};
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		peb2466->ch[i].tg1_freq_item = PEB2466_TONE_1000HZ;
+		peb2466->ch[i].tg2_freq_item = PEB2466_TONE_1000HZ;
+
+		/*
+		 * Even if not used, disabling IM/R1 filter is not recommended.
+		 * Instead, we must configure it with default coefficients and
+		 * enable it.
+		 * The filter will be enabled right after (in the following
+		 * regmap_multi_reg_write() call).
+		 */
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P1(i), imr1_p1, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P2(i), imr1_p2, 8);
+		if (ret)
+			return ret;
+
+		/* Set all other filters coefficients to zero */
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P1(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P2(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P3(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_FRX_FILTER(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_FRR_FILTER(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_AX_FILTER(i), zero, 4);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_AR_FILTER(i), zero, 4);
+		if (ret)
+			return ret;
+	}
+
+	return regmap_multi_reg_write(peb2466->regmap, reg_reset, ARRAY_SIZE(reg_reset));
+}
+
+static int peb2466_fw_parse_thfilter(struct snd_soc_component *component,
+				     u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw TH filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * TH_FILTER TLV data:
+	 *   - @0  1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1  8 bytes: TH-Filter coefficients part1
+	 *   - @9  8 bytes: TH-Filter coefficients part2
+	 *   - @17 8 bytes: TH-Filter coefficients part3
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_TH, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P1(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P2(i), data + 9, 8);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P3(i), data + 17, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_TH | PEB2466_CR0_THSEL_MASK,
+					 PEB2466_CR0_TH | PEB2466_CR0_THSEL(i));
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_imr1filter(struct snd_soc_component *component,
+				       u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw IM/R1 filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * IMR1_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 8 bytes: IM/R1-Filter coefficients part1
+	 *   - @9 8 bytes: IM/R1-Filter coefficients part2
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_IMR1, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P1(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P2(i), data + 9, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_IMR1, PEB2466_CR0_IMR1);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_frxfilter(struct snd_soc_component *component,
+				      u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw FRX filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * FRX_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 8 bytes: FRX-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRX, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_FRX_FILTER(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRX, PEB2466_CR0_FRX);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_frrfilter(struct snd_soc_component *component,
+				      u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw FRR filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * FRR_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 8 bytes: FRR-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRR, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_FRR_FILTER(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRR, PEB2466_CR0_FRR);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_axfilter(struct snd_soc_component *component,
+				     u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw AX filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * AX_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 4 bytes: AX-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AX_FILTER(i), data + 1, 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, PEB2466_CR0_AX);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_arfilter(struct snd_soc_component *component,
+				     u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw AR filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * AR_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 4 bytes: AR-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AR_FILTER(i), data + 1, 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, PEB2466_CR0_AR);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static const char * const peb2466_ax_ctrl_names[] = {
+	"ADC0 Capture Volume",
+	"ADC1 Capture Volume",
+	"ADC2 Capture Volume",
+	"ADC3 Capture Volume",
+};
+
+static int peb2466_fw_parse_axtable(struct snd_soc_component *component,
+				    u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct peb2466_lkup_ctrl *lkup_ctrl;
+	struct peb2466_lookup *lookup;
+	u8 (*table)[4];
+	u32 table_size;
+	u32 init_index;
+	s32 min_val;
+	s32 step;
+	u8 mask;
+	int ret;
+	int i;
+
+	/*
+	 * AX_TABLE TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 32bits signed: Min table value in centi dB (MinVal)
+	 *                       ie -300 means -3.0 dB
+	 *   - @5 32bits signed: Step from on item to other item in centi dB (Step)
+	 *                       ie 25 means 0.25 dB)
+	 *   - @9 32bits unsigned: Item index in the table to use for the initial
+	 *                         value
+	 *   - @13 N*4 bytes: Table composed of 4 bytes items.
+	 *                    Each item correspond to an AX filter value.
+	 *
+	 * The conversion from raw value item in the table to/from the value in
+	 * dB is: Raw value at index i <-> (MinVal + i * Step) in centi dB.
+	 */
+
+	/* Check Lng and extract the table size. */
+	if (lng < 13 || ((lng - 13) % 4)) {
+		dev_err(component->dev, "fw AX table lng %u invalid\n", lng);
+		return -EINVAL;
+	}
+	table_size = lng - 13;
+
+	min_val = get_unaligned_be32(data + 1);
+	step = get_unaligned_be32(data + 5);
+	init_index = get_unaligned_be32(data + 9);
+	if (init_index >= (table_size / 4)) {
+		dev_err(component->dev, "fw AX table index %u out of table[%u]\n",
+			init_index, table_size / 4);
+		return -EINVAL;
+	}
+
+	dev_info(component->dev,
+		 "fw AX table: mask %x, min %d, step %d, %u items, tbl[%u] %*phN\n",
+		 *data, min_val, step, table_size / 4, init_index,
+		 4, data + 13 + (init_index * 4));
+
+	BUILD_BUG_ON(sizeof(*table) != 4);
+	table = devm_kzalloc(&peb2466->spi->dev, table_size, GFP_KERNEL);
+	if (!table)
+		return -ENOMEM;
+	memcpy(table, data + 13, table_size);
+
+	mask = *data;
+	BUILD_BUG_ON(ARRAY_SIZE(peb2466_ax_ctrl_names) != ARRAY_SIZE(peb2466->ch));
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		lookup = &peb2466->ch[i].ax_lookup;
+		lookup->table = table;
+		lookup->count = table_size / 4;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AX_FILTER(i),
+					lookup->table[init_index], 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, PEB2466_CR0_AX);
+		if (ret)
+			return ret;
+
+		lkup_ctrl = &peb2466->ch[i].ax_lkup_ctrl;
+		lkup_ctrl->lookup = lookup;
+		lkup_ctrl->reg = PEB2466_AX_FILTER(i);
+		lkup_ctrl->index = init_index;
+
+		ret = peb2466_add_lkup_ctrl(component, lkup_ctrl,
+					    peb2466_ax_ctrl_names[i],
+					    min_val, step);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static const char * const peb2466_ar_ctrl_names[] = {
+	"DAC0 Playback Volume",
+	"DAC1 Playback Volume",
+	"DAC2 Playback Volume",
+	"DAC3 Playback Volume",
+};
+
+static int peb2466_fw_parse_artable(struct snd_soc_component *component,
+				    u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct peb2466_lkup_ctrl *lkup_ctrl;
+	struct peb2466_lookup *lookup;
+	u8 (*table)[4];
+	u32 table_size;
+	u32 init_index;
+	s32 min_val;
+	s32 step;
+	u8 mask;
+	int ret;
+	int i;
+
+	/*
+	 * AR_TABLE TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 32bits signed: Min table value in centi dB (MinVal)
+	 *                       ie -300 means -3.0 dB
+	 *   - @5 32bits signed: Step from on item to other item in centi dB (Step)
+	 *                       ie 25 means 0.25 dB)
+	 *   - @9 32bits unsigned: Item index in the table to use for the initial
+	 *                         value
+	 *   - @13 N*4 bytes: Table composed of 4 bytes items.
+	 *                    Each item correspond to an AR filter value.
+	 *
+	 * The conversion from raw value item in the table to/from the value in
+	 * dB is: Raw value at index i <-> (MinVal + i * Step) in centi dB.
+	 */
+
+	/* Check Lng and extract the table size. */
+	if (lng < 13 || ((lng - 13) % 4)) {
+		dev_err(component->dev, "fw AR table lng %u invalid\n", lng);
+		return -EINVAL;
+	}
+	table_size = lng - 13;
+
+	min_val = get_unaligned_be32(data + 1);
+	step = get_unaligned_be32(data + 5);
+	init_index = get_unaligned_be32(data + 9);
+	if (init_index >= (table_size / 4)) {
+		dev_err(component->dev, "fw AR table index %u out of table[%u]\n",
+			init_index, table_size / 4);
+		return -EINVAL;
+	}
+
+	dev_info(component->dev,
+		 "fw AR table: mask %x, min %d, step %d, %u items, tbl[%u] %*phN\n",
+		 *data, min_val, step, table_size / 4, init_index,
+		 4, data + 13 + (init_index * 4));
+
+	BUILD_BUG_ON(sizeof(*table) != 4);
+	table = devm_kzalloc(&peb2466->spi->dev, table_size, GFP_KERNEL);
+	if (!table)
+		return -ENOMEM;
+	memcpy(table, data + 13, table_size);
+
+	mask = *data;
+	BUILD_BUG_ON(ARRAY_SIZE(peb2466_ar_ctrl_names) != ARRAY_SIZE(peb2466->ch));
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		lookup = &peb2466->ch[i].ar_lookup;
+		lookup->table = table;
+		lookup->count = table_size / 4;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AR_FILTER(i),
+					lookup->table[init_index], 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, PEB2466_CR0_AR);
+		if (ret)
+			return ret;
+
+		lkup_ctrl = &peb2466->ch[i].ar_lkup_ctrl;
+		lkup_ctrl->lookup = lookup;
+		lkup_ctrl->reg = PEB2466_AR_FILTER(i);
+		lkup_ctrl->index = init_index;
+
+		ret = peb2466_add_lkup_ctrl(component, lkup_ctrl,
+					    peb2466_ar_ctrl_names[i],
+					    min_val, step);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+struct peb2466_fw_tag_def {
+	u16 tag;
+	u32 lng_min;
+	u32 lng_max;
+	int (*parse)(struct snd_soc_component *component,
+		     u16 tag, u32 lng, const u8 *data);
+};
+
+#define PEB2466_TAG_DEF_LNG_EQ(__tag, __lng, __parse) { \
+	.tag = __tag,		\
+	.lng_min = __lng,	\
+	.lng_max = __lng,	\
+	.parse = __parse,	\
+}
+
+#define PEB2466_TAG_DEF_LNG_MIN(__tag, __lng_min, __parse) { \
+	.tag = __tag,		\
+	.lng_min = __lng_min,	\
+	.lng_max = U32_MAX,	\
+	.parse = __parse,	\
+}
+
+static const struct peb2466_fw_tag_def peb2466_fw_tag_defs[] = {
+	/* TH FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0001, 1 + 3 * 8, peb2466_fw_parse_thfilter),
+	/* IMR1 FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0002, 1 + 2 * 8, peb2466_fw_parse_imr1filter),
+	/* FRX FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0003, 1 + 8, peb2466_fw_parse_frxfilter),
+	/* FRR FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0004, 1 + 8, peb2466_fw_parse_frrfilter),
+	/* AX FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0005, 1 + 4, peb2466_fw_parse_axfilter),
+	/* AR FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0006, 1 + 4, peb2466_fw_parse_arfilter),
+	/* AX TABLE */
+	PEB2466_TAG_DEF_LNG_MIN(0x0105, 1 + 3 * 4, peb2466_fw_parse_axtable),
+	/* AR TABLE */
+	PEB2466_TAG_DEF_LNG_MIN(0x0106, 1 + 3 * 4, peb2466_fw_parse_artable),
+};
+
+static const struct peb2466_fw_tag_def *peb2466_fw_get_tag_def(u16 tag)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(peb2466_fw_tag_defs); i++) {
+		if (peb2466_fw_tag_defs[i].tag == tag)
+			return &peb2466_fw_tag_defs[i];
+	}
+	return NULL;
+}
+
+static int peb2466_fw_parse(struct snd_soc_component *component,
+			    const u8 *data, size_t size)
+{
+	const struct peb2466_fw_tag_def *tag_def;
+	size_t left;
+	const u8 *buf;
+	u16 val16;
+	u16 tag;
+	u32 lng;
+	int ret;
+
+	/*
+	 * Coefficients firmware binary structure (16bits and 32bits are
+	 * big-endian values).
+	 *
+	 * @0, 16bits: Magic (0x2466)
+	 * @2, 16bits: Version (0x0100 for version 1.0)
+	 * @4, 2+4+N bytes: TLV block
+	 * @4+(2+4+N) bytes: Next TLV block
+	 * ...
+	 *
+	 * Detail of a TLV block:
+	 *   @0, 16bits: Tag
+	 *   @2, 32bits: Lng
+	 *   @6, lng bytes: Data
+	 *
+	 * The detail the Data for a given TLV Tag is provided in the related
+	 * parser.
+	 */
+
+	left = size;
+	buf = data;
+
+	if (left < 4) {
+		dev_err(component->dev, "fw size %zu, exp at least 4\n", left);
+		return -EINVAL;
+	}
+
+	/* Check magic */
+	val16 = get_unaligned_be16(buf);
+	if (val16 != 0x2466) {
+		dev_err(component->dev, "fw magic 0x%04x exp 0x2466\n", val16);
+		return -EINVAL;
+	}
+	buf += 2;
+	left -= 2;
+
+	/* Check version */
+	val16 = get_unaligned_be16(buf);
+	if (val16 != 0x0100) {
+		dev_err(component->dev, "fw magic 0x%04x exp 0x0100\n", val16);
+		return -EINVAL;
+	}
+	buf += 2;
+	left -= 2;
+
+	while (left) {
+		if (left < 6) {
+			dev_err(component->dev, "fw %td/%zu left %zu, exp at least 6\n",
+				buf - data, size, left);
+			return -EINVAL;
+		}
+		/* Check tag and lng */
+		tag = get_unaligned_be16(buf);
+		lng = get_unaligned_be32(buf + 2);
+		tag_def = peb2466_fw_get_tag_def(tag);
+		if (!tag_def) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x unknown\n",
+				buf - data, size, tag);
+			return -EINVAL;
+		}
+		if (lng < tag_def->lng_min || lng > tag_def->lng_max) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x lng %u, exp [%u;%u]\n",
+				buf - data, size, tag, lng, tag_def->lng_min, tag_def->lng_max);
+			return -EINVAL;
+		}
+		buf += 6;
+		left -= 6;
+		if (left < lng) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x lng %u, left %zu\n",
+				buf - data, size, tag, lng, left);
+			return -EINVAL;
+		}
+
+		/* TLV block is valid -> parse the data part */
+		ret = tag_def->parse(component, tag, lng, buf);
+		if (ret) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x lng %u parse failed\n",
+				buf - data, size, tag, lng);
+			return ret;
+		}
+
+		buf += lng;
+		left -= lng;
+	}
+	return 0;
+}
+
+static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name)
+{
+	const struct firmware *fw;
+	int ret;
+
+	ret = request_firmware(&fw, fw_name, component->dev);
+	if (ret)
+		return ret;
+
+	ret = peb2466_fw_parse(component, fw->data, fw->size);
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int peb2466_component_probe(struct snd_soc_component *component)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	const char *firmware_name;
+	int ret;
+
+	/* reset peb2466 audio part */
+	ret = peb2466_reset_audio(peb2466);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_string(peb2466->spi->dev.of_node,
+				      "firmware-name", &firmware_name);
+	if (ret)
+		return (ret == -EINVAL) ? 0 : ret;
+
+	return peb2466_load_coeffs(component, firmware_name);
+}
+
+static const struct snd_soc_component_driver peb2466_component_driver = {
+	.probe			= peb2466_component_probe,
+	.controls		= peb2466_controls,
+	.num_controls		= ARRAY_SIZE(peb2466_controls),
+	.dapm_widgets		= peb2466_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(peb2466_dapm_widgets),
+	.dapm_routes		= peb2466_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(peb2466_dapm_routes),
+	.endianness		= 1,
+};
+
+/*
+ * The mapping used for the relationship between the gpio offset and the
+ * physical pin is the following:
+ *
+ * offset     pin
+ *      0     SI1_0
+ *      1     SI1_1
+ *      2     SI2_0
+ *      3     SI2_1
+ *      4     SI3_0
+ *      5     SI3_1
+ *      6     SI4_0
+ *      7     SI4_1
+ *      8     SO1_0
+ *      9     SO1_1
+ *     10     SO2_0
+ *     11     SO2_1
+ *     12     SO3_0
+ *     13     SO3_1
+ *     14     SO4_0
+ *     15     SO4_1
+ *     16     SB1_0
+ *     17     SB1_1
+ *     18     SB2_0
+ *     19     SB2_1
+ *     20     SB3_0
+ *     21     SB3_1
+ *     22     SB4_0
+ *     23     SB4_1
+ *     24     SB1_2
+ *     25     SB2_2
+ *     26     SB3_2
+ *     27     SB4_2
+ */
+
+static int peb2466_chip_gpio_offset_to_data_regmask(unsigned int offset,
+						    unsigned int *xr_reg,
+						    unsigned int *mask)
+{
+	if (offset < 16) {
+		/*
+		 * SIx_{0,1} and SOx_{0,1}
+		 *   Read accesses read SIx_{0,1} values
+		 *   Write accesses write SOx_{0,1} values
+		 */
+		*xr_reg = PEB2466_XR0;
+		*mask = (1 << (offset % 8));
+		return 0;
+	}
+	if (offset < 24) {
+		/* SBx_{0,1} */
+		*xr_reg = PEB2466_XR1;
+		*mask = (1 << (offset - 16));
+		return 0;
+	}
+	if (offset < 28) {
+		/* SBx_2 */
+		*xr_reg = PEB2466_XR3;
+		*mask = (1 << (offset - 24 + 4));
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int peb2466_chip_gpio_offset_to_dir_regmask(unsigned int offset,
+						   unsigned int *xr_reg,
+						   unsigned int *mask)
+{
+	if (offset < 16) {
+		/* Direction cannot be changed for these GPIOs */
+		return -EINVAL;
+	}
+	if (offset < 24) {
+		*xr_reg = PEB2466_XR2;
+		*mask = (1 << (offset - 16));
+		return 0;
+	}
+	if (offset < 28) {
+		*xr_reg = PEB2466_XR3;
+		*mask = (1 << (offset - 24));
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static unsigned int *peb2466_chip_gpio_get_cache(struct peb2466 *peb2466,
+						 unsigned int xr_reg)
+{
+	unsigned int *cache;
+
+	switch (xr_reg) {
+	case PEB2466_XR0:
+		cache = &peb2466->gpio.cache.xr0;
+		break;
+	case PEB2466_XR1:
+		cache = &peb2466->gpio.cache.xr1;
+		break;
+	case PEB2466_XR2:
+		cache = &peb2466->gpio.cache.xr2;
+		break;
+	case PEB2466_XR3:
+		cache = &peb2466->gpio.cache.xr3;
+		break;
+	default:
+		cache = NULL;
+		break;
+	}
+	return cache;
+}
+
+static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int xr_reg,
+					 unsigned int mask, unsigned int val)
+{
+	unsigned int tmp;
+	unsigned int *cache;
+	int ret;
+
+	/*
+	 * Read and write accesses use different peb2466 internal signals (input
+	 * signals on reads and output signals on writes). regmap_update_bits
+	 * cannot be used to read/modify/write the value.
+	 * So, a specific cache value is used.
+	 */
+
+	mutex_lock(&peb2466->gpio.lock);
+
+	cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg);
+	if (!cache) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	tmp = *cache;
+	tmp &= ~mask;
+	tmp |= val;
+
+	ret = regmap_write(peb2466->regmap, xr_reg, tmp);
+	if (ret)
+		goto end;
+
+	*cache = tmp;
+	ret = 0;
+
+end:
+	mutex_unlock(&peb2466->gpio.lock);
+	return ret;
+}
+
+static void peb2466_chip_gpio_set(struct gpio_chip *c, unsigned int offset, int val)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	int ret;
+
+	if (offset < 8) {
+		/*
+		 * SIx_{0,1} signals cannot be set and writing the related
+		 * register will change the SOx_{0,1} signals
+		 */
+		dev_warn(&peb2466->spi->dev, "cannot set gpio %d (read-only)\n",
+			 offset);
+		return;
+	}
+
+	ret = peb2466_chip_gpio_offset_to_data_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot set gpio %d (%d)\n",
+			offset, ret);
+		return;
+	}
+
+	ret = peb2466_chip_gpio_update_bits(peb2466, xr_reg, mask, val ? mask : 0);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "set gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+	}
+}
+
+static int peb2466_chip_gpio_get(struct gpio_chip *c, unsigned int offset)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	bool use_cache = false;
+	unsigned int *cache;
+	unsigned int xr_reg;
+	unsigned int mask;
+	unsigned int val;
+	int ret;
+
+	if (offset >= 8 && offset < 16) {
+		/*
+		 * SOx_{0,1} signals cannot be read. Reading the related
+		 * register will read the SIx_{0,1} signals.
+		 * Use the cache to get value;
+		 */
+		use_cache = true;
+	}
+
+	ret = peb2466_chip_gpio_offset_to_data_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot get gpio %d (%d)\n",
+			offset, ret);
+		return -EINVAL;
+	}
+
+	if (use_cache) {
+		cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg);
+		if (!cache)
+			return -EINVAL;
+		val = *cache;
+	} else {
+		ret = regmap_read(peb2466->regmap, xr_reg, &val);
+		if (ret) {
+			dev_err(&peb2466->spi->dev, "get gpio %d (0x%x, 0x%x) failed (%d)\n",
+				offset, xr_reg, mask, ret);
+			return ret;
+		}
+	}
+
+	return !!(val & mask);
+}
+
+static int peb2466_chip_get_direction(struct gpio_chip *c, unsigned int offset)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	unsigned int val;
+	int ret;
+
+	if (offset < 8) {
+		/* SIx_{0,1} */
+		return GPIO_LINE_DIRECTION_IN;
+	}
+	if (offset < 16) {
+		/* SOx_{0,1} */
+		return GPIO_LINE_DIRECTION_OUT;
+	}
+
+	ret = peb2466_chip_gpio_offset_to_dir_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot get gpio %d direction (%d)\n",
+			offset, ret);
+		return ret;
+	}
+
+	ret = regmap_read(peb2466->regmap, xr_reg, &val);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "get dir gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+		return ret;
+	}
+
+	return val & mask ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int peb2466_chip_direction_input(struct gpio_chip *c, unsigned int offset)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	int ret;
+
+	if (offset < 8) {
+		/* SIx_{0,1} */
+		return 0;
+	}
+	if (offset < 16) {
+		/* SOx_{0,1} */
+		return -EINVAL;
+	};
+
+	ret = peb2466_chip_gpio_offset_to_dir_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot set gpio %d direction (%d)\n",
+			offset, ret);
+		return ret;
+	}
+
+	ret = peb2466_chip_gpio_update_bits(peb2466, xr_reg, mask, 0);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "Set dir in gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int peb2466_chip_direction_output(struct gpio_chip *c, unsigned int offset, int val)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	int ret;
+
+	if (offset < 8) {
+		/* SIx_{0,1} */
+		return -EINVAL;
+	}
+
+	peb2466_chip_gpio_set(c, offset, val);
+
+	if (offset < 16) {
+		/* SOx_{0,1} */
+		return 0;
+	};
+
+	ret = peb2466_chip_gpio_offset_to_dir_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot set gpio %d direction (%d)\n",
+			offset, ret);
+		return ret;
+	}
+
+	ret = peb2466_chip_gpio_update_bits(peb2466, xr_reg, mask, mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "Set dir in gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int peb2466_reset_gpio(struct peb2466 *peb2466)
+{
+	static const struct reg_sequence reg_reset[] = {
+		/* Output pins at 0, input/output pins as input */
+		{  .reg = PEB2466_XR0, .def = 0 },
+		{  .reg = PEB2466_XR1, .def = 0 },
+		{  .reg = PEB2466_XR2, .def = 0 },
+		{  .reg = PEB2466_XR3, .def = 0 },
+	};
+
+	peb2466->gpio.cache.xr0 = 0;
+	peb2466->gpio.cache.xr1 = 0;
+	peb2466->gpio.cache.xr2 = 0;
+	peb2466->gpio.cache.xr3 = 0;
+
+	return regmap_multi_reg_write(peb2466->regmap, reg_reset, ARRAY_SIZE(reg_reset));
+}
+
+static int peb2466_gpio_init(struct peb2466 *peb2466)
+{
+	int ret;
+
+	mutex_init(&peb2466->gpio.lock);
+
+	ret = peb2466_reset_gpio(peb2466);
+	if (ret)
+		return ret;
+
+	peb2466->gpio.gpio_chip.owner = THIS_MODULE;
+	peb2466->gpio.gpio_chip.label = dev_name(&peb2466->spi->dev);
+	peb2466->gpio.gpio_chip.parent = &peb2466->spi->dev;
+	peb2466->gpio.gpio_chip.base = -1;
+	peb2466->gpio.gpio_chip.ngpio = 28;
+	peb2466->gpio.gpio_chip.get_direction = peb2466_chip_get_direction;
+	peb2466->gpio.gpio_chip.direction_input = peb2466_chip_direction_input;
+	peb2466->gpio.gpio_chip.direction_output = peb2466_chip_direction_output;
+	peb2466->gpio.gpio_chip.get = peb2466_chip_gpio_get;
+	peb2466->gpio.gpio_chip.set = peb2466_chip_gpio_set;
+	peb2466->gpio.gpio_chip.can_sleep = true;
+
+	return devm_gpiochip_add_data(&peb2466->spi->dev, &peb2466->gpio.gpio_chip,
+				      peb2466);
+}
+
+static int peb2466_spi_probe(struct spi_device *spi)
+{
+	struct peb2466 *peb2466;
+	unsigned long mclk_rate;
+	int ret;
+	u8 xr5;
+
+	spi->bits_per_word = 8;
+	ret = spi_setup(spi);
+	if (ret < 0)
+		return ret;
+
+	peb2466 = devm_kzalloc(&spi->dev, sizeof(*peb2466), GFP_KERNEL);
+	if (!peb2466)
+		return -ENOMEM;
+
+	peb2466->spi = spi;
+
+	peb2466->regmap = devm_regmap_init(&peb2466->spi->dev, NULL, peb2466,
+					   &peb2466_regmap_config);
+	if (IS_ERR(peb2466->regmap))
+		return PTR_ERR(peb2466->regmap);
+
+	peb2466->reset_gpio = devm_gpiod_get_optional(&peb2466->spi->dev,
+						      "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(peb2466->reset_gpio))
+		return PTR_ERR(peb2466->reset_gpio);
+
+	peb2466->mclk = devm_clk_get(&peb2466->spi->dev, "mclk");
+	if (IS_ERR(peb2466->mclk))
+		return PTR_ERR(peb2466->mclk);
+	ret = clk_prepare_enable(peb2466->mclk);
+	if (ret)
+		return ret;
+
+	if (peb2466->reset_gpio) {
+		gpiod_set_value_cansleep(peb2466->reset_gpio, 1);
+		udelay(4);
+		gpiod_set_value_cansleep(peb2466->reset_gpio, 0);
+		udelay(4);
+	}
+
+	spi_set_drvdata(spi, peb2466);
+
+	mclk_rate = clk_get_rate(peb2466->mclk);
+	switch (mclk_rate) {
+	case 1536000:
+		xr5 = PEB2466_XR5_MCLK_1536;
+		break;
+	case 2048000:
+		xr5 = PEB2466_XR5_MCLK_2048;
+		break;
+	case 4096000:
+		xr5 = PEB2466_XR5_MCLK_4096;
+		break;
+	case 8192000:
+		xr5 = PEB2466_XR5_MCLK_8192;
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Unsupported clock rate %lu\n",
+			mclk_rate);
+		ret = -EINVAL;
+		goto failed;
+	}
+	ret = regmap_write(peb2466->regmap, PEB2466_XR5, xr5);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "Setting MCLK failed (%d)\n", ret);
+		goto failed;
+	}
+
+	ret = devm_snd_soc_register_component(&spi->dev, &peb2466_component_driver,
+					      &peb2466_dai_driver, 1);
+	if (ret)
+		goto failed;
+
+	if (IS_ENABLED(CONFIG_GPIOLIB)) {
+		ret = peb2466_gpio_init(peb2466);
+		if (ret)
+			goto failed;
+	}
+
+	return 0;
+
+failed:
+	clk_disable_unprepare(peb2466->mclk);
+	return ret;
+}
+
+static void peb2466_spi_remove(struct spi_device *spi)
+{
+	struct peb2466 *peb2466 = spi_get_drvdata(spi);
+
+	clk_disable_unprepare(peb2466->mclk);
+}
+
+static const struct of_device_id peb2466_of_match[] = {
+	{ .compatible = "infineon,peb2466", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, peb2466_of_match);
+
+static const struct spi_device_id peb2466_id_table[] = {
+	{ "peb2466", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, peb2466_id_table);
+
+static struct spi_driver peb2466_spi_driver = {
+	.driver  = {
+		.name   = "peb2466",
+		.of_match_table = peb2466_of_match,
+	},
+	.id_table = peb2466_id_table,
+	.probe  = peb2466_spi_probe,
+	.remove = peb2466_spi_remove,
+};
+
+module_spi_driver(peb2466_spi_driver);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("PEB2466 ALSA SoC driver");
+MODULE_LICENSE("GPL");
-- 
2.39.1


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

* [PATCH 2/3] ASoC: codecs: Add support for the Infineon PEB2466 codec
@ 2023-02-06 14:49   ` Herve Codina
  0 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: devicetree, alsa-devel, linux-kernel, Christophe Leroy,
	linux-gpio, Thomas Petazzoni

The Infineon PEB2466 codec is a programmable DSP-based four channels
codec with filters capabilities.
It also provides signals as GPIOs.

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

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 19afa0067305..80dd454695b6 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -165,6 +165,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_PCM5102A
 	imply SND_SOC_PCM512x_I2C
 	imply SND_SOC_PCM512x_SPI
+	imply SND_SOC_PEB2466
 	imply SND_SOC_RK3328
 	imply SND_SOC_RK817
 	imply SND_SOC_RT274
@@ -1217,6 +1218,17 @@ config SND_SOC_PCM512x_SPI
 	select SND_SOC_PCM512x
 	select REGMAP_SPI
 
+config SND_SOC_PEB2466
+	tristate "Infineon PEB2466 quad PCM codec"
+	depends on SPI
+	select REGMAP_SPI
+	help
+	  Enable support for the Infineon PEB2466 quad PCM codec,
+	  also named SICOFI 4-uC.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-soc-peb2466.
+
 config SND_SOC_RK3328
 	tristate "Rockchip RK3328 audio CODEC"
 	select REGMAP_MMIO
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index a1d6de6a18f6..a48fec03703d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -184,6 +184,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-peb2466-objs := peb2466.o
 snd-soc-rk3328-objs := rk3328_codec.o
 snd-soc-rk817-objs := rk817_codec.o
 snd-soc-rl6231-objs := rl6231.o
@@ -542,6 +543,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_PEB2466)	+= snd-soc-peb2466.o
 obj-$(CONFIG_SND_SOC_RK3328)	+= snd-soc-rk3328.o
 obj-$(CONFIG_SND_SOC_RK817)	+= snd-soc-rk817.o
 obj-$(CONFIG_SND_SOC_RL6231)	+= snd-soc-rl6231.o
diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c
new file mode 100644
index 000000000000..4640b1860312
--- /dev/null
+++ b/sound/soc/codecs/peb2466.c
@@ -0,0 +1,2071 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// peb2466.c  --  Infineon PEB2466 ALSA SoC driver
+//
+// Copyright 2023 CS GROUP France
+//
+// Author: Herve Codina <herve.codina@bootlin.com>
+
+#include <asm/unaligned.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#define PEB2466_NB_CHANNEL	4
+
+struct peb2466_lookup {
+	u8 (*table)[4];
+	unsigned int count;
+};
+
+#define PEB2466_TLV_SIZE  (sizeof((unsigned int []){TLV_DB_SCALE_ITEM(0, 0, 0)}) / \
+			   sizeof(unsigned int))
+
+struct peb2466_lkup_ctrl {
+	int reg;
+	unsigned int index;
+	const struct peb2466_lookup *lookup;
+	unsigned int tlv_array[PEB2466_TLV_SIZE];
+};
+
+struct peb2466 {
+	struct spi_device *spi;
+	struct clk *mclk;
+	struct gpio_desc *reset_gpio;
+	u8 spi_tx_buf[2 + 8]; /* Cannot use stack area for SPI (dma-safe memory) */
+	u8 spi_rx_buf[2 + 8]; /* Cannot use stack area for SPI (dma-safe memory) */
+	struct regmap *regmap;
+	struct {
+		struct peb2466_lookup ax_lookup;
+		struct peb2466_lookup ar_lookup;
+		struct peb2466_lkup_ctrl ax_lkup_ctrl;
+		struct peb2466_lkup_ctrl ar_lkup_ctrl;
+		unsigned int tg1_freq_item;
+		unsigned int tg2_freq_item;
+	} ch[PEB2466_NB_CHANNEL];
+	int max_chan_playback;
+	int max_chan_capture;
+	struct {
+		struct gpio_chip gpio_chip;
+		struct mutex lock;
+		struct {
+			unsigned int xr0;
+			unsigned int xr1;
+			unsigned int xr2;
+			unsigned int xr3;
+		} cache;
+	} gpio;
+};
+
+#define PEB2466_CMD_R	(1 << 5)
+#define PEB2466_CMD_W	(0 << 5)
+
+#define PEB2466_CMD_MASK 0x18
+#define PEB2466_CMD_XOP  0x18  /* XOP is 0bxxx11xxx */
+#define PEB2466_CMD_SOP  0x10  /* SOP is 0bxxx10xxx */
+#define PEB2466_CMD_COP  0x00  /* COP is 0bxxx0xxxx, handle 0bxxx00xxx */
+#define PEB2466_CMD_COP1 0x08  /* COP is 0bxxx0xxxx, handle 0bxxx01xxx */
+
+#define PEB2466_MAKE_XOP(_lsel)      (PEB2466_CMD_XOP | (_lsel))
+#define PEB2466_MAKE_SOP(_ad, _lsel) (PEB2466_CMD_SOP | ((_ad) << 6) | (_lsel))
+#define PEB2466_MAKE_COP(_ad, _code) (PEB2466_CMD_COP | ((_ad) << 6) | (_code))
+
+#define PEB2466_CR0(_ch)	PEB2466_MAKE_SOP(_ch, 0x0)
+#define   PEB2466_CR0_TH		(1 << 7)
+#define   PEB2466_CR0_IMR1		(1 << 6)
+#define   PEB2466_CR0_FRX		(1 << 5)
+#define   PEB2466_CR0_FRR		(1 << 4)
+#define   PEB2466_CR0_AX		(1 << 3)
+#define   PEB2466_CR0_AR		(1 << 2)
+#define   PEB2466_CR0_THSEL_MASK	(0x3 << 0)
+#define   PEB2466_CR0_THSEL(_set)	((_set) << 0)
+
+#define PEB2466_CR1(_ch)	PEB2466_MAKE_SOP(_ch, 0x1)
+#define   PEB2466_CR1_ETG2		(1 << 7)
+#define   PEB2466_CR1_ETG1		(1 << 6)
+#define   PEB2466_CR1_PTG2		(1 << 5)
+#define   PEB2466_CR1_PTG1		(1 << 4)
+#define   PEB2466_CR1_LAW_MASK		(1 << 3)
+#define   PEB2466_CR1_LAW_ALAW		(0 << 3)
+#define   PEB2466_CR1_LAW_MULAW		(1 << 3)
+#define   PEB2466_CR1_PU		(1 << 0)
+
+#define PEB2466_CR2(_ch)	PEB2466_MAKE_SOP(_ch, 0x2)
+#define PEB2466_CR3(_ch)	PEB2466_MAKE_SOP(_ch, 0x3)
+#define PEB2466_CR4(_ch)	PEB2466_MAKE_SOP(_ch, 0x4)
+#define PEB2466_CR5(_ch)	PEB2466_MAKE_SOP(_ch, 0x5)
+
+#define PEB2466_XR0		PEB2466_MAKE_XOP(0x0)
+#define PEB2466_XR1		PEB2466_MAKE_XOP(0x1)
+#define PEB2466_XR2		PEB2466_MAKE_XOP(0x2)
+#define PEB2466_XR3		PEB2466_MAKE_XOP(0x3)
+#define PEB2466_XR4		PEB2466_MAKE_XOP(0x4)
+#define PEB2466_XR5		PEB2466_MAKE_XOP(0x5)
+#define   PEB2466_XR5_MCLK_1536		(0x0 << 6)
+#define   PEB2466_XR5_MCLK_2048		(0x1 << 6)
+#define   PEB2466_XR5_MCLK_4096		(0x2 << 6)
+#define   PEB2466_XR5_MCLK_8192		(0x3 << 6)
+
+#define PEB2466_XR6		PEB2466_MAKE_XOP(0x6)
+#define   PEB2466_XR6_PCM_OFFSET(_off)	((_off) << 0)
+
+#define PEB2466_XR7		PEB2466_MAKE_XOP(0x7)
+
+#define PEB2466_TH_FILTER_P1(_ch)	PEB2466_MAKE_COP(_ch, 0x0)
+#define PEB2466_TH_FILTER_P2(_ch)	PEB2466_MAKE_COP(_ch, 0x1)
+#define PEB2466_TH_FILTER_P3(_ch)	PEB2466_MAKE_COP(_ch, 0x2)
+#define PEB2466_IMR1_FILTER_P1(_ch)	PEB2466_MAKE_COP(_ch, 0x4)
+#define PEB2466_IMR1_FILTER_P2(_ch)	PEB2466_MAKE_COP(_ch, 0x5)
+#define PEB2466_FRX_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x6)
+#define PEB2466_FRR_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x7)
+#define PEB2466_AX_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x8)
+#define PEB2466_AR_FILTER(_ch)		PEB2466_MAKE_COP(_ch, 0x9)
+#define PEB2466_TG1(_ch)		PEB2466_MAKE_COP(_ch, 0xc)
+#define PEB2466_TG2(_ch)		PEB2466_MAKE_COP(_ch, 0xd)
+
+static int peb2466_write_byte(struct peb2466 *peb2466, u8 cmd, u8 val)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = &peb2466->spi_tx_buf,
+		.len = 2,
+	};
+
+	peb2466->spi_tx_buf[0] = cmd | PEB2466_CMD_W;
+	peb2466->spi_tx_buf[1] = val;
+
+	dev_dbg(&peb2466->spi->dev, "write byte (cmd %02x) %02x\n",
+		peb2466->spi_tx_buf[0], peb2466->spi_tx_buf[1]);
+
+	return spi_sync_transfer(peb2466->spi, &xfer, 1);
+}
+
+static int peb2466_read_byte(struct peb2466 *peb2466, u8 cmd, u8 *val)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = &peb2466->spi_tx_buf,
+		.rx_buf = &peb2466->spi_rx_buf,
+		.len = 3,
+	};
+	int ret;
+
+	peb2466->spi_tx_buf[0] = cmd | PEB2466_CMD_R;
+
+	ret = spi_sync_transfer(peb2466->spi, &xfer, 1);
+	if (ret)
+		return ret;
+
+	if (peb2466->spi_rx_buf[1] != 0x81) {
+		dev_err(&peb2466->spi->dev,
+			"spi xfer rd (cmd %02x) invalid ident byte (0x%02x)\n",
+			peb2466->spi_tx_buf[0], peb2466->spi_rx_buf[1]);
+		return -EILSEQ;
+	}
+
+	*val = peb2466->spi_rx_buf[2];
+
+	dev_dbg(&peb2466->spi->dev, "read byte (cmd %02x) %02x\n",
+		peb2466->spi_tx_buf[0], *val);
+
+	return 0;
+}
+
+static int peb2466_write_buf(struct peb2466 *peb2466, u8 cmd, const u8 *buf, unsigned int len)
+{
+	struct spi_transfer xfer = {
+		.tx_buf = &peb2466->spi_tx_buf,
+		.len = len + 1,
+	};
+
+	if (len > 8)
+		return -EINVAL;
+
+	peb2466->spi_tx_buf[0] = cmd | PEB2466_CMD_W;
+	memcpy(&peb2466->spi_tx_buf[1], buf, len);
+
+	dev_dbg(&peb2466->spi->dev, "write buf (cmd %02x, %u) %*ph\n",
+		peb2466->spi_tx_buf[0], len, len, &peb2466->spi_tx_buf[1]);
+
+	return spi_sync_transfer(peb2466->spi, &xfer, 1);
+}
+
+static int peb2466_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct peb2466 *peb2466 = context;
+	int ret;
+
+	/*
+	 * Only XOP and SOP commands can be handled as registers.
+	 * COP commands are handled using direct peb2466_write_buf() calls.
+	 */
+	switch (reg & PEB2466_CMD_MASK) {
+	case PEB2466_CMD_XOP:
+	case PEB2466_CMD_SOP:
+		ret = peb2466_write_byte(peb2466, reg, val);
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Not a XOP or SOP command\n");
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int peb2466_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct peb2466 *peb2466 = context;
+	int ret;
+	u8 tmp;
+
+	/* Only XOP and SOP commands can be handled as registers */
+	switch (reg & PEB2466_CMD_MASK) {
+	case PEB2466_CMD_XOP:
+	case PEB2466_CMD_SOP:
+		ret = peb2466_read_byte(peb2466, reg, &tmp);
+		*val = tmp;
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Not a XOP or SOP command\n");
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static const struct regmap_config peb2466_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0xFF,
+	.reg_write = peb2466_reg_write,
+	.reg_read = peb2466_reg_read,
+	.cache_type = REGCACHE_NONE,
+};
+
+static int peb2466_lkup_ctrl_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct peb2466_lkup_ctrl *lkup_ctrl =
+		(struct peb2466_lkup_ctrl *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = lkup_ctrl->lookup->count - 1;
+	return 0;
+}
+
+static int peb2466_lkup_ctrl_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct peb2466_lkup_ctrl *lkup_ctrl =
+		(struct peb2466_lkup_ctrl *)kcontrol->private_value;
+
+	ucontrol->value.integer.value[0] = lkup_ctrl->index;
+	return 0;
+}
+
+static int peb2466_lkup_ctrl_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct peb2466_lkup_ctrl *lkup_ctrl =
+		(struct peb2466_lkup_ctrl *)kcontrol->private_value;
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	unsigned int index;
+	int ret;
+
+	index = ucontrol->value.integer.value[0];
+	if (index >= lkup_ctrl->lookup->count)
+		return -EINVAL;
+
+	if (index == lkup_ctrl->index)
+		return 0;
+
+	ret = peb2466_write_buf(peb2466, lkup_ctrl->reg,
+				lkup_ctrl->lookup->table[index], 4);
+	if (ret)
+		return ret;
+
+	lkup_ctrl->index = index;
+	return 1; /* The value changed */
+}
+
+static int peb2466_add_lkup_ctrl(struct snd_soc_component *component,
+				 struct peb2466_lkup_ctrl *lkup_ctrl,
+				 const char *name, int min_val, int step)
+{
+	DECLARE_TLV_DB_SCALE(tlv_array, min_val, step, 0);
+	struct snd_kcontrol_new control = {0};
+
+	BUILD_BUG_ON(sizeof(lkup_ctrl->tlv_array) < sizeof(tlv_array));
+	memcpy(lkup_ctrl->tlv_array, tlv_array, sizeof(tlv_array));
+
+	control.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	control.name = name;
+	control.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+			 SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	control.tlv.p = lkup_ctrl->tlv_array;
+	control.info = peb2466_lkup_ctrl_info;
+	control.get = peb2466_lkup_ctrl_get;
+	control.put = peb2466_lkup_ctrl_put;
+	control.private_value = (unsigned long)lkup_ctrl;
+
+	return snd_soc_add_component_controls(component, &control, 1);
+}
+
+enum peb2466_tone_freq {
+	PEB2466_TONE_697HZ,
+	PEB2466_TONE_800HZ,
+	PEB2466_TONE_950HZ,
+	PEB2466_TONE_1000HZ,
+	PEB2466_TONE_1008HZ,
+	PEB2466_TONE_2000HZ,
+};
+
+static const u8 peb2466_tone_lookup[][4] = {
+	[PEB2466_TONE_697HZ] = {0x0a, 0x33, 0x5a, 0x2c},
+	[PEB2466_TONE_800HZ] = {0x12, 0xD6, 0x5a, 0xc0},
+	[PEB2466_TONE_950HZ] = {0x1c, 0xf0, 0x5c, 0xc0},
+	[PEB2466_TONE_1000HZ] = {0}, /* lookup value not used for 1000Hz */
+	[PEB2466_TONE_1008HZ] = {0x1a, 0xae, 0x57, 0x70},
+	[PEB2466_TONE_2000HZ] = {0x00, 0x80, 0x50, 0x09},
+};
+
+static const char * const peb2466_tone_freq_txt[] = {
+	[PEB2466_TONE_697HZ] = "697Hz",
+	[PEB2466_TONE_800HZ] = "800Hz",
+	[PEB2466_TONE_950HZ] = "950Hz",
+	[PEB2466_TONE_1000HZ] = "1000Hz",
+	[PEB2466_TONE_1008HZ] = "1008Hz",
+	[PEB2466_TONE_2000HZ] = "2000Hz"
+};
+
+static const struct soc_enum peb2466_tg_freq[][2] = {
+	[0] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(0), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(0), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	},
+	[1] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(1), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(1), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	},
+	[2] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(2), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(2), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	},
+	[3] = {
+		SOC_ENUM_SINGLE(PEB2466_TG1(3), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt),
+		SOC_ENUM_SINGLE(PEB2466_TG2(3), 0, ARRAY_SIZE(peb2466_tone_freq_txt),
+				peb2466_tone_freq_txt)
+	}
+};
+
+static int peb2466_tg_freq_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+	switch (e->reg) {
+	case PEB2466_TG1(0):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[0].tg1_freq_item;
+		break;
+	case PEB2466_TG2(0):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[0].tg2_freq_item;
+		break;
+	case PEB2466_TG1(1):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[1].tg1_freq_item;
+		break;
+	case PEB2466_TG2(1):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[1].tg2_freq_item;
+		break;
+	case PEB2466_TG1(2):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[2].tg1_freq_item;
+		break;
+	case PEB2466_TG2(2):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[2].tg2_freq_item;
+		break;
+	case PEB2466_TG1(3):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[3].tg1_freq_item;
+		break;
+	case PEB2466_TG2(3):
+		ucontrol->value.enumerated.item[0] = peb2466->ch[3].tg2_freq_item;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int peb2466_tg_freq_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int *tg_freq_item;
+	u8 cr1_reg, cr1_mask;
+	unsigned int index;
+	int ret;
+
+	index = ucontrol->value.enumerated.item[0];
+
+	if (index >= ARRAY_SIZE(peb2466_tone_lookup))
+		return -EINVAL;
+
+	switch (e->reg) {
+	case PEB2466_TG1(0):
+		tg_freq_item = &peb2466->ch[0].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(0);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(0):
+		tg_freq_item = &peb2466->ch[0].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(0);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	case PEB2466_TG1(1):
+		tg_freq_item = &peb2466->ch[1].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(1);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(1):
+		tg_freq_item = &peb2466->ch[1].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(1);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	case PEB2466_TG1(2):
+		tg_freq_item = &peb2466->ch[2].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(2);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(2):
+		tg_freq_item = &peb2466->ch[2].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(2);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	case PEB2466_TG1(3):
+		tg_freq_item = &peb2466->ch[3].tg1_freq_item;
+		cr1_reg = PEB2466_CR1(3);
+		cr1_mask = PEB2466_CR1_PTG1;
+		break;
+	case PEB2466_TG2(3):
+		tg_freq_item = &peb2466->ch[3].tg2_freq_item;
+		cr1_reg = PEB2466_CR1(3);
+		cr1_mask = PEB2466_CR1_PTG2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (index == *tg_freq_item)
+		return 0;
+
+	if (index == PEB2466_TONE_1000HZ) {
+		ret = regmap_update_bits(peb2466->regmap, cr1_reg, cr1_mask, 0);
+		if (ret)
+			return ret;
+	} else {
+		ret = peb2466_write_buf(peb2466, e->reg, peb2466_tone_lookup[index], 4);
+		if (ret)
+			return ret;
+		ret = regmap_update_bits(peb2466->regmap, cr1_reg, cr1_mask, cr1_mask);
+		if (ret)
+			return ret;
+	}
+
+	*tg_freq_item = index;
+	return 1; /* The value changed */
+}
+
+static const struct snd_kcontrol_new peb2466_ch0_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(0), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(0), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(0), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_ch1_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(1), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(1), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(1), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_ch2_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(2), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(2), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(2), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_ch3_out_mix_controls[] = {
+	SOC_DAPM_SINGLE("TG1 Switch", PEB2466_CR1(3), 6, 1, 0),
+	SOC_DAPM_SINGLE("TG2 Switch", PEB2466_CR1(3), 7, 1, 0),
+	SOC_DAPM_SINGLE("Voice Switch", PEB2466_CR2(3), 0, 1, 0)
+};
+
+static const struct snd_kcontrol_new peb2466_controls[] = {
+	/* Attenuators */
+	SOC_SINGLE("DAC0 -6dB Playback Switch", PEB2466_CR3(0), 2, 1, 0),
+	SOC_SINGLE("DAC1 -6dB Playback Switch", PEB2466_CR3(1), 2, 1, 0),
+	SOC_SINGLE("DAC2 -6dB Playback Switch", PEB2466_CR3(2), 2, 1, 0),
+	SOC_SINGLE("DAC3 -6dB Playback Switch", PEB2466_CR3(3), 2, 1, 0),
+
+	/* Amplifiers */
+	SOC_SINGLE("ADC0 +6dB Capture Switch", PEB2466_CR3(0), 3, 1, 0),
+	SOC_SINGLE("ADC1 +6dB Capture Switch", PEB2466_CR3(1), 3, 1, 0),
+	SOC_SINGLE("ADC2 +6dB Capture Switch", PEB2466_CR3(2), 3, 1, 0),
+	SOC_SINGLE("ADC3 +6dB Capture Switch", PEB2466_CR3(3), 3, 1, 0),
+
+	/* Tone generators */
+	SOC_ENUM_EXT("DAC0 TG1 Freq", peb2466_tg_freq[0][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC1 TG1 Freq", peb2466_tg_freq[1][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC2 TG1 Freq", peb2466_tg_freq[2][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC3 TG1 Freq", peb2466_tg_freq[3][0],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+
+	SOC_ENUM_EXT("DAC0 TG2 Freq", peb2466_tg_freq[0][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC1 TG2 Freq", peb2466_tg_freq[1][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC2 TG2 Freq", peb2466_tg_freq[2][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+	SOC_ENUM_EXT("DAC3 TG2 Freq", peb2466_tg_freq[3][1],
+		     peb2466_tg_freq_get, peb2466_tg_freq_put),
+};
+
+static const struct snd_soc_dapm_widget peb2466_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("CH0 PWR", PEB2466_CR1(0), 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("CH1 PWR", PEB2466_CR1(1), 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("CH2 PWR", PEB2466_CR1(2), 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("CH3 PWR", PEB2466_CR1(3), 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("CH0 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("CH1 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("CH2 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("CH3 DIN", "Playback", SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_SIGGEN("CH0 TG1"),
+	SND_SOC_DAPM_SIGGEN("CH1 TG1"),
+	SND_SOC_DAPM_SIGGEN("CH2 TG1"),
+	SND_SOC_DAPM_SIGGEN("CH3 TG1"),
+
+	SND_SOC_DAPM_SIGGEN("CH0 TG2"),
+	SND_SOC_DAPM_SIGGEN("CH1 TG2"),
+	SND_SOC_DAPM_SIGGEN("CH2 TG2"),
+	SND_SOC_DAPM_SIGGEN("CH3 TG2"),
+
+	SND_SOC_DAPM_MIXER("DAC0 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch0_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch0_out_mix_controls)),
+	SND_SOC_DAPM_MIXER("DAC1 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch1_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch1_out_mix_controls)),
+	SND_SOC_DAPM_MIXER("DAC2 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch2_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch2_out_mix_controls)),
+	SND_SOC_DAPM_MIXER("DAC3 Mixer", SND_SOC_NOPM, 0, 0,
+			   peb2466_ch3_out_mix_controls,
+			   ARRAY_SIZE(peb2466_ch3_out_mix_controls)),
+
+	SND_SOC_DAPM_PGA("DAC0 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("DAC1 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("DAC2 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("DAC3 PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_OUTPUT("OUT0"),
+	SND_SOC_DAPM_OUTPUT("OUT1"),
+	SND_SOC_DAPM_OUTPUT("OUT2"),
+	SND_SOC_DAPM_OUTPUT("OUT3"),
+
+	SND_SOC_DAPM_INPUT("IN0"),
+	SND_SOC_DAPM_INPUT("IN1"),
+	SND_SOC_DAPM_INPUT("IN2"),
+	SND_SOC_DAPM_INPUT("IN3"),
+
+	SND_SOC_DAPM_DAC("ADC0", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ADC1", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ADC2", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ADC3", "Capture", SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route peb2466_dapm_routes[] = {
+	{ "CH0 DIN", NULL, "CH0 PWR" },
+	{ "CH1 DIN", NULL, "CH1 PWR" },
+	{ "CH2 DIN", NULL, "CH2 PWR" },
+	{ "CH3 DIN", NULL, "CH3 PWR" },
+
+	{ "CH0 TG1", NULL, "CH0 PWR" },
+	{ "CH1 TG1", NULL, "CH1 PWR" },
+	{ "CH2 TG1", NULL, "CH2 PWR" },
+	{ "CH3 TG1", NULL, "CH3 PWR" },
+
+	{ "CH0 TG2", NULL, "CH0 PWR" },
+	{ "CH1 TG2", NULL, "CH1 PWR" },
+	{ "CH2 TG2", NULL, "CH2 PWR" },
+	{ "CH3 TG2", NULL, "CH3 PWR" },
+
+	{ "DAC0 Mixer", "TG1 Switch", "CH0 TG1" },
+	{ "DAC0 Mixer", "TG2 Switch", "CH0 TG2" },
+	{ "DAC0 Mixer", "Voice Switch", "CH0 DIN" },
+	{ "DAC0 Mixer", NULL, "CH0 DIN" },
+
+	{ "DAC1 Mixer", "TG1 Switch", "CH1 TG1" },
+	{ "DAC1 Mixer", "TG2 Switch", "CH1 TG2" },
+	{ "DAC1 Mixer", "Voice Switch", "CH1 DIN" },
+	{ "DAC1 Mixer", NULL, "CH1 DIN" },
+
+	{ "DAC2 Mixer", "TG1 Switch", "CH2 TG1" },
+	{ "DAC2 Mixer", "TG2 Switch", "CH2 TG2" },
+	{ "DAC2 Mixer", "Voice Switch", "CH2 DIN" },
+	{ "DAC2 Mixer", NULL, "CH2 DIN" },
+
+	{ "DAC3 Mixer", "TG1 Switch", "CH3 TG1" },
+	{ "DAC3 Mixer", "TG2 Switch", "CH3 TG2" },
+	{ "DAC3 Mixer", "Voice Switch", "CH3 DIN" },
+	{ "DAC3 Mixer", NULL, "CH3 DIN" },
+
+	{ "DAC0 PGA", NULL, "DAC0 Mixer" },
+	{ "DAC1 PGA", NULL, "DAC1 Mixer" },
+	{ "DAC2 PGA", NULL, "DAC2 Mixer" },
+	{ "DAC3 PGA", NULL, "DAC3 Mixer" },
+
+	{ "OUT0", NULL, "DAC0 PGA" },
+	{ "OUT1", NULL, "DAC1 PGA" },
+	{ "OUT2", NULL, "DAC2 PGA" },
+	{ "OUT3", NULL, "DAC3 PGA" },
+
+	{ "ADC0", NULL, "IN0" },
+	{ "ADC1", NULL, "IN1" },
+	{ "ADC2", NULL, "IN2" },
+	{ "ADC3", NULL, "IN3" },
+
+	{ "ADC0", NULL, "CH0 PWR" },
+	{ "ADC1", NULL, "CH1 PWR" },
+	{ "ADC2", NULL, "CH2 PWR" },
+	{ "ADC3", NULL, "CH3 PWR" },
+};
+
+static int peb2466_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+				    unsigned int rx_mask, int slots, int width)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int chan;
+	unsigned int mask;
+	u8 slot;
+	int ret;
+
+	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;
+	chan = 0;
+	while (mask && chan < PEB2466_NB_CHANNEL) {
+		if (mask & 0x1) {
+			ret = regmap_write(peb2466->regmap, PEB2466_CR5(chan), slot);
+			if (ret) {
+				dev_err(dai->dev, "chan %d set tx tdm slot failed (%d)\n",
+					chan, ret);
+				return ret;
+			}
+			chan++;
+		}
+		mask >>= 1;
+		slot++;
+	}
+	if (mask) {
+		dev_err(dai->dev, "too much tx slots defined (mask = 0x%x) support max %d\n",
+			tx_mask, PEB2466_NB_CHANNEL);
+		return -EINVAL;
+	}
+	peb2466->max_chan_playback = chan;
+
+	mask = rx_mask;
+	slot = 0;
+	chan = 0;
+	while (mask && chan < PEB2466_NB_CHANNEL) {
+		if (mask & 0x1) {
+			ret = regmap_write(peb2466->regmap, PEB2466_CR4(chan), slot);
+			if (ret) {
+				dev_err(dai->dev, "chan %d set rx tdm slot failed (%d)\n",
+					chan, ret);
+				return ret;
+			}
+			chan++;
+		}
+		mask >>= 1;
+		slot++;
+	}
+	if (mask) {
+		dev_err(dai->dev, "too much rx slots defined (mask = 0x%x) support max %d\n",
+			rx_mask, PEB2466_NB_CHANNEL);
+		return -EINVAL;
+	}
+	peb2466->max_chan_capture = chan;
+
+	return 0;
+}
+
+static int peb2466_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	u8 xr6;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		xr6 = PEB2466_XR6_PCM_OFFSET(1);
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		xr6 = PEB2466_XR6_PCM_OFFSET(0);
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported format 0x%x\n",
+			fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+	return regmap_write(peb2466->regmap, PEB2466_XR6, xr6);
+}
+
+static int peb2466_dai_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int ch;
+	int ret;
+	u8 cr1;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		cr1 = PEB2466_CR1_LAW_MULAW;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		cr1 = PEB2466_CR1_LAW_ALAW;
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Unsupported format 0x%x\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	for (ch = 0; ch < PEB2466_NB_CHANNEL; ch++) {
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR1(ch),
+					 PEB2466_CR1_LAW_MASK, cr1);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const unsigned int peb2466_sample_bits[] = {8};
+
+static struct snd_pcm_hw_constraint_list peb2466_sample_bits_constr = {
+	.list = peb2466_sample_bits,
+	.count = ARRAY_SIZE(peb2466_sample_bits),
+};
+
+static int peb2466_dai_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int max_ch;
+	int ret;
+
+	max_ch = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		peb2466->max_chan_playback : peb2466->max_chan_capture;
+
+	/*
+	 * Disable stream support (min = 0, max = 0) if no timeslots were
+	 * 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;
+
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+					  SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					  &peb2466_sample_bits_constr);
+}
+
+static u64 peb2466_dai_formats[] = {
+	SND_SOC_POSSIBLE_DAIFMT_DSP_A	|
+	SND_SOC_POSSIBLE_DAIFMT_DSP_B,
+};
+
+static const struct snd_soc_dai_ops peb2466_dai_ops = {
+	.startup = peb2466_dai_startup,
+	.hw_params = peb2466_dai_hw_params,
+	.set_tdm_slot = peb2466_dai_set_tdm_slot,
+	.set_fmt = peb2466_dai_set_fmt,
+	.auto_selectable_formats     = peb2466_dai_formats,
+	.num_auto_selectable_formats = ARRAY_SIZE(peb2466_dai_formats),
+};
+
+static struct snd_soc_dai_driver peb2466_dai_driver = {
+	.name = "peb2466",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = PEB2466_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 = PEB2466_NB_CHANNEL,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
+	},
+	.ops = &peb2466_dai_ops,
+};
+
+static int peb2466_reset_audio(struct peb2466 *peb2466)
+{
+	static const struct reg_sequence reg_reset[] = {
+		{  .reg = PEB2466_XR6,    .def = 0x00 },
+
+		{  .reg = PEB2466_CR5(0), .def = 0x00 },
+		{  .reg = PEB2466_CR4(0), .def = 0x00 },
+		{  .reg = PEB2466_CR3(0), .def = 0x00 },
+		{  .reg = PEB2466_CR2(0), .def = 0x00 },
+		{  .reg = PEB2466_CR1(0), .def = 0x00 },
+		{  .reg = PEB2466_CR0(0), .def = PEB2466_CR0_IMR1 },
+
+		{  .reg = PEB2466_CR5(1), .def = 0x00 },
+		{  .reg = PEB2466_CR4(1), .def = 0x00 },
+		{  .reg = PEB2466_CR3(1), .def = 0x00 },
+		{  .reg = PEB2466_CR2(1), .def = 0x00 },
+		{  .reg = PEB2466_CR1(1), .def = 0x00 },
+		{  .reg = PEB2466_CR0(1), .def = PEB2466_CR0_IMR1 },
+
+		{  .reg = PEB2466_CR5(2), .def = 0x00 },
+		{  .reg = PEB2466_CR4(2), .def = 0x00 },
+		{  .reg = PEB2466_CR3(2), .def = 0x00 },
+		{  .reg = PEB2466_CR2(2), .def = 0x00 },
+		{  .reg = PEB2466_CR1(2), .def = 0x00 },
+		{  .reg = PEB2466_CR0(2), .def = PEB2466_CR0_IMR1 },
+
+		{  .reg = PEB2466_CR5(3), .def = 0x00 },
+		{  .reg = PEB2466_CR4(3), .def = 0x00 },
+		{  .reg = PEB2466_CR3(3), .def = 0x00 },
+		{  .reg = PEB2466_CR2(3), .def = 0x00 },
+		{  .reg = PEB2466_CR1(3), .def = 0x00 },
+		{  .reg = PEB2466_CR0(3), .def = PEB2466_CR0_IMR1 },
+	};
+	static const u8 imr1_p1[8] = {0x00, 0x90, 0x09, 0x00, 0x90, 0x09, 0x00, 0x00};
+	static const u8 imr1_p2[8] = {0x7F, 0xFF, 0x00, 0x00, 0x90, 0x14, 0x40, 0x08};
+	static const u8 zero[8] = {0};
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		peb2466->ch[i].tg1_freq_item = PEB2466_TONE_1000HZ;
+		peb2466->ch[i].tg2_freq_item = PEB2466_TONE_1000HZ;
+
+		/*
+		 * Even if not used, disabling IM/R1 filter is not recommended.
+		 * Instead, we must configure it with default coefficients and
+		 * enable it.
+		 * The filter will be enabled right after (in the following
+		 * regmap_multi_reg_write() call).
+		 */
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P1(i), imr1_p1, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P2(i), imr1_p2, 8);
+		if (ret)
+			return ret;
+
+		/* Set all other filters coefficients to zero */
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P1(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P2(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P3(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_FRX_FILTER(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_FRR_FILTER(i), zero, 8);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_AX_FILTER(i), zero, 4);
+		if (ret)
+			return ret;
+		ret = peb2466_write_buf(peb2466, PEB2466_AR_FILTER(i), zero, 4);
+		if (ret)
+			return ret;
+	}
+
+	return regmap_multi_reg_write(peb2466->regmap, reg_reset, ARRAY_SIZE(reg_reset));
+}
+
+static int peb2466_fw_parse_thfilter(struct snd_soc_component *component,
+				     u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw TH filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * TH_FILTER TLV data:
+	 *   - @0  1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1  8 bytes: TH-Filter coefficients part1
+	 *   - @9  8 bytes: TH-Filter coefficients part2
+	 *   - @17 8 bytes: TH-Filter coefficients part3
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_TH, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P1(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P2(i), data + 9, 8);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_TH_FILTER_P3(i), data + 17, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_TH | PEB2466_CR0_THSEL_MASK,
+					 PEB2466_CR0_TH | PEB2466_CR0_THSEL(i));
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_imr1filter(struct snd_soc_component *component,
+				       u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw IM/R1 filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * IMR1_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 8 bytes: IM/R1-Filter coefficients part1
+	 *   - @9 8 bytes: IM/R1-Filter coefficients part2
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_IMR1, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P1(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_IMR1_FILTER_P2(i), data + 9, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_IMR1, PEB2466_CR0_IMR1);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_frxfilter(struct snd_soc_component *component,
+				      u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw FRX filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * FRX_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 8 bytes: FRX-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRX, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_FRX_FILTER(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRX, PEB2466_CR0_FRX);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_frrfilter(struct snd_soc_component *component,
+				      u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw FRR filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * FRR_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 8 bytes: FRR-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRR, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_FRR_FILTER(i), data + 1, 8);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_FRR, PEB2466_CR0_FRR);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_axfilter(struct snd_soc_component *component,
+				     u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw AX filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * AX_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 4 bytes: AX-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AX_FILTER(i), data + 1, 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, PEB2466_CR0_AX);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int peb2466_fw_parse_arfilter(struct snd_soc_component *component,
+				     u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	u8 mask;
+	int ret;
+	int i;
+
+	dev_info(component->dev, "fw AR filter: mask %x, %*phN\n", *data,
+		 lng - 1, data + 1);
+
+	/*
+	 * AR_FILTER TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 4 bytes: AR-Filter coefficients
+	 */
+	mask = *data;
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AR_FILTER(i), data + 1, 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, PEB2466_CR0_AR);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static const char * const peb2466_ax_ctrl_names[] = {
+	"ADC0 Capture Volume",
+	"ADC1 Capture Volume",
+	"ADC2 Capture Volume",
+	"ADC3 Capture Volume",
+};
+
+static int peb2466_fw_parse_axtable(struct snd_soc_component *component,
+				    u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct peb2466_lkup_ctrl *lkup_ctrl;
+	struct peb2466_lookup *lookup;
+	u8 (*table)[4];
+	u32 table_size;
+	u32 init_index;
+	s32 min_val;
+	s32 step;
+	u8 mask;
+	int ret;
+	int i;
+
+	/*
+	 * AX_TABLE TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 32bits signed: Min table value in centi dB (MinVal)
+	 *                       ie -300 means -3.0 dB
+	 *   - @5 32bits signed: Step from on item to other item in centi dB (Step)
+	 *                       ie 25 means 0.25 dB)
+	 *   - @9 32bits unsigned: Item index in the table to use for the initial
+	 *                         value
+	 *   - @13 N*4 bytes: Table composed of 4 bytes items.
+	 *                    Each item correspond to an AX filter value.
+	 *
+	 * The conversion from raw value item in the table to/from the value in
+	 * dB is: Raw value at index i <-> (MinVal + i * Step) in centi dB.
+	 */
+
+	/* Check Lng and extract the table size. */
+	if (lng < 13 || ((lng - 13) % 4)) {
+		dev_err(component->dev, "fw AX table lng %u invalid\n", lng);
+		return -EINVAL;
+	}
+	table_size = lng - 13;
+
+	min_val = get_unaligned_be32(data + 1);
+	step = get_unaligned_be32(data + 5);
+	init_index = get_unaligned_be32(data + 9);
+	if (init_index >= (table_size / 4)) {
+		dev_err(component->dev, "fw AX table index %u out of table[%u]\n",
+			init_index, table_size / 4);
+		return -EINVAL;
+	}
+
+	dev_info(component->dev,
+		 "fw AX table: mask %x, min %d, step %d, %u items, tbl[%u] %*phN\n",
+		 *data, min_val, step, table_size / 4, init_index,
+		 4, data + 13 + (init_index * 4));
+
+	BUILD_BUG_ON(sizeof(*table) != 4);
+	table = devm_kzalloc(&peb2466->spi->dev, table_size, GFP_KERNEL);
+	if (!table)
+		return -ENOMEM;
+	memcpy(table, data + 13, table_size);
+
+	mask = *data;
+	BUILD_BUG_ON(ARRAY_SIZE(peb2466_ax_ctrl_names) != ARRAY_SIZE(peb2466->ch));
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		lookup = &peb2466->ch[i].ax_lookup;
+		lookup->table = table;
+		lookup->count = table_size / 4;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AX_FILTER(i),
+					lookup->table[init_index], 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AX, PEB2466_CR0_AX);
+		if (ret)
+			return ret;
+
+		lkup_ctrl = &peb2466->ch[i].ax_lkup_ctrl;
+		lkup_ctrl->lookup = lookup;
+		lkup_ctrl->reg = PEB2466_AX_FILTER(i);
+		lkup_ctrl->index = init_index;
+
+		ret = peb2466_add_lkup_ctrl(component, lkup_ctrl,
+					    peb2466_ax_ctrl_names[i],
+					    min_val, step);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static const char * const peb2466_ar_ctrl_names[] = {
+	"DAC0 Playback Volume",
+	"DAC1 Playback Volume",
+	"DAC2 Playback Volume",
+	"DAC3 Playback Volume",
+};
+
+static int peb2466_fw_parse_artable(struct snd_soc_component *component,
+				    u16 tag, u32 lng, const u8 *data)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	struct peb2466_lkup_ctrl *lkup_ctrl;
+	struct peb2466_lookup *lookup;
+	u8 (*table)[4];
+	u32 table_size;
+	u32 init_index;
+	s32 min_val;
+	s32 step;
+	u8 mask;
+	int ret;
+	int i;
+
+	/*
+	 * AR_TABLE TLV data:
+	 *   - @0 1 byte:  Chan mask (bit set means related channel is concerned)
+	 *   - @1 32bits signed: Min table value in centi dB (MinVal)
+	 *                       ie -300 means -3.0 dB
+	 *   - @5 32bits signed: Step from on item to other item in centi dB (Step)
+	 *                       ie 25 means 0.25 dB)
+	 *   - @9 32bits unsigned: Item index in the table to use for the initial
+	 *                         value
+	 *   - @13 N*4 bytes: Table composed of 4 bytes items.
+	 *                    Each item correspond to an AR filter value.
+	 *
+	 * The conversion from raw value item in the table to/from the value in
+	 * dB is: Raw value at index i <-> (MinVal + i * Step) in centi dB.
+	 */
+
+	/* Check Lng and extract the table size. */
+	if (lng < 13 || ((lng - 13) % 4)) {
+		dev_err(component->dev, "fw AR table lng %u invalid\n", lng);
+		return -EINVAL;
+	}
+	table_size = lng - 13;
+
+	min_val = get_unaligned_be32(data + 1);
+	step = get_unaligned_be32(data + 5);
+	init_index = get_unaligned_be32(data + 9);
+	if (init_index >= (table_size / 4)) {
+		dev_err(component->dev, "fw AR table index %u out of table[%u]\n",
+			init_index, table_size / 4);
+		return -EINVAL;
+	}
+
+	dev_info(component->dev,
+		 "fw AR table: mask %x, min %d, step %d, %u items, tbl[%u] %*phN\n",
+		 *data, min_val, step, table_size / 4, init_index,
+		 4, data + 13 + (init_index * 4));
+
+	BUILD_BUG_ON(sizeof(*table) != 4);
+	table = devm_kzalloc(&peb2466->spi->dev, table_size, GFP_KERNEL);
+	if (!table)
+		return -ENOMEM;
+	memcpy(table, data + 13, table_size);
+
+	mask = *data;
+	BUILD_BUG_ON(ARRAY_SIZE(peb2466_ar_ctrl_names) != ARRAY_SIZE(peb2466->ch));
+	for (i = 0; i < ARRAY_SIZE(peb2466->ch); i++) {
+		if (!(mask & (1 << i)))
+			continue;
+
+		lookup = &peb2466->ch[i].ar_lookup;
+		lookup->table = table;
+		lookup->count = table_size / 4;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, 0);
+		if (ret)
+			return ret;
+
+		ret = peb2466_write_buf(peb2466, PEB2466_AR_FILTER(i),
+					lookup->table[init_index], 4);
+		if (ret)
+			return ret;
+
+		ret = regmap_update_bits(peb2466->regmap, PEB2466_CR0(i),
+					 PEB2466_CR0_AR, PEB2466_CR0_AR);
+		if (ret)
+			return ret;
+
+		lkup_ctrl = &peb2466->ch[i].ar_lkup_ctrl;
+		lkup_ctrl->lookup = lookup;
+		lkup_ctrl->reg = PEB2466_AR_FILTER(i);
+		lkup_ctrl->index = init_index;
+
+		ret = peb2466_add_lkup_ctrl(component, lkup_ctrl,
+					    peb2466_ar_ctrl_names[i],
+					    min_val, step);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+struct peb2466_fw_tag_def {
+	u16 tag;
+	u32 lng_min;
+	u32 lng_max;
+	int (*parse)(struct snd_soc_component *component,
+		     u16 tag, u32 lng, const u8 *data);
+};
+
+#define PEB2466_TAG_DEF_LNG_EQ(__tag, __lng, __parse) { \
+	.tag = __tag,		\
+	.lng_min = __lng,	\
+	.lng_max = __lng,	\
+	.parse = __parse,	\
+}
+
+#define PEB2466_TAG_DEF_LNG_MIN(__tag, __lng_min, __parse) { \
+	.tag = __tag,		\
+	.lng_min = __lng_min,	\
+	.lng_max = U32_MAX,	\
+	.parse = __parse,	\
+}
+
+static const struct peb2466_fw_tag_def peb2466_fw_tag_defs[] = {
+	/* TH FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0001, 1 + 3 * 8, peb2466_fw_parse_thfilter),
+	/* IMR1 FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0002, 1 + 2 * 8, peb2466_fw_parse_imr1filter),
+	/* FRX FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0003, 1 + 8, peb2466_fw_parse_frxfilter),
+	/* FRR FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0004, 1 + 8, peb2466_fw_parse_frrfilter),
+	/* AX FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0005, 1 + 4, peb2466_fw_parse_axfilter),
+	/* AR FILTER */
+	PEB2466_TAG_DEF_LNG_EQ(0x0006, 1 + 4, peb2466_fw_parse_arfilter),
+	/* AX TABLE */
+	PEB2466_TAG_DEF_LNG_MIN(0x0105, 1 + 3 * 4, peb2466_fw_parse_axtable),
+	/* AR TABLE */
+	PEB2466_TAG_DEF_LNG_MIN(0x0106, 1 + 3 * 4, peb2466_fw_parse_artable),
+};
+
+static const struct peb2466_fw_tag_def *peb2466_fw_get_tag_def(u16 tag)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(peb2466_fw_tag_defs); i++) {
+		if (peb2466_fw_tag_defs[i].tag == tag)
+			return &peb2466_fw_tag_defs[i];
+	}
+	return NULL;
+}
+
+static int peb2466_fw_parse(struct snd_soc_component *component,
+			    const u8 *data, size_t size)
+{
+	const struct peb2466_fw_tag_def *tag_def;
+	size_t left;
+	const u8 *buf;
+	u16 val16;
+	u16 tag;
+	u32 lng;
+	int ret;
+
+	/*
+	 * Coefficients firmware binary structure (16bits and 32bits are
+	 * big-endian values).
+	 *
+	 * @0, 16bits: Magic (0x2466)
+	 * @2, 16bits: Version (0x0100 for version 1.0)
+	 * @4, 2+4+N bytes: TLV block
+	 * @4+(2+4+N) bytes: Next TLV block
+	 * ...
+	 *
+	 * Detail of a TLV block:
+	 *   @0, 16bits: Tag
+	 *   @2, 32bits: Lng
+	 *   @6, lng bytes: Data
+	 *
+	 * The detail the Data for a given TLV Tag is provided in the related
+	 * parser.
+	 */
+
+	left = size;
+	buf = data;
+
+	if (left < 4) {
+		dev_err(component->dev, "fw size %zu, exp at least 4\n", left);
+		return -EINVAL;
+	}
+
+	/* Check magic */
+	val16 = get_unaligned_be16(buf);
+	if (val16 != 0x2466) {
+		dev_err(component->dev, "fw magic 0x%04x exp 0x2466\n", val16);
+		return -EINVAL;
+	}
+	buf += 2;
+	left -= 2;
+
+	/* Check version */
+	val16 = get_unaligned_be16(buf);
+	if (val16 != 0x0100) {
+		dev_err(component->dev, "fw magic 0x%04x exp 0x0100\n", val16);
+		return -EINVAL;
+	}
+	buf += 2;
+	left -= 2;
+
+	while (left) {
+		if (left < 6) {
+			dev_err(component->dev, "fw %td/%zu left %zu, exp at least 6\n",
+				buf - data, size, left);
+			return -EINVAL;
+		}
+		/* Check tag and lng */
+		tag = get_unaligned_be16(buf);
+		lng = get_unaligned_be32(buf + 2);
+		tag_def = peb2466_fw_get_tag_def(tag);
+		if (!tag_def) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x unknown\n",
+				buf - data, size, tag);
+			return -EINVAL;
+		}
+		if (lng < tag_def->lng_min || lng > tag_def->lng_max) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x lng %u, exp [%u;%u]\n",
+				buf - data, size, tag, lng, tag_def->lng_min, tag_def->lng_max);
+			return -EINVAL;
+		}
+		buf += 6;
+		left -= 6;
+		if (left < lng) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x lng %u, left %zu\n",
+				buf - data, size, tag, lng, left);
+			return -EINVAL;
+		}
+
+		/* TLV block is valid -> parse the data part */
+		ret = tag_def->parse(component, tag, lng, buf);
+		if (ret) {
+			dev_err(component->dev, "fw %td/%zu tag 0x%04x lng %u parse failed\n",
+				buf - data, size, tag, lng);
+			return ret;
+		}
+
+		buf += lng;
+		left -= lng;
+	}
+	return 0;
+}
+
+static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name)
+{
+	const struct firmware *fw;
+	int ret;
+
+	ret = request_firmware(&fw, fw_name, component->dev);
+	if (ret)
+		return ret;
+
+	ret = peb2466_fw_parse(component, fw->data, fw->size);
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int peb2466_component_probe(struct snd_soc_component *component)
+{
+	struct peb2466 *peb2466 = snd_soc_component_get_drvdata(component);
+	const char *firmware_name;
+	int ret;
+
+	/* reset peb2466 audio part */
+	ret = peb2466_reset_audio(peb2466);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_string(peb2466->spi->dev.of_node,
+				      "firmware-name", &firmware_name);
+	if (ret)
+		return (ret == -EINVAL) ? 0 : ret;
+
+	return peb2466_load_coeffs(component, firmware_name);
+}
+
+static const struct snd_soc_component_driver peb2466_component_driver = {
+	.probe			= peb2466_component_probe,
+	.controls		= peb2466_controls,
+	.num_controls		= ARRAY_SIZE(peb2466_controls),
+	.dapm_widgets		= peb2466_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(peb2466_dapm_widgets),
+	.dapm_routes		= peb2466_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(peb2466_dapm_routes),
+	.endianness		= 1,
+};
+
+/*
+ * The mapping used for the relationship between the gpio offset and the
+ * physical pin is the following:
+ *
+ * offset     pin
+ *      0     SI1_0
+ *      1     SI1_1
+ *      2     SI2_0
+ *      3     SI2_1
+ *      4     SI3_0
+ *      5     SI3_1
+ *      6     SI4_0
+ *      7     SI4_1
+ *      8     SO1_0
+ *      9     SO1_1
+ *     10     SO2_0
+ *     11     SO2_1
+ *     12     SO3_0
+ *     13     SO3_1
+ *     14     SO4_0
+ *     15     SO4_1
+ *     16     SB1_0
+ *     17     SB1_1
+ *     18     SB2_0
+ *     19     SB2_1
+ *     20     SB3_0
+ *     21     SB3_1
+ *     22     SB4_0
+ *     23     SB4_1
+ *     24     SB1_2
+ *     25     SB2_2
+ *     26     SB3_2
+ *     27     SB4_2
+ */
+
+static int peb2466_chip_gpio_offset_to_data_regmask(unsigned int offset,
+						    unsigned int *xr_reg,
+						    unsigned int *mask)
+{
+	if (offset < 16) {
+		/*
+		 * SIx_{0,1} and SOx_{0,1}
+		 *   Read accesses read SIx_{0,1} values
+		 *   Write accesses write SOx_{0,1} values
+		 */
+		*xr_reg = PEB2466_XR0;
+		*mask = (1 << (offset % 8));
+		return 0;
+	}
+	if (offset < 24) {
+		/* SBx_{0,1} */
+		*xr_reg = PEB2466_XR1;
+		*mask = (1 << (offset - 16));
+		return 0;
+	}
+	if (offset < 28) {
+		/* SBx_2 */
+		*xr_reg = PEB2466_XR3;
+		*mask = (1 << (offset - 24 + 4));
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int peb2466_chip_gpio_offset_to_dir_regmask(unsigned int offset,
+						   unsigned int *xr_reg,
+						   unsigned int *mask)
+{
+	if (offset < 16) {
+		/* Direction cannot be changed for these GPIOs */
+		return -EINVAL;
+	}
+	if (offset < 24) {
+		*xr_reg = PEB2466_XR2;
+		*mask = (1 << (offset - 16));
+		return 0;
+	}
+	if (offset < 28) {
+		*xr_reg = PEB2466_XR3;
+		*mask = (1 << (offset - 24));
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static unsigned int *peb2466_chip_gpio_get_cache(struct peb2466 *peb2466,
+						 unsigned int xr_reg)
+{
+	unsigned int *cache;
+
+	switch (xr_reg) {
+	case PEB2466_XR0:
+		cache = &peb2466->gpio.cache.xr0;
+		break;
+	case PEB2466_XR1:
+		cache = &peb2466->gpio.cache.xr1;
+		break;
+	case PEB2466_XR2:
+		cache = &peb2466->gpio.cache.xr2;
+		break;
+	case PEB2466_XR3:
+		cache = &peb2466->gpio.cache.xr3;
+		break;
+	default:
+		cache = NULL;
+		break;
+	}
+	return cache;
+}
+
+static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int xr_reg,
+					 unsigned int mask, unsigned int val)
+{
+	unsigned int tmp;
+	unsigned int *cache;
+	int ret;
+
+	/*
+	 * Read and write accesses use different peb2466 internal signals (input
+	 * signals on reads and output signals on writes). regmap_update_bits
+	 * cannot be used to read/modify/write the value.
+	 * So, a specific cache value is used.
+	 */
+
+	mutex_lock(&peb2466->gpio.lock);
+
+	cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg);
+	if (!cache) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	tmp = *cache;
+	tmp &= ~mask;
+	tmp |= val;
+
+	ret = regmap_write(peb2466->regmap, xr_reg, tmp);
+	if (ret)
+		goto end;
+
+	*cache = tmp;
+	ret = 0;
+
+end:
+	mutex_unlock(&peb2466->gpio.lock);
+	return ret;
+}
+
+static void peb2466_chip_gpio_set(struct gpio_chip *c, unsigned int offset, int val)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	int ret;
+
+	if (offset < 8) {
+		/*
+		 * SIx_{0,1} signals cannot be set and writing the related
+		 * register will change the SOx_{0,1} signals
+		 */
+		dev_warn(&peb2466->spi->dev, "cannot set gpio %d (read-only)\n",
+			 offset);
+		return;
+	}
+
+	ret = peb2466_chip_gpio_offset_to_data_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot set gpio %d (%d)\n",
+			offset, ret);
+		return;
+	}
+
+	ret = peb2466_chip_gpio_update_bits(peb2466, xr_reg, mask, val ? mask : 0);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "set gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+	}
+}
+
+static int peb2466_chip_gpio_get(struct gpio_chip *c, unsigned int offset)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	bool use_cache = false;
+	unsigned int *cache;
+	unsigned int xr_reg;
+	unsigned int mask;
+	unsigned int val;
+	int ret;
+
+	if (offset >= 8 && offset < 16) {
+		/*
+		 * SOx_{0,1} signals cannot be read. Reading the related
+		 * register will read the SIx_{0,1} signals.
+		 * Use the cache to get value;
+		 */
+		use_cache = true;
+	}
+
+	ret = peb2466_chip_gpio_offset_to_data_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot get gpio %d (%d)\n",
+			offset, ret);
+		return -EINVAL;
+	}
+
+	if (use_cache) {
+		cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg);
+		if (!cache)
+			return -EINVAL;
+		val = *cache;
+	} else {
+		ret = regmap_read(peb2466->regmap, xr_reg, &val);
+		if (ret) {
+			dev_err(&peb2466->spi->dev, "get gpio %d (0x%x, 0x%x) failed (%d)\n",
+				offset, xr_reg, mask, ret);
+			return ret;
+		}
+	}
+
+	return !!(val & mask);
+}
+
+static int peb2466_chip_get_direction(struct gpio_chip *c, unsigned int offset)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	unsigned int val;
+	int ret;
+
+	if (offset < 8) {
+		/* SIx_{0,1} */
+		return GPIO_LINE_DIRECTION_IN;
+	}
+	if (offset < 16) {
+		/* SOx_{0,1} */
+		return GPIO_LINE_DIRECTION_OUT;
+	}
+
+	ret = peb2466_chip_gpio_offset_to_dir_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot get gpio %d direction (%d)\n",
+			offset, ret);
+		return ret;
+	}
+
+	ret = regmap_read(peb2466->regmap, xr_reg, &val);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "get dir gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+		return ret;
+	}
+
+	return val & mask ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int peb2466_chip_direction_input(struct gpio_chip *c, unsigned int offset)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	int ret;
+
+	if (offset < 8) {
+		/* SIx_{0,1} */
+		return 0;
+	}
+	if (offset < 16) {
+		/* SOx_{0,1} */
+		return -EINVAL;
+	};
+
+	ret = peb2466_chip_gpio_offset_to_dir_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot set gpio %d direction (%d)\n",
+			offset, ret);
+		return ret;
+	}
+
+	ret = peb2466_chip_gpio_update_bits(peb2466, xr_reg, mask, 0);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "Set dir in gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int peb2466_chip_direction_output(struct gpio_chip *c, unsigned int offset, int val)
+{
+	struct peb2466 *peb2466 = gpiochip_get_data(c);
+	unsigned int xr_reg;
+	unsigned int mask;
+	int ret;
+
+	if (offset < 8) {
+		/* SIx_{0,1} */
+		return -EINVAL;
+	}
+
+	peb2466_chip_gpio_set(c, offset, val);
+
+	if (offset < 16) {
+		/* SOx_{0,1} */
+		return 0;
+	};
+
+	ret = peb2466_chip_gpio_offset_to_dir_regmask(offset, &xr_reg, &mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "cannot set gpio %d direction (%d)\n",
+			offset, ret);
+		return ret;
+	}
+
+	ret = peb2466_chip_gpio_update_bits(peb2466, xr_reg, mask, mask);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "Set dir in gpio %d (0x%x, 0x%x) failed (%d)\n",
+			offset, xr_reg, mask, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int peb2466_reset_gpio(struct peb2466 *peb2466)
+{
+	static const struct reg_sequence reg_reset[] = {
+		/* Output pins at 0, input/output pins as input */
+		{  .reg = PEB2466_XR0, .def = 0 },
+		{  .reg = PEB2466_XR1, .def = 0 },
+		{  .reg = PEB2466_XR2, .def = 0 },
+		{  .reg = PEB2466_XR3, .def = 0 },
+	};
+
+	peb2466->gpio.cache.xr0 = 0;
+	peb2466->gpio.cache.xr1 = 0;
+	peb2466->gpio.cache.xr2 = 0;
+	peb2466->gpio.cache.xr3 = 0;
+
+	return regmap_multi_reg_write(peb2466->regmap, reg_reset, ARRAY_SIZE(reg_reset));
+}
+
+static int peb2466_gpio_init(struct peb2466 *peb2466)
+{
+	int ret;
+
+	mutex_init(&peb2466->gpio.lock);
+
+	ret = peb2466_reset_gpio(peb2466);
+	if (ret)
+		return ret;
+
+	peb2466->gpio.gpio_chip.owner = THIS_MODULE;
+	peb2466->gpio.gpio_chip.label = dev_name(&peb2466->spi->dev);
+	peb2466->gpio.gpio_chip.parent = &peb2466->spi->dev;
+	peb2466->gpio.gpio_chip.base = -1;
+	peb2466->gpio.gpio_chip.ngpio = 28;
+	peb2466->gpio.gpio_chip.get_direction = peb2466_chip_get_direction;
+	peb2466->gpio.gpio_chip.direction_input = peb2466_chip_direction_input;
+	peb2466->gpio.gpio_chip.direction_output = peb2466_chip_direction_output;
+	peb2466->gpio.gpio_chip.get = peb2466_chip_gpio_get;
+	peb2466->gpio.gpio_chip.set = peb2466_chip_gpio_set;
+	peb2466->gpio.gpio_chip.can_sleep = true;
+
+	return devm_gpiochip_add_data(&peb2466->spi->dev, &peb2466->gpio.gpio_chip,
+				      peb2466);
+}
+
+static int peb2466_spi_probe(struct spi_device *spi)
+{
+	struct peb2466 *peb2466;
+	unsigned long mclk_rate;
+	int ret;
+	u8 xr5;
+
+	spi->bits_per_word = 8;
+	ret = spi_setup(spi);
+	if (ret < 0)
+		return ret;
+
+	peb2466 = devm_kzalloc(&spi->dev, sizeof(*peb2466), GFP_KERNEL);
+	if (!peb2466)
+		return -ENOMEM;
+
+	peb2466->spi = spi;
+
+	peb2466->regmap = devm_regmap_init(&peb2466->spi->dev, NULL, peb2466,
+					   &peb2466_regmap_config);
+	if (IS_ERR(peb2466->regmap))
+		return PTR_ERR(peb2466->regmap);
+
+	peb2466->reset_gpio = devm_gpiod_get_optional(&peb2466->spi->dev,
+						      "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(peb2466->reset_gpio))
+		return PTR_ERR(peb2466->reset_gpio);
+
+	peb2466->mclk = devm_clk_get(&peb2466->spi->dev, "mclk");
+	if (IS_ERR(peb2466->mclk))
+		return PTR_ERR(peb2466->mclk);
+	ret = clk_prepare_enable(peb2466->mclk);
+	if (ret)
+		return ret;
+
+	if (peb2466->reset_gpio) {
+		gpiod_set_value_cansleep(peb2466->reset_gpio, 1);
+		udelay(4);
+		gpiod_set_value_cansleep(peb2466->reset_gpio, 0);
+		udelay(4);
+	}
+
+	spi_set_drvdata(spi, peb2466);
+
+	mclk_rate = clk_get_rate(peb2466->mclk);
+	switch (mclk_rate) {
+	case 1536000:
+		xr5 = PEB2466_XR5_MCLK_1536;
+		break;
+	case 2048000:
+		xr5 = PEB2466_XR5_MCLK_2048;
+		break;
+	case 4096000:
+		xr5 = PEB2466_XR5_MCLK_4096;
+		break;
+	case 8192000:
+		xr5 = PEB2466_XR5_MCLK_8192;
+		break;
+	default:
+		dev_err(&peb2466->spi->dev, "Unsupported clock rate %lu\n",
+			mclk_rate);
+		ret = -EINVAL;
+		goto failed;
+	}
+	ret = regmap_write(peb2466->regmap, PEB2466_XR5, xr5);
+	if (ret) {
+		dev_err(&peb2466->spi->dev, "Setting MCLK failed (%d)\n", ret);
+		goto failed;
+	}
+
+	ret = devm_snd_soc_register_component(&spi->dev, &peb2466_component_driver,
+					      &peb2466_dai_driver, 1);
+	if (ret)
+		goto failed;
+
+	if (IS_ENABLED(CONFIG_GPIOLIB)) {
+		ret = peb2466_gpio_init(peb2466);
+		if (ret)
+			goto failed;
+	}
+
+	return 0;
+
+failed:
+	clk_disable_unprepare(peb2466->mclk);
+	return ret;
+}
+
+static void peb2466_spi_remove(struct spi_device *spi)
+{
+	struct peb2466 *peb2466 = spi_get_drvdata(spi);
+
+	clk_disable_unprepare(peb2466->mclk);
+}
+
+static const struct of_device_id peb2466_of_match[] = {
+	{ .compatible = "infineon,peb2466", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, peb2466_of_match);
+
+static const struct spi_device_id peb2466_id_table[] = {
+	{ "peb2466", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, peb2466_id_table);
+
+static struct spi_driver peb2466_spi_driver = {
+	.driver  = {
+		.name   = "peb2466",
+		.of_match_table = peb2466_of_match,
+	},
+	.id_table = peb2466_id_table,
+	.probe  = peb2466_spi_probe,
+	.remove = peb2466_spi_remove,
+};
+
+module_spi_driver(peb2466_spi_driver);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("PEB2466 ALSA SoC driver");
+MODULE_LICENSE("GPL");
-- 
2.39.1


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

* [PATCH 3/3] MAINTAINERS: add the Infineon PEB2466 codec entry
  2023-02-06 14:49 ` Herve Codina
@ 2023-02-06 14:49   ` Herve Codina
  -1 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 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
Infineon PEB2466 codec.

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

diff --git a/MAINTAINERS b/MAINTAINERS
index cd22e8e06561..0cd8a4d1f7b0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10184,6 +10184,13 @@ L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	drivers/iio/pressure/dps310.c
 
+INFINEON PEB2466 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/infineon,peb2466.yaml
+F:	sound/soc/codecs/peb2466.c
+
 INFINIBAND SUBSYSTEM
 M:	Jason Gunthorpe <jgg@nvidia.com>
 M:	Leon Romanovsky <leonro@nvidia.com>
-- 
2.39.1


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

* [PATCH 3/3] MAINTAINERS: add the Infineon PEB2466 codec entry
@ 2023-02-06 14:49   ` Herve Codina
  0 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-06 14:49 UTC (permalink / raw)
  To: Herve Codina, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Linus Walleij, Bartosz Golaszewski,
	Jaroslav Kysela, Takashi Iwai
  Cc: devicetree, alsa-devel, linux-kernel, Christophe Leroy,
	linux-gpio, Thomas Petazzoni

After contributing the driver, add myself as the maintainer for the
Infineon PEB2466 codec.

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

diff --git a/MAINTAINERS b/MAINTAINERS
index cd22e8e06561..0cd8a4d1f7b0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10184,6 +10184,13 @@ L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	drivers/iio/pressure/dps310.c
 
+INFINEON PEB2466 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/infineon,peb2466.yaml
+F:	sound/soc/codecs/peb2466.c
+
 INFINIBAND SUBSYSTEM
 M:	Jason Gunthorpe <jgg@nvidia.com>
 M:	Leon Romanovsky <leonro@nvidia.com>
-- 
2.39.1


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

* Re: [PATCH 1/3] dt-bindings: sound: Add the Infineon PEB2466 codec
  2023-02-06 14:49   ` Herve Codina
@ 2023-02-07 21:06     ` Rob Herring
  -1 siblings, 0 replies; 14+ messages in thread
From: Rob Herring @ 2023-02-07 21:06 UTC (permalink / raw)
  To: Herve Codina
  Cc: Bartosz Golaszewski, linux-kernel, Takashi Iwai, linux-gpio,
	Rob Herring, Thomas Petazzoni, Jaroslav Kysela,
	Krzysztof Kozlowski, devicetree, Linus Walleij, alsa-devel,
	Mark Brown, Christophe Leroy, Liam Girdwood


On Mon, 06 Feb 2023 15:49:02 +0100, Herve Codina wrote:
> The Infineon PEB2466 codec is a programmable DSP-based four channels
> codec with filters capabilities.
> 
> Signed-off-by: Herve Codina <herve.codina@bootlin.com>
> ---
>  .../bindings/sound/infineon,peb2466.yaml      | 91 +++++++++++++++++++
>  1 file changed, 91 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
> 

Reviewed-by: Rob Herring <robh@kernel.org>


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

* Re: [PATCH 1/3] dt-bindings: sound: Add the Infineon PEB2466 codec
@ 2023-02-07 21:06     ` Rob Herring
  0 siblings, 0 replies; 14+ messages in thread
From: Rob Herring @ 2023-02-07 21:06 UTC (permalink / raw)
  To: Herve Codina
  Cc: Bartosz Golaszewski, linux-kernel, Takashi Iwai, linux-gpio,
	Rob Herring, Thomas Petazzoni, Krzysztof Kozlowski, devicetree,
	Linus Walleij, alsa-devel, Mark Brown, Christophe Leroy,
	Liam Girdwood


On Mon, 06 Feb 2023 15:49:02 +0100, Herve Codina wrote:
> The Infineon PEB2466 codec is a programmable DSP-based four channels
> codec with filters capabilities.
> 
> Signed-off-by: Herve Codina <herve.codina@bootlin.com>
> ---
>  .../bindings/sound/infineon,peb2466.yaml      | 91 +++++++++++++++++++
>  1 file changed, 91 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/infineon,peb2466.yaml
> 

Reviewed-by: Rob Herring <robh@kernel.org>

_______________________________________________
Alsa-devel mailing list -- alsa-devel@alsa-project.org
To unsubscribe send an email to alsa-devel-leave@alsa-project.org

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

* Re: [PATCH 2/3] ASoC: codecs: Add support for the Infineon PEB2466 codec
  2023-02-06 14:49   ` Herve Codina
  (?)
@ 2023-02-07 21:17   ` Christophe JAILLET
  2023-02-08  7:10       ` Herve Codina
  -1 siblings, 1 reply; 14+ messages in thread
From: Christophe JAILLET @ 2023-02-07 21:17 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

Le 06/02/2023 à 15:49, Herve Codina a écrit :
> The Infineon PEB2466 codec is a programmable DSP-based four channels
> codec with filters capabilities.
> It also provides signals as GPIOs.
> 
> Signed-off-by: Herve Codina <herve.codina@bootlin.com>
> ---
>   sound/soc/codecs/Kconfig   |   12 +
>   sound/soc/codecs/Makefile  |    2 +
>   sound/soc/codecs/peb2466.c | 2071 ++++++++++++++++++++++++++++++++++++
>   3 files changed, 2085 insertions(+)
>   create mode 100644 sound/soc/codecs/peb2466.c
> 

[...]

> +static int peb2466_spi_probe(struct spi_device *spi)
> +{
> +	struct peb2466 *peb2466;
> +	unsigned long mclk_rate;
> +	int ret;
> +	u8 xr5;
> +
> +	spi->bits_per_word = 8;
> +	ret = spi_setup(spi);
> +	if (ret < 0)
> +		return ret;
> +
> +	peb2466 = devm_kzalloc(&spi->dev, sizeof(*peb2466), GFP_KERNEL);
> +	if (!peb2466)
> +		return -ENOMEM;
> +
> +	peb2466->spi = spi;
> +
> +	peb2466->regmap = devm_regmap_init(&peb2466->spi->dev, NULL, peb2466,
> +					   &peb2466_regmap_config);
> +	if (IS_ERR(peb2466->regmap))
> +		return PTR_ERR(peb2466->regmap);
> +
> +	peb2466->reset_gpio = devm_gpiod_get_optional(&peb2466->spi->dev,
> +						      "reset", GPIOD_OUT_LOW);
> +	if (IS_ERR(peb2466->reset_gpio))
> +		return PTR_ERR(peb2466->reset_gpio);
> +
> +	peb2466->mclk = devm_clk_get(&peb2466->spi->dev, "mclk");

Hi,

Up to you to decide if it is a good idea or not, but using 
devm_clk_get_enabled() would save the 'mclk' field in peb2466 ...

> +	if (IS_ERR(peb2466->mclk))
> +		return PTR_ERR(peb2466->mclk);
> +	ret = clk_prepare_enable(peb2466->mclk);
> +	if (ret)
> +		return ret;

... these 3 lines ...

> +
> +	if (peb2466->reset_gpio) {
> +		gpiod_set_value_cansleep(peb2466->reset_gpio, 1);
> +		udelay(4);
> +		gpiod_set_value_cansleep(peb2466->reset_gpio, 0);
> +		udelay(4);
> +	}
> +
> +	spi_set_drvdata(spi, peb2466);

... this spi_set_drvdata() call ...

> +
> +	mclk_rate = clk_get_rate(peb2466->mclk);
> +	switch (mclk_rate) {
> +	case 1536000:
> +		xr5 = PEB2466_XR5_MCLK_1536;
> +		break;
> +	case 2048000:
> +		xr5 = PEB2466_XR5_MCLK_2048;
> +		break;
> +	case 4096000:
> +		xr5 = PEB2466_XR5_MCLK_4096;
> +		break;
> +	case 8192000:
> +		xr5 = PEB2466_XR5_MCLK_8192;
> +		break;
> +	default:
> +		dev_err(&peb2466->spi->dev, "Unsupported clock rate %lu\n",
> +			mclk_rate);
> +		ret = -EINVAL;
> +		goto failed;
> +	}
> +	ret = regmap_write(peb2466->regmap, PEB2466_XR5, xr5);
> +	if (ret) {
> +		dev_err(&peb2466->spi->dev, "Setting MCLK failed (%d)\n", ret);
> +		goto failed;
> +	}
> +
> +	ret = devm_snd_soc_register_component(&spi->dev, &peb2466_component_driver,
> +					      &peb2466_dai_driver, 1);
> +	if (ret)
> +		goto failed;
> +
> +	if (IS_ENABLED(CONFIG_GPIOLIB)) {
> +		ret = peb2466_gpio_init(peb2466);
> +		if (ret)
> +			goto failed;
> +	}
> +
> +	return 0;
> +
> +failed:
> +	clk_disable_unprepare(peb2466->mclk);
> +	return ret;

... this error handling path ...

> +}
> +
> +static void peb2466_spi_remove(struct spi_device *spi)
> +{
> +	struct peb2466 *peb2466 = spi_get_drvdata(spi);
> +
> +	clk_disable_unprepare(peb2466->mclk);
> +}

... and the remove function.

CJ


> +
> +static const struct of_device_id peb2466_of_match[] = {
> +	{ .compatible = "infineon,peb2466", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, peb2466_of_match);
> +
> +static const struct spi_device_id peb2466_id_table[] = {
> +	{ "peb2466", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(spi, peb2466_id_table);
> +
> +static struct spi_driver peb2466_spi_driver = {
> +	.driver  = {
> +		.name   = "peb2466",
> +		.of_match_table = peb2466_of_match,
> +	},
> +	.id_table = peb2466_id_table,
> +	.probe  = peb2466_spi_probe,
> +	.remove = peb2466_spi_remove,
> +};
> +
> +module_spi_driver(peb2466_spi_driver);
> +
> +MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
> +MODULE_DESCRIPTION("PEB2466 ALSA SoC driver");
> +MODULE_LICENSE("GPL");


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

* Re: [PATCH 2/3] ASoC: codecs: Add support for the Infineon PEB2466 codec
  2023-02-07 21:17   ` Christophe JAILLET
@ 2023-02-08  7:10       ` Herve Codina
  0 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-08  7:10 UTC (permalink / raw)
  To: Christophe JAILLET
  Cc: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Linus Walleij, Bartosz Golaszewski, Jaroslav Kysela,
	Takashi Iwai, alsa-devel, devicetree, linux-kernel, linux-gpio,
	Christophe Leroy, Thomas Petazzoni

On Tue, 7 Feb 2023 22:17:39 +0100
Hi Christophe,

Christophe JAILLET <christophe.jaillet@wanadoo.fr> wrote:

> Le 06/02/2023 à 15:49, Herve Codina a écrit :
> > The Infineon PEB2466 codec is a programmable DSP-based four channels
> > codec with filters capabilities.
> > It also provides signals as GPIOs.
> > 
> > Signed-off-by: Herve Codina <herve.codina@bootlin.com>
> > ---
> >   sound/soc/codecs/Kconfig   |   12 +
> >   sound/soc/codecs/Makefile  |    2 +
> >   sound/soc/codecs/peb2466.c | 2071 ++++++++++++++++++++++++++++++++++++
> >   3 files changed, 2085 insertions(+)
> >   create mode 100644 sound/soc/codecs/peb2466.c
> >   
> 
> [...]
> 
> > +static int peb2466_spi_probe(struct spi_device *spi)
> > +{
> > +	struct peb2466 *peb2466;
> > +	unsigned long mclk_rate;
> > +	int ret;
> > +	u8 xr5;
> > +
> > +	spi->bits_per_word = 8;
> > +	ret = spi_setup(spi);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	peb2466 = devm_kzalloc(&spi->dev, sizeof(*peb2466), GFP_KERNEL);
> > +	if (!peb2466)
> > +		return -ENOMEM;
> > +
> > +	peb2466->spi = spi;
> > +
> > +	peb2466->regmap = devm_regmap_init(&peb2466->spi->dev, NULL, peb2466,
> > +					   &peb2466_regmap_config);
> > +	if (IS_ERR(peb2466->regmap))
> > +		return PTR_ERR(peb2466->regmap);
> > +
> > +	peb2466->reset_gpio = devm_gpiod_get_optional(&peb2466->spi->dev,
> > +						      "reset", GPIOD_OUT_LOW);
> > +	if (IS_ERR(peb2466->reset_gpio))
> > +		return PTR_ERR(peb2466->reset_gpio);
> > +
> > +	peb2466->mclk = devm_clk_get(&peb2466->spi->dev, "mclk");  
> 
> Hi,
> 
> Up to you to decide if it is a good idea or not, but using 
> devm_clk_get_enabled() would save the 'mclk' field in peb2466 ...
> 
> > +	if (IS_ERR(peb2466->mclk))
> > +		return PTR_ERR(peb2466->mclk);
> > +	ret = clk_prepare_enable(peb2466->mclk);
> > +	if (ret)
> > +		return ret;  
> 
> ... these 3 lines ...
> 
> > +
> > +	if (peb2466->reset_gpio) {
> > +		gpiod_set_value_cansleep(peb2466->reset_gpio, 1);
> > +		udelay(4);
> > +		gpiod_set_value_cansleep(peb2466->reset_gpio, 0);
> > +		udelay(4);
> > +	}
> > +
> > +	spi_set_drvdata(spi, peb2466);  
> 
> ... this spi_set_drvdata() call ...
> 
> > +
> > +	mclk_rate = clk_get_rate(peb2466->mclk);
> > +	switch (mclk_rate) {
> > +	case 1536000:
> > +		xr5 = PEB2466_XR5_MCLK_1536;
> > +		break;
> > +	case 2048000:
> > +		xr5 = PEB2466_XR5_MCLK_2048;
> > +		break;
> > +	case 4096000:
> > +		xr5 = PEB2466_XR5_MCLK_4096;
> > +		break;
> > +	case 8192000:
> > +		xr5 = PEB2466_XR5_MCLK_8192;
> > +		break;
> > +	default:
> > +		dev_err(&peb2466->spi->dev, "Unsupported clock rate %lu\n",
> > +			mclk_rate);
> > +		ret = -EINVAL;
> > +		goto failed;
> > +	}
> > +	ret = regmap_write(peb2466->regmap, PEB2466_XR5, xr5);
> > +	if (ret) {
> > +		dev_err(&peb2466->spi->dev, "Setting MCLK failed (%d)\n", ret);
> > +		goto failed;
> > +	}
> > +
> > +	ret = devm_snd_soc_register_component(&spi->dev, &peb2466_component_driver,
> > +					      &peb2466_dai_driver, 1);
> > +	if (ret)
> > +		goto failed;
> > +
> > +	if (IS_ENABLED(CONFIG_GPIOLIB)) {
> > +		ret = peb2466_gpio_init(peb2466);
> > +		if (ret)
> > +			goto failed;
> > +	}
> > +
> > +	return 0;
> > +
> > +failed:
> > +	clk_disable_unprepare(peb2466->mclk);
> > +	return ret;  
> 
> ... this error handling path ...
> 
> > +}
> > +
> > +static void peb2466_spi_remove(struct spi_device *spi)
> > +{
> > +	struct peb2466 *peb2466 = spi_get_drvdata(spi);
> > +
> > +	clk_disable_unprepare(peb2466->mclk);
> > +}  
> 
> ... and the remove function.
> 
> CJ
> 

Thanks for pointing this.
I will use devm_clk_get_enabled() in the next series iteration as suggested.

Best regards,
Hervé

-- 
Hervé Codina, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

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

* Re: [PATCH 2/3] ASoC: codecs: Add support for the Infineon PEB2466 codec
@ 2023-02-08  7:10       ` Herve Codina
  0 siblings, 0 replies; 14+ messages in thread
From: Herve Codina @ 2023-02-08  7:10 UTC (permalink / raw)
  To: Christophe JAILLET
  Cc: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Linus Walleij, Bartosz Golaszewski, Takashi Iwai, alsa-devel,
	devicetree, linux-kernel, linux-gpio, Christophe Leroy,
	Thomas Petazzoni

On Tue, 7 Feb 2023 22:17:39 +0100
Hi Christophe,

Christophe JAILLET <christophe.jaillet@wanadoo.fr> wrote:

> Le 06/02/2023 à 15:49, Herve Codina a écrit :
> > The Infineon PEB2466 codec is a programmable DSP-based four channels
> > codec with filters capabilities.
> > It also provides signals as GPIOs.
> > 
> > Signed-off-by: Herve Codina <herve.codina@bootlin.com>
> > ---
> >   sound/soc/codecs/Kconfig   |   12 +
> >   sound/soc/codecs/Makefile  |    2 +
> >   sound/soc/codecs/peb2466.c | 2071 ++++++++++++++++++++++++++++++++++++
> >   3 files changed, 2085 insertions(+)
> >   create mode 100644 sound/soc/codecs/peb2466.c
> >   
> 
> [...]
> 
> > +static int peb2466_spi_probe(struct spi_device *spi)
> > +{
> > +	struct peb2466 *peb2466;
> > +	unsigned long mclk_rate;
> > +	int ret;
> > +	u8 xr5;
> > +
> > +	spi->bits_per_word = 8;
> > +	ret = spi_setup(spi);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	peb2466 = devm_kzalloc(&spi->dev, sizeof(*peb2466), GFP_KERNEL);
> > +	if (!peb2466)
> > +		return -ENOMEM;
> > +
> > +	peb2466->spi = spi;
> > +
> > +	peb2466->regmap = devm_regmap_init(&peb2466->spi->dev, NULL, peb2466,
> > +					   &peb2466_regmap_config);
> > +	if (IS_ERR(peb2466->regmap))
> > +		return PTR_ERR(peb2466->regmap);
> > +
> > +	peb2466->reset_gpio = devm_gpiod_get_optional(&peb2466->spi->dev,
> > +						      "reset", GPIOD_OUT_LOW);
> > +	if (IS_ERR(peb2466->reset_gpio))
> > +		return PTR_ERR(peb2466->reset_gpio);
> > +
> > +	peb2466->mclk = devm_clk_get(&peb2466->spi->dev, "mclk");  
> 
> Hi,
> 
> Up to you to decide if it is a good idea or not, but using 
> devm_clk_get_enabled() would save the 'mclk' field in peb2466 ...
> 
> > +	if (IS_ERR(peb2466->mclk))
> > +		return PTR_ERR(peb2466->mclk);
> > +	ret = clk_prepare_enable(peb2466->mclk);
> > +	if (ret)
> > +		return ret;  
> 
> ... these 3 lines ...
> 
> > +
> > +	if (peb2466->reset_gpio) {
> > +		gpiod_set_value_cansleep(peb2466->reset_gpio, 1);
> > +		udelay(4);
> > +		gpiod_set_value_cansleep(peb2466->reset_gpio, 0);
> > +		udelay(4);
> > +	}
> > +
> > +	spi_set_drvdata(spi, peb2466);  
> 
> ... this spi_set_drvdata() call ...
> 
> > +
> > +	mclk_rate = clk_get_rate(peb2466->mclk);
> > +	switch (mclk_rate) {
> > +	case 1536000:
> > +		xr5 = PEB2466_XR5_MCLK_1536;
> > +		break;
> > +	case 2048000:
> > +		xr5 = PEB2466_XR5_MCLK_2048;
> > +		break;
> > +	case 4096000:
> > +		xr5 = PEB2466_XR5_MCLK_4096;
> > +		break;
> > +	case 8192000:
> > +		xr5 = PEB2466_XR5_MCLK_8192;
> > +		break;
> > +	default:
> > +		dev_err(&peb2466->spi->dev, "Unsupported clock rate %lu\n",
> > +			mclk_rate);
> > +		ret = -EINVAL;
> > +		goto failed;
> > +	}
> > +	ret = regmap_write(peb2466->regmap, PEB2466_XR5, xr5);
> > +	if (ret) {
> > +		dev_err(&peb2466->spi->dev, "Setting MCLK failed (%d)\n", ret);
> > +		goto failed;
> > +	}
> > +
> > +	ret = devm_snd_soc_register_component(&spi->dev, &peb2466_component_driver,
> > +					      &peb2466_dai_driver, 1);
> > +	if (ret)
> > +		goto failed;
> > +
> > +	if (IS_ENABLED(CONFIG_GPIOLIB)) {
> > +		ret = peb2466_gpio_init(peb2466);
> > +		if (ret)
> > +			goto failed;
> > +	}
> > +
> > +	return 0;
> > +
> > +failed:
> > +	clk_disable_unprepare(peb2466->mclk);
> > +	return ret;  
> 
> ... this error handling path ...
> 
> > +}
> > +
> > +static void peb2466_spi_remove(struct spi_device *spi)
> > +{
> > +	struct peb2466 *peb2466 = spi_get_drvdata(spi);
> > +
> > +	clk_disable_unprepare(peb2466->mclk);
> > +}  
> 
> ... and the remove function.
> 
> CJ
> 

Thanks for pointing this.
I will use devm_clk_get_enabled() in the next series iteration as suggested.

Best regards,
Hervé

-- 
Hervé Codina, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

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

* Re: [PATCH 0/3] Add the Infineon PEB2466 codec support
  2023-02-06 14:49 ` Herve Codina
                   ` (3 preceding siblings ...)
  (?)
@ 2023-02-09 18:36 ` Mark Brown
  -1 siblings, 0 replies; 14+ messages in thread
From: Mark Brown @ 2023-02-09 18:36 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 Mon, 06 Feb 2023 15:49:01 +0100, Herve Codina wrote:
> The Infineon PEB2466 codec is a programmable DSP-based four channels
> codec with filters capabilities.
> It also provides signals as GPIOs.
> 
> Best regards,
> Herve Codina
> 
> [...]

Applied to

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

Thanks!

[1/3] dt-bindings: sound: Add the Infineon PEB2466 codec
      commit: 4c03e952ad8688a28aed9b72fc48291240707c7d
[2/3] ASoC: codecs: Add support for the Infineon PEB2466 codec
      commit: 227f609c7c0e44f2103a8406ff9379ee1de3bc28
[3/3] MAINTAINERS: add the Infineon PEB2466 codec entry
      commit: 564972dbf85c0900521e6273b772fd7f0b720dd7

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] 14+ messages in thread

end of thread, other threads:[~2023-02-09 18:36 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-02-06 14:49 [PATCH 0/3] Add the Infineon PEB2466 codec support Herve Codina
2023-02-06 14:49 ` Herve Codina
2023-02-06 14:49 ` [PATCH 1/3] dt-bindings: sound: Add the Infineon PEB2466 codec Herve Codina
2023-02-06 14:49   ` Herve Codina
2023-02-07 21:06   ` Rob Herring
2023-02-07 21:06     ` Rob Herring
2023-02-06 14:49 ` [PATCH 2/3] ASoC: codecs: Add support for " Herve Codina
2023-02-06 14:49   ` Herve Codina
2023-02-07 21:17   ` Christophe JAILLET
2023-02-08  7:10     ` Herve Codina
2023-02-08  7:10       ` Herve Codina
2023-02-06 14:49 ` [PATCH 3/3] MAINTAINERS: add the Infineon PEB2466 codec entry Herve Codina
2023-02-06 14:49   ` Herve Codina
2023-02-09 18:36 ` [PATCH 0/3] Add the Infineon PEB2466 codec support Mark Brown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.