xref: /linux/sound/soc/codecs/cs-amp-lib.c (revision 3482062c786ce4233f8ed3224d824184f53ec154)
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/cleanup.h>
11 #include <linux/debugfs.h>
12 #include <linux/dev_printk.h>
13 #include <linux/efi.h>
14 #include <linux/firmware/cirrus/cs_dsp.h>
15 #include <linux/math64.h>
16 #include <linux/module.h>
17 #include <linux/mutex.h>
18 #include <linux/overflow.h>
19 #include <linux/pci_ids.h>
20 #include <linux/slab.h>
21 #include <linux/timekeeping.h>
22 #include <linux/types.h>
23 #include <sound/cs-amp-lib.h>
24 
25 #define CIRRUS_LOGIC_CALIBRATION_EFI_NAME L"CirrusSmartAmpCalibrationData"
26 #define CIRRUS_LOGIC_CALIBRATION_EFI_GUID \
27 	EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3)
28 
29 #define LENOVO_SPEAKER_ID_EFI_NAME L"SdwSpeaker"
30 #define LENOVO_SPEAKER_ID_EFI_GUID \
31 	EFI_GUID(0x48df970e, 0xe27f, 0x460a, 0xb5, 0x86, 0x77, 0x19, 0x80, 0x1d, 0x92, 0x82)
32 
33 #define HP_SPEAKER_ID_EFI_NAME L"HPSpeakerID"
34 #define HP_SPEAKER_ID_EFI_GUID \
35 	EFI_GUID(0xc49593a4, 0xd099, 0x419b, 0xa2, 0xc3, 0x67, 0xe9, 0x80, 0xe6, 0x1d, 0x1e)
36 
37 #define HP_CALIBRATION_EFI_NAME L"SmartAmpCalibrationData"
38 #define HP_CALIBRATION_EFI_GUID \
39 	EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93)
40 
41 #define DELL_SSIDEXV2_EFI_NAME L"SSIDexV2Data"
42 #define DELL_SSIDEXV2_EFI_GUID \
43 	EFI_GUID(0x6a5f35df, 0x1432, 0x4656, 0x85, 0x97, 0x31, 0x04, 0xd5, 0xbf, 0x3a, 0xb0)
44 
45 static const struct cs_amp_lib_cal_efivar {
46 	efi_char16_t *name;
47 	efi_guid_t *guid;
48 } cs_amp_lib_cal_efivars[] = {
49 	{
50 		.name = HP_CALIBRATION_EFI_NAME,
51 		.guid = &HP_CALIBRATION_EFI_GUID,
52 	},
53 	{
54 		.name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME,
55 		.guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID,
56 	},
57 };
58 
59 #define CS_AMP_CAL_DEFAULT_EFI_ATTR			\
60 		(EFI_VARIABLE_NON_VOLATILE |		\
61 		 EFI_VARIABLE_BOOTSERVICE_ACCESS |	\
62 		 EFI_VARIABLE_RUNTIME_ACCESS)
63 
64 /* Offset from Unix time to Windows time (100ns since 1 Jan 1601) */
65 #define UNIX_TIME_TO_WINDOWS_TIME_OFFSET	116444736000000000ULL
66 
67 static DEFINE_MUTEX(cs_amp_efi_cal_write_lock);
68 
69 static u64 cs_amp_time_now_in_windows_time(void)
70 {
71 	u64 time_in_100ns = div_u64(ktime_get_real_ns(), 100);
72 
73 	return time_in_100ns + UNIX_TIME_TO_WINDOWS_TIME_OFFSET;
74 }
75 
76 static int cs_amp_write_cal_coeff(struct cs_dsp *dsp,
77 				  const struct cirrus_amp_cal_controls *controls,
78 				  const char *ctl_name, u32 val)
79 {
80 	struct cs_dsp_coeff_ctl *cs_ctl;
81 	__be32 beval = cpu_to_be32(val);
82 	int ret;
83 
84 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val);
85 
86 	if (IS_REACHABLE(CONFIG_FW_CS_DSP)) {
87 		scoped_guard(mutex, &dsp->pwr_lock) {
88 			cs_ctl = cs_dsp_get_ctl(dsp, ctl_name,
89 						controls->mem_region,
90 						controls->alg_id);
91 			ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval));
92 		}
93 
94 		if (ret < 0) {
95 			dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret);
96 			return ret;
97 		}
98 
99 		return 0;
100 	}
101 
102 	return -ENODEV;
103 }
104 
105 static int cs_amp_read_cal_coeff(struct cs_dsp *dsp,
106 				 const struct cirrus_amp_cal_controls *controls,
107 				 const char *ctl_name, u32 *val)
108 {
109 	struct cs_dsp_coeff_ctl *cs_ctl;
110 	__be32 beval;
111 	int ret;
112 
113 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_read_cal_coeff, dsp, controls, ctl_name, val);
114 
115 	if (!IS_REACHABLE(CONFIG_FW_CS_DSP))
116 		return -ENODEV;
117 
118 	scoped_guard(mutex, &dsp->pwr_lock) {
119 		cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id);
120 		ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, &beval, sizeof(beval));
121 	}
122 
123 	if (ret < 0) {
124 		dev_err(dsp->dev, "Failed to read '%s': %d\n", ctl_name, ret);
125 		return ret;
126 	}
127 
128 	*val = be32_to_cpu(beval);
129 
130 	return 0;
131 }
132 
133 static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
134 				    const struct cirrus_amp_cal_controls *controls,
135 				    const struct cirrus_amp_cal_data *data)
136 {
137 	int ret;
138 
139 	dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n",
140 		data->calAmbient, data->calStatus, data->calR);
141 
142 	if (list_empty(&dsp->ctl_list)) {
143 		dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n");
144 		return -ENOENT;
145 	}
146 
147 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient);
148 	if (ret)
149 		return ret;
150 
151 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR);
152 	if (ret)
153 		return ret;
154 
155 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus);
156 	if (ret)
157 		return ret;
158 
159 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1);
160 	if (ret)
161 		return ret;
162 
163 	return 0;
164 }
165 
166 static int _cs_amp_read_cal_coeffs(struct cs_dsp *dsp,
167 				    const struct cirrus_amp_cal_controls *controls,
168 				    struct cirrus_amp_cal_data *data)
169 {
170 	u64 time;
171 	u32 val;
172 	int ret;
173 
174 	if (list_empty(&dsp->ctl_list)) {
175 		dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n");
176 		return -ENOENT;
177 	}
178 
179 	ret = cs_amp_read_cal_coeff(dsp, controls, controls->ambient, &val);
180 	if (ret)
181 		return ret;
182 
183 	data->calAmbient = (s8)val;
184 
185 	ret = cs_amp_read_cal_coeff(dsp, controls, controls->calr, &val);
186 	if (ret)
187 		return ret;
188 
189 	data->calR = (u16)val;
190 
191 	ret = cs_amp_read_cal_coeff(dsp, controls, controls->status, &val);
192 	if (ret)
193 		return ret;
194 
195 	data->calStatus = (u8)val;
196 
197 	/* Fill in timestamp */
198 	time = cs_amp_time_now_in_windows_time();
199 	data->calTime[0] = (u32)time;
200 	data->calTime[1] = (u32)(time >> 32);
201 
202 	return 0;
203 }
204 
205 /**
206  * cs_amp_write_cal_coeffs - Write calibration data to firmware controls.
207  * @dsp:	Pointer to struct cs_dsp.
208  * @controls:	Pointer to definition of firmware controls to be written.
209  * @data:	Pointer to calibration data.
210  *
211  * Returns: 0 on success, else negative error value.
212  */
213 int cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
214 			    const struct cirrus_amp_cal_controls *controls,
215 			    const struct cirrus_amp_cal_data *data)
216 {
217 	if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS))
218 		return _cs_amp_write_cal_coeffs(dsp, controls, data);
219 	else
220 		return -ENODEV;
221 }
222 EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, "SND_SOC_CS_AMP_LIB");
223 
224 /**
225  * cs_amp_read_cal_coeffs - Read calibration data from firmware controls.
226  * @dsp:	Pointer to struct cs_dsp.
227  * @controls:	Pointer to definition of firmware controls to be read.
228  * @data:	Pointer to calibration data where results will be written.
229  *
230  * Returns: 0 on success, else negative error value.
231  */
232 int cs_amp_read_cal_coeffs(struct cs_dsp *dsp,
233 			   const struct cirrus_amp_cal_controls *controls,
234 			   struct cirrus_amp_cal_data *data)
235 {
236 	if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS))
237 		return _cs_amp_read_cal_coeffs(dsp, controls, data);
238 	else
239 		return -ENODEV;
240 }
241 EXPORT_SYMBOL_NS_GPL(cs_amp_read_cal_coeffs, "SND_SOC_CS_AMP_LIB");
242 
243 /**
244  * cs_amp_write_ambient_temp - write value to calibration ambient temperature
245  * @dsp:	Pointer to struct cs_dsp.
246  * @controls:	Pointer to definition of firmware controls to be read.
247  * @temp:	Temperature in degrees celcius.
248  *
249  * Returns: 0 on success, else negative error value.
250  */
251 int cs_amp_write_ambient_temp(struct cs_dsp *dsp,
252 			      const struct cirrus_amp_cal_controls *controls,
253 			      u32 temp)
254 {
255 	return cs_amp_write_cal_coeff(dsp, controls, controls->ambient, temp);
256 }
257 EXPORT_SYMBOL_NS_GPL(cs_amp_write_ambient_temp, "SND_SOC_CS_AMP_LIB");
258 
259 static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name,
260 					    efi_guid_t *guid,
261 					    u32 *returned_attr,
262 					    unsigned long *size,
263 					    void *buf)
264 {
265 	u32 attr;
266 
267 	if (!returned_attr)
268 		returned_attr = &attr;
269 
270 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid,
271 				   returned_attr, size, buf);
272 
273 	if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
274 		return efi.get_variable(name, guid, returned_attr, size, buf);
275 
276 	return EFI_NOT_FOUND;
277 }
278 
279 static efi_status_t cs_amp_set_efi_variable(efi_char16_t *name,
280 					    efi_guid_t *guid,
281 					    u32 attr,
282 					    unsigned long size,
283 					    void *buf)
284 {
285 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_set_efi_variable, name, guid, attr, size, buf);
286 
287 	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
288 		return EFI_NOT_FOUND;
289 
290 	return efi.set_variable(name, guid, attr, size, buf);
291 }
292 
293 static int cs_amp_convert_efi_status(efi_status_t status)
294 {
295 	switch (status) {
296 	case EFI_SUCCESS:
297 		return 0;
298 	case EFI_NOT_FOUND:
299 		return -ENOENT;
300 	case EFI_BUFFER_TOO_SMALL:
301 		return -EFBIG;
302 	case EFI_WRITE_PROTECTED:
303 	case EFI_UNSUPPORTED:
304 	case EFI_ACCESS_DENIED:
305 	case EFI_SECURITY_VIOLATION:
306 		return -EACCES;
307 	default:
308 		return -EIO;
309 	}
310 }
311 
312 static void *cs_amp_alloc_get_efi_variable(efi_char16_t *name,
313 					   efi_guid_t *guid,
314 					   u32 *returned_attr)
315 {
316 	efi_status_t status;
317 	unsigned long size = 0;
318 
319 	status = cs_amp_get_efi_variable(name, guid, NULL, &size, NULL);
320 	if (status == EFI_SUCCESS)
321 		return ERR_PTR(-ENOENT);
322 	if (status != EFI_BUFFER_TOO_SMALL)
323 		return ERR_PTR(cs_amp_convert_efi_status(status));
324 
325 	/* Over-alloc to ensure strings are always NUL-terminated */
326 	void *buf __free(kfree) = kzalloc(size + 1, GFP_KERNEL);
327 	if (!buf)
328 		return ERR_PTR(-ENOMEM);
329 
330 	status = cs_amp_get_efi_variable(name, guid, returned_attr, &size, buf);
331 	if (status != EFI_SUCCESS)
332 		return ERR_PTR(cs_amp_convert_efi_status(status));
333 
334 	return_ptr(buf);
335 }
336 
337 static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev,
338 							     efi_char16_t **name,
339 							     efi_guid_t **guid,
340 							     u32 *attr)
341 {
342 	struct cirrus_amp_efi_data *efi_data;
343 	unsigned long data_size = 0;
344 	u8 *data;
345 	efi_status_t status;
346 	int i, ret;
347 
348 	/* Find EFI variable and get size */
349 	for (i = 0; i < ARRAY_SIZE(cs_amp_lib_cal_efivars); i++) {
350 		status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name,
351 						 cs_amp_lib_cal_efivars[i].guid,
352 						 attr, &data_size, NULL);
353 		if (status == EFI_BUFFER_TOO_SMALL)
354 			break;
355 	}
356 
357 	if (status != EFI_BUFFER_TOO_SMALL)
358 		return ERR_PTR(-ENOENT);
359 
360 	if (name)
361 		*name = cs_amp_lib_cal_efivars[i].name;
362 
363 	if (guid)
364 		*guid = cs_amp_lib_cal_efivars[i].guid;
365 
366 	if (data_size < sizeof(*efi_data)) {
367 		dev_err(dev, "EFI cal variable truncated\n");
368 		return ERR_PTR(-EOVERFLOW);
369 	}
370 
371 	/* Get variable contents into buffer */
372 	data = kmalloc(data_size, GFP_KERNEL);
373 	if (!data)
374 		return ERR_PTR(-ENOMEM);
375 
376 	status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name,
377 					 cs_amp_lib_cal_efivars[i].guid,
378 					 attr, &data_size, data);
379 	if (status != EFI_SUCCESS) {
380 		ret = -EINVAL;
381 		goto err;
382 	}
383 
384 	efi_data = (struct cirrus_amp_efi_data *)data;
385 	dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count);
386 
387 	if ((efi_data->count > 128) ||
388 	    struct_size(efi_data, data, efi_data->count) > data_size) {
389 		dev_err(dev, "EFI cal variable truncated\n");
390 		ret = -EOVERFLOW;
391 		goto err;
392 	}
393 
394 	/* This could be zero-filled space pre-allocated by the BIOS */
395 	if (efi_data->size == 0)
396 		efi_data->size = data_size;
397 
398 	return efi_data;
399 
400 err:
401 	kfree(data);
402 	dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret);
403 
404 	return ERR_PTR(ret);
405 }
406 
407 static int cs_amp_set_cal_efi_buffer(struct device *dev,
408 				     efi_char16_t *name,
409 				     efi_guid_t *guid,
410 				     u32 attr,
411 				     struct cirrus_amp_efi_data *data)
412 {
413 	efi_status_t status;
414 
415 	status = cs_amp_set_efi_variable(name, guid, attr,
416 					 struct_size(data, data, data->count), data);
417 
418 	return cs_amp_convert_efi_status(status);
419 }
420 
421 static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
422 					    struct cirrus_amp_cal_data *out_data)
423 {
424 	struct cirrus_amp_efi_data *efi_data;
425 	struct cirrus_amp_cal_data *cal = NULL;
426 	int i, ret;
427 
428 	efi_data = cs_amp_get_cal_efi_buffer(dev, NULL, NULL, NULL);
429 	if (IS_ERR(efi_data))
430 		return PTR_ERR(efi_data);
431 
432 	if (target_uid) {
433 		for (i = 0; i < efi_data->count; ++i) {
434 			u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]);
435 
436 			/* Skip empty entries */
437 			if (!efi_data->data[i].calTime[0] && !efi_data->data[i].calTime[1])
438 				continue;
439 
440 			/* Skip entries with unpopulated silicon ID */
441 			if (cal_target == 0)
442 				continue;
443 
444 			if (cal_target == target_uid) {
445 				cal = &efi_data->data[i];
446 				break;
447 			}
448 		}
449 	}
450 
451 	if (!cal && (amp_index >= 0) && (amp_index < efi_data->count) &&
452 	    (efi_data->data[amp_index].calTime[0] || efi_data->data[amp_index].calTime[1])) {
453 		u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]);
454 
455 		/*
456 		 * Treat unpopulated cal_target as a wildcard.
457 		 * If target_uid != 0 we can only get here if cal_target == 0
458 		 * or it didn't match any cal_target value.
459 		 * If target_uid == 0 it is a wildcard.
460 		 */
461 		if ((cal_target == 0) || (target_uid == 0))
462 			cal = &efi_data->data[amp_index];
463 		else
464 			dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index);
465 	}
466 
467 	if (cal) {
468 		memcpy(out_data, cal, sizeof(*out_data));
469 		ret = 0;
470 	} else {
471 		dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid);
472 		ret = -ENOENT;
473 	}
474 
475 	kfree(efi_data);
476 
477 	return ret;
478 }
479 
480 static int _cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps,
481 					    const struct cirrus_amp_cal_data *in_data)
482 {
483 	u64 cal_target = cs_amp_cal_target_u64(in_data);
484 	unsigned long num_entries;
485 	struct cirrus_amp_efi_data *data;
486 	efi_char16_t *name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME;
487 	efi_guid_t *guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID;
488 	u32 attr = CS_AMP_CAL_DEFAULT_EFI_ATTR;
489 	int i, ret;
490 
491 	if (cal_target == 0)
492 		return -EINVAL;
493 
494 	data = cs_amp_get_cal_efi_buffer(dev, &name, &guid, &attr);
495 	ret = PTR_ERR_OR_ZERO(data);
496 	if (ret == -ENOENT) {
497 		data = NULL;
498 		goto alloc_new;
499 	} else if (ret) {
500 		return ret;
501 	}
502 
503 	/*
504 	 * If the EFI variable is just zero-filled reserved space the count
505 	 * must be set.
506 	 */
507 	if (data->count == 0)
508 		data->count = (data->size - struct_offset(data, data)) / sizeof(data->data[0]);
509 
510 	if (amp_index < 0) {
511 		/* Is there already a slot for this target? */
512 		for (amp_index = 0; amp_index < data->count; amp_index++) {
513 			if (cs_amp_cal_target_u64(&data->data[amp_index]) == cal_target)
514 				break;
515 		}
516 
517 		/* Else find an empty slot */
518 		if (amp_index >= data->count) {
519 			for (amp_index = 0; amp_index < data->count; amp_index++) {
520 				if ((data->data[amp_index].calTime[0] == 0) &&
521 				    (data->data[amp_index].calTime[1] == 0))
522 					break;
523 			}
524 		}
525 	} else {
526 		/*
527 		 * If the index is forced there could be another active
528 		 * slot with the same calTarget. So deduplicate.
529 		 */
530 		for (i = 0; i < data->count; i++) {
531 			if (i == amp_index)
532 				continue;
533 
534 			if ((data->data[i].calTime[0] == 0) && (data->data[i].calTime[1] == 0))
535 				continue;
536 
537 			if (cs_amp_cal_target_u64(&data->data[i]) == cal_target)
538 				memset(data->data[i].calTime, 0, sizeof(data->data[i].calTime));
539 		}
540 	}
541 
542 alloc_new:
543 	if (amp_index < 0)
544 		amp_index = 0;
545 
546 	num_entries = max(num_amps, amp_index + 1);
547 	if (!data || (data->count < num_entries)) {
548 		struct cirrus_amp_efi_data *new_data;
549 		unsigned int new_data_size = struct_size(data, data, num_entries);
550 
551 		new_data = kzalloc(new_data_size, GFP_KERNEL);
552 		if (!new_data) {
553 			ret = -ENOMEM;
554 			goto err;
555 		}
556 
557 		if (data) {
558 			memcpy(new_data, data, struct_size(data, data, data->count));
559 			kfree(data);
560 		}
561 
562 		data = new_data;
563 		data->count = num_entries;
564 		data->size = new_data_size;
565 	}
566 
567 	data->data[amp_index] = *in_data;
568 	ret = cs_amp_set_cal_efi_buffer(dev, name, guid, attr, data);
569 	if (ret)
570 		dev_err(dev, "Failed writing calibration to EFI: %d\n", ret);
571 err:
572 	kfree(data);
573 
574 	return ret;
575 }
576 
577 /**
578  * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI.
579  * @dev:	struct device of the caller.
580  * @target_uid:	UID to match, or zero to ignore UID matching.
581  * @amp_index:	Entry index to use, or -1 to prevent lookup by index.
582  * @out_data:	struct cirrus_amp_cal_data where the entry will be copied.
583  *
584  * This function can perform 3 types of lookup:
585  *
586  * (target_uid > 0, amp_index >= 0)
587  *	UID search with fallback to using the array index.
588  *	Search the calibration data for a non-zero calTarget that matches
589  *	target_uid, and if found return that entry. Else, if the entry at
590  *	[amp_index] has calTarget == 0, return that entry. Else fail.
591  *
592  * (target_uid > 0, amp_index < 0)
593  *	UID search only.
594  *	Search the calibration data for a non-zero calTarget that matches
595  *	target_uid, and if found return that entry. Else fail.
596  *
597  * (target_uid == 0, amp_index >= 0)
598  *	Array index fetch only.
599  *	Return the entry at [amp_index].
600  *
601  * An array lookup will be skipped if amp_index exceeds the number of
602  * entries in the calibration array, and in this case the return will
603  * be -ENOENT. An out-of-range amp_index does not prevent matching by
604  * target_uid - it has the same effect as passing amp_index < 0.
605  *
606  * If the EFI data is too short to be a valid entry, or the entry count
607  * in the EFI data overflows the actual length of the data, this function
608  * returns -EOVERFLOW.
609  *
610  * Return: 0 if the entry was found, -ENOENT if no entry was found,
611  *	   -EOVERFLOW if the EFI file is corrupt, else other error value.
612  */
613 int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
614 				    struct cirrus_amp_cal_data *out_data)
615 {
616 	if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS))
617 		return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data);
618 	else
619 		return -ENOENT;
620 }
621 EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB");
622 
623 /**
624  * cs_amp_set_efi_calibration_data - write a calibration data entry to EFI.
625  * @dev:	struct device of the caller.
626  * @amp_index:	Entry index to use, or -1 to use any available slot.
627  * @num_amps:	Maximum number of amps to reserve slots for, or -1 to ignore.
628  * @in_data:	struct cirrus_amp_cal_data entry to be written to EFI.
629  *
630  * If a Vendor-specific variable exists it will be updated,
631  * else if the Cirrus variable exists it will be updated
632  * else the Cirrus variable will be created.
633  *
634  * If amp_index >= 0 the data will be placed in this entry of the calibration
635  * data array, overwriting what was in that entry. Any other entries with the
636  * same calTarget will be marked empty.
637  *
638  * If amp_index < 0 and in_data->calTarget matches any existing entry, that
639  * entry will be overwritten. Else the first available free entry will be used,
640  * extending the size of the EFI variable if there are no free entries.
641  *
642  * If num_amps > 0 the EFI variable will be sized to contain at least this
643  * many calibration entries, with any new entries marked empty.
644  *
645  * Return: 0 if the write was successful, -EFBIG if space could not be made in
646  *	   the EFI file to add the entry, -EACCES if it was not possible to
647  *	   read or write the EFI variable.
648  */
649 int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps,
650 				    const struct cirrus_amp_cal_data *in_data)
651 {
652 	if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS)) {
653 		scoped_guard(mutex, &cs_amp_efi_cal_write_lock) {
654 			return _cs_amp_set_efi_calibration_data(dev, amp_index,
655 								num_amps, in_data);
656 		}
657 	}
658 
659 	return -ENOENT;
660 }
661 EXPORT_SYMBOL_NS_GPL(cs_amp_set_efi_calibration_data, "SND_SOC_CS_AMP_LIB");
662 
663 struct cs_amp_spkid_efi {
664 	efi_char16_t *name;
665 	efi_guid_t *guid;
666 	u8 values[2];
667 };
668 
669 static int cs_amp_get_efi_byte_spkid(struct device *dev, const struct cs_amp_spkid_efi *info)
670 {
671 	efi_status_t status;
672 	unsigned long size;
673 	u8 spkid;
674 	int i, ret;
675 
676 	size = sizeof(spkid);
677 	status = cs_amp_get_efi_variable(info->name, info->guid, NULL, &size, &spkid);
678 	ret = cs_amp_convert_efi_status(status);
679 	if (ret < 0)
680 		return ret;
681 
682 	if (size == 0)
683 		return -ENOENT;
684 
685 	for (i = 0; i < ARRAY_SIZE(info->values); i++) {
686 		if (info->values[i] == spkid)
687 			return i;
688 	}
689 
690 	dev_err(dev, "EFI speaker ID bad value %#x\n", spkid);
691 
692 	return -EINVAL;
693 }
694 
695 static const struct cs_amp_spkid_efi cs_amp_spkid_byte_types[] = {
696 	{
697 		.name = LENOVO_SPEAKER_ID_EFI_NAME,
698 		.guid = &LENOVO_SPEAKER_ID_EFI_GUID,
699 		.values = { 0xd0, 0xd1 },
700 	},
701 	{
702 		.name = HP_SPEAKER_ID_EFI_NAME,
703 		.guid = &HP_SPEAKER_ID_EFI_GUID,
704 		.values = { 0x30, 0x31 },
705 	},
706 };
707 
708 /**
709  * cs_amp_get_vendor_spkid - get a speaker ID from vendor-specific storage
710  * @dev:	pointer to struct device
711  *
712  * Known vendor-specific methods of speaker ID are checked and if one is
713  * found its speaker ID value is returned.
714  *
715  * Return: >=0 is a valid speaker ID. -ENOENT if a vendor-specific method
716  *	   was not found. -EACCES if the vendor-specific storage could not
717  *	   be read. Other error values indicate that the data from the
718  *	   vendor-specific storage was found but could not be understood.
719  */
720 int cs_amp_get_vendor_spkid(struct device *dev)
721 {
722 	int i, ret;
723 
724 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_vendor_spkid, dev);
725 
726 	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) &&
727 	    !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS))
728 		return -ENOENT;
729 
730 	for (i = 0; i < ARRAY_SIZE(cs_amp_spkid_byte_types); i++) {
731 		ret = cs_amp_get_efi_byte_spkid(dev, &cs_amp_spkid_byte_types[i]);
732 		if (ret != -ENOENT)
733 			return ret;
734 	}
735 
736 	return -ENOENT;
737 }
738 EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB");
739 
740 static const char *cs_amp_devm_get_dell_ssidex(struct device *dev,
741 					       int ssid_vendor, int ssid_device)
742 {
743 	unsigned int hex_prefix;
744 	char audio_id[4];
745 	char delim;
746 	char *p;
747 	int ret;
748 
749 	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) &&
750 	    !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS))
751 		return ERR_PTR(-ENOENT);
752 
753 	char *ssidex_buf __free(kfree) = cs_amp_alloc_get_efi_variable(DELL_SSIDEXV2_EFI_NAME,
754 								       &DELL_SSIDEXV2_EFI_GUID,
755 								       NULL);
756 	if (IS_ERR(ssidex_buf))
757 		return ssidex_buf;
758 
759 	/*
760 	 * SSIDExV2 string is a series of underscore delimited fields.
761 	 * First field is all or part of the SSID. Second field should be
762 	 * a 2-character audio hardware id, followed by other identifiers.
763 	 * Older models did not have the 2-character audio id, so reject
764 	 * the string if the second field is not 2 characters.
765 	 */
766 	ret = sscanf(ssidex_buf, "%8x_%2s%c", &hex_prefix, audio_id, &delim);
767 	if (ret < 2)
768 		return ERR_PTR(-ENOENT);
769 
770 	if ((ret == 3) && (delim != '_'))
771 		return ERR_PTR(-ENOENT);
772 
773 	if (strlen(audio_id) != 2)
774 		return ERR_PTR(-ENOENT);
775 
776 	p = devm_kstrdup(dev, audio_id, GFP_KERNEL);
777 	if (!p)
778 		return ERR_PTR(-ENOMEM);
779 
780 	return p;
781 }
782 
783 /**
784  * cs_amp_devm_get_vendor_specific_variant_id - get variant ID string
785  * @dev:	 pointer to struct device
786  * @ssid_vendor: PCI Subsystem Vendor (-1 if unknown)
787  * @ssid_device: PCI Subsystem Device (-1 if unknown)
788  *
789  * Known vendor-specific hardware identifiers are checked and if one is
790  * found its content is returned as a NUL-terminated string. The returned
791  * string is devm-managed.
792  *
793  * The returned string is not guaranteed to be globally unique.
794  * Generally it should be combined with some other qualifier, such as
795  * PCI SSID, to create a globally unique ID.
796  *
797  * If the caller has a PCI SSID it should pass it in @ssid_vendor and
798  * @ssid_device. If the vendor-spefic ID contains this SSID it will be
799  * stripped from the returned string to prevent duplication.
800  *
801  * If the caller does not have a PCI SSID, pass -1 for @ssid_vendor and
802  * @ssid_device.
803  *
804  * Return:
805  * * a pointer to a devm-managed string
806  * * ERR_PTR(-ENOENT) if no vendor-specific qualifier
807  * * ERR_PTR error value
808  */
809 const char *cs_amp_devm_get_vendor_specific_variant_id(struct device *dev,
810 						       int ssid_vendor,
811 						       int ssid_device)
812 {
813 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_devm_get_vendor_specific_variant_id,
814 				   dev, ssid_vendor, ssid_device);
815 
816 	if ((ssid_vendor == PCI_VENDOR_ID_DELL) || (ssid_vendor < 0))
817 		return cs_amp_devm_get_dell_ssidex(dev, ssid_vendor, ssid_device);
818 
819 	return ERR_PTR(-ENOENT);
820 }
821 EXPORT_SYMBOL_NS_GPL(cs_amp_devm_get_vendor_specific_variant_id, "SND_SOC_CS_AMP_LIB");
822 
823 /**
824  * cs_amp_create_debugfs - create a debugfs directory for a device
825  *
826  * @dev: pointer to struct device
827  *
828  * Creates a node under "cirrus_logic" in the root of the debugfs filesystem.
829  * This is for Cirrus-specific debugfs functionality to be grouped in a
830  * defined way, independently of the debugfs provided by ALSA/ASoC.
831  * The general ALSA/ASoC debugfs may not be enabled, and does not necessarily
832  * have a stable layout or naming convention.
833  *
834  * Return: Pointer to the dentry for the created directory, or -ENODEV.
835  */
836 struct dentry *cs_amp_create_debugfs(struct device *dev)
837 {
838 	struct dentry *dir, *created;
839 
840 	/* debugfs_lookup() can return NULL or ERR_PTR on error */
841 	dir = debugfs_lookup("cirrus_logic", NULL);
842 	if (!IS_ERR_OR_NULL(dir)) {
843 		created = debugfs_create_dir(dev_name(dev), dir);
844 		dput(dir);
845 
846 		return created;
847 	}
848 
849 	dir = debugfs_create_dir("cirrus_logic", NULL);
850 
851 	return debugfs_create_dir(dev_name(dev), dir);
852 }
853 EXPORT_SYMBOL_NS_GPL(cs_amp_create_debugfs, "SND_SOC_CS_AMP_LIB");
854 
855 static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = {
856 	.get_efi_variable = cs_amp_get_efi_variable,
857 	.set_efi_variable = cs_amp_set_efi_variable,
858 	.write_cal_coeff = cs_amp_write_cal_coeff,
859 	.read_cal_coeff = cs_amp_read_cal_coeff,
860 };
861 
862 const struct cs_amp_test_hooks * const cs_amp_test_hooks =
863 	PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST_HOOKS), &cs_amp_test_hook_ptrs);
864 EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, "SND_SOC_CS_AMP_LIB");
865 
866 MODULE_DESCRIPTION("Cirrus Logic amplifier library");
867 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
868 MODULE_LICENSE("GPL");
869 MODULE_IMPORT_NS("FW_CS_DSP");
870