mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 20:22:09 +00:00
ALSA: cs35l56: Apply calibration from EFI
Merge series from Richard Fitzgerald <rf@opensource.cirrus.com>: Factory calibration of the speakers stores the calibration information into an EFI variable. This set of patches adds support for applying speaker calibration data from that EFI variable. The HDA patch (#5) depends on the ASoC patches #2 and #3
This commit is contained in:
commit
0c4ebb28b3
52
include/sound/cs-amp-lib.h
Normal file
52
include/sound/cs-amp-lib.h
Normal file
@ -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 */
|
@ -12,6 +12,7 @@
|
||||
#include <linux/firmware/cirrus/cs_dsp.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <sound/cs-amp-lib.h>
|
||||
|
||||
#define CS35L56_DEVID 0x0000000
|
||||
#define CS35L56_REVID 0x0000004
|
||||
@ -23,6 +24,9 @@
|
||||
#define CS35L56_BLOCK_ENABLES2 0x000201C
|
||||
#define CS35L56_REFCLK_INPUT 0x0002C04
|
||||
#define CS35L56_GLOBAL_SAMPLE_RATE 0x0002C0C
|
||||
#define CS35L56_OTP_MEM_53 0x00300D4
|
||||
#define CS35L56_OTP_MEM_54 0x00300D8
|
||||
#define CS35L56_OTP_MEM_55 0x00300DC
|
||||
#define CS35L56_ASP1_ENABLES1 0x0004800
|
||||
#define CS35L56_ASP1_CONTROL1 0x0004804
|
||||
#define CS35L56_ASP1_CONTROL2 0x0004808
|
||||
@ -262,6 +266,9 @@ struct cs35l56_base {
|
||||
bool fw_patched;
|
||||
bool secured;
|
||||
bool can_hibernate;
|
||||
bool cal_data_valid;
|
||||
s8 cal_index;
|
||||
struct cirrus_amp_cal_data cal_data;
|
||||
struct gpio_desc *reset_gpio;
|
||||
};
|
||||
|
||||
@ -269,6 +276,8 @@ extern struct regmap_config cs35l56_regmap_i2c;
|
||||
extern struct regmap_config cs35l56_regmap_spi;
|
||||
extern struct regmap_config cs35l56_regmap_sdw;
|
||||
|
||||
extern const struct cirrus_amp_cal_controls cs35l56_calibration_controls;
|
||||
|
||||
extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
|
||||
extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
|
||||
|
||||
@ -286,6 +295,7 @@ int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base);
|
||||
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);
|
||||
int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base,
|
||||
bool *fw_missing, unsigned int *fw_version);
|
||||
int cs35l56_hw_init(struct cs35l56_base *cs35l56_base);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
263
sound/soc/codecs/cs-amp-lib.c
Normal file
263
sound/soc/codecs/cs-amp-lib.c
Normal file
@ -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);
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user