diff mbox series

[v2] ASoC: codecs: add uspport for the TI SRC4392 codec

Message ID 20220808214028.2502801-1-flatmax@flatmax.com
State Superseded
Headers show
Series [v2] ASoC: codecs: add uspport for the TI SRC4392 codec | expand

Commit Message

Matt Flax Aug. 8, 2022, 9:40 p.m. UTC
The src4xxx keyword is used for	future capability to integrate
other codecs similar to the src4392 to the same	code base.

Signed-off-by: Matt Flax <flatmax@flatmax.com>
---

Notes:
    V2 changes :
    * Fixed indents in Kconfig
    * Comment header of source code is now one block
    * Documented the devicetree binding
    * sample rate converter MCLK src is non-user settable
    * hushed spurious dev_info output
    * src4xxx_remove was empty is removed.
    * Using switch in hw_params to select port reg. for clarity
    * Removed interrupt reg. settings - will re-intro. properly later
    * probe function is now EXPORT_SYMBOL_GPL

 .../devicetree/bindings/sound/src4xxx.txt     |  20 +
 sound/soc/codecs/Kconfig                      |  13 +
 sound/soc/codecs/Makefile                     |   4 +
 sound/soc/codecs/src4xxx-i2c.c                |  55 ++
 sound/soc/codecs/src4xxx.c                    | 508 ++++++++++++++++++
 sound/soc/codecs/src4xxx.h                    | 115 ++++
 6 files changed, 715 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/src4xxx.txt
 create mode 100644 sound/soc/codecs/src4xxx-i2c.c
 create mode 100644 sound/soc/codecs/src4xxx.c
 create mode 100644 sound/soc/codecs/src4xxx.h

Comments

kernel test robot Aug. 9, 2022, 7:35 a.m. UTC | #1
Hi Matt,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on broonie-sound/for-next]
[also build test ERROR on tiwai-sound/for-next linus/master v5.19 next-20220809]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Matt-Flax/ASoC-codecs-add-uspport-for-the-TI-SRC4392-codec/20220809-054524
base:   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
config: openrisc-randconfig-c023-20220808 (https://download.01.org/0day-ci/archive/20220809/202208091504.Z4NfNeeX-lkp@intel.com/config)
compiler: or1k-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/7a9219a8431d7740c0958e53078820cbfef4f3f7
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Matt-Flax/ASoC-codecs-add-uspport-for-the-TI-SRC4392-codec/20220809-054524
        git checkout 7a9219a8431d7740c0958e53078820cbfef4f3f7
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=openrisc SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>, old ones prefixed by <<):

>> ERROR: modpost: "src4xxx_remove" [sound/soc/codecs/snd-soc-src4xxx-i2c.ko] undefined!
kernel test robot Aug. 9, 2022, 8:25 a.m. UTC | #2
Hi Matt,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on broonie-sound/for-next]
[also build test ERROR on tiwai-sound/for-next linus/master v5.19 next-20220809]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Matt-Flax/ASoC-codecs-add-uspport-for-the-TI-SRC4392-codec/20220809-054524
base:   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
config: hexagon-randconfig-r002-20220808 (https://download.01.org/0day-ci/archive/20220809/202208091637.XWbySJt5-lkp@intel.com/config)
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 5f1c7e2cc5a3c07cbc2412e851a7283c1841f520)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/7a9219a8431d7740c0958e53078820cbfef4f3f7
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Matt-Flax/ASoC-codecs-add-uspport-for-the-TI-SRC4392-codec/20220809-054524
        git checkout 7a9219a8431d7740c0958e53078820cbfef4f3f7
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

>> ld.lld: error: undefined symbol: src4xxx_remove
   >>> referenced by src4xxx-i2c.c:25 (sound/soc/codecs/src4xxx-i2c.c:25)
   >>>               soc/codecs/src4xxx-i2c.o:(src4xxx_i2c_remove) in archive sound/built-in.a
   >>> referenced by src4xxx-i2c.c:25 (sound/soc/codecs/src4xxx-i2c.c:25)
   >>>               soc/codecs/src4xxx-i2c.o:(src4xxx_i2c_remove) in archive sound/built-in.a
Mark Brown Aug. 9, 2022, 12:53 p.m. UTC | #3
On Tue, Aug 09, 2022 at 07:40:28AM +1000, Matt Flax wrote:

>     * Documented the devicetree binding

The DT binding needs to be a separate patch CCed to the DT
maintainers...

> index 000000000000..f8d845d42f42
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/src4xxx.txt
> @@ -0,0 +1,20 @@
> +Texas Instruments src4392 DT bindings

...and new DT bindings need to be in YAML format.

> +++ b/sound/soc/codecs/src4xxx-i2c.c
> @@ -0,0 +1,55 @@
> +/* SPDX-License-Identifier: GPL-2.0
> + *
> + * Driver for SRC4XXX codecs

Please make the entire comment a *C++* one - the SPDX bit needs to be
C++ so the rest of it should be.

> +/* SRC attenuation */
> +static const DECLARE_TLV_DB_SCALE(src_tlv, -12750, 50, 0);
> +
> +static const struct snd_kcontrol_new src4xxx_controls[] = {
> +	SOC_DOUBLE_R_TLV("SRC vol",
> +		SRC4XXX_SCR_CTL_30, SRC4XXX_SCR_CTL_31, 0, 255, 1, src_tlv),
> +};

Volume controls need to end in Volume.  You should run the mixer-test
kselftest against a card with this driver, it should spot problems like
this.

