All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/5] ASoC: Add a driver for the Cirrus Logic CS35L45 Smart Amplifier
@ 2022-04-05 13:54 ` Richard Fitzgerald
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: devicetree, alsa-devel, patches, linux-kernel,
	Richard Fitzgerald, robh+dt

This adds basic audio support for the Cirrus Logic CS35L45 amplifier.

The first two patches add two generic helpers to ASoC, and patch 3 is
a kunit test for patch 2.

CHANGES SINCE V1:
Patch #5:
 - spi .remove callback now has void return
 - use new I2C .probe_new callback
 - force boost-bypass mode as default

James Schulman (1):
  ASoC: cs35l45: Add driver for Cirrus Logic CS35L45 Smart Amp

Richard Fitzgerald (4):
  ASoC: soc.h: Add SOC_SINGLE_S_TLV() macro
  ASoC: soc-utils: Add helper to calculate BCLK from TDM info
  ASoC: soc-utils: Add kunit test for snd_soc_tdm_params_to_bclk()
  ASoC: dt-bindings: cs35l45: Cirrus Logic CS35L45 Smart Amp

 .../bindings/sound/cirrus,cs35l45.yaml        |  75 ++
 MAINTAINERS                                   |   2 +
 include/dt-bindings/sound/cs35l45.h           |  20 +
 include/sound/soc.h                           |   4 +
 sound/soc/Kconfig                             |   9 +-
 sound/soc/Makefile                            |   5 +
 sound/soc/codecs/Kconfig                      |  30 +
 sound/soc/codecs/Makefile                     |   8 +
 sound/soc/codecs/cs35l45-i2c.c                |  72 ++
 sound/soc/codecs/cs35l45-spi.c                |  72 ++
 sound/soc/codecs/cs35l45-tables.c             | 202 +++++
 sound/soc/codecs/cs35l45.c                    | 693 ++++++++++++++++++
 sound/soc/codecs/cs35l45.h                    | 217 ++++++
 sound/soc/soc-utils-test.c                    | 186 +++++
 sound/soc/soc-utils.c                         |  45 ++
 15 files changed, 1639 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
 create mode 100644 include/dt-bindings/sound/cs35l45.h
 create mode 100644 sound/soc/codecs/cs35l45-i2c.c
 create mode 100644 sound/soc/codecs/cs35l45-spi.c
 create mode 100644 sound/soc/codecs/cs35l45-tables.c
 create mode 100644 sound/soc/codecs/cs35l45.c
 create mode 100644 sound/soc/codecs/cs35l45.h
 create mode 100644 sound/soc/soc-utils-test.c

-- 
2.30.2


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

* [PATCH v2 0/5] ASoC: Add a driver for the Cirrus Logic CS35L45 Smart Amplifier
@ 2022-04-05 13:54 ` Richard Fitzgerald
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: robh+dt, alsa-devel, devicetree, linux-kernel, patches,
	Richard Fitzgerald

This adds basic audio support for the Cirrus Logic CS35L45 amplifier.

The first two patches add two generic helpers to ASoC, and patch 3 is
a kunit test for patch 2.

CHANGES SINCE V1:
Patch #5:
 - spi .remove callback now has void return
 - use new I2C .probe_new callback
 - force boost-bypass mode as default

James Schulman (1):
  ASoC: cs35l45: Add driver for Cirrus Logic CS35L45 Smart Amp

Richard Fitzgerald (4):
  ASoC: soc.h: Add SOC_SINGLE_S_TLV() macro
  ASoC: soc-utils: Add helper to calculate BCLK from TDM info
  ASoC: soc-utils: Add kunit test for snd_soc_tdm_params_to_bclk()
  ASoC: dt-bindings: cs35l45: Cirrus Logic CS35L45 Smart Amp

 .../bindings/sound/cirrus,cs35l45.yaml        |  75 ++
 MAINTAINERS                                   |   2 +
 include/dt-bindings/sound/cs35l45.h           |  20 +
 include/sound/soc.h                           |   4 +
 sound/soc/Kconfig                             |   9 +-
 sound/soc/Makefile                            |   5 +
 sound/soc/codecs/Kconfig                      |  30 +
 sound/soc/codecs/Makefile                     |   8 +
 sound/soc/codecs/cs35l45-i2c.c                |  72 ++
 sound/soc/codecs/cs35l45-spi.c                |  72 ++
 sound/soc/codecs/cs35l45-tables.c             | 202 +++++
 sound/soc/codecs/cs35l45.c                    | 693 ++++++++++++++++++
 sound/soc/codecs/cs35l45.h                    | 217 ++++++
 sound/soc/soc-utils-test.c                    | 186 +++++
 sound/soc/soc-utils.c                         |  45 ++
 15 files changed, 1639 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
 create mode 100644 include/dt-bindings/sound/cs35l45.h
 create mode 100644 sound/soc/codecs/cs35l45-i2c.c
 create mode 100644 sound/soc/codecs/cs35l45-spi.c
 create mode 100644 sound/soc/codecs/cs35l45-tables.c
 create mode 100644 sound/soc/codecs/cs35l45.c
 create mode 100644 sound/soc/codecs/cs35l45.h
 create mode 100644 sound/soc/soc-utils-test.c

-- 
2.30.2


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

* [PATCH v2 1/5] ASoC: soc.h: Add SOC_SINGLE_S_TLV() macro
  2022-04-05 13:54 ` Richard Fitzgerald
@ 2022-04-05 13:54   ` Richard Fitzgerald
  -1 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: devicetree, alsa-devel, patches, linux-kernel,
	Richard Fitzgerald, robh+dt

Add a convenience macro for defining a single (mono) TLV control
with a signed value. This can already be done by using
SOC_DOUBLE_R_S_TLV() with the same address for left and right
registers, but a dedicated macro is more readable.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/soc.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index d3d3a26e8867..55fb6a6d7d25 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -176,6 +176,8 @@
 	.get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \
 	.private_value = SOC_DOUBLE_R_S_VALUE(reg_left, reg_right, xshift, \
 					    xmin, xmax, xsign_bit, xinvert) }
+#define SOC_SINGLE_S_TLV(xname, xreg, xshift, xmin, xmax, xsign_bit, xinvert, tlv_array) \
+	SOC_DOUBLE_R_S_TLV(xname, xreg, xreg, xshift, xmin, xmax, xsign_bit, xinvert, tlv_array)
 #define SOC_SINGLE_S8_TLV(xname, xreg, xmin, xmax, tlv_array) \
 {	.iface  = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
 	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
-- 
2.30.2


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

* [PATCH v2 1/5] ASoC: soc.h: Add SOC_SINGLE_S_TLV() macro
@ 2022-04-05 13:54   ` Richard Fitzgerald
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: robh+dt, alsa-devel, devicetree, linux-kernel, patches,
	Richard Fitzgerald

Add a convenience macro for defining a single (mono) TLV control
with a signed value. This can already be done by using
SOC_DOUBLE_R_S_TLV() with the same address for left and right
registers, but a dedicated macro is more readable.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/soc.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index d3d3a26e8867..55fb6a6d7d25 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -176,6 +176,8 @@
 	.get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \
 	.private_value = SOC_DOUBLE_R_S_VALUE(reg_left, reg_right, xshift, \
 					    xmin, xmax, xsign_bit, xinvert) }
+#define SOC_SINGLE_S_TLV(xname, xreg, xshift, xmin, xmax, xsign_bit, xinvert, tlv_array) \
+	SOC_DOUBLE_R_S_TLV(xname, xreg, xreg, xshift, xmin, xmax, xsign_bit, xinvert, tlv_array)
 #define SOC_SINGLE_S8_TLV(xname, xreg, xmin, xmax, tlv_array) \
 {	.iface  = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
 	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
-- 
2.30.2


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

* [PATCH v2 2/5] ASoC: soc-utils: Add helper to calculate BCLK from TDM info
  2022-04-05 13:54 ` Richard Fitzgerald
@ 2022-04-05 13:54   ` Richard Fitzgerald
  -1 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: devicetree, alsa-devel, patches, linux-kernel,
	Richard Fitzgerald, robh+dt

Add a helper function snd_soc_tdm_params_to_bclk() to calculate
the bclk from params info and the tdm sots configuration.

When using a TDM frame of N slots of width W bits:

   bclk = sample_rate * N * W

As a convenience to simplify calling code, if the slot count or
slot width are 0 a value will be obtained from the params. This
allows calling code to use this one function to handle cases of
TDM where only one parameter is fixed, or I2S where the slot width
is fixed (for example to set a 32-bit slot for 24-bit samples).

Also as a convenience the slot count can optionally be rounded up
to a multiple. This is mainly useful for I2S systems, since I2S has
two phases of LRCLK the number of slots is always a multiple of 2.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/soc.h   |  2 ++
 sound/soc/soc-utils.c | 45 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index 55fb6a6d7d25..2d3261799d2c 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -487,6 +487,8 @@ int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots);
 int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params);
 int snd_soc_calc_bclk(int fs, int sample_size, int channels, int tdm_slots);
 int snd_soc_params_to_bclk(struct snd_pcm_hw_params *parms);
+int snd_soc_tdm_params_to_bclk(struct snd_pcm_hw_params *params,
+			       int tdm_width, int tdm_slots, int slot_multiple);
 
 /* set runtime hw params */
 int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c
index a4efe7e52a8b..594cb311ff30 100644
--- a/sound/soc/soc-utils.c
+++ b/sound/soc/soc-utils.c
@@ -9,6 +9,7 @@
 
 #include <linux/platform_device.h>
 #include <linux/export.h>
+#include <linux/math.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -52,6 +53,50 @@ int snd_soc_params_to_bclk(struct snd_pcm_hw_params *params)
 }
 EXPORT_SYMBOL_GPL(snd_soc_params_to_bclk);
 
+/**
+ * snd_soc_tdm_params_to_bclk - calculate bclk from params and tdm slot info.
+ *
+ * Calculate the bclk from the params sample rate and the tdm slot count and
+ * tdm slot width. Either or both of tdm_width and tdm_slots can be 0.
+ *
+ * If tdm_width == 0 and tdm_slots > 0:	the params_width will be used.
+ * If tdm_width > 0 and tdm_slots == 0:	the params_channels will be used
+ *					as the slot count.
+ * Both tdm_width and tdm_slots are 0:	this is equivalent to calling
+ *					snd_soc_params_to_bclk().
+ *
+ * If slot_multiple > 1 the slot count (or params_channels if tdm_slots == 0)
+ * will be rounded up to a multiple of this value. This is mainly useful for
+ * I2S mode, which has a left and right phase so the number of slots is always
+ * a multiple of 2.
+ *
+ * @params:        Pointer to struct_pcm_hw_params.
+ * @tdm_width:     Width in bits of the tdm slots.
+ * @tdm_slots:     Number of tdm slots per frame.
+ * @slot_multiple: If >1 roundup slot count to a multiple of this value.
+ *
+ * Return: bclk frequency in Hz, else a negative error code if params format
+ *	   is invalid.
+ */
+int snd_soc_tdm_params_to_bclk(struct snd_pcm_hw_params *params,
+			       int tdm_width, int tdm_slots, int slot_multiple)
+{
+	if (!tdm_slots)
+		tdm_slots = params_channels(params);
+
+	if (slot_multiple > 1)
+		tdm_slots = roundup(tdm_slots, slot_multiple);
+
+	if (!tdm_width) {
+		tdm_width = snd_pcm_format_width(params_format(params));
+		if (tdm_width < 0)
+			return tdm_width;
+	}
+
+	return snd_soc_calc_bclk(params_rate(params), tdm_width, 1, tdm_slots);
+}
+EXPORT_SYMBOL_GPL(snd_soc_tdm_params_to_bclk);
+
 static const struct snd_pcm_hardware dummy_dma_hardware = {
 	/* Random values to keep userspace happy when checking constraints */
 	.info			= SNDRV_PCM_INFO_INTERLEAVED |
-- 
2.30.2


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

* [PATCH v2 2/5] ASoC: soc-utils: Add helper to calculate BCLK from TDM info
@ 2022-04-05 13:54   ` Richard Fitzgerald
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: robh+dt, alsa-devel, devicetree, linux-kernel, patches,
	Richard Fitzgerald

Add a helper function snd_soc_tdm_params_to_bclk() to calculate
the bclk from params info and the tdm sots configuration.

When using a TDM frame of N slots of width W bits:

   bclk = sample_rate * N * W

As a convenience to simplify calling code, if the slot count or
slot width are 0 a value will be obtained from the params. This
allows calling code to use this one function to handle cases of
TDM where only one parameter is fixed, or I2S where the slot width
is fixed (for example to set a 32-bit slot for 24-bit samples).

Also as a convenience the slot count can optionally be rounded up
to a multiple. This is mainly useful for I2S systems, since I2S has
two phases of LRCLK the number of slots is always a multiple of 2.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 include/sound/soc.h   |  2 ++
 sound/soc/soc-utils.c | 45 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index 55fb6a6d7d25..2d3261799d2c 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -487,6 +487,8 @@ int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots);
 int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params);
 int snd_soc_calc_bclk(int fs, int sample_size, int channels, int tdm_slots);
 int snd_soc_params_to_bclk(struct snd_pcm_hw_params *parms);
+int snd_soc_tdm_params_to_bclk(struct snd_pcm_hw_params *params,
+			       int tdm_width, int tdm_slots, int slot_multiple);
 
 /* set runtime hw params */
 int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c
index a4efe7e52a8b..594cb311ff30 100644
--- a/sound/soc/soc-utils.c
+++ b/sound/soc/soc-utils.c
@@ -9,6 +9,7 @@
 
 #include <linux/platform_device.h>
 #include <linux/export.h>
+#include <linux/math.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -52,6 +53,50 @@ int snd_soc_params_to_bclk(struct snd_pcm_hw_params *params)
 }
 EXPORT_SYMBOL_GPL(snd_soc_params_to_bclk);
 
+/**
+ * snd_soc_tdm_params_to_bclk - calculate bclk from params and tdm slot info.
+ *
+ * Calculate the bclk from the params sample rate and the tdm slot count and
+ * tdm slot width. Either or both of tdm_width and tdm_slots can be 0.
+ *
+ * If tdm_width == 0 and tdm_slots > 0:	the params_width will be used.
+ * If tdm_width > 0 and tdm_slots == 0:	the params_channels will be used
+ *					as the slot count.
+ * Both tdm_width and tdm_slots are 0:	this is equivalent to calling
+ *					snd_soc_params_to_bclk().
+ *
+ * If slot_multiple > 1 the slot count (or params_channels if tdm_slots == 0)
+ * will be rounded up to a multiple of this value. This is mainly useful for
+ * I2S mode, which has a left and right phase so the number of slots is always
+ * a multiple of 2.
+ *
+ * @params:        Pointer to struct_pcm_hw_params.
+ * @tdm_width:     Width in bits of the tdm slots.
+ * @tdm_slots:     Number of tdm slots per frame.
+ * @slot_multiple: If >1 roundup slot count to a multiple of this value.
+ *
+ * Return: bclk frequency in Hz, else a negative error code if params format
+ *	   is invalid.
+ */
+int snd_soc_tdm_params_to_bclk(struct snd_pcm_hw_params *params,
+			       int tdm_width, int tdm_slots, int slot_multiple)
+{
+	if (!tdm_slots)
+		tdm_slots = params_channels(params);
+
+	if (slot_multiple > 1)
+		tdm_slots = roundup(tdm_slots, slot_multiple);
+
+	if (!tdm_width) {
+		tdm_width = snd_pcm_format_width(params_format(params));
+		if (tdm_width < 0)
+			return tdm_width;
+	}
+
+	return snd_soc_calc_bclk(params_rate(params), tdm_width, 1, tdm_slots);
+}
+EXPORT_SYMBOL_GPL(snd_soc_tdm_params_to_bclk);
+
 static const struct snd_pcm_hardware dummy_dma_hardware = {
 	/* Random values to keep userspace happy when checking constraints */
 	.info			= SNDRV_PCM_INFO_INTERLEAVED |
-- 
2.30.2


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

* [PATCH v2 3/5] ASoC: soc-utils: Add kunit test for snd_soc_tdm_params_to_bclk()
  2022-04-05 13:54 ` Richard Fitzgerald
@ 2022-04-05 13:54   ` Richard Fitzgerald
  -1 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: devicetree, alsa-devel, patches, linux-kernel,
	Richard Fitzgerald, robh+dt

Create a new kunit test for soc-utils and use it to test
snd_soc_tdm_params_to_bclk().

The test uses a table of values to avoid the possibility that an
on-the-fly generator contains the same algorithmic error as the
function-under-test and so fails to detect a bug.

There is no need to test every possible combination of values.
Enough test cases are included to give confidence that the function
is producing the correct results.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/soc/Kconfig          |   9 +-
 sound/soc/Makefile         |   5 +
 sound/soc/soc-utils-test.c | 186 +++++++++++++++++++++++++++++++++++++
 3 files changed, 199 insertions(+), 1 deletion(-)
 create mode 100644 sound/soc/soc-utils-test.c

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 5dcf77af07af..7d4747b6bab2 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -14,7 +14,7 @@ menuconfig SND_SOC
 
 	  If you want ASoC support, you should say Y here and also to the
 	  specific driver for your SoC platform below.
-	  
+
 	  ASoC provides power efficient ALSA support for embedded battery powered
 	  SoC based systems like PDA's, Phones and Personal Media Players.
 
@@ -55,6 +55,13 @@ config SND_SOC_TOPOLOGY_KUNIT_TEST
 	  userspace applications such as pulseaudio, to prevent unnecessary
 	  problems.
 
+config SND_SOC_UTILS_KUNIT_TEST
+	tristate "KUnit tests for SoC utils"
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  If you want to perform tests on ALSA SoC utils library say Y here.
+
 config SND_SOC_ACPI
 	tristate
 
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index a7b37c06dc43..d4528962ac34 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -12,6 +12,11 @@ ifneq ($(CONFIG_SND_SOC_TOPOLOGY_KUNIT_TEST),)
 obj-$(CONFIG_SND_SOC_TOPOLOGY_KUNIT_TEST) := soc-topology-test.o
 endif
 
+ifneq ($(CONFIG_SND_SOC_UTILS_KUNIT_TEST),)
+# snd-soc-test-objs := soc-utils-test.o
+obj-$(CONFIG_SND_SOC_UTILS_KUNIT_TEST) := soc-utils-test.o
+endif
+
 ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),)
 snd-soc-core-objs += soc-generic-dmaengine-pcm.o
 endif
