xref: /linux/sound/soc/codecs/cs-amp-lib.c (revision eb01fe7abbe2d0b38824d2a93fdb4cc3eaf2ccc1)
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/slab.h>
15 #include <linux/types.h>
16 #include <sound/cs-amp-lib.h>
17 
18 #define CS_AMP_CAL_GUID \
19 	EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3)
20 
21 #define CS_AMP_CAL_NAME	L"CirrusSmartAmpCalibrationData"
22 
23 static int cs_amp_write_cal_coeff(struct cs_dsp *dsp,
24 				  const struct cirrus_amp_cal_controls *controls,
25 				  const char *ctl_name, u32 val)
26 {
27 	struct cs_dsp_coeff_ctl *cs_ctl;
28 	__be32 beval = cpu_to_be32(val);
29 	int ret;
30 
31 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val);
32 
33 	if (IS_REACHABLE(CONFIG_FW_CS_DSP)) {
34 		mutex_lock(&dsp->pwr_lock);
35 		cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id);
36 		ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval));
37 		mutex_unlock(&dsp->pwr_lock);
38 
39 		if (ret < 0) {
40 			dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret);
41 			return ret;
42 		}
43 
44 		return 0;
45 	}
46 
47 	return -ENODEV;
48 }
49 
50 static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
51 				    const struct cirrus_amp_cal_controls *controls,
52 				    const struct cirrus_amp_cal_data *data)
53 {
54 	int ret;
55 
56 	dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n",
57 		data->calAmbient, data->calStatus, data->calR);
58 
59 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient);
60 	if (ret)
61 		return ret;
62 
63 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR);
64 	if (ret)
65 		return ret;
66 
67 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus);
68 	if (ret)
69 		return ret;
70 
71 	ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1);
72 	if (ret)
73 		return ret;
74 
75 	return 0;
76 }
77 
78 /**
79  * cs_amp_write_cal_coeffs - Write calibration data to firmware controls.
80  * @dsp:	Pointer to struct cs_dsp.
81  * @controls:	Pointer to definition of firmware controls to be written.
82  * @data:	Pointer to calibration data.
83  *
84  * Returns: 0 on success, else negative error value.
85  */
86 int cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
87 			    const struct cirrus_amp_cal_controls *controls,
88 			    const struct cirrus_amp_cal_data *data)
89 {
90 	if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
91 		return _cs_amp_write_cal_coeffs(dsp, controls, data);
92 	else
93 		return -ENODEV;
94 }
95 EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, SND_SOC_CS_AMP_LIB);
96 
97 static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name,
98 					    efi_guid_t *guid,
99 					    unsigned long *size,
100 					    void *buf)
101 {
102 	u32 attr;
103 
104 	KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf);
105 
106 	if (IS_ENABLED(CONFIG_EFI))
107 		return efi.get_variable(name, guid, &attr, size, buf);
108 
109 	return EFI_NOT_FOUND;
110 }
111 
112 static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
113 {
114 	struct cirrus_amp_efi_data *efi_data;
115 	unsigned long data_size = 0;
116 	u8 *data;
117 	efi_status_t status;
118 	int ret;
119 
120 	/* Get real size of UEFI variable */
121 	status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, NULL);
122 	if (status != EFI_BUFFER_TOO_SMALL)
123 		return ERR_PTR(-ENOENT);
124 
125 	if (data_size < sizeof(*efi_data)) {
126 		dev_err(dev, "EFI cal variable truncated\n");
127 		return ERR_PTR(-EOVERFLOW);
128 	}
129 
130 	/* Get variable contents into buffer */
131 	data = kmalloc(data_size, GFP_KERNEL);
132 	if (!data)
133 		return ERR_PTR(-ENOMEM);
134 
135 	status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, data);
136 	if (status != EFI_SUCCESS) {
137 		ret = -EINVAL;
138 		goto err;
139 	}
140 
141 	efi_data = (struct cirrus_amp_efi_data *)data;
142 	dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count);
143 
144 	if ((efi_data->count > 128) ||
145 	    offsetof(struct cirrus_amp_efi_data, data[efi_data->count]) > data_size) {
146 		dev_err(dev, "EFI cal variable truncated\n");
147 		ret = -EOVERFLOW;
148 		goto err;
149 	}
150 
151 	return efi_data;
152 
153 err:
154 	kfree(data);
155 	dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret);
156 
157 	return ERR_PTR(ret);
158 }
159 
160 static u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data)
161 {
162 	return ((u64)data->calTarget[1] << 32) | data->calTarget[0];
163 }
164 
165 static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
166 					    struct cirrus_amp_cal_data *out_data)
167 {
168 	struct cirrus_amp_efi_data *efi_data;
169 	struct cirrus_amp_cal_data *cal = NULL;
170 	int i, ret;
171 
172 	efi_data = cs_amp_get_cal_efi_buffer(dev);
173 	if (IS_ERR(efi_data))
174 		return PTR_ERR(efi_data);
175 
176 	if (target_uid) {
177 		for (i = 0; i < efi_data->count; ++i) {
178 			u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]);
179 
180 			/* Skip entries with unpopulated silicon ID */
181 			if (cal_target == 0)
182 				continue;
183 
184 			if (cal_target == target_uid) {
185 				cal = &efi_data->data[i];
186 				break;
187 			}
188 		}
189 	}
190 
191 	if (!cal && (amp_index >= 0) && (amp_index < efi_data->count)) {
192 		u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]);
193 
194 		/*
195 		 * Treat unpopulated cal_target as a wildcard.
196 		 * If target_uid != 0 we can only get here if cal_target == 0
197 		 * or it didn't match any cal_target value.
198 		 * If target_uid == 0 it is a wildcard.
199 		 */
200 		if ((cal_target == 0) || (target_uid == 0))
201 			cal = &efi_data->data[amp_index];
202 		else
203 			dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index);
204 	}
205 
206 	if (cal) {
207 		memcpy(out_data, cal, sizeof(*out_data));
208 		ret = 0;
209 	} else {
210 		dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid);
211 		ret = -ENOENT;
212 	}
213 
214 	kfree(efi_data);
215 
216 	return ret;
217 }
218 
219 /**
220  * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI.
221  * @dev:	struct device of the caller.
222  * @target_uid:	UID to match, or zero to ignore UID matching.
223  * @amp_index:	Entry index to use, or -1 to prevent lookup by index.
224  * @out_data:	struct cirrus_amp_cal_data where the entry will be copied.
225  *
226  * This function can perform 3 types of lookup:
227  *
228  * (target_uid > 0, amp_index >= 0)
229  *	UID search with fallback to using the array index.
230  *	Search the calibration data for a non-zero calTarget that matches
231  *	target_uid, and if found return that entry. Else, if the entry at
232  *	[amp_index] has calTarget == 0, return that entry. Else fail.
233  *
234  * (target_uid > 0, amp_index < 0)
235  *	UID search only.
236  *	Search the calibration data for a non-zero calTarget that matches
237  *	target_uid, and if found return that entry. Else fail.
238  *
239  * (target_uid == 0, amp_index >= 0)
240  *	Array index fetch only.
241  *	Return the entry at [amp_index].
242  *
243  * An array lookup will be skipped if amp_index exceeds the number of
244  * entries in the calibration array, and in this case the return will
245  * be -ENOENT. An out-of-range amp_index does not prevent matching by
246  * target_uid - it has the same effect as passing amp_index < 0.
247  *
248  * If the EFI data is too short to be a valid entry, or the entry count
249  * in the EFI data overflows the actual length of the data, this function
250  * returns -EOVERFLOW.
251  *
252  * Return: 0 if the entry was found, -ENOENT if no entry was found,
253  *	   -EOVERFLOW if the EFI file is corrupt, else other error value.
254  */
255 int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
256 				    struct cirrus_amp_cal_data *out_data)
257 {
258 	if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
259 		return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data);
260 	else
261 		return -ENOENT;
262 }
263 EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, SND_SOC_CS_AMP_LIB);
264 
265 static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = {
266 	.get_efi_variable = cs_amp_get_efi_variable,
267 	.write_cal_coeff = cs_amp_write_cal_coeff,
268 };
269 
270 const struct cs_amp_test_hooks * const cs_amp_test_hooks =
271 	PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST), &cs_amp_test_hook_ptrs);
272 EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, SND_SOC_CS_AMP_LIB);
273 
274 MODULE_DESCRIPTION("Cirrus Logic amplifier library");
275 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
276 MODULE_LICENSE("GPL");
277 MODULE_IMPORT_NS(FW_CS_DSP);
278