summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2024-10-01 14:51:29 +0100
committerMark Brown <broonie@kernel.org>2024-10-01 14:51:29 +0100
commit436bf27903ee56a242bcd373053dd8d1b5d17915 (patch)
treeeb12c1a82868db8ddaf55e6bf7bf96e7c60124ae
parent0243533fe2fc6b9aa9843b5cd4c1b0088b5363eb (diff)
parentdc9004ea273a9141c16b90a687da70b77f5a640a (diff)
ASoC: Add NTP8918 and NTP8835 codecs support
Merge series from Igor Prusov <ivprusov@salutedevices.com>: This series adds support for two NeoFidelity amplifiers. For both amplifiers vendor provides software for equalizer and filters configuration, which generates firmware files with registers values. Since in both cases those files have same encoding, a common helper module is added to get firmware via request_firmware() API and set registers values.
-rw-r--r--Documentation/devicetree/bindings/sound/neofidelity,ntp8835.yaml73
-rw-r--r--Documentation/devicetree/bindings/sound/neofidelity,ntp8918.yaml70
-rw-r--r--Documentation/devicetree/bindings/vendor-prefixes.yaml2
-rw-r--r--sound/soc/codecs/Kconfig13
-rw-r--r--sound/soc/codecs/Makefile6
-rw-r--r--sound/soc/codecs/ntp8835.c480
-rw-r--r--sound/soc/codecs/ntp8918.c397
-rw-r--r--sound/soc/codecs/ntpfw.c137
-rw-r--r--sound/soc/codecs/ntpfw.h23
9 files changed, 1201 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/sound/neofidelity,ntp8835.yaml b/Documentation/devicetree/bindings/sound/neofidelity,ntp8835.yaml
new file mode 100644
index 000000000000..44d72a2ddfc9
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/neofidelity,ntp8835.yaml
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/neofidelity,ntp8835.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NeoFidelity NTP8835/NTP8835C Amplifiers
+
+maintainers:
+ - Igor Prusov <ivprusov@salutedevices.com>
+
+description: |
+ The NTP8835 is a single chip full digital audio amplifier
+ including power stages for stereo amplifier systems.
+ NTP8835 is integrated with versatile digital audio signal
+ processing functions, high-performance, high-fidelity fully
+ digital PWM modulator and two high-power full-bridge MOSFET
+ power stages. NTP8835C has identical programming interface,
+ but has different output signal characteristics.
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - neofidelity,ntp8835
+ - neofidelity,ntp8835c
+
+ reg:
+ enum:
+ - 0x2a
+ - 0x2b
+ - 0x2c
+ - 0x2d
+
+ reset-gpios:
+ maxItems: 1
+
+ '#sound-dai-cells':
+ const: 0
+
+ clocks:
+ maxItems: 4
+
+ clock-names:
+ items:
+ - const: wck
+ - const: bck
+ - const: scl
+ - const: mclk
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ audio-codec@2b {
+ compatible = "neofidelity,ntp8835";
+ #sound-dai-cells = <0>;
+ reg = <0x2b>;
+ reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>;
+ clocks = <&clkc 551>, <&clkc 552>, <&clkc 553>, <&clkc 554>;
+ clock-names = "wck", "bck", "scl", "mclk";
+ };
+ };
diff --git a/Documentation/devicetree/bindings/sound/neofidelity,ntp8918.yaml b/Documentation/devicetree/bindings/sound/neofidelity,ntp8918.yaml
new file mode 100644
index 000000000000..952768b35902
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/neofidelity,ntp8918.yaml
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/neofidelity,ntp8918.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NeoFidelity NTP8918 Amplifier
+
+maintainers:
+ - Igor Prusov <ivprusov@salutedevices.com>
+
+description:
+ The NTP8918 is a single chip full digital audio amplifier
+ including power stage for stereo amplifier system.
+ The NTP8918 is integrated with versatile digital audio signal
+ processing functions, high-performance, high-fidelity fully
+ digital PWM modulator and two high-power full-bridge MOSFET
+ power stages.
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - neofidelity,ntp8918
+
+ reg:
+ enum:
+ - 0x2a
+ - 0x2b
+ - 0x2c
+ - 0x2d
+
+ reset-gpios:
+ maxItems: 1
+
+ '#sound-dai-cells':
+ const: 0
+
+ clocks:
+ maxItems: 3
+
+ clock-names:
+ items:
+ - const: wck
+ - const: scl
+ - const: bck
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ audio-codec@2a {
+ compatible = "neofidelity,ntp8918";
+ #sound-dai-cells = <0>;
+ reg = <0x2a>;
+ clocks = <&clkc 150>, <&clkc 151>, <&clkc 152>;
+ clock-names = "wck", "scl", "bck";
+ reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index b320a39de7fe..fbfce9b4ae6b 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1013,6 +1013,8 @@ patternProperties:
description: Shanghai Neardi Technology Co., Ltd.
"^nec,.*":
description: NEC LCD Technologies, Ltd.
+ "^neofidelity,.*":
+ description: Neofidelity Inc.
"^neonode,.*":
description: Neonode Inc.
"^netgear,.*":
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 7092842480ef..c6c4c7481b4c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -2565,6 +2565,19 @@ config SND_SOC_NAU8825
tristate
depends on I2C
+config SND_SOC_NTPFW
+ tristate
+
+config SND_SOC_NTP8918
+ select SND_SOC_NTPFW
+ tristate "NeoFidelity NTP8918 amplifier"
+ depends on I2C
+
+config SND_SOC_NTP8835
+ select SND_SOC_NTPFW
+ tristate "NeoFidelity NTP8835 and NTP8835C amplifiers"
+ depends on I2C
+
config SND_SOC_TPA6130A2
tristate "Texas Instruments TPA6130A2 headphone amplifier"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 54cbc3feae32..850c6249e3df 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -189,6 +189,9 @@ snd-soc-nau8821-y := nau8821.o
snd-soc-nau8822-y := nau8822.o
snd-soc-nau8824-y := nau8824.o
snd-soc-nau8825-y := nau8825.o
+snd-soc-ntp8835-y := ntp8835.o
+snd-soc-ntp8918-y := ntp8918.o
+snd-soc-ntpfw-y := ntpfw.o
snd-soc-hdmi-codec-y := hdmi-codec.o
snd-soc-pcm1681-y := pcm1681.o
snd-soc-pcm1789-codec-y := pcm1789.o
@@ -591,6 +594,9 @@ obj-$(CONFIG_SND_SOC_NAU8821) += snd-soc-nau8821.o
obj-$(CONFIG_SND_SOC_NAU8822) += snd-soc-nau8822.o
obj-$(CONFIG_SND_SOC_NAU8824) += snd-soc-nau8824.o
obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o
+obj-$(CONFIG_SND_SOC_NTP8835) += snd-soc-ntp8835.o
+obj-$(CONFIG_SND_SOC_NTP8918) += snd-soc-ntp8918.o
+obj-$(CONFIG_SND_SOC_NTPFW) += snd-soc-ntpfw.o
obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o
obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o
obj-$(CONFIG_SND_SOC_PCM179X) += snd-soc-pcm179x-codec.o
diff --git a/sound/soc/codecs/ntp8835.c b/sound/soc/codecs/ntp8835.c
new file mode 100644
index 000000000000..97056d8de2bb
--- /dev/null
+++ b/sound/soc/codecs/ntp8835.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the NTP8835/NTP8835C Audio Amplifiers
+ *
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Igor Prusov <ivprusov@salutedevices.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/bits.h>
+#include <linux/reset.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#include <sound/initval.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-component.h>
+#include <sound/tlv.h>
+
+#include "ntpfw.h"
+
+#define NTP8835_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+#define NTP8835_INPUT_FMT 0x0
+#define NTP8835_INPUT_FMT_MASTER_MODE BIT(0)
+#define NTP8835_INPUT_FMT_GSA_MODE BIT(1)
+#define NTP8835_GSA_FMT 0x1
+#define NTP8835_GSA_BS_MASK GENMASK(3, 2)
+#define NTP8835_GSA_BS(x) ((x) << 2)
+#define NTP8835_GSA_RIGHT_J BIT(0)
+#define NTP8835_GSA_LSB BIT(1)
+#define NTP8835_MCLK_FREQ_CTRL 0x2
+#define NTP8835_MCLK_FREQ_MCF GENMASK(1, 0)
+#define NTP8835_SOFT_MUTE 0x26
+#define NTP8835_SOFT_MUTE_SM1 BIT(0)
+#define NTP8835_SOFT_MUTE_SM2 BIT(1)
+#define NTP8835_SOFT_MUTE_SM3 BIT(2)
+#define NTP8835_PWM_SWITCH 0x27
+#define NTP8835_PWM_SWITCH_POF1 BIT(0)
+#define NTP8835_PWM_SWITCH_POF2 BIT(1)
+#define NTP8835_PWM_SWITCH_POF3 BIT(2)
+#define NTP8835_PWM_MASK_CTRL0 0x28
+#define NTP8835_PWM_MASK_CTRL0_OUT_LOW BIT(1)
+#define NTP8835_PWM_MASK_CTRL0_FPMLD BIT(2)
+#define NTP8835_MASTER_VOL 0x2e
+#define NTP8835_CHNL_A_VOL 0x2f
+#define NTP8835_CHNL_B_VOL 0x30
+#define NTP8835_CHNL_C_VOL 0x31
+#define REG_MAX NTP8835_CHNL_C_VOL
+
+#define NTP8835_FW_NAME "eq_8835.bin"
+#define NTP8835_FW_MAGIC 0x38383335 /* "8835" */
+
+struct ntp8835_priv {
+ struct i2c_client *i2c;
+ struct reset_control *reset;
+ unsigned int format;
+ struct clk *mclk;
+ unsigned int mclk_rate;
+};
+
+static const DECLARE_TLV_DB_RANGE(ntp8835_vol_scale,
+ 0, 1, TLV_DB_SCALE_ITEM(-15000, 0, 0),
+ 2, 6, TLV_DB_SCALE_ITEM(-15000, 1000, 0),
+ 7, 0xff, TLV_DB_SCALE_ITEM(-10000, 50, 0),
+);
+
+static int ntp8835_mute_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->access =
+ (SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE);
+ uinfo->count = 1;
+
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ uinfo->value.integer.step = 1;
+
+ return 0;
+}
+
+static int ntp8835_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int val;
+
+ val = snd_soc_component_read(component, NTP8835_SOFT_MUTE);
+
+ ucontrol->value.integer.value[0] = val ? 0 : 1;
+ return 0;
+}
+
+static int ntp8835_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int val;
+
+ val = ucontrol->value.integer.value[0] ? 0 : 7;
+
+ snd_soc_component_write(component, NTP8835_SOFT_MUTE, val);
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new ntp8835_vol_control[] = {
+ SOC_SINGLE_TLV("Playback Volume", NTP8835_MASTER_VOL, 0,
+ 0xff, 0, ntp8835_vol_scale),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Playback Switch",
+ .info = ntp8835_mute_info,
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .get = ntp8835_mute_get,
+ .put = ntp8835_mute_put,
+ },
+};
+
+static void ntp8835_reset_gpio(struct ntp8835_priv *ntp8835)
+{
+ /*
+ * Proper initialization sequence for NTP835 amplifier requires driving
+ * /RESET signal low during power up for at least 0.1us. The sequence is,
+ * according to NTP8835 datasheet, 6.2 Timing Sequence (recommended):
+ * Deassert for T2 >= 1ms...
+ */
+ reset_control_deassert(ntp8835->reset);
+ fsleep(1000);
+
+ /* ...Assert for T3 >= 0.1us... */
+ reset_control_assert(ntp8835->reset);
+ fsleep(1);
+
+ /* ...Deassert, and wait for T4 >= 0.5ms before sound on sequence. */
+ reset_control_deassert(ntp8835->reset);
+ fsleep(500);
+}
+
+static const struct reg_sequence ntp8835_sound_on[] = {
+ { NTP8835_PWM_MASK_CTRL0, NTP8835_PWM_MASK_CTRL0_FPMLD },
+ { NTP8835_PWM_SWITCH, 0x00 },
+ { NTP8835_SOFT_MUTE, 0x00 },
+};
+
+static const struct reg_sequence ntp8835_sound_off[] = {
+ { NTP8835_SOFT_MUTE, NTP8835_SOFT_MUTE_SM1 |
+ NTP8835_SOFT_MUTE_SM2 |
+ NTP8835_SOFT_MUTE_SM3 },
+
+ { NTP8835_PWM_SWITCH, NTP8835_PWM_SWITCH_POF1 |
+ NTP8835_PWM_SWITCH_POF2 |
+ NTP8835_PWM_SWITCH_POF3 },
+
+ { NTP8835_PWM_MASK_CTRL0, NTP8835_PWM_MASK_CTRL0_OUT_LOW |
+ NTP8835_PWM_MASK_CTRL0_FPMLD },
+};
+
+static int ntp8835_load_firmware(struct ntp8835_priv *ntp8835)
+{
+ int ret;
+
+ ret = ntpfw_load(ntp8835->i2c, NTP8835_FW_NAME, NTP8835_FW_MAGIC);
+ if (ret == -ENOENT) {
+ dev_warn_once(&ntp8835->i2c->dev,
+ "Could not find firmware %s\n", NTP8835_FW_NAME);
+ return 0;
+ }
+
+ return ret;
+}
+
+static int ntp8835_snd_suspend(struct snd_soc_component *component)
+{
+ struct ntp8835_priv *ntp8835 = snd_soc_component_get_drvdata(component);
+
+ regcache_cache_only(component->regmap, true);
+
+ regmap_multi_reg_write_bypassed(component->regmap,
+ ntp8835_sound_off,
+ ARRAY_SIZE(ntp8835_sound_off));
+
+ /*
+ * According to NTP8835 datasheet, 6.2 Timing Sequence (recommended):
+ * wait after sound off for T6 >= 0.5ms
+ */
+ fsleep(500);
+ reset_control_assert(ntp8835->reset);
+
+ regcache_mark_dirty(component->regmap);
+ clk_disable_unprepare(ntp8835->mclk);
+
+ return 0;
+}
+
+static int ntp8835_snd_resume(struct snd_soc_component *component)
+{
+ struct ntp8835_priv *ntp8835 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ ntp8835_reset_gpio(ntp8835);
+ ret = clk_prepare_enable(ntp8835->mclk);
+ if (ret)
+ return ret;
+
+ regmap_multi_reg_write_bypassed(component->regmap,
+ ntp8835_sound_on,
+ ARRAY_SIZE(ntp8835_sound_on));
+
+ ret = ntp8835_load_firmware(ntp8835);
+ if (ret) {
+ dev_err(&ntp8835->i2c->dev, "Failed to load firmware\n");
+ return ret;
+ }
+
+ regcache_cache_only(component->regmap, false);
+ snd_soc_component_cache_sync(component);
+
+ return 0;
+}
+
+static int ntp8835_probe(struct snd_soc_component *component)
+{
+ int ret;
+ struct ntp8835_priv *ntp8835 = snd_soc_component_get_drvdata(component);
+ struct device *dev = component->dev;
+
+ ret = snd_soc_add_component_controls(component, ntp8835_vol_control,
+ ARRAY_SIZE(ntp8835_vol_control));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add controls\n");
+
+ ret = ntp8835_load_firmware(ntp8835);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to load firmware\n");
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget ntp8835_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("AIFIN", "Playback", SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_OUTPUT("OUT1"),
+ SND_SOC_DAPM_OUTPUT("OUT2"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_route ntp8835_dapm_routes[] = {
+ { "OUT1", NULL, "AIFIN" },
+ { "OUT2", NULL, "AIFIN" },
+ { "OUT3", NULL, "AIFIN" },
+};
+
+static int ntp8835_set_component_sysclk(struct snd_soc_component *component,
+ int clk_id, int source,
+ unsigned int freq, int dir)
+{
+ struct ntp8835_priv *ntp8835 = snd_soc_component_get_drvdata(component);
+
+ switch (freq) {
+ case 12288000:
+ case 24576000:
+ case 18432000:
+ ntp8835->mclk_rate = freq;
+ break;
+ default:
+ ntp8835->mclk_rate = 0;
+ dev_err(component->dev, "Unsupported MCLK value: %u", freq);
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_component_ntp8835 = {
+ .probe = ntp8835_probe,
+ .suspend = ntp8835_snd_suspend,
+ .resume = ntp8835_snd_resume,
+ .dapm_widgets = ntp8835_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ntp8835_dapm_widgets),
+ .dapm_routes = ntp8835_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(ntp8835_dapm_routes),
+ .set_sysclk = ntp8835_set_component_sysclk,
+};
+
+static int ntp8835_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 ntp8835_priv *ntp8835 = snd_soc_component_get_drvdata(component);
+ unsigned int input_fmt = 0;
+ unsigned int gsa_fmt = 0;
+ unsigned int gsa_fmt_mask;
+ unsigned int mcf;
+ int ret;
+
+ switch (ntp8835->mclk_rate) {
+ case 12288000:
+ mcf = 0;
+ break;
+ case 24576000:
+ mcf = 1;
+ break;
+ case 18432000:
+ mcf = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = snd_soc_component_update_bits(component, NTP8835_MCLK_FREQ_CTRL,
+ NTP8835_MCLK_FREQ_MCF, mcf);
+ if (ret)
+ return ret;
+
+ switch (ntp8835->format) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ input_fmt |= NTP8835_INPUT_FMT_GSA_MODE;
+ gsa_fmt |= NTP8835_GSA_RIGHT_J;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ input_fmt |= NTP8835_INPUT_FMT_GSA_MODE;
+ break;
+ }
+
+ ret = snd_soc_component_update_bits(component, NTP8835_INPUT_FMT,
+ NTP8835_INPUT_FMT_MASTER_MODE |
+ NTP8835_INPUT_FMT_GSA_MODE,
+ input_fmt);
+
+ if (!(input_fmt & NTP8835_INPUT_FMT_GSA_MODE) || ret < 0)
+ return ret;
+
+ switch (params_width(params)) {
+ case 24:
+ gsa_fmt |= NTP8835_GSA_BS(0);
+ break;
+ case 20:
+ gsa_fmt |= NTP8835_GSA_BS(1);
+ break;
+ case 18:
+ gsa_fmt |= NTP8835_GSA_BS(2);
+ break;
+ case 16:
+ gsa_fmt |= NTP8835_GSA_BS(3);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ gsa_fmt_mask = NTP8835_GSA_BS_MASK |
+ NTP8835_GSA_RIGHT_J |
+ NTP8835_GSA_LSB;
+ return snd_soc_component_update_bits(component, NTP8835_GSA_FMT,
+ gsa_fmt_mask, gsa_fmt);
+}
+
+static int ntp8835_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ struct ntp8835_priv *ntp8835 = snd_soc_component_get_drvdata(component);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ ntp8835->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+};
+
+static const struct snd_soc_dai_ops ntp8835_dai_ops = {
+ .hw_params = ntp8835_hw_params,
+ .set_fmt = ntp8835_set_fmt,
+};
+
+static struct snd_soc_dai_driver ntp8835_dai = {
+ .name = "ntp8835-amplifier",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 3,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = NTP8835_FORMATS,
+ },
+ .ops = &ntp8835_dai_ops,
+};
+
+static const struct regmap_config ntp8835_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = REG_MAX,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int ntp8835_i2c_probe(struct i2c_client *i2c)
+{
+ struct ntp8835_priv *ntp8835;
+ struct regmap *regmap;
+ int ret;
+
+ ntp8835 = devm_kzalloc(&i2c->dev, sizeof(*ntp8835), GFP_KERNEL);
+ if (!ntp8835)
+ return -ENOMEM;
+
+ ntp8835->i2c = i2c;
+
+ ntp8835->reset = devm_reset_control_get_shared(&i2c->dev, NULL);
+ if (IS_ERR(ntp8835->reset))
+ return dev_err_probe(&i2c->dev, PTR_ERR(ntp8835->reset),
+ "Failed to get reset\n");
+
+ ret = reset_control_deassert(ntp8835->reset);
+ if (ret)
+ return dev_err_probe(&i2c->dev, PTR_ERR(ntp8835->reset),
+ "Failed to deassert reset\n");
+
+ dev_set_drvdata(&i2c->dev, ntp8835);
+
+ ntp8835_reset_gpio(ntp8835);
+
+ regmap = devm_regmap_init_i2c(i2c, &ntp8835_regmap);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&i2c->dev, PTR_ERR(regmap),
+ "Failed to allocate regmap\n");
+
+ ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_ntp8835,
+ &ntp8835_dai, 1);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret,
+ "Failed to register component\n");
+
+ ntp8835->mclk = devm_clk_get_enabled(&i2c->dev, "mclk");
+ if (IS_ERR(ntp8835->mclk))
+ return dev_err_probe(&i2c->dev, PTR_ERR(ntp8835->mclk), "failed to get mclk\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id ntp8835_i2c_id[] = {
+ { "ntp8835", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ntp8835_i2c_id);
+
+static const struct of_device_id ntp8835_of_match[] = {
+ {.compatible = "neofidelity,ntp8835",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, ntp8835_of_match);
+
+static struct i2c_driver ntp8835_i2c_driver = {
+ .probe = ntp8835_i2c_probe,
+ .id_table = ntp8835_i2c_id,
+ .driver = {
+ .name = "ntp8835",
+ .of_match_table = ntp8835_of_match,
+ },
+};
+module_i2c_driver(ntp8835_i2c_driver);
+
+MODULE_AUTHOR("Igor Prusov <ivprusov@salutedevices.com>");
+MODULE_DESCRIPTION("NTP8835 Audio Amplifier Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ntp8918.c b/sound/soc/codecs/ntp8918.c
new file mode 100644
index 000000000000..0493ab6acbe4
--- /dev/null
+++ b/sound/soc/codecs/ntp8918.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the NTP8918 Audio Amplifier
+ *
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Igor Prusov <ivprusov@salutedevices.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+
+#include <sound/initval.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-component.h>
+#include <sound/tlv.h>
+
+#include "ntpfw.h"
+
+#define NTP8918_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+
+#define NTP8918_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+#define NTP8918_INPUT_FMT 0x0
+#define NTP8918_INPUT_FMT_MASTER_MODE BIT(0)
+#define NTP8918_INPUT_FMT_GSA_MODE BIT(1)
+#define NTP8918_GSA_FMT 0x1
+#define NTP8918_GSA_BS_MASK GENMASK(3, 2)
+#define NTP8918_GSA_BS(x) ((x) << 2)
+#define NTP8918_GSA_RIGHT_J BIT(0)
+#define NTP8918_GSA_LSB BIT(1)
+#define NTP8918_MCLK_FREQ_CTRL 0x2
+#define NTP8918_MCLK_FREQ_MCF GENMASK(1, 0)
+#define NTP8918_MASTER_VOL 0x0C
+#define NTP8918_CHNL_A_VOL 0x17
+#define NTP8918_CHNL_B_VOL 0x18
+#define NTP8918_SOFT_MUTE 0x33
+#define NTP8918_SOFT_MUTE_SM1 BIT(0)
+#define NTP8918_SOFT_MUTE_SM2 BIT(1)
+#define NTP8918_PWM_SWITCH 0x34
+#define NTP8918_PWM_MASK_CTRL0 0x35
+#define REG_MAX NTP8918_PWM_MASK_CTRL0
+
+#define NTP8918_FW_NAME "eq_8918.bin"
+#define NTP8918_FW_MAGIC 0x38393138 /* "8918" */
+
+struct ntp8918_priv {
+ struct i2c_client *i2c;
+ struct clk *bck;
+ struct reset_control *reset;
+ unsigned int format;
+};
+
+static const DECLARE_TLV_DB_SCALE(ntp8918_master_vol_scale, -12550, 50, 0);
+
+static const struct snd_kcontrol_new ntp8918_vol_control[] = {
+ SOC_SINGLE_RANGE_TLV("Playback Volume", NTP8918_MASTER_VOL, 0,
+ 0x04, 0xff, 0, ntp8918_master_vol_scale),
+ SOC_SINGLE("Playback Switch", NTP8918_PWM_MASK_CTRL0, 1, 1, 1),
+};
+
+static void ntp8918_reset_gpio(struct ntp8918_priv *ntp8918)
+{
+ /*
+ * Proper initialization sequence for NTP8918 amplifier requires driving
+ * /RESET signal low during power up for at least 0.1us. The sequence is,
+ * according to NTP8918 datasheet, 6.2 Timing Sequence 1:
+ * Deassert for T2 >= 1ms...
+ */
+ reset_control_deassert(ntp8918->reset);
+ fsleep(1000);
+
+ /* ...Assert for T3 >= 0.1us... */
+ reset_control_assert(ntp8918->reset);
+ fsleep(1);
+
+ /* ...Deassert, and wait for T4 >= 0.5ms before sound on sequence. */
+ reset_control_deassert(ntp8918->reset);
+ fsleep(500);
+}
+
+static const struct reg_sequence ntp8918_sound_off[] = {
+ { NTP8918_MASTER_VOL, 0 },
+};
+
+static const struct reg_sequence ntp8918_sound_on[] = {
+ { NTP8918_MASTER_VOL, 0b11 },
+};
+
+static int ntp8918_load_firmware(struct ntp8918_priv *ntp8918)
+{
+ int ret;
+
+ ret = ntpfw_load(ntp8918->i2c, NTP8918_FW_NAME, NTP8918_FW_MAGIC);
+ if (ret == -ENOENT) {
+ dev_warn_once(&ntp8918->i2c->dev, "Could not find firmware %s\n",
+ NTP8918_FW_NAME);
+ return 0;
+ }
+
+ return ret;
+}
+
+static int ntp8918_snd_suspend(struct snd_soc_component *component)
+{
+ struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component);
+
+ regcache_cache_only(component->regmap, true);
+
+ regmap_multi_reg_write_bypassed(component->regmap,
+ ntp8918_sound_off,
+ ARRAY_SIZE(ntp8918_sound_off));
+
+ /*
+ * According to NTP8918 datasheet, 6.2 Timing Sequence 1:
+ * wait after sound off for T6 >= 0.5ms
+ */
+ fsleep(500);
+ reset_control_assert(ntp8918->reset);
+
+ regcache_mark_dirty(component->regmap);
+ clk_disable_unprepare(ntp8918->bck);
+
+ return 0;
+}
+
+static int ntp8918_snd_resume(struct snd_soc_component *component)
+{
+ struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ ret = clk_prepare_enable(ntp8918->bck);
+ if (ret)
+ return ret;
+
+ ntp8918_reset_gpio(ntp8918);
+
+ regmap_multi_reg_write_bypassed(component->regmap,
+ ntp8918_sound_on,
+ ARRAY_SIZE(ntp8918_sound_on));
+
+ ret = ntp8918_load_firmware(ntp8918);
+ if (ret) {
+ dev_err(&ntp8918->i2c->dev, "Failed to load firmware\n");
+ return ret;
+ }
+
+ regcache_cache_only(component->regmap, false);
+ snd_soc_component_cache_sync(component);
+
+ return 0;
+}
+
+static int ntp8918_probe(struct snd_soc_component *component)
+{
+ int ret;
+ struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component);
+ struct device *dev = component->dev;
+
+ ret = snd_soc_add_component_controls(component, ntp8918_vol_control,
+ ARRAY_SIZE(ntp8918_vol_control));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add controls\n");
+
+ ret = ntp8918_load_firmware(ntp8918);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to load firmware\n");
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget ntp8918_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("AIFIN", "Playback", SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_OUTPUT("OUT1"),
+ SND_SOC_DAPM_OUTPUT("OUT2"),
+};
+
+static const struct snd_soc_dapm_route ntp8918_dapm_routes[] = {
+ { "OUT1", NULL, "AIFIN" },
+ { "OUT2", NULL, "AIFIN" },
+};
+
+static const struct snd_soc_component_driver soc_component_ntp8918 = {
+ .probe = ntp8918_probe,
+ .suspend = ntp8918_snd_suspend,
+ .resume = ntp8918_snd_resume,
+ .dapm_widgets = ntp8918_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ntp8918_dapm_widgets),
+ .dapm_routes = ntp8918_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(ntp8918_dapm_routes),
+};
+
+static int ntp8918_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 ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component);
+ unsigned int input_fmt = 0;
+ unsigned int gsa_fmt = 0;
+ unsigned int gsa_fmt_mask;
+ unsigned int mcf;
+ int bclk;
+ int ret;
+
+ bclk = snd_soc_params_to_bclk(params);
+ switch (bclk) {
+ case 3072000:
+ case 2822400:
+ mcf = 0;
+ break;
+ case 6144000:
+ mcf = 1;
+ break;
+ case 2048000:
+ mcf = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = snd_soc_component_update_bits(component, NTP8918_MCLK_FREQ_CTRL,
+ NTP8918_MCLK_FREQ_MCF, mcf);
+ if (ret)
+ return ret;
+
+ switch (ntp8918->format) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ input_fmt |= NTP8918_INPUT_FMT_GSA_MODE;
+ gsa_fmt |= NTP8918_GSA_RIGHT_J;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ input_fmt |= NTP8918_INPUT_FMT_GSA_MODE;
+ break;
+ }
+
+ ret = snd_soc_component_update_bits(component, NTP8918_INPUT_FMT,
+ NTP8918_INPUT_FMT_MASTER_MODE |
+ NTP8918_INPUT_FMT_GSA_MODE,
+ input_fmt);
+
+ if (!(input_fmt & NTP8918_INPUT_FMT_GSA_MODE) || ret < 0)
+ return ret;
+
+ switch (params_width(params)) {
+ case 24:
+ gsa_fmt |= NTP8918_GSA_BS(0);
+ break;
+ case 20:
+ gsa_fmt |= NTP8918_GSA_BS(1);
+ break;
+ case 18:
+ gsa_fmt |= NTP8918_GSA_BS(2);
+ break;
+ case 16:
+ gsa_fmt |= NTP8918_GSA_BS(3);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ gsa_fmt_mask = NTP8918_GSA_BS_MASK |
+ NTP8918_GSA_RIGHT_J |
+ NTP8918_GSA_LSB;
+ return snd_soc_component_update_bits(component, NTP8918_GSA_FMT,
+ gsa_fmt_mask, gsa_fmt);
+}
+
+static int ntp8918_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ struct ntp8918_priv *ntp8918 = snd_soc_component_get_drvdata(component);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ ntp8918->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ntp8918_digital_mute(struct snd_soc_dai *dai, int mute, int stream)
+{
+ unsigned int mute_mask = NTP8918_SOFT_MUTE_SM1 |
+ NTP8918_SOFT_MUTE_SM2;
+
+ return snd_soc_component_update_bits(dai->component, NTP8918_SOFT_MUTE,
+ mute_mask, mute ? mute_mask : 0);
+}
+
+static const struct snd_soc_dai_ops ntp8918_dai_ops = {
+ .hw_params = ntp8918_hw_params,
+ .set_fmt = ntp8918_set_fmt,
+ .mute_stream = ntp8918_digital_mute,
+};
+
+static struct snd_soc_dai_driver ntp8918_dai = {
+ .name = "ntp8918-amplifier",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = NTP8918_RATES,
+ .formats = NTP8918_FORMATS,
+ },
+ .ops = &ntp8918_dai_ops,
+};
+
+static const struct regmap_config ntp8918_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = REG_MAX,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int ntp8918_i2c_probe(struct i2c_client *i2c)
+{
+ struct ntp8918_priv *ntp8918;
+ int ret;
+ struct regmap *regmap;
+
+ ntp8918 = devm_kzalloc(&i2c->dev, sizeof(*ntp8918), GFP_KERNEL);
+ if (!ntp8918)
+ return -ENOMEM;
+
+ ntp8918->i2c = i2c;
+
+ ntp8918->reset = devm_reset_control_get_shared(&i2c->dev, NULL);
+ if (IS_ERR(ntp8918->reset))
+ return dev_err_probe(&i2c->dev, PTR_ERR(ntp8918->reset), "Failed to get reset\n");
+
+ dev_set_drvdata(&i2c->dev, ntp8918);
+
+ ntp8918_reset_gpio(ntp8918);
+
+ regmap = devm_regmap_init_i2c(i2c, &ntp8918_regmap);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&i2c->dev, PTR_ERR(regmap),
+ "Failed to allocate regmap\n");
+
+ ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_ntp8918,
+ &ntp8918_dai, 1);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret,
+ "Failed to register component\n");
+
+ ntp8918->bck = devm_clk_get_enabled(&i2c->dev, "bck");
+ if (IS_ERR(ntp8918->bck))
+ return dev_err_probe(&i2c->dev, PTR_ERR(ntp8918->bck), "failed to get bck clock\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id ntp8918_i2c_id[] = {
+ { "ntp8918", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ntp8918_i2c_id);
+
+static const struct of_device_id ntp8918_of_match[] = {
+ {.compatible = "neofidelity,ntp8918"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, ntp8918_of_match);
+
+static struct i2c_driver ntp8918_i2c_driver = {
+ .probe = ntp8918_i2c_probe,
+ .id_table = ntp8918_i2c_id,
+ .driver = {
+ .name = "ntp8918",
+ .of_match_table = ntp8918_of_match,
+ },
+};
+module_i2c_driver(ntp8918_i2c_driver);
+
+MODULE_AUTHOR("Igor Prusov <ivprusov@salutedevices.com>");
+MODULE_DESCRIPTION("NTP8918 Audio Amplifier Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ntpfw.c b/sound/soc/codecs/ntpfw.c
new file mode 100644
index 000000000000..5ced2e966ab7
--- /dev/null
+++ b/sound/soc/codecs/ntpfw.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ntpfw.c - Firmware helper functions for Neofidelity codecs
+ *
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ */
+
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+
+#include "ntpfw.h"
+
+struct ntpfw_chunk {
+ __be16 length;
+ u8 step;
+ u8 data[];
+} __packed;
+
+struct ntpfw_header {
+ __be32 magic;
+} __packed;
+
+static bool ntpfw_verify(struct device *dev, const u8 *buf, size_t buf_size, u32 magic)
+{
+ const struct ntpfw_header *header = (struct ntpfw_header *)buf;
+ u32 buf_magic;
+
+ if (buf_size <= sizeof(*header)) {
+ dev_err(dev, "Failed to load firmware: image too small\n");
+ return false;
+ }
+
+ buf_magic = be32_to_cpu(header->magic);
+ if (buf_magic != magic) {
+ dev_err(dev, "Failed to load firmware: invalid magic 0x%x:\n", buf_magic);
+ return false;
+ }
+
+ return true;
+}
+
+static bool ntpfw_verify_chunk(struct device *dev, const struct ntpfw_chunk *chunk, size_t buf_size)
+{
+ size_t chunk_size;
+
+ if (buf_size <= sizeof(*chunk)) {
+ dev_err(dev, "Failed to load firmware: chunk size too big\n");
+ return false;
+ }
+
+ if (chunk->step != 2 && chunk->step != 5) {
+ dev_err(dev, "Failed to load firmware: invalid chunk step: %d\n", chunk->step);
+ return false;
+ }
+
+ chunk_size = be16_to_cpu(chunk->length);
+ if (chunk_size > buf_size) {
+ dev_err(dev, "Failed to load firmware: invalid chunk length\n");
+ return false;
+ }
+
+ if (chunk_size % chunk->step) {
+ dev_err(dev, "Failed to load firmware: chunk length and step mismatch\n");
+ return false;
+ }
+
+ return true;
+}
+
+static int ntpfw_send_chunk(struct i2c_client *i2c, const struct ntpfw_chunk *chunk)
+{
+ int ret;
+ size_t i;
+ size_t length = be16_to_cpu(chunk->length);
+
+ for (i = 0; i < length; i += chunk->step) {
+ ret = i2c_master_send(i2c, &chunk->data[i], chunk->step);
+ if (ret != chunk->step) {
+ dev_err(&i2c->dev, "I2C send failed: %d\n", ret);
+ return ret < 0 ? ret : -EIO;
+ }
+ }
+
+ return 0;
+}
+
+int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic)
+{
+ struct device *dev = &i2c->dev;
+ const struct ntpfw_chunk *chunk;
+ const struct firmware *fw;
+ const u8 *data;
+ size_t leftover;
+ int ret;
+
+ ret = request_firmware(&fw, name, dev);
+ if (ret) {
+ dev_warn(dev, "request_firmware '%s' failed with %d\n",
+ name, ret);
+ return ret;
+ }
+
+ if (!ntpfw_verify(dev, fw->data, fw->size, magic)) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ data = fw->data + sizeof(struct ntpfw_header);
+ leftover = fw->size - sizeof(struct ntpfw_header);
+
+ while (leftover) {
+ chunk = (struct ntpfw_chunk *)data;
+
+ if (!ntpfw_verify_chunk(dev, chunk, leftover)) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = ntpfw_send_chunk(i2c, chunk);
+ if (ret)
+ goto done;
+
+ data += be16_to_cpu(chunk->length) + sizeof(*chunk);
+ leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk);
+ }
+
+done:
+ release_firmware(fw);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ntpfw_load);
+
+MODULE_AUTHOR("Igor Prusov <ivprusov@salutedevices.com>");
+MODULE_DESCRIPTION("Helper for loading Neofidelity amplifiers firmware");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ntpfw.h b/sound/soc/codecs/ntpfw.h
new file mode 100644
index 000000000000..1cf10d5480ee
--- /dev/null
+++ b/sound/soc/codecs/ntpfw.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/**
+ * ntpfw.h - Firmware helper functions for Neofidelity codecs
+ *
+ * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
+ */
+
+#ifndef __NTPFW_H__
+#define __NTPFW_H__
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+
+/**
+ * ntpfw_load - load firmware to amplifier over i2c interface.
+ *
+ * @i2c Pointer to amplifier's I2C client.
+ * @name Firmware file name.
+ * @magic Magic number to validate firmware.
+ * @return 0 or error code upon error.
+ */
+int ntpfw_load(struct i2c_client *i2c, const char *name, const u32 magic);
+
+#endif /* __NTPFW_H__ */