diff --git a/sound/soc/soc-utils-test.c b/sound/soc/soc-utils-test.c
new file mode 100644
index 000000000000..5ad8e23af49a
--- /dev/null
+++ b/sound/soc/soc-utils-test.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2022 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+
+#include <kunit/test.h>
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <uapi/sound/asound.h>
+
+static const struct {
+	u32 rate;
+	snd_pcm_format_t fmt;
+	u8 channels;
+	u8 tdm_width;
+	u8 tdm_slots;
+	u8 slot_multiple;
+	u32 bclk;
+} tdm_params_to_bclk_cases[] = {
+	/* rate		fmt	   channels tdm_width tdm_slots slot_multiple bclk */
+
+	/* From params only */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	0,	  128000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	0,	  192000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	0,	  384000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	0,	  512000 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	0,	  705600 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	0,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	0,	 1058400 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	0,	 2116800 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	0,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	0,	 2822400 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	0,	 6144000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	0,	 9216000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	0,	18432000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	0,	24576000 },
+
+	/* I2S from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	2,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	2,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	2,	  384000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	2,	  384000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	2,	  512000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	2,	  512000 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	2,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	2,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	2,	 2116800 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	2,	 2116800 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	2,	 2822400 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	2,	 2822400 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	2,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	2,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	2,	18432000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	2,	18432000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	2,	24576000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	2,	24576000 },
+
+	/* Fixed 8-slot TDM, other values from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 3,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 4,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	8,	0,	 2048000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	8,	0,	 2048000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 3,	0,	8,	0,	 2048000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 4,	0,	8,	0,	 2048000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 3,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 4,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	8,	0,	98304000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	8,	0,	98304000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 3,	0,	8,	0,	98304000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 4,	0,	8,	0,	98304000 },
+
+	/* Fixed 32-bit TDM, other values from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	32,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	32,	0,	0,	  512000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 3,	32,	0,	0,	  768000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 4,	32,	0,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	32,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	32,	0,	0,	  512000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 3,	32,	0,	0,	  768000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 4,	32,	0,	0,	 1024000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	32,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	32,	0,	0,	24576000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 3,	32,	0,	0,	36864000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 4,	32,	0,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	32,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	32,	0,	0,	24576000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 3,	32,	0,	0,	36864000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 4,	32,	0,	0,	49152000 },
+
+	/* Fixed 6-slot 24-bit TDM, other values from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 3,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 4,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 1,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 2,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 3,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 4,	24,	6,	0,	 1152000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 1,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 2,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 3,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 4,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 1,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 2,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 3,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 4,	24,	6,	0,	27648000 },
+};
+
+static void test_tdm_params_to_bclk_one(struct kunit *test,
+					unsigned int rate, snd_pcm_format_t fmt,
+					unsigned int channels,
+					unsigned int tdm_width, unsigned int tdm_slots,
+					unsigned int slot_multiple,
+					unsigned int expected_bclk)
+{
+	struct snd_pcm_hw_params params;
+	int got_bclk;
+
+	_snd_pcm_hw_params_any(&params);
+	snd_mask_none(hw_param_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT));
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_RATE)->min = rate;
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_RATE)->max = rate;
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = channels;
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS)->max = channels;
+	params_set_format(&params, fmt);
+
+	got_bclk = snd_soc_tdm_params_to_bclk(&params, tdm_width, tdm_slots, slot_multiple);
+	pr_debug("%s: r=%u sb=%u ch=%u tw=%u ts=%u sm=%u expected=%u got=%d\n",
+		 __func__,
+		 rate, params_width(&params), channels, tdm_width, tdm_slots, slot_multiple,
+		 expected_bclk, got_bclk);
+	KUNIT_ASSERT_EQ(test, expected_bclk, (unsigned int)got_bclk);
+}
+
+static void test_tdm_params_to_bclk(struct kunit *test)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tdm_params_to_bclk_cases); ++i) {
+		test_tdm_params_to_bclk_one(test,
+					    tdm_params_to_bclk_cases[i].rate,
+					    tdm_params_to_bclk_cases[i].fmt,
+					    tdm_params_to_bclk_cases[i].channels,
+					    tdm_params_to_bclk_cases[i].tdm_width,
+					    tdm_params_to_bclk_cases[i].tdm_slots,
+					    tdm_params_to_bclk_cases[i].slot_multiple,
+					    tdm_params_to_bclk_cases[i].bclk);
+
+		if (tdm_params_to_bclk_cases[i].slot_multiple > 0)
+			continue;
+
+		/* Slot multiple 1 should have the same effect as multiple 0 */
+		test_tdm_params_to_bclk_one(test,
+					    tdm_params_to_bclk_cases[i].rate,
+					    tdm_params_to_bclk_cases[i].fmt,
+					    tdm_params_to_bclk_cases[i].channels,
+					    tdm_params_to_bclk_cases[i].tdm_width,
+					    tdm_params_to_bclk_cases[i].tdm_slots,
+					    1,
+					    tdm_params_to_bclk_cases[i].bclk);
+	}
+}
+
+static struct kunit_case soc_utils_test_cases[] = {
+	KUNIT_CASE(test_tdm_params_to_bclk),
+	{}
+};
+
+static struct kunit_suite soc_utils_test_suite = {
+	.name = "soc-utils",
+	.test_cases = soc_utils_test_cases,
+};
+
+kunit_test_suites(&soc_utils_test_suite);
+
+MODULE_DESCRIPTION("ASoC soc-utils kunit test");
+MODULE_LICENSE("GPL");
-- 
2.30.2


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

* [PATCH v2 3/5] ASoC: soc-utils: Add kunit test for snd_soc_tdm_params_to_bclk()
@ 2022-04-05 13:54   ` Richard Fitzgerald
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: robh+dt, alsa-devel, devicetree, linux-kernel, patches,
	Richard Fitzgerald

Create a new kunit test for soc-utils and use it to test
snd_soc_tdm_params_to_bclk().

The test uses a table of values to avoid the possibility that an
on-the-fly generator contains the same algorithmic error as the
function-under-test and so fails to detect a bug.

There is no need to test every possible combination of values.
Enough test cases are included to give confidence that the function
is producing the correct results.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/soc/Kconfig          |   9 +-
 sound/soc/Makefile         |   5 +
 sound/soc/soc-utils-test.c | 186 +++++++++++++++++++++++++++++++++++++
 3 files changed, 199 insertions(+), 1 deletion(-)
 create mode 100644 sound/soc/soc-utils-test.c

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 5dcf77af07af..7d4747b6bab2 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -14,7 +14,7 @@ menuconfig SND_SOC
 
 	  If you want ASoC support, you should say Y here and also to the
 	  specific driver for your SoC platform below.
-	  
+
 	  ASoC provides power efficient ALSA support for embedded battery powered
 	  SoC based systems like PDA's, Phones and Personal Media Players.
 
@@ -55,6 +55,13 @@ config SND_SOC_TOPOLOGY_KUNIT_TEST
 	  userspace applications such as pulseaudio, to prevent unnecessary
 	  problems.
 
+config SND_SOC_UTILS_KUNIT_TEST
+	tristate "KUnit tests for SoC utils"
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  If you want to perform tests on ALSA SoC utils library say Y here.
+
 config SND_SOC_ACPI
 	tristate
 
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index a7b37c06dc43..d4528962ac34 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -12,6 +12,11 @@ ifneq ($(CONFIG_SND_SOC_TOPOLOGY_KUNIT_TEST),)
 obj-$(CONFIG_SND_SOC_TOPOLOGY_KUNIT_TEST) := soc-topology-test.o
 endif
 
+ifneq ($(CONFIG_SND_SOC_UTILS_KUNIT_TEST),)
+# snd-soc-test-objs := soc-utils-test.o
+obj-$(CONFIG_SND_SOC_UTILS_KUNIT_TEST) := soc-utils-test.o
+endif
+
 ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),)
 snd-soc-core-objs += soc-generic-dmaengine-pcm.o
 endif
diff --git a/sound/soc/soc-utils-test.c b/sound/soc/soc-utils-test.c
new file mode 100644
index 000000000000..5ad8e23af49a
--- /dev/null
+++ b/sound/soc/soc-utils-test.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2022 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+
+#include <kunit/test.h>
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <uapi/sound/asound.h>
+
+static const struct {
+	u32 rate;
+	snd_pcm_format_t fmt;
+	u8 channels;
+	u8 tdm_width;
+	u8 tdm_slots;
+	u8 slot_multiple;
+	u32 bclk;
+} tdm_params_to_bclk_cases[] = {
+	/* rate		fmt	   channels tdm_width tdm_slots slot_multiple bclk */
+
+	/* From params only */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	0,	  128000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	0,	  192000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	0,	  384000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	0,	  512000 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	0,	  705600 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	0,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	0,	 1058400 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	0,	 2116800 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	0,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	0,	 2822400 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	0,	 6144000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	0,	 9216000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	0,	18432000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	0,	24576000 },
+
+	/* I2S from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	2,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	2,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	2,	  384000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	2,	  384000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	2,	  512000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	2,	  512000 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	2,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	2,	 1411200 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	2,	 2116800 },
+	{  44100,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	2,	 2116800 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	2,	 2822400 },
+	{  44100,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	2,	 2822400 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	0,	2,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	0,	2,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 1,	0,	0,	2,	18432000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S24_LE, 2,	0,	0,	2,	18432000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	0,	2,	24576000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	0,	2,	24576000 },
+
+	/* Fixed 8-slot TDM, other values from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 3,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 4,	0,	8,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	8,	0,	 2048000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	8,	0,	 2048000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 3,	0,	8,	0,	 2048000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 4,	0,	8,	0,	 2048000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 3,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 4,	0,	8,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	0,	8,	0,	98304000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	0,	8,	0,	98304000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 3,	0,	8,	0,	98304000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 4,	0,	8,	0,	98304000 },
+
+	/* Fixed 32-bit TDM, other values from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	32,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	32,	0,	0,	  512000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 3,	32,	0,	0,	  768000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 4,	32,	0,	0,	 1024000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 1,	32,	0,	0,	  256000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 2,	32,	0,	0,	  512000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 3,	32,	0,	0,	  768000 },
+	{   8000,  SNDRV_PCM_FORMAT_S32_LE, 4,	32,	0,	0,	 1024000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 1,	32,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 2,	32,	0,	0,	24576000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 3,	32,	0,	0,	36864000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S16_LE, 4,	32,	0,	0,	49152000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 1,	32,	0,	0,	12288000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 2,	32,	0,	0,	24576000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 3,	32,	0,	0,	36864000 },
+	{ 384000,  SNDRV_PCM_FORMAT_S32_LE, 4,	32,	0,	0,	49152000 },
+
+	/* Fixed 6-slot 24-bit TDM, other values from params */
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 1,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 2,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 3,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S16_LE, 4,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 1,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 2,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 3,	24,	6,	0,	 1152000 },
+	{   8000,  SNDRV_PCM_FORMAT_S24_LE, 4,	24,	6,	0,	 1152000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 1,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 2,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 3,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S16_LE, 4,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 1,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 2,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 3,	24,	6,	0,	27648000 },
+	{ 192000,  SNDRV_PCM_FORMAT_S24_LE, 4,	24,	6,	0,	27648000 },
+};
+
+static void test_tdm_params_to_bclk_one(struct kunit *test,
+					unsigned int rate, snd_pcm_format_t fmt,
+					unsigned int channels,
+					unsigned int tdm_width, unsigned int tdm_slots,
+					unsigned int slot_multiple,
+					unsigned int expected_bclk)
+{
+	struct snd_pcm_hw_params params;
+	int got_bclk;
+
+	_snd_pcm_hw_params_any(&params);
+	snd_mask_none(hw_param_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT));
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_RATE)->min = rate;
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_RATE)->max = rate;
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = channels;
+	hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS)->max = channels;
+	params_set_format(&params, fmt);
+
+	got_bclk = snd_soc_tdm_params_to_bclk(&params, tdm_width, tdm_slots, slot_multiple);
+	pr_debug("%s: r=%u sb=%u ch=%u tw=%u ts=%u sm=%u expected=%u got=%d\n",
+		 __func__,
+		 rate, params_width(&params), channels, tdm_width, tdm_slots, slot_multiple,
+		 expected_bclk, got_bclk);
+	KUNIT_ASSERT_EQ(test, expected_bclk, (unsigned int)got_bclk);
+}
+
+static void test_tdm_params_to_bclk(struct kunit *test)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tdm_params_to_bclk_cases); ++i) {
+		test_tdm_params_to_bclk_one(test,
+					    tdm_params_to_bclk_cases[i].rate,
+					    tdm_params_to_bclk_cases[i].fmt,
+					    tdm_params_to_bclk_cases[i].channels,
+					    tdm_params_to_bclk_cases[i].tdm_width,
+					    tdm_params_to_bclk_cases[i].tdm_slots,
+					    tdm_params_to_bclk_cases[i].slot_multiple,
+					    tdm_params_to_bclk_cases[i].bclk);
+
+		if (tdm_params_to_bclk_cases[i].slot_multiple > 0)
+			continue;
+
+		/* Slot multiple 1 should have the same effect as multiple 0 */
+		test_tdm_params_to_bclk_one(test,
+					    tdm_params_to_bclk_cases[i].rate,
+					    tdm_params_to_bclk_cases[i].fmt,
+					    tdm_params_to_bclk_cases[i].channels,
+					    tdm_params_to_bclk_cases[i].tdm_width,
+					    tdm_params_to_bclk_cases[i].tdm_slots,
+					    1,
+					    tdm_params_to_bclk_cases[i].bclk);
+	}
+}
+
+static struct kunit_case soc_utils_test_cases[] = {
+	KUNIT_CASE(test_tdm_params_to_bclk),
+	{}
+};
+
+static struct kunit_suite soc_utils_test_suite = {
+	.name = "soc-utils",
+	.test_cases = soc_utils_test_cases,
+};
+
+kunit_test_suites(&soc_utils_test_suite);
+
+MODULE_DESCRIPTION("ASoC soc-utils kunit test");
+MODULE_LICENSE("GPL");
-- 
2.30.2


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

* [PATCH v2 4/5] ASoC: dt-bindings: cs35l45: Cirrus Logic CS35L45 Smart Amp
  2022-04-05 13:54 ` Richard Fitzgerald
@ 2022-04-05 13:54   ` Richard Fitzgerald
  -1 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: devicetree, alsa-devel, Rob Herring, patches, linux-kernel,
	Richard Fitzgerald, robh+dt

This adds the schema binding for the Cirrus Logic CS35L45 Smart Amp
and associated header file.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 .../bindings/sound/cirrus,cs35l45.yaml        | 75 +++++++++++++++++++
 MAINTAINERS                                   |  2 +
 include/dt-bindings/sound/cs35l45.h           | 20 +++++
 3 files changed, 97 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
 create mode 100644 include/dt-bindings/sound/cs35l45.h

diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
new file mode 100644
index 000000000000..184a1344ea76
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/cirrus,cs35l45.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS35L45 Speaker Amplifier
+
+maintainers:
+  - Ricardo Rivera-Matos <rriveram@opensource.cirrus.com>
+  - Richard Fitzgerald <rf@opensource.cirrus.com>
+
+description: |
+  CS35L45 is a Boosted Mono Class D Amplifier with DSP
+  Speaker Protection and Adaptive Battery Management.
+
+properties:
+  compatible:
+    enum:
+      - cirrus,cs35l45
+
+  reg:
+    maxItems: 1
+
+  '#sound-dai-cells':
+    const: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  vdd-a-supply:
+    description: voltage regulator phandle for the VDD_A supply
+
+  vdd-batt-supply:
+    description: voltage regulator phandle for the VDD_BATT supply
+
+  spi-max-frequency:
+    maximum: 5000000
+
+  cirrus,asp-sdout-hiz-ctrl:
+    description:
+      Audio serial port SDOUT Hi-Z control. Sets the Hi-Z
+      configuration for SDOUT pin of amplifier. Logical OR of
+      CS35L45_ASP_TX_HIZ_xxx values.
+    $ref: "/schemas/types.yaml#/definitions/uint32"
+    minimum: 0
+    maximum: 3
+    default: 2
+
+required:
+  - compatible
+  - reg
+  - "#sound-dai-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/sound/cs35l45.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        cs35l45: cs35l45@2 {
+          #sound-dai-cells = <1>;
+          compatible = "cirrus,cs35l45";
+          reg = <2>;
+          spi-max-frequency = <5000000>;
+          vdd-a-supply = <&dummy_vreg>;
+          vdd-batt-supply = <&dummy_vreg>;
+          reset-gpios = <&gpio 110 0>;
+          cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED |
+                                        CS35L45_ASP_TX_HIZ_DISABLED)>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index fd768d43e048..0c5971c3dd9e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4688,10 +4688,12 @@ CIRRUS LOGIC AUDIO CODEC DRIVERS
 M:	James Schulman <james.schulman@cirrus.com>
 M:	David Rhodes <david.rhodes@cirrus.com>
 M:	Lucas Tanure <tanureal@opensource.cirrus.com>
+M:	Richard Fitzgerald <rf@opensource.cirrus.com>
 L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
 L:	patches@opensource.cirrus.com
 S:	Maintained
 F:	Documentation/devicetree/bindings/sound/cirrus,cs*
+F:	include/dt-bindings/sound/cs*
 F:	sound/pci/hda/cs*
 F:	sound/soc/codecs/cs*
 
diff --git a/include/dt-bindings/sound/cs35l45.h b/include/dt-bindings/sound/cs35l45.h
new file mode 100644
index 000000000000..076da4b2c28d
--- /dev/null
+++ b/include/dt-bindings/sound/cs35l45.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * cs35l45.h -- CS35L45 ALSA SoC audio driver DT bindings header
+ *
+ * Copyright 2022 Cirrus Logic, Inc.
+ */
+
+#ifndef DT_CS35L45_H
+#define DT_CS35L45_H
+
+/*
+ * cirrus,asp-sdout-hiz-ctrl
+ *
+ * TX_HIZ_UNUSED:   TX pin high-impedance during unused slots.
+ * TX_HIZ_DISABLED: TX pin high-impedance when all channels disabled.
+ */
+#define CS35L45_ASP_TX_HIZ_UNUSED	0x1
+#define CS35L45_ASP_TX_HIZ_DISABLED	0x2
+
+#endif /* DT_CS35L45_H */
-- 
2.30.2


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

* [PATCH v2 4/5] ASoC: dt-bindings: cs35l45: Cirrus Logic CS35L45 Smart Amp
@ 2022-04-05 13:54   ` Richard Fitzgerald
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: robh+dt, alsa-devel, devicetree, linux-kernel, patches,
	Richard Fitzgerald, Rob Herring

This adds the schema binding for the Cirrus Logic CS35L45 Smart Amp
and associated header file.

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 .../bindings/sound/cirrus,cs35l45.yaml        | 75 +++++++++++++++++++
 MAINTAINERS                                   |  2 +
 include/dt-bindings/sound/cs35l45.h           | 20 +++++
 3 files changed, 97 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
 create mode 100644 include/dt-bindings/sound/cs35l45.h

diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
new file mode 100644
index 000000000000..184a1344ea76
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/cirrus,cs35l45.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS35L45 Speaker Amplifier
+
+maintainers:
+  - Ricardo Rivera-Matos <rriveram@opensource.cirrus.com>
+  - Richard Fitzgerald <rf@opensource.cirrus.com>
+
+description: |
+  CS35L45 is a Boosted Mono Class D Amplifier with DSP
+  Speaker Protection and Adaptive Battery Management.
+
+properties:
+  compatible:
+    enum:
+      - cirrus,cs35l45
+
+  reg:
+    maxItems: 1
+
+  '#sound-dai-cells':
+    const: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  vdd-a-supply:
+    description: voltage regulator phandle for the VDD_A supply
+
+  vdd-batt-supply:
+    description: voltage regulator phandle for the VDD_BATT supply
+
+  spi-max-frequency:
+    maximum: 5000000
+
+  cirrus,asp-sdout-hiz-ctrl:
+    description:
+      Audio serial port SDOUT Hi-Z control. Sets the Hi-Z
+      configuration for SDOUT pin of amplifier. Logical OR of
+      CS35L45_ASP_TX_HIZ_xxx values.
+    $ref: "/schemas/types.yaml#/definitions/uint32"
+    minimum: 0
+    maximum: 3
+    default: 2
+
+required:
+  - compatible
+  - reg
+  - "#sound-dai-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/sound/cs35l45.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        cs35l45: cs35l45@2 {
+          #sound-dai-cells = <1>;
+          compatible = "cirrus,cs35l45";
+          reg = <2>;
+          spi-max-frequency = <5000000>;
+          vdd-a-supply = <&dummy_vreg>;
+          vdd-batt-supply = <&dummy_vreg>;
+          reset-gpios = <&gpio 110 0>;
+          cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED |
+                                        CS35L45_ASP_TX_HIZ_DISABLED)>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index fd768d43e048..0c5971c3dd9e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4688,10 +4688,12 @@ CIRRUS LOGIC AUDIO CODEC DRIVERS
 M:	James Schulman <james.schulman@cirrus.com>
 M:	David Rhodes <david.rhodes@cirrus.com>
 M:	Lucas Tanure <tanureal@opensource.cirrus.com>
