ASoC: rt722-sdca: Add a control to support CAE firmware update

Realtek CAE requires specific tuning parameters based on
the system vendor and SKU.
This patch adds a kcontrol to trigger the firmware loading process.

Signed-off-by: Jack Yu <jack.yu@realtek.com>
Link: https://patch.msgid.link/20260520053243.3645180-1-jack.yu@realtek.com
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Jack Yu 2026-05-20 13:32:43 +08:00 committed by Mark Brown
parent b7b0f9bb1a
commit 14b5795b63
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
3 changed files with 346 additions and 8 deletions

View File

@ -19,7 +19,9 @@
static int rt722_sdca_mbq_size(struct device *dev, unsigned int reg)
{
switch (reg) {
case 0x2f01 ... 0x2f0a:
case 0x22f0 ... 0x22f1:
case 0x2f01 ... 0x2f0c:
case 0x2f21 ... 0x2f24:
case 0x2f35 ... 0x2f36:
case 0x2f50 ... 0x2f52:
case 0x2f54:
@ -84,6 +86,21 @@ static int rt722_sdca_mbq_size(struct device *dev, unsigned int reg)
case SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_CS31,
RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, 0):
case RT722_BUF_ADDR_HID1 ... RT722_BUF_ADDR_HID2:
case 0x44011000 ... 0x440115ff:
case 0x44012000:
case 0x44012021:
case 0x44012022:
case 0x44012025:
case 0x44021000 ... 0x440211ff:
case 0x44022000:
case 0x44022019:
case 0x4402201a:
case 0x4402201d:
case 0x44041000 ... 0x440415ff:
case 0x44042000:
case 0x44042019:
case 0x4404201a:
case 0x4404201d:
return 1;
case 0x2000000 ... 0x2000024:
case 0x2000029 ... 0x200004a:
@ -95,8 +112,10 @@ static int rt722_sdca_mbq_size(struct device *dev, unsigned int reg)
case 0x200007f:
case 0x2000082 ... 0x200008e:
case 0x2000090 ... 0x2000094:
case 0x20000b1:
case 0x20000b4:
case 0x3110000:
case 0x5300000 ... 0x5300002:
case 0x5300000 ... 0x5300300:
case 0x5400002:
case 0x5600000 ... 0x5600007:
case 0x5700000 ... 0x5700004:
@ -175,6 +194,7 @@ static bool rt722_sdca_volatile_register(struct device *dev, unsigned int reg)
case SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_PDE23, RT722_SDCA_CTL_ACTUAL_POWER_STATE, 0):
case RT722_BUF_ADDR_HID1 ... RT722_BUF_ADDR_HID2:
case 0x2000000:
case 0x2000007:
case 0x200000d:
case 0x2000019:
case 0x2000020:
@ -186,6 +206,21 @@ static bool rt722_sdca_volatile_register(struct device *dev, unsigned int reg)
case 0x3110000:
case 0x5800003:
case 0x5810000:
case 0x44011000 ... 0x440115ff:
case 0x44012000:
case 0x44012021:
case 0x44012022:
case 0x44012025:
case 0x44021000 ... 0x440211ff:
case 0x44022000:
case 0x44022019:
case 0x4402201a:
case 0x4402201d:
case 0x44041000 ... 0x440415ff:
case 0x44042000:
case 0x44042019:
case 0x4404201a:
case 0x4404201d:
return true;
default:
return false;

View File

