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"); 266