+M:	Richard Fitzgerald <rf@opensource.cirrus.com>
 L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
 L:	patches@opensource.cirrus.com
 S:	Maintained
 F:	Documentation/devicetree/bindings/sound/cirrus,cs*
+F:	include/dt-bindings/sound/cs*
 F:	sound/pci/hda/cs*
 F:	sound/soc/codecs/cs*
 
diff --git a/include/dt-bindings/sound/cs35l45.h b/include/dt-bindings/sound/cs35l45.h
new file mode 100644
index 000000000000..076da4b2c28d
--- /dev/null
+++ b/include/dt-bindings/sound/cs35l45.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * cs35l45.h -- CS35L45 ALSA SoC audio driver DT bindings header
+ *
+ * Copyright 2022 Cirrus Logic, Inc.
+ */
+
+#ifndef DT_CS35L45_H
+#define DT_CS35L45_H
+
+/*
+ * cirrus,asp-sdout-hiz-ctrl
+ *
+ * TX_HIZ_UNUSED:   TX pin high-impedance during unused slots.
+ * TX_HIZ_DISABLED: TX pin high-impedance when all channels disabled.
+ */
+#define CS35L45_ASP_TX_HIZ_UNUSED	0x1
+#define CS35L45_ASP_TX_HIZ_DISABLED	0x2
+
+#endif /* DT_CS35L45_H */
-- 
2.30.2


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

* [PATCH v2 5/5] ASoC: cs35l45: Add driver for Cirrus Logic CS35L45 Smart Amp
  2022-04-05 13:54 ` Richard Fitzgerald
@ 2022-04-05 13:54   ` Richard Fitzgerald
  -1 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: devicetree, alsa-devel, patches, linux-kernel, James Schulman,
	Richard Fitzgerald, robh+dt

From: James Schulman <james.schulman@cirrus.com>

The CS35L45 is a 15 V Boosted Mono Class D Amplifier with DSP
Speaker Protection and Adaptive Battery Management.

This initial driver provides standard non-boosted audio operation
without the DSP.

Signed-off-by: James Schulman <james.schulman@cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/soc/codecs/Kconfig          |  30 ++
 sound/soc/codecs/Makefile         |   8 +
 sound/soc/codecs/cs35l45-i2c.c    |  72 ++++
 sound/soc/codecs/cs35l45-spi.c    |  72 ++++
 sound/soc/codecs/cs35l45-tables.c | 202 +++++++++
 sound/soc/codecs/cs35l45.c        | 693 ++++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l45.h        | 217 ++++++++++
 7 files changed, 1294 insertions(+)
 create mode 100644 sound/soc/codecs/cs35l45-i2c.c
 create mode 100644 sound/soc/codecs/cs35l45-spi.c
 create mode 100644 sound/soc/codecs/cs35l45-tables.c
 create mode 100644 sound/soc/codecs/cs35l45.c
 create mode 100644 sound/soc/codecs/cs35l45.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 21db336375ad..33edfd0ff079 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -65,6 +65,8 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_CS35L36
 	imply SND_SOC_CS35L41_SPI
 	imply SND_SOC_CS35L41_I2C
+	imply SND_SOC_CS35L45_I2C
+	imply SND_SOC_CS35L45_SPI
 	imply SND_SOC_CS42L42
 	imply SND_SOC_CS42L51_I2C
 	imply SND_SOC_CS42L52
@@ -656,6 +658,34 @@ config SND_SOC_CS35L41_I2C
 	select SND_SOC_CS35L41
 	select REGMAP_I2C
 
+config SND_SOC_CS35L45_TABLES
+	tristate
+
+config SND_SOC_CS35L45
+	tristate
+
+config SND_SOC_CS35L45_SPI
+	tristate "Cirrus Logic CS35L45 CODEC (SPI)"
+	depends on SPI_MASTER
+	select REGMAP
+	select REGMAP_SPI
+	select SND_SOC_CS35L45_TABLES
+	select SND_SOC_CS35L45
+	help
+	  Enable support for Cirrus Logic CS35L45 smart speaker amplifier
+	  with SPI control.
+
+config SND_SOC_CS35L45_I2C
+	tristate "Cirrus Logic CS35L45 CODEC (I2C)"
+	depends on I2C
+	select REGMAP
+	select REGMAP_I2C
+	select SND_SOC_CS35L45_TABLES
+	select SND_SOC_CS35L45
+	help
+	  Enable support for Cirrus Logic CS35L45 smart speaker amplifier
+	  with I2C control.
+
 config SND_SOC_CS42L42
 	tristate "Cirrus Logic CS42L42 CODEC"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 46a100b200b9..57e651532749 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -60,6 +60,10 @@ snd-soc-cs35l41-lib-objs := cs35l41-lib.o
 snd-soc-cs35l41-objs := cs35l41.o
 snd-soc-cs35l41-spi-objs := cs35l41-spi.o
 snd-soc-cs35l41-i2c-objs := cs35l41-i2c.o
+snd-soc-cs35l45-tables-objs := cs35l45-tables.o
+snd-soc-cs35l45-objs := cs35l45.o
+snd-soc-cs35l45-spi-objs := cs35l45-spi.o
+snd-soc-cs35l45-i2c-objs := cs35l45-i2c.o
 snd-soc-cs42l42-objs := cs42l42.o
 snd-soc-cs42l51-objs := cs42l51.o
 snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
@@ -406,6 +410,10 @@ obj-$(CONFIG_SND_SOC_CS35L41)	+= snd-soc-cs35l41.o
 obj-$(CONFIG_SND_SOC_CS35L41_LIB)	+= snd-soc-cs35l41-lib.o
 obj-$(CONFIG_SND_SOC_CS35L41_SPI)	+= snd-soc-cs35l41-spi.o
 obj-$(CONFIG_SND_SOC_CS35L41_I2C)	+= snd-soc-cs35l41-i2c.o
+obj-$(CONFIG_SND_SOC_CS35L45_TABLES)	+= snd-soc-cs35l45-tables.o
+obj-$(CONFIG_SND_SOC_CS35L45)	+= snd-soc-cs35l45.o
+obj-$(CONFIG_SND_SOC_CS35L45_SPI)	+= snd-soc-cs35l45-spi.o
+obj-$(CONFIG_SND_SOC_CS35L45_I2C)	+= snd-soc-cs35l45-i2c.o
 obj-$(CONFIG_SND_SOC_CS42L42)	+= snd-soc-cs42l42.o
 obj-$(CONFIG_SND_SOC_CS42L51)	+= snd-soc-cs42l51.o
 obj-$(CONFIG_SND_SOC_CS42L51_I2C)	+= snd-soc-cs42l51-i2c.o
