Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: codecs: Add library for FourSemi audio amplifiers

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>

authored by

Nick Li and committed by
Mark Brown
e0bbbcac fd90680d

+420
+3
sound/soc/codecs/Kconfig
··· 1232 1232 To compile this driver as a module, choose M here: the module 1233 1233 will be called snd-soc-framer. 1234 1234 1235 + config SND_SOC_FS_AMP_LIB 1236 + select CRC16 1237 + tristate 1235 1238 1236 1239 config SND_SOC_GTM601 1237 1240 tristate 'GTM601 UMTS modem audio codec'
+2
sound/soc/codecs/Makefile
··· 137 137 snd-soc-es8375-y := es8375.o 138 138 snd-soc-es8389-y := es8389.o 139 139 snd-soc-framer-y := framer-codec.o 140 + snd-soc-fs-amp-lib-y := fs-amp-lib.o 140 141 snd-soc-gtm601-y := gtm601.o 141 142 snd-soc-hdac-hdmi-y := hdac_hdmi.o 142 143 snd-soc-hdac-hda-y := hdac_hda.o ··· 563 562 obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o 564 563 obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o 565 564 obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o 565 + obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o 566 566 obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o 567 567 obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o 568 568 obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
+265
sound/soc/codecs/fs-amp-lib.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + // 3 + // fs-amp-lib.c --- Common library for FourSemi Audio Amplifiers 4 + // 5 + // Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. 6 + 7 + #include <linux/crc16.h> 8 + #include <linux/device.h> 9 + #include <linux/firmware.h> 10 + #include <linux/module.h> 11 + #include <linux/slab.h> 12 + 13 + #include "fs-amp-lib.h" 14 + 15 + static int fs_get_scene_count(struct fs_amp_lib *amp_lib) 16 + { 17 + const struct fs_fwm_table *table; 18 + int count; 19 + 20 + if (!amp_lib || !amp_lib->dev) 21 + return -EINVAL; 22 + 23 + table = amp_lib->table[FS_INDEX_SCENE]; 24 + if (!table) 25 + return -EFAULT; 26 + 27 + count = table->size / sizeof(struct fs_scene_index); 28 + if (count < 1 || count > FS_SCENE_COUNT_MAX) { 29 + dev_err(amp_lib->dev, "Invalid scene count: %d\n", count); 30 + return -ERANGE; 31 + } 32 + 33 + return count; 34 + } 35 + 36 + static void fs_get_fwm_string(struct fs_amp_lib *amp_lib, 37 + int offset, const char **pstr) 38 + { 39 + const struct fs_fwm_table *table; 40 + 41 + if (!amp_lib || !amp_lib->dev || !pstr) 42 + return; 43 + 44 + table = amp_lib->table[FS_INDEX_STRING]; 45 + if (table && offset > 0 && offset < table->size + sizeof(*table)) 46 + *pstr = (char *)table + offset; 47 + else 48 + *pstr = NULL; 49 + } 50 + 51 + static void fs_get_scene_reg(struct fs_amp_lib *amp_lib, 52 + int offset, struct fs_amp_scene *scene) 53 + { 54 + const struct fs_fwm_table *table; 55 + 56 + if (!amp_lib || !amp_lib->dev || !scene) 57 + return; 58 + 59 + table = amp_lib->table[FS_INDEX_REG]; 60 + if (table && offset > 0 && offset < table->size + sizeof(*table)) 61 + scene->reg = (struct fs_reg_table *)((char *)table + offset); 62 + else 63 + scene->reg = NULL; 64 + } 65 + 66 + static void fs_get_scene_model(struct fs_amp_lib *amp_lib, 67 + int offset, struct fs_amp_scene *scene) 68 + { 69 + const struct fs_fwm_table *table; 70 + const char *ptr; 71 + 72 + if (!amp_lib || !amp_lib->dev || !scene) 73 + return; 74 + 75 + table = amp_lib->table[FS_INDEX_MODEL]; 76 + ptr = (char *)table; 77 + if (table && offset > 0 && offset < table->size + sizeof(*table)) 78 + scene->model = (struct fs_file_table *)(ptr + offset); 79 + else 80 + scene->model = NULL; 81 + } 82 + 83 + static void fs_get_scene_effect(struct fs_amp_lib *amp_lib, 84 + int offset, struct fs_amp_scene *scene) 85 + { 86 + const struct fs_fwm_table *table; 87 + const char *ptr; 88 + 89 + if (!amp_lib || !amp_lib->dev || !scene) 90 + return; 91 + 92 + table = amp_lib->table[FS_INDEX_EFFECT]; 93 + ptr = (char *)table; 94 + if (table && offset > 0 && offset < table->size + sizeof(*table)) 95 + scene->effect = (struct fs_file_table *)(ptr + offset); 96 + else 97 + scene->effect = NULL; 98 + } 99 + 100 + static int fs_parse_scene_tables(struct fs_amp_lib *amp_lib) 101 + { 102 + const struct fs_scene_index *scene_index; 103 + const struct fs_fwm_table *table; 104 + struct fs_amp_scene *scene; 105 + int idx, count; 106 + 107 + if (!amp_lib || !amp_lib->dev) 108 + return -EINVAL; 109 + 110 + count = fs_get_scene_count(amp_lib); 111 + if (count <= 0) 112 + return -EFAULT; 113 + 114 + scene = devm_kzalloc(amp_lib->dev, count * sizeof(*scene), GFP_KERNEL); 115 + if (!scene) 116 + return -ENOMEM; 117 + 118 + amp_lib->scene_count = count; 119 + amp_lib->scene = scene; 120 + 121 + table = amp_lib->table[FS_INDEX_SCENE]; 122 + scene_index = (struct fs_scene_index *)table->buf; 123 + 124 + for (idx = 0; idx < count; idx++) { 125 + fs_get_fwm_string(amp_lib, scene_index->name, &scene->name); 126 + if (!scene->name) 127 + scene->name = devm_kasprintf(amp_lib->dev, 128 + GFP_KERNEL, "S%d", idx); 129 + dev_dbg(amp_lib->dev, "scene.%d name: %s\n", idx, scene->name); 130 + fs_get_scene_reg(amp_lib, scene_index->reg, scene); 131 + fs_get_scene_model(amp_lib, scene_index->model, scene); 132 + fs_get_scene_effect(amp_lib, scene_index->effect, scene); 133 + scene++; 134 + scene_index++; 135 + } 136 + 137 + return 0; 138 + } 139 + 140 + static int fs_parse_all_tables(struct fs_amp_lib *amp_lib) 141 + { 142 + const struct fs_fwm_table *table; 143 + const struct fs_fwm_index *index; 144 + const char *ptr; 145 + int idx, count; 146 + int ret; 147 + 148 + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) 149 + return -EINVAL; 150 + 151 + /* Parse all fwm tables */ 152 + table = (struct fs_fwm_table *)amp_lib->hdr->params; 153 + index = (struct fs_fwm_index *)table->buf; 154 + count = table->size / sizeof(*index); 155 + 156 + for (idx = 0; idx < count; idx++, index++) { 157 + if (index->type >= FS_INDEX_MAX) 158 + return -ERANGE; 159 + ptr = (char *)table + (int)index->offset; 160 + amp_lib->table[index->type] = (struct fs_fwm_table *)ptr; 161 + } 162 + 163 + /* Parse all scene tables */ 164 + ret = fs_parse_scene_tables(amp_lib); 165 + if (ret) 166 + dev_err(amp_lib->dev, "Failed to parse scene: %d\n", ret); 167 + 168 + return ret; 169 + } 170 + 171 + static int fs_verify_firmware(struct fs_amp_lib *amp_lib) 172 + { 173 + const struct fs_fwm_header *hdr; 174 + int crcsum; 175 + 176 + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) 177 + return -EINVAL; 178 + 179 + hdr = amp_lib->hdr; 180 + 181 + /* Verify the crcsum code */ 182 + crcsum = crc16(0x0000, (const char *)&hdr->crc_size, hdr->crc_size); 183 + if (crcsum != hdr->crc16) { 184 + dev_err(amp_lib->dev, "Failed to checksum: %x-%x\n", 185 + crcsum, hdr->crc16); 186 + return -EFAULT; 187 + } 188 + 189 + /* Verify the devid(chip_type) */ 190 + if (amp_lib->devid != LO_U16(hdr->chip_type)) { 191 + dev_err(amp_lib->dev, "DEVID dismatch: %04X#%04X\n", 192 + amp_lib->devid, hdr->chip_type); 193 + return -EINVAL; 194 + } 195 + 196 + return 0; 197 + } 198 + 199 + static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) 200 + { 201 + const struct fs_fwm_header *hdr; 202 + const char *pro_name = NULL; 203 + const char *dev_name = NULL; 204 + 205 + if (!amp_lib || !amp_lib->dev || !amp_lib->hdr) 206 + return; 207 + 208 + hdr = amp_lib->hdr; 209 + 210 + fs_get_fwm_string(amp_lib, hdr->project, &pro_name); 211 + fs_get_fwm_string(amp_lib, hdr->device, &dev_name); 212 + 213 + dev_info(amp_lib->dev, "Project: %s Device: %s\n", 214 + pro_name ? pro_name : "null", 215 + dev_name ? dev_name : "null"); 216 + 217 + dev_info(amp_lib->dev, "Date: %04d%02d%02d-%02d%02d\n", 218 + hdr->date.year, hdr->date.month, hdr->date.day, 219 + hdr->date.hour, hdr->date.minute); 220 + } 221 + 222 + int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) 223 + { 224 + const struct firmware *cont; 225 + struct fs_fwm_header *hdr; 226 + int ret; 227 + 228 + if (!amp_lib || !amp_lib->dev || !name) 229 + return -EINVAL; 230 + 231 + ret = request_firmware(&cont, name, amp_lib->dev); 232 + if (ret) { 233 + dev_err(amp_lib->dev, "Failed to request %s: %d\n", name, ret); 234 + return ret; 235 + } 236 + 237 + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); 238 + 239 + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); 240 + release_firmware(cont); 241 + if (!hdr) 242 + return -ENOMEM; 243 + 244 + amp_lib->hdr = hdr; 245 + ret = fs_verify_firmware(amp_lib); 246 + if (ret) { 247 + amp_lib->hdr = NULL; 248 + return ret; 249 + } 250 + 251 + ret = fs_parse_all_tables(amp_lib); 252 + if (ret) { 253 + amp_lib->hdr = NULL; 254 + return ret; 255 + } 256 + 257 + fs_print_firmware_info(amp_lib); 258 + 259 + return 0; 260 + } 261 + EXPORT_SYMBOL_GPL(fs_amp_load_firmware); 262 + 263 + MODULE_AUTHOR("Nick Li <nick.li@foursemi.com>"); 264 + MODULE_DESCRIPTION("FourSemi audio amplifier library"); 265 + MODULE_LICENSE("GPL");
+150
sound/soc/codecs/fs-amp-lib.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0+ */ 2 + /* 3 + * fs-amp-lib.h --- Common library for FourSemi Audio Amplifiers 4 + * 5 + * Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. 6 + */ 7 + 8 + #ifndef __FS_AMP_LIB_H__ 9 + #define __FS_AMP_LIB_H__ 10 + 11 + #define HI_U16(a) (((a) >> 8) & 0xFF) 12 + #define LO_U16(a) ((a) & 0xFF) 13 + #define FS_TABLE_NAME_LEN (4) 14 + #define FS_SCENE_COUNT_MAX (16) 15 + #define FS_CMD_DELAY_MS_MAX (100) /* 100ms */ 16 + 17 + #define FS_CMD_DELAY (0xFF) 18 + #define FS_CMD_BURST (0xFE) 19 + #define FS_CMD_UPDATE (0xFD) 20 + 21 + #define FS_SOC_ENUM_EXT(xname, xhandler_info, xhandler_get, xhandler_put) \ 22 + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ 23 + .info = xhandler_info, \ 24 + .get = xhandler_get, .put = xhandler_put \ 25 + } 26 + 27 + enum fs_index_type { 28 + FS_INDEX_INFO = 0, 29 + FS_INDEX_STCOEF, 30 + FS_INDEX_SCENE, 31 + FS_INDEX_MODEL, 32 + FS_INDEX_REG, 33 + FS_INDEX_EFFECT, 34 + FS_INDEX_STRING, 35 + FS_INDEX_WOOFER, 36 + FS_INDEX_MAX, 37 + }; 38 + 39 + #pragma pack(push, 1) 40 + 41 + struct fs_reg_val { 42 + u8 reg; 43 + u16 val; 44 + }; 45 + 46 + struct fs_reg_bits { 47 + u8 cmd; /* FS_CMD_UPDATE */ 48 + u8 reg; 49 + u16 val; 50 + u16 mask; 51 + }; 52 + 53 + struct fs_cmd_pkg { 54 + union { 55 + u8 cmd; 56 + struct fs_reg_val regv; 57 + struct fs_reg_bits regb; 58 + }; 59 + }; 60 + 61 + struct fs_fwm_index { 62 + /* Index type */ 63 + u16 type; 64 + /* Offset address starting from the end of header */ 65 + u16 offset; 66 + }; 67 + 68 + struct fs_fwm_table { 69 + char name[FS_TABLE_NAME_LEN]; 70 + u16 size; /* size of buf */ 71 + u8 buf[]; 72 + }; 73 + 74 + struct fs_scene_index { 75 + /* Offset address(scene name) in string table */ 76 + u16 name; 77 + /* Offset address(scene reg) in register table */ 78 + u16 reg; 79 + /* Offset address(scene model) in model table */ 80 + u16 model; 81 + /* Offset address(scene effect) in effect table */ 82 + u16 effect; 83 + }; 84 + 85 + struct fs_reg_table { 86 + u16 size; /* size of buf */ 87 + u8 buf[]; 88 + }; 89 + 90 + struct fs_file_table { 91 + u16 name; 92 + u16 size; /* size of buf */ 93 + u8 buf[]; 94 + }; 95 + 96 + struct fs_fwm_date { 97 + u32 year:12; 98 + u32 month:4; 99 + u32 day:5; 100 + u32 hour:5; 101 + u32 minute:6; 102 + }; 103 + 104 + struct fs_fwm_header { 105 + u16 version; 106 + u16 project; /* Offset address(project name) in string table */ 107 + u16 device; /* Offset address(device name) in string table */ 108 + struct fs_fwm_date date; 109 + u16 crc16; 110 + u16 crc_size; /* Starting position for CRC checking */ 111 + u16 chip_type; 112 + u16 addr; /* 7-bit i2c address */ 113 + u16 spkid; 114 + u16 rsvd[6]; 115 + u8 params[]; 116 + }; 117 + 118 + #pragma pack(pop) 119 + 120 + struct fs_i2s_srate { 121 + u32 srate; /* Sample rate */ 122 + u16 i2ssr; /* Value of Bit field[I2SSR] */ 123 + }; 124 + 125 + struct fs_pll_div { 126 + unsigned int bclk; /* Rate of bit clock */ 127 + u16 pll1; 128 + u16 pll2; 129 + u16 pll3; 130 + }; 131 + 132 + struct fs_amp_scene { 133 + const char *name; 134 + const struct fs_reg_table *reg; 135 + const struct fs_file_table *model; 136 + const struct fs_file_table *effect; 137 + }; 138 + 139 + struct fs_amp_lib { 140 + const struct fs_fwm_header *hdr; 141 + const struct fs_fwm_table *table[FS_INDEX_MAX]; 142 + struct fs_amp_scene *scene; 143 + struct device *dev; 144 + int scene_count; 145 + u16 devid; 146 + }; 147 + 148 + int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name); 149 + 150 + #endif // __FS_AMP_LIB_H__