diff options
| author | Richard Fitzgerald <rf@opensource.cirrus.com> | 2025-10-21 11:50:19 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2025-10-27 14:07:48 +0000 |
| commit | 2b62e66626f05e277c8fdeb50d4c1e0cbab2fe0e (patch) | |
| tree | c2edd7a83daa52f2e123042dec470d9b70c9e14c | |
| parent | 959400caf51eb31f95d1ab754a285b5546ebd3e4 (diff) | |
ASoC: cs-amp-lib: Add function to write calibration to UEFI
Add cs_amp_set_efi_calibration_data() to write an amp calibration
blob to UEFI calibration variable.
The UEFI variable will be updated or created as necessary.
- If a Vendor-specific variable exists it will be updated,
else if the Cirrus variable exists it will be update
else the Cirrus variable will be created.
Some collateral changes are required:
- cs_amp_convert_efi_status() now specifically handles
EFI_WRITE_PROTECTED error.
- cs_amp_get_cal_efi_buffer() can optionally return the name,
guid and attr of the variable it found.
- cs_amp_get_cal_efi_buffer() will update the 'size' field of
the returned data blob if it is zero. The BIOS could have
pre-allocated the UEFI variable as zero-filled
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20251021105022.1013685-9-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
| -rw-r--r-- | include/sound/cs-amp-lib.h | 2 | ||||
| -rw-r--r-- | sound/soc/codecs/cs-amp-lib.c | 190 |
2 files changed, 188 insertions, 4 deletions
diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h index 2e5616a5e1f7..240bc53a9307 100644 --- a/include/sound/cs-amp-lib.h +++ b/include/sound/cs-amp-lib.h @@ -55,6 +55,8 @@ int cs_amp_write_ambient_temp(struct cs_dsp *dsp, u32 temp); int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, struct cirrus_amp_cal_data *out_data); +int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, + const struct cirrus_amp_cal_data *in_data); int cs_amp_get_vendor_spkid(struct device *dev); struct dentry *cs_amp_create_debugfs(struct device *dev); diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index c5791cbeb5b8..7038574e3f4b 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -13,6 +13,7 @@ #include <linux/firmware/cirrus/cs_dsp.h> #include <linux/math64.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/overflow.h> #include <linux/slab.h> #include <linux/timekeeping.h> @@ -49,9 +50,16 @@ static const struct cs_amp_lib_cal_efivar { }, }; +#define CS_AMP_CAL_DEFAULT_EFI_ATTR \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) + /* Offset from Unix time to Windows time (100ns since 1 Jan 1601) */ #define UNIX_TIME_TO_WINDOWS_TIME_OFFSET 116444736000000000ULL +static DEFINE_MUTEX(cs_amp_efi_cal_write_lock); + static u64 cs_amp_time_now_in_windows_time(void) { u64 time_in_100ns = div_u64(ktime_get_real_ns(), 100); @@ -263,6 +271,20 @@ static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, return EFI_NOT_FOUND; } +static efi_status_t cs_amp_set_efi_variable(efi_char16_t *name, + efi_guid_t *guid, + u32 attr, + unsigned long size, + void *buf) +{ + KUNIT_STATIC_STUB_REDIRECT(cs_amp_set_efi_variable, name, guid, attr, size, buf); + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE)) + return EFI_NOT_FOUND; + + return efi.set_variable(name, guid, attr, size, buf); +} + static int cs_amp_convert_efi_status(efi_status_t status) { switch (status) { @@ -272,6 +294,7 @@ static int cs_amp_convert_efi_status(efi_status_t status) return -ENOENT; case EFI_BUFFER_TOO_SMALL: return -EFBIG; + case EFI_WRITE_PROTECTED: case EFI_UNSUPPORTED: case EFI_ACCESS_DENIED: case EFI_SECURITY_VIOLATION: @@ -281,7 +304,10 @@ static int cs_amp_convert_efi_status(efi_status_t status) } } -static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) +static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev, + efi_char16_t **name, + efi_guid_t **guid, + u32 *attr) { struct cirrus_amp_efi_data *efi_data; unsigned long data_size = 0; @@ -293,7 +319,7 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) for (i = 0; i < ARRAY_SIZE(cs_amp_lib_cal_efivars); i++) { status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, cs_amp_lib_cal_efivars[i].guid, - NULL, &data_size, NULL); + attr, &data_size, NULL); if (status == EFI_BUFFER_TOO_SMALL) break; } @@ -301,6 +327,12 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) if (status != EFI_BUFFER_TOO_SMALL) return ERR_PTR(-ENOENT); + if (name) + *name = cs_amp_lib_cal_efivars[i].name; + + if (guid) + *guid = cs_amp_lib_cal_efivars[i].guid; + if (data_size < sizeof(*efi_data)) { dev_err(dev, "EFI cal variable truncated\n"); return ERR_PTR(-EOVERFLOW); @@ -313,7 +345,7 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, cs_amp_lib_cal_efivars[i].guid, - NULL, &data_size, data); + attr, &data_size, data); if (status != EFI_SUCCESS) { ret = -EINVAL; goto err; @@ -329,6 +361,10 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) goto err; } + /* This could be zero-filled space pre-allocated by the BIOS */ + if (efi_data->size == 0) + efi_data->size = data_size; + return efi_data; err: @@ -338,6 +374,20 @@ err: return ERR_PTR(ret); } +static int cs_amp_set_cal_efi_buffer(struct device *dev, + efi_char16_t *name, + efi_guid_t *guid, + u32 attr, + struct cirrus_amp_efi_data *data) +{ + efi_status_t status; + + status = cs_amp_set_efi_variable(name, guid, attr, + struct_size(data, data, data->count), data); + + return cs_amp_convert_efi_status(status); +} + static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, struct cirrus_amp_cal_data *out_data) { @@ -345,7 +395,7 @@ static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, struct cirrus_amp_cal_data *cal = NULL; int i, ret; - efi_data = cs_amp_get_cal_efi_buffer(dev); + efi_data = cs_amp_get_cal_efi_buffer(dev, NULL, NULL, NULL); if (IS_ERR(efi_data)) return PTR_ERR(efi_data); @@ -397,6 +447,98 @@ static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, return ret; } +static int _cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, + const struct cirrus_amp_cal_data *in_data) +{ + u64 cal_target = cs_amp_cal_target_u64(in_data); + unsigned long num_entries; + struct cirrus_amp_efi_data *data __free(kfree) = NULL; + efi_char16_t *name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME; + efi_guid_t *guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID; + u32 attr = CS_AMP_CAL_DEFAULT_EFI_ATTR; + int i, ret; + + if (cal_target == 0) + return -EINVAL; + + data = cs_amp_get_cal_efi_buffer(dev, &name, &guid, &attr); + ret = PTR_ERR_OR_ZERO(data); + if (ret == -ENOENT) { + data = NULL; + goto alloc_new; + } else if (ret) { + return ret; + } + + /* + * If the EFI variable is just zero-filled reserved space the count + * must be set. + */ + if (data->count == 0) + data->count = (data->size - sizeof(data)) / sizeof(data->data[0]); + + if (amp_index < 0) { + /* Is there already a slot for this target? */ + for (amp_index = 0; amp_index < data->count; amp_index++) { + if (cs_amp_cal_target_u64(&data->data[amp_index]) == cal_target) + break; + } + + /* Else find an empty slot */ + if (amp_index >= data->count) { + for (amp_index = 0; amp_index < data->count; amp_index++) { + if ((data->data[amp_index].calTime[0] == 0) && + (data->data[amp_index].calTime[1] == 0)) + break; + } + } + } else { + /* + * If the index is forced there could be another active + * slot with the same calTarget. So deduplicate. + */ + for (i = 0; i < data->count; i++) { + if (i == amp_index) + continue; + + if ((data->data[i].calTime[0] == 0) && (data->data[i].calTime[1] == 0)) + continue; + + if (cs_amp_cal_target_u64(&data->data[i]) == cal_target) + memset(data->data[i].calTime, 0, sizeof(data->data[i].calTime)); + } + } + +alloc_new: + if (amp_index < 0) + amp_index = 0; + + num_entries = max(num_amps, amp_index + 1); + if (!data || (data->count < num_entries)) { + struct cirrus_amp_efi_data *old_data __free(kfree) = no_free_ptr(data); + unsigned int new_data_size = struct_size(data, data, num_entries); + + data = kzalloc(new_data_size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (old_data) + memcpy(data, old_data, struct_size(old_data, data, old_data->count)); + + data->count = num_entries; + data->size = new_data_size; + } + + data->data[amp_index] = *in_data; + ret = cs_amp_set_cal_efi_buffer(dev, name, guid, attr, data); + if (ret) { + dev_err(dev, "Failed writing calibration to EFI: %d\n", ret); + return ret; + } + + return 0; +} + /** * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI. * @dev: struct device of the caller. @@ -443,6 +585,46 @@ int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_ } EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); +/** + * cs_amp_set_efi_calibration_data - write a calibration data entry to EFI. + * @dev: struct device of the caller. + * @amp_index: Entry index to use, or -1 to use any available slot. + * @num_amps: Maximum number of amps to reserve slots for, or -1 to ignore. + * @in_data: struct cirrus_amp_cal_data entry to be written to EFI. + * + * If a Vendor-specific variable exists it will be updated, + * else if the Cirrus variable exists it will be updated + * else the Cirrus variable will be created. + * + * If amp_index >= 0 the data will be placed in this entry of the calibration + * data array, overwriting what was in that entry. Any other entries with the + * same calTarget will be marked empty. + * + * If amp_index < 0 and in_data->calTarget matches any existing entry, that + * entry will be overwritten. Else the first available free entry will be used, + * extending the size of the EFI variable if there are no free entries. + * + * If num_amps > 0 the EFI variable will be sized to contain at least this + * many calibration entries, with any new entries marked empty. + * + * Return: 0 if the write was successful, -EFBIG if space could not be made in + * the EFI file to add the entry, -EACCES if it was not possible to + * read or write the EFI variable. + */ +int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, + const struct cirrus_amp_cal_data *in_data) +{ + if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) { + scoped_guard(mutex, &cs_amp_efi_cal_write_lock) { + return _cs_amp_set_efi_calibration_data(dev, amp_index, + num_amps, in_data); + } + } + + return -ENOENT; +} +EXPORT_SYMBOL_NS_GPL(cs_amp_set_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); + struct cs_amp_spkid_efi { efi_char16_t *name; efi_guid_t *guid; |