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