ASoC: codecs: Add NeoFidelity Firmware helpers
authorIgor Prusov <ivprusov@salutedevices.com>
Wed, 25 Sep 2024 14:52:40 +0000 (17:52 +0300)
committerMark Brown <broonie@kernel.org>
Sun, 29 Sep 2024 23:09:17 +0000 (01:09 +0200)
Add support for loading firmware for NeoFidelity amplifiers.

Signed-off-by: Igor Prusov <ivprusov@salutedevices.com>
Link: https://patch.msgid.link/20240925-ntp-amps-8918-8835-v3-2-e2459a8191a6@salutedevices.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/ntpfw.c [new file with mode: 0644]
sound/soc/codecs/ntpfw.h [new file with mode: 0644]

index 7092842480ef17d705920a6ac62a85158119352e..a911a81caf8b3941dc4a3117290739cb6ac802d3 100644 (file)
@@ -2565,6 +2565,9 @@ config SND_SOC_NAU8825
        tristate
        depends on I2C
 
+config SND_SOC_NTPFW
+       tristate
+
 config SND_SOC_TPA6130A2
        tristate "Texas Instruments TPA6130A2 headphone amplifier"
        depends on I2C
index 54cbc3feae3277ae29d6ea8fe891d4d17e5c9b17..12f97fc8a9e7c410b0e8859f34b303caa677fcd2 100644 (file)
@@ -189,6 +189,7 @@ 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-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 +592,7 @@ 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_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/ntpfw.c b/sound/soc/codecs/ntpfw.c
new file mode 100644 (file)
index 0000000..5ced2e9
--- /dev/null
@@ -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 (file)
index 0000000..1cf10d5
--- /dev/null
@@ -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__ */