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
23static 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, name: ctl_name, type: controls->mem_region, alg: controls->alg_id);
36 ret = cs_dsp_coeff_write_ctrl(ctl: cs_ctl, off: 0, buf: &beval, len: sizeof(beval));
37 mutex_unlock(lock: &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
50static 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 if (list_empty(head: &dsp->ctl_list)) {
60 dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n");
61 return -ENOENT;
62 }
63
64 ret = cs_amp_write_cal_coeff(dsp, controls, ctl_name: controls->ambient, val: data->calAmbient);
65 if (ret)
66 return ret;
67
68 ret = cs_amp_write_cal_coeff(dsp, controls, ctl_name: controls->calr, val: data->calR);
69 if (ret)
70 return ret;
71
72 ret = cs_amp_write_cal_coeff(dsp, controls, ctl_name: controls->status, val: data->calStatus);
73 if (ret)
74 return ret;
75
76 ret = cs_amp_write_cal_coeff(dsp, controls, ctl_name: controls->checksum, val: 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 */
91int 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{
95 if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
96 return _cs_amp_write_cal_coeffs(dsp, controls, data);
97 else
98 return -ENODEV;
99}
100EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, SND_SOC_CS_AMP_LIB);
101
102static 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
109 KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf);
110
111 if (IS_ENABLED(CONFIG_EFI))
112 return efi.get_variable(name, guid, &attr, size, buf);
113
114 return EFI_NOT_FOUND;
115}
116
117static 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, guid: &CS_AMP_CAL_GUID, size: &data_size, NULL);
127 if (status != EFI_BUFFER_TOO_SMALL)
128 return ERR_PTR(error: -ENOENT);
129
130 if (data_size < sizeof(*efi_data)) {
131 dev_err(dev, "EFI cal variable truncated\n");
132 return ERR_PTR(error: -EOVERFLOW);
133 }
134
135 /* Get variable contents into buffer */
136 data = kmalloc(size: data_size, GFP_KERNEL);
137 if (!data)
138 return ERR_PTR(error: -ENOMEM);
139
140 status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, guid: &CS_AMP_CAL_GUID, size: &data_size, buf: 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
158err:
159 kfree(objp: data);
160 dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret);
161
162 return ERR_PTR(error: ret);
163}
164
165static 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
170static 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(ptr: efi_data))
179 return PTR_ERR(ptr: 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(data: &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(data: &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(objp: 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 */
260int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
261 struct cirrus_amp_cal_data *out_data)
262{
263 if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
264 return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data);
265 else
266 return -ENOENT;
267}
268EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, SND_SOC_CS_AMP_LIB);
269
270static 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
275const 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);
277EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, SND_SOC_CS_AMP_LIB);
278
279MODULE_DESCRIPTION("Cirrus Logic amplifier library");
280MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
281MODULE_LICENSE("GPL");
282MODULE_IMPORT_NS(FW_CS_DSP);
283

source code of linux/sound/soc/codecs/cs-amp-lib.c