Add support for loading firmware for NeoFidelity amplifiers. Signed-off-by: Igor Prusov <ivprusov@xxxxxxxxxxxxxxxxx> --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ntpfw.c | 137 ++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ntpfw.h | 23 +++++++ 4 files changed, 165 insertions(+) create mode 100644 sound/soc/codecs/ntpfw.c create mode 100644 sound/soc/codecs/ntpfw.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 4afc43d3f71f..9583243f1966 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2492,6 +2492,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 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index b4df22186e25..eae4ab047c72 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -183,6 +183,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 @@ -575,6 +576,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 index 000000000000..beaf300fe59c --- /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@xxxxxxxxxxxxxxxxx>"); +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__ */ -- 2.34.1