diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h
new file mode 100644
index 000000000000..077fe36885b5
--- /dev/null
+++ b/include/sound/cs-amp-lib.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Cirrus Logic, Inc. and
+ *                    Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef CS_AMP_LIB_H
+#define CS_AMP_LIB_H
+
+#include <linux/efi.h>
+#include <linux/types.h>
+
+struct cs_dsp;
+
+struct cirrus_amp_cal_data {
+	u32 calTarget[2];
+	u32 calTime[2];
+	s8  calAmbient;
+	u8  calStatus;
+	u16 calR;
+} __packed;
+
+struct cirrus_amp_efi_data {
+	u32 size;
+	u32 count;
+	struct cirrus_amp_cal_data data[];
+} __packed;
+
+/**
+ * struct cirrus_amp_cal_controls - definition of firmware calibration controls
+ * @alg_id:	ID of algorithm containing the controls.
+ * @mem_region:	DSP memory region containing the controls.
+ * @ambient:	Name of control for calAmbient value.
+ * @calr:	Name of control for calR value.
+ * @status:	Name of control for calStatus value.
+ * @checksum:	Name of control for checksum value.
+ */
+struct cirrus_amp_cal_controls {
+	unsigned int alg_id;
+	int mem_region;
+	const char *ambient;
+	const char *calr;
+	const char *status;
+	const char *checksum;
+};
+
+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_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
+				    struct cirrus_amp_cal_data *out_data);
+#endif /* CS_AMP_LIB_H */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 027d9da85251..8356bd1256d8 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -729,6 +729,9 @@ config SND_SOC_CROS_EC_CODEC
 	  If you say yes here you will get support for the
 	  ChromeOS Embedded Controller's Audio Codec.
 
+config SND_SOC_CS_AMP_LIB
+	tristate
+
 config SND_SOC_CS35L32
 	tristate "Cirrus Logic CS35L32 CODEC"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4080646b2dd6..0fc40640e5d0 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -59,6 +59,7 @@ snd-soc-chv3-codec-objs := chv3-codec.o
 snd-soc-cpcap-objs := cpcap.o
 snd-soc-cq93vc-objs := cq93vc.o
 snd-soc-cros-ec-codec-objs := cros_ec_codec.o
+snd-soc-cs-amp-lib-objs := cs-amp-lib.o
 snd-soc-cs35l32-objs := cs35l32.o
 snd-soc-cs35l33-objs := cs35l33.o
 snd-soc-cs35l34-objs := cs35l34.o
@@ -452,6 +453,7 @@ obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o
 obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
 obj-$(CONFIG_SND_SOC_CPCAP)	+= snd-soc-cpcap.o
 obj-$(CONFIG_SND_SOC_CROS_EC_CODEC)	+= snd-soc-cros-ec-codec.o
+obj-$(CONFIG_SND_SOC_CS_AMP_LIB)	+= snd-soc-cs-amp-lib.o
 obj-$(CONFIG_SND_SOC_CS35L32)	+= snd-soc-cs35l32.o
 obj-$(CONFIG_SND_SOC_CS35L33)	+= snd-soc-cs35l33.o
 obj-$(CONFIG_SND_SOC_CS35L34)	+= snd-soc-cs35l34.o
diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c
new file mode 100644
index 000000000000..4e2e5157a73f
--- /dev/null
+++ b/sound/soc/codecs/cs-amp-lib.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Common code for Cirrus Logic Smart Amplifiers
+//
+// Copyright (C) 2024 Cirrus Logic, Inc. and
+//               Cirrus Logic International Semiconductor Ltd.
+
+#include <asm/byteorder.h>
+#include <linux/dev_printk.h>
+#include <linux/efi.h>
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <sound/cs-amp-lib.h>
+
+#define CS_AMP_CAL_GUID \
+	EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3)
+
+#define CS_AMP_CAL_NAME	L"CirrusSmartAmpCalibrationData"
+
+static int cs_amp_write_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 = cpu_to_be32(val);
+	int ret;
+
+	if (IS_REACHABLE(CONFIG_FW_CS_DSP)) {
+		mutex_lock(&dsp->pwr_lock);
+		cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id);
+		ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval));
+		mutex_unlock(&dsp->pwr_lock);
+
+		if (ret < 0) {
+			dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret);
+			return ret;
+		}
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+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)
+{
+	int ret;
+
+	dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n",
+		data->calAmbient, data->calStatus, data->calR);
+
+	ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient);
+	if (ret)
+		return ret;
+
+	ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR);
+	if (ret)
+		return ret;
+
+	ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus);
+	if (ret)
+		return ret;
+
+	ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/**
+ * cs_amp_write_cal_coeffs - Write calibration data to firmware controls.
+ * @dsp:	Pointer to struct cs_dsp.
+ * @controls:	Pointer to definition of firmware controls to be written.
+ * @data:	Pointer to calibration data.
+ *
+ * Returns: 0 on success, else negative error value.
+ */
+int cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
+			    const struct cirrus_amp_cal_controls *controls,
+			    const struct cirrus_amp_cal_data *data)
+{
+	if (IS_REACHABLE(CONFIG_FW_CS_DSP))
+		return _cs_amp_write_cal_coeffs(dsp, controls, data);
+	else
+		return -ENODEV;
+}
+EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, 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,
+					    void *buf)
+{
+	u32 attr;
+
+	if (IS_ENABLED(CONFIG_EFI))
+		return efi.get_variable(name, guid, &attr, size, buf);
+
+	return EFI_NOT_FOUND;
+}
+
+static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
+{
+	struct cirrus_amp_efi_data *efi_data;
+	unsigned long data_size = 0;
+	u8 *data;
+	efi_status_t status;
+	int ret;
+
+	/* Get real size of UEFI variable */
+	status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, NULL);
+	if (status != EFI_BUFFER_TOO_SMALL)
+		return ERR_PTR(-ENOENT);
+
+	if (data_size < sizeof(*efi_data)) {
+		dev_err(dev, "EFI cal variable truncated\n");
+		return ERR_PTR(-EOVERFLOW);
+	}
+
+	/* Get variable contents into buffer */
+	data = kmalloc(data_size, GFP_KERNEL);
+	if (!data)
+		return ERR_PTR(-ENOMEM);
+
+	status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, data);
+	if (status != EFI_SUCCESS) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	efi_data = (struct cirrus_amp_efi_data *)data;
+	dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count);
+
+	if ((efi_data->count > 128) ||
+	    offsetof(struct cirrus_amp_efi_data, data[efi_data->count]) > data_size) {
+		dev_err(dev, "EFI cal variable truncated\n");
+		ret = -EOVERFLOW;
+		goto err;
+	}
+
+	return efi_data;
+
+err:
+	kfree(data);
+	dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret);
+
+	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)
+{
+	struct cirrus_amp_efi_data *efi_data;
+	struct cirrus_amp_cal_data *cal = NULL;
+	int i, ret;
+
+	efi_data = cs_amp_get_cal_efi_buffer(dev);
+	if (IS_ERR(efi_data))
+		return PTR_ERR(efi_data);
+
+	if (target_uid) {
+		for (i = 0; i < efi_data->count; ++i) {
+			u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]);
+
+			/* Skip entries with unpopulated silicon ID */
+			if (cal_target == 0)
+				continue;
+
+			if (cal_target == target_uid) {
+				cal = &efi_data->data[i];
+				break;
+			}
+		}
+	}
+
+	if (!cal && (amp_index >= 0) && (amp_index < efi_data->count)) {
+		u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]);
+
+		/*
+		 * Treat unpopulated cal_target as a wildcard.
+		 * If target_uid != 0 we can only get here if cal_target == 0
+		 * or it didn't match any cal_target value.
+		 * If target_uid == 0 it is a wildcard.
+		 */
+		if ((cal_target == 0) || (target_uid == 0))
+			cal = &efi_data->data[amp_index];
+		else
+			dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index);
+	}
+
+	if (cal) {
+		memcpy(out_data, cal, sizeof(*out_data));
+		ret = 0;
+	} else {
+		dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid);
+		ret = -ENOENT;
+	}
+
+	kfree(efi_data);
+
+	return ret;
+}
+
+/**
+ * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI.
+ * @dev:	struct device of the caller.
+ * @target_uid:	UID to match, or zero to ignore UID matching.
+ * @amp_index:	Entry index to use, or -1 to prevent lookup by index.
+ * @out_data:	struct cirrus_amp_cal_data where the entry will be copied.
+ *
+ * This function can perform 3 types of lookup:
+ *
+ * (target_uid > 0, amp_index >= 0)
+ *	UID search with fallback to using the array index.
+ *	Search the calibration data for a non-zero calTarget that matches
+ *	target_uid, and if found return that entry. Else, if the entry at
+ *	[amp_index] has calTarget == 0, return that entry. Else fail.
+ *
+ * (target_uid > 0, amp_index < 0)
+ *	UID search only.
+ *	Search the calibration data for a non-zero calTarget that matches
+ *	target_uid, and if found return that entry. Else fail.
+ *
+ * (target_uid == 0, amp_index >= 0)
+ *	Array index fetch only.
+ *	Return the entry at [amp_index].
+ *
+ * An array lookup will be skipped if amp_index exceeds the number of
+ * entries in the calibration array, and in this case the return will
+ * be -ENOENT. An out-of-range amp_index does not prevent matching by
+ * target_uid - it has the same effect as passing amp_index < 0.
+ *
+ * If the EFI data is too short to be a valid entry, or the entry count
+ * in the EFI data overflows the actual length of the data, this function
+ * returns -EOVERFLOW.
+ *
+ * Return: 0 if the entry was found, -ENOENT if no entry was found,
+ *	   -EOVERFLOW if the EFI file is corrupt, else other error value.
+ */
+int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
+				    struct cirrus_amp_cal_data *out_data)
+{
+	if (IS_ENABLED(CONFIG_EFI))
+		return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data);
+	else
+		return -ENOENT;
+}
+EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, SND_SOC_CS_AMP_LIB);
+
+MODULE_DESCRIPTION("Cirrus Logic amplifier library");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(FW_CS_DSP);