@ -7,19 +7,21 @@
//
#include <linux/bitops.h>
#include <sound/core.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/firmware.h>
#include <linux/init.h>
#include <sound/initval.h>
#include <sound/jack.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <sound/pcm.h>
#include <linux/pm_runtime.h>
#include <sound/pcm_params.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/slab.h>
#include <linux/soundwire/sdw_registers.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
@ -344,6 +346,280 @@ static int rt722_sdca_set_jack_detect(struct snd_soc_component *component,
return 0;
}
static int rt722_cae_load(struct rt722_sdca_priv *rt722)
{
struct device *dev = &rt722->slave->dev;
static const char func_tag[] = "FUNC";
static const char xu_tag[] = "XU";
const char *dmi_vendor, *dmi_product, *dmi_sku;
char *cae_filename;
const struct firmware *cae_fw = NULL;
unsigned int cae_st_spk, cae_st_hp, cae_st_mic;
unsigned int func, value;
unsigned int combined_val;
unsigned int addr, size;
unsigned int fw_offset;
unsigned char mbq_high_val = 0;
unsigned char *param_data;
unsigned char *fw_data;
char tag[5];
char *space;
int v_len, p_len, s_len;
int ret = 0, i;
int retry = 50;
dmi_vendor = dmi_get_system_info(DMI_SYS_VENDOR);
dmi_product = dmi_get_system_info(DMI_PRODUCT_NAME);
dmi_sku = dmi_get_system_info(DMI_PRODUCT_SKU);
if (!dmi_vendor || !dmi_product || !dmi_sku) {
dev_warn(dev, "%s: Incomplete DMI info\n", __func__);
return -EINVAL;
}
space = strchr(dmi_vendor, ' ');
v_len = space ? space - dmi_vendor : strlen(dmi_vendor);
space = strchr(dmi_product, ' ');
p_len = space ? space - dmi_product : strlen(dmi_product);
space = strchr(dmi_sku, ' ');
s_len = space ? space - dmi_sku : strlen(dmi_sku);
cae_filename = kasprintf(GFP_KERNEL,
"realtek/rt722/rt722_RAE_%.*s_%.*s_%.*s.dat",
v_len, dmi_vendor,
p_len, dmi_product,
s_len, dmi_sku);
if (!cae_filename)
return -ENOMEM;
dev_dbg(dev, "%s: try to load CAE file %s\n", __func__, cae_filename);
regmap_write(rt722->regmap, RT722_SPK_CAE_PARAM1, 0x5f);
regmap_write(rt722->regmap, RT722_HP_CAE_PARAM39, 0x5f);
regmap_write(rt722->regmap, RT722_MIC_CAE_PARAM39, 0x5f);
usleep_range(50000, 60000);
request_firmware(&cae_fw, cae_filename, dev);
kfree(cae_filename);
if (!cae_fw) {
dev_err(dev, "%s: Failed to load CAE firmware\n", __func__);
return -ENOENT;
}
regmap_read(rt722->regmap, RT722_SPK_CAE_PARAM38, &cae_st_spk);
regmap_read(rt722->regmap, RT722_HP_CAE_PARAM68, &cae_st_hp);
regmap_read(rt722->regmap, RT722_MIC_CAE_PARAM99, &cae_st_mic);
cae_st_spk &= 0x80;
cae_st_hp &= 0x80;
cae_st_mic &= 0x80;
dev_dbg(dev, "%s(%d) spk_crc:%x, hp_crc:%x, mic_crc:%x\n",
__func__, __LINE__, cae_st_spk, cae_st_hp, cae_st_mic);
if (cae_st_spk)
rt722_sdca_index_update_bits(rt722, RT722_VENDOR_EQ_CAE,
RT722_EQ_CTRL_SPK, 0x0008, 0x0008);
else if (cae_st_hp)
rt722_sdca_index_update_bits(rt722, RT722_VENDOR_EQ_CAE,
RT722_EQ_CTRL_HP, 0x0008, 0x0008);
else if (cae_st_mic)
rt722_sdca_index_update_bits(rt722, RT722_VENDOR_EQ_CAE,
RT722_EQ_CTRL_DMIC, 0x0008, 0x0008);
rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG,
RT722_MISC_CTRL1, 0x8000, 0x8000);
regmap_update_bits(rt722->regmap, RT722_SPK_CAE_PARAM34, 0x1, 0x0);
regmap_update_bits(rt722->regmap, RT722_HP_CAE_PARAM64, 0x1, 0x0);
regmap_update_bits(rt722->regmap, RT722_MIC_CAE_PARAM95, 0x1, 0x0);
while (--retry) {
regmap_read(rt722->regmap, RT722_SPK_CAE_PARAM35, &cae_st_spk);
regmap_read(rt722->regmap, RT722_HP_CAE_PARAM65, &cae_st_hp);
regmap_read(rt722->regmap, RT722_MIC_CAE_PARAM96, &cae_st_mic);
dev_dbg(dev, "%s(%d) cae_st_spk:%x, cae_st_hp:%x, cae_st_mic:%x\n",
__func__, __LINE__, cae_st_spk, cae_st_hp, cae_st_mic);
if ((cae_st_spk & 0x40) && (cae_st_hp & 0x40) && (cae_st_mic & 0x40))
break;
usleep_range(1000, 1100);
}
if (!retry && !((cae_st_spk & 0x40) && (cae_st_hp & 0x40)
&& (cae_st_mic & 0x40))) {
dev_err(dev, "%s: CAE is not ready to be loaded.\n", __func__);
ret = -ETIMEDOUT;
goto out_release;
}
dev_dbg(dev, "%s, cae_fw size=0x%zx, start\n", __func__, cae_fw->size);
rt722_sdca_index_write(rt722, RT722_VENDOR_EQ_CAE,
RT722_EQ_CTRL_AMIC, 0x8000);
rt722_sdca_index_write(rt722, RT722_VENDOR_EQ_CAE,
RT722_EQ_CTRL_DMIC, 0x8004);
rt722_sdca_index_write(rt722, RT722_VENDOR_EQ_CAE,
RT722_EQ_CTRL_HP, 0x8074);
rt722_sdca_index_write(rt722, RT722_VENDOR_EQ_CAE,
RT722_EQ_CTRL_SPK, 0xa074);
regcache_cache_bypass(rt722->regmap, true);
for (fw_offset = 0; fw_offset < cae_fw->size;) {
if (fw_offset + 12 > cae_fw->size) {
dev_err(dev, "%s: Unexpected end of firmware\n", __func__);
ret = -EINVAL;
goto verify_abort;
}
fw_data = (unsigned char *)&cae_fw->data[fw_offset];
memcpy(tag, fw_data, 4);
tag[4] = '\0';
if (strcmp(tag, xu_tag) == 0) {
dev_dbg(dev, "%s: This is a XU tag", __func__);
memcpy(&addr, (fw_data + 4), 4);
memcpy(&size, (fw_data + 8), 4);
if (size == 0 || size > cae_fw->size - fw_offset - 12) {
dev_err(dev, "%s: Invalid payload size: %u\n", __func__, size);
ret = -EINVAL;
goto verify_abort;
}
param_data = (unsigned char *)(fw_data + 12);
dev_dbg(dev, "%s: addr=0x%x, size=0x%x\n", __func__, addr, size);
if ((addr <= 0x05302300 && addr >= 0x05300000) ||
(addr <= 0x020020b4 && addr >= 0x020000b1)) {
if (addr & BIT(13)) {
mbq_high_val = param_data[0];
dev_dbg(dev, "MBQ: High Byte 0x%02x\n", mbq_high_val);
fw_offset += (size + 12);
continue;
} else {
regcache_cache_bypass(rt722->regmap, false);
combined_val = (mbq_high_val << 8) | param_data[0];
if (addr == 0x20000b1 || addr == 0x20000b4)
combined_val |= (0x2 << 8);
ret = regmap_write(rt722->regmap, addr, combined_val);
if (ret) {
dev_err(dev,
"MBQ fail: addr=0x%x ret=%d\n", addr, ret);
regcache_cache_bypass(rt722->regmap, true);
goto verify_abort;
}
dev_dbg(dev, "MBQ-reg=0x%x, value=0x%x\n",
addr, combined_val);
fw_offset += (size + 12);
regcache_cache_bypass(rt722->regmap, true);
continue;
}
}
for (i = 0; i < size; i++) {
ret = regmap_write(rt722->regmap, addr + i, param_data[i]);
if (ret) {
dev_err(dev,
"CAE write fail: addr=0x%x ret=%d\n",
addr + i, ret);
goto verify_abort;
}
}
fw_offset += (size + 12);
} else if (strcmp(tag, func_tag) == 0) {
dev_dbg(dev, "%s: This is a FUNC tag", __func__);
memcpy(&func, (fw_data + 4), 4);
memcpy(&value, (fw_data + 8), 4);
dev_dbg(dev, "%s: func=0x%x, value=0x%x\n",
__func__, func, value);
if (func == 1)
msleep(value);
fw_offset += 12;
} else {
dev_err(dev, "%s: No XU/FUNC tag at fw_offset=0x%x\n",
__func__, fw_offset);
ret = -EINVAL;
goto verify_abort;
}
}
rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG,
RT722_MISC_CTRL1, 0x8000, 0x0000);
regcache_cache_bypass(rt722->regmap, false);
rt722->cae_update_done = 1;
dev_dbg(dev, "%s: CAE FW update done.\n", __func__);
release_firmware(cae_fw);
return 0;
verify_abort:
regcache_cache_bypass(rt722->regmap, false);
if (!ret)
ret = -EIO;
out_release:
rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG,
RT722_MISC_CTRL1, 0x8000, 0x0000);
release_firmware(cae_fw);
dev_err(dev, "%s: CAE FW update aborted (ret=%d).\n", __func__, ret);
return ret;
}
static int rt722_cae_update_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
int ret, changed = 0;
if (!rt722->hw_init)
return 0;
ret = pm_runtime_resume_and_get(component->dev);
if (ret < 0 && ret != -EACCES)
return ret;
if (ucontrol->value.integer.value[0]) {
if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_OFF) {
ret = rt722_cae_load(rt722);
if (ret) {
dev_err(component->dev, "CAE load failed: %d\n", ret);
goto out;
} else
changed = 1;
}
} else {
if (rt722->cae_update_done) {
rt722->cae_update_done = 0;
changed = 1;
}
}
out:
pm_runtime_mark_last_busy(component->dev);
pm_runtime_put_autosuspend(component->dev);
return ret < 0 ? ret : changed;
}
static int rt722_cae_update_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component);
ucontrol->value.integer.value[0] = rt722->cae_update_done;
return 0;
}
/* For SDCA control DAC/ADC Gain */
static int rt722_sdca_set_gain_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
@ -795,6 +1071,9 @@ static const struct snd_kcontrol_new rt722_sdca_controls[] = {
RT722_SDCA_CTL_FU_CH_GAIN, CH_01),
rt722_sdca_dmic_set_gain_get, rt722_sdca_dmic_set_gain_put,
4, 3, boost_vol_tlv),
/* CAE firmware update */
SOC_SINGLE_EXT("CAE Update", SND_SOC_NOPM, 0, 1, 0,
rt722_cae_update_get, rt722_cae_update_put),
};
static const char * const adc22_mux_text[] = {
@ -1376,6 +1655,7 @@ int rt722_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave
rt722->fu0f_mixer_l_mute = rt722->fu0f_mixer_r_mute = true;
rt722->fu1e_mixer_mute[0] = rt722->fu1e_mixer_mute[1] =
rt722->fu1e_mixer_mute[2] = rt722->fu1e_mixer_mute[3] = true;
rt722->cae_update_done = 0;
return devm_snd_soc_register_component(dev,
&soc_sdca_dev_rt722, rt722_sdca_dai, ARRAY_SIZE(rt722_sdca_dai));

View File

@ -44,6 +44,7 @@ struct rt722_sdca_priv {
bool fu1e_dapm_mute;
bool fu1e_mixer_mute[4];
int hw_vid;
int cae_update_done;
};
struct rt722_sdca_dmic_kctrl_priv {
@ -55,6 +56,7 @@ struct rt722_sdca_dmic_kctrl_priv {
/* NID */
#define RT722_VENDOR_REG 0x20
#define RT722_VENDOR_EQ_CAE 0x53
#define RT722_VENDOR_CALI 0x58
#define RT722_VENDOR_SPK_EFUSE 0x5c
#define RT722_VENDOR_IMS_DRE 0x5b
@ -64,6 +66,7 @@ struct rt722_sdca_dmic_kctrl_priv {
/* Index (NID:20h) */
#define RT722_JD_PRODUCT_NUM 0x00
#define RT722_ANALOG_BIAS_CTL3 0x04
#define RT722_MISC_CTRL1 0x07
#define RT722_JD_CTRL1 0x09
#define RT722_LDO2_3_CTL1 0x0e
#define RT722_LDO1_CTL 0x1a
@ -79,6 +82,12 @@ struct rt722_sdca_dmic_kctrl_priv {
#define RT722_SW_CONFIG1 0x8a
#define RT722_SW_CONFIG2 0x8b
/* Index (NID:53h) */
#define RT722_EQ_CTRL_SPK 0x00
#define RT722_EQ_CTRL_HP 0x100
#define RT722_EQ_CTRL_DMIC 0x200
#define RT722_EQ_CTRL_AMIC 0x300
/* Index (NID:58h) */
#define RT722_DAC_DC_CALI_CTL0 0x00
#define RT722_DAC_DC_CALI_CTL1 0x01
@ -156,6 +165,20 @@ struct rt722_sdca_dmic_kctrl_priv {
#define RT722_BUF_ADDR_HID1 0x44030000
#define RT722_BUF_ADDR_HID2 0x44030020
/* RT722 CAE parameter settings */
#define RT722_SPK_CAE_PARAM1 0x44012000
#define RT722_SPK_CAE_PARAM34 0x44012021
#define RT722_SPK_CAE_PARAM35 0x44012022
#define RT722_SPK_CAE_PARAM38 0x44012025
#define RT722_HP_CAE_PARAM39 0x44022000
#define RT722_HP_CAE_PARAM64 0x44022019
#define RT722_HP_CAE_PARAM65 0x4402201a
#define RT722_HP_CAE_PARAM68 0x4402201d
#define RT722_MIC_CAE_PARAM39 0x44042000
#define RT722_MIC_CAE_PARAM95 0x44042019
#define RT722_MIC_CAE_PARAM96 0x4404201a
#define RT722_MIC_CAE_PARAM99 0x4404201d
/* RT722 SDCA Control - function number */
#define FUNC_NUM_JACK_CODEC 0x01
#define FUNC_NUM_MIC_ARRAY 0x02