[PATCH v2 0/5] ASoC: Add a driver for the Cirrus Logic CS35L45 Smart Amplifier
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
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 | \
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 |
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(¶ms); + snd_mask_none(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT)); + hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE)->min = rate; + hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE)->max = rate; + hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS)->min = channels; + hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS)->max = channels; + params_set_format(¶ms, fmt); + + got_bclk = snd_soc_tdm_params_to_bclk(¶ms, 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(¶ms), 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");
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 */
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 */
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
participants (2)
-
Mark Brown
-
Richard Fitzgerald