aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/pci/hda/Kconfig2
-rw-r--r--sound/pci/hda/cs35l56_hda.c39
-rw-r--r--sound/soc/codecs/Kconfig4
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/cs-amp-lib.c263
-rw-r--r--sound/soc/codecs/cs35l56-sdw.c20
-rw-r--r--sound/soc/codecs/cs35l56-shared.c83
-rw-r--r--sound/soc/codecs/cs35l56.c44
-rw-r--r--sound/soc/codecs/wm_adsp.c27
-rw-r--r--sound/soc/codecs/wm_adsp.h2
10 files changed, 467 insertions, 19 deletions
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index 8e0ff70fb6101..ce2132ed6dbb9 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -162,6 +162,7 @@ config SND_HDA_SCODEC_CS35L56_I2C
select SND_HDA_SCODEC_CS35L56
select SND_HDA_CIRRUS_SCODEC
select SND_HDA_CS_DSP_CONTROLS
+ select SND_SOC_CS_AMP_LIB
help
Say Y or M here to include CS35L56 amplifier support with
I2C control.
@@ -177,6 +178,7 @@ config SND_HDA_SCODEC_CS35L56_SPI
select SND_HDA_SCODEC_CS35L56
select SND_HDA_CIRRUS_SCODEC
select SND_HDA_CS_DSP_CONTROLS
+ select SND_SOC_CS_AMP_LIB
help
Say Y or M here to include CS35L56 amplifier support with
SPI control.
diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c
index 75a14ba54fcd1..5ad76d6914c31 100644
--- a/sound/pci/hda/cs35l56_hda.c
+++ b/sound/pci/hda/cs35l56_hda.c
@@ -14,6 +14,7 @@
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
+#include <sound/cs-amp-lib.h>
#include <sound/hda_codec.h>
#include <sound/tlv.h>
#include "cirrus_scodec.h"
@@ -547,6 +548,22 @@ static void cs35l56_hda_add_dsp_controls(struct cs35l56_hda *cs35l56)
hda_cs_dsp_add_controls(&cs35l56->cs_dsp, &info);
}
+static void cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56)
+{
+ int ret;
+
+ if (!cs35l56->base.cal_data_valid || cs35l56->base.secured)
+ return;
+
+ ret = cs_amp_write_cal_coeffs(&cs35l56->cs_dsp,
+ &cs35l56_calibration_controls,
+ &cs35l56->base.cal_data);
+ if (ret < 0)
+ dev_warn(cs35l56->base.dev, "Failed to write calibration: %d\n", ret);
+ else
+ dev_info(cs35l56->base.dev, "Calibration applied\n");
+}
+
static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
{
const struct firmware *coeff_firmware = NULL;
@@ -618,12 +635,8 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
if (coeff_filename)
dev_dbg(cs35l56->base.dev, "Loaded Coefficients: %s\n", coeff_filename);
- if (!firmware_missing) {
- ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
- if (ret)
- goto err_powered_up;
- } else if (wmfw_firmware || coeff_firmware) {
- /* If we downloaded firmware, reset the device and wait for it to boot */
+ /* If we downloaded firmware, reset the device and wait for it to boot */
+ if (firmware_missing && (wmfw_firmware || coeff_firmware)) {
cs35l56_system_reset(&cs35l56->base, false);
regcache_mark_dirty(cs35l56->base.regmap);
ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
@@ -646,6 +659,11 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56)
if (ret)
dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret);
+ cs35l56_hda_apply_calibration(cs35l56);
+ ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
+ if (ret)
+ cs_dsp_stop(&cs35l56->cs_dsp);
+
err_powered_up:
if (!cs35l56->base.fw_patched)
cs_dsp_power_down(&cs35l56->cs_dsp);
@@ -953,6 +971,8 @@ int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id)
goto err;
}
+ cs35l56->base.cal_index = cs35l56->index;
+
cs35l56_init_cs_dsp(&cs35l56->base, &cs35l56->cs_dsp);
cs35l56->cs_dsp.client_ops = &cs35l56_hda_client_ops;
@@ -990,6 +1010,10 @@ int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id)
if (ret)
goto err;
+ ret = cs35l56_get_calibration(&cs35l56->base);
+ if (ret)
+ goto err;
+
ret = cs_dsp_halo_init(&cs35l56->cs_dsp);
if (ret) {
dev_err_probe(cs35l56->base.dev, ret, "cs_dsp_halo_init failed\n");
@@ -1064,10 +1088,11 @@ const struct dev_pm_ops cs35l56_hda_pm_ops = {
EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56);
MODULE_DESCRIPTION("CS35L56 HDA Driver");
+MODULE_IMPORT_NS(FW_CS_DSP);
MODULE_IMPORT_NS(SND_HDA_CIRRUS_SCODEC);
MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS);
MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_IMPORT_NS(SND_SOC_CS_AMP_LIB);
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(FW_CS_DSP);
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 027d9da852519..15f287784d8be 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
@@ -797,6 +800,7 @@ config SND_SOC_CS35L56
tristate
config SND_SOC_CS35L56_SHARED
+ select SND_SOC_CS_AMP_LIB
tristate
config SND_SOC_CS35L56_I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4080646b2dd6a..0fc40640e5d0c 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 0000000000000..4e2e5157a73f8
--- /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);
diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c
index ab960a1c171e6..eaa4c706f3a2f 100644
--- a/sound/soc/codecs/cs35l56-sdw.c
+++ b/sound/soc/codecs/cs35l56-sdw.c
@@ -161,6 +161,20 @@ static const struct regmap_bus cs35l56_regmap_bus_sdw = {
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
+static int cs35l56_sdw_set_cal_index(struct cs35l56_private *cs35l56)
+{
+ int ret;
+
+ /* SoundWire UniqueId is used to index the calibration array */
+ ret = sdw_read_no_pm(cs35l56->sdw_peripheral, SDW_SCP_DEVID_0);
+ if (ret < 0)
+ return ret;
+
+ cs35l56->base.cal_index = ret & 0xf;
+
+ return 0;
+}
+
static void cs35l56_sdw_init(struct sdw_slave *peripheral)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
@@ -168,6 +182,12 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral)
pm_runtime_get_noresume(cs35l56->base.dev);
+ if (cs35l56->base.cal_index < 0) {
+ ret = cs35l56_sdw_set_cal_index(cs35l56);
+ if (ret < 0)
+ goto out;
+ }
+
regcache_cache_only(cs35l56->base.regmap, false);
ret = cs35l56_init(cs35l56);
diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c
index 995d979b6d87e..517beaad5cd5d 100644
--- a/sound/soc/codecs/cs35l56-shared.c
+++ b/sound/soc/codecs/cs35l56-shared.c
@@ -5,10 +5,12 @@
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
+#include <linux/firmware/cirrus/wmfw.h>
#include <linux/gpio/consumer.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>
+#include <sound/cs-amp-lib.h>
#include "cs35l56.h"
@@ -36,6 +38,8 @@ int cs35l56_set_patch(struct cs35l56_base *cs35l56_base)
EXPORT_SYMBOL_NS_GPL(cs35l56_set_patch, SND_SOC_CS35L56_SHARED);
static const struct reg_default cs35l56_reg_defaults[] = {
+ /* no defaults for OTP_MEM - first read populates cache */
+
{ CS35L56_ASP1_ENABLES1, 0x00000000 },
{ CS35L56_ASP1_CONTROL1, 0x00000028 },
{ CS35L56_ASP1_CONTROL2, 0x18180200 },
@@ -91,6 +95,9 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg)
case CS35L56_BLOCK_ENABLES2:
case CS35L56_REFCLK_INPUT:
case CS35L56_GLOBAL_SAMPLE_RATE:
+ case CS35L56_OTP_MEM_53:
+ case CS35L56_OTP_MEM_54:
+ case CS35L56_OTP_MEM_55:
case CS35L56_ASP1_ENABLES1:
case CS35L56_ASP1_CONTROL1:
case CS35L56_ASP1_CONTROL2:
@@ -628,6 +635,81 @@ void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_ds
}
EXPORT_SYMBOL_NS_GPL(cs35l56_init_cs_dsp, SND_SOC_CS35L56_SHARED);
+struct cs35l56_pte {
+ u8 x;
+ u8 wafer_id;
+ u8 pte[2];
+ u8 lot[3];
+ u8 y;
+ u8 unused[3];
+ u8 dvs;
+} __packed;
+static_assert((sizeof(struct cs35l56_pte) % sizeof(u32)) == 0);
+
+static int cs35l56_read_silicon_uid(struct cs35l56_base *cs35l56_base, u64 *uid)
+{
+ struct cs35l56_pte pte;
+ u64 unique_id;
+ int ret;
+
+ ret = regmap_raw_read(cs35l56_base->regmap, CS35L56_OTP_MEM_53, &pte, sizeof(pte));
+ if (ret) {
+ dev_err(cs35l56_base->dev, "Failed to read OTP: %d\n", ret);
+ return ret;
+ }
+
+ unique_id = pte.lot[2] | (pte.lot[1] << 8) | (pte.lot[0] << 16);
+ unique_id <<= 32;
+ unique_id |= pte.x | (pte.y << 8) | (pte.wafer_id << 16) | (pte.dvs << 24);
+
+ dev_dbg(cs35l56_base->dev, "UniqueID = %#llx\n", unique_id);
+
+ *uid = unique_id;
+
+ return 0;
+}
+
+/* Firmware calibration controls */
+const struct cirrus_amp_cal_controls cs35l56_calibration_controls = {
+ .alg_id = 0x9f210,
+ .mem_region = WMFW_ADSP2_YM,
+ .ambient = "CAL_AMBIENT",
+ .calr = "CAL_R",
+ .status = "CAL_STATUS",
+ .checksum = "CAL_CHECKSUM",
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_calibration_controls, SND_SOC_CS35L56_SHARED);
+
+int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base)
+{
+ u64 silicon_uid;
+ int ret;
+
+ /* Driver can't apply calibration to a secured part, so skip */
+ if (cs35l56_base->secured)
+ return 0;
+
+ ret = cs35l56_read_silicon_uid(cs35l56_base, &silicon_uid);
+ if (ret < 0)
+ return ret;
+
+ ret = cs_amp_get_efi_calibration_data(cs35l56_base->dev, silicon_uid,
+ cs35l56_base->cal_index,
+ &cs35l56_base->cal_data);
+
+ /* Only return an error status if probe should be aborted */
+ if ((ret == -ENOENT) || (ret == -EOVERFLOW))
+ return 0;
+
+ if (ret < 0)
+ return ret;
+
+ cs35l56_base->cal_data_valid = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_get_calibration, SND_SOC_CS35L56_SHARED);
+
int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base,
bool *fw_missing, unsigned int *fw_version)
{
@@ -922,3 +1004,4 @@ MODULE_DESCRIPTION("ASoC CS35L56 Shared");
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(SND_SOC_CS_AMP_LIB);
diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c
index 2c1313e34cce4..23da9b96d8a75 100644
--- a/sound/soc/codecs/cs35l56.c
+++ b/sound/soc/codecs/cs35l56.c
@@ -23,6 +23,7 @@
#include <linux/soundwire/sdw.h>
#include <linux/types.h>
#include <linux/workqueue.h>
+#include <sound/cs-amp-lib.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
@@ -802,16 +803,44 @@ static struct snd_soc_dai_driver cs35l56_dai[] = {
}
};
+static int cs35l56_write_cal(struct cs35l56_private *cs35l56)
+{
+ int ret;
+
+ if (cs35l56->base.secured || !cs35l56->base.cal_data_valid)
+ return -ENODATA;
+
+ ret = wm_adsp_run(&cs35l56->dsp);
+ if (ret)
+ return ret;
+
+ ret = cs_amp_write_cal_coeffs(&cs35l56->dsp.cs_dsp,
+ &cs35l56_calibration_controls,
+ &cs35l56->base.cal_data);
+
+ wm_adsp_stop(&cs35l56->dsp);
+
+ if (ret == 0)
+ dev_info(cs35l56->base.dev, "Calibration applied\n");
+
+ return ret;
+}
+
static void cs35l56_reinit_patch(struct cs35l56_private *cs35l56)
{
int ret;
/* Use wm_adsp to load and apply the firmware patch and coefficient files */
ret = wm_adsp_power_up(&cs35l56->dsp, true);
- if (ret)
+ if (ret) {
dev_dbg(cs35l56->base.dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
- else
- cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
+ return;
+ }
+
+ cs35l56_write_cal(cs35l56);
+
+ /* Always REINIT after applying patch or coefficients */
+ cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
}
static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing)
@@ -874,6 +903,9 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing
CS35L56_FIRMWARE_MISSING);
cs35l56->base.fw_patched = true;
+ if (cs35l56_write_cal(cs35l56) == 0)
+ cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
+
err_unlock:
mutex_unlock(&cs35l56->base.irq_lock);
err:
@@ -1356,6 +1388,7 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56)
init_completion(&cs35l56->init_completion);
mutex_init(&cs35l56->base.irq_lock);
+ cs35l56->base.cal_index = -1;
cs35l56->speaker_id = -ENOENT;
dev_set_drvdata(cs35l56->base.dev, cs35l56);
@@ -1457,6 +1490,10 @@ int cs35l56_init(struct cs35l56_private *cs35l56)
if (ret)
return ret;
+ ret = cs35l56_get_calibration(&cs35l56->base);
+ if (ret)
+ return ret;
+
if (!cs35l56->base.reset_gpio) {
dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
cs35l56->soft_resetting = true;
@@ -1541,6 +1578,7 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = {
MODULE_DESCRIPTION("ASoC CS35L56 driver");
MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_IMPORT_NS(SND_SOC_CS_AMP_LIB);
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c
index 36ea0dcdc7ab0..e451c009f2d99 100644
--- a/sound/soc/codecs/wm_adsp.c
+++ b/sound/soc/codecs/wm_adsp.c
@@ -1092,27 +1092,36 @@ static void wm_adsp_event_post_stop(struct cs_dsp *cs_dsp)
dsp->fatal_error = false;
}
+int wm_adsp_run(struct wm_adsp *dsp)
+{
+ flush_work(&dsp->boot_work);
+
+ return cs_dsp_run(&dsp->cs_dsp);
+}
+EXPORT_SYMBOL_GPL(wm_adsp_run);
+
+void wm_adsp_stop(struct wm_adsp *dsp)
+{
+ cs_dsp_stop(&dsp->cs_dsp);
+}
+EXPORT_SYMBOL_GPL(wm_adsp_stop);
+
int wm_adsp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct wm_adsp *dsps = snd_soc_component_get_drvdata(component);
struct wm_adsp *dsp = &dsps[w->shift];
- int ret = 0;
switch (event) {
case SND_SOC_DAPM_POST_PMU:
- flush_work(&dsp->boot_work);
- ret = cs_dsp_run(&dsp->cs_dsp);
- break;
+ return wm_adsp_run(dsp);
case SND_SOC_DAPM_PRE_PMD:
- cs_dsp_stop(&dsp->cs_dsp);
- break;
+ wm_adsp_stop(dsp);
+ return 0;
default:
- break;
+ return 0;
}
-
- return ret;
}
EXPORT_SYMBOL_GPL(wm_adsp_event);
diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h
index 067d807a7ca82..e53dfcf1f78ff 100644
--- a/sound/soc/codecs/wm_adsp.h
+++ b/sound/soc/codecs/wm_adsp.h
@@ -98,6 +98,8 @@ irqreturn_t wm_adsp2_bus_error(int irq, void *data);
irqreturn_t wm_halo_bus_error(int irq, void *data);
irqreturn_t wm_halo_wdt_expire(int irq, void *data);
+int wm_adsp_run(struct wm_adsp *dsp);
+void wm_adsp_stop(struct wm_adsp *dsp);
int wm_adsp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);