mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
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>
This commit is contained in:
committed by
Mark Brown
parent
959400caf5
commit
2b62e66626
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user