1 // SPDX-License-Identifier: GPL-2.0-only
3 // Common code for Cirrus Logic Smart Amplifiers
5 // Copyright (C) 2024 Cirrus Logic, Inc. and
6 // Cirrus Logic International Semiconductor Ltd.
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>
18 #define CS_AMP_CAL_GUID \
19 EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3)
21 #define CS_AMP_CAL_NAME L"CirrusSmartAmpCalibrationData"
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
)
27 struct cs_dsp_coeff_ctl
*cs_ctl
;
28 __be32 beval
= cpu_to_be32(val
);
31 KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff
, dsp
, controls
, ctl_name
, val
);
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
);
40 dev_err(dsp
->dev
, "Failed to write to '%s': %d\n", ctl_name
, ret
);
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
)
56 dev_dbg(dsp
->dev
, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n",
57 data
->calAmbient
, data
->calStatus
, data
->calR
);
59 if (list_empty(&dsp
->ctl_list
)) {
60 dev_info(dsp
->dev
, "Calibration disabled due to missing firmware controls\n");
64 ret
= cs_amp_write_cal_coeff(dsp
, controls
, controls
->ambient
, data
->calAmbient
);
68 ret
= cs_amp_write_cal_coeff(dsp
, controls
, controls
->calr
, data
->calR
);
72 ret
= cs_amp_write_cal_coeff(dsp
, controls
, controls
->status
, data
->calStatus
);
76 ret
= cs_amp_write_cal_coeff(dsp
, controls
, controls
->checksum
, data
->calR
+ 1);
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.
89 * Returns: 0 on success, else negative error value.
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
)
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
);
100 EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs
, SND_SOC_CS_AMP_LIB
);
102 static efi_status_t
cs_amp_get_efi_variable(efi_char16_t
*name
,
109 KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable
, name
, guid
, size
, buf
);
111 if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE
))
112 return efi
.get_variable(name
, guid
, &attr
, size
, buf
);
114 return EFI_NOT_FOUND
;
117 static struct cirrus_amp_efi_data
*cs_amp_get_cal_efi_buffer(struct device
*dev
)
119 struct cirrus_amp_efi_data
*efi_data
;
120 unsigned long data_size
= 0;
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
);
130 if (data_size
< sizeof(*efi_data
)) {
131 dev_err(dev
, "EFI cal variable truncated\n");
132 return ERR_PTR(-EOVERFLOW
);
135 /* Get variable contents into buffer */
136 data
= kmalloc(data_size
, GFP_KERNEL
);
138 return ERR_PTR(-ENOMEM
);
140 status
= cs_amp_get_efi_variable(CS_AMP_CAL_NAME
, &CS_AMP_CAL_GUID
, &data_size
, data
);
141 if (status
!= EFI_SUCCESS
) {
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
);
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");
160 dev_err(dev
, "Failed to read calibration data from EFI: %d\n", ret
);
165 static u64
cs_amp_cal_target_u64(const struct cirrus_amp_cal_data
*data
)
167 return ((u64
)data
->calTarget
[1] << 32) | data
->calTarget
[0];
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
)
173 struct cirrus_amp_efi_data
*efi_data
;
174 struct cirrus_amp_cal_data
*cal
= NULL
;
177 efi_data
= cs_amp_get_cal_efi_buffer(dev
);
178 if (IS_ERR(efi_data
))
179 return PTR_ERR(efi_data
);
182 for (i
= 0; i
< efi_data
->count
; ++i
) {
183 u64 cal_target
= cs_amp_cal_target_u64(&efi_data
->data
[i
]);
185 /* Skip empty entries */
186 if (!efi_data
->data
[i
].calTime
[0] && !efi_data
->data
[i
].calTime
[1])
189 /* Skip entries with unpopulated silicon ID */
193 if (cal_target
== target_uid
) {
194 cal
= &efi_data
->data
[i
];
200 if (!cal
&& (amp_index
>= 0) && (amp_index
< efi_data
->count
) &&
201 (efi_data
->data
[amp_index
].calTime
[0] || efi_data
->data
[amp_index
].calTime
[1])) {
202 u64 cal_target
= cs_amp_cal_target_u64(&efi_data
->data
[amp_index
]);
205 * Treat unpopulated cal_target as a wildcard.
206 * If target_uid != 0 we can only get here if cal_target == 0
207 * or it didn't match any cal_target value.
208 * If target_uid == 0 it is a wildcard.
210 if ((cal_target
== 0) || (target_uid
== 0))
211 cal
= &efi_data
->data
[amp_index
];
213 dev_warn(dev
, "Calibration entry %d does not match silicon ID", amp_index
);
217 memcpy(out_data
, cal
, sizeof(*out_data
));
220 dev_warn(dev
, "No calibration for silicon ID %#llx\n", target_uid
);
230 * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI.
231 * @dev: struct device of the caller.
232 * @target_uid: UID to match, or zero to ignore UID matching.
233 * @amp_index: Entry index to use, or -1 to prevent lookup by index.
234 * @out_data: struct cirrus_amp_cal_data where the entry will be copied.
236 * This function can perform 3 types of lookup:
238 * (target_uid > 0, amp_index >= 0)
239 * UID search with fallback to using the array index.
240 * Search the calibration data for a non-zero calTarget that matches
241 * target_uid, and if found return that entry. Else, if the entry at
242 * [amp_index] has calTarget == 0, return that entry. Else fail.
244 * (target_uid > 0, amp_index < 0)
246 * Search the calibration data for a non-zero calTarget that matches
247 * target_uid, and if found return that entry. Else fail.
249 * (target_uid == 0, amp_index >= 0)
250 * Array index fetch only.
251 * Return the entry at [amp_index].
253 * An array lookup will be skipped if amp_index exceeds the number of
254 * entries in the calibration array, and in this case the return will
255 * be -ENOENT. An out-of-range amp_index does not prevent matching by
256 * target_uid - it has the same effect as passing amp_index < 0.
258 * If the EFI data is too short to be a valid entry, or the entry count
259 * in the EFI data overflows the actual length of the data, this function
260 * returns -EOVERFLOW.
262 * Return: 0 if the entry was found, -ENOENT if no entry was found,
263 * -EOVERFLOW if the EFI file is corrupt, else other error value.
265 int cs_amp_get_efi_calibration_data(struct device
*dev
, u64 target_uid
, int amp_index
,
266 struct cirrus_amp_cal_data
*out_data
)
268 if (IS_ENABLED(CONFIG_EFI
) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST
))
269 return _cs_amp_get_efi_calibration_data(dev
, target_uid
, amp_index
, out_data
);
273 EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data
, SND_SOC_CS_AMP_LIB
);
275 static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs
= {
276 .get_efi_variable
= cs_amp_get_efi_variable
,
277 .write_cal_coeff
= cs_amp_write_cal_coeff
,
280 const struct cs_amp_test_hooks
* const cs_amp_test_hooks
=
281 PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST
), &cs_amp_test_hook_ptrs
);
282 EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks
, SND_SOC_CS_AMP_LIB
);
284 MODULE_DESCRIPTION("Cirrus Logic amplifier library");
285 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
286 MODULE_LICENSE("GPL");
287 MODULE_IMPORT_NS(FW_CS_DSP
);