]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ASoC: codecs: Add library for FourSemi audio amplifiers
authorNick Li <nick.li@foursemi.com>
Mon, 11 Aug 2025 10:46:08 +0000 (18:46 +0800)
committerMark Brown <broonie@kernel.org>
Mon, 11 Aug 2025 10:57:28 +0000 (11:57 +0100)
This patch adds firmware loading and parsing support for FourSemi audio
amplifiers. The library handles firmware file (*.bin) generated by the
FourSemi tuning tool, which contains:
- Register initialization settings
- DSP effect parameters
- Multi-scene sound effect switching configurations(optional)

The firmware is required for proper initialization and configuration
of FourSemi amplifier devices.

Signed-off-by: Nick Li <nick.li@foursemi.com>
Link: https://patch.msgid.link/77822D0108CCC1D0+20250811104610.8993-4-nick.li@foursemi.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/fs-amp-lib.c [new file with mode: 0644]
sound/soc/codecs/fs-amp-lib.h [new file with mode: 0644]

index 6d7e4725d89cd33647770e4f2c4e81445b8335ce..ecdc05ef3e4448277c7c20ab800ba702263abc3e 100644 (file)
@@ -1232,6 +1232,9 @@ config SND_SOC_FRAMER
          To compile this driver as a module, choose M here: the module
          will be called snd-soc-framer.
 
+config SND_SOC_FS_AMP_LIB
+       select CRC16
+       tristate
 
 config SND_SOC_GTM601
        tristate 'GTM601 UMTS modem audio codec'
index a68c3d192a1b6ccec513c6bc447c29be532ea70c..646e017a8754906adcf01c525e4ff00e0d0b788f 100644 (file)
@@ -137,6 +137,7 @@ snd-soc-es8328-spi-y := es8328-spi.o
 snd-soc-es8375-y := es8375.o
 snd-soc-es8389-y := es8389.o
 snd-soc-framer-y := framer-codec.o
+snd-soc-fs-amp-lib-y := fs-amp-lib.o
 snd-soc-gtm601-y := gtm601.o
 snd-soc-hdac-hdmi-y := hdac_hdmi.o
 snd-soc-hdac-hda-y := hdac_hda.o
@@ -562,6 +563,7 @@ obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
 obj-$(CONFIG_SND_SOC_ES8375)    += snd-soc-es8375.o
 obj-$(CONFIG_SND_SOC_ES8389)    += snd-soc-es8389.o
 obj-$(CONFIG_SND_SOC_FRAMER)   += snd-soc-framer.o
+obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o
 obj-$(CONFIG_SND_SOC_GTM601)    += snd-soc-gtm601.o
 obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o
 obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c