> +	switch (dai->id) {
> +	case SRC4XXX_PORTB:
> +		reg = SRC4XXX_PORTB_CTL_06;
> +		break;
> +	default:
> +		reg = SRC4XXX_PORTA_CTL_04;
> +	}

Missing break in the default case.

> +					SRC4XXX_MCLK_DIV_MASK, val);
> +	} else
> +		dev_info(dai->dev, "not setting up MCLK as not master\n");
> +

{ } on both sides or neither of the else please.
Mark Brown Aug. 17, 2022, 4:21 p.m. UTC | #4
On Tue, Aug 16, 2022 at 11:00:10AM -0700, Nathan Chancellor wrote:
> On Tue, Aug 09, 2022 at 09:49:38AM +0800, kernel test robot wrote:

> > config: hexagon-randconfig-r002-20220808 (https://download.01.org/0day-ci/archive/20220809/202208090909.Pg0BZGie-lkp@intel.com/config)
> > compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 5f1c7e2cc5a3c07cbc2412e851a7283c1841f520)

> It doesn't look like these warnings were addressed before the change was
> applied to -next as commit 4e6bedd3c396 ("ASoC: codecs: add support for
> the TI SRC4392 codec"). I now see them in next-20220816.

It's probably worth talking to the 0day people about prioritising what
they're reporting against, especially given that the reports have
evolved into a bit of an eye chart - this was reported against a Hexagon
randconfig with an unreleased compiler which is underselling it rather.
Mark Brown Aug. 18, 2022, 11:44 a.m. UTC | #5
On Wed, Aug 17, 2022 at 10:23:23AM -0700, Nathan Chancellor wrote:
> On Wed, Aug 17, 2022 at 05:21:23PM +0100, Mark Brown wrote:

> > It's probably worth talking to the 0day people about prioritising what
> > they're reporting against, especially given that the reports have
> > evolved into a bit of an eye chart - this was reported against a Hexagon
> > randconfig with an unreleased compiler which is underselling it rather.

> At the same time, I would expect developers and maintainers to focus on
> the warning text first and foremost, not what architecture,
> configuration, or compiler is being used. This issue is very clearly not
> architecture or configuration specific, there is no #ifdef in this

That's the eye chart bit of it - part of the problem with 0day
specificially is that a lot of their reports have become quite hard to
read, they've been putting in something that looks a lot like git
annotate output which makes things very wide which causes wrapping
issues (this one is actually a bit better than most now I go look at it
again since it doesn't have that indentation).  Picking obscure
configurations makes it more likely that people won't get round to
figuring out what the issue being reported is since it seems less urgent
and therefore gets pushed further to the back of the queue.  For
something that's cropping up on a wide range of configurations it'd be
good to priorirtize the more prominent ones to mitigate against this.

> function that changes the nature of the warning. While it is compiler
> specific (because possible uninitialized variable warnings are disabled
> with GCC), it is not dependent on the version (although I guess that
> isn't apparent). I suppose I can just comment on future randconfig
> reports to point out how they will affect allmodconfig and such.

That'd probably help.
diff mbox series

Patch

diff --git a/Documentation/devicetree/bindings/sound/src4xxx.txt b/Documentation/devicetree/bindings/sound/src4xxx.txt
new file mode 100644
index 000000000000..f8d845d42f42
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/src4xxx.txt
@@ -0,0 +1,20 @@ 
+Texas Instruments src4392 DT bindings
+
+SRC4392 is a digital audio codec that can be connected via
+I2C or SPI. Currently, only I2C bus is supported.
+
+Required properties:
+
+ - compatible: "ti,src4392"
+
+Required properties on I2C:
+
+ - reg: the I2C address
+
+Examples:
+
+        src4392@70 {
+                compatible = "ti,src4392";
+                reg = <0x70>;
+                #sound-dai-cells = <0>;
+        };
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index d16b4efb88a7..9c9d74074888 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -205,6 +205,7 @@  config SND_SOC_ALL_CODECS
 	imply SND_SOC_SIMPLE_AMPLIFIER
 	imply SND_SOC_SIMPLE_MUX
 	imply SND_SOC_SPDIF
+	imply SND_SOC_SRC4XXX_I2C
 	imply SND_SOC_SSM2305
 	imply SND_SOC_SSM2518
 	imply SND_SOC_SSM2602_SPI
@@ -1471,6 +1472,18 @@  config SND_SOC_SIMPLE_MUX
 config SND_SOC_SPDIF
 	tristate "S/PDIF CODEC"
 
+config SND_SOC_SRC4XXX_I2C
+	tristate
+	depends on I2C
+	select SND_SOC_SRC4XXX
+
+config SND_SOC_SRC4XXX
+	tristate "Texas Instruments SRC4XXX DIR/DIT and SRC codecs"
+	help
+	  Enable support for the TI SRC4XXX family of codecs. These include the
+	  scr4392 which has digital receivers, transmitters, and
+	  a sample rate converter, including numerous ports.
+
 config SND_SOC_SSM2305
 	tristate "Analog Devices SSM2305 Class-D Amplifier"
 	help
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 92fd441d426a..a599b727c65b 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -231,6 +231,8 @@  snd-soc-sigmadsp-regmap-objs := sigmadsp-regmap.o
 snd-soc-si476x-objs := si476x.o
 snd-soc-spdif-tx-objs := spdif_transmitter.o
 snd-soc-spdif-rx-objs := spdif_receiver.o
+snd-soc-src4xxx-objs := src4xxx.o
+snd-soc-src4xxx-i2c-objs := src4xxx-i2c.o
 snd-soc-ssm2305-objs := ssm2305.o
 snd-soc-ssm2518-objs := ssm2518.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -579,6 +581,8 @@  obj-$(CONFIG_SND_SOC_SIGMADSP_I2C)	+= snd-soc-sigmadsp-i2c.o
 obj-$(CONFIG_SND_SOC_SIGMADSP_REGMAP)	+= snd-soc-sigmadsp-regmap.o
 obj-$(CONFIG_SND_SOC_SI476X)	+= snd-soc-si476x.o
 obj-$(CONFIG_SND_SOC_SPDIF)	+= snd-soc-spdif-rx.o snd-soc-spdif-tx.o
+obj-$(CONFIG_SND_SOC_SRC4XXX)	+= snd-soc-src4xxx.o
+obj-$(CONFIG_SND_SOC_SRC4XXX_I2C)	+= snd-soc-src4xxx-i2c.o
 obj-$(CONFIG_SND_SOC_SSM2305)	+= snd-soc-ssm2305.o
 obj-$(CONFIG_SND_SOC_SSM2518)	+= snd-soc-ssm2518.o
 obj-$(CONFIG_SND_SOC_SSM2602)	+= snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/src4xxx-i2c.c b/sound/soc/codecs/src4xxx-i2c.c
new file mode 100644
index 000000000000..a42d1b463dbf
--- /dev/null
+++ b/sound/soc/codecs/src4xxx-i2c.c
@@ -0,0 +1,55 @@ 
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Driver for SRC4XXX codecs
+ *
+ * Copyright 2021-2022 Deqx Pty Ltd
+ * Author: Matt Flax <flatmax@flatmax.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "src4xxx.h"
+
+static int src4xxx_i2c_probe(struct i2c_client *i2c,
+			const struct i2c_device_id *id)
+{
+	return src4xxx_probe(&i2c->dev,
+		devm_regmap_init_i2c(i2c, &src4xxx_regmap_config), NULL);
+}
+
+static int src4xxx_i2c_remove(struct i2c_client *i2c)
+{
+	src4xxx_remove(&i2c->dev);
+	return 0;
+}
+
+static const struct i2c_device_id src4xxx_i2c_ids[] = {
+	{ "src4392", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, src4xxx_i2c_ids);
+
+static const struct of_device_id src4xxx_of_match[] = {
+	{ .compatible = "ti,src4392", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, src4xxx_of_match);
+
+
+static struct i2c_driver src4xxx_i2c_driver = {
+	.driver = {
+		.name = "src4xxx",
+		.of_match_table = of_match_ptr(src4xxx_of_match),
+	},
+	.probe = src4xxx_i2c_probe,
+	.remove = src4xxx_i2c_remove,
+	.id_table = src4xxx_i2c_ids,
+};
+module_i2c_driver(src4xxx_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC SRC4392 CODEC I2C driver");
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/src4xxx.c b/sound/soc/codecs/src4xxx.c
new file mode 100644
index 000000000000..46f693a59186
--- /dev/null
+++ b/sound/soc/codecs/src4xxx.c
@@ -0,0 +1,508 @@ 
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * TI SRC4xxx Audio Codec driver
+ *
+ * Copyright 2021-2022 Deqx Pty Ltd
+ * Author: Matt Flax <flatmax@flatmax.com>
+ */
+
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "src4xxx.h"
+
+struct src4xxx {
+	struct regmap *regmap;
+	bool master[2];
+	int mclk_hz;
+	struct device *dev;
+};
+
+enum {SRC4XXX_PORTA, SRC4XXX_PORTB};
+
+/* SRC attenuation */
+static const DECLARE_TLV_DB_SCALE(src_tlv, -12750, 50, 0);
+
+static const struct snd_kcontrol_new src4xxx_controls[] = {
+	SOC_DOUBLE_R_TLV("SRC vol",
+		SRC4XXX_SCR_CTL_30, SRC4XXX_SCR_CTL_31, 0, 255, 1, src_tlv),
+};
+
+/* I2S port control */
+static const char * const port_out_src_text[] = {
+	"loopback", "other_port", "DIR", "SRC"
+};
+static SOC_ENUM_SINGLE_DECL(porta_out_src_enum, SRC4XXX_PORTA_CTL_03, 4,
+	port_out_src_text);
+static SOC_ENUM_SINGLE_DECL(portb_out_src_enum, SRC4XXX_PORTB_CTL_05, 4,
+	port_out_src_text);
+static const struct snd_kcontrol_new porta_out_control =
+	SOC_DAPM_ENUM("Port A source select", porta_out_src_enum);
+static const struct snd_kcontrol_new portb_out_control =
+	SOC_DAPM_ENUM("Port B source select", portb_out_src_enum);
+
+/* Digital audio transmitter control */
+static const char * const dit_mux_text[] = {"Port A", "Port B", "DIR", "SRC"};
+static SOC_ENUM_SINGLE_DECL(dit_mux_enum, SRC4XXX_TX_CTL_07, 3, dit_mux_text);
+static const struct snd_kcontrol_new dit_mux_control =
+	SOC_DAPM_ENUM("DIT source", dit_mux_enum);
+
+/* SRC control */
+static const char * const src_in_text[] = {"Port A", "Port B", "DIR"};
+static SOC_ENUM_SINGLE_DECL(src_in_enum, SRC4XXX_SCR_CTL_2D, 0, src_in_text);
+static const struct snd_kcontrol_new src_in_control =
+	SOC_DAPM_ENUM("SRC source select", src_in_enum);
+
+/* DIR control */
+static const char * const dir_in_text[] = {"Ch 1", "Ch 2", "Ch 3", "Ch 4"};
+static SOC_ENUM_SINGLE_DECL(dir_in_enum, SRC4XXX_RCV_CTL_0D, 0, dir_in_text);
+static const struct snd_kcontrol_new dir_in_control =
+	SOC_DAPM_ENUM("Digital Input", dir_in_enum);
+
+static const struct snd_soc_dapm_widget src4xxx_dapm_widgets[] = {
+	SND_SOC_DAPM_INPUT("loopback_A"),
+	SND_SOC_DAPM_INPUT("other_port_A"),
+	SND_SOC_DAPM_INPUT("DIR_A"),
+	SND_SOC_DAPM_INPUT("SRC_A"),
+	SND_SOC_DAPM_MUX("Port A source",
+		SND_SOC_NOPM, 0, 0, &porta_out_control),
+
+	SND_SOC_DAPM_INPUT("loopback_B"),
+	SND_SOC_DAPM_INPUT("other_port_B"),
+	SND_SOC_DAPM_INPUT("DIR_B"),
+	SND_SOC_DAPM_INPUT("SRC_B"),
+	SND_SOC_DAPM_MUX("Port B source",
+		SND_SOC_NOPM, 0, 0, &portb_out_control),
+
+	SND_SOC_DAPM_INPUT("Port_A"),
+	SND_SOC_DAPM_INPUT("Port_B"),
+	SND_SOC_DAPM_INPUT("DIR_"),
+
+	/* Digital audio receivers and transmitters */
+	SND_SOC_DAPM_OUTPUT("DIR_OUT"),
+	SND_SOC_DAPM_OUTPUT("SRC_OUT"),
+	SND_SOC_DAPM_MUX("DIT Out Src", SRC4XXX_PWR_RST_01,
+		SRC4XXX_ENABLE_DIT_SHIFT, 1, &dit_mux_control),
+
+	/* Audio Interface */
+	SND_SOC_DAPM_AIF_IN("AIF_A_RX", "Playback A", 0,
+		SRC4XXX_PWR_RST_01, SRC4XXX_ENABLE_PORT_A_SHIFT, 1),
+	SND_SOC_DAPM_AIF_OUT("AIF_A_TX", "Capture A", 0,
+		SRC4XXX_PWR_RST_01, SRC4XXX_ENABLE_PORT_A_SHIFT, 1),
+	SND_SOC_DAPM_AIF_IN("AIF_B_RX", "Playback B", 0,
+		SRC4XXX_PWR_RST_01, SRC4XXX_ENABLE_PORT_B_SHIFT, 1),
+	SND_SOC_DAPM_AIF_OUT("AIF_B_TX", "Capture B", 0,
+		SRC4XXX_PWR_RST_01, SRC4XXX_ENABLE_PORT_B_SHIFT, 1),
+
+	SND_SOC_DAPM_MUX("SRC source", SND_SOC_NOPM, 0, 0, &src_in_control),
+
+	SND_SOC_DAPM_INPUT("MCLK"),
+	SND_SOC_DAPM_INPUT("RXMCLKI"),
+	SND_SOC_DAPM_INPUT("RXMCLKO"),
+
+	SND_SOC_DAPM_INPUT("RX1"),
+	SND_SOC_DAPM_INPUT("RX2"),
+	SND_SOC_DAPM_INPUT("RX3"),
+	SND_SOC_DAPM_INPUT("RX4"),
+	SND_SOC_DAPM_MUX("Digital Input", SRC4XXX_PWR_RST_01,
+		SRC4XXX_ENABLE_DIR_SHIFT, 1, &dir_in_control),
+};
+
+static const struct snd_soc_dapm_route src4xxx_audio_routes[] = {
+	/* I2S Input to Output Routing */
+	{"Port A source", "loopback", "loopback_A"},
+	{"Port A source", "other_port", "other_port_A"},
+	{"Port A source", "DIR", "DIR_A"},
+	{"Port A source", "SRC", "SRC_A"},
+	{"Port B source", "loopback", "loopback_B"},
+	{"Port B source", "other_port", "other_port_B"},
+	{"Port B source", "DIR", "DIR_B"},
+	{"Port B source", "SRC", "SRC_B"},
+	/* DIT muxing */
+	{"DIT Out Src", "Port A", "Capture A"},
+	{"DIT Out Src", "Port B", "Capture B"},
+	{"DIT Out Src", "DIR", "DIR_OUT"},
+	{"DIT Out Src", "SRC", "SRC_OUT"},
+
+	/* SRC input selection */
+	{"SRC source", "Port A", "Port_A"},
+	{"SRC source", "Port B", "Port_B"},
+	{"SRC source", "DIR", "DIR_"},
+	/* SRC mclk selection */
+	{"SRC mclk source", "Master (MCLK)", "MCLK"},
+	{"SRC mclk source", "Master (RXCLKI)", "RXMCLKI"},
+	{"SRC mclk source", "Recovered receiver clk", "RXMCLKO"},
+	/* DIR input selection */
+	{"Digital Input", "Ch 1", "RX1"},
+	{"Digital Input", "Ch 2", "RX2"},
+	{"Digital Input", "Ch 3", "RX3"},
+	{"Digital Input", "Ch 4", "RX4"},
+};
+
+
+static const struct snd_soc_component_driver src4xxx_driver = {
+	.controls = src4xxx_controls,
+	.num_controls = ARRAY_SIZE(src4xxx_controls),
+
+	.dapm_widgets = src4xxx_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(src4xxx_dapm_widgets),
+	.dapm_routes = src4xxx_audio_routes,
+	.num_dapm_routes = ARRAY_SIZE(src4xxx_audio_routes),
+};
+
+static int src4xxx_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = dai->component;
+	struct src4xxx *src4xxx = snd_soc_component_get_drvdata(component);
+	unsigned int ctrl;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		ctrl = SRC4XXX_BUS_MASTER;
+		src4xxx->master[dai->id] = true;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		ctrl = 0;
+		src4xxx->master[dai->id] = false;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		ctrl |= SRC4XXX_BUS_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		ctrl |= SRC4XXX_BUS_LEFT_J;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		ctrl |= SRC4XXX_BUS_RIGHT_J_24;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(src4xxx->regmap, SRC4XXX_BUS_FMT(dai->id),
+		SRC4XXX_BUS_FMT_MS_MASK, ctrl);
+
+	return 0;
+}
+
+static int src4xxx_set_mclk_hz(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct src4xxx *src4xxx = snd_soc_component_get_drvdata(component);
+
+	dev_info(component->dev, "changing mclk rate from %d to %d Hz\n",
+		src4xxx->mclk_hz, freq);
+	src4xxx->mclk_hz = freq;
+
+	return 0;
+}
+
+static int src4xxx_hw_params(struct snd_pcm_substream *substream,
+			struct snd_pcm_hw_params *params,
+			struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct src4xxx *src4xxx = snd_soc_component_get_drvdata(component);
+	unsigned int mclk_div;
+	int val, pj, jd, d;
+	int reg;
+	int ret;
+
+	switch (dai->id) {
+	case SRC4XXX_PORTB:
+		reg = SRC4XXX_PORTB_CTL_06;
+		break;
+	default:
+		reg = SRC4XXX_PORTA_CTL_04;
+	}
+
+	if (src4xxx->master[dai->id]) {
+		mclk_div = src4xxx->mclk_hz/params_rate(params);
+		if (src4xxx->mclk_hz != mclk_div*params_rate(params)) {
+			dev_err(component->dev,
+				"mclk %d / rate %d has a remainder.\n",
+				src4xxx->mclk_hz, params_rate(params));
+			return -EINVAL;
+		}
+
+		val = ((int)mclk_div - 128) / 128;
+		if ((val < 0) | (val > 3)) {
+			dev_err(component->dev,
+				"div register setting %d is out of range\n",
+				val);
+			dev_err(component->dev,
+				"unsupported sample rate %d Hz for the master clock of %d Hz\n",
+				params_rate(params), src4xxx->mclk_hz);
+			return -EINVAL;
+		}
+
+		/* set the TX DIV */
+		ret = regmap_update_bits(src4xxx->regmap,
+			SRC4XXX_TX_CTL_07, SRC4XXX_TX_MCLK_DIV_MASK,
+			val<<SRC4XXX_TX_MCLK_DIV_SHIFT);
+		if (ret) {
+			dev_err(component->dev,
+				"Couldn't set the TX's div register to %d << %d = 0x%x\n",
+				val, SRC4XXX_TX_MCLK_DIV_SHIFT,
+				val<<SRC4XXX_TX_MCLK_DIV_SHIFT);
+			return ret;
+		}
+
+		/* set the PLL for the digital receiver */
+		switch (src4xxx->mclk_hz) {
+		case 24576000:
+			pj = 0x22;
+			jd = 0x00;
+			d = 0x00;
+			break;
+		case 22579200:
+			pj = 0x22;
+			jd = 0x1b;
+			d = 0xa3;
+			break;
+		default:
+			/* don't error out here,
+			 * other parts of the chip are still functional
+			 */
+			dev_info(component->dev,
+				"Couldn't set the RCV PLL as this master clock rate is unknown\n");
+		}
+		ret = regmap_write(src4xxx->regmap, SRC4XXX_RCV_PLL_0F, pj);
+		if (ret < 0)
+			dev_err(component->dev,
+				"Failed to update PLL register 0x%x\n",
+				SRC4XXX_RCV_PLL_0F);
+		ret = regmap_write(src4xxx->regmap, SRC4XXX_RCV_PLL_10, jd);
+		if (ret < 0)
+			dev_err(component->dev,
+				"Failed to update PLL register 0x%x\n",
+				SRC4XXX_RCV_PLL_10);
+		ret = regmap_write(src4xxx->regmap, SRC4XXX_RCV_PLL_11, d);
+		if (ret < 0)
+			dev_err(component->dev,
+				"Failed to update PLL register 0x%x\n",
+				SRC4XXX_RCV_PLL_11);
+
+		ret = regmap_update_bits(src4xxx->regmap,
+			SRC4XXX_TX_CTL_07, SRC4XXX_TX_MCLK_DIV_MASK,
+			val<<SRC4XXX_TX_MCLK_DIV_SHIFT);
+		if (ret < 0) {
+			dev_err(component->dev,
+				"Couldn't set the TX's div register to %d << %d = 0x%x\n",
+				val, SRC4XXX_TX_MCLK_DIV_SHIFT,
+				val<<SRC4XXX_TX_MCLK_DIV_SHIFT);
+			return ret;
+		}
+
+		return regmap_update_bits(src4xxx->regmap, reg,
+					SRC4XXX_MCLK_DIV_MASK, val);
+	} else
+		dev_info(dai->dev, "not setting up MCLK as not master\n");
+
+	return 0;
+};
+
+static const struct snd_soc_dai_ops src4xxx_dai_ops = {
+	.hw_params	= src4xxx_hw_params,
+	.set_sysclk	= src4xxx_set_mclk_hz,
+	.set_fmt	= src4xxx_set_dai_fmt,
+};
+
+#define SRC4XXX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |	SNDRV_PCM_FMTBIT_S32_LE)
+#define SRC4XXX_RATES (SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000|\
+				SNDRV_PCM_RATE_88200|\
+				SNDRV_PCM_RATE_96000|\
+				SNDRV_PCM_RATE_176400|\
+				SNDRV_PCM_RATE_192000)
+
+static struct snd_soc_dai_driver src4xxx_dai_driver[] = {
+	{
+		.id = SRC4XXX_PORTA,
+		.name = "src4xxx-portA",
+		.playback = {
+			.stream_name = "Playback A",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SRC4XXX_RATES,
+			.formats = SRC4XXX_FORMATS,
+		},
+		.capture = {
+			.stream_name = "Capture A",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SRC4XXX_RATES,
+			.formats = SRC4XXX_FORMATS,
+		},
+		.ops = &src4xxx_dai_ops,
+	},
+	{
+		.id = SRC4XXX_PORTB,
+		.name = "src4xxx-portB",
+		.playback = {
+			.stream_name = "Playback B",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SRC4XXX_RATES,
+			.formats = SRC4XXX_FORMATS,
+		},
+		.capture = {
+			.stream_name = "Capture B",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SRC4XXX_RATES,
+			.formats = SRC4XXX_FORMATS,
+		},
+		.ops = &src4xxx_dai_ops,
+	},
+};
+
+static const struct reg_default src4xxx_reg_defaults[] = {
+	{ SRC4XXX_PWR_RST_01,		0x00 }, /* all powered down intially */
+	{ SRC4XXX_PORTA_CTL_03,		0x00 },
+	{ SRC4XXX_PORTA_CTL_04,		0x00 },
+	{ SRC4XXX_PORTB_CTL_05,		0x00 },
+	{ SRC4XXX_PORTB_CTL_06,		0x00 },
+	{ SRC4XXX_TX_CTL_07,		0x00 },
+	{ SRC4XXX_TX_CTL_08,		0x00 },
+	{ SRC4XXX_TX_CTL_09,		0x00 },
+	{ SRC4XXX_SRC_DIT_IRQ_MSK_0B,	0x00 },
+	{ SRC4XXX_SRC_DIT_IRQ_MODE_0C,	0x00 },
+	{ SRC4XXX_RCV_CTL_0D,		0x00 },
+	{ SRC4XXX_RCV_CTL_0E,		0x00 },
+	{ SRC4XXX_RCV_PLL_0F,		0x00 }, /* not spec. in the datasheet */
+	{ SRC4XXX_RCV_PLL_10,		0xff }, /* not spec. in the datasheet */
+	{ SRC4XXX_RCV_PLL_11,		0xff }, /* not spec. in the datasheet */
+	{ SRC4XXX_RVC_IRQ_MSK_16,	0x00 },
+	{ SRC4XXX_RVC_IRQ_MSK_17,	0x00 },
+	{ SRC4XXX_RVC_IRQ_MODE_18,	0x00 },
+	{ SRC4XXX_RVC_IRQ_MODE_19,	0x00 },
+	{ SRC4XXX_RVC_IRQ_MODE_1A,	0x00 },
+	{ SRC4XXX_GPIO_1_1B,		0x00 },
+	{ SRC4XXX_GPIO_2_1C,		0x00 },
+	{ SRC4XXX_GPIO_3_1D,		0x00 },
+	{ SRC4XXX_GPIO_4_1E,		0x00 },
+	{ SRC4XXX_SCR_CTL_2D,		0x00 },
+	{ SRC4XXX_SCR_CTL_2E,		0x00 },
+	{ SRC4XXX_SCR_CTL_2F,		0x00 },
+	{ SRC4XXX_SCR_CTL_30,		0x00 },
+	{ SRC4XXX_SCR_CTL_31,		0x00 },
+};
+
+int src4xxx_probe(struct device *dev, struct regmap *regmap,
+			void (*switch_mode)(struct device *dev))
+{
+	struct src4xxx *src4xxx;
+	int ret;
+
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	src4xxx = devm_kzalloc(dev, sizeof(*src4xxx), GFP_KERNEL);
+	if (!src4xxx)
+		return -ENOMEM;
+
+	src4xxx->regmap = regmap;
+	src4xxx->dev = dev;
+	src4xxx->mclk_hz = 0; /* mclk has not been configured yet */
+	dev_set_drvdata(dev, src4xxx);
+
+	ret = regmap_write(regmap, SRC4XXX_PWR_RST_01, SRC4XXX_RESET);
+	if (ret < 0)
+		dev_err(dev, "Failed to issue reset: %d\n", ret);
+	usleep_range(1, 500); /* sleep for more then 500 ns */
+	ret = regmap_write(regmap, SRC4XXX_PWR_RST_01, SRC4XXX_POWER_DOWN);
+	if (ret < 0)
+		dev_err(dev, "Failed to decommission reset: %d\n", ret);
+	usleep_range(500, 1000); /* sleep for 500 us or more */
+
+	ret = regmap_update_bits(src4xxx->regmap, SRC4XXX_PWR_RST_01,
+		SRC4XXX_POWER_ENABLE, SRC4XXX_POWER_ENABLE);
+	if (ret < 0)
+		dev_err(dev, "Failed to port A and B : %d\n", ret);
+
+	/* set receiver to use master clock (rcv mclk is most likely jittery) */
+	ret = regmap_update_bits(src4xxx->regmap, SRC4XXX_RCV_CTL_0D,
+		SRC4XXX_RXCLK_MCLK,	SRC4XXX_RXCLK_MCLK);
+	if (ret < 0)
+		dev_err(dev,
+			"Failed to enable mclk as the PLL1 DIR reference : %d\n", ret);
+
+	/* default to leaving the PLL2 running on loss of lock, divide by 8 */
+	ret = regmap_update_bits(src4xxx->regmap, SRC4XXX_RCV_CTL_0E,
+		SRC4XXX_PLL2_DIV_8 | SRC4XXX_REC_MCLK_EN | SRC4XXX_PLL2_LOL,
+		SRC4XXX_PLL2_DIV_8 | SRC4XXX_REC_MCLK_EN | SRC4XXX_PLL2_LOL);
+	if (ret < 0)
+		dev_err(dev, "Failed to enable mclk rec and div : %d\n", ret);
+
+	ret = devm_snd_soc_register_component(dev, &src4xxx_driver,
+			src4xxx_dai_driver, ARRAY_SIZE(src4xxx_dai_driver));
+	if (ret == 0)
+		dev_info(dev, "src4392 probe ok %d\n", ret);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(src4xxx_probe);
+
+static bool src4xxx_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SRC4XXX_RES_00:
+	case SRC4XXX_GLOBAL_ITR_STS_02:
+	case SRC4XXX_SRC_DIT_STS_0A:
+	case SRC4XXX_NON_AUDIO_D_12:
+	case SRC4XXX_RVC_STS_13:
+	case SRC4XXX_RVC_STS_14:
+	case SRC4XXX_RVC_STS_15:
+	case SRC4XXX_SUB_CODE_1F:
+	case SRC4XXX_SUB_CODE_20:
+	case SRC4XXX_SUB_CODE_21:
+	case SRC4XXX_SUB_CODE_22:
+	case SRC4XXX_SUB_CODE_23:
+	case SRC4XXX_SUB_CODE_24:
+	case SRC4XXX_SUB_CODE_25:
+	case SRC4XXX_SUB_CODE_26:
+	case SRC4XXX_SUB_CODE_27:
+	case SRC4XXX_SUB_CODE_28:
+	case SRC4XXX_PC_PREAMBLE_HI_29:
+	case SRC4XXX_PC_PREAMBLE_LO_2A:
+	case SRC4XXX_PD_PREAMBLE_HI_2B:
+	case SRC4XXX_PC_PREAMBLE_LO_2C:
+	case SRC4XXX_IO_RATIO_32:
+	case SRC4XXX_IO_RATIO_33:
+		return true;
+	}
+
+	if (reg > SRC4XXX_IO_RATIO_33 && reg < SRC4XXX_PAGE_SEL_7F)
+		return true;
+
+	return false;
+}
+
+const struct regmap_config src4xxx_regmap_config = {
+	.val_bits = 8,
+	.reg_bits = 8,
+	.max_register = SRC4XXX_IO_RATIO_33,
+
+	.reg_defaults = src4xxx_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(src4xxx_reg_defaults),
+	.volatile_reg = src4xxx_volatile_register,
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(src4xxx_regmap_config);
+
+MODULE_DESCRIPTION("ASoC SRC4XXX CODEC driver");
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/src4xxx.h b/sound/soc/codecs/src4xxx.h
new file mode 100644
index 000000000000..0407db8a8445
--- /dev/null
+++ b/sound/soc/codecs/src4xxx.h
@@ -0,0 +1,115 @@ 
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * src4xxx.h  --  SRC4XXX ALSA SoC audio driver
+ *
+ * Copyright 2021-2022 Deqx Pty Ltd
+ * Author: Matt R Flax <flatmax@flatmax.com>
+ */
+
+#ifndef __SRC4XXX_H__
+#define __SRC4XXX_H__
+
+#define SRC4XXX_RES_00 0x00
+#define SRC4XXX_PWR_RST_01 0x01
+#define SRC4XXX_RESET 0x80
+#define SRC4XXX_POWER_DOWN 0x00
+#define SRC4XXX_POWER_ENABLE 0x20
+#define SRC4XXX_ENABLE_SRC 0x1
+#define SRC4XXX_ENABLE_SRC_SHIFT 0
+#define SRC4XXX_ENABLE_DIR 0x2
+#define SRC4XXX_ENABLE_DIR_SHIFT 1
+#define SRC4XXX_ENABLE_DIT 0x4
+#define SRC4XXX_ENABLE_DIT_SHIFT 2
+#define SRC4XXX_ENABLE_PORT_B 0x8
+#define SRC4XXX_ENABLE_PORT_B_SHIFT 3
+#define SRC4XXX_ENABLE_PORT_A 0x10
+#define SRC4XXX_ENABLE_PORT_A_SHIFT 4
+
+#define SRC4XXX_PORTA_CTL_03 0x03
+#define SRC4XXX_BUS_MASTER 0x8
+#define SRC4XXX_BUS_LEFT_J 0x0
+#define SRC4XXX_BUS_I2S 0x1
+#define SRC4XXX_BUS_RIGHT_J_16 0x4
+#define SRC4XXX_BUS_RIGHT_J_18 0x5
+#define SRC4XXX_BUS_RIGHT_J_20 0x6
+#define SRC4XXX_BUS_RIGHT_J_24 0x7
+#define SRC4XXX_BUS_FMT_MS_MASK 0xf
+
+#define SRC4XXX_PORTA_CTL_04 0x04
+#define SRC4XXX_MCLK_DIV_MASK 0x3
+
+#define SRC4XXX_BUS_FMT(id) (SRC4XXX_PORTA_CTL_03+2*id)
+#define SRC4XXX_BUS_CLK(id) (SRC4XXX_PORTA_CTL_04+2*id)
+
+#define SRC4XXX_PORTB_CTL_05 0x05
+#define SRC4XXX_PORTB_CTL_06 0x06
+
+#define SRC4XXX_TX_CTL_07 0x07
+#define SRC4XXX_TX_MCLK_DIV_MASK 0x60
+#define SRC4XXX_TX_MCLK_DIV_SHIFT 5
+
+#define SRC4XXX_TX_CTL_08 0x08
+#define SRC4XXX_TX_CTL_09 0x09
+#define SRC4XXX_SRC_DIT_IRQ_MSK_0B 0x0B
+#define SRC4XXX_SRC_BTI_EN 0x01
+#define SRC4XXX_SRC_TSLIP_EN 0x02
+#define SRC4XXX_SRC_DIT_IRQ_MODE_0C 0x0C
+#define SRC4XXX_RCV_CTL_0D 0x0D
+#define SRC4XXX_RXCLK_RXCKI 0x0
+#define SRC4XXX_RXCLK_MCLK 0x8
+#define SRC4XXX_RCV_CTL_0E 0x0E
+#define SRC4XXX_REC_MCLK_EN 0x1
+#define SRC4XXX_PLL2_DIV_0 (0x0<<1)
+#define SRC4XXX_PLL2_DIV_2 (0x1<<1)
+#define SRC4XXX_PLL2_DIV_4 (0x2<<1)
+#define SRC4XXX_PLL2_DIV_8 (0x3<<1)
+#define SRC4XXX_PLL2_LOL 0x8
+#define SRC4XXX_RCV_PLL_0F 0x0F
+#define SRC4XXX_RCV_PLL_10 0x10
+#define SRC4XXX_RCV_PLL_11 0x11
+#define SRC4XXX_RVC_IRQ_MSK_16 0x16
+#define SRC4XXX_RVC_IRQ_MSK_17 0x17
+#define SRC4XXX_RVC_IRQ_MODE_18 0x18
+#define SRC4XXX_RVC_IRQ_MODE_19 0x19
+#define SRC4XXX_RVC_IRQ_MODE_1A 0x1A
+#define SRC4XXX_GPIO_1_1B 0x1B
+#define SRC4XXX_GPIO_2_1C 0x1C
+#define SRC4XXX_GPIO_3_1D 0x1D
+#define SRC4XXX_GPIO_4_1E 0x1E
+#define SRC4XXX_SCR_CTL_2D 0x2D
+#define SRC4XXX_SCR_CTL_2E 0x2E
+#define SRC4XXX_SCR_CTL_2F 0x2F
+#define SRC4XXX_SCR_CTL_30 0x30
+#define SRC4XXX_SCR_CTL_31 0x31
+#define SRC4XXX_PAGE_SEL_7F 0x7F
+
+// read only registers
+#define SRC4XXX_GLOBAL_ITR_STS_02 0x02
+#define SRC4XXX_SRC_DIT_STS_0A 0x0A
+#define SRC4XXX_NON_AUDIO_D_12 0x12
+#define SRC4XXX_RVC_STS_13 0x13
+#define SRC4XXX_RVC_STS_14 0x14
+#define SRC4XXX_RVC_STS_15 0x15
+#define SRC4XXX_SUB_CODE_1F 0x1F
+#define SRC4XXX_SUB_CODE_20 0x20
+#define SRC4XXX_SUB_CODE_21 0x21
+#define SRC4XXX_SUB_CODE_22 0x22
+#define SRC4XXX_SUB_CODE_23 0x23
+#define SRC4XXX_SUB_CODE_24 0x24
+#define SRC4XXX_SUB_CODE_25 0x25
+#define SRC4XXX_SUB_CODE_26 0x26
+#define SRC4XXX_SUB_CODE_27 0x27
+#define SRC4XXX_SUB_CODE_28 0x28
+#define SRC4XXX_PC_PREAMBLE_HI_29 0x29
+#define SRC4XXX_PC_PREAMBLE_LO_2A 0x2A
+#define SRC4XXX_PD_PREAMBLE_HI_2B 0x2B
+#define SRC4XXX_PC_PREAMBLE_LO_2C 0x2C
+#define SRC4XXX_IO_RATIO_32 0x32
+#define SRC4XXX_IO_RATIO_33 0x33
+
+int src4xxx_probe(struct device *dev, struct regmap *regmap,
+		void (*switch_mode)(struct device *dev));
+void src4xxx_remove(struct device *dev);
+extern const struct regmap_config src4xxx_regmap_config;
+
+#endif /* __SRC4XXX_H__ */