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