diff --git a/sound/soc/codecs/cs35l45-i2c.c b/sound/soc/codecs/cs35l45-i2c.c
new file mode 100644
index 000000000000..18ebf1e14b91
--- /dev/null
+++ b/sound/soc/codecs/cs35l45-i2c.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45-i2c.c -- CS35L45 I2C driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#include "cs35l45.h"
+
+static int cs35l45_i2c_probe(struct i2c_client *client)
+{
+	struct cs35l45_private *cs35l45;
+	struct device *dev = &client->dev;
+	int ret;
+
+	cs35l45 = devm_kzalloc(dev, sizeof(struct cs35l45_private), GFP_KERNEL);
+	if (!cs35l45)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, cs35l45);
+	cs35l45->regmap = devm_regmap_init_i2c(client, &cs35l45_i2c_regmap);
+	if (IS_ERR(cs35l45->regmap)) {
+		ret = PTR_ERR(cs35l45->regmap);
+		dev_err(dev, "Failed to allocate register map: %d\n", ret);
+		return ret;
+	}
+
+	cs35l45->dev = dev;
+
+	return cs35l45_probe(cs35l45);
+}
+
+static int cs35l45_i2c_remove(struct i2c_client *client)
+{
+	struct cs35l45_private *cs35l45 = i2c_get_clientdata(client);
+
+	return cs35l45_remove(cs35l45);
+}
+
+static const struct of_device_id cs35l45_of_match[] = {
+	{ .compatible = "cirrus,cs35l45" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, cs35l45_of_match);
+
+static const struct i2c_device_id cs35l45_id_i2c[] = {
+	{ "cs35l45", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, cs35l45_id_i2c);
+
+static struct i2c_driver cs35l45_i2c_driver = {
+	.driver = {
+		.name		= "cs35l45",
+		.of_match_table = cs35l45_of_match,
+		.pm		= &cs35l45_pm_ops,
+	},
+	.id_table	= cs35l45_id_i2c,
+	.probe_new	= cs35l45_i2c_probe,
+	.remove		= cs35l45_i2c_remove,
+};
+module_i2c_driver(cs35l45_i2c_driver);
+
+MODULE_DESCRIPTION("I2C CS35L45 driver");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45-spi.c b/sound/soc/codecs/cs35l45-spi.c
new file mode 100644
index 000000000000..445f57ff1fcd
--- /dev/null
+++ b/sound/soc/codecs/cs35l45-spi.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45-spi.c -- CS35L45 SPI driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "cs35l45.h"
+
+static int cs35l45_spi_probe(struct spi_device *spi)
+{
+	struct cs35l45_private *cs35l45;
+	struct device *dev = &spi->dev;
+	int ret;
+
+	cs35l45 = devm_kzalloc(dev, sizeof(struct cs35l45_private), GFP_KERNEL);
+	if (cs35l45 == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, cs35l45);
+	cs35l45->regmap = devm_regmap_init_spi(spi, &cs35l45_spi_regmap);
+	if (IS_ERR(cs35l45->regmap)) {
+		ret = PTR_ERR(cs35l45->regmap);
+		dev_err(dev, "Failed to allocate register map: %d\n", ret);
+		return ret;
+	}
+
+	cs35l45->dev = dev;
+
+	return cs35l45_probe(cs35l45);
+}
+
+static void cs35l45_spi_remove(struct spi_device *spi)
+{
+	struct cs35l45_private *cs35l45 = spi_get_drvdata(spi);
+
+	cs35l45_remove(cs35l45);
+}
+
+static const struct of_device_id cs35l45_of_match[] = {
+	{ .compatible = "cirrus,cs35l45" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, cs35l45_of_match);
+
+static const struct spi_device_id cs35l45_id_spi[] = {
+	{ "cs35l45", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(spi, cs35l45_id_spi);
+
+static struct spi_driver cs35l45_spi_driver = {
+	.driver = {
+		.name		= "cs35l45",
+		.of_match_table = cs35l45_of_match,
+		.pm		= &cs35l45_pm_ops,
+	},
+	.id_table	= cs35l45_id_spi,
+	.probe		= cs35l45_spi_probe,
+	.remove		= cs35l45_spi_remove,
+};
+module_spi_driver(cs35l45_spi_driver);
+
+MODULE_DESCRIPTION("SPI CS35L45 driver");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c
new file mode 100644
index 000000000000..95d5b3606448
--- /dev/null
+++ b/sound/soc/codecs/cs35l45-tables.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45-tables.c -- CS35L45 ALSA SoC audio driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "cs35l45.h"
+
+static const struct reg_sequence cs35l45_patch[] = {
+	{ 0x00000040,			0x00000055 },
+	{ 0x00000040,			0x000000AA },
+	{ 0x00000044,			0x00000055 },
+	{ 0x00000044,			0x000000AA },
+	{ 0x00006480,			0x0830500A },
+	{ 0x00007C60,			0x1000850B },
+	{ CS35L45_BOOST_OV_CFG,		0x007000D0 },
+	{ CS35L45_LDPM_CONFIG,		0x0001B636 },
+	{ 0x00002C08,			0x00000009 },
+	{ 0x00006850,			0x0A30FFC4 },
+	{ 0x00003820,			0x00040100 },
+	{ 0x00003824,			0x00000000 },
+	{ 0x00007CFC,			0x62870004 },
+	{ 0x00007C60,			0x1001850B },
+	{ 0x00000040,			0x00000000 },
+	{ 0x00000044,			0x00000000 },
+	{ CS35L45_BOOST_CCM_CFG,	0xF0000003 },
+	{ CS35L45_BOOST_DCM_CFG,	0x08710220 },
+	{ CS35L45_ERROR_RELEASE,	0x00200000 },
+};
+
+int cs35l45_apply_patch(struct cs35l45_private *cs35l45)
+{
+	return regmap_register_patch(cs35l45->regmap, cs35l45_patch,
+				     ARRAY_SIZE(cs35l45_patch));
+}
+EXPORT_SYMBOL_GPL(cs35l45_apply_patch);
+
+static const struct reg_default cs35l45_defaults[] = {
+	{ CS35L45_BLOCK_ENABLES,		0x00003323 },
+	{ CS35L45_BLOCK_ENABLES2,		0x00000010 },
+	{ CS35L45_REFCLK_INPUT,			0x00000510 },
+	{ CS35L45_GLOBAL_SAMPLE_RATE,		0x00000003 },
+	{ CS35L45_ASP_ENABLES1,			0x00000000 },
+	{ CS35L45_ASP_CONTROL1,			0x00000028 },
+	{ CS35L45_ASP_CONTROL2,			0x18180200 },
+	{ CS35L45_ASP_CONTROL3,			0x00000002 },
+	{ CS35L45_ASP_FRAME_CONTROL1,		0x03020100 },
+	{ CS35L45_ASP_FRAME_CONTROL2,		0x00000004 },
+	{ CS35L45_ASP_FRAME_CONTROL5,		0x00000100 },
+	{ CS35L45_ASP_DATA_CONTROL1,		0x00000018 },
+	{ CS35L45_ASP_DATA_CONTROL5,		0x00000018 },
+	{ CS35L45_DACPCM1_INPUT,		0x00000008 },
+	{ CS35L45_ASPTX1_INPUT,			0x00000018 },
+	{ CS35L45_ASPTX2_INPUT,			0x00000019 },
+	{ CS35L45_ASPTX3_INPUT,			0x00000020 },
+	{ CS35L45_ASPTX4_INPUT,			0x00000028 },
+	{ CS35L45_ASPTX5_INPUT,			0x00000048 },
+	{ CS35L45_AMP_PCM_CONTROL,		0x00100000 },
+};
+
+static bool cs35l45_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L45_DEVID ... CS35L45_OTPID:
+	case CS35L45_SFT_RESET:
+	case CS35L45_GLOBAL_ENABLES:
+	case CS35L45_BLOCK_ENABLES:
+	case CS35L45_BLOCK_ENABLES2:
+	case CS35L45_ERROR_RELEASE:
+	case CS35L45_REFCLK_INPUT:
+	case CS35L45_GLOBAL_SAMPLE_RATE:
+	case CS35L45_ASP_ENABLES1:
+	case CS35L45_ASP_CONTROL1:
+	case CS35L45_ASP_CONTROL2:
+	case CS35L45_ASP_CONTROL3:
+	case CS35L45_ASP_FRAME_CONTROL1:
+	case CS35L45_ASP_FRAME_CONTROL2:
+	case CS35L45_ASP_FRAME_CONTROL5:
+	case CS35L45_ASP_DATA_CONTROL1:
+	case CS35L45_ASP_DATA_CONTROL5:
+	case CS35L45_DACPCM1_INPUT:
+	case CS35L45_ASPTX1_INPUT:
+	case CS35L45_ASPTX2_INPUT:
+	case CS35L45_ASPTX3_INPUT:
+	case CS35L45_ASPTX4_INPUT:
+	case CS35L45_ASPTX5_INPUT:
+	case CS35L45_AMP_PCM_CONTROL:
+	case CS35L45_AMP_PCM_HPF_TST:
+	case CS35L45_IRQ1_EINT_4:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cs35l45_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L45_DEVID ... CS35L45_OTPID:
+	case CS35L45_SFT_RESET:
+	case CS35L45_GLOBAL_ENABLES:
+	case CS35L45_ERROR_RELEASE:
+	case CS35L45_AMP_PCM_HPF_TST:	/* not cachable */
+	case CS35L45_IRQ1_EINT_4:
+		return true;
+	default:
+		return false;
+	}
+}
+
+const struct regmap_config cs35l45_i2c_regmap = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+	.max_register = CS35L45_LASTREG,
+	.reg_defaults = cs35l45_defaults,
+	.num_reg_defaults = ARRAY_SIZE(cs35l45_defaults),
+	.volatile_reg = cs35l45_volatile_reg,
+	.readable_reg = cs35l45_readable_reg,
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(cs35l45_i2c_regmap);
+
+const struct regmap_config cs35l45_spi_regmap = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.pad_bits = 16,
+	.reg_stride = 4,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+	.max_register = CS35L45_LASTREG,
+	.reg_defaults = cs35l45_defaults,
+	.num_reg_defaults = ARRAY_SIZE(cs35l45_defaults),
+	.volatile_reg = cs35l45_volatile_reg,
+	.readable_reg = cs35l45_readable_reg,
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(cs35l45_spi_regmap);
+
+static const struct {
+	u8 cfg_id;
+	u32 freq;
+} cs35l45_pll_refclk_freq[] = {
+	{ 0x0C,   128000 },
+	{ 0x0F,   256000 },
+	{ 0x11,   384000 },
+	{ 0x12,   512000 },
+	{ 0x15,   768000 },
+	{ 0x17,  1024000 },
+	{ 0x19,  1411200 },
+	{ 0x1B,  1536000 },
+	{ 0x1C,  2116800 },
+	{ 0x1D,  2048000 },
+	{ 0x1E,  2304000 },
+	{ 0x1F,  2822400 },
+	{ 0x21,  3072000 },
+	{ 0x23,  4233600 },
+	{ 0x24,  4096000 },
+	{ 0x25,  4608000 },
+	{ 0x26,  5644800 },
+	{ 0x27,  6000000 },
+	{ 0x28,  6144000 },
+	{ 0x29,  6350400 },
+	{ 0x2A,  6912000 },
+	{ 0x2D,  7526400 },
+	{ 0x2E,  8467200 },
+	{ 0x2F,  8192000 },
+	{ 0x30,  9216000 },
+	{ 0x31, 11289600 },
+	{ 0x33, 12288000 },
+	{ 0x37, 16934400 },
+	{ 0x38, 18432000 },
+	{ 0x39, 22579200 },
+	{ 0x3B, 24576000 },
+};
+
+unsigned int cs35l45_get_clk_freq_id(unsigned int freq)
+{
+	int i;
+
+	if (freq == 0)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(cs35l45_pll_refclk_freq); ++i) {
+		if (cs35l45_pll_refclk_freq[i].freq == freq)
+			return cs35l45_pll_refclk_freq[i].cfg_id;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(cs35l45_get_clk_freq_id);
+
+MODULE_DESCRIPTION("ASoC CS35L45 driver tables");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c
new file mode 100644
index 000000000000..7822626d3693
--- /dev/null
+++ b/sound/soc/codecs/cs35l45.c
@@ -0,0 +1,693 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45.c - CS35L45 ALSA SoC audio driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "cs35l45.h"
+
+static int cs35l45_global_en_ev(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
+
+	dev_dbg(cs35l45->dev, "%s event : %x\n", __func__, event);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		regmap_write(cs35l45->regmap, CS35L45_GLOBAL_ENABLES,
+			     CS35L45_GLOBAL_EN_MASK);
+
+		usleep_range(CS35L45_POST_GLOBAL_EN_US, CS35L45_POST_GLOBAL_EN_US + 100);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		usleep_range(CS35L45_PRE_GLOBAL_DIS_US, CS35L45_PRE_GLOBAL_DIS_US + 100);
+
+		regmap_write(cs35l45->regmap, CS35L45_GLOBAL_ENABLES, 0);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const char * const cs35l45_asp_tx_txt[] = {
+	"Zero", "ASP_RX1", "ASP_RX2",
+	"VMON", "IMON", "ERR_VOL",
+	"VDD_BATTMON", "VDD_BSTMON",
+	"Interpolator", "IL_TARGET",
+};
+
+static const unsigned int cs35l45_asp_tx_val[] = {
+	CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2,
+	CS35L45_PCM_SRC_VMON, CS35L45_PCM_SRC_IMON, CS35L45_PCM_SRC_ERR_VOL,
+	CS35L45_PCM_SRC_VDD_BATTMON, CS35L45_PCM_SRC_VDD_BSTMON,
+	CS35L45_PCM_SRC_INTERPOLATOR, CS35L45_PCM_SRC_IL_TARGET,
+};
+
+static const struct soc_enum cs35l45_asp_tx_enums[] = {
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX1_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX2_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX3_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX4_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX5_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+};
+
+static const char * const cs35l45_dac_txt[] = {
+	"Zero", "ASP_RX1", "ASP_RX2"
+};
+
+static const unsigned int cs35l45_dac_val[] = {
+	CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2
+};
+
+static const struct soc_enum cs35l45_dacpcm_enums[] = {
+	SOC_VALUE_ENUM_SINGLE(CS35L45_DACPCM1_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_dac_txt), cs35l45_dac_txt,
+			      cs35l45_dac_val),
+};
+
+static const struct snd_kcontrol_new cs35l45_asp_muxes[] = {
+	SOC_DAPM_ENUM("ASP_TX1 Source", cs35l45_asp_tx_enums[0]),
+	SOC_DAPM_ENUM("ASP_TX2 Source", cs35l45_asp_tx_enums[1]),
+	SOC_DAPM_ENUM("ASP_TX3 Source", cs35l45_asp_tx_enums[2]),
+	SOC_DAPM_ENUM("ASP_TX4 Source", cs35l45_asp_tx_enums[3]),
+	SOC_DAPM_ENUM("ASP_TX5 Source", cs35l45_asp_tx_enums[4]),
+};
+
+static const struct snd_kcontrol_new cs35l45_dac_muxes[] = {
+	SOC_DAPM_ENUM("DACPCM1 Source", cs35l45_dacpcm_enums[0]),
+};
+
+static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("GLOBAL_EN", SND_SOC_NOPM, 0, 0,
+			    cs35l45_global_en_ev,
+			    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_SUPPLY("ASP_EN", CS35L45_BLOCK_ENABLES2, CS35L45_ASP_EN_SHIFT, 0, NULL, 0),
+
+	SND_SOC_DAPM_SIGGEN("VMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("IMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("VDD_BATTMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("VDD_BSTMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("ERR_VOL"),
+	SND_SOC_DAPM_SIGGEN("AMP_INTP"),
+	SND_SOC_DAPM_SIGGEN("IL_TARGET"),
+	SND_SOC_DAPM_ADC("VMON", NULL, CS35L45_BLOCK_ENABLES, CS35L45_VMON_EN_SHIFT, 0),
+	SND_SOC_DAPM_ADC("IMON", NULL, CS35L45_BLOCK_ENABLES, CS35L45_IMON_EN_SHIFT, 0),
+	SND_SOC_DAPM_ADC("VDD_BATTMON", NULL, CS35L45_BLOCK_ENABLES,
+			 CS35L45_VDD_BATTMON_EN_SHIFT, 0),
+	SND_SOC_DAPM_ADC("VDD_BSTMON", NULL, CS35L45_BLOCK_ENABLES,
+			 CS35L45_VDD_BSTMON_EN_SHIFT, 0),
+
+	SND_SOC_DAPM_AIF_IN("ASP_RX1", NULL, 0, CS35L45_ASP_ENABLES1, CS35L45_ASP_RX1_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_IN("ASP_RX2", NULL, 1, CS35L45_ASP_ENABLES1, CS35L45_ASP_RX2_EN_SHIFT, 0),
+
+	SND_SOC_DAPM_AIF_OUT("ASP_TX1", NULL, 0, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX1_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX2", NULL, 1, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX2_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX3", NULL, 2, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX3_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX4", NULL, 3, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX4_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX5", NULL, 3, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX5_EN_SHIFT, 0),
+
+	SND_SOC_DAPM_MUX("ASP_TX1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[0]),
+	SND_SOC_DAPM_MUX("ASP_TX2 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[1]),
+	SND_SOC_DAPM_MUX("ASP_TX3 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[2]),
+	SND_SOC_DAPM_MUX("ASP_TX4 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[3]),
+	SND_SOC_DAPM_MUX("ASP_TX5 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[4]),
+
+	SND_SOC_DAPM_MUX("DACPCM1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dac_muxes[0]),
+
+	SND_SOC_DAPM_OUT_DRV("AMP", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_OUTPUT("SPK"),
+};
+
+#define CS35L45_ASP_MUX_ROUTE(name) \
+	{ name" Source", "ASP_RX1",	 "ASP_RX1" }, \
+	{ name" Source", "ASP_RX2",	 "ASP_RX2" }, \
+	{ name" Source", "VMON",	 "VMON" }, \
+	{ name" Source", "IMON",	 "IMON" }, \
+	{ name" Source", "ERR_VOL",	 "ERR_VOL" }, \
+	{ name" Source", "VDD_BATTMON",	 "VDD_BATTMON" }, \
+	{ name" Source", "VDD_BSTMON",	 "VDD_BSTMON" }, \
+	{ name" Source", "Interpolator", "AMP_INTP" }, \
+	{ name" Source", "IL_TARGET",	 "IL_TARGET" }
+
+#define CS35L45_DAC_MUX_ROUTE(name) \
+	{ name" Source", "ASP_RX1",	"ASP_RX1" }, \
+	{ name" Source", "ASP_RX2",	"ASP_RX2" }
+
+static const struct snd_soc_dapm_route cs35l45_dapm_routes[] = {
+	/* Feedback */
+	{ "VMON", NULL, "VMON_SRC" },
+	{ "IMON", NULL, "IMON_SRC" },
+	{ "VDD_BATTMON", NULL, "VDD_BATTMON_SRC" },
+	{ "VDD_BSTMON", NULL, "VDD_BSTMON_SRC" },
+
+	{ "Capture", NULL, "ASP_TX1"},
+	{ "Capture", NULL, "ASP_TX2"},
+	{ "Capture", NULL, "ASP_TX3"},
+	{ "Capture", NULL, "ASP_TX4"},
+	{ "Capture", NULL, "ASP_TX5"},
+	{ "ASP_TX1", NULL, "ASP_TX1 Source"},
+	{ "ASP_TX2", NULL, "ASP_TX2 Source"},
+	{ "ASP_TX3", NULL, "ASP_TX3 Source"},
+	{ "ASP_TX4", NULL, "ASP_TX4 Source"},
+	{ "ASP_TX5", NULL, "ASP_TX5 Source"},
+
+	{ "ASP_TX1", NULL, "ASP_EN" },
+	{ "ASP_TX2", NULL, "ASP_EN" },
+	{ "ASP_TX3", NULL, "ASP_EN" },
+	{ "ASP_TX4", NULL, "ASP_EN" },
+	{ "ASP_TX1", NULL, "GLOBAL_EN" },
+	{ "ASP_TX2", NULL, "GLOBAL_EN" },
+	{ "ASP_TX3", NULL, "GLOBAL_EN" },
+	{ "ASP_TX4", NULL, "GLOBAL_EN" },
+	{ "ASP_TX5", NULL, "GLOBAL_EN" },
+
+	CS35L45_ASP_MUX_ROUTE("ASP_TX1"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX2"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX3"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX4"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX5"),
+
+	/* Playback */
+	{ "ASP_RX1", NULL, "Playback" },
+	{ "ASP_RX2", NULL, "Playback" },
+	{ "ASP_RX1", NULL, "ASP_EN" },
+	{ "ASP_RX2", NULL, "ASP_EN" },
+
+	{ "AMP", NULL, "DACPCM1 Source"},
+	{ "AMP", NULL, "GLOBAL_EN"},
+
+	CS35L45_DAC_MUX_ROUTE("DACPCM1"),
+
+	{ "SPK", NULL, "AMP"},
+};
+
+static const DECLARE_TLV_DB_SCALE(cs35l45_dig_pcm_vol_tlv, -10225, 25, true);
+
+static const struct snd_kcontrol_new cs35l45_controls[] = {
+	/* Ignore bit 0: it is beyond the resolution of TLV_DB_SCALE */
+	SOC_SINGLE_S_TLV("Digital PCM Volume",
+			 CS35L45_AMP_PCM_CONTROL,
+			 CS35L45_AMP_VOL_PCM_SHIFT + 1,
+			 -409, 48,
+			 (CS35L45_AMP_VOL_PCM_WIDTH - 1) - 1,
+			 0, cs35l45_dig_pcm_vol_tlv),
+};
+
+static int cs35l45_set_pll(struct cs35l45_private *cs35l45, unsigned int freq)
+{
+	unsigned int val;
+	int freq_id;
+
+	freq_id = cs35l45_get_clk_freq_id(freq);
+	if (freq_id < 0) {
+		dev_err(cs35l45->dev, "Invalid freq: %u\n", freq);
+		return -EINVAL;
+	}
+
+	regmap_read(cs35l45->regmap, CS35L45_REFCLK_INPUT, &val);
+	val = (val & CS35L45_PLL_REFCLK_FREQ_MASK) >> CS35L45_PLL_REFCLK_FREQ_SHIFT;
+	if (val == freq_id)
+		return 0;
+
+	regmap_set_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_OPEN_LOOP_MASK);
+	regmap_update_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT,
+			   CS35L45_PLL_REFCLK_FREQ_MASK,
+			   freq_id << CS35L45_PLL_REFCLK_FREQ_SHIFT);
+	regmap_clear_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_REFCLK_EN_MASK);
+	regmap_clear_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_OPEN_LOOP_MASK);
+	regmap_set_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_REFCLK_EN_MASK);
+
+	return 0;
+}
+
+static int cs35l45_asp_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(codec_dai->component);
+	unsigned int asp_fmt, fsync_inv, bclk_inv;
+
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_CBC_CFC:
+		break;
+	default:
+		dev_err(cs35l45->dev, "Invalid DAI clocking\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		asp_fmt = CS35l45_ASP_FMT_DSP_A;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		asp_fmt = CS35L45_ASP_FMT_I2S;
+		break;
+	default:
+		dev_err(cs35l45->dev, "Invalid DAI format\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_IF:
+		fsync_inv = 1;
+		bclk_inv = 0;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		fsync_inv = 0;
+		bclk_inv = 1;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		fsync_inv = 1;
+		bclk_inv = 1;
+		break;
+	case SND_SOC_DAIFMT_NB_NF:
+		fsync_inv = 0;
+		bclk_inv = 0;
+		break;
+	default:
+		dev_warn(cs35l45->dev, "Invalid DAI clock polarity\n");
+		return -EINVAL;
+	}
+
+	regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL2,
+			   CS35L45_ASP_FMT_MASK |
+			   CS35L45_ASP_FSYNC_INV_MASK |
+			   CS35L45_ASP_BCLK_INV_MASK,
+			   (asp_fmt << CS35L45_ASP_FMT_SHIFT) |
+			   (fsync_inv << CS35L45_ASP_FSYNC_INV_SHIFT) |
+			   (bclk_inv << CS35L45_ASP_BCLK_INV_SHIFT));
+
+	return 0;
+}
+
+static int cs35l45_asp_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int asp_width, asp_wl, global_fs, slot_multiple, asp_fmt;
+	int bclk;
+
+	switch (params_rate(params)) {
+	case 44100:
+		global_fs = CS35L45_44P100_KHZ;
+		break;
+	case 48000:
+		global_fs = CS35L45_48P0_KHZ;
+		break;
+	case 88200:
+		global_fs = CS35L45_88P200_KHZ;
+		break;
+	case 96000:
+		global_fs = CS35L45_96P0_KHZ;
+		break;
+	default:
+		dev_warn(cs35l45->dev, "Unsupported sample rate (%d)\n",
+			 params_rate(params));
+		return -EINVAL;
+	}
+
+	regmap_update_bits(cs35l45->regmap, CS35L45_GLOBAL_SAMPLE_RATE,
+			   CS35L45_GLOBAL_FS_MASK,
+			   global_fs << CS35L45_GLOBAL_FS_SHIFT);
+
+	asp_wl = params_width(params);
+
+	if (cs35l45->slot_width)
+		asp_width = cs35l45->slot_width;
+	else
+		asp_width = params_width(params);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL2,
+				   CS35L45_ASP_WIDTH_RX_MASK,
+				   asp_width << CS35L45_ASP_WIDTH_RX_SHIFT);
+
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_DATA_CONTROL5,
+				   CS35L45_ASP_WL_MASK,
+				   asp_wl << CS35L45_ASP_WL_SHIFT);
+	} else {
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL2,
+				   CS35L45_ASP_WIDTH_TX_MASK,
+				   asp_width << CS35L45_ASP_WIDTH_TX_SHIFT);
+
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_DATA_CONTROL1,
+				   CS35L45_ASP_WL_MASK,
+				   asp_wl << CS35L45_ASP_WL_SHIFT);
+	}
+
+	if (cs35l45->sysclk_set)
+		return 0;
+
+	/* I2S always has an even number of channels */
+	regmap_read(cs35l45->regmap, CS35L45_ASP_CONTROL2, &asp_fmt);
+	asp_fmt = (asp_fmt & CS35L45_ASP_FMT_MASK) >> CS35L45_ASP_FMT_SHIFT;
+	if (asp_fmt == CS35L45_ASP_FMT_I2S)
+		slot_multiple = 2;
+	else
+		slot_multiple = 1;
+
+	bclk = snd_soc_tdm_params_to_bclk(params, asp_width,
+					  cs35l45->slot_count, slot_multiple);
+
+	return cs35l45_set_pll(cs35l45, bclk);
+}
+
+static int cs35l45_asp_set_tdm_slot(struct snd_soc_dai *dai,
+				    unsigned int tx_mask, unsigned int rx_mask,
+				    int slots, int slot_width)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+
+	if (slot_width && ((slot_width < 16) || (slot_width > 128)))
+		return -EINVAL;
+
+	cs35l45->slot_width = slot_width;
+	cs35l45->slot_count = slots;
+
+	return 0;
+}
+
+static int cs35l45_asp_set_sysclk(struct snd_soc_dai *dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+	int ret;
+
+	if (clk_id != 0) {
+		dev_err(cs35l45->dev, "Invalid clk_id %d\n", clk_id);
+		return -EINVAL;
+	}
+
+	cs35l45->sysclk_set = false;
+	if (freq == 0)
+		return 0;
+
+	ret = cs35l45_set_pll(cs35l45, freq);
+	if (ret < 0)
+		return -EINVAL;
+
+	cs35l45->sysclk_set = true;
+
+	return 0;
+}
+
+static int cs35l45_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int global_fs, val, hpf_tune;
+
+	if (mute)
+		return 0;
+
+	regmap_read(cs35l45->regmap, CS35L45_GLOBAL_SAMPLE_RATE, &global_fs);
+	global_fs = (global_fs & CS35L45_GLOBAL_FS_MASK) >> CS35L45_GLOBAL_FS_SHIFT;
+	switch (global_fs) {
+	case CS35L45_44P100_KHZ:
+		hpf_tune = CS35L45_HPF_44P1;
+		break;
+	case CS35L45_88P200_KHZ:
+		hpf_tune = CS35L45_HPF_88P2;
+		break;
+	default:
+		hpf_tune = CS35l45_HPF_DEFAULT;
+		break;
+	}
+
+	regmap_read(cs35l45->regmap, CS35L45_AMP_PCM_HPF_TST, &val);
+	if (val != hpf_tune) {
+		struct reg_sequence hpf_override_seq[] = {
+			{ 0x00000040,			0x00000055 },
+			{ 0x00000040,			0x000000AA },
+			{ 0x00000044,			0x00000055 },
+			{ 0x00000044,			0x000000AA },
+			{ CS35L45_AMP_PCM_HPF_TST,	hpf_tune },
+			{ 0x00000040,			0x00000000 },
+			{ 0x00000044,			0x00000000 },
+		};
+		regmap_multi_reg_write(cs35l45->regmap, hpf_override_seq,
+				       ARRAY_SIZE(hpf_override_seq));
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops cs35l45_asp_dai_ops = {
+	.set_fmt = cs35l45_asp_set_fmt,
+	.hw_params = cs35l45_asp_hw_params,
+	.set_tdm_slot = cs35l45_asp_set_tdm_slot,
+	.set_sysclk = cs35l45_asp_set_sysclk,
+	.mute_stream = cs35l45_mute_stream,
+};
+
+static struct snd_soc_dai_driver cs35l45_dai[] = {
+	{
+		.name = "cs35l45",
+		.playback = {
+			.stream_name = "Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = CS35L45_RATES,
+			.formats = CS35L45_FORMATS,
+		},
+		.capture = {
+			.stream_name = "Capture",
+			.channels_min = 1,
+			.channels_max = 5,
+			.rates = CS35L45_RATES,
+			.formats = CS35L45_FORMATS,
+		},
+		.symmetric_rate = true,
+		.symmetric_sample_bits = true,
+		.ops = &cs35l45_asp_dai_ops,
+	},
+};
+
+static const struct snd_soc_component_driver cs35l45_component = {
+	.dapm_widgets = cs35l45_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(cs35l45_dapm_widgets),
+
+	.dapm_routes = cs35l45_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(cs35l45_dapm_routes),
+
+	.controls = cs35l45_controls,
+	.num_controls = ARRAY_SIZE(cs35l45_controls),
+
+	.name = "cs35l45",
+};
+
+static int __maybe_unused cs35l45_runtime_suspend(struct device *dev)
+{
+	struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
+
+	regcache_cache_only(cs35l45->regmap, true);
+
+	dev_dbg(cs35l45->dev, "Runtime suspended\n");
+
+	return 0;
+}
+
+static int __maybe_unused cs35l45_runtime_resume(struct device *dev)
+{
+	struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
+	int ret;
+
+	dev_dbg(cs35l45->dev, "Runtime resume\n");
+
+	regcache_cache_only(cs35l45->regmap, false);
+	ret = regcache_sync(cs35l45->regmap);
+	if (ret != 0)
+		dev_warn(cs35l45->dev, "regcache_sync failed: %d\n", ret);
+
+	/* Clear global error status */
+	regmap_clear_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK);
+	regmap_set_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK);
+	regmap_clear_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK);
+	return ret;
+}
+
+static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45)
+{
+	unsigned int val;
+
+	if (device_property_read_u32(cs35l45->dev,
+				     "cirrus,asp-sdout-hiz-ctrl", &val) == 0) {
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL3,
+				   CS35L45_ASP_DOUT_HIZ_CTRL_MASK,
+				   val << CS35L45_ASP_DOUT_HIZ_CTRL_SHIFT);
+	}
+
+	return 0;
+}
+
+static int cs35l45_initialize(struct cs35l45_private *cs35l45)
+{
+	struct device *dev = cs35l45->dev;
+	unsigned int dev_id[5];
+	unsigned int sts;
+	int ret;
+
+	ret = regmap_read_poll_timeout(cs35l45->regmap, CS35L45_IRQ1_EINT_4, sts,
+				       (sts & CS35L45_OTP_BOOT_DONE_STS_MASK),
+				       1000, 5000);
+	if (ret < 0) {
+		dev_err(cs35l45->dev, "Timeout waiting for OTP boot\n");
+		return ret;
+	}
+
+	ret = regmap_bulk_read(cs35l45->regmap, CS35L45_DEVID, dev_id, ARRAY_SIZE(dev_id));
+	if (ret) {
+		dev_err(cs35l45->dev, "Get Device ID failed: %d\n", ret);
+		return ret;
+	}
+
+	switch (dev_id[0]) {
+	case 0x35A450:
+		break;
+	default:
+		dev_err(cs35l45->dev, "Bad DEVID 0x%x\n", dev_id[0]);
+		return -ENODEV;
+	}
+
+	dev_info(cs35l45->dev, "Cirrus Logic CS35L45: REVID %02X OTPID %02X\n",
+		 dev_id[1], dev_id[4]);
+
+	regmap_write(cs35l45->regmap, CS35L45_IRQ1_EINT_4,
+		     CS35L45_OTP_BOOT_DONE_STS_MASK | CS35L45_OTP_BUSY_MASK);
+
+	ret = cs35l45_apply_patch(cs35l45);
+	if (ret < 0) {
+		dev_err(dev, "Failed to apply init patch %d\n", ret);
+		return ret;
+	}
+
+	/* Default to boost bypass */
+	regmap_update_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES, CS35L45_BST_EN_MASK,
+			   CS35L45_BST_DISABLE_FET_ON << CS35L45_BST_EN_SHIFT);
+
+	ret = cs35l45_apply_property_config(cs35l45);
+	if (ret < 0)
+		return ret;
+
+	pm_runtime_set_autosuspend_delay(cs35l45->dev, 3000);
+	pm_runtime_use_autosuspend(cs35l45->dev);
+	pm_runtime_set_active(cs35l45->dev);
+	pm_runtime_enable(cs35l45->dev);
+
+	return 0;
+}
+
+int cs35l45_probe(struct cs35l45_private *cs35l45)
+{
+	struct device *dev = cs35l45->dev;
+	int ret;
+
+	cs35l45->vdd_batt = devm_regulator_get(dev, "vdd-batt");
+	if (IS_ERR(cs35l45->vdd_batt))
+		return dev_err_probe(dev, PTR_ERR(cs35l45->vdd_batt),
+				     "Failed to request vdd-batt\n");
+
+	cs35l45->vdd_a = devm_regulator_get(dev, "vdd-a");
+	if (IS_ERR(cs35l45->vdd_a))
+		return dev_err_probe(dev, PTR_ERR(cs35l45->vdd_a),
+				     "Failed to request vdd-a\n");
+
+	/* VDD_BATT must always be enabled before other supplies */
+	ret = regulator_enable(cs35l45->vdd_batt);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to enable vdd-batt\n");
+
+	ret = regulator_enable(cs35l45->vdd_a);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to enable vdd-a\n");
+
+	/* If reset is shared only one instance can claim it */
+	cs35l45->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(cs35l45->reset_gpio)) {
+		ret = PTR_ERR(cs35l45->reset_gpio);
+		cs35l45->reset_gpio = NULL;
+		if (ret == -EBUSY) {
+			dev_dbg(dev, "Reset line busy, assuming shared reset\n");
+		} else {
+			dev_err_probe(dev, ret, "Failed to get reset GPIO\n");
+			goto err;
+		}
+	}
+
+	if (cs35l45->reset_gpio) {
+		usleep_range(CS35L45_RESET_HOLD_US, CS35L45_RESET_HOLD_US + 100);
+		gpiod_set_value_cansleep(cs35l45->reset_gpio, 1);
+	}
+
+	usleep_range(CS35L45_RESET_US, CS35L45_RESET_US + 100);
+
+	ret = cs35l45_initialize(cs35l45);
+	if (ret < 0)
+		goto err_reset;
+
+	ret = devm_snd_soc_register_component(dev, &cs35l45_component,
+					      cs35l45_dai,
+					      ARRAY_SIZE(cs35l45_dai));
+	if (ret < 0)
+		goto err_reset;
+
+	return 0;
+
+err_reset:
+	gpiod_set_value_cansleep(cs35l45->reset_gpio, 0);
+err:
+	regulator_disable(cs35l45->vdd_a);
+	regulator_disable(cs35l45->vdd_batt);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(cs35l45_probe);
+
+int cs35l45_remove(struct cs35l45_private *cs35l45)
+{
+	pm_runtime_disable(cs35l45->dev);
+
+	gpiod_set_value_cansleep(cs35l45->reset_gpio, 0);
+	regulator_disable(cs35l45->vdd_a);
+	/* VDD_BATT must be the last to power-off */
+	regulator_disable(cs35l45->vdd_batt);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cs35l45_remove);
+
+const struct dev_pm_ops cs35l45_pm_ops = {
+	SET_RUNTIME_PM_OPS(cs35l45_runtime_suspend, cs35l45_runtime_resume, NULL)
+};
+EXPORT_SYMBOL_GPL(cs35l45_pm_ops);
+
+MODULE_DESCRIPTION("ASoC CS35L45 driver");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h
new file mode 100644
index 000000000000..4e266d19cd1c
--- /dev/null
+++ b/sound/soc/codecs/cs35l45.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * cs35l45.h - CS35L45 ALSA SoC audio driver
+ *
+ * Copyright 2019-2022 Cirrus Logic, Inc.
+ *
+ * Author: James Schulman <james.schulman@cirrus.com>
+ *
+ */
+
+#ifndef CS35L45_H
+#define CS35L45_H
+
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define CS35L45_DEVID				0x00000000
+#define CS35L45_REVID				0x00000004
+#define CS35L45_RELID				0x0000000C
+#define CS35L45_OTPID				0x00000010
+#define CS35L45_SFT_RESET			0x00000020
+#define CS35L45_GLOBAL_ENABLES			0x00002014
+#define CS35L45_BLOCK_ENABLES			0x00002018
+#define CS35L45_BLOCK_ENABLES2			0x0000201C
+#define CS35L45_ERROR_RELEASE			0x00002034
+#define CS35L45_REFCLK_INPUT			0x00002C04
+#define CS35L45_GLOBAL_SAMPLE_RATE		0x00002C0C
+#define CS35L45_BOOST_CCM_CFG			0x00003808
+#define CS35L45_BOOST_DCM_CFG			0x0000380C
+#define CS35L45_BOOST_OV_CFG			0x0000382C
+#define CS35L45_ASP_ENABLES1			0x00004800
+#define CS35L45_ASP_CONTROL1			0x00004804
+#define CS35L45_ASP_CONTROL2			0x00004808
+#define CS35L45_ASP_CONTROL3			0x0000480C
+#define CS35L45_ASP_FRAME_CONTROL1		0x00004810
+#define CS35L45_ASP_FRAME_CONTROL2		0x00004814
+#define CS35L45_ASP_FRAME_CONTROL5		0x00004820
+#define CS35L45_ASP_DATA_CONTROL1		0x00004830
+#define CS35L45_ASP_DATA_CONTROL5		0x00004840
+#define CS35L45_DACPCM1_INPUT			0x00004C00
+#define CS35L45_ASPTX1_INPUT			0x00004C20
+#define CS35L45_ASPTX2_INPUT			0x00004C24
+#define CS35L45_ASPTX3_INPUT			0x00004C28
+#define CS35L45_ASPTX4_INPUT			0x00004C2C
+#define CS35L45_ASPTX5_INPUT			0x00004C30
+#define CS35L45_LDPM_CONFIG			0x00006404
+#define CS35L45_AMP_PCM_CONTROL			0x00007000
+#define CS35L45_AMP_PCM_HPF_TST			0x00007004
+#define CS35L45_IRQ1_EINT_4			0x0000E01C
+#define CS35L45_LASTREG				0x0000E01C
+
+/* SFT_RESET */
+#define CS35L45_SOFT_RESET_TRIGGER		0x5A000000
+
+/* GLOBAL_ENABLES */
+#define CS35L45_GLOBAL_EN_SHIFT			0
+#define CS35L45_GLOBAL_EN_MASK			BIT(0)
+
+/* BLOCK_ENABLES */
+#define CS35L45_IMON_EN_SHIFT			13
+#define CS35L45_VMON_EN_SHIFT			12
+#define CS35L45_VDD_BSTMON_EN_SHIFT		9
+#define CS35L45_VDD_BATTMON_EN_SHIFT		8
+#define CS35L45_BST_EN_SHIFT			4
+#define CS35L45_BST_EN_MASK			GENMASK(5, 4)
+
+#define CS35L45_BST_DISABLE_FET_ON              0x01
+
+/* BLOCK_ENABLES2 */
+#define CS35L45_ASP_EN_SHIFT			27
+
+/* ERROR_RELEASE */
+#define CS35L45_GLOBAL_ERR_RLS_MASK		BIT(11)
+
+/* REFCLK_INPUT */
+#define CS35L45_PLL_FORCE_EN_SHIFT		16
+#define CS35L45_PLL_FORCE_EN_MASK		BIT(16)
+#define CS35L45_PLL_OPEN_LOOP_SHIFT		11
+#define CS35L45_PLL_OPEN_LOOP_MASK		BIT(11)
+#define CS35L45_PLL_REFCLK_FREQ_SHIFT		5
+#define CS35L45_PLL_REFCLK_FREQ_MASK		GENMASK(10, 5)
+#define CS35L45_PLL_REFCLK_EN_SHIFT		4
+#define CS35L45_PLL_REFCLK_EN_MASK		BIT(4)
+#define CS35L45_PLL_REFCLK_SEL_SHIFT		0
+#define CS35L45_PLL_REFCLK_SEL_MASK		GENMASK(2, 0)
+
+#define CS35L45_PLL_REFCLK_SEL_BCLK		0x0
+
+/* GLOBAL_SAMPLE_RATE */
+#define CS35L45_GLOBAL_FS_SHIFT			0
+#define CS35L45_GLOBAL_FS_MASK			GENMASK(4, 0)
+
+#define CS35L45_48P0_KHZ			0x03
+#define CS35L45_96P0_KHZ			0x04
+#define CS35L45_44P100_KHZ			0x0B
+#define CS35L45_88P200_KHZ			0x0C
+
+/* ASP_ENABLES_1 */
+#define CS35L45_ASP_RX2_EN_SHIFT		17
+#define CS35L45_ASP_RX1_EN_SHIFT		16
+#define CS35L45_ASP_TX5_EN_SHIFT		4
+#define CS35L45_ASP_TX4_EN_SHIFT		3
+#define CS35L45_ASP_TX3_EN_SHIFT		2
+#define CS35L45_ASP_TX2_EN_SHIFT		1
+#define CS35L45_ASP_TX1_EN_SHIFT		0
+
+/* ASP_CONTROL2 */
+#define CS35L45_ASP_WIDTH_RX_SHIFT		24
+#define CS35L45_ASP_WIDTH_RX_MASK		GENMASK(31, 24)
+#define CS35L45_ASP_WIDTH_TX_SHIFT		16
+#define CS35L45_ASP_WIDTH_TX_MASK		GENMASK(23, 16)
+#define CS35L45_ASP_FMT_SHIFT			8
+#define CS35L45_ASP_FMT_MASK			GENMASK(10, 8)
+#define CS35L45_ASP_BCLK_INV_SHIFT		6
+#define CS35L45_ASP_BCLK_INV_MASK		BIT(6)
+#define CS35L45_ASP_FSYNC_INV_SHIFT		2
+#define CS35L45_ASP_FSYNC_INV_MASK		BIT(2)
+
+#define CS35l45_ASP_FMT_DSP_A			0
+#define CS35L45_ASP_FMT_I2S			2
+
+/* ASP_CONTROL3 */
+#define CS35L45_ASP_DOUT_HIZ_CTRL_SHIFT		0
+#define CS35L45_ASP_DOUT_HIZ_CTRL_MASK		GENMASK(1, 0)
+
+/* ASP_FRAME_CONTROL1 */
+#define CS35L45_ASP_TX4_SLOT_SHIFT		24
+#define CS35L45_ASP_TX4_SLOT_MASK		GENMASK(29, 24)
+#define CS35L45_ASP_TX3_SLOT_SHIFT		16
+#define CS35L45_ASP_TX3_SLOT_MASK		GENMASK(21, 16)
+#define CS35L45_ASP_TX2_SLOT_SHIFT		8
+#define CS35L45_ASP_TX2_SLOT_MASK		GENMASK(13, 8)
+#define CS35L45_ASP_TX1_SLOT_SHIFT		0
+#define CS35L45_ASP_TX1_SLOT_MASK		GENMASK(5, 0)
+
+#define CS35L45_ASP_TX_ALL_SLOTS		(CS35L45_ASP_TX4_SLOT_MASK | \
+						CS35L45_ASP_TX3_SLOT_MASK  | \
+						CS35L45_ASP_TX2_SLOT_MASK  | \
+						CS35L45_ASP_TX1_SLOT_MASK)
+/* ASP_FRAME_CONTROL5 */
+#define CS35L45_ASP_RX2_SLOT_SHIFT		8
+#define CS35L45_ASP_RX2_SLOT_MASK		GENMASK(13, 8)
+#define CS35L45_ASP_RX1_SLOT_SHIFT		0
+#define CS35L45_ASP_RX1_SLOT_MASK		GENMASK(5, 0)
+
+#define CS35L45_ASP_RX_ALL_SLOTS		(CS35L45_ASP_RX2_SLOT_MASK | \
+						CS35L45_ASP_RX1_SLOT_MASK)
+
+/* ASP_DATA_CONTROL1 */
+/* ASP_DATA_CONTROL5 */
+#define CS35L45_ASP_WL_SHIFT			0
+#define CS35L45_ASP_WL_MASK			GENMASK(5, 0)
+
+/* AMP_PCM_CONTROL */
+#define CS35L45_AMP_VOL_PCM_SHIFT		0
+#define CS35L45_AMP_VOL_PCM_WIDTH		11
+
+/* AMP_PCM_HPF_TST */
+#define CS35l45_HPF_DEFAULT			0x00000000
+#define CS35L45_HPF_44P1			0x000108BD
+#define CS35L45_HPF_88P2			0x0001045F
+
+/* IRQ1_EINT_4 */
+#define CS35L45_OTP_BOOT_DONE_STS_MASK		BIT(1)
+#define CS35L45_OTP_BUSY_MASK			BIT(0)
+
+/* Mixer sources */
+#define CS35L45_PCM_SRC_MASK			0x7F
+#define CS35L45_PCM_SRC_ZERO			0x00
+#define CS35L45_PCM_SRC_ASP_RX1			0x08
+#define CS35L45_PCM_SRC_ASP_RX2			0x09
+#define CS35L45_PCM_SRC_VMON			0x18
+#define CS35L45_PCM_SRC_IMON			0x19
+#define CS35L45_PCM_SRC_ERR_VOL			0x20
+#define CS35L45_PCM_SRC_CLASSH_TGT		0x21
+#define CS35L45_PCM_SRC_VDD_BATTMON		0x28
+#define CS35L45_PCM_SRC_VDD_BSTMON		0x29
+#define CS35L45_PCM_SRC_TEMPMON			0x3A
+#define CS35L45_PCM_SRC_INTERPOLATOR		0x40
+#define CS35L45_PCM_SRC_IL_TARGET		0x48
+
+#define CS35L45_RESET_HOLD_US			2000
+#define CS35L45_RESET_US			2000
+#define CS35L45_POST_GLOBAL_EN_US		5000
+#define CS35L45_PRE_GLOBAL_DIS_US		3000
+
+#define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+			 SNDRV_PCM_FMTBIT_S24_3LE| \
+			 SNDRV_PCM_FMTBIT_S24_LE)
+
+#define CS35L45_RATES (SNDRV_PCM_RATE_44100 | \
+		       SNDRV_PCM_RATE_48000 | \
+		       SNDRV_PCM_RATE_88200 | \
+		       SNDRV_PCM_RATE_96000)
+
+struct cs35l45_private {
+	struct device *dev;
+	struct regmap *regmap;
+	struct gpio_desc *reset_gpio;
+	struct regulator *vdd_batt;
+	struct regulator *vdd_a;
+	bool initialized;
+	bool sysclk_set;
+	u8 slot_width;
+	u8 slot_count;
+};
+
+extern const struct dev_pm_ops cs35l45_pm_ops;
+extern const struct regmap_config cs35l45_i2c_regmap;
+extern const struct regmap_config cs35l45_spi_regmap;
+int cs35l45_apply_patch(struct cs35l45_private *cs43l45);
+unsigned int cs35l45_get_clk_freq_id(unsigned int freq);
+int cs35l45_probe(struct cs35l45_private *cs35l45);
+int cs35l45_remove(struct cs35l45_private *cs35l45);
+
+#endif /* CS35L45_H */
-- 
2.30.2


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

* [PATCH v2 5/5] ASoC: cs35l45: Add driver for Cirrus Logic CS35L45 Smart Amp
@ 2022-04-05 13:54   ` Richard Fitzgerald
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Fitzgerald @ 2022-04-05 13:54 UTC (permalink / raw)
  To: broonie
  Cc: robh+dt, alsa-devel, devicetree, linux-kernel, patches,
	James Schulman, Richard Fitzgerald

From: James Schulman <james.schulman@cirrus.com>

The CS35L45 is a 15 V Boosted Mono Class D Amplifier with DSP
Speaker Protection and Adaptive Battery Management.

This initial driver provides standard non-boosted audio operation
without the DSP.

Signed-off-by: James Schulman <james.schulman@cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
 sound/soc/codecs/Kconfig          |  30 ++
 sound/soc/codecs/Makefile         |   8 +
 sound/soc/codecs/cs35l45-i2c.c    |  72 ++++
 sound/soc/codecs/cs35l45-spi.c    |  72 ++++
 sound/soc/codecs/cs35l45-tables.c | 202 +++++++++
 sound/soc/codecs/cs35l45.c        | 693 ++++++++++++++++++++++++++++++
 sound/soc/codecs/cs35l45.h        | 217 ++++++++++
 7 files changed, 1294 insertions(+)
 create mode 100644 sound/soc/codecs/cs35l45-i2c.c
 create mode 100644 sound/soc/codecs/cs35l45-spi.c
 create mode 100644 sound/soc/codecs/cs35l45-tables.c
 create mode 100644 sound/soc/codecs/cs35l45.c
 create mode 100644 sound/soc/codecs/cs35l45.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 21db336375ad..33edfd0ff079 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -65,6 +65,8 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_CS35L36
 	imply SND_SOC_CS35L41_SPI
 	imply SND_SOC_CS35L41_I2C
+	imply SND_SOC_CS35L45_I2C
+	imply SND_SOC_CS35L45_SPI
 	imply SND_SOC_CS42L42
 	imply SND_SOC_CS42L51_I2C
 	imply SND_SOC_CS42L52
@@ -656,6 +658,34 @@ config SND_SOC_CS35L41_I2C
 	select SND_SOC_CS35L41
 	select REGMAP_I2C
 
+config SND_SOC_CS35L45_TABLES
+	tristate
+
+config SND_SOC_CS35L45
+	tristate
+
+config SND_SOC_CS35L45_SPI
+	tristate "Cirrus Logic CS35L45 CODEC (SPI)"
+	depends on SPI_MASTER
+	select REGMAP
+	select REGMAP_SPI
+	select SND_SOC_CS35L45_TABLES
+	select SND_SOC_CS35L45
+	help
+	  Enable support for Cirrus Logic CS35L45 smart speaker amplifier
+	  with SPI control.
+
+config SND_SOC_CS35L45_I2C
+	tristate "Cirrus Logic CS35L45 CODEC (I2C)"
+	depends on I2C
+	select REGMAP
+	select REGMAP_I2C
+	select SND_SOC_CS35L45_TABLES
+	select SND_SOC_CS35L45
+	help
+	  Enable support for Cirrus Logic CS35L45 smart speaker amplifier
+	  with I2C control.
+
 config SND_SOC_CS42L42
 	tristate "Cirrus Logic CS42L42 CODEC"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 46a100b200b9..57e651532749 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -60,6 +60,10 @@ snd-soc-cs35l41-lib-objs := cs35l41-lib.o
 snd-soc-cs35l41-objs := cs35l41.o
 snd-soc-cs35l41-spi-objs := cs35l41-spi.o
 snd-soc-cs35l41-i2c-objs := cs35l41-i2c.o
+snd-soc-cs35l45-tables-objs := cs35l45-tables.o
+snd-soc-cs35l45-objs := cs35l45.o
+snd-soc-cs35l45-spi-objs := cs35l45-spi.o
+snd-soc-cs35l45-i2c-objs := cs35l45-i2c.o
 snd-soc-cs42l42-objs := cs42l42.o
 snd-soc-cs42l51-objs := cs42l51.o
 snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
@@ -406,6 +410,10 @@ obj-$(CONFIG_SND_SOC_CS35L41)	+= snd-soc-cs35l41.o
 obj-$(CONFIG_SND_SOC_CS35L41_LIB)	+= snd-soc-cs35l41-lib.o
 obj-$(CONFIG_SND_SOC_CS35L41_SPI)	+= snd-soc-cs35l41-spi.o
 obj-$(CONFIG_SND_SOC_CS35L41_I2C)	+= snd-soc-cs35l41-i2c.o
+obj-$(CONFIG_SND_SOC_CS35L45_TABLES)	+= snd-soc-cs35l45-tables.o
+obj-$(CONFIG_SND_SOC_CS35L45)	+= snd-soc-cs35l45.o
+obj-$(CONFIG_SND_SOC_CS35L45_SPI)	+= snd-soc-cs35l45-spi.o
+obj-$(CONFIG_SND_SOC_CS35L45_I2C)	+= snd-soc-cs35l45-i2c.o
 obj-$(CONFIG_SND_SOC_CS42L42)	+= snd-soc-cs42l42.o
 obj-$(CONFIG_SND_SOC_CS42L51)	+= snd-soc-cs42l51.o
 obj-$(CONFIG_SND_SOC_CS42L51_I2C)	+= snd-soc-cs42l51-i2c.o
diff --git a/sound/soc/codecs/cs35l45-i2c.c b/sound/soc/codecs/cs35l45-i2c.c
new file mode 100644
index 000000000000..18ebf1e14b91
--- /dev/null
+++ b/sound/soc/codecs/cs35l45-i2c.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45-i2c.c -- CS35L45 I2C driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#include "cs35l45.h"
+
+static int cs35l45_i2c_probe(struct i2c_client *client)
+{
+	struct cs35l45_private *cs35l45;
+	struct device *dev = &client->dev;
+	int ret;
+
+	cs35l45 = devm_kzalloc(dev, sizeof(struct cs35l45_private), GFP_KERNEL);
+	if (!cs35l45)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, cs35l45);
+	cs35l45->regmap = devm_regmap_init_i2c(client, &cs35l45_i2c_regmap);
+	if (IS_ERR(cs35l45->regmap)) {
+		ret = PTR_ERR(cs35l45->regmap);
+		dev_err(dev, "Failed to allocate register map: %d\n", ret);
+		return ret;
+	}
+
+	cs35l45->dev = dev;
+
+	return cs35l45_probe(cs35l45);
+}
+
+static int cs35l45_i2c_remove(struct i2c_client *client)
+{
+	struct cs35l45_private *cs35l45 = i2c_get_clientdata(client);
+
+	return cs35l45_remove(cs35l45);
+}
+
+static const struct of_device_id cs35l45_of_match[] = {
+	{ .compatible = "cirrus,cs35l45" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, cs35l45_of_match);
+
+static const struct i2c_device_id cs35l45_id_i2c[] = {
+	{ "cs35l45", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, cs35l45_id_i2c);
+
+static struct i2c_driver cs35l45_i2c_driver = {
+	.driver = {
+		.name		= "cs35l45",
+		.of_match_table = cs35l45_of_match,
+		.pm		= &cs35l45_pm_ops,
+	},
+	.id_table	= cs35l45_id_i2c,
+	.probe_new	= cs35l45_i2c_probe,
+	.remove		= cs35l45_i2c_remove,
+};
+module_i2c_driver(cs35l45_i2c_driver);
+
+MODULE_DESCRIPTION("I2C CS35L45 driver");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45-spi.c b/sound/soc/codecs/cs35l45-spi.c
new file mode 100644
index 000000000000..445f57ff1fcd
--- /dev/null
+++ b/sound/soc/codecs/cs35l45-spi.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45-spi.c -- CS35L45 SPI driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "cs35l45.h"
+
+static int cs35l45_spi_probe(struct spi_device *spi)
+{
+	struct cs35l45_private *cs35l45;
+	struct device *dev = &spi->dev;
+	int ret;
+
+	cs35l45 = devm_kzalloc(dev, sizeof(struct cs35l45_private), GFP_KERNEL);
+	if (cs35l45 == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, cs35l45);
+	cs35l45->regmap = devm_regmap_init_spi(spi, &cs35l45_spi_regmap);
+	if (IS_ERR(cs35l45->regmap)) {
+		ret = PTR_ERR(cs35l45->regmap);
+		dev_err(dev, "Failed to allocate register map: %d\n", ret);
+		return ret;
+	}
+
+	cs35l45->dev = dev;
+
+	return cs35l45_probe(cs35l45);
+}
+
+static void cs35l45_spi_remove(struct spi_device *spi)
+{
+	struct cs35l45_private *cs35l45 = spi_get_drvdata(spi);
+
+	cs35l45_remove(cs35l45);
+}
+
+static const struct of_device_id cs35l45_of_match[] = {
+	{ .compatible = "cirrus,cs35l45" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, cs35l45_of_match);
+
+static const struct spi_device_id cs35l45_id_spi[] = {
+	{ "cs35l45", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(spi, cs35l45_id_spi);
+
+static struct spi_driver cs35l45_spi_driver = {
+	.driver = {
+		.name		= "cs35l45",
+		.of_match_table = cs35l45_of_match,
+		.pm		= &cs35l45_pm_ops,
+	},
+	.id_table	= cs35l45_id_spi,
+	.probe		= cs35l45_spi_probe,
+	.remove		= cs35l45_spi_remove,
+};
+module_spi_driver(cs35l45_spi_driver);
+
+MODULE_DESCRIPTION("SPI CS35L45 driver");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c
new file mode 100644
index 000000000000..95d5b3606448
--- /dev/null
+++ b/sound/soc/codecs/cs35l45-tables.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45-tables.c -- CS35L45 ALSA SoC audio driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "cs35l45.h"
+
+static const struct reg_sequence cs35l45_patch[] = {
+	{ 0x00000040,			0x00000055 },
+	{ 0x00000040,			0x000000AA },
+	{ 0x00000044,			0x00000055 },
+	{ 0x00000044,			0x000000AA },
+	{ 0x00006480,			0x0830500A },
+	{ 0x00007C60,			0x1000850B },
+	{ CS35L45_BOOST_OV_CFG,		0x007000D0 },
+	{ CS35L45_LDPM_CONFIG,		0x0001B636 },
+	{ 0x00002C08,			0x00000009 },
+	{ 0x00006850,			0x0A30FFC4 },
+	{ 0x00003820,			0x00040100 },
+	{ 0x00003824,			0x00000000 },
+	{ 0x00007CFC,			0x62870004 },
+	{ 0x00007C60,			0x1001850B },
+	{ 0x00000040,			0x00000000 },
+	{ 0x00000044,			0x00000000 },
+	{ CS35L45_BOOST_CCM_CFG,	0xF0000003 },
+	{ CS35L45_BOOST_DCM_CFG,	0x08710220 },
+	{ CS35L45_ERROR_RELEASE,	0x00200000 },
+};
+
+int cs35l45_apply_patch(struct cs35l45_private *cs35l45)
+{
+	return regmap_register_patch(cs35l45->regmap, cs35l45_patch,
+				     ARRAY_SIZE(cs35l45_patch));
+}
+EXPORT_SYMBOL_GPL(cs35l45_apply_patch);
+
+static const struct reg_default cs35l45_defaults[] = {
+	{ CS35L45_BLOCK_ENABLES,		0x00003323 },
+	{ CS35L45_BLOCK_ENABLES2,		0x00000010 },
+	{ CS35L45_REFCLK_INPUT,			0x00000510 },
+	{ CS35L45_GLOBAL_SAMPLE_RATE,		0x00000003 },
+	{ CS35L45_ASP_ENABLES1,			0x00000000 },
+	{ CS35L45_ASP_CONTROL1,			0x00000028 },
+	{ CS35L45_ASP_CONTROL2,			0x18180200 },
+	{ CS35L45_ASP_CONTROL3,			0x00000002 },
+	{ CS35L45_ASP_FRAME_CONTROL1,		0x03020100 },
+	{ CS35L45_ASP_FRAME_CONTROL2,		0x00000004 },
+	{ CS35L45_ASP_FRAME_CONTROL5,		0x00000100 },
+	{ CS35L45_ASP_DATA_CONTROL1,		0x00000018 },
+	{ CS35L45_ASP_DATA_CONTROL5,		0x00000018 },
+	{ CS35L45_DACPCM1_INPUT,		0x00000008 },
+	{ CS35L45_ASPTX1_INPUT,			0x00000018 },
+	{ CS35L45_ASPTX2_INPUT,			0x00000019 },
+	{ CS35L45_ASPTX3_INPUT,			0x00000020 },
+	{ CS35L45_ASPTX4_INPUT,			0x00000028 },
+	{ CS35L45_ASPTX5_INPUT,			0x00000048 },
+	{ CS35L45_AMP_PCM_CONTROL,		0x00100000 },
+};
+
+static bool cs35l45_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L45_DEVID ... CS35L45_OTPID:
+	case CS35L45_SFT_RESET:
+	case CS35L45_GLOBAL_ENABLES:
+	case CS35L45_BLOCK_ENABLES:
+	case CS35L45_BLOCK_ENABLES2:
+	case CS35L45_ERROR_RELEASE:
+	case CS35L45_REFCLK_INPUT:
+	case CS35L45_GLOBAL_SAMPLE_RATE:
+	case CS35L45_ASP_ENABLES1:
+	case CS35L45_ASP_CONTROL1:
+	case CS35L45_ASP_CONTROL2:
+	case CS35L45_ASP_CONTROL3:
+	case CS35L45_ASP_FRAME_CONTROL1:
+	case CS35L45_ASP_FRAME_CONTROL2:
+	case CS35L45_ASP_FRAME_CONTROL5:
+	case CS35L45_ASP_DATA_CONTROL1:
+	case CS35L45_ASP_DATA_CONTROL5:
+	case CS35L45_DACPCM1_INPUT:
+	case CS35L45_ASPTX1_INPUT:
+	case CS35L45_ASPTX2_INPUT:
+	case CS35L45_ASPTX3_INPUT:
+	case CS35L45_ASPTX4_INPUT:
+	case CS35L45_ASPTX5_INPUT:
+	case CS35L45_AMP_PCM_CONTROL:
+	case CS35L45_AMP_PCM_HPF_TST:
+	case CS35L45_IRQ1_EINT_4:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool cs35l45_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS35L45_DEVID ... CS35L45_OTPID:
+	case CS35L45_SFT_RESET:
+	case CS35L45_GLOBAL_ENABLES:
+	case CS35L45_ERROR_RELEASE:
+	case CS35L45_AMP_PCM_HPF_TST:	/* not cachable */
+	case CS35L45_IRQ1_EINT_4:
+		return true;
+	default:
+		return false;
+	}
+}
+
+const struct regmap_config cs35l45_i2c_regmap = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+	.max_register = CS35L45_LASTREG,
+	.reg_defaults = cs35l45_defaults,
+	.num_reg_defaults = ARRAY_SIZE(cs35l45_defaults),
+	.volatile_reg = cs35l45_volatile_reg,
+	.readable_reg = cs35l45_readable_reg,
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(cs35l45_i2c_regmap);
+
+const struct regmap_config cs35l45_spi_regmap = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.pad_bits = 16,
+	.reg_stride = 4,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+	.max_register = CS35L45_LASTREG,
+	.reg_defaults = cs35l45_defaults,
+	.num_reg_defaults = ARRAY_SIZE(cs35l45_defaults),
+	.volatile_reg = cs35l45_volatile_reg,
+	.readable_reg = cs35l45_readable_reg,
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(cs35l45_spi_regmap);
+
+static const struct {
+	u8 cfg_id;
+	u32 freq;
+} cs35l45_pll_refclk_freq[] = {
+	{ 0x0C,   128000 },
+	{ 0x0F,   256000 },
+	{ 0x11,   384000 },
+	{ 0x12,   512000 },
+	{ 0x15,   768000 },
+	{ 0x17,  1024000 },
+	{ 0x19,  1411200 },
+	{ 0x1B,  1536000 },
+	{ 0x1C,  2116800 },
+	{ 0x1D,  2048000 },
+	{ 0x1E,  2304000 },
+	{ 0x1F,  2822400 },
+	{ 0x21,  3072000 },
+	{ 0x23,  4233600 },
+	{ 0x24,  4096000 },
+	{ 0x25,  4608000 },
+	{ 0x26,  5644800 },
+	{ 0x27,  6000000 },
+	{ 0x28,  6144000 },
+	{ 0x29,  6350400 },
+	{ 0x2A,  6912000 },
+	{ 0x2D,  7526400 },
+	{ 0x2E,  8467200 },
+	{ 0x2F,  8192000 },
+	{ 0x30,  9216000 },
+	{ 0x31, 11289600 },
+	{ 0x33, 12288000 },
+	{ 0x37, 16934400 },
+	{ 0x38, 18432000 },
+	{ 0x39, 22579200 },
+	{ 0x3B, 24576000 },
+};
+
+unsigned int cs35l45_get_clk_freq_id(unsigned int freq)
+{
+	int i;
+
+	if (freq == 0)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(cs35l45_pll_refclk_freq); ++i) {
+		if (cs35l45_pll_refclk_freq[i].freq == freq)
+			return cs35l45_pll_refclk_freq[i].cfg_id;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(cs35l45_get_clk_freq_id);
+
+MODULE_DESCRIPTION("ASoC CS35L45 driver tables");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c
new file mode 100644
index 000000000000..7822626d3693
--- /dev/null
+++ b/sound/soc/codecs/cs35l45.c
@@ -0,0 +1,693 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// cs35l45.c - CS35L45 ALSA SoC audio driver
+//
+// Copyright 2019-2022 Cirrus Logic, Inc.
+//
+// Author: James Schulman <james.schulman@cirrus.com>
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "cs35l45.h"
+
+static int cs35l45_global_en_ev(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
+
+	dev_dbg(cs35l45->dev, "%s event : %x\n", __func__, event);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		regmap_write(cs35l45->regmap, CS35L45_GLOBAL_ENABLES,
+			     CS35L45_GLOBAL_EN_MASK);
+
+		usleep_range(CS35L45_POST_GLOBAL_EN_US, CS35L45_POST_GLOBAL_EN_US + 100);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		usleep_range(CS35L45_PRE_GLOBAL_DIS_US, CS35L45_PRE_GLOBAL_DIS_US + 100);
+
+		regmap_write(cs35l45->regmap, CS35L45_GLOBAL_ENABLES, 0);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const char * const cs35l45_asp_tx_txt[] = {
+	"Zero", "ASP_RX1", "ASP_RX2",
+	"VMON", "IMON", "ERR_VOL",
+	"VDD_BATTMON", "VDD_BSTMON",
+	"Interpolator", "IL_TARGET",
+};
+
+static const unsigned int cs35l45_asp_tx_val[] = {
+	CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2,
+	CS35L45_PCM_SRC_VMON, CS35L45_PCM_SRC_IMON, CS35L45_PCM_SRC_ERR_VOL,
+	CS35L45_PCM_SRC_VDD_BATTMON, CS35L45_PCM_SRC_VDD_BSTMON,
+	CS35L45_PCM_SRC_INTERPOLATOR, CS35L45_PCM_SRC_IL_TARGET,
+};
+
+static const struct soc_enum cs35l45_asp_tx_enums[] = {
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX1_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX2_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX3_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX4_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+	SOC_VALUE_ENUM_SINGLE(CS35L45_ASPTX5_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_asp_tx_txt), cs35l45_asp_tx_txt,
+			      cs35l45_asp_tx_val),
+};
+
+static const char * const cs35l45_dac_txt[] = {
+	"Zero", "ASP_RX1", "ASP_RX2"
+};
+
+static const unsigned int cs35l45_dac_val[] = {
+	CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2
+};
+
+static const struct soc_enum cs35l45_dacpcm_enums[] = {
+	SOC_VALUE_ENUM_SINGLE(CS35L45_DACPCM1_INPUT, 0, CS35L45_PCM_SRC_MASK,
+			      ARRAY_SIZE(cs35l45_dac_txt), cs35l45_dac_txt,
+			      cs35l45_dac_val),
+};
+
+static const struct snd_kcontrol_new cs35l45_asp_muxes[] = {
+	SOC_DAPM_ENUM("ASP_TX1 Source", cs35l45_asp_tx_enums[0]),
+	SOC_DAPM_ENUM("ASP_TX2 Source", cs35l45_asp_tx_enums[1]),
+	SOC_DAPM_ENUM("ASP_TX3 Source", cs35l45_asp_tx_enums[2]),
+	SOC_DAPM_ENUM("ASP_TX4 Source", cs35l45_asp_tx_enums[3]),
+	SOC_DAPM_ENUM("ASP_TX5 Source", cs35l45_asp_tx_enums[4]),
+};
+
+static const struct snd_kcontrol_new cs35l45_dac_muxes[] = {
+	SOC_DAPM_ENUM("DACPCM1 Source", cs35l45_dacpcm_enums[0]),
+};
+
+static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("GLOBAL_EN", SND_SOC_NOPM, 0, 0,
+			    cs35l45_global_en_ev,
+			    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_SUPPLY("ASP_EN", CS35L45_BLOCK_ENABLES2, CS35L45_ASP_EN_SHIFT, 0, NULL, 0),
+
+	SND_SOC_DAPM_SIGGEN("VMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("IMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("VDD_BATTMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("VDD_BSTMON_SRC"),
+	SND_SOC_DAPM_SIGGEN("ERR_VOL"),
+	SND_SOC_DAPM_SIGGEN("AMP_INTP"),
+	SND_SOC_DAPM_SIGGEN("IL_TARGET"),
+	SND_SOC_DAPM_ADC("VMON", NULL, CS35L45_BLOCK_ENABLES, CS35L45_VMON_EN_SHIFT, 0),
+	SND_SOC_DAPM_ADC("IMON", NULL, CS35L45_BLOCK_ENABLES, CS35L45_IMON_EN_SHIFT, 0),
+	SND_SOC_DAPM_ADC("VDD_BATTMON", NULL, CS35L45_BLOCK_ENABLES,
+			 CS35L45_VDD_BATTMON_EN_SHIFT, 0),
+	SND_SOC_DAPM_ADC("VDD_BSTMON", NULL, CS35L45_BLOCK_ENABLES,
+			 CS35L45_VDD_BSTMON_EN_SHIFT, 0),
+
+	SND_SOC_DAPM_AIF_IN("ASP_RX1", NULL, 0, CS35L45_ASP_ENABLES1, CS35L45_ASP_RX1_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_IN("ASP_RX2", NULL, 1, CS35L45_ASP_ENABLES1, CS35L45_ASP_RX2_EN_SHIFT, 0),
+
+	SND_SOC_DAPM_AIF_OUT("ASP_TX1", NULL, 0, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX1_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX2", NULL, 1, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX2_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX3", NULL, 2, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX3_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX4", NULL, 3, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX4_EN_SHIFT, 0),
+	SND_SOC_DAPM_AIF_OUT("ASP_TX5", NULL, 3, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX5_EN_SHIFT, 0),
+
+	SND_SOC_DAPM_MUX("ASP_TX1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[0]),
+	SND_SOC_DAPM_MUX("ASP_TX2 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[1]),
+	SND_SOC_DAPM_MUX("ASP_TX3 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[2]),
+	SND_SOC_DAPM_MUX("ASP_TX4 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[3]),
+	SND_SOC_DAPM_MUX("ASP_TX5 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[4]),
+
+	SND_SOC_DAPM_MUX("DACPCM1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dac_muxes[0]),
+
+	SND_SOC_DAPM_OUT_DRV("AMP", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_OUTPUT("SPK"),
+};
+
+#define CS35L45_ASP_MUX_ROUTE(name) \
+	{ name" Source", "ASP_RX1",	 "ASP_RX1" }, \
+	{ name" Source", "ASP_RX2",	 "ASP_RX2" }, \
+	{ name" Source", "VMON",	 "VMON" }, \
+	{ name" Source", "IMON",	 "IMON" }, \
+	{ name" Source", "ERR_VOL",	 "ERR_VOL" }, \
+	{ name" Source", "VDD_BATTMON",	 "VDD_BATTMON" }, \
+	{ name" Source", "VDD_BSTMON",	 "VDD_BSTMON" }, \
+	{ name" Source", "Interpolator", "AMP_INTP" }, \
+	{ name" Source", "IL_TARGET",	 "IL_TARGET" }
+
+#define CS35L45_DAC_MUX_ROUTE(name) \
+	{ name" Source", "ASP_RX1",	"ASP_RX1" }, \
+	{ name" Source", "ASP_RX2",	"ASP_RX2" }
+
+static const struct snd_soc_dapm_route cs35l45_dapm_routes[] = {
+	/* Feedback */
+	{ "VMON", NULL, "VMON_SRC" },
+	{ "IMON", NULL, "IMON_SRC" },
+	{ "VDD_BATTMON", NULL, "VDD_BATTMON_SRC" },
+	{ "VDD_BSTMON", NULL, "VDD_BSTMON_SRC" },
+
+	{ "Capture", NULL, "ASP_TX1"},
+	{ "Capture", NULL, "ASP_TX2"},
+	{ "Capture", NULL, "ASP_TX3"},
+	{ "Capture", NULL, "ASP_TX4"},
+	{ "Capture", NULL, "ASP_TX5"},
+	{ "ASP_TX1", NULL, "ASP_TX1 Source"},
+	{ "ASP_TX2", NULL, "ASP_TX2 Source"},
+	{ "ASP_TX3", NULL, "ASP_TX3 Source"},
+	{ "ASP_TX4", NULL, "ASP_TX4 Source"},
+	{ "ASP_TX5", NULL, "ASP_TX5 Source"},
+
+	{ "ASP_TX1", NULL, "ASP_EN" },
+	{ "ASP_TX2", NULL, "ASP_EN" },
+	{ "ASP_TX3", NULL, "ASP_EN" },
+	{ "ASP_TX4", NULL, "ASP_EN" },
+	{ "ASP_TX1", NULL, "GLOBAL_EN" },
+	{ "ASP_TX2", NULL, "GLOBAL_EN" },
+	{ "ASP_TX3", NULL, "GLOBAL_EN" },
+	{ "ASP_TX4", NULL, "GLOBAL_EN" },
+	{ "ASP_TX5", NULL, "GLOBAL_EN" },
+
+	CS35L45_ASP_MUX_ROUTE("ASP_TX1"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX2"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX3"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX4"),
+	CS35L45_ASP_MUX_ROUTE("ASP_TX5"),
+
+	/* Playback */
+	{ "ASP_RX1", NULL, "Playback" },
+	{ "ASP_RX2", NULL, "Playback" },
+	{ "ASP_RX1", NULL, "ASP_EN" },
+	{ "ASP_RX2", NULL, "ASP_EN" },
+
+	{ "AMP", NULL, "DACPCM1 Source"},
+	{ "AMP", NULL, "GLOBAL_EN"},
+
+	CS35L45_DAC_MUX_ROUTE("DACPCM1"),
+
+	{ "SPK", NULL, "AMP"},
+};
+
+static const DECLARE_TLV_DB_SCALE(cs35l45_dig_pcm_vol_tlv, -10225, 25, true);
+
+static const struct snd_kcontrol_new cs35l45_controls[] = {
+	/* Ignore bit 0: it is beyond the resolution of TLV_DB_SCALE */
+	SOC_SINGLE_S_TLV("Digital PCM Volume",
+			 CS35L45_AMP_PCM_CONTROL,
+			 CS35L45_AMP_VOL_PCM_SHIFT + 1,
+			 -409, 48,
+			 (CS35L45_AMP_VOL_PCM_WIDTH - 1) - 1,
+			 0, cs35l45_dig_pcm_vol_tlv),
+};
+
+static int cs35l45_set_pll(struct cs35l45_private *cs35l45, unsigned int freq)
+{
+	unsigned int val;
+	int freq_id;
+
+	freq_id = cs35l45_get_clk_freq_id(freq);
+	if (freq_id < 0) {
+		dev_err(cs35l45->dev, "Invalid freq: %u\n", freq);
+		return -EINVAL;
+	}
+
+	regmap_read(cs35l45->regmap, CS35L45_REFCLK_INPUT, &val);
+	val = (val & CS35L45_PLL_REFCLK_FREQ_MASK) >> CS35L45_PLL_REFCLK_FREQ_SHIFT;
+	if (val == freq_id)
+		return 0;
+
+	regmap_set_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_OPEN_LOOP_MASK);
+	regmap_update_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT,
+			   CS35L45_PLL_REFCLK_FREQ_MASK,
+			   freq_id << CS35L45_PLL_REFCLK_FREQ_SHIFT);
+	regmap_clear_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_REFCLK_EN_MASK);
+	regmap_clear_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_OPEN_LOOP_MASK);
+	regmap_set_bits(cs35l45->regmap, CS35L45_REFCLK_INPUT, CS35L45_PLL_REFCLK_EN_MASK);
+
+	return 0;
+}
+
+static int cs35l45_asp_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(codec_dai->component);
+	unsigned int asp_fmt, fsync_inv, bclk_inv;
+
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_CBC_CFC:
+		break;
+	default:
+		dev_err(cs35l45->dev, "Invalid DAI clocking\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		asp_fmt = CS35l45_ASP_FMT_DSP_A;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		asp_fmt = CS35L45_ASP_FMT_I2S;
+		break;
+	default:
+		dev_err(cs35l45->dev, "Invalid DAI format\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_IF:
+		fsync_inv = 1;
+		bclk_inv = 0;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		fsync_inv = 0;
+		bclk_inv = 1;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		fsync_inv = 1;
+		bclk_inv = 1;
+		break;
+	case SND_SOC_DAIFMT_NB_NF:
+		fsync_inv = 0;
+		bclk_inv = 0;
+		break;
+	default:
+		dev_warn(cs35l45->dev, "Invalid DAI clock polarity\n");
+		return -EINVAL;
+	}
+
+	regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL2,
+			   CS35L45_ASP_FMT_MASK |
+			   CS35L45_ASP_FSYNC_INV_MASK |
+			   CS35L45_ASP_BCLK_INV_MASK,
+			   (asp_fmt << CS35L45_ASP_FMT_SHIFT) |
+			   (fsync_inv << CS35L45_ASP_FSYNC_INV_SHIFT) |
+			   (bclk_inv << CS35L45_ASP_BCLK_INV_SHIFT));
+
+	return 0;
+}
+
+static int cs35l45_asp_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int asp_width, asp_wl, global_fs, slot_multiple, asp_fmt;
+	int bclk;
+
+	switch (params_rate(params)) {
+	case 44100:
+		global_fs = CS35L45_44P100_KHZ;
+		break;
+	case 48000:
+		global_fs = CS35L45_48P0_KHZ;
+		break;
+	case 88200:
+		global_fs = CS35L45_88P200_KHZ;
+		break;
+	case 96000:
+		global_fs = CS35L45_96P0_KHZ;
+		break;
+	default:
+		dev_warn(cs35l45->dev, "Unsupported sample rate (%d)\n",
+			 params_rate(params));
+		return -EINVAL;
+	}
+
+	regmap_update_bits(cs35l45->regmap, CS35L45_GLOBAL_SAMPLE_RATE,
+			   CS35L45_GLOBAL_FS_MASK,
+			   global_fs << CS35L45_GLOBAL_FS_SHIFT);
+
+	asp_wl = params_width(params);
+
+	if (cs35l45->slot_width)
+		asp_width = cs35l45->slot_width;
+	else
+		asp_width = params_width(params);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL2,
+				   CS35L45_ASP_WIDTH_RX_MASK,
+				   asp_width << CS35L45_ASP_WIDTH_RX_SHIFT);
+
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_DATA_CONTROL5,
+				   CS35L45_ASP_WL_MASK,
+				   asp_wl << CS35L45_ASP_WL_SHIFT);
+	} else {
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL2,
+				   CS35L45_ASP_WIDTH_TX_MASK,
+				   asp_width << CS35L45_ASP_WIDTH_TX_SHIFT);
+
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_DATA_CONTROL1,
+				   CS35L45_ASP_WL_MASK,
+				   asp_wl << CS35L45_ASP_WL_SHIFT);
+	}
+
+	if (cs35l45->sysclk_set)
+		return 0;
+
+	/* I2S always has an even number of channels */
+	regmap_read(cs35l45->regmap, CS35L45_ASP_CONTROL2, &asp_fmt);
+	asp_fmt = (asp_fmt & CS35L45_ASP_FMT_MASK) >> CS35L45_ASP_FMT_SHIFT;
+	if (asp_fmt == CS35L45_ASP_FMT_I2S)
+		slot_multiple = 2;
+	else
+		slot_multiple = 1;
+
+	bclk = snd_soc_tdm_params_to_bclk(params, asp_width,
+					  cs35l45->slot_count, slot_multiple);
+
+	return cs35l45_set_pll(cs35l45, bclk);
+}
+
+static int cs35l45_asp_set_tdm_slot(struct snd_soc_dai *dai,
+				    unsigned int tx_mask, unsigned int rx_mask,
+				    int slots, int slot_width)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+
+	if (slot_width && ((slot_width < 16) || (slot_width > 128)))
+		return -EINVAL;
+
+	cs35l45->slot_width = slot_width;
+	cs35l45->slot_count = slots;
+
+	return 0;
+}
+
+static int cs35l45_asp_set_sysclk(struct snd_soc_dai *dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+	int ret;
+
+	if (clk_id != 0) {
+		dev_err(cs35l45->dev, "Invalid clk_id %d\n", clk_id);
+		return -EINVAL;
+	}
+
+	cs35l45->sysclk_set = false;
+	if (freq == 0)
+		return 0;
+
+	ret = cs35l45_set_pll(cs35l45, freq);
+	if (ret < 0)
+		return -EINVAL;
+
+	cs35l45->sysclk_set = true;
+
+	return 0;
+}
+
+static int cs35l45_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(dai->component);
+	unsigned int global_fs, val, hpf_tune;
+
+	if (mute)
+		return 0;
+
+	regmap_read(cs35l45->regmap, CS35L45_GLOBAL_SAMPLE_RATE, &global_fs);
+	global_fs = (global_fs & CS35L45_GLOBAL_FS_MASK) >> CS35L45_GLOBAL_FS_SHIFT;
+	switch (global_fs) {
+	case CS35L45_44P100_KHZ:
+		hpf_tune = CS35L45_HPF_44P1;
+		break;
+	case CS35L45_88P200_KHZ:
+		hpf_tune = CS35L45_HPF_88P2;
+		break;
+	default:
+		hpf_tune = CS35l45_HPF_DEFAULT;
+		break;
+	}
+
+	regmap_read(cs35l45->regmap, CS35L45_AMP_PCM_HPF_TST, &val);
+	if (val != hpf_tune) {
+		struct reg_sequence hpf_override_seq[] = {
+			{ 0x00000040,			0x00000055 },
+			{ 0x00000040,			0x000000AA },
+			{ 0x00000044,			0x00000055 },
+			{ 0x00000044,			0x000000AA },
+			{ CS35L45_AMP_PCM_HPF_TST,	hpf_tune },
+			{ 0x00000040,			0x00000000 },
+			{ 0x00000044,			0x00000000 },
+		};
+		regmap_multi_reg_write(cs35l45->regmap, hpf_override_seq,
+				       ARRAY_SIZE(hpf_override_seq));
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops cs35l45_asp_dai_ops = {
+	.set_fmt = cs35l45_asp_set_fmt,
+	.hw_params = cs35l45_asp_hw_params,
+	.set_tdm_slot = cs35l45_asp_set_tdm_slot,
+	.set_sysclk = cs35l45_asp_set_sysclk,
+	.mute_stream = cs35l45_mute_stream,
+};
+
+static struct snd_soc_dai_driver cs35l45_dai[] = {
+	{
+		.name = "cs35l45",
+		.playback = {
+			.stream_name = "Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = CS35L45_RATES,
+			.formats = CS35L45_FORMATS,
+		},
+		.capture = {
+			.stream_name = "Capture",
+			.channels_min = 1,
+			.channels_max = 5,
+			.rates = CS35L45_RATES,
+			.formats = CS35L45_FORMATS,
+		},
+		.symmetric_rate = true,
+		.symmetric_sample_bits = true,
+		.ops = &cs35l45_asp_dai_ops,
+	},
+};
+
+static const struct snd_soc_component_driver cs35l45_component = {
+	.dapm_widgets = cs35l45_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(cs35l45_dapm_widgets),
+
+	.dapm_routes = cs35l45_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(cs35l45_dapm_routes),
+
+	.controls = cs35l45_controls,
+	.num_controls = ARRAY_SIZE(cs35l45_controls),
+
+	.name = "cs35l45",
+};
+
+static int __maybe_unused cs35l45_runtime_suspend(struct device *dev)
+{
+	struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
+
+	regcache_cache_only(cs35l45->regmap, true);
+
+	dev_dbg(cs35l45->dev, "Runtime suspended\n");
+
+	return 0;
+}
+
+static int __maybe_unused cs35l45_runtime_resume(struct device *dev)
+{
+	struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
+	int ret;
+
+	dev_dbg(cs35l45->dev, "Runtime resume\n");
+
+	regcache_cache_only(cs35l45->regmap, false);
+	ret = regcache_sync(cs35l45->regmap);
+	if (ret != 0)
+		dev_warn(cs35l45->dev, "regcache_sync failed: %d\n", ret);
+
+	/* Clear global error status */
+	regmap_clear_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK);
+	regmap_set_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK);
+	regmap_clear_bits(cs35l45->regmap, CS35L45_ERROR_RELEASE, CS35L45_GLOBAL_ERR_RLS_MASK);
+	return ret;
+}
+
+static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45)
+{
+	unsigned int val;
+
+	if (device_property_read_u32(cs35l45->dev,
+				     "cirrus,asp-sdout-hiz-ctrl", &val) == 0) {
+		regmap_update_bits(cs35l45->regmap, CS35L45_ASP_CONTROL3,
+				   CS35L45_ASP_DOUT_HIZ_CTRL_MASK,
+				   val << CS35L45_ASP_DOUT_HIZ_CTRL_SHIFT);
+	}
+
+	return 0;
+}
+
+static int cs35l45_initialize(struct cs35l45_private *cs35l45)
+{
+	struct device *dev = cs35l45->dev;
+	unsigned int dev_id[5];
+	unsigned int sts;
+	int ret;
+
+	ret = regmap_read_poll_timeout(cs35l45->regmap, CS35L45_IRQ1_EINT_4, sts,
+				       (sts & CS35L45_OTP_BOOT_DONE_STS_MASK),
+				       1000, 5000);
+	if (ret < 0) {
+		dev_err(cs35l45->dev, "Timeout waiting for OTP boot\n");
+		return ret;
+	}
+
+	ret = regmap_bulk_read(cs35l45->regmap, CS35L45_DEVID, dev_id, ARRAY_SIZE(dev_id));
+	if (ret) {
+		dev_err(cs35l45->dev, "Get Device ID failed: %d\n", ret);
+		return ret;
+	}
+
+	switch (dev_id[0]) {
+	case 0x35A450:
+		break;
+	default:
+		dev_err(cs35l45->dev, "Bad DEVID 0x%x\n", dev_id[0]);
+		return -ENODEV;
+	}
+
+	dev_info(cs35l45->dev, "Cirrus Logic CS35L45: REVID %02X OTPID %02X\n",
+		 dev_id[1], dev_id[4]);
+
+	regmap_write(cs35l45->regmap, CS35L45_IRQ1_EINT_4,
+		     CS35L45_OTP_BOOT_DONE_STS_MASK | CS35L45_OTP_BUSY_MASK);
+
+	ret = cs35l45_apply_patch(cs35l45);
+	if (ret < 0) {
+		dev_err(dev, "Failed to apply init patch %d\n", ret);
+		return ret;
+	}
+
+	/* Default to boost bypass */
+	regmap_update_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES, CS35L45_BST_EN_MASK,
+			   CS35L45_BST_DISABLE_FET_ON << CS35L45_BST_EN_SHIFT);
+
+	ret = cs35l45_apply_property_config(cs35l45);
+	if (ret < 0)
+		return ret;
+
+	pm_runtime_set_autosuspend_delay(cs35l45->dev, 3000);
+	pm_runtime_use_autosuspend(cs35l45->dev);
+	pm_runtime_set_active(cs35l45->dev);
+	pm_runtime_enable(cs35l45->dev);
+
+	return 0;
+}
+
+int cs35l45_probe(struct cs35l45_private *cs35l45)
+{
+	struct device *dev = cs35l45->dev;
+	int ret;
+
+	cs35l45->vdd_batt = devm_regulator_get(dev, "vdd-batt");
+	if (IS_ERR(cs35l45->vdd_batt))
+		return dev_err_probe(dev, PTR_ERR(cs35l45->vdd_batt),
+				     "Failed to request vdd-batt\n");
+
+	cs35l45->vdd_a = devm_regulator_get(dev, "vdd-a");
+	if (IS_ERR(cs35l45->vdd_a))
+		return dev_err_probe(dev, PTR_ERR(cs35l45->vdd_a),
+				     "Failed to request vdd-a\n");
+
+	/* VDD_BATT must always be enabled before other supplies */
+	ret = regulator_enable(cs35l45->vdd_batt);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to enable vdd-batt\n");
+
+	ret = regulator_enable(cs35l45->vdd_a);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to enable vdd-a\n");
+
+	/* If reset is shared only one instance can claim it */
+	cs35l45->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(cs35l45->reset_gpio)) {
+		ret = PTR_ERR(cs35l45->reset_gpio);
+		cs35l45->reset_gpio = NULL;
+		if (ret == -EBUSY) {
+			dev_dbg(dev, "Reset line busy, assuming shared reset\n");
+		} else {
+			dev_err_probe(dev, ret, "Failed to get reset GPIO\n");
+			goto err;
+		}
+	}
+
+	if (cs35l45->reset_gpio) {
+		usleep_range(CS35L45_RESET_HOLD_US, CS35L45_RESET_HOLD_US + 100);
+		gpiod_set_value_cansleep(cs35l45->reset_gpio, 1);
+	}
+
+	usleep_range(CS35L45_RESET_US, CS35L45_RESET_US + 100);
+
+	ret = cs35l45_initialize(cs35l45);
+	if (ret < 0)
+		goto err_reset;
+
+	ret = devm_snd_soc_register_component(dev, &cs35l45_component,
+					      cs35l45_dai,
+					      ARRAY_SIZE(cs35l45_dai));
+	if (ret < 0)
+		goto err_reset;
+
+	return 0;
+
+err_reset:
+	gpiod_set_value_cansleep(cs35l45->reset_gpio, 0);
+err:
+	regulator_disable(cs35l45->vdd_a);
+	regulator_disable(cs35l45->vdd_batt);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(cs35l45_probe);
+
+int cs35l45_remove(struct cs35l45_private *cs35l45)
+{
+	pm_runtime_disable(cs35l45->dev);
+
+	gpiod_set_value_cansleep(cs35l45->reset_gpio, 0);
+	regulator_disable(cs35l45->vdd_a);
+	/* VDD_BATT must be the last to power-off */
+	regulator_disable(cs35l45->vdd_batt);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cs35l45_remove);
+
+const struct dev_pm_ops cs35l45_pm_ops = {
+	SET_RUNTIME_PM_OPS(cs35l45_runtime_suspend, cs35l45_runtime_resume, NULL)
+};
+EXPORT_SYMBOL_GPL(cs35l45_pm_ops);
+
+MODULE_DESCRIPTION("ASoC CS35L45 driver");
+MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <james.schulman@cirrus.com>");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h
new file mode 100644
index 000000000000..4e266d19cd1c
--- /dev/null
+++ b/sound/soc/codecs/cs35l45.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/*
+ * cs35l45.h - CS35L45 ALSA SoC audio driver
+ *
+ * Copyright 2019-2022 Cirrus Logic, Inc.
+ *
+ * Author: James Schulman <james.schulman@cirrus.com>
+ *
+ */
+
+#ifndef CS35L45_H
+#define CS35L45_H
+
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define CS35L45_DEVID				0x00000000
+#define CS35L45_REVID				0x00000004
+#define CS35L45_RELID				0x0000000C
+#define CS35L45_OTPID				0x00000010
+#define CS35L45_SFT_RESET			0x00000020
+#define CS35L45_GLOBAL_ENABLES			0x00002014
+#define CS35L45_BLOCK_ENABLES			0x00002018
+#define CS35L45_BLOCK_ENABLES2			0x0000201C
+#define CS35L45_ERROR_RELEASE			0x00002034
+#define CS35L45_REFCLK_INPUT			0x00002C04
+#define CS35L45_GLOBAL_SAMPLE_RATE		0x00002C0C
+#define CS35L45_BOOST_CCM_CFG			0x00003808
+#define CS35L45_BOOST_DCM_CFG			0x0000380C
+#define CS35L45_BOOST_OV_CFG			0x0000382C
+#define CS35L45_ASP_ENABLES1			0x00004800
+#define CS35L45_ASP_CONTROL1			0x00004804
+#define CS35L45_ASP_CONTROL2			0x00004808
+#define CS35L45_ASP_CONTROL3			0x0000480C
+#define CS35L45_ASP_FRAME_CONTROL1		0x00004810
+#define CS35L45_ASP_FRAME_CONTROL2		0x00004814
+#define CS35L45_ASP_FRAME_CONTROL5		0x00004820
+#define CS35L45_ASP_DATA_CONTROL1		0x00004830
+#define CS35L45_ASP_DATA_CONTROL5		0x00004840
+#define CS35L45_DACPCM1_INPUT			0x00004C00
+#define CS35L45_ASPTX1_INPUT			0x00004C20
+#define CS35L45_ASPTX2_INPUT			0x00004C24
+#define CS35L45_ASPTX3_INPUT			0x00004C28
+#define CS35L45_ASPTX4_INPUT			0x00004C2C
+#define CS35L45_ASPTX5_INPUT			0x00004C30
+#define CS35L45_LDPM_CONFIG			0x00006404
+#define CS35L45_AMP_PCM_CONTROL			0x00007000
+#define CS35L45_AMP_PCM_HPF_TST			0x00007004
+#define CS35L45_IRQ1_EINT_4			0x0000E01C
+#define CS35L45_LASTREG				0x0000E01C
+
+/* SFT_RESET */
+#define CS35L45_SOFT_RESET_TRIGGER		0x5A000000
+
+/* GLOBAL_ENABLES */
+#define CS35L45_GLOBAL_EN_SHIFT			0
+#define CS35L45_GLOBAL_EN_MASK			BIT(0)
+
+/* BLOCK_ENABLES */
+#define CS35L45_IMON_EN_SHIFT			13
+#define CS35L45_VMON_EN_SHIFT			12
+#define CS35L45_VDD_BSTMON_EN_SHIFT		9
+#define CS35L45_VDD_BATTMON_EN_SHIFT		8
+#define CS35L45_BST_EN_SHIFT			4
+#define CS35L45_BST_EN_MASK			GENMASK(5, 4)
+
+#define CS35L45_BST_DISABLE_FET_ON              0x01
+
+/* BLOCK_ENABLES2 */
+#define CS35L45_ASP_EN_SHIFT			27
+
+/* ERROR_RELEASE */
+#define CS35L45_GLOBAL_ERR_RLS_MASK		BIT(11)
+
+/* REFCLK_INPUT */
+#define CS35L45_PLL_FORCE_EN_SHIFT		16
+#define CS35L45_PLL_FORCE_EN_MASK		BIT(16)
+#define CS35L45_PLL_OPEN_LOOP_SHIFT		11
+#define CS35L45_PLL_OPEN_LOOP_MASK		BIT(11)
+#define CS35L45_PLL_REFCLK_FREQ_SHIFT		5
+#define CS35L45_PLL_REFCLK_FREQ_MASK		GENMASK(10, 5)
+#define CS35L45_PLL_REFCLK_EN_SHIFT		4
+#define CS35L45_PLL_REFCLK_EN_MASK		BIT(4)
+#define CS35L45_PLL_REFCLK_SEL_SHIFT		0
+#define CS35L45_PLL_REFCLK_SEL_MASK		GENMASK(2, 0)
+
+#define CS35L45_PLL_REFCLK_SEL_BCLK		0x0
+
+/* GLOBAL_SAMPLE_RATE */
+#define CS35L45_GLOBAL_FS_SHIFT			0
+#define CS35L45_GLOBAL_FS_MASK			GENMASK(4, 0)
+
+#define CS35L45_48P0_KHZ			0x03
+#define CS35L45_96P0_KHZ			0x04
+#define CS35L45_44P100_KHZ			0x0B
+#define CS35L45_88P200_KHZ			0x0C
+
+/* ASP_ENABLES_1 */
+#define CS35L45_ASP_RX2_EN_SHIFT		17
+#define CS35L45_ASP_RX1_EN_SHIFT		16
+#define CS35L45_ASP_TX5_EN_SHIFT		4
+#define CS35L45_ASP_TX4_EN_SHIFT		3
+#define CS35L45_ASP_TX3_EN_SHIFT		2
+#define CS35L45_ASP_TX2_EN_SHIFT		1
+#define CS35L45_ASP_TX1_EN_SHIFT		0
+
+/* ASP_CONTROL2 */
+#define CS35L45_ASP_WIDTH_RX_SHIFT		24
+#define CS35L45_ASP_WIDTH_RX_MASK		GENMASK(31, 24)
+#define CS35L45_ASP_WIDTH_TX_SHIFT		16
+#define CS35L45_ASP_WIDTH_TX_MASK		GENMASK(23, 16)
+#define CS35L45_ASP_FMT_SHIFT			8
+#define CS35L45_ASP_FMT_MASK			GENMASK(10, 8)
+#define CS35L45_ASP_BCLK_INV_SHIFT		6
+#define CS35L45_ASP_BCLK_INV_MASK		BIT(6)
+#define CS35L45_ASP_FSYNC_INV_SHIFT		2
+#define CS35L45_ASP_FSYNC_INV_MASK		BIT(2)
+
+#define CS35l45_ASP_FMT_DSP_A			0
+#define CS35L45_ASP_FMT_I2S			2
+
+/* ASP_CONTROL3 */
+#define CS35L45_ASP_DOUT_HIZ_CTRL_SHIFT		0
+#define CS35L45_ASP_DOUT_HIZ_CTRL_MASK		GENMASK(1, 0)
+
+/* ASP_FRAME_CONTROL1 */
+#define CS35L45_ASP_TX4_SLOT_SHIFT		24
+#define CS35L45_ASP_TX4_SLOT_MASK		GENMASK(29, 24)
+#define CS35L45_ASP_TX3_SLOT_SHIFT		16
+#define CS35L45_ASP_TX3_SLOT_MASK		GENMASK(21, 16)
+#define CS35L45_ASP_TX2_SLOT_SHIFT		8
+#define CS35L45_ASP_TX2_SLOT_MASK		GENMASK(13, 8)
+#define CS35L45_ASP_TX1_SLOT_SHIFT		0
+#define CS35L45_ASP_TX1_SLOT_MASK		GENMASK(5, 0)
+
+#define CS35L45_ASP_TX_ALL_SLOTS		(CS35L45_ASP_TX4_SLOT_MASK | \
+						CS35L45_ASP_TX3_SLOT_MASK  | \
+						CS35L45_ASP_TX2_SLOT_MASK  | \
+						CS35L45_ASP_TX1_SLOT_MASK)
+/* ASP_FRAME_CONTROL5 */
+#define CS35L45_ASP_RX2_SLOT_SHIFT		8
+#define CS35L45_ASP_RX2_SLOT_MASK		GENMASK(13, 8)
+#define CS35L45_ASP_RX1_SLOT_SHIFT		0
+#define CS35L45_ASP_RX1_SLOT_MASK		GENMASK(5, 0)
+
+#define CS35L45_ASP_RX_ALL_SLOTS		(CS35L45_ASP_RX2_SLOT_MASK | \
+						CS35L45_ASP_RX1_SLOT_MASK)
+
+/* ASP_DATA_CONTROL1 */
+/* ASP_DATA_CONTROL5 */
+#define CS35L45_ASP_WL_SHIFT			0
+#define CS35L45_ASP_WL_MASK			GENMASK(5, 0)
+
+/* AMP_PCM_CONTROL */
+#define CS35L45_AMP_VOL_PCM_SHIFT		0
+#define CS35L45_AMP_VOL_PCM_WIDTH		11
+
+/* AMP_PCM_HPF_TST */
+#define CS35l45_HPF_DEFAULT			0x00000000
+#define CS35L45_HPF_44P1			0x000108BD
+#define CS35L45_HPF_88P2			0x0001045F
+
+/* IRQ1_EINT_4 */
+#define CS35L45_OTP_BOOT_DONE_STS_MASK		BIT(1)
+#define CS35L45_OTP_BUSY_MASK			BIT(0)
+
+/* Mixer sources */
+#define CS35L45_PCM_SRC_MASK			0x7F
+#define CS35L45_PCM_SRC_ZERO			0x00
+#define CS35L45_PCM_SRC_ASP_RX1			0x08
+#define CS35L45_PCM_SRC_ASP_RX2			0x09
+#define CS35L45_PCM_SRC_VMON			0x18
+#define CS35L45_PCM_SRC_IMON			0x19
+#define CS35L45_PCM_SRC_ERR_VOL			0x20
+#define CS35L45_PCM_SRC_CLASSH_TGT		0x21
+#define CS35L45_PCM_SRC_VDD_BATTMON		0x28
+#define CS35L45_PCM_SRC_VDD_BSTMON		0x29
+#define CS35L45_PCM_SRC_TEMPMON			0x3A
+#define CS35L45_PCM_SRC_INTERPOLATOR		0x40
+#define CS35L45_PCM_SRC_IL_TARGET		0x48
+
+#define CS35L45_RESET_HOLD_US			2000
+#define CS35L45_RESET_US			2000
+#define CS35L45_POST_GLOBAL_EN_US		5000
+#define CS35L45_PRE_GLOBAL_DIS_US		3000
+
+#define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+			 SNDRV_PCM_FMTBIT_S24_3LE| \
+			 SNDRV_PCM_FMTBIT_S24_LE)
+
+#define CS35L45_RATES (SNDRV_PCM_RATE_44100 | \
+		       SNDRV_PCM_RATE_48000 | \
+		       SNDRV_PCM_RATE_88200 | \
+		       SNDRV_PCM_RATE_96000)
+
+struct cs35l45_private {
+	struct device *dev;
+	struct regmap *regmap;
+	struct gpio_desc *reset_gpio;
+	struct regulator *vdd_batt;
+	struct regulator *vdd_a;
+	bool initialized;
+	bool sysclk_set;
+	u8 slot_width;
+	u8 slot_count;
+};
+
+extern const struct dev_pm_ops cs35l45_pm_ops;
+extern const struct regmap_config cs35l45_i2c_regmap;
+extern const struct regmap_config cs35l45_spi_regmap;
+int cs35l45_apply_patch(struct cs35l45_private *cs43l45);
+unsigned int cs35l45_get_clk_freq_id(unsigned int freq);
+int cs35l45_probe(struct cs35l45_private *cs35l45);
+int cs35l45_remove(struct cs35l45_private *cs35l45);
+
+#endif /* CS35L45_H */
-- 
2.30.2


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

* Re: [PATCH v2 0/5] ASoC: Add a driver for the Cirrus Logic CS35L45 Smart Amplifier
  2022-04-05 13:54 ` Richard Fitzgerald
