* [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration
@ 2025-10-16 10:42 Richard Fitzgerald
2025-10-16 10:42 ` [PATCH 01/11] ASoC: cs35l56: Read silicon ID during initialization and save it Richard Fitzgerald
` (10 more replies)
0 siblings, 11 replies; 17+ messages in thread
From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw)
To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches
Until now, all products with an amplifier supported by the cs35l56 driver
have shipped with Microsoft Windows pre-installed. The factory calibration
of speaker protection has therefore been done using the Windows driver.
However, products that ship with a Linux-based distro must be able to
perform the factory calibration procedure from within the Linux-based
environment. This patch series adds that support.
NOTE: unfortunately this is yet another series that is mainly ASoC but
also needs some changes to the HDA driver, and they have build dependencies
on the ASoC code. I suggest taking this all through Mark's tree and we'll
avoid sending any other commits to the HDA driver until it has all landed
in Takashi's tree.
Richard Fitzgerald (11):
ASoC: cs35l56: Read silicon ID during initialization and save it
ASoC: cs-amp-lib: Add helpers for factory calibration
ASoC: cs35l56: Add common code for factory calibration
ASoC: cs35l56: Create sysfs files for factory calibration
ALSA: hda/cs35l56: Create sysfs files for factory calibration
ASoC: cs-amp-lib-test: Add cases for factory calibration helpers
ASoC: cs-amp-lib: Return attributes from cs_amp_get_efi_variable()
ASoC: cs-amp-lib: Add function to write calibration to EFI
ASoC: cs35l56: Add calibration command to store into UEFI
ALSA: hda/cs35l56: Set cal_index to the amp index
ASoC: cs-amp-lib-test: Add test cases for
cs_amp_set_efi_calibration_data()
include/sound/cs-amp-lib.h | 24 +-
include/sound/cs35l56.h | 20 +
sound/hda/codecs/side-codecs/Kconfig | 15 +
sound/hda/codecs/side-codecs/cs35l56_hda.c | 141 +-
sound/soc/codecs/Kconfig | 18 +
sound/soc/codecs/cs-amp-lib-test.c | 1491 +++++++++++++++++++-
sound/soc/codecs/cs-amp-lib.c | 319 ++++-
sound/soc/codecs/cs35l56-shared.c | 321 ++++-
sound/soc/codecs/cs35l56.c | 182 +++
9 files changed, 2477 insertions(+), 54 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 01/11] ASoC: cs35l56: Read silicon ID during initialization and save it 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 02/11] ASoC: cs-amp-lib: Add helpers for factory calibration Richard Fitzgerald ` (9 subsequent siblings) 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Read the silicon ID from the amp during one-time cs35l56_hw_init() and store it in struct cs35l56_base, instead of reading it from registers every time it is needed. Note that marking it non-volatile without a default in regmap isn't a suitable alternative because this causes regcache_sync() to always write the cached value out to the registers. This could trigger a bus fault interrupt inside the amp, which we want to avoid. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs35l56.h | 1 + sound/soc/codecs/cs35l56-shared.c | 53 +++++++++++++++---------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index ab044ce2aa8b..ec9b1072d6be 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -309,6 +309,7 @@ struct cs35l56_base { struct cs35l56_spi_payload *spi_payload_buf; const struct cs35l56_fw_reg *fw_reg; const struct cirrus_amp_cal_controls *calibration_controls; + u64 silicon_uid; }; static inline bool cs35l56_is_otp_register(unsigned int reg) diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index 9e6b9ca2f354..1ecfc38d8eb4 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -853,7 +853,7 @@ struct cs35l56_pte { } __packed; static_assert((sizeof(struct cs35l56_pte) % sizeof(u32)) == 0); -static int cs35l56_read_silicon_uid(struct cs35l56_base *cs35l56_base, u64 *uid) +static int cs35l56_read_silicon_uid(struct cs35l56_base *cs35l56_base) { struct cs35l56_pte pte; u64 unique_id; @@ -870,14 +870,15 @@ static int cs35l56_read_silicon_uid(struct cs35l56_base *cs35l56_base, u64 *uid) unique_id |= (u32)pte.x | ((u32)pte.y << 8) | ((u32)pte.wafer_id << 16) | ((u32)pte.dvs << 24); - *uid = unique_id; + cs35l56_base->silicon_uid = unique_id; return 0; } -static int cs35l63_read_silicon_uid(struct cs35l56_base *cs35l56_base, u64 *uid) +static int cs35l63_read_silicon_uid(struct cs35l56_base *cs35l56_base) { u32 tmp[2]; + u64 unique_id; int ret; ret = regmap_bulk_read(cs35l56_base->regmap, CS35L56_DIE_STS1, tmp, ARRAY_SIZE(tmp)); @@ -886,9 +887,11 @@ static int cs35l63_read_silicon_uid(struct cs35l56_base *cs35l56_base, u64 *uid) return ret; } - *uid = tmp[1]; - *uid <<= 32; - *uid |= tmp[0]; + unique_id = tmp[1]; + unique_id <<= 32; + unique_id |= tmp[0]; + + cs35l56_base->silicon_uid = unique_id; return 0; } @@ -915,33 +918,14 @@ static const struct cirrus_amp_cal_controls cs35l63_calibration_controls = { int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base) { - u64 silicon_uid = 0; int ret; /* Driver can't apply calibration to a secured part, so skip */ if (cs35l56_base->secured) return 0; - switch (cs35l56_base->type) { - case 0x54: - case 0x56: - case 0x57: - ret = cs35l56_read_silicon_uid(cs35l56_base, &silicon_uid); - break; - case 0x63: - ret = cs35l63_read_silicon_uid(cs35l56_base, &silicon_uid); - break; - default: - ret = -ENODEV; - break; - } - - if (ret < 0) - return ret; - - dev_dbg(cs35l56_base->dev, "UniqueID = %#llx\n", silicon_uid); - - ret = cs_amp_get_efi_calibration_data(cs35l56_base->dev, silicon_uid, + ret = cs_amp_get_efi_calibration_data(cs35l56_base->dev, + cs35l56_base->silicon_uid, cs35l56_base->cal_index, &cs35l56_base->cal_data); @@ -1111,6 +1095,21 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base) CS35L56_TEMP_ERR_EINT1_MASK, 0); + switch (cs35l56_base->type) { + case 0x54: + case 0x56: + case 0x57: + ret = cs35l56_read_silicon_uid(cs35l56_base); + break; + default: + ret = cs35l63_read_silicon_uid(cs35l56_base); + break; + } + if (ret) + return ret; + + dev_dbg(cs35l56_base->dev, "SiliconID = %#llx\n", cs35l56_base->silicon_uid); + return 0; } EXPORT_SYMBOL_NS_GPL(cs35l56_hw_init, "SND_SOC_CS35L56_SHARED"); -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 02/11] ASoC: cs-amp-lib: Add helpers for factory calibration 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 01/11] ASoC: cs35l56: Read silicon ID during initialization and save it Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 03/11] ASoC: cs35l56: Add common code " Richard Fitzgerald ` (8 subsequent siblings) 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Add two helper functions for performing factory calibration. cs_amp_read_cal_coeffs() reads the results of a calibration into a struct cirrus_amp_cal_data. The calTime member is also filled in with the current time (which is defined to be in Windows format). cs_amp_write_ambient_temp() writes a given temperature value to the firmware control for ambient temperature. The cs_amp_cal_target_u64() has been moved into the header file so that it can be used by the calling code and by KUnit tests. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs-amp-lib.h | 11 +++ sound/soc/codecs/cs-amp-lib.c | 122 ++++++++++++++++++++++++++++++++-- 2 files changed, 128 insertions(+), 5 deletions(-) diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h index 43a87a39110c..e4df0736039a 100644 --- a/include/sound/cs-amp-lib.h +++ b/include/sound/cs-amp-lib.h @@ -47,10 +47,21 @@ struct cirrus_amp_cal_controls { int cs_amp_write_cal_coeffs(struct cs_dsp *dsp, const struct cirrus_amp_cal_controls *controls, const struct cirrus_amp_cal_data *data); +int cs_amp_read_cal_coeffs(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + struct cirrus_amp_cal_data *data); +int cs_amp_write_ambient_temp(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + 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_get_vendor_spkid(struct device *dev); +static inline u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) +{ + return ((u64)data->calTarget[1] << 32) | data->calTarget[0]; +} + struct cs_amp_test_hooks { efi_status_t (*get_efi_variable)(efi_char16_t *name, efi_guid_t *guid, diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index 8434d5196107..b2509c5c0690 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -10,9 +10,11 @@ #include <linux/dev_printk.h> #include <linux/efi.h> #include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/overflow.h> #include <linux/slab.h> +#include <linux/timekeeping.h> #include <linux/types.h> #include <sound/cs-amp-lib.h> @@ -46,6 +48,16 @@ static const struct cs_amp_lib_cal_efivar { }, }; +/* Offset from Unix time to Windows time (100ns since 1 Jan 1601) */ +#define UNIX_TIME_TO_WINDOWS_TIME_OFFSET 116444736000000000ULL + +static u64 cs_amp_time_now_in_windows_time(void) +{ + u64 time_in_100ns = div_u64(ktime_get_real_ns(), 100); + + return time_in_100ns + UNIX_TIME_TO_WINDOWS_TIME_OFFSET; +} + static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, const struct cirrus_amp_cal_controls *controls, const char *ctl_name, u32 val) @@ -73,6 +85,34 @@ static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, return -ENODEV; } +static int cs_amp_read_cal_coeff(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + const char *ctl_name, u32 *val) +{ + struct cs_dsp_coeff_ctl *cs_ctl; + __be32 beval; + int ret; + + KUNIT_STATIC_STUB_REDIRECT(cs_amp_read_cal_coeff, dsp, controls, ctl_name, val); + + if (!IS_REACHABLE(CONFIG_FW_CS_DSP)) + return -ENODEV; + + scoped_guard(mutex, &dsp->pwr_lock) { + cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, &beval, sizeof(beval)); + } + + if (ret < 0) { + dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); + return ret; + } + + *val = be32_to_cpu(beval); + + return 0; +} + static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp, const struct cirrus_amp_cal_controls *controls, const struct cirrus_amp_cal_data *data) @@ -106,6 +146,45 @@ static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp, return 0; } +static int _cs_amp_read_cal_coeffs(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + struct cirrus_amp_cal_data *data) +{ + u64 time; + u32 val; + int ret; + + if (list_empty(&dsp->ctl_list)) { + dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n"); + return -ENOENT; + } + + ret = cs_amp_read_cal_coeff(dsp, controls, controls->ambient, &val); + if (ret) + return ret; + + data->calAmbient = (s8)val; + + ret = cs_amp_read_cal_coeff(dsp, controls, controls->calr, &val); + if (ret) + return ret; + + data->calR = (u16)val; + + ret = cs_amp_read_cal_coeff(dsp, controls, controls->status, &val); + if (ret) + return ret; + + data->calStatus = (u8)val; + + /* Fill in timestamp */ + time = cs_amp_time_now_in_windows_time(); + data->calTime[0] = (u32)time; + data->calTime[1] = (u32)(time >> 32); + + return 0; +} + /** * cs_amp_write_cal_coeffs - Write calibration data to firmware controls. * @dsp: Pointer to struct cs_dsp. @@ -125,6 +204,44 @@ int cs_amp_write_cal_coeffs(struct cs_dsp *dsp, } EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, "SND_SOC_CS_AMP_LIB"); +/** + * cs_amp_read_cal_coeffs - Read calibration data from firmware controls. + * @dsp: Pointer to struct cs_dsp. + * @controls: Pointer to definition of firmware controls to be read. + * @data: Pointer to calibration data where results will be written. + * + * Returns: 0 on success, else negative error value. + */ +int cs_amp_read_cal_coeffs(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + struct cirrus_amp_cal_data *data) +{ + if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) + return _cs_amp_read_cal_coeffs(dsp, controls, data); + else + return -ENODEV; +} +EXPORT_SYMBOL_NS_GPL(cs_amp_read_cal_coeffs, "SND_SOC_CS_AMP_LIB"); + +/** + * cs_amp_write_ambient_temp - write value to calibration ambient temperature + * @dsp: Pointer to struct cs_dsp. + * @controls: Pointer to definition of firmware controls to be read. + * @temp: Temperature in degrees celcius. + * + * Returns: 0 on success, else negative error value. + */ +int cs_amp_write_ambient_temp(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + u32 temp) +{ + if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) + return cs_amp_write_cal_coeff(dsp, controls, controls->ambient, temp); + else + return -ENODEV; +} +EXPORT_SYMBOL_NS_GPL(cs_amp_write_ambient_temp, "SND_SOC_CS_AMP_LIB"); + static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, efi_guid_t *guid, unsigned long *size, @@ -215,11 +332,6 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) return ERR_PTR(ret); } -static u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) -{ - return ((u64)data->calTarget[1] << 32) | data->calTarget[0]; -} - static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, struct cirrus_amp_cal_data *out_data) { -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 03/11] ASoC: cs35l56: Add common code for factory calibration 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 01/11] ASoC: cs35l56: Read silicon ID during initialization and save it Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 02/11] ASoC: cs-amp-lib: Add helpers for factory calibration Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 04/11] ASoC: cs35l56: Create sysfs files " Richard Fitzgerald ` (7 subsequent siblings) 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Add core code to support factory calibration. This can be used by both the ASoC and HDA drivers. This code consists of implementations of sysfs handlers for three sysfs files used to start factory calibration and read the results. Sysfs is used here so that access will be restricted. These are mainly intended to be used by manufacturers and in service centres after a repair. It is not something that a normal user should ever touch. Calibration affects the matching of the amp hardware to the external speakers. If not done correctly (either accidentally or maliciously) it can cause the speakers to be under-protected. The PC manufacturer's tunings can limit the allowed range of a calibration result but some speakers have very wide manufacturing tolerances, so the allowed range could be wide. This is not a full implementation of sysfs files. There are some requirements to synchronize with the rest of the amp driver, and the way this is done is significantly different between ASoC and HDA. Therefore cs35l56-shared.c provides the main part of the sysfs file handlers, but the files themselves are defined in the ASoC and HDA drivers with suitable handling before calling into this shared code. The cal_data sysfs allows the calibration to be read and also for a previous calibration to be written (for systems where the storage is not something directly accessible to drivers, such as on filesystems). As a precaution cal_data can only be written if the driver does not already have valid calibration. Code outside the kernel should treat the content of cal_data as an opaque blob, so the struct definition is not exported as a user API. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs35l56.h | 18 +++ sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/cs35l56-shared.c | 253 +++++++++++++++++++++++++++++- 3 files changed, 269 insertions(+), 5 deletions(-) diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index ec9b1072d6be..4ed738615021 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -62,6 +62,8 @@ #define CS35L56_IRQ1_MASK_8 0x000E0AC #define CS35L56_IRQ1_MASK_18 0x000E0D4 #define CS35L56_IRQ1_MASK_20 0x000E0DC +#define CS35L56_MIXER_NGATE_CH1_CFG 0x0010004 +#define CS35L56_MIXER_NGATE_CH2_CFG 0x0010008 #define CS35L56_DSP_MBOX_1_RAW 0x0011000 #define CS35L56_DSP_VIRTUAL1_MBOX_1 0x0011020 #define CS35L56_DSP_VIRTUAL1_MBOX_2 0x0011024 @@ -177,6 +179,9 @@ /* IRQ1_EINT_8 */ #define CS35L56_TEMP_ERR_EINT1_MASK 0x80000000 +/* MIXER_NGATE_CHn_CFG */ +#define CS35L56_AUX_NGATE_CHn_EN 0x00000001 + /* Mixer input sources */ #define CS35L56_INPUT_SRC_NONE 0x00 #define CS35L56_INPUT_SRC_ASP1RX1 0x08 @@ -243,6 +248,7 @@ #define CS35L56_MBOX_CMD_AUDIO_PLAY 0x0B000001 #define CS35L56_MBOX_CMD_AUDIO_PAUSE 0x0B000002 #define CS35L56_MBOX_CMD_AUDIO_REINIT 0x0B000003 +#define CS35L56_MBOX_CMD_AUDIO_CALIBRATION 0x0B000006 #define CS35L56_MBOX_CMD_HIBERNATE_NOW 0x02000001 #define CS35L56_MBOX_CMD_WAKEUP 0x02000002 #define CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE 0x02000003 @@ -264,6 +270,9 @@ #define CS35L56_RESET_PULSE_MIN_US 1100 #define CS35L56_WAKE_HOLD_TIME_US 1000 +#define CS35L56_CALIBRATION_POLL_US (100 * USEC_PER_MSEC) +#define CS35L56_CALIBRATION_TIMEOUT_US (5 * USEC_PER_SEC) + #define CS35L56_SDW1_PLAYBACK_PORT 1 #define CS35L56_SDW1_CAPTURE_PORT 3 @@ -294,6 +303,7 @@ struct cs35l56_fw_reg { struct cs35l56_base { struct device *dev; struct regmap *regmap; + struct cs_dsp *dsp; int irq; struct mutex irq_lock; u8 type; @@ -359,6 +369,14 @@ int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp); int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base); +ssize_t cs35l56_calibrate_sysfs_store(struct cs35l56_base *cs35l56_base, + const char *buf, size_t count); +ssize_t cs35l56_cal_ambient_sysfs_store(struct cs35l56_base *cs35l56_base, + const char *buf, size_t count); +ssize_t cs35l56_cal_data_sysfs_read(struct cs35l56_base *cs35l56_base, + char *buf, loff_t pos, size_t count); +ssize_t cs35l56_cal_data_sysfs_write(struct cs35l56_base *cs35l56_base, + const char *buf, loff_t pos, size_t count); int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base, bool *fw_missing, unsigned int *fw_version); void cs35l56_log_tuning(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 160c07699a8b..5917bf5a72f8 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -896,6 +896,9 @@ config SND_SOC_CS35L56_SDW help Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control +config SND_SOC_CS35L56_CAL_SYSFS_COMMON + bool + config SND_SOC_CS40L50 tristate "Cirrus Logic CS40L50 CODEC" depends on MFD_CS40L50_CORE diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index 1ecfc38d8eb4..dc6e49e3421a 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -6,11 +6,15 @@ // Cirrus Logic International Semiconductor Ltd. #include <linux/array_size.h> +#include <linux/cleanup.h> #include <linux/firmware/cirrus/wmfw.h> #include <linux/gpio/consumer.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> +#include <linux/stddef.h> +#include <linux/string.h> +#include <linux/string_choices.h> #include <linux/types.h> #include <sound/cs-amp-lib.h> @@ -206,6 +210,8 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg) case CS35L56_IRQ1_MASK_8: case CS35L56_IRQ1_MASK_18: case CS35L56_IRQ1_MASK_20: + case CS35L56_MIXER_NGATE_CH1_CFG: + case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: case CS35L56_DSP_VIRTUAL1_MBOX_2: case CS35L56_DSP_VIRTUAL1_MBOX_3: @@ -263,6 +269,8 @@ static bool cs35l56_common_volatile_reg(unsigned int reg) case CS35L56_IRQ1_EINT_1 ... CS35L56_IRQ1_EINT_8: case CS35L56_IRQ1_EINT_18: case CS35L56_IRQ1_EINT_20: + case CS35L56_MIXER_NGATE_CH1_CFG: + case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: case CS35L56_DSP_VIRTUAL1_MBOX_2: case CS35L56_DSP_VIRTUAL1_MBOX_3: @@ -724,15 +732,11 @@ static void cs35l56_issue_wake_event(struct cs35l56_base *cs35l56_base) cs35l56_wait_control_port_ready(); } -int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base) +static int cs35l56_wait_for_ps3(struct cs35l56_base *cs35l56_base) { unsigned int val; int ret; - if (!cs35l56_base->init_done) - return 0; - - /* Firmware must have entered a power-save state */ ret = regmap_read_poll_timeout(cs35l56_base->regmap, cs35l56_base->fw_reg->transducer_actual_ps, val, (val >= CS35L56_PS3), @@ -741,6 +745,17 @@ int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base) if (ret) dev_warn(cs35l56_base->dev, "PS3 wait failed: %d\n", ret); + return ret; +} + +int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base) +{ + if (!cs35l56_base->init_done) + return 0; + + /* Firmware must have entered a power-save state */ + cs35l56_wait_for_ps3(cs35l56_base); + /* Clear BOOT_DONE so it can be used to detect a reboot */ regmap_write(cs35l56_base->regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK); @@ -839,6 +854,8 @@ void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_ds cs_dsp->mem = cs35l56_dsp1_regions; cs_dsp->num_mems = ARRAY_SIZE(cs35l56_dsp1_regions); cs_dsp->no_core_startstop = true; + + cs35l56_base->dsp = cs_dsp; } EXPORT_SYMBOL_NS_GPL(cs35l56_init_cs_dsp, "SND_SOC_CS35L56_SHARED"); @@ -942,6 +959,232 @@ int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base) } EXPORT_SYMBOL_NS_GPL(cs35l56_get_calibration, "SND_SOC_CS35L56_SHARED"); +static int cs35l56_stash_calibration(struct cs35l56_base *cs35l56_base, + const struct cirrus_amp_cal_data *data) +{ + + /* Ignore if it is empty */ + if (!data->calTime[0] && !data->calTime[1]) + return -ENODATA; + + if (cs_amp_cal_target_u64(data) != cs35l56_base->silicon_uid) { + dev_err(cs35l56_base->dev, "cal_data not for this silicon ID\n"); + return -EINVAL; + } + + cs35l56_base->cal_data = *data; + cs35l56_base->cal_data_valid = true; + + return 0; +} + +static int cs35l56_perform_calibration(struct cs35l56_base *cs35l56_base) +{ + const struct cirrus_amp_cal_controls *calibration_controls = + cs35l56_base->calibration_controls; + struct cs_dsp *dsp = cs35l56_base->dsp; + struct cirrus_amp_cal_data cal_data; + struct cs_dsp_coeff_ctl *ctl; + bool ngate_ch1_was_enabled = false; + bool ngate_ch2_was_enabled = false; + int cali_norm_en_alg_id, cali_norm_en_mem; + int ret; + __be32 val; + + if (cs35l56_base->silicon_uid == 0) { + dev_err(cs35l56_base->dev, "Cannot calibrate: no silicon UID\n"); + return -ENXIO; + } + + switch (cs35l56_base->type) { + case 0x54: + case 0x56: + case 0x57: + if (cs35l56_base->rev < 0xb2) { + cali_norm_en_alg_id = 0x9f22f; + cali_norm_en_mem = WMFW_ADSP2_YM; + } else { + cali_norm_en_alg_id = 0x9f210; + cali_norm_en_mem = WMFW_ADSP2_XM; + } + break; + default: + cali_norm_en_alg_id = 0xbf210; + cali_norm_en_mem = WMFW_ADSP2_XM; + break; + } + + ret = pm_runtime_resume_and_get(cs35l56_base->dev); + if (ret) + return ret; + + ret = cs35l56_wait_for_ps3(cs35l56_base); + if (ret) + goto err_pm_put; + + regmap_update_bits_check(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH1_CFG, + CS35L56_AUX_NGATE_CHn_EN, 0, &ngate_ch1_was_enabled); + regmap_update_bits_check(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH2_CFG, + CS35L56_AUX_NGATE_CHn_EN, 0, &ngate_ch2_was_enabled); + + scoped_guard(mutex, &dsp->pwr_lock) { + ctl = cs_dsp_get_ctl(dsp, + calibration_controls->status, + calibration_controls->mem_region, + calibration_controls->alg_id); + if (!ctl) { + dev_err(cs35l56_base->dev, "Could not get %s control\n", + calibration_controls->status); + ret = -ENXIO; + goto err; + } + + val = cpu_to_be32(0); + ret = cs_dsp_coeff_write_ctrl(cs_dsp_get_ctl(dsp, + "CALI_NORM_EN", + cali_norm_en_mem, + cali_norm_en_alg_id), + 0, &val, sizeof(val)); + if (ret < 0) { + dev_err(cs35l56_base->dev, "Could not write %s: %d\n", "CALI_NORM_EN", ret); + goto err; + } + + ret = cs35l56_mbox_send(cs35l56_base, CS35L56_MBOX_CMD_AUDIO_CALIBRATION); + if (ret) + goto err; + + if (read_poll_timeout(cs_dsp_coeff_read_ctrl, ret, + (val == cpu_to_be32(1)), + CS35L56_CALIBRATION_POLL_US, + CS35L56_CALIBRATION_TIMEOUT_US, + true, + ctl, 0, &val, sizeof(val))) { + dev_err(cs35l56_base->dev, "Calibration timed out (CAL_STATUS: %u)\n", + be32_to_cpu(val)); + ret = -ETIMEDOUT; + goto err; + } + } + + cs35l56_base->cal_data_valid = false; + memset(&cal_data, 0, sizeof(cal_data)); + ret = cs_amp_read_cal_coeffs(dsp, calibration_controls, &cal_data); + if (ret) + goto err; + + dev_info(cs35l56_base->dev, "Cal status:%d calR:%d ambient:%d\n", + cal_data.calStatus, cal_data.calR, cal_data.calAmbient); + + cal_data.calTarget[0] = (u32)cs35l56_base->silicon_uid; + cal_data.calTarget[1] = (u32)(cs35l56_base->silicon_uid >> 32); + cs35l56_base->cal_data = cal_data; + cs35l56_base->cal_data_valid = true; + + ret = 0; + +err: + if (ngate_ch1_was_enabled) { + regmap_set_bits(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH1_CFG, + CS35L56_AUX_NGATE_CHn_EN); + } + if (ngate_ch2_was_enabled) { + regmap_set_bits(cs35l56_base->regmap, CS35L56_MIXER_NGATE_CH2_CFG, + CS35L56_AUX_NGATE_CHn_EN); + } +err_pm_put: + pm_runtime_put(cs35l56_base->dev); + + return ret; +} + +ssize_t cs35l56_calibrate_sysfs_store(struct cs35l56_base *cs35l56_base, + const char *buf, size_t count) +{ + static const char * const options[] = { "factory" }; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS_COMMON)) + return -ENXIO; + + switch (sysfs_match_string(options, buf)) { + case 0: + ret = cs35l56_perform_calibration(cs35l56_base); + if (ret < 0) + return ret; + break; + default: + return -ENXIO; + } + + return count; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_calibrate_sysfs_store, "SND_SOC_CS35L56_SHARED"); + +ssize_t cs35l56_cal_ambient_sysfs_store(struct cs35l56_base *cs35l56_base, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS_COMMON)) + return -ENXIO; + + ret = pm_runtime_resume_and_get(cs35l56_base->dev); + if (ret) + return ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) { + ret = -ENXIO; + goto out; + } + + ret = cs_amp_write_ambient_temp(cs35l56_base->dsp, cs35l56_base->calibration_controls, val); +out: + pm_runtime_put(cs35l56_base->dev); + + if (ret < 0) + return ret; + + return count; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_cal_ambient_sysfs_store, "SND_SOC_CS35L56_SHARED"); + +ssize_t cs35l56_cal_data_sysfs_read(struct cs35l56_base *cs35l56_base, + char *buf, loff_t pos, size_t count) +{ + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS_COMMON)) + return -ENXIO; + + if (!cs35l56_base->cal_data_valid) + return 0; + + return memory_read_from_buffer(buf, count, &pos, &cs35l56_base->cal_data, + sizeof(cs35l56_base->cal_data)); +} +EXPORT_SYMBOL_NS_GPL(cs35l56_cal_data_sysfs_read, "SND_SOC_CS35L56_SHARED"); + +ssize_t cs35l56_cal_data_sysfs_write(struct cs35l56_base *cs35l56_base, + const char *buf, loff_t pos, size_t count) +{ + const struct cirrus_amp_cal_data *cal_data = (const void *)buf; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS_COMMON)) + return -ENXIO; + + if (count != sizeof(*cal_data)) + return -EMSGSIZE; + + ret = cs35l56_stash_calibration(cs35l56_base, cal_data); + if (ret) + return ret; + + return count; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_cal_data_sysfs_write, "SND_SOC_CS35L56_SHARED"); + int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base, bool *fw_missing, unsigned int *fw_version) { -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 04/11] ASoC: cs35l56: Create sysfs files for factory calibration 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (2 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 03/11] ASoC: cs35l56: Add common code " Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 05/11] ALSA: hda/cs35l56: " Richard Fitzgerald ` (6 subsequent siblings) 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Create sysfs files that can be used to perform factory calibration. During manufacture, the production line must perform a factory calibration of the amps. This patch adds this functionality via sysfs files. Sysfs is used here to restrict access to the factory calibration. It is only intended to be used during manufacture. It is not something that a normal user should ever touch. Calibration affects the matching of the amp hardware to the external speakers. If not done correctly it can cause the speakers to be under-protected. As this is only needed during manufacture, there is no need for this to be available in a normal system so a Kconfig item has been added to enable this. The new Kconfig option is inside a sub-menu because items do not group and indent if the parent is invisible or there are multiple parent dependencies. Anyway the sub-menu reduces the clutter. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- sound/soc/codecs/Kconfig | 15 +++ sound/soc/codecs/cs35l56.c | 182 +++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5917bf5a72f8..4bb57223ef82 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -899,6 +899,21 @@ config SND_SOC_CS35L56_SDW config SND_SOC_CS35L56_CAL_SYSFS_COMMON bool +menu "CS35L56 driver options" + depends on SND_SOC_CS35L56 + +config SND_SOC_CS35L56_CAL_SYSFS + bool "CS35L56 create sysfs for factory calibration" + default N + select SND_SOC_CS35L56_CAL_SYSFS_COMMON + help + Create sysfs entries used during factory-line manufacture + for factory calibration. + This is not needed for normal use. + + If unsure select "N". +endmenu + config SND_SOC_CS40L50 tristate "Cirrus Logic CS40L50 CODEC" depends on MFD_CS40L50_CORE diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 2c1edbd636ef..930991dd4b6f 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -10,6 +10,7 @@ #include <linux/completion.h> #include <linux/debugfs.h> #include <linux/delay.h> +#include <linux/device.h> #include <linux/err.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> @@ -22,6 +23,7 @@ #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/soundwire/sdw.h> +#include <linux/sysfs.h> #include <linux/types.h> #include <linux/workqueue.h> #include <sound/cs-amp-lib.h> @@ -250,6 +252,8 @@ static const struct snd_soc_dapm_widget cs35l56_dapm_widgets[] = { SND_SOC_DAPM_SIGGEN("VDDBMON ADC"), SND_SOC_DAPM_SIGGEN("VBSTMON ADC"), SND_SOC_DAPM_SIGGEN("TEMPMON ADC"), + + SND_SOC_DAPM_INPUT("Calibrate"), }; #define CS35L56_SRC_ROUTE(name) \ @@ -286,6 +290,7 @@ static const struct snd_soc_dapm_route cs35l56_audio_map[] = { { "DSP1", NULL, "ASP1RX1" }, { "DSP1", NULL, "ASP1RX2" }, { "DSP1", NULL, "SDW1 Playback" }, + { "DSP1", NULL, "Calibrate" }, { "AMP", NULL, "DSP1" }, { "SPK", NULL, "AMP" }, @@ -874,6 +879,175 @@ static void cs35l56_dsp_work(struct work_struct *work) pm_runtime_put_autosuspend(cs35l56->base.dev); } +static struct snd_soc_dapm_context *cs35l56_power_up_for_cal(struct cs35l56_private *cs35l56) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l56->component); + int ret; + + ret = snd_soc_component_enable_pin(cs35l56->component, "Calibrate"); + if (ret) + return ERR_PTR(ret); + + snd_soc_dapm_sync(dapm); + + return dapm; +} + +static void cs35l56_power_down_after_cal(struct cs35l56_private *cs35l56) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l56->component); + + snd_soc_component_disable_pin(cs35l56->component, "Calibrate"); + snd_soc_dapm_sync(dapm); +} + +static ssize_t calibrate_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); + struct snd_soc_dapm_context *dapm; + ssize_t ret; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + snd_soc_dapm_mutex_lock(dapm); + ret = cs35l56_calibrate_sysfs_store(&cs35l56->base, buf, count); + snd_soc_dapm_mutex_unlock(dapm); + + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static ssize_t cal_temperature_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); + struct snd_soc_dapm_context *dapm; + ssize_t ret; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + ret = cs35l56_cal_ambient_sysfs_store(&cs35l56->base, buf, count); + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static ssize_t cal_data_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *battr, char *buf, loff_t pos, + size_t count) +{ + struct cs35l56_private *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj)); + struct snd_soc_dapm_context *dapm; + ssize_t ret; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + ret = cs35l56_cal_data_sysfs_read(&cs35l56->base, buf, pos, count); + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static int cs35l56_new_cal_data_apply(struct cs35l56_private *cs35l56) +{ + struct snd_soc_dapm_context *dapm; + int ret; + + if (!cs35l56->base.cal_data_valid) + return -ENXIO; + + if (cs35l56->base.secured) + return -EACCES; + + dapm = cs35l56_power_up_for_cal(cs35l56); + if (IS_ERR(dapm)) + return PTR_ERR(dapm); + + snd_soc_dapm_mutex_lock(dapm); + ret = cs_amp_write_cal_coeffs(&cs35l56->dsp.cs_dsp, + cs35l56->base.calibration_controls, + &cs35l56->base.cal_data); + if (ret == 0) + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); + else + ret = -EIO; + + snd_soc_dapm_mutex_unlock(dapm); + cs35l56_power_down_after_cal(cs35l56); + + return ret; +} + +static ssize_t cal_data_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *battr, char *buf, loff_t pos, + size_t count) +{ + struct cs35l56_private *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj)); + int ret; + + ret = cs35l56_cal_data_sysfs_write(&cs35l56->base, buf, pos, count); + if (ret == -ENODATA) + return count; /* Ignore writes of empty cal blobs */ + else if (ret < 0) + return -EIO; + + ret = cs35l56_new_cal_data_apply(cs35l56); + if (ret) + return ret; + + return count; +} + +static const DEVICE_ATTR_WO(calibrate); +static const DEVICE_ATTR_WO(cal_temperature); +static const BIN_ATTR_RW(cal_data, sizeof_field(struct cs35l56_base, cal_data)); + +static const struct attribute *cs35l56_cal_attributes[] = { + &dev_attr_calibrate.attr, + &dev_attr_cal_temperature.attr, + NULL +}; + +static void cs35l56_create_calibration_sysfs(struct cs35l56_private *cs35l56) +{ + struct device *dev = cs35l56->base.dev; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS)) + return; + + ret = sysfs_create_files(&dev->kobj, cs35l56_cal_attributes); + if (ret) + goto err; + + ret = sysfs_create_bin_file(&dev->kobj, &bin_attr_cal_data); + if (ret) + goto err; + + return; +err: + dev_err_probe(dev, ret, "Failed creating calibration sysfs\n"); +} + +static void cs35l56_remove_calibration_sysfs(struct cs35l56_private *cs35l56) +{ + struct device *dev = cs35l56->base.dev; + + if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS)) + return; + + sysfs_remove_files(&dev->kobj, cs35l56_cal_attributes); + sysfs_remove_bin_file(&dev->kobj, &bin_attr_cal_data); +} + static int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56) { if (cs35l56->dsp.fwf_suffix) @@ -971,6 +1145,12 @@ static int cs35l56_component_probe(struct snd_soc_component *component) if (ret) return dev_err_probe(cs35l56->base.dev, ret, "unable to add controls\n"); + ret = snd_soc_component_disable_pin(component, "Calibrate"); + if (ret) + return ret; + + cs35l56_create_calibration_sysfs(cs35l56); + queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work); return 0; @@ -982,6 +1162,8 @@ static void cs35l56_component_remove(struct snd_soc_component *component) cancel_work_sync(&cs35l56->dsp_work); + cs35l56_remove_calibration_sysfs(cs35l56); + if (cs35l56->dsp.cs_dsp.booted) wm_adsp_power_down(&cs35l56->dsp); -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 05/11] ALSA: hda/cs35l56: Create sysfs files for factory calibration 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (3 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 04/11] ASoC: cs35l56: Create sysfs files " Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 11:01 ` Takashi Iwai 2025-10-16 10:42 ` [PATCH 06/11] ASoC: cs-amp-lib-test: Add cases for factory calibration helpers Richard Fitzgerald ` (5 subsequent siblings) 10 siblings, 1 reply; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Create sysfs files that can be used to perform factory calibration. During manufacture, the production line must perform a factory calibration of the amps. This patch adds this functionality via sysfs files. Sysfs is used here to restrict access to the factory calibration. It is only intended to be used during manufacture. It is not something that a normal user should ever touch. Calibration affects the matching of the amp hardware to the external speakers. If not done correctly it can cause the speakers to be under-protected. As this is only needed during manufacture, there is no need for this to be available in a normal system so a Kconfig item has been added to enable this. The new Kconfig option is inside a sub-menu because items do not group and indent if the parent is invisible or there are multiple parent dependencies. Anyway the sub-menu reduces the clutter. cs35l56_hda_apply_calibration() has been changed to return an error code that can be reported back through the sysfs write. The original call to this function doesn't check the return code because in normal use it doesn't matter whether this fails - the firmware will default to a safe calibration for the platform. But tooling using sysfs might want to know if there was an error. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- sound/hda/codecs/side-codecs/Kconfig | 15 +++ sound/hda/codecs/side-codecs/cs35l56_hda.c | 139 ++++++++++++++++++++- 2 files changed, 149 insertions(+), 5 deletions(-) diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index cbf1847896bc..0218aa41bba2 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -88,6 +88,21 @@ config SND_HDA_SCODEC_CS35L56_SPI Say Y or M here to include CS35L56 amplifier support with SPI control. +menu "CS35L56 driver options" + depends on SND_HDA_SCODEC_CS35L56 + +config SND_HDA_SCODEC_CS35L56_CAL_SYSFS + bool "CS35L56 create sysfs for factory calibration" + default N + select SND_SOC_CS35L56_CAL_SYSFS_COMMON + help + Create sysfs entries used during factory-line manufacture + for factory calibration. + This is not needed for normal use. + + If unsure select "N". +endmenu + config SND_HDA_SCODEC_TAS2781 tristate select SND_HDA_GENERIC diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c index 5bb1c4ebeaf3..4a1bd934887a 100644 --- a/sound/hda/codecs/side-codecs/cs35l56_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c @@ -548,20 +548,24 @@ static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmw kfree(coeff_filename); } -static void cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) +static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) { int ret; if (!cs35l56->base.cal_data_valid || cs35l56->base.secured) - return; + return -EACCES; ret = cs_amp_write_cal_coeffs(&cs35l56->cs_dsp, &cs35l56_calibration_controls, &cs35l56->base.cal_data); - if (ret < 0) + if (ret < 0) { dev_warn(cs35l56->base.dev, "Failed to write calibration: %d\n", ret); - else - dev_info(cs35l56->base.dev, "Calibration applied\n"); + return ret; + } + + dev_info(cs35l56->base.dev, "Calibration applied\n"); + + return 0; } static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) @@ -669,7 +673,9 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) if (ret) dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret); + /* Don't need to check return code, it's not fatal if this fails */ cs35l56_hda_apply_calibration(cs35l56); + ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); if (ret) cs_dsp_stop(&cs35l56->cs_dsp); @@ -695,6 +701,126 @@ static void cs35l56_hda_dsp_work(struct work_struct *work) cs35l56_hda_fw_load(cs35l56); } +static ssize_t calibrate_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); + ssize_t ret; + + ret = pm_runtime_resume_and_get(cs35l56->base.dev); + if (ret) + return ret; + + ret = cs35l56_calibrate_sysfs_store(&cs35l56->base, buf, count); + pm_runtime_autosuspend(cs35l56->base.dev); + + return ret; +} + +static ssize_t cal_temperature_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); + ssize_t ret; + + ret = pm_runtime_resume_and_get(cs35l56->base.dev); + if (ret) + return ret; + + ret = cs35l56_cal_ambient_sysfs_store(&cs35l56->base, buf, count); + pm_runtime_autosuspend(cs35l56->base.dev); + + return ret; +} + +static ssize_t cal_data_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *battr, char *buf, loff_t pos, + size_t count) +{ + struct cs35l56_hda *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj)); + ssize_t ret; + + ret = pm_runtime_resume_and_get(cs35l56->base.dev); + if (ret) + return ret; + + ret = cs35l56_cal_data_sysfs_read(&cs35l56->base, buf, pos, count); + pm_runtime_autosuspend(cs35l56->base.dev); + + return ret; +} + +static ssize_t cal_data_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *battr, char *buf, loff_t pos, + size_t count) +{ + struct cs35l56_hda *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj)); + ssize_t ret; + + ret = cs35l56_cal_data_sysfs_write(&cs35l56->base, buf, pos, count); + if (ret == -ENODATA) + return count; /* Ignore writes of empty cal blobs */ + + if (ret < 0) + return ret; + + ret = pm_runtime_resume_and_get(cs35l56->base.dev); + if (ret) + return ret; + + ret = cs35l56_hda_apply_calibration(cs35l56); + if (ret == 0) + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); + else + count = -EIO; + + pm_runtime_autosuspend(cs35l56->base.dev); + + return count; +} + +static const DEVICE_ATTR_WO(calibrate); +static const DEVICE_ATTR_WO(cal_temperature); +static const BIN_ATTR_RW(cal_data, sizeof_field(struct cs35l56_base, cal_data)); + +static const struct attribute *cs35l56_hda_cal_attributes[] = { + &dev_attr_calibrate.attr, + &dev_attr_cal_temperature.attr, + NULL +}; + +static void cs35l56_hda_create_calibration_sysfs(struct cs35l56_hda *cs35l56) +{ + struct device *dev = cs35l56->base.dev; + int ret; + + if (!IS_ENABLED(CONFIG_SND_HDA_SCODEC_CS35L56_CAL_SYSFS)) + return; + + ret = sysfs_create_files(&dev->kobj, cs35l56_hda_cal_attributes); + if (ret) + goto err; + + ret = sysfs_create_bin_file(&dev->kobj, &bin_attr_cal_data); + if (ret) + goto err; + + return; +err: + dev_err_probe(dev, ret, "Failed creating calibration sysfs\n"); +} + +static void cs35l56_hda_remove_calibration_sysfs(struct cs35l56_hda *cs35l56) +{ + struct device *dev = cs35l56->base.dev; + + if (!IS_ENABLED(CONFIG_SND_HDA_SCODEC_CS35L56_CAL_SYSFS)) + return; + + sysfs_remove_files(&dev->kobj, cs35l56_hda_cal_attributes); + sysfs_remove_bin_file(&dev->kobj, &bin_attr_cal_data); +} + static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data) { struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); @@ -722,6 +848,8 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas cs_dsp_init_debugfs(&cs35l56->cs_dsp, cs35l56->debugfs_root); #endif + cs35l56_hda_create_calibration_sysfs(cs35l56); + dev_dbg(cs35l56->base.dev, "Bound\n"); return 0; @@ -736,6 +864,7 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void * cancel_work_sync(&cs35l56->dsp_work); cs35l56_hda_remove_controls(cs35l56); + cs35l56_hda_remove_calibration_sysfs(cs35l56); #if IS_ENABLED(CONFIG_SND_DEBUG) cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp); -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 05/11] ALSA: hda/cs35l56: Create sysfs files for factory calibration 2025-10-16 10:42 ` [PATCH 05/11] ALSA: hda/cs35l56: " Richard Fitzgerald @ 2025-10-16 11:01 ` Takashi Iwai 2025-10-16 11:27 ` Mark Brown 0 siblings, 1 reply; 17+ messages in thread From: Takashi Iwai @ 2025-10-16 11:01 UTC (permalink / raw) To: Richard Fitzgerald; +Cc: broonie, tiwai, linux-sound, linux-kernel, patches On Thu, 16 Oct 2025 12:42:36 +0200, Richard Fitzgerald wrote: > > Create sysfs files that can be used to perform factory calibration. > > During manufacture, the production line must perform a factory calibration > of the amps. This patch adds this functionality via sysfs files. > > Sysfs is used here to restrict access to the factory calibration. > It is only intended to be used during manufacture. It is not something > that a normal user should ever touch. Calibration affects the matching of > the amp hardware to the external speakers. If not done correctly it can > cause the speakers to be under-protected. > > As this is only needed during manufacture, there is no need for this to be > available in a normal system so a Kconfig item has been added to enable > this. The new Kconfig option is inside a sub-menu because items do not > group and indent if the parent is invisible or there are multiple parent > dependencies. Anyway the sub-menu reduces the clutter. > > cs35l56_hda_apply_calibration() has been changed to return an error code > that can be reported back through the sysfs write. The original call to > this function doesn't check the return code because in normal use it > doesn't matter whether this fails - the firmware will default to a safe > calibration for the platform. But tooling using sysfs might want to know > if there was an error. > > Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> What kind of data format should be written to calibrate sysfs entry? Since those are no trivial files, we may need to document the formats. Also, the scenario isn't clear -- who should write this sysfs at which moment? e.g. is this supposed to be set up at each boot? thanks, Takashi > --- > sound/hda/codecs/side-codecs/Kconfig | 15 +++ > sound/hda/codecs/side-codecs/cs35l56_hda.c | 139 ++++++++++++++++++++- > 2 files changed, 149 insertions(+), 5 deletions(-) > > diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig > index cbf1847896bc..0218aa41bba2 100644 > --- a/sound/hda/codecs/side-codecs/Kconfig > +++ b/sound/hda/codecs/side-codecs/Kconfig > @@ -88,6 +88,21 @@ config SND_HDA_SCODEC_CS35L56_SPI > Say Y or M here to include CS35L56 amplifier support with > SPI control. > > +menu "CS35L56 driver options" > + depends on SND_HDA_SCODEC_CS35L56 > + > +config SND_HDA_SCODEC_CS35L56_CAL_SYSFS > + bool "CS35L56 create sysfs for factory calibration" > + default N > + select SND_SOC_CS35L56_CAL_SYSFS_COMMON > + help > + Create sysfs entries used during factory-line manufacture > + for factory calibration. > + This is not needed for normal use. > + > + If unsure select "N". > +endmenu > + > config SND_HDA_SCODEC_TAS2781 > tristate > select SND_HDA_GENERIC > diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c > index 5bb1c4ebeaf3..4a1bd934887a 100644 > --- a/sound/hda/codecs/side-codecs/cs35l56_hda.c > +++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c > @@ -548,20 +548,24 @@ static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmw > kfree(coeff_filename); > } > > -static void cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) > +static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) > { > int ret; > > if (!cs35l56->base.cal_data_valid || cs35l56->base.secured) > - return; > + return -EACCES; > > ret = cs_amp_write_cal_coeffs(&cs35l56->cs_dsp, > &cs35l56_calibration_controls, > &cs35l56->base.cal_data); > - if (ret < 0) > + if (ret < 0) { > dev_warn(cs35l56->base.dev, "Failed to write calibration: %d\n", ret); > - else > - dev_info(cs35l56->base.dev, "Calibration applied\n"); > + return ret; > + } > + > + dev_info(cs35l56->base.dev, "Calibration applied\n"); > + > + return 0; > } > > static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) > @@ -669,7 +673,9 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) > if (ret) > dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret); > > + /* Don't need to check return code, it's not fatal if this fails */ > cs35l56_hda_apply_calibration(cs35l56); > + > ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); > if (ret) > cs_dsp_stop(&cs35l56->cs_dsp); > @@ -695,6 +701,126 @@ static void cs35l56_hda_dsp_work(struct work_struct *work) > cs35l56_hda_fw_load(cs35l56); > } > > +static ssize_t calibrate_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); > + ssize_t ret; > + > + ret = pm_runtime_resume_and_get(cs35l56->base.dev); > + if (ret) > + return ret; > + > + ret = cs35l56_calibrate_sysfs_store(&cs35l56->base, buf, count); > + pm_runtime_autosuspend(cs35l56->base.dev); > + > + return ret; > +} > + > +static ssize_t cal_temperature_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); > + ssize_t ret; > + > + ret = pm_runtime_resume_and_get(cs35l56->base.dev); > + if (ret) > + return ret; > + > + ret = cs35l56_cal_ambient_sysfs_store(&cs35l56->base, buf, count); > + pm_runtime_autosuspend(cs35l56->base.dev); > + > + return ret; > +} > + > +static ssize_t cal_data_read(struct file *filp, struct kobject *kobj, > + const struct bin_attribute *battr, char *buf, loff_t pos, > + size_t count) > +{ > + struct cs35l56_hda *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj)); > + ssize_t ret; > + > + ret = pm_runtime_resume_and_get(cs35l56->base.dev); > + if (ret) > + return ret; > + > + ret = cs35l56_cal_data_sysfs_read(&cs35l56->base, buf, pos, count); > + pm_runtime_autosuspend(cs35l56->base.dev); > + > + return ret; > +} > + > +static ssize_t cal_data_write(struct file *filp, struct kobject *kobj, > + const struct bin_attribute *battr, char *buf, loff_t pos, > + size_t count) > +{ > + struct cs35l56_hda *cs35l56 = dev_get_drvdata(kobj_to_dev(kobj)); > + ssize_t ret; > + > + ret = cs35l56_cal_data_sysfs_write(&cs35l56->base, buf, pos, count); > + if (ret == -ENODATA) > + return count; /* Ignore writes of empty cal blobs */ > + > + if (ret < 0) > + return ret; > + > + ret = pm_runtime_resume_and_get(cs35l56->base.dev); > + if (ret) > + return ret; > + > + ret = cs35l56_hda_apply_calibration(cs35l56); > + if (ret == 0) > + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); > + else > + count = -EIO; > + > + pm_runtime_autosuspend(cs35l56->base.dev); > + > + return count; > +} > + > +static const DEVICE_ATTR_WO(calibrate); > +static const DEVICE_ATTR_WO(cal_temperature); > +static const BIN_ATTR_RW(cal_data, sizeof_field(struct cs35l56_base, cal_data)); > + > +static const struct attribute *cs35l56_hda_cal_attributes[] = { > + &dev_attr_calibrate.attr, > + &dev_attr_cal_temperature.attr, > + NULL > +}; > + > +static void cs35l56_hda_create_calibration_sysfs(struct cs35l56_hda *cs35l56) > +{ > + struct device *dev = cs35l56->base.dev; > + int ret; > + > + if (!IS_ENABLED(CONFIG_SND_HDA_SCODEC_CS35L56_CAL_SYSFS)) > + return; > + > + ret = sysfs_create_files(&dev->kobj, cs35l56_hda_cal_attributes); > + if (ret) > + goto err; > + > + ret = sysfs_create_bin_file(&dev->kobj, &bin_attr_cal_data); > + if (ret) > + goto err; > + > + return; > +err: > + dev_err_probe(dev, ret, "Failed creating calibration sysfs\n"); > +} > + > +static void cs35l56_hda_remove_calibration_sysfs(struct cs35l56_hda *cs35l56) > +{ > + struct device *dev = cs35l56->base.dev; > + > + if (!IS_ENABLED(CONFIG_SND_HDA_SCODEC_CS35L56_CAL_SYSFS)) > + return; > + > + sysfs_remove_files(&dev->kobj, cs35l56_hda_cal_attributes); > + sysfs_remove_bin_file(&dev->kobj, &bin_attr_cal_data); > +} > + > static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data) > { > struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); > @@ -722,6 +848,8 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas > cs_dsp_init_debugfs(&cs35l56->cs_dsp, cs35l56->debugfs_root); > #endif > > + cs35l56_hda_create_calibration_sysfs(cs35l56); > + > dev_dbg(cs35l56->base.dev, "Bound\n"); > > return 0; > @@ -736,6 +864,7 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void * > cancel_work_sync(&cs35l56->dsp_work); > > cs35l56_hda_remove_controls(cs35l56); > + cs35l56_hda_remove_calibration_sysfs(cs35l56); > > #if IS_ENABLED(CONFIG_SND_DEBUG) > cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp); > -- > 2.47.3 > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 05/11] ALSA: hda/cs35l56: Create sysfs files for factory calibration 2025-10-16 11:01 ` Takashi Iwai @ 2025-10-16 11:27 ` Mark Brown 2025-10-16 11:58 ` Richard Fitzgerald 0 siblings, 1 reply; 17+ messages in thread From: Mark Brown @ 2025-10-16 11:27 UTC (permalink / raw) To: Takashi Iwai Cc: Richard Fitzgerald, tiwai, linux-sound, linux-kernel, patches [-- Attachment #1: Type: text/plain, Size: 683 bytes --] On Thu, Oct 16, 2025 at 01:01:41PM +0200, Takashi Iwai wrote: > Richard Fitzgerald wrote: > > Create sysfs files that can be used to perform factory calibration. > What kind of data format should be written to calibrate sysfs entry? > Since those are no trivial files, we may need to document the > formats. This feels like it might be a better fit for debugfs or possibly configfs? It's not really within the sysfs rules, and especially debugfs is a lot more relaxed about everything. > Also, the scenario isn't clear -- who should write this sysfs at which > moment? e.g. is this supposed to be set up at each boot? AIUI it's some tooling that gets run in the factory only. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 05/11] ALSA: hda/cs35l56: Create sysfs files for factory calibration 2025-10-16 11:27 ` Mark Brown @ 2025-10-16 11:58 ` Richard Fitzgerald 2025-10-16 12:13 ` Mark Brown 0 siblings, 1 reply; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 11:58 UTC (permalink / raw) To: Mark Brown, Takashi Iwai; +Cc: tiwai, linux-sound, linux-kernel, patches On 16/10/2025 12:27 pm, Mark Brown wrote: > On Thu, Oct 16, 2025 at 01:01:41PM +0200, Takashi Iwai wrote: >> Richard Fitzgerald wrote: > >>> Create sysfs files that can be used to perform factory calibration. > >> What kind of data format should be written to calibrate sysfs entry? >> Since those are no trivial files, we may need to document the >> formats. > > This feels like it might be a better fit for debugfs or possibly > configfs? It's not really within the sysfs rules, and especially > debugfs is a lot more relaxed about everything. > debugfs is an easy change in the driver but more complex for the tooling. Unlike sysfs there's no standard layout or naming convention for the ASoC debugfs tree, so it's more troublesome to locate the amp entries. ASoC creates a debugfs root by default if DEBUGFS is enabled. But HDA doesn't have its own debugfs. There is an ALSA debugfs enabled by CONFIG_SND_DEBUG, which is normally disabled. But enabling CONFIG_SND_DEBUG has other side-effects, it enables more than debugfs. Or we could create a new debugfs node specifically for Cirrus Logic directly in /sys/kernel/debug. I'll have a look at configfs. Although it works backwards (userside must create the directory in which attributes will appear) it might have some benefits. ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 05/11] ALSA: hda/cs35l56: Create sysfs files for factory calibration 2025-10-16 11:58 ` Richard Fitzgerald @ 2025-10-16 12:13 ` Mark Brown 2025-10-16 12:45 ` Takashi Iwai 0 siblings, 1 reply; 17+ messages in thread From: Mark Brown @ 2025-10-16 12:13 UTC (permalink / raw) To: Richard Fitzgerald Cc: Takashi Iwai, tiwai, linux-sound, linux-kernel, patches [-- Attachment #1: Type: text/plain, Size: 1019 bytes --] On Thu, Oct 16, 2025 at 12:58:15PM +0100, Richard Fitzgerald wrote: > On 16/10/2025 12:27 pm, Mark Brown wrote: > > This feels like it might be a better fit for debugfs or possibly > > configfs? It's not really within the sysfs rules, and especially > > debugfs is a lot more relaxed about everything. > debugfs is an easy change in the driver but more complex for the > tooling. Unlike sysfs there's no standard layout or naming convention > for the ASoC debugfs tree, so it's more troublesome to locate the amp > entries. > ASoC creates a debugfs root by default if DEBUGFS is enabled. But HDA > doesn't have its own debugfs. There is an ALSA debugfs enabled by > CONFIG_SND_DEBUG, which is normally disabled. But enabling > CONFIG_SND_DEBUG has other side-effects, it enables more than debugfs. > Or we could create a new debugfs node specifically for Cirrus Logic > directly in /sys/kernel/debug. Dunno about Takashi but for this application I'd be happy for you to just put something in the root of debugfs. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 05/11] ALSA: hda/cs35l56: Create sysfs files for factory calibration 2025-10-16 12:13 ` Mark Brown @ 2025-10-16 12:45 ` Takashi Iwai 0 siblings, 0 replies; 17+ messages in thread From: Takashi Iwai @ 2025-10-16 12:45 UTC (permalink / raw) To: Mark Brown Cc: Richard Fitzgerald, Takashi Iwai, tiwai, linux-sound, linux-kernel, patches On Thu, 16 Oct 2025 14:13:16 +0200, Mark Brown wrote: > > On Thu, Oct 16, 2025 at 12:58:15PM +0100, Richard Fitzgerald wrote: > > On 16/10/2025 12:27 pm, Mark Brown wrote: > > > > This feels like it might be a better fit for debugfs or possibly > > > configfs? It's not really within the sysfs rules, and especially > > > debugfs is a lot more relaxed about everything. > > > debugfs is an easy change in the driver but more complex for the > > tooling. Unlike sysfs there's no standard layout or naming convention > > for the ASoC debugfs tree, so it's more troublesome to locate the amp > > entries. > > > ASoC creates a debugfs root by default if DEBUGFS is enabled. But HDA > > doesn't have its own debugfs. There is an ALSA debugfs enabled by > > CONFIG_SND_DEBUG, which is normally disabled. But enabling > > CONFIG_SND_DEBUG has other side-effects, it enables more than debugfs. > > > Or we could create a new debugfs node specifically for Cirrus Logic > > directly in /sys/kernel/debug. > > Dunno about Takashi but for this application I'd be happy for you to > just put something in the root of debugfs. I'm fine with debugfs root. The current approach with sysfs looks too generic for the need of this access, so I find debugfs would be a better fit, too. thanks, Takashi ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 06/11] ASoC: cs-amp-lib-test: Add cases for factory calibration helpers 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (4 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 05/11] ALSA: hda/cs35l56: " Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 07/11] ASoC: cs-amp-lib: Return attributes from cs_amp_get_efi_variable() Richard Fitzgerald ` (4 subsequent siblings) 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Add test cases for the cs_amp_read_cal_coeffs() and cs_amp_write_ambient_temp() functions. In both cases the test is simply to confirm that the correct data value(s) get passed back to the caller. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs-amp-lib.h | 5 +- sound/soc/codecs/cs-amp-lib-test.c | 75 +++++++++++++++++++++++++++++- sound/soc/codecs/cs-amp-lib.c | 1 + 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h index e4df0736039a..485daadd13a1 100644 --- a/include/sound/cs-amp-lib.h +++ b/include/sound/cs-amp-lib.h @@ -71,8 +71,11 @@ struct cs_amp_test_hooks { int (*write_cal_coeff)(struct cs_dsp *dsp, const struct cirrus_amp_cal_controls *controls, const char *ctl_name, u32 val); -}; + int (*read_cal_coeff)(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + const char *ctl_name, u32 *val); +}; extern const struct cs_amp_test_hooks * const cs_amp_test_hooks; #endif /* CS_AMP_LIB_H */ diff --git a/sound/soc/codecs/cs-amp-lib-test.c b/sound/soc/codecs/cs-amp-lib-test.c index 3406887cdfa2..b19aaacd7f2b 100644 --- a/sound/soc/codecs/cs-amp-lib-test.c +++ b/sound/soc/codecs/cs-amp-lib-test.c @@ -702,6 +702,77 @@ static void cs_amp_lib_test_write_cal_data_test(struct kunit *test) KUNIT_EXPECT_EQ(test, entry->value, data.calStatus); } +static int cs_amp_lib_test_read_cal_coeff(struct cs_dsp *dsp, + const struct cirrus_amp_cal_controls *controls, + const char *ctl_name, u32 *val) +{ + struct kunit *test = kunit_get_current_test(); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctl_name); + KUNIT_EXPECT_PTR_EQ(test, controls, &cs_amp_lib_test_calibration_controls); + + if (strcmp(ctl_name, controls->ambient) == 0) + *val = 19; + else if (strcmp(ctl_name, controls->calr) == 0) + *val = 1077; + else if (strcmp(ctl_name, controls->status) == 0) + *val = 2; + else + kunit_fail_current_test("Bad control '%s'\n", ctl_name); + + return 0; +} + +static void cs_amp_lib_test_read_cal_data_test(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct cirrus_amp_cal_data data = { 0 }; + struct cs_dsp *dsp; + int ret; + + dsp = kunit_kzalloc(test, sizeof(*dsp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dsp); + dsp->dev = &priv->amp_dev->dev; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->read_cal_coeff, + cs_amp_lib_test_read_cal_coeff); + + ret = cs_amp_read_cal_coeffs(dsp, &cs_amp_lib_test_calibration_controls, &data); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, 19, data.calAmbient); + KUNIT_EXPECT_EQ(test, 1077, data.calR); + KUNIT_EXPECT_EQ(test, 2, data.calStatus); + KUNIT_EXPECT_NE(test, 0, data.calTime[0] | data.calTime[1]); +} + +static void cs_amp_lib_test_write_ambient_test(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct cs_amp_lib_test_ctl_write_entry *entry; + struct cs_dsp *dsp; + int ret; + + dsp = kunit_kzalloc(test, sizeof(*dsp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dsp); + dsp->dev = &priv->amp_dev->dev; + + /* Redirect calls to write firmware controls */ + kunit_activate_static_stub(test, + cs_amp_test_hooks->write_cal_coeff, + cs_amp_lib_test_write_cal_coeff); + + ret = cs_amp_write_ambient_temp(dsp, &cs_amp_lib_test_calibration_controls, 18); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, list_count_nodes(&priv->ctl_write_list), 1); + + entry = list_first_entry(&priv->ctl_write_list, typeof(*entry), list); + KUNIT_EXPECT_STREQ(test, entry->name, cs_amp_lib_test_calibration_controls.ambient); + KUNIT_EXPECT_EQ(test, entry->value, 18); +} + static void cs_amp_lib_test_spkid_lenovo_not_present(struct kunit *test) { struct cs_amp_lib_test_priv *priv = test->priv; @@ -974,8 +1045,10 @@ static struct kunit_case cs_amp_lib_test_cases[] = { cs_amp_lib_test_get_cal_gen_params), KUNIT_CASE(cs_amp_lib_test_get_efi_cal_empty_entry_test), - /* Tests for writing calibration data */ + /* Tests for writing and reading calibration data */ KUNIT_CASE(cs_amp_lib_test_write_cal_data_test), + KUNIT_CASE(cs_amp_lib_test_read_cal_data_test), + KUNIT_CASE(cs_amp_lib_test_write_ambient_test), /* Test cases for speaker ID */ KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_not_present), diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index b2509c5c0690..d510e0e065ca 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -515,6 +515,7 @@ EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB"); static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = { .get_efi_variable = cs_amp_get_efi_variable, .write_cal_coeff = cs_amp_write_cal_coeff, + .read_cal_coeff = cs_amp_read_cal_coeff, }; const struct cs_amp_test_hooks * const cs_amp_test_hooks = -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 07/11] ASoC: cs-amp-lib: Return attributes from cs_amp_get_efi_variable() 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (5 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 06/11] ASoC: cs-amp-lib-test: Add cases for factory calibration helpers Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 08/11] ASoC: cs-amp-lib: Add function to write calibration to EFI Richard Fitzgerald ` (3 subsequent siblings) 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Add a pointer argument to cs_amp_get_efi_variable() to optionally return the EFI variable attributes. Originally this function internally consumed the attributes from efi.get_variable(). The calling code did not use the attributes so this was a small simplification. However, when writing to a pre-existing variable we would want to pass the existing attributes to efi.set_variable(). This patch deals with the change to return the attribute in preparation for adding code to update the variable. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs-amp-lib.h | 1 + sound/soc/codecs/cs-amp-lib-test.c | 23 +++++++++++++++++++++++ sound/soc/codecs/cs-amp-lib.c | 15 ++++++++++----- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h index 485daadd13a1..09c54b1bbe6c 100644 --- a/include/sound/cs-amp-lib.h +++ b/include/sound/cs-amp-lib.h @@ -65,6 +65,7 @@ static inline u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) struct cs_amp_test_hooks { efi_status_t (*get_efi_variable)(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf); diff --git a/sound/soc/codecs/cs-amp-lib-test.c b/sound/soc/codecs/cs-amp-lib-test.c index b19aaacd7f2b..d3419f48297e 100644 --- a/sound/soc/codecs/cs-amp-lib-test.c +++ b/sound/soc/codecs/cs-amp-lib-test.c @@ -90,6 +90,7 @@ static u64 cs_amp_lib_test_get_target_uid(struct kunit *test) /* Redirected get_efi_variable to simulate that the file is too short */ static efi_status_t cs_amp_lib_test_get_efi_variable_nohead(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -122,6 +123,7 @@ static void cs_amp_lib_test_cal_data_too_short_test(struct kunit *test) /* Redirected get_efi_variable to simulate that the count is larger than the file */ static efi_status_t cs_amp_lib_test_get_efi_variable_bad_count(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -165,6 +167,7 @@ static void cs_amp_lib_test_cal_count_too_big_test(struct kunit *test) /* Redirected get_efi_variable to simulate that the variable not found */ static efi_status_t cs_amp_lib_test_get_efi_variable_none(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -192,6 +195,7 @@ static void cs_amp_lib_test_no_cal_data_test(struct kunit *test) /* Redirected get_efi_variable to simulate reading a cal data blob */ static efi_status_t cs_amp_lib_test_get_efi_variable(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -218,11 +222,18 @@ static efi_status_t cs_amp_lib_test_get_efi_variable(efi_char16_t *name, memcpy(buf, priv->cal_blob, priv->cal_blob->size); + if (returned_attr) { + *returned_attr = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + } + return EFI_SUCCESS; } static efi_status_t cs_amp_lib_test_get_hp_cal_efi_variable(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -249,6 +260,12 @@ static efi_status_t cs_amp_lib_test_get_hp_cal_efi_variable(efi_char16_t *name, memcpy(buf, priv->cal_blob, priv->cal_blob->size); + if (returned_attr) { + *returned_attr = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + } + return EFI_SUCCESS; } @@ -787,6 +804,7 @@ static void cs_amp_lib_test_spkid_lenovo_not_present(struct kunit *test) static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_d0(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -805,6 +823,7 @@ static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_d0(efi_char16_t *nam static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_d1(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -823,6 +842,7 @@ static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_d1(efi_char16_t *nam static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_00(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -874,6 +894,7 @@ static void cs_amp_lib_test_spkid_lenovo_illegal(struct kunit *test) static efi_status_t cs_amp_lib_test_get_efi_variable_buf_too_small(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -894,6 +915,7 @@ static void cs_amp_lib_test_spkid_lenovo_oversize(struct kunit *test) static efi_status_t cs_amp_lib_test_get_efi_variable_hp_30(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { @@ -912,6 +934,7 @@ static efi_status_t cs_amp_lib_test_get_efi_variable_hp_30(efi_char16_t *name, static efi_status_t cs_amp_lib_test_get_efi_variable_hp_31(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index d510e0e065ca..2630ea6a8f3a 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -244,15 +244,20 @@ EXPORT_SYMBOL_NS_GPL(cs_amp_write_ambient_temp, "SND_SOC_CS_AMP_LIB"); static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, efi_guid_t *guid, + u32 *returned_attr, unsigned long *size, void *buf) { u32 attr; - KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf); + if (!returned_attr) + returned_attr = &attr; + + KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, + returned_attr, size, buf); if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) - return efi.get_variable(name, guid, &attr, size, buf); + return efi.get_variable(name, guid, returned_attr, size, buf); return EFI_NOT_FOUND; } @@ -287,7 +292,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, - &data_size, NULL); + NULL, &data_size, NULL); if (status == EFI_BUFFER_TOO_SMALL) break; } @@ -307,7 +312,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, - &data_size, data); + NULL, &data_size, data); if (status != EFI_SUCCESS) { ret = -EINVAL; goto err; @@ -451,7 +456,7 @@ static int cs_amp_get_efi_byte_spkid(struct device *dev, const struct cs_amp_spk int i, ret; size = sizeof(spkid); - status = cs_amp_get_efi_variable(info->name, info->guid, &size, &spkid); + status = cs_amp_get_efi_variable(info->name, info->guid, NULL, &size, &spkid); ret = cs_amp_convert_efi_status(status); if (ret < 0) return ret; -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 08/11] ASoC: cs-amp-lib: Add function to write calibration to EFI 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (6 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 07/11] ASoC: cs-amp-lib: Return attributes from cs_amp_get_efi_variable() Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 09/11] ASoC: cs35l56: Add calibration command to store into UEFI Richard Fitzgerald ` (2 subsequent siblings) 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Add cs_amp_set_efi_calibration_data() to write an amp calibration blob to EFI calibration variable. The EFI 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 EFI variable as zero-filled Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs-amp-lib.h | 2 + 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 09c54b1bbe6c..4c8b84ee087e 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); static inline u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index 2630ea6a8f3a..3f352ecad3e0 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -12,6 +12,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> @@ -48,9 +49,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); @@ -262,6 +270,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) { @@ -271,6 +293,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: @@ -280,7 +303,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; @@ -292,7 +318,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; } @@ -300,6 +326,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); @@ -312,7 +344,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; @@ -328,6 +360,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: @@ -337,6 +373,20 @@ static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) 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) { @@ -344,7 +394,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); @@ -396,6 +446,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. @@ -442,6 +584,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; -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 09/11] ASoC: cs35l56: Add calibration command to store into UEFI 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (7 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 08/11] ASoC: cs-amp-lib: Add function to write calibration to EFI Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 10/11] ALSA: hda/cs35l56: Set cal_index to the amp index Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 11/11] ASoC: cs-amp-lib-test: Add test cases for cs_amp_set_efi_calibration_data() Richard Fitzgerald 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Add a new command 'store_uefi' to the calibrate sysfs file. Writing this command will call cs_amp_set_efi_calibration_data() to save the new data into an EFI variable. This is intended to be used after a successful factory calibration to save the data into EFI. On systems without EFI it will return an error. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs35l56.h | 1 + sound/soc/codecs/cs35l56-shared.c | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 4ed738615021..b3770285261c 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -314,6 +314,7 @@ struct cs35l56_base { bool can_hibernate; bool cal_data_valid; s8 cal_index; + u8 num_amps; struct cirrus_amp_cal_data cal_data; struct gpio_desc *reset_gpio; struct cs35l56_spi_payload *spi_payload_buf; diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index dc6e49e3421a..27fb2f9b83c5 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -1101,8 +1101,8 @@ static int cs35l56_perform_calibration(struct cs35l56_base *cs35l56_base) ssize_t cs35l56_calibrate_sysfs_store(struct cs35l56_base *cs35l56_base, const char *buf, size_t count) { - static const char * const options[] = { "factory" }; - int ret; + static const char * const options[] = { "factory", "store_uefi" }; + int num_amps, ret; if (!IS_ENABLED(CONFIG_SND_SOC_CS35L56_CAL_SYSFS_COMMON)) return -ENXIO; @@ -1113,6 +1113,21 @@ ssize_t cs35l56_calibrate_sysfs_store(struct cs35l56_base *cs35l56_base, if (ret < 0) return ret; break; + case 1: + if (!cs35l56_base->cal_data_valid) + return -ENODATA; + + num_amps = cs35l56_base->num_amps; + if (num_amps == 0) + num_amps = -1; + + ret = cs_amp_set_efi_calibration_data(cs35l56_base->dev, + cs35l56_base->cal_index, + num_amps, + &cs35l56_base->cal_data); + if (ret < 0) + return ret; + break; default: return -ENXIO; } -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 10/11] ALSA: hda/cs35l56: Set cal_index to the amp index 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (8 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 09/11] ASoC: cs35l56: Add calibration command to store into UEFI Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 11/11] ASoC: cs-amp-lib-test: Add test cases for cs_amp_set_efi_calibration_data() Richard Fitzgerald 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Set cs35l56_base->cal_index to the (zero-based) amp index derived from cirrus,dev-index property. This is so that factory calibration data will be written to the EFI array in the slot equal to the amp index, for compatibility with the Windows driver. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- sound/hda/codecs/side-codecs/cs35l56_hda.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c index 4a1bd934887a..19baa8be310d 100644 --- a/sound/hda/codecs/side-codecs/cs35l56_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c @@ -1179,7 +1179,7 @@ int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int hid, int id) } cs35l56->base.type = hid & 0xff; - cs35l56->base.cal_index = -1; + cs35l56->base.cal_index = cs35l56->index; cs35l56_init_cs_dsp(&cs35l56->base, &cs35l56->cs_dsp); cs35l56->cs_dsp.client_ops = &cs35l56_hda_client_ops; -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 11/11] ASoC: cs-amp-lib-test: Add test cases for cs_amp_set_efi_calibration_data() 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald ` (9 preceding siblings ...) 2025-10-16 10:42 ` [PATCH 10/11] ALSA: hda/cs35l56: Set cal_index to the amp index Richard Fitzgerald @ 2025-10-16 10:42 ` Richard Fitzgerald 10 siblings, 0 replies; 17+ messages in thread From: Richard Fitzgerald @ 2025-10-16 10:42 UTC (permalink / raw) To: broonie, tiwai; +Cc: linux-sound, linux-kernel, patches Add a set of test cases for cs_amp_set_efi_calibration_data(). Broadly there are two type of behavior being tested: How the EFI is updated: - Create a new EFI - Overwrite part of existing content - Overwrite part of zero-filled preallocated content - Grow the file to append new content And how the location within the content is chosen: - Overwrite a specific array entry - Overwrite an entry with the same calTarget (silicon ID) - Overwrite a free entry - Append after existing data Plus some cases for error conditions. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> --- include/sound/cs-amp-lib.h | 5 + sound/soc/codecs/cs-amp-lib-test.c | 1399 +++++++++++++++++++++++++++- sound/soc/codecs/cs-amp-lib.c | 1 + 3 files changed, 1397 insertions(+), 8 deletions(-) diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h index 4c8b84ee087e..8eee4ce9eefe 100644 --- a/include/sound/cs-amp-lib.h +++ b/include/sound/cs-amp-lib.h @@ -70,6 +70,11 @@ struct cs_amp_test_hooks { u32 *returned_attr, unsigned long *size, void *buf); + efi_status_t (*set_efi_variable)(efi_char16_t *name, + efi_guid_t *guid, + u32 attr, + unsigned long size, + void *buf); int (*write_cal_coeff)(struct cs_dsp *dsp, const struct cirrus_amp_cal_controls *controls, diff --git a/sound/soc/codecs/cs-amp-lib-test.c b/sound/soc/codecs/cs-amp-lib-test.c index d3419f48297e..7fa6aabc746a 100644 --- a/sound/soc/codecs/cs-amp-lib-test.c +++ b/sound/soc/codecs/cs-amp-lib-test.c @@ -20,6 +20,10 @@ #include <linux/random.h> #include <sound/cs-amp-lib.h> +#define CIRRUS_LOGIC_CALIBRATION_EFI_NAME L"CirrusSmartAmpCalibrationData" +#define CIRRUS_LOGIC_CALIBRATION_EFI_GUID \ + EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3) + #define LENOVO_SPEAKER_ID_EFI_NAME L"SdwSpeaker" #define LENOVO_SPEAKER_ID_EFI_GUID \ EFI_GUID(0x48df970e, 0xe27f, 0x460a, 0xb5, 0x86, 0x77, 0x19, 0x80, 0x1d, 0x92, 0x82) @@ -28,6 +32,10 @@ #define HP_SPEAKER_ID_EFI_GUID \ EFI_GUID(0xc49593a4, 0xd099, 0x419b, 0xa2, 0xc3, 0x67, 0xe9, 0x80, 0xe6, 0x1d, 0x1e) +#define HP_CALIBRATION_EFI_NAME L"SmartAmpCalibrationData" +#define HP_CALIBRATION_EFI_GUID \ + EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93) + KUNIT_DEFINE_ACTION_WRAPPER(faux_device_destroy_wrapper, faux_device_destroy, struct faux_device *) @@ -36,6 +44,7 @@ struct cs_amp_lib_test_priv { struct cirrus_amp_efi_data *cal_blob; struct list_head ctl_write_list; + u32 efi_attr; }; struct cs_amp_lib_test_ctl_write_entry { @@ -49,6 +58,20 @@ struct cs_amp_lib_test_param { int amp_index; }; +static struct cirrus_amp_efi_data *cs_amp_lib_test_cal_blob_dup(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct cirrus_amp_efi_data *temp; + + KUNIT_ASSERT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + temp = kunit_kmalloc(test, priv->cal_blob->size, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, temp); + memcpy(temp, priv->cal_blob, priv->cal_blob->size); + + return temp; +} + static void cs_amp_lib_test_init_dummy_cal_blob(struct kunit *test, int num_amps) { struct cs_amp_lib_test_priv *priv = test->priv; @@ -69,9 +92,15 @@ static void cs_amp_lib_test_init_dummy_cal_blob(struct kunit *test, int num_amps for (i = 0; i < num_amps; i++) priv->cal_blob->data[i].calTime[0] |= 1; - /* Ensure that all UIDs are non-zero and unique. */ - for (i = 0; i < num_amps; i++) + /* + * Ensure that all UIDs are non-zero and unique. + * Make both words non-zero and not equal values, so that + * tests can verify that both words were checked or changed. + */ + for (i = 0; i < num_amps; i++) { *(u8 *)&priv->cal_blob->data[i].calTarget[0] = i + 1; + *(u8 *)&priv->cal_blob->data[i].calTarget[1] = i; + } } static u64 cs_amp_lib_test_get_target_uid(struct kunit *test) @@ -199,9 +228,8 @@ static efi_status_t cs_amp_lib_test_get_efi_variable(efi_char16_t *name, unsigned long *size, void *buf) { - static const efi_char16_t expected_name[] = L"CirrusSmartAmpCalibrationData"; - static const efi_guid_t expected_guid = - EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3); + static const efi_char16_t expected_name[] = CIRRUS_LOGIC_CALIBRATION_EFI_NAME; + static const efi_guid_t expected_guid = CIRRUS_LOGIC_CALIBRATION_EFI_GUID; struct kunit *test = kunit_get_current_test(); struct cs_amp_lib_test_priv *priv = test->priv; @@ -223,9 +251,56 @@ static efi_status_t cs_amp_lib_test_get_efi_variable(efi_char16_t *name, memcpy(buf, priv->cal_blob, priv->cal_blob->size); if (returned_attr) { - *returned_attr = EFI_VARIABLE_NON_VOLATILE | - EFI_VARIABLE_BOOTSERVICE_ACCESS | - EFI_VARIABLE_RUNTIME_ACCESS; + if (priv->efi_attr) + *returned_attr = priv->efi_attr; + else + *returned_attr = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + } + + return EFI_SUCCESS; +} + +#define CS_AMP_LIB_ZERO_FILLED_BLOB_SIZE \ + struct_size_t(struct cirrus_amp_efi_data, data, 8) + +/* Redirected get_efi_variable to simulate reading a prealloced zero-filled blob */ +static efi_status_t cs_amp_lib_test_get_efi_variable_all_zeros(efi_char16_t *name, + efi_guid_t *guid, + u32 *returned_attr, + unsigned long *size, + void *buf) +{ + static const efi_char16_t expected_name[] = CIRRUS_LOGIC_CALIBRATION_EFI_NAME; + static const efi_guid_t expected_guid = CIRRUS_LOGIC_CALIBRATION_EFI_GUID; + struct kunit *test = kunit_get_current_test(); + struct cs_amp_lib_test_priv *priv = test->priv; + + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, name); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, guid); + + if (memcmp(name, expected_name, sizeof(expected_name)) || + efi_guidcmp(*guid, expected_guid)) + return -EFI_NOT_FOUND; + + if (!buf) { + *size = CS_AMP_LIB_ZERO_FILLED_BLOB_SIZE; + return EFI_BUFFER_TOO_SMALL; + } + + KUNIT_ASSERT_EQ(test, *size, struct_size(priv->cal_blob, data, 8)); + priv->cal_blob = kunit_kzalloc(test, CS_AMP_LIB_ZERO_FILLED_BLOB_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv->cal_blob); + memset(buf, 0, CS_AMP_LIB_ZERO_FILLED_BLOB_SIZE); + + if (returned_attr) { + if (priv->efi_attr) + *returned_attr = priv->efi_attr; + else + *returned_attr = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; } return EFI_SUCCESS; @@ -790,6 +865,1292 @@ static void cs_amp_lib_test_write_ambient_test(struct kunit *test) KUNIT_EXPECT_EQ(test, entry->value, 18); } +static efi_status_t cs_amp_lib_test_set_efi_variable(efi_char16_t *name, + efi_guid_t *guid, + u32 attr, + unsigned long size, + void *buf) +{ + static const efi_char16_t expected_name[] = CIRRUS_LOGIC_CALIBRATION_EFI_NAME; + static const efi_guid_t expected_guid = CIRRUS_LOGIC_CALIBRATION_EFI_GUID; + struct kunit *test = kunit_get_current_test(); + struct cs_amp_lib_test_priv *priv = test->priv; + + KUNIT_ASSERT_NOT_NULL(test, name); + KUNIT_ASSERT_NOT_NULL(test, guid); + + if (memcmp(name, expected_name, sizeof(expected_name)) || + efi_guidcmp(*guid, expected_guid)) + return -EFI_NOT_FOUND; + + KUNIT_ASSERT_NOT_NULL(test, buf); + KUNIT_ASSERT_NE(test, 0, size); + + kunit_kfree(test, priv->cal_blob); + priv->cal_blob = kunit_kmalloc(test, size, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv->cal_blob); + memcpy(priv->cal_blob, buf, size); + priv->efi_attr = attr; + + return EFI_SUCCESS; +} + +static efi_status_t cs_amp_lib_test_set_efi_variable_denied(efi_char16_t *name, + efi_guid_t *guid, + u32 attr, + unsigned long size, + void *buf) +{ + return EFI_WRITE_PROTECTED; +} + +#define CS_AMP_CAL_DEFAULT_EFI_ATTR \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) + +static void cs_amp_lib_test_create_new_cal_efi(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + int i; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable_none); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* For unspecified number of amps */ + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, CS_AMP_CAL_DEFAULT_EFI_ATTR, priv->efi_attr); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 1); + KUNIT_EXPECT_LE(test, priv->cal_blob->count, 8); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + for (i = 1; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + /* For 2 amps */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 2, &data)); + KUNIT_EXPECT_EQ(test, CS_AMP_CAL_DEFAULT_EFI_ATTR, priv->efi_attr); + KUNIT_EXPECT_EQ(test, 2, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 2), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + + /* For 4 amps */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 4, &data)); + KUNIT_EXPECT_EQ(test, 4, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 4), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + + /* For 6 amps */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); +} + +static void cs_amp_lib_test_create_new_cal_efi_indexed(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable_none); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* In slot 0 */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 0, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* In slot 1 */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[0], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* In slot 5 */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 5, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[5], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[0], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); +} + +static void cs_amp_lib_test_create_new_cal_efi_indexed_no_max(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + int i; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable_none); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* In slot 0 with unspecified number of amps */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 0, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 1); + KUNIT_EXPECT_LE(test, priv->cal_blob->count, 8); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + for (i = 1; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + /* In slot 1 with unspecified number of amps */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 2); + KUNIT_EXPECT_LE(test, priv->cal_blob->count, 8); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[0], sizeof(data))); + for (i = 2; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + /* In slot 5 with unspecified number of amps */ + priv->cal_blob = NULL; + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 5, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 6); + KUNIT_EXPECT_LE(test, priv->cal_blob->count, 8); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + for (i = 0; (i < 5) && (i < priv->cal_blob->count); i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[5], sizeof(data)); + for (i = 6; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); +} + +static void cs_amp_lib_test_grow_append_cal_efi(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* Initially 1 used entry grown to 2 entries */ + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 2, &data)); + KUNIT_EXPECT_EQ(test, CS_AMP_CAL_DEFAULT_EFI_ATTR, priv->efi_attr); + KUNIT_EXPECT_EQ(test, 2, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 2), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + + /* Initially 1 entry grown to 4 entries */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 4, &data)); + KUNIT_EXPECT_EQ(test, 4, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 4), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + + /* Initially 2 entries grown to 4 entries */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 2); + KUNIT_ASSERT_EQ(test, 2, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 4, &data)); + KUNIT_EXPECT_EQ(test, 4, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 4), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + + /* Initially 1 entry grown to 6 entries */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* Initially 4 entries grown to 6 entries */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); +} + +static void cs_amp_lib_test_grow_append_cal_efi_indexed(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* Initially 1 entry grown to 2 entries using slot 1 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, 2, &data)); + KUNIT_EXPECT_EQ(test, 2, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 2), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + + /* Initially 1 entry grown to 6 entries using slot 1 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* Initially 2 entries grown to 6 entries using slot 2 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 2); + KUNIT_ASSERT_EQ(test, 2, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 2, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* Initially 2 entries grown to 6 entries using slot 4 */ + kunit_kfree(test, original_blob); + kunit_kfree(test, priv->cal_blob); + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 2); + KUNIT_ASSERT_EQ(test, 2, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 4, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); +} + +static void cs_amp_lib_test_cal_efi_all_zeros_add_first(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + int i; + + /* Simulate a BIOS reserving EFI space that is entirely zero-filled. */ + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable_all_zeros); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* + * Add an entry. The header should be filled in to match the + * original EFI variable size. + */ + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 8), priv->cal_blob->size); + KUNIT_EXPECT_EQ(test, 8, priv->cal_blob->count); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + for (i = 1; i < priv->cal_blob->count; i++) { + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[1]); + } +} + +static void cs_amp_lib_test_cal_efi_all_zeros_add_first_no_shrink(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + int i; + + /* Simulate a BIOS reserving EFI space that is entirely zero-filled. */ + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable_all_zeros); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* + * Add an entry. The header should be filled in to match the + * original EFI variable size. A number of amps less than the + * available preallocated space does not shrink the EFI variable. + */ + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 4, &data)); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 8), priv->cal_blob->size); + KUNIT_EXPECT_EQ(test, 8, priv->cal_blob->count); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + for (i = 1; i < priv->cal_blob->count; i++) { + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[1]); + } +} + +static void cs_amp_lib_test_cal_efi_all_zeros_add_first_indexed(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + int i; + + /* Simulate a BIOS reserving EFI space that is entirely zero-filled. */ + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable_all_zeros); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* + * Write entry to slot 2. The header should be filled in to match + * the original EFI variable size. + */ + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 2, -1, &data)); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 8), priv->cal_blob->size); + KUNIT_EXPECT_EQ(test, 8, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[1]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[1]); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[2], sizeof(data)); + for (i = 3; i < priv->cal_blob->count; i++) { + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[1]); + } +} + +static void cs_amp_lib_test_cal_efi_all_zeros_add_first_indexed_no_shrink(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + int i; + + /* Simulate a BIOS reserving EFI space that is entirely zero-filled. */ + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable_all_zeros); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* + * Write entry to slot 2. The header should be filled in to match + * the original EFI variable size. A number of amps less than the + * available preallocated space does not shrink the EFI variable. + */ + get_random_bytes(&data, sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 2, 4, &data)); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 8), priv->cal_blob->size); + KUNIT_EXPECT_EQ(test, 8, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[1]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[1]); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[2], sizeof(data)); + for (i = 3; i < priv->cal_blob->count; i++) { + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[i].calTime[1]); + } +} + +static void cs_amp_lib_test_grow_append_cal_efi_indexed_no_max(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + int i; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* Initially 1 entry adding slot 1 */ + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 2); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + for (i = 2; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + /* Initially 1 entry adding slot 3 */ + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 3, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 4); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[3], sizeof(data)); + for (i = 4; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + /* Initially 2 entries adding slot 3 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 2); + KUNIT_ASSERT_EQ(test, 2, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 3, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 1); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[3], sizeof(data)); + for (i = 4; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + /* Initially 4 entries adding slot 4 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 4, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 1); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + for (i = 5; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); + + /* Initially 4 entries adding slot 6 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 6, -1, &data)); + KUNIT_EXPECT_GE(test, priv->cal_blob->count, 1); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, priv->cal_blob->count), + priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[6], sizeof(data)); + for (i = 7; i < priv->cal_blob->count; i++) + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[i], sizeof(data))); +} + +static void cs_amp_lib_test_grow_cal_efi_replace_indexed(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* Initially 1 entry grown to 2 entries overwriting slot 0 */ + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 0, 2, &data)); + KUNIT_EXPECT_EQ(test, 2, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 2), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + + /* Initially 2 entries grown to 4 entries overwriting slot 1 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 2); + KUNIT_ASSERT_EQ(test, 2, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, 4, &data)); + KUNIT_EXPECT_EQ(test, 4, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 4), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + + /* Initially 4 entries grown to 6 entries overwriting slot 1 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* Initially 4 entries grown to 6 entries overwriting slot 3 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 3, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* Initially 6 entries grown to 8 entries overwriting slot 4 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 6); + KUNIT_ASSERT_EQ(test, 6, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; /* won't match */ + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 4, 8, &data)); + KUNIT_EXPECT_EQ(test, 8, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 8), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[6], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[7], sizeof(data))); +} + +static void cs_amp_lib_test_grow_cal_efi_replace_by_uid(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* Initially 1 entry grown to 2 entries overwriting slot 0 */ + cs_amp_lib_test_init_dummy_cal_blob(test, 1); + KUNIT_ASSERT_EQ(test, 1, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[0].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 2, &data)); + KUNIT_EXPECT_EQ(test, 2, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 2), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[1], sizeof(data))); + + /* Initially 2 entries grown to 4 entries overwriting slot 1 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 2); + KUNIT_ASSERT_EQ(test, 2, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[1].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 4, &data)); + KUNIT_EXPECT_EQ(test, 4, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 4), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[2], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[3], sizeof(data))); + + /* Initially 4 entries grown to 6 entries overwriting slot 1 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[1].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* Initially 4 entries grown to 6 entries overwriting slot 3 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[3].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 6, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[4], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[5], sizeof(data))); + + /* Initially 6 entries grown to 8 entries overwriting slot 4 */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 6); + KUNIT_ASSERT_EQ(test, 6, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[4].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, 8, &data)); + KUNIT_EXPECT_EQ(test, 8, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 8), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[6], sizeof(data))); + KUNIT_EXPECT_TRUE(test, mem_is_zero(&priv->cal_blob->data[7], sizeof(data))); +} + +static void cs_amp_lib_test_cal_efi_replace_by_uid(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + cs_amp_lib_test_init_dummy_cal_blob(test, 6); + KUNIT_ASSERT_EQ(test, 6, priv->cal_blob->count); + + /* Replace entry matching slot 0 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[0].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Replace entry matching slot 4 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[4].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Replace entry matching slot 3 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[3].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Replace entry matching slot 5 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[5].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[5], sizeof(data)); +} + +static void cs_amp_lib_test_cal_efi_replace_by_index(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + cs_amp_lib_test_init_dummy_cal_blob(test, 6); + KUNIT_ASSERT_EQ(test, 6, priv->cal_blob->count); + + /* + * Replace entry matching slot 0. + * data.calTarget is deliberately set different to current calTarget + * of the slot to check that the index forces that slot to be used. + */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = ~priv->cal_blob->data[0].calTarget[0]; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 0, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Replace entry matching slot 4 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = ~priv->cal_blob->data[4].calTarget[0]; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 4, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Replace entry matching slot 3 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = ~priv->cal_blob->data[3].calTarget[0]; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 3, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Replace entry matching slot 5 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = ~priv->cal_blob->data[5].calTarget[0]; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 5, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[5], sizeof(data)); +} + +static void cs_amp_lib_test_cal_efi_deduplicate(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + int i; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + /* + * Replace entry matching slot 0. + * An active entry in slot 1 for the same UID should be marked empty. + * Other entries are unaltered. + */ + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[1].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 0, -1, &data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[1]); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + + /* + * Replace entry matching slot 1. + * An active entry in slot 0 for the same UID should be marked empty. + * Other entries are unaltered. + */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[0].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, -1, &data)); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[1]); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + + /* + * Replace entry matching slot 1. + * An active entry in slot 3 for the same UID should be marked empty. + * Other entries are unaltered. + */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + memcpy(data.calTarget, priv->cal_blob->data[3].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 1, -1, &data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[3].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[3].calTime[1]); + + /* + * Worst case, all entries have the same UID + */ + priv->cal_blob = NULL; + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + for (i = 0; i < priv->cal_blob->count; i++) { + priv->cal_blob->data[i].calTarget[0] = 0xe5e5e5e5; + priv->cal_blob->data[i].calTarget[1] = 0xa7a7a7a7; + } + memcpy(data.calTarget, priv->cal_blob->data[2].calTarget, sizeof(data.calTarget)); + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 2, -1, &data)); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[0].calTime[1]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[1].calTime[1]); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[3].calTime[0]); + KUNIT_EXPECT_EQ(test, 0, priv->cal_blob->data[3].calTime[1]); +} + +static void cs_amp_lib_test_cal_efi_find_free(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + cs_amp_lib_test_init_dummy_cal_blob(test, 6); + KUNIT_ASSERT_EQ(test, 6, priv->cal_blob->count); + + /* + * Slot 0 is empty. + * data.calTarget is set to a value that won't match any existing entry. + */ + memset(&priv->cal_blob->data[0].calTime, 0, sizeof(priv->cal_blob->data[0].calTime)); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Slot 4 is empty */ + memset(&priv->cal_blob->data[4].calTime, 0, sizeof(priv->cal_blob->data[4].calTime)); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Slot 3 is empty */ + memset(&priv->cal_blob->data[3].calTime, 0, sizeof(priv->cal_blob->data[3].calTime)); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); + + /* Replace entry matching slot 5 */ + memset(&priv->cal_blob->data[5].calTime, 0, sizeof(priv->cal_blob->data[5].calTime)); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = 0xaaaaaaaa; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[4], &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[5], sizeof(data)); +} + +static void cs_amp_lib_test_cal_efi_bad_cal_target(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + + /* Zero calTarget is illegal */ + get_random_bytes(&data, sizeof(data)); + memset(data.calTarget, 0, sizeof(data.calTarget)); + KUNIT_EXPECT_LT(test, cs_amp_set_efi_calibration_data(dev, -1, -1, &data), 0); + KUNIT_EXPECT_LT(test, cs_amp_set_efi_calibration_data(dev, 0, -1, &data), 0); + KUNIT_EXPECT_LT(test, cs_amp_set_efi_calibration_data(dev, 0, 2, &data), 0); +} + +static void cs_amp_lib_test_cal_efi_write_denied(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable_denied); + + cs_amp_lib_test_init_dummy_cal_blob(test, 4); + KUNIT_ASSERT_EQ(test, 4, priv->cal_blob->count); + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + + /* Unspecified slot */ + KUNIT_EXPECT_LT(test, cs_amp_set_efi_calibration_data(dev, -1, -1, &data), 0); + KUNIT_EXPECT_MEMEQ(test, original_blob, priv->cal_blob, original_blob->size); + + /* Unspecified slot with size */ + KUNIT_EXPECT_LT(test, cs_amp_set_efi_calibration_data(dev, -1, 6, &data), 0); + KUNIT_EXPECT_MEMEQ(test, original_blob, priv->cal_blob, original_blob->size); + + /* Specified slot */ + KUNIT_EXPECT_LT(test, cs_amp_set_efi_calibration_data(dev, 1, -1, &data), 0); + KUNIT_EXPECT_MEMEQ(test, original_blob, priv->cal_blob, original_blob->size); + + /* Specified slot with size */ + KUNIT_EXPECT_LT(test, cs_amp_set_efi_calibration_data(dev, 1, 6, &data), 0); + KUNIT_EXPECT_MEMEQ(test, original_blob, priv->cal_blob, original_blob->size); +} + +static void cs_amp_lib_test_cal_efi_attr_preserved(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_efi_variable); + + cs_amp_lib_test_init_dummy_cal_blob(test, 6); + KUNIT_ASSERT_EQ(test, 6, priv->cal_blob->count); + memset(&priv->cal_blob->data[0], 0, sizeof(priv->cal_blob->data[0])); + get_random_bytes(&data, sizeof(data)); + + /* Set a non-standard attr to return from get_efi_variable() */ + priv->efi_attr = EFI_VARIABLE_HARDWARE_ERROR_RECORD; + + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, -1, -1, &data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_EQ(test, priv->efi_attr, EFI_VARIABLE_HARDWARE_ERROR_RECORD); +} + +static efi_status_t cs_amp_lib_test_set_hp_efi_cal_variable(efi_char16_t *name, + efi_guid_t *guid, + u32 attr, + unsigned long size, + void *buf) +{ + static const efi_char16_t expected_name[] = HP_CALIBRATION_EFI_NAME; + static const efi_guid_t expected_guid = HP_CALIBRATION_EFI_GUID; + struct kunit *test = kunit_get_current_test(); + struct cs_amp_lib_test_priv *priv = test->priv; + + KUNIT_ASSERT_NOT_NULL(test, name); + KUNIT_ASSERT_NOT_NULL(test, guid); + + if (memcmp(name, expected_name, sizeof(expected_name)) || + efi_guidcmp(*guid, expected_guid)) + return -EFI_ACCESS_DENIED; + + KUNIT_ASSERT_NOT_NULL(test, buf); + KUNIT_ASSERT_NE(test, 0, size); + + kunit_kfree(test, priv->cal_blob); + priv->cal_blob = kunit_kmalloc(test, size, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv->cal_blob); + memcpy(priv->cal_blob, buf, size); + priv->efi_attr = attr; + + return EFI_SUCCESS; +} + +/* + * If the HP EFI exists it should be the one that is updated. + */ +static void cs_amp_lib_test_cal_efi_update_hp(struct kunit *test) +{ + struct cs_amp_lib_test_priv *priv = test->priv; + struct device *dev = &priv->amp_dev->dev; + const struct cirrus_amp_efi_data *original_blob; + struct cirrus_amp_cal_data data; + + kunit_activate_static_stub(test, + cs_amp_test_hooks->get_efi_variable, + cs_amp_lib_test_get_hp_cal_efi_variable); + kunit_activate_static_stub(test, + cs_amp_test_hooks->set_efi_variable, + cs_amp_lib_test_set_hp_efi_cal_variable); + + cs_amp_lib_test_init_dummy_cal_blob(test, 6); + KUNIT_ASSERT_EQ(test, 6, priv->cal_blob->count); + + /* Replace entry matching slot 4 */ + original_blob = cs_amp_lib_test_cal_blob_dup(test); + get_random_bytes(&data, sizeof(data)); + data.calTarget[0] = ~priv->cal_blob->data[4].calTarget[0]; + KUNIT_EXPECT_EQ(test, 0, cs_amp_set_efi_calibration_data(dev, 4, -1, &data)); + KUNIT_EXPECT_EQ(test, 6, priv->cal_blob->count); + KUNIT_EXPECT_EQ(test, struct_size(priv->cal_blob, data, 6), priv->cal_blob->size); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[0], &priv->cal_blob->data[0], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[1], &priv->cal_blob->data[1], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[2], &priv->cal_blob->data[2], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[3], &priv->cal_blob->data[3], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &data, &priv->cal_blob->data[4], sizeof(data)); + KUNIT_EXPECT_MEMEQ(test, &original_blob->data[5], &priv->cal_blob->data[5], sizeof(data)); +} + static void cs_amp_lib_test_spkid_lenovo_not_present(struct kunit *test) { struct cs_amp_lib_test_priv *priv = test->priv; @@ -1073,6 +2434,28 @@ static struct kunit_case cs_amp_lib_test_cases[] = { KUNIT_CASE(cs_amp_lib_test_read_cal_data_test), KUNIT_CASE(cs_amp_lib_test_write_ambient_test), + /* Test cases for writing cal data to UEFI */ + KUNIT_CASE(cs_amp_lib_test_create_new_cal_efi), + KUNIT_CASE(cs_amp_lib_test_create_new_cal_efi_indexed), + KUNIT_CASE(cs_amp_lib_test_create_new_cal_efi_indexed_no_max), + KUNIT_CASE(cs_amp_lib_test_cal_efi_all_zeros_add_first), + KUNIT_CASE(cs_amp_lib_test_cal_efi_all_zeros_add_first_no_shrink), + KUNIT_CASE(cs_amp_lib_test_cal_efi_all_zeros_add_first_indexed), + KUNIT_CASE(cs_amp_lib_test_cal_efi_all_zeros_add_first_indexed_no_shrink), + KUNIT_CASE(cs_amp_lib_test_grow_append_cal_efi), + KUNIT_CASE(cs_amp_lib_test_grow_append_cal_efi_indexed), + KUNIT_CASE(cs_amp_lib_test_grow_append_cal_efi_indexed_no_max), + KUNIT_CASE(cs_amp_lib_test_grow_cal_efi_replace_indexed), + KUNIT_CASE(cs_amp_lib_test_grow_cal_efi_replace_by_uid), + KUNIT_CASE(cs_amp_lib_test_cal_efi_replace_by_uid), + KUNIT_CASE(cs_amp_lib_test_cal_efi_replace_by_index), + KUNIT_CASE(cs_amp_lib_test_cal_efi_deduplicate), + KUNIT_CASE(cs_amp_lib_test_cal_efi_find_free), + KUNIT_CASE(cs_amp_lib_test_cal_efi_bad_cal_target), + KUNIT_CASE(cs_amp_lib_test_cal_efi_write_denied), + KUNIT_CASE(cs_amp_lib_test_cal_efi_attr_preserved), + KUNIT_CASE(cs_amp_lib_test_cal_efi_update_hp), + /* Test cases for speaker ID */ KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_not_present), KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_d0), diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index 3f352ecad3e0..1c53328d7165 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -701,6 +701,7 @@ EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB"); static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = { .get_efi_variable = cs_amp_get_efi_variable, + .set_efi_variable = cs_amp_set_efi_variable, .write_cal_coeff = cs_amp_write_cal_coeff, .read_cal_coeff = cs_amp_read_cal_coeff, }; -- 2.47.3 ^ permalink raw reply related [flat|nested] 17+ messages in thread
end of thread, other threads:[~2025-10-16 12:45 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-10-16 10:42 [PATCH 00/11] ALSA: cs35l56: Add support for factory calibration Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 01/11] ASoC: cs35l56: Read silicon ID during initialization and save it Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 02/11] ASoC: cs-amp-lib: Add helpers for factory calibration Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 03/11] ASoC: cs35l56: Add common code " Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 04/11] ASoC: cs35l56: Create sysfs files " Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 05/11] ALSA: hda/cs35l56: " Richard Fitzgerald 2025-10-16 11:01 ` Takashi Iwai 2025-10-16 11:27 ` Mark Brown 2025-10-16 11:58 ` Richard Fitzgerald 2025-10-16 12:13 ` Mark Brown 2025-10-16 12:45 ` Takashi Iwai 2025-10-16 10:42 ` [PATCH 06/11] ASoC: cs-amp-lib-test: Add cases for factory calibration helpers Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 07/11] ASoC: cs-amp-lib: Return attributes from cs_amp_get_efi_variable() Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 08/11] ASoC: cs-amp-lib: Add function to write calibration to EFI Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 09/11] ASoC: cs35l56: Add calibration command to store into UEFI Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 10/11] ALSA: hda/cs35l56: Set cal_index to the amp index Richard Fitzgerald 2025-10-16 10:42 ` [PATCH 11/11] ASoC: cs-amp-lib-test: Add test cases for cs_amp_set_efi_calibration_data() Richard Fitzgerald
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox