xref: /linux/sound/soc/codecs/cs-amp-lib.c (revision 05a54fa773284d1a7923cdfdd8f0c8dabb98bd26)
1 // SPDX-License-Identifier: GPL-2.0-only
2 //
3 // Common code for Cirrus Logic Smart Amplifiers
4 //
5 // Copyright (C) 2024 Cirrus Logic, Inc. and
6 //               Cirrus Logic International Semiconductor Ltd.
7 
8 #include <asm/byteorder.h>
9 #include <kunit/static_stub.h>
10 #include <linux/dev_printk.h>
11 #include <linux/efi.h>
12 #include <linux/firmware/cirrus/cs_dsp.h>
13 #include <linux/module.h>
14 #include <linux/overflow.h>
15 #include <linux/slab.h>
16 #include <linux/types.h>
17 #include <sound/cs-amp-lib.h>
18 
19 #define CIRRUS_LOGIC_CALIBRATION_EFI_NAME L"CirrusSmartAmpCalibrationData"
20 #define CIRRUS_LOGIC_CALIBRATION_EFI_GUID \
21 	EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3)
22 
23 #define LENOVO_SPEAKER_ID_EFI_NAME L"SdwSpeaker"
24 #define LENOVO_SPEAKER_ID_EFI_GUID \
25 	EFI_GUID(0x48df970e, 0xe27f, 0x460a, 0xb5, 0x86, 0x77, 0x19, 0x80, 0x1d, 0x92, 0x82)
26 
27 #define HP_SPEAKER_ID_EFI_NAME L"HPSpeakerID"
28 #define HP_SPEAKER_ID_EFI_GUID \
29 	EFI_GUID(0xc49593a4, 0xd099, 0x419b, 0xa2, 0xc3, 0x67, 0xe9, 0x80, 0xe6, 0x1d, 0x1e)
30 
31 #define HP_CALIBRATION_EFI_NAME L"SmartAmpCalibrationData"
32 #define HP_CALIBRATION_EFI_GUID \
33 	EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93)
34 
35 static const struct cs_amp_lib_cal_efivar {
36 	efi_char16_t *name;
37 	efi_guid_t *guid;
38 } cs_amp_lib_cal_efivars[] = {
39 	{
40 		.name = HP_CALIBRATION_EFI_NAME,
41 		.guid = &HP_CALIBRATION_EFI_GUID,
42 	},
43 	{
44 		.name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME,
45 		.guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID,
46 	},
47 };
48 
49 static int cs_amp_write_cal_coeff(struct cs_dsp *dsp,
50 				  const struct cirrus_amp_cal_controls *controls,
51 				  const char *ctl_name, u32 val)
52 {
53 	struct cs_dsp_coeff_ctl *cs_ctl;
54 	__be32 beval = cpu_to_be32(val);
55 	int ret;
56 
57 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val);
58 
59 	if (IS_REACHABLE(CONFIG_FW_CS_DSP)) {
60 		mutex_lock(&dsp->pwr_lock);
61 		cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id);
62 		ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval));
63 		mutex_unlock(&dsp->pwr_lock);
64 
65 		if (ret < 0) {
66 			dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret);
67 			return ret;
68 		}
69 
70 		return 0;
71 	}
72 
73 	return -ENODEV;
74 }
75 
76 static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
77 				    const struct cirrus_amp_cal_controls *controls,
78 				    const struct cirrus_amp_cal_data *data)
79 {
80 	int ret;
81 
82 	dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n",
83 		data->calAmbient, data->calStatus, data->calR);
84 
85 	if (list_empty(&dsp->ctl_list)) {
86 		dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n");
87 		return -ENOENT;
88 	}
89 
90 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient);
91 	if (ret)
92 		return ret;
93 
94 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR);
95 	if (ret)
96 		return ret;
97 
98 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus);
99 	if (ret)
100 		return ret;
101 
102 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1);
103 	if (ret)
104 		return ret;
105 
106 	return 0;
107 }
108 
109 /**
110  * cs_amp_write_cal_coeffs - Write calibration data to firmware controls.
111  * @dsp:	Pointer to struct cs_dsp.
112  * @controls:	Pointer to definition of firmware controls to be written.
113  * @data:	Pointer to calibration data.
114  *
115  * Returns: 0 on success, else negative error value.
116  */
117 int cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
118 			    const struct cirrus_amp_cal_controls *controls,
119 			    const struct cirrus_amp_cal_data *data)
120 {
121 	if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
122 		return _cs_amp_write_cal_coeffs(dsp, controls, data);
123 	else
124 		return -ENODEV;
125 }
126 EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, "SND_SOC_CS_AMP_LIB");
127 
128 static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name,
129 					    efi_guid_t *guid,
130 					    unsigned long *size,
131 					    void *buf)
132 {
133 	u32 attr;
134 
135 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf);
136 
137 	if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
138 		return efi.get_variable(name, guid, &attr, size, buf);
139 
140 	return EFI_NOT_FOUND;
141 }
142 
143 static int cs_amp_convert_efi_status(efi_status_t status)
144 {
145 	switch (status) {
146 	case EFI_SUCCESS:
147 		return 0;
148 	case EFI_NOT_FOUND:
149 		return -ENOENT;
150 	case EFI_BUFFER_TOO_SMALL:
151 		return -EFBIG;
152 	case EFI_UNSUPPORTED:
153 	case EFI_ACCESS_DENIED:
154 	case EFI_SECURITY_VIOLATION:
155 		return -EACCES;
156 	default:
157 		return -EIO;
158 	}
159 }
160 
161 static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
162 {
163 	struct cirrus_amp_efi_data *efi_data;
164 	unsigned long data_size = 0;
165 	u8 *data;
166 	efi_status_t status;
167 	int i, ret;
168 
169 	/* Find EFI variable and get size */
170 	for (i = 0; i < ARRAY_SIZE(cs_amp_lib_cal_efivars); i++) {
171 		status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name,
172 						 cs_amp_lib_cal_efivars[i].guid,
173 						 &data_size, NULL);
174 		if (status == EFI_BUFFER_TOO_SMALL)
175 			break;
176 	}
177 
178 	if (status != EFI_BUFFER_TOO_SMALL)
179 		return ERR_PTR(-ENOENT);
180 
181 	if (data_size < sizeof(*efi_data)) {
182 		dev_err(dev, "EFI cal variable truncated\n");
183 		return ERR_PTR(-EOVERFLOW);
184 	}
185 
186 	/* Get variable contents into buffer */
187 	data = kmalloc(data_size, GFP_KERNEL);
188 	if (!data)
189 		return ERR_PTR(-ENOMEM);
190 
191 	status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name,
192 					 cs_amp_lib_cal_efivars[i].guid,
193 					 &data_size, data);
194 	if (status != EFI_SUCCESS) {
195 		ret = -EINVAL;
196 		goto err;
197 	}
198 
199 	efi_data = (struct cirrus_amp_efi_data *)data;
200 	dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count);
201 
202 	if ((efi_data->count > 128) ||
203 	    struct_size(efi_data, data, efi_data->count) > data_size) {
204 		dev_err(dev, "EFI cal variable truncated\n");
205 		ret = -EOVERFLOW;
206 		goto err;
207 	}
208 
209 	return efi_data;
210 
211 err:
212 	kfree(data);
213 	dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret);
214 
215 	return ERR_PTR(ret);
216 }
217 
218 static u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data)
219 {
220 	return ((u64)data->calTarget[1] << 32) | data->calTarget[0];
221 }
222 
223 static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
224 					    struct cirrus_amp_cal_data *out_data)
225 {
226 	struct cirrus_amp_efi_data *efi_data;
227 	struct cirrus_amp_cal_data *cal = NULL;
228 	int i, ret;
229 
230 	efi_data = cs_amp_get_cal_efi_buffer(dev);
231 	if (IS_ERR(efi_data))
232 		return PTR_ERR(efi_data);
233 
234 	if (target_uid) {
235 		for (i = 0; i < efi_data->count; ++i) {
236 			u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]);
237 
238 			/* Skip empty entries */
239 			if (!efi_data->data[i].calTime[0] && !efi_data->data[i].calTime[1])
240 				continue;
241 
242 			/* Skip entries with unpopulated silicon ID */
243 			if (cal_target == 0)
244 				continue;
245 
246 			if (cal_target == target_uid) {
247 				cal = &efi_data->data[i];
248 				break;
249 			}
250 		}
251 	}
252 
253 	if (!cal && (amp_index >= 0) && (amp_index < efi_data->count) &&
254 	    (efi_data->data[amp_index].calTime[0] || efi_data->data[amp_index].calTime[1])) {
255 		u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]);
256 
257 		/*
258 		 * Treat unpopulated cal_target as a wildcard.
259 		 * If target_uid != 0 we can only get here if cal_target == 0
260 		 * or it didn't match any cal_target value.
261 		 * If target_uid == 0 it is a wildcard.
262 		 */
263 		if ((cal_target == 0) || (target_uid == 0))
264 			cal = &efi_data->data[amp_index];
265 		else
266 			dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index);
267 	}
268 
269 	if (cal) {
270 		memcpy(out_data, cal, sizeof(*out_data));
271 		ret = 0;
272 	} else {
273 		dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid);
274 		ret = -ENOENT;
275 	}
276 
277 	kfree(efi_data);
278 
279 	return ret;
280 }
281 
282 /**
283  * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI.
284  * @dev:	struct device of the caller.
285  * @target_uid:	UID to match, or zero to ignore UID matching.
286  * @amp_index:	Entry index to use, or -1 to prevent lookup by index.
287  * @out_data:	struct cirrus_amp_cal_data where the entry will be copied.
288  *
289  * This function can perform 3 types of lookup:
290  *
291  * (target_uid > 0, amp_index >= 0)
292  *	UID search with fallback to using the array index.
293  *	Search the calibration data for a non-zero calTarget that matches
294  *	target_uid, and if found return that entry. Else, if the entry at
295  *	[amp_index] has calTarget == 0, return that entry. Else fail.
296  *
297  * (target_uid > 0, amp_index < 0)
298  *	UID search only.
299  *	Search the calibration data for a non-zero calTarget that matches
300  *	target_uid, and if found return that entry. Else fail.
301  *
302  * (target_uid == 0, amp_index >= 0)
303  *	Array index fetch only.
304  *	Return the entry at [amp_index].
305  *
306  * An array lookup will be skipped if amp_index exceeds the number of
307  * entries in the calibration array, and in this case the return will
308  * be -ENOENT. An out-of-range amp_index does not prevent matching by
309  * target_uid - it has the same effect as passing amp_index < 0.
310  *
311  * If the EFI data is too short to be a valid entry, or the entry count
312  * in the EFI data overflows the actual length of the data, this function
313  * returns -EOVERFLOW.
314  *
315  * Return: 0 if the entry was found, -ENOENT if no entry was found,
316  *	   -EOVERFLOW if the EFI file is corrupt, else other error value.
317  */
318 int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
319 				    struct cirrus_amp_cal_data *out_data)
320 {
321 	if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
322 		return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data);
323 	else
324 		return -ENOENT;
325 }
326 EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB");
327 
328 struct cs_amp_spkid_efi {
329 	efi_char16_t *name;
330 	efi_guid_t *guid;
331 	u8 values[2];
332 };
333 
334 static int cs_amp_get_efi_byte_spkid(struct device *dev, const struct cs_amp_spkid_efi *info)
335 {
336 	efi_status_t status;
337 	unsigned long size;
338 	u8 spkid;
339 	int i, ret;
340 
341 	size = sizeof(spkid);
342 	status = cs_amp_get_efi_variable(info->name, info->guid, &size, &spkid);
343 	ret = cs_amp_convert_efi_status(status);
344 	if (ret < 0)
345 		return ret;
346 
347 	if (size == 0)
348 		return -ENOENT;
349 
350 	for (i = 0; i < ARRAY_SIZE(info->values); i++) {
351 		if (info->values[i] == spkid)
352 			return i;
353 	}
354 
355 	dev_err(dev, "EFI speaker ID bad value %#x\n", spkid);
356 
357 	return -EINVAL;
358 }
359 
360 static const struct cs_amp_spkid_efi cs_amp_spkid_byte_types[] = {
361 	{
362 		.name = LENOVO_SPEAKER_ID_EFI_NAME,
363 		.guid = &LENOVO_SPEAKER_ID_EFI_GUID,
364 		.values = { 0xd0, 0xd1 },
365 	},
366 	{
367 		.name = HP_SPEAKER_ID_EFI_NAME,
368 		.guid = &HP_SPEAKER_ID_EFI_GUID,
369 		.values = { 0x30, 0x31 },
370 	},
371 };
372 
373 /**
374  * cs_amp_get_vendor_spkid - get a speaker ID from vendor-specific storage
375  * @dev:	pointer to struct device
376  *
377  * Known vendor-specific methods of speaker ID are checked and if one is
378  * found its speaker ID value is returned.
379  *
380  * Return: >=0 is a valid speaker ID. -ENOENT if a vendor-specific method
381  *	   was not found. -EACCES if the vendor-specific storage could not
382  *	   be read. Other error values indicate that the data from the
383  *	   vendor-specific storage was found but could not be understood.
384  */
385 int cs_amp_get_vendor_spkid(struct device *dev)
386 {
387 	int i, ret;
388 
389 	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) &&
390 	    !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
391 		return -ENOENT;
392 
393 	for (i = 0; i < ARRAY_SIZE(cs_amp_spkid_byte_types); i++) {
394 		ret = cs_amp_get_efi_byte_spkid(dev, &cs_amp_spkid_byte_types[i]);
395 		if (ret != -ENOENT)
396 			return ret;
397 	}
398 
399 	return -ENOENT;
400 }
401 EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB");
402 
403 static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = {
404 	.get_efi_variable = cs_amp_get_efi_variable,
405 	.write_cal_coeff = cs_amp_write_cal_coeff,
406 };
407 
408 const struct cs_amp_test_hooks * const cs_amp_test_hooks =
409 	PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST), &cs_amp_test_hook_ptrs);
410 EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, "SND_SOC_CS_AMP_LIB");
411 
412 MODULE_DESCRIPTION("Cirrus Logic amplifier library");
413 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
414 MODULE_LICENSE("GPL");
415 MODULE_IMPORT_NS("FW_CS_DSP");
416