@ 2022-04-06 13:28   ` Mark Brown
  -1 siblings, 0 replies; 14+ messages in thread
From: Mark Brown @ 2022-04-06 13:28 UTC (permalink / raw)
  To: rf; +Cc: devicetree, alsa-devel, robh+dt, linux-kernel, patches

On Tue, 5 Apr 2022 14:54:14 +0100, Richard Fitzgerald wrote:
> This adds basic audio support for the Cirrus Logic CS35L45 amplifier.
> 
> The first two patches add two generic helpers to ASoC, and patch 3 is
> a kunit test for patch 2.
> 
> CHANGES SINCE V1:
> Patch #5:
>  - spi .remove callback now has void return
>  - use new I2C .probe_new callback
>  - force boost-bypass mode as default
> 
> [...]

Applied to

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

Thanks!

[1/5] ASoC: soc.h: Add SOC_SINGLE_S_TLV() macro
      commit: bc8cb02976cd602b8d7631a6f4a54a9cf305d38c
[2/5] ASoC: soc-utils: Add helper to calculate BCLK from TDM info
      commit: 1ef34dd2b90d78a9830398441801658ef86eee9d
[3/5] ASoC: soc-utils: Add kunit test for snd_soc_tdm_params_to_bclk()
      commit: 89342fa38bbaade51584f255eee5cd43621f4e10
[4/5] ASoC: dt-bindings: cs35l45: Cirrus Logic CS35L45 Smart Amp
      commit: 72661ff7662acc00d51976d4b2d2d13eb5628385
[5/5] ASoC: cs35l45: Add driver for Cirrus Logic CS35L45 Smart Amp
      commit: 0d463d016000d68d7e982720b5e4380b2d83409a

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

* Re: [PATCH v2 0/5] ASoC: Add a driver for the Cirrus Logic CS35L45 Smart Amplifier
@ 2022-04-06 13:28   ` Mark Brown
  0 siblings, 0 replies; 14+ messages in thread
