xref: /linux/sound/soc/codecs/fs-amp-lib.c (revision e0bbbcaceba1cf47751f264d0dbe36206eab0ef0)
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