new file mode 100644 (file)
index 0000000..75d8d50
--- /dev/null
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// fs-amp-lib.c --- Common library for FourSemi Audio Amplifiers
+//
+// Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd.
+
+#include <linux/crc16.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "fs-amp-lib.h"
+
+static int fs_get_scene_count(struct fs_amp_lib *amp_lib)
+{
+       const struct fs_fwm_table *table;
+       int count;
+
+       if (!amp_lib || !amp_lib->dev)
+               return -EINVAL;
+
+       table = amp_lib->table[FS_INDEX_SCENE];
+       if (!table)
+               return -EFAULT;
+
+       count = table->size / sizeof(struct fs_scene_index);
+       if (count < 1 || count > FS_SCENE_COUNT_MAX) {
+               dev_err(amp_lib->dev, "Invalid scene count: %d\n", count);
+               return -ERANGE;
+       }
+
+       return count;
+}
+
+static void fs_get_fwm_string(struct fs_amp_lib *amp_lib,
+                             int offset, const char **pstr)
+{
+       const struct fs_fwm_table *table;
+
+       if (!amp_lib || !amp_lib->dev || !pstr)
+               return;
+
+       table = amp_lib->table[FS_INDEX_STRING];
+       if (table && offset > 0 && offset < table->size + sizeof(*table))
+               *pstr = (char *)table + offset;
+       else
+               *pstr = NULL;
+}
+
+static void fs_get_scene_reg(struct fs_amp_lib *amp_lib,
+                            int offset, struct fs_amp_scene *scene)
+{
+       const struct fs_fwm_table *table;
+
+       if (!amp_lib || !amp_lib->dev || !scene)
+               return;
+
+       table = amp_lib->table[FS_INDEX_REG];
+       if (table && offset > 0 && offset < table->size + sizeof(*table))
+               scene->reg = (struct fs_reg_table *)((char *)table + offset);
+       else
+               scene->reg = NULL;
+}
+
+static void fs_get_scene_model(struct fs_amp_lib *amp_lib,
+                              int offset, struct fs_amp_scene *scene)
+{
+       const struct fs_fwm_table *table;
+       const char *ptr;
+
+       if (!amp_lib || !amp_lib->dev || !scene)
+               return;
+
+       table = amp_lib->table[FS_INDEX_MODEL];
+       ptr = (char *)table;
+       if (table && offset > 0 && offset < table->size + sizeof(*table))
+               scene->model = (struct fs_file_table *)(ptr + offset);
+       else
+               scene->model = NULL;
+}
+
+static void fs_get_scene_effect(struct fs_amp_lib *amp_lib,
+                               int offset, struct fs_amp_scene *scene)
+{
+       const struct fs_fwm_table *table;
+       const char *ptr;
+
+       if (!amp_lib || !amp_lib->dev || !scene)
+               return;
+
+       table = amp_lib->table[FS_INDEX_EFFECT];
+       ptr = (char *)table;
+       if (table && offset > 0 && offset < table->size + sizeof(*table))
+               scene->effect = (struct fs_file_table *)(ptr + offset);
+       else
+               scene->effect = NULL;
+}
+
+static int fs_parse_scene_tables(struct fs_amp_lib *amp_lib)
+{
+       const struct fs_scene_index *scene_index;
+       const struct fs_fwm_table *table;
+       struct fs_amp_scene *scene;
+       int idx, count;
+
+       if (!amp_lib || !amp_lib->dev)
+               return -EINVAL;
+
+       count = fs_get_scene_count(amp_lib);
+       if (count <= 0)
+               return -EFAULT;
+
+       scene = devm_kzalloc(amp_lib->dev, count * sizeof(*scene), GFP_KERNEL);
+       if (!scene)
+               return -ENOMEM;
+
+       amp_lib->scene_count = count;
+       amp_lib->scene = scene;
+
+       table = amp_lib->table[FS_INDEX_SCENE];
+       scene_index = (struct fs_scene_index *)table->buf;
+
+       for (idx = 0; idx < count; idx++) {
+               fs_get_fwm_string(amp_lib, scene_index->name, &scene->name);
+               if (!scene->name)
+                       scene->name = devm_kasprintf(amp_lib->dev,
+                                                    GFP_KERNEL, "S%d", idx);
+               dev_dbg(amp_lib->dev, "scene.%d name: %s\n", idx, scene->name);
+               fs_get_scene_reg(amp_lib, scene_index->reg, scene);
+               fs_get_scene_model(amp_lib, scene_index->model, scene);
+               fs_get_scene_effect(amp_lib, scene_index->effect, scene);
+               scene++;
+               scene_index++;
+       }
+
+       return 0;
+}
+
+static int fs_parse_all_tables(struct fs_amp_lib *amp_lib)
+{
+       const struct fs_fwm_table *table;
+       const struct fs_fwm_index *index;
+       const char *ptr;
+       int idx, count;
+       int ret;
+
+       if (!amp_lib || !amp_lib->dev || !amp_lib->hdr)
+               return -EINVAL;
+
+       /* Parse all fwm tables */
+       table = (struct fs_fwm_table *)amp_lib->hdr->params;
+       index = (struct fs_fwm_index *)table->buf;
+       count = table->size / sizeof(*index);
+
+       for (idx = 0; idx < count; idx++, index++) {
+               if (index->type >= FS_INDEX_MAX)
+                       return -ERANGE;
+               ptr = (char *)table + (int)index->offset;
+               amp_lib->table[index->type] = (struct fs_fwm_table *)ptr;
+       }
+
+       /* Parse all scene tables */
+       ret = fs_parse_scene_tables(amp_lib);
+       if (ret)
+               dev_err(amp_lib->dev, "Failed to parse scene: %d\n", ret);
+
+       return ret;
+}
+
+static int fs_verify_firmware(struct fs_amp_lib *amp_lib)
+{
+       const struct fs_fwm_header *hdr;
+       int crcsum;
+
+       if (!amp_lib || !amp_lib->dev || !amp_lib->hdr)
+               return -EINVAL;
+
+       hdr = amp_lib->hdr;
+
+       /* Verify the crcsum code */
+       crcsum = crc16(0x0000, (const char *)&hdr->crc_size, hdr->crc_size);
+       if (crcsum != hdr->crc16) {
+               dev_err(amp_lib->dev, "Failed to checksum: %x-%x\n",
+                       crcsum, hdr->crc16);
+               return -EFAULT;
+       }
+
+       /* Verify the devid(chip_type) */
+       if (amp_lib->devid != LO_U16(hdr->chip_type)) {
+               dev_err(amp_lib->dev, "DEVID dismatch: %04X#%04X\n",
+                       amp_lib->devid, hdr->chip_type);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void fs_print_firmware_info(struct fs_amp_lib *amp_lib)
+{
+       const struct fs_fwm_header *hdr;
+       const char *pro_name = NULL;
+       const char *dev_name = NULL;
+
+       if (!amp_lib || !amp_lib->dev || !amp_lib->hdr)
+               return;
+
+       hdr = amp_lib->hdr;
+
+       fs_get_fwm_string(amp_lib, hdr->project, &pro_name);
+       fs_get_fwm_string(amp_lib, hdr->device, &dev_name);
+
+       dev_info(amp_lib->dev, "Project: %s Device: %s\n",
+                pro_name ? pro_name : "null",
+                dev_name ? dev_name : "null");
+
+       dev_info(amp_lib->dev, "Date: %04d%02d%02d-%02d%02d\n",
+                hdr->date.year, hdr->date.month, hdr->date.day,
+                hdr->date.hour, hdr->date.minute);
+}
+
+int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name)
+{
+       const struct firmware *cont;
+       struct fs_fwm_header *hdr;
+       int ret;
+
+       if (!amp_lib || !amp_lib->dev || !name)
+               return -EINVAL;
+
+       ret = request_firmware(&cont, name, amp_lib->dev);
+       if (ret) {
+               dev_err(amp_lib->dev, "Failed to request %s: %d\n", name, ret);
+               return ret;
+       }
+
+       dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size);
+
+       hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL);
+       release_firmware(cont);
+       if (!hdr)
+               return -ENOMEM;
+
+       amp_lib->hdr = hdr;
+       ret = fs_verify_firmware(amp_lib);
+       if (ret) {
+               amp_lib->hdr = NULL;
+               return ret;
+       }
+
+       ret = fs_parse_all_tables(amp_lib);
+       if (ret) {
+               amp_lib->hdr = NULL;
+               return ret;
+       }
+
+       fs_print_firmware_info(amp_lib);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(fs_amp_load_firmware);
+
+MODULE_AUTHOR("Nick Li <nick.li@foursemi.com>");
+MODULE_DESCRIPTION("FourSemi audio amplifier library");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/fs-amp-lib.h b/sound/soc/codecs/fs-amp-lib.h
new file mode 100644 (file)
index 0000000..4a77c7b
--- /dev/null
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * fs-amp-lib.h --- Common library for FourSemi Audio Amplifiers
+ *
+ * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd.
+ */
+
+#ifndef __FS_AMP_LIB_H__
+#define __FS_AMP_LIB_H__
+
+#define HI_U16(a)              (((a) >> 8) & 0xFF)
+#define LO_U16(a)              ((a) & 0xFF)
+#define FS_TABLE_NAME_LEN      (4)
+#define FS_SCENE_COUNT_MAX     (16)
+#define FS_CMD_DELAY_MS_MAX    (100) /* 100ms */
+
+#define FS_CMD_DELAY           (0xFF)
+#define FS_CMD_BURST           (0xFE)
+#define FS_CMD_UPDATE          (0xFD)
+
+#define FS_SOC_ENUM_EXT(xname, xhandler_info, xhandler_get, xhandler_put) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = xhandler_info, \
+       .get = xhandler_get, .put = xhandler_put \
+}
+
+enum fs_index_type {
+       FS_INDEX_INFO = 0,
+       FS_INDEX_STCOEF,
+       FS_INDEX_SCENE,
+       FS_INDEX_MODEL,
+       FS_INDEX_REG,
+       FS_INDEX_EFFECT,
+       FS_INDEX_STRING,
+       FS_INDEX_WOOFER,
+       FS_INDEX_MAX,
+};
+
+#pragma pack(push, 1)
+
+struct fs_reg_val {
+       u8 reg;
+       u16 val;
+};
+
+struct fs_reg_bits {
+       u8 cmd; /* FS_CMD_UPDATE */
+       u8 reg;
+       u16 val;
+       u16 mask;
+};
+
+struct fs_cmd_pkg {
+       union {
+               u8 cmd;
+               struct fs_reg_val regv;
+               struct fs_reg_bits regb;
+       };
+};
+
+struct fs_fwm_index {
+       /* Index type */
+       u16 type;
+       /* Offset address starting from the end of header */
+       u16 offset;
+};
+
+struct fs_fwm_table {
+       char name[FS_TABLE_NAME_LEN];
+       u16 size; /* size of buf */
+       u8 buf[];
+};
+
+struct fs_scene_index {
+       /* Offset address(scene name) in string table */
+       u16 name;
+       /* Offset address(scene reg) in register table */
+       u16 reg;
+       /* Offset address(scene model) in model table */
+       u16 model;
+       /* Offset address(scene effect) in effect table */
+       u16 effect;
+};
+
+struct fs_reg_table {
+       u16 size; /* size of buf */
+       u8 buf[];
+};
+
+struct fs_file_table {
+       u16 name;
+       u16 size; /* size of buf */
+       u8 buf[];
+};
+
+struct fs_fwm_date {
+       u32 year:12;
+       u32 month:4;
+       u32 day:5;
+       u32 hour:5;
+       u32 minute:6;
+};
+
+struct fs_fwm_header {
+       u16 version;
+       u16 project; /* Offset address(project name) in string table */
+       u16 device; /* Offset address(device name) in string table */
+       struct fs_fwm_date date;
+       u16 crc16;
+       u16 crc_size; /* Starting position for CRC checking */
+       u16 chip_type;
+       u16 addr; /* 7-bit i2c address */
+       u16 spkid;
+       u16 rsvd[6];
+       u8 params[];
+};
+
+#pragma pack(pop)
+
+struct fs_i2s_srate {
+       u32 srate; /* Sample rate */
+       u16 i2ssr; /* Value of Bit field[I2SSR] */
+};
+
+struct fs_pll_div {
+       unsigned int bclk; /* Rate of bit clock */
+       u16 pll1;
+       u16 pll2;
+       u16 pll3;
+};
+
+struct fs_amp_scene {
+       const char *name;
+       const struct fs_reg_table  *reg;
+       const struct fs_file_table *model;
+       const struct fs_file_table *effect;
+};
+
+struct fs_amp_lib {
+       const struct fs_fwm_header *hdr;
+       const struct fs_fwm_table *table[FS_INDEX_MAX];
+       struct fs_amp_scene *scene;
+       struct device *dev;
+       int scene_count;
+       u16 devid;
+};
+
+int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name);
+
+#endif // __FS_AMP_LIB_H__