From: Mark Brown @ 2022-04-06 13:28 UTC (permalink / raw)
  To: rf; +Cc: linux-kernel, robh+dt, devicetree, patches, alsa-devel

On Tue, 5 Apr 2022 14:54:14 +0100, Richard Fitzgerald wrote:
> This adds basic audio support for the Cirrus Logic CS35L45 amplifier.
> 
> The first two patches add two generic helpers to ASoC, and patch 3 is
> a kunit test for patch 2.
> 
> CHANGES SINCE V1:
> Patch #5:
>  - spi .remove callback now has void return
>  - use new I2C .probe_new callback
>  - force boost-bypass mode as default
> 
> [...]

Applied to

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

Thanks!

[1/5] ASoC: soc.h: Add SOC_SINGLE_S_TLV() macro
      commit: bc8cb02976cd602b8d7631a6f4a54a9cf305d38c
[2/5] ASoC: soc-utils: Add helper to calculate BCLK from TDM info
      commit: 1ef34dd2b90d78a9830398441801658ef86eee9d
[3/5] ASoC: soc-utils: Add kunit test for snd_soc_tdm_params_to_bclk()
      commit: 89342fa38bbaade51584f255eee5cd43621f4e10
[4/5] ASoC: dt-bindings: cs35l45: Cirrus Logic CS35L45 Smart Amp
      commit: 72661ff7662acc00d51976d4b2d2d13eb5628385
