File Index
Files and directories
Sign in
FILE

fs-amp-lib.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/codecs/fs-amp-lib.c 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
 15static 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
 36static 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
 51static 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
 66static 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
 83static 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
100static 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_kcalloc(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
140static 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
171static 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
199static 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
222int 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}
261EXPORT_SYMBOL_GPL(fs_amp_load_firmware);
262
263MODULE_AUTHOR("Nick Li <nick.li@foursemi.com>");
264MODULE_DESCRIPTION("FourSemi audio amplifier library");
265MODULE_LICENSE("GPL");
~/ Read-only file preview.