[5/5] ASoC: cs35l45: Add driver for Cirrus Logic CS35L45 Smart Amp
      commit: 0d463d016000d68d7e982720b5e4380b2d83409a

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:[~2022-04-06 15:59 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-05 13:54 [PATCH v2 0/5] ASoC: Add a driver for the Cirrus Logic CS35L45 Smart Amplifier Richard Fitzgerald
2022-04-05 13:54 ` Richard Fitzgerald
2022-04-05 13:54 ` [PATCH v2 1/5] ASoC: soc.h: Add SOC_SINGLE_S_TLV() macro Richard Fitzgerald
2022-04-05 13:54   ` Richard Fitzgerald
2022-04-05 13:54 ` [PATCH v2 2/5] ASoC: soc-utils: Add helper to calculate BCLK from TDM info Richard Fitzgerald
2022-04-05 13:54   ` Richard Fitzgerald
2022-04-05 13:54 ` [PATCH v2 3/5] ASoC: soc-utils: Add kunit test for snd_soc_tdm_params_to_bclk() Richard Fitzgerald
2022-04-05 13:54   ` Richard Fitzgerald
2022-04-05 13:54 ` [PATCH v2 4/5] ASoC: dt-bindings: cs35l45: Cirrus Logic CS35L45 Smart Amp Richard Fitzgerald
2022-04-05 13:54   ` Richard Fitzgerald
2022-04-05 13:54 ` [PATCH v2 5/5] ASoC: cs35l45: Add driver for " Richard Fitzgerald
2022-04-05 13:54   ` Richard Fitzgerald
2022-04-06 13:28 ` [PATCH v2 0/5] ASoC: Add a driver for the Cirrus Logic CS35L45 Smart Amplifier Mark Brown
2022-04-06 13:28   ` 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.