Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+529 -19
+52
include/sound/cs-amp-lib.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + /* 3 + * Copyright (C) 2024 Cirrus Logic, Inc. and 4 + * Cirrus Logic International Semiconductor Ltd. 5 + */ 6 + 7 + #ifndef CS_AMP_LIB_H 8 + #define CS_AMP_LIB_H 9 + 10 + #include <linux/efi.h> 11 + #include <linux/types.h> 12 + 13 + struct cs_dsp; 14 + 15 + struct cirrus_amp_cal_data { 16 + u32 calTarget[2]; 17 + u32 calTime[2]; 18 + s8 calAmbient; 19 + u8 calStatus; 20 + u16 calR; 21 + } __packed; 22 + 23 + struct cirrus_amp_efi_data { 24 + u32 size; 25 + u32 count; 26 + struct cirrus_amp_cal_data data[]; 27 + } __packed; 28 + 29 + /** 30 + * struct cirrus_amp_cal_controls - definition of firmware calibration controls 31 + * @alg_id: ID of algorithm containing the controls. 32 + * @mem_region: DSP memory region containing the controls. 33 + * @ambient: Name of control for calAmbient value. 34 + * @calr: Name of control for calR value. 35 + * @status: Name of control for calStatus value. 36 + * @checksum: Name of control for checksum value. 37 + */ 38 + struct cirrus_amp_cal_controls { 39 + unsigned int alg_id; 40 + int mem_region; 41 + const char *ambient; 42 + const char *calr; 43 + const char *status; 44 + const char *checksum; 45 + }; 46 + 47 + int cs_amp_write_cal_coeffs(struct cs_dsp *dsp, 48 + const struct cirrus_amp_cal_controls *controls, 49 + const struct cirrus_amp_cal_data *data); 50 + int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 51 + struct cirrus_amp_cal_data *out_data); 52 + #endif /* CS_AMP_LIB_H */
+10
include/sound/cs35l56.h
··· 12 12 #include <linux/firmware/cirrus/cs_dsp.h> 13 13 #include <linux/regulator/consumer.h> 14 14 #include <linux/regmap.h> 15 + #include <sound/cs-amp-lib.h> 15 16 16 17 #define CS35L56_DEVID 0x0000000 17 18 #define CS35L56_REVID 0x0000004 ··· 24 23 #define CS35L56_BLOCK_ENABLES2 0x000201C 25 24 #define CS35L56_REFCLK_INPUT 0x0002C04 26 25 #define CS35L56_GLOBAL_SAMPLE_RATE 0x0002C0C 26 + #define CS35L56_OTP_MEM_53 0x00300D4 27 + #define CS35L56_OTP_MEM_54 0x00300D8 28 + #define CS35L56_OTP_MEM_55 0x00300DC 27 29 #define CS35L56_ASP1_ENABLES1 0x0004800 28 30 #define CS35L56_ASP1_CONTROL1 0x0004804 29 31 #define CS35L56_ASP1_CONTROL2 0x0004808 ··· 266 262 bool fw_patched; 267 263 bool secured; 268 264 bool can_hibernate; 265 + bool cal_data_valid; 266 + s8 cal_index; 267 + struct cirrus_amp_cal_data cal_data; 269 268 struct gpio_desc *reset_gpio; 270 269 }; 271 270 272 271 extern struct regmap_config cs35l56_regmap_i2c; 273 272 extern struct regmap_config cs35l56_regmap_spi; 274 273 extern struct regmap_config cs35l56_regmap_sdw; 274 + 275 + extern const struct cirrus_amp_cal_controls cs35l56_calibration_controls; 275 276 276 277 extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC]; 277 278 extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC]; ··· 295 286 int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); 296 287 int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); 297 288 void cs35l56_init_cs_dsp(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp); 289 + int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base); 298 290 int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base, 299 291 bool *fw_missing, unsigned int *fw_version); 300 292 int cs35l56_hw_init(struct cs35l56_base *cs35l56_base);
+2
sound/pci/hda/Kconfig
··· 162 162 select SND_HDA_SCODEC_CS35L56 163 163 select SND_HDA_CIRRUS_SCODEC 164 164 select SND_HDA_CS_DSP_CONTROLS 165 + select SND_SOC_CS_AMP_LIB 165 166 help 166 167 Say Y or M here to include CS35L56 amplifier support with 167 168 I2C control. ··· 178 177 select SND_HDA_SCODEC_CS35L56 179 178 select SND_HDA_CIRRUS_SCODEC 180 179 select SND_HDA_CS_DSP_CONTROLS 180 + select SND_SOC_CS_AMP_LIB 181 181 help 182 182 Say Y or M here to include CS35L56 amplifier support with 183 183 SPI control.
+32 -7
sound/pci/hda/cs35l56_hda.c
··· 14 14 #include <linux/regmap.h> 15 15 #include <linux/slab.h> 16 16 #include <sound/core.h> 17 + #include <sound/cs-amp-lib.h> 17 18 #include <sound/hda_codec.h> 18 19 #include <sound/tlv.h> 19 20 #include "cirrus_scodec.h" ··· 548 547 hda_cs_dsp_add_controls(&cs35l56->cs_dsp, &info); 549 548 } 550 549 550 + static void cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) 551 + { 552 + int ret; 553 + 554 + if (!cs35l56->base.cal_data_valid || cs35l56->base.secured) 555 + return; 556 + 557 + ret = cs_amp_write_cal_coeffs(&cs35l56->cs_dsp, 558 + &cs35l56_calibration_controls, 559 + &cs35l56->base.cal_data); 560 + if (ret < 0) 561 + dev_warn(cs35l56->base.dev, "Failed to write calibration: %d\n", ret); 562 + else 563 + dev_info(cs35l56->base.dev, "Calibration applied\n"); 564 + } 565 + 551 566 static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) 552 567 { 553 568 const struct firmware *coeff_firmware = NULL; ··· 635 618 if (coeff_filename) 636 619 dev_dbg(cs35l56->base.dev, "Loaded Coefficients: %s\n", coeff_filename); 637 620 638 - if (!firmware_missing) { 639 - ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); 640 - if (ret) 641 - goto err_powered_up; 642 - } else if (wmfw_firmware || coeff_firmware) { 643 - /* If we downloaded firmware, reset the device and wait for it to boot */ 621 + /* If we downloaded firmware, reset the device and wait for it to boot */ 622 + if (firmware_missing && (wmfw_firmware || coeff_firmware)) { 644 623 cs35l56_system_reset(&cs35l56->base, false); 645 624 regcache_mark_dirty(cs35l56->base.regmap); 646 625 ret = cs35l56_wait_for_firmware_boot(&cs35l56->base); ··· 658 645 ret = cs_dsp_run(&cs35l56->cs_dsp); 659 646 if (ret) 660 647 dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret); 648 + 649 + cs35l56_hda_apply_calibration(cs35l56); 650 + ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); 651 + if (ret) 652 + cs_dsp_stop(&cs35l56->cs_dsp); 661 653 662 654 err_powered_up: 663 655 if (!cs35l56->base.fw_patched) ··· 971 953 goto err; 972 954 } 973 955 956 + cs35l56->base.cal_index = cs35l56->index; 957 + 974 958 cs35l56_init_cs_dsp(&cs35l56->base, &cs35l56->cs_dsp); 975 959 cs35l56->cs_dsp.client_ops = &cs35l56_hda_client_ops; 976 960 ··· 1007 987 1008 988 /* Disable auto-hibernate so that runtime_pm has control */ 1009 989 ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE); 990 + if (ret) 991 + goto err; 992 + 993 + ret = cs35l56_get_calibration(&cs35l56->base); 1010 994 if (ret) 1011 995 goto err; 1012 996 ··· 1088 1064 EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56); 1089 1065 1090 1066 MODULE_DESCRIPTION("CS35L56 HDA Driver"); 1067 + MODULE_IMPORT_NS(FW_CS_DSP); 1091 1068 MODULE_IMPORT_NS(SND_HDA_CIRRUS_SCODEC); 1092 1069 MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS); 1093 1070 MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED); 1071 + MODULE_IMPORT_NS(SND_SOC_CS_AMP_LIB); 1094 1072 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 1095 1073 MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>"); 1096 1074 MODULE_LICENSE("GPL"); 1097 - MODULE_IMPORT_NS(FW_CS_DSP);
+4
sound/soc/codecs/Kconfig
··· 729 729 If you say yes here you will get support for the 730 730 ChromeOS Embedded Controller's Audio Codec. 731 731 732 + config SND_SOC_CS_AMP_LIB 733 + tristate 734 + 732 735 config SND_SOC_CS35L32 733 736 tristate "Cirrus Logic CS35L32 CODEC" 734 737 depends on I2C ··· 800 797 tristate 801 798 802 799 config SND_SOC_CS35L56_SHARED 800 + select SND_SOC_CS_AMP_LIB 803 801 tristate 804 802 805 803 config SND_SOC_CS35L56_I2C
+2
sound/soc/codecs/Makefile
··· 59 59 snd-soc-cpcap-objs := cpcap.o 60 60 snd-soc-cq93vc-objs := cq93vc.o 61 61 snd-soc-cros-ec-codec-objs := cros_ec_codec.o 62 + snd-soc-cs-amp-lib-objs := cs-amp-lib.o 62 63 snd-soc-cs35l32-objs := cs35l32.o 63 64 snd-soc-cs35l33-objs := cs35l33.o 64 65 snd-soc-cs35l34-objs := cs35l34.o ··· 453 452 obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o 454 453 obj-$(CONFIG_SND_SOC_CPCAP) += snd-soc-cpcap.o 455 454 obj-$(CONFIG_SND_SOC_CROS_EC_CODEC) += snd-soc-cros-ec-codec.o 455 + obj-$(CONFIG_SND_SOC_CS_AMP_LIB) += snd-soc-cs-amp-lib.o 456 456 obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o 457 457 obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o 458 458 obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o
+263
sound/soc/codecs/cs-amp-lib.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + // 3 + // Common code for Cirrus Logic Smart Amplifiers 4 + // 5 + // Copyright (C) 2024 Cirrus Logic, Inc. and 6 + // Cirrus Logic International Semiconductor Ltd. 7 + 8 + #include <asm/byteorder.h> 9 + #include <linux/dev_printk.h> 10 + #include <linux/efi.h> 11 + #include <linux/firmware/cirrus/cs_dsp.h> 12 + #include <linux/module.h> 13 + #include <linux/slab.h> 14 + #include <linux/types.h> 15 + #include <sound/cs-amp-lib.h> 16 + 17 + #define CS_AMP_CAL_GUID \ 18 + EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3) 19 + 20 + #define CS_AMP_CAL_NAME L"CirrusSmartAmpCalibrationData" 21 + 22 + static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, 23 + const struct cirrus_amp_cal_controls *controls, 24 + const char *ctl_name, u32 val) 25 + { 26 + struct cs_dsp_coeff_ctl *cs_ctl; 27 + __be32 beval = cpu_to_be32(val); 28 + int ret; 29 + 30 + if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { 31 + mutex_lock(&dsp->pwr_lock); 32 + cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); 33 + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); 34 + mutex_unlock(&dsp->pwr_lock); 35 + 36 + if (ret < 0) { 37 + dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); 38 + return ret; 39 + } 40 + 41 + return 0; 42 + } 43 + 44 + return -ENODEV; 45 + } 46 + 47 + static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp, 48 + const struct cirrus_amp_cal_controls *controls, 49 + const struct cirrus_amp_cal_data *data) 50 + { 51 + int ret; 52 + 53 + dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n", 54 + data->calAmbient, data->calStatus, data->calR); 55 + 56 + ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient); 57 + if (ret) 58 + return ret; 59 + 60 + ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR); 61 + if (ret) 62 + return ret; 63 + 64 + ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus); 65 + if (ret) 66 + return ret; 67 + 68 + ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1); 69 + if (ret) 70 + return ret; 71 + 72 + return 0; 73 + } 74 + 75 + /** 76 + * cs_amp_write_cal_coeffs - Write calibration data to firmware controls. 77 + * @dsp: Pointer to struct cs_dsp. 78 + * @controls: Pointer to definition of firmware controls to be written. 79 + * @data: Pointer to calibration data. 80 + * 81 + * Returns: 0 on success, else negative error value. 82 + */ 83 + int cs_amp_write_cal_coeffs(struct cs_dsp *dsp, 84 + const struct cirrus_amp_cal_controls *controls, 85 + const struct cirrus_amp_cal_data *data) 86 + { 87 + if (IS_REACHABLE(CONFIG_FW_CS_DSP)) 88 + return _cs_amp_write_cal_coeffs(dsp, controls, data); 89 + else 90 + return -ENODEV; 91 + } 92 + EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, SND_SOC_CS_AMP_LIB); 93 + 94 + static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, 95 + efi_guid_t *guid, 96 + unsigned long *size, 97 + void *buf) 98 + { 99 + u32 attr; 100 + 101 + if (IS_ENABLED(CONFIG_EFI)) 102 + return efi.get_variable(name, guid, &attr, size, buf); 103 + 104 + return EFI_NOT_FOUND; 105 + } 106 + 107 + static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) 108 + { 109 + struct cirrus_amp_efi_data *efi_data; 110 + unsigned long data_size = 0; 111 + u8 *data; 112 + efi_status_t status; 113 + int ret; 114 + 115 + /* Get real size of UEFI variable */ 116 + status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, NULL); 117 + if (status != EFI_BUFFER_TOO_SMALL) 118 + return ERR_PTR(-ENOENT); 119 + 120 + if (data_size < sizeof(*efi_data)) { 121 + dev_err(dev, "EFI cal variable truncated\n"); 122 + return ERR_PTR(-EOVERFLOW); 123 + } 124 + 125 + /* Get variable contents into buffer */ 126 + data = kmalloc(data_size, GFP_KERNEL); 127 + if (!data) 128 + return ERR_PTR(-ENOMEM); 129 + 130 + status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, data); 131 + if (status != EFI_SUCCESS) { 132 + ret = -EINVAL; 133 + goto err; 134 + } 135 + 136 + efi_data = (struct cirrus_amp_efi_data *)data; 137 + dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count); 138 + 139 + if ((efi_data->count > 128) || 140 + offsetof(struct cirrus_amp_efi_data, data[efi_data->count]) > data_size) { 141 + dev_err(dev, "EFI cal variable truncated\n"); 142 + ret = -EOVERFLOW; 143 + goto err; 144 + } 145 + 146 + return efi_data; 147 + 148 + err: 149 + kfree(data); 150 + dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret); 151 + 152 + return ERR_PTR(ret); 153 + } 154 + 155 + static u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) 156 + { 157 + return ((u64)data->calTarget[1] << 32) | data->calTarget[0]; 158 + } 159 + 160 + static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 161 + struct cirrus_amp_cal_data *out_data) 162 + { 163 + struct cirrus_amp_efi_data *efi_data; 164 + struct cirrus_amp_cal_data *cal = NULL; 165 + int i, ret; 166 + 167 + efi_data = cs_amp_get_cal_efi_buffer(dev); 168 + if (IS_ERR(efi_data)) 169 + return PTR_ERR(efi_data); 170 + 171 + if (target_uid) { 172 + for (i = 0; i < efi_data->count; ++i) { 173 + u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]); 174 + 175 + /* Skip entries with unpopulated silicon ID */ 176 + if (cal_target == 0) 177 + continue; 178 + 179 + if (cal_target == target_uid) { 180 + cal = &efi_data->data[i]; 181 + break; 182 + } 183 + } 184 + } 185 + 186 + if (!cal && (amp_index >= 0) && (amp_index < efi_data->count)) { 187 + u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]); 188 + 189 + /* 190 + * Treat unpopulated cal_target as a wildcard. 191 + * If target_uid != 0 we can only get here if cal_target == 0 192 + * or it didn't match any cal_target value. 193 + * If target_uid == 0 it is a wildcard. 194 + */ 195 + if ((cal_target == 0) || (target_uid == 0)) 196 + cal = &efi_data->data[amp_index]; 197 + else 198 + dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index); 199 + } 200 + 201 + if (cal) { 202 + memcpy(out_data, cal, sizeof(*out_data)); 203 + ret = 0; 204 + } else { 205 + dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid); 206 + ret = -ENOENT; 207 + } 208 + 209 + kfree(efi_data); 210 + 211 + return ret; 212 + } 213 + 214 + /** 215 + * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI. 216 + * @dev: struct device of the caller. 217 + * @target_uid: UID to match, or zero to ignore UID matching. 218 + * @amp_index: Entry index to use, or -1 to prevent lookup by index. 219 + * @out_data: struct cirrus_amp_cal_data where the entry will be copied. 220 + * 221 + * This function can perform 3 types of lookup: 222 + * 223 + * (target_uid > 0, amp_index >= 0) 224 + * UID search with fallback to using the array index. 225 + * Search the calibration data for a non-zero calTarget that matches 226 + * target_uid, and if found return that entry. Else, if the entry at 227 + * [amp_index] has calTarget == 0, return that entry. Else fail. 228 + * 229 + * (target_uid > 0, amp_index < 0) 230 + * UID search only. 231 + * Search the calibration data for a non-zero calTarget that matches 232 + * target_uid, and if found return that entry. Else fail. 233 + * 234 + * (target_uid == 0, amp_index >= 0) 235 + * Array index fetch only. 236 + * Return the entry at [amp_index]. 237 + * 238 + * An array lookup will be skipped if amp_index exceeds the number of 239 + * entries in the calibration array, and in this case the return will 240 + * be -ENOENT. An out-of-range amp_index does not prevent matching by 241 + * target_uid - it has the same effect as passing amp_index < 0. 242 + * 243 + * If the EFI data is too short to be a valid entry, or the entry count 244 + * in the EFI data overflows the actual length of the data, this function 245 + * returns -EOVERFLOW. 246 + * 247 + * Return: 0 if the entry was found, -ENOENT if no entry was found, 248 + * -EOVERFLOW if the EFI file is corrupt, else other error value. 249 + */ 250 + int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 251 + struct cirrus_amp_cal_data *out_data) 252 + { 253 + if (IS_ENABLED(CONFIG_EFI)) 254 + return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data); 255 + else 256 + return -ENOENT; 257 + } 258 + EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, SND_SOC_CS_AMP_LIB); 259 + 260 + MODULE_DESCRIPTION("Cirrus Logic amplifier library"); 261 + MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 262 + MODULE_LICENSE("GPL"); 263 + MODULE_IMPORT_NS(FW_CS_DSP);
+20
sound/soc/codecs/cs35l56-sdw.c
··· 161 161 .val_format_endian_default = REGMAP_ENDIAN_BIG, 162 162 }; 163 163 164 + static int cs35l56_sdw_set_cal_index(struct cs35l56_private *cs35l56) 165 + { 166 + int ret; 167 + 168 + /* SoundWire UniqueId is used to index the calibration array */ 169 + ret = sdw_read_no_pm(cs35l56->sdw_peripheral, SDW_SCP_DEVID_0); 170 + if (ret < 0) 171 + return ret; 172 + 173 + cs35l56->base.cal_index = ret & 0xf; 174 + 175 + return 0; 176 + } 177 + 164 178 static void cs35l56_sdw_init(struct sdw_slave *peripheral) 165 179 { 166 180 struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); 167 181 int ret; 168 182 169 183 pm_runtime_get_noresume(cs35l56->base.dev); 184 + 185 + if (cs35l56->base.cal_index < 0) { 186 + ret = cs35l56_sdw_set_cal_index(cs35l56); 187 + if (ret < 0) 188 + goto out; 189 + } 170 190 171 191 regcache_cache_only(cs35l56->base.regmap, false); 172 192
+83
sound/soc/codecs/cs35l56-shared.c
··· 5 5 // Copyright (C) 2023 Cirrus Logic, Inc. and 6 6 // Cirrus Logic International Semiconductor Ltd. 7 7 8 + #include <linux/firmware/cirrus/wmfw.h> 8 9 #include <linux/gpio/consumer.h> 9 10 #include <linux/regmap.h> 10 11 #include <linux/regulator/consumer.h> 11 12 #include <linux/types.h> 13 + #include <sound/cs-amp-lib.h> 12 14 13 15 #include "cs35l56.h" 14 16 ··· 38 36 EXPORT_SYMBOL_NS_GPL(cs35l56_set_patch, SND_SOC_CS35L56_SHARED); 39 37 40 38 static const struct reg_default cs35l56_reg_defaults[] = { 39 + /* no defaults for OTP_MEM - first read populates cache */ 40 + 41 41 { CS35L56_ASP1_ENABLES1, 0x00000000 }, 42 42 { CS35L56_ASP1_CONTROL1, 0x00000028 }, 43 43 { CS35L56_ASP1_CONTROL2, 0x18180200 }, ··· 95 91 case CS35L56_BLOCK_ENABLES2: 96 92 case CS35L56_REFCLK_INPUT: 97 93 case CS35L56_GLOBAL_SAMPLE_RATE: 94 + case CS35L56_OTP_MEM_53: 95 + case CS35L56_OTP_MEM_54: 96 + case CS35L56_OTP_MEM_55: 98 97 case CS35L56_ASP1_ENABLES1: 99 98 case CS35L56_ASP1_CONTROL1: 100 99 case CS35L56_ASP1_CONTROL2: ··· 635 628 } 636 629 EXPORT_SYMBOL_NS_GPL(cs35l56_init_cs_dsp, SND_SOC_CS35L56_SHARED); 637 630 631 + struct cs35l56_pte { 632 + u8 x; 633 + u8 wafer_id; 634 + u8 pte[2]; 635 + u8 lot[3]; 636 + u8 y; 637 + u8 unused[3]; 638 + u8 dvs; 639 + } __packed; 640 + static_assert((sizeof(struct cs35l56_pte) % sizeof(u32)) == 0); 641 + 642 + static int cs35l56_read_silicon_uid(struct cs35l56_base *cs35l56_base, u64 *uid) 643 + { 644 + struct cs35l56_pte pte; 645 + u64 unique_id; 646 + int ret; 647 + 648 + ret = regmap_raw_read(cs35l56_base->regmap, CS35L56_OTP_MEM_53, &pte, sizeof(pte)); 649 + if (ret) { 650 + dev_err(cs35l56_base->dev, "Failed to read OTP: %d\n", ret); 651 + return ret; 652 + } 653 + 654 + unique_id = pte.lot[2] | (pte.lot[1] << 8) | (pte.lot[0] << 16); 655 + unique_id <<= 32; 656 + unique_id |= pte.x | (pte.y << 8) | (pte.wafer_id << 16) | (pte.dvs << 24); 657 + 658 + dev_dbg(cs35l56_base->dev, "UniqueID = %#llx\n", unique_id); 659 + 660 + *uid = unique_id; 661 + 662 + return 0; 663 + } 664 + 665 + /* Firmware calibration controls */ 666 + const struct cirrus_amp_cal_controls cs35l56_calibration_controls = { 667 + .alg_id = 0x9f210, 668 + .mem_region = WMFW_ADSP2_YM, 669 + .ambient = "CAL_AMBIENT", 670 + .calr = "CAL_R", 671 + .status = "CAL_STATUS", 672 + .checksum = "CAL_CHECKSUM", 673 + }; 674 + EXPORT_SYMBOL_NS_GPL(cs35l56_calibration_controls, SND_SOC_CS35L56_SHARED); 675 + 676 + int cs35l56_get_calibration(struct cs35l56_base *cs35l56_base) 677 + { 678 + u64 silicon_uid; 679 + int ret; 680 + 681 + /* Driver can't apply calibration to a secured part, so skip */ 682 + if (cs35l56_base->secured) 683 + return 0; 684 + 685 + ret = cs35l56_read_silicon_uid(cs35l56_base, &silicon_uid); 686 + if (ret < 0) 687 + return ret; 688 + 689 + ret = cs_amp_get_efi_calibration_data(cs35l56_base->dev, silicon_uid, 690 + cs35l56_base->cal_index, 691 + &cs35l56_base->cal_data); 692 + 693 + /* Only return an error status if probe should be aborted */ 694 + if ((ret == -ENOENT) || (ret == -EOVERFLOW)) 695 + return 0; 696 + 697 + if (ret < 0) 698 + return ret; 699 + 700 + cs35l56_base->cal_data_valid = true; 701 + 702 + return 0; 703 + } 704 + EXPORT_SYMBOL_NS_GPL(cs35l56_get_calibration, SND_SOC_CS35L56_SHARED); 705 + 638 706 int cs35l56_read_prot_status(struct cs35l56_base *cs35l56_base, 639 707 bool *fw_missing, unsigned int *fw_version) 640 708 { ··· 1004 922 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 1005 923 MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>"); 1006 924 MODULE_LICENSE("GPL"); 925 + MODULE_IMPORT_NS(SND_SOC_CS_AMP_LIB);
+41 -3
sound/soc/codecs/cs35l56.c
··· 23 23 #include <linux/soundwire/sdw.h> 24 24 #include <linux/types.h> 25 25 #include <linux/workqueue.h> 26 + #include <sound/cs-amp-lib.h> 26 27 #include <sound/pcm.h> 27 28 #include <sound/pcm_params.h> 28 29 #include <sound/soc.h> ··· 803 802 } 804 803 }; 805 804 805 + static int cs35l56_write_cal(struct cs35l56_private *cs35l56) 806 + { 807 + int ret; 808 + 809 + if (cs35l56->base.secured || !cs35l56->base.cal_data_valid) 810 + return -ENODATA; 811 + 812 + ret = wm_adsp_run(&cs35l56->dsp); 813 + if (ret) 814 + return ret; 815 + 816 + ret = cs_amp_write_cal_coeffs(&cs35l56->dsp.cs_dsp, 817 + &cs35l56_calibration_controls, 818 + &cs35l56->base.cal_data); 819 + 820 + wm_adsp_stop(&cs35l56->dsp); 821 + 822 + if (ret == 0) 823 + dev_info(cs35l56->base.dev, "Calibration applied\n"); 824 + 825 + return ret; 826 + } 827 + 806 828 static void cs35l56_reinit_patch(struct cs35l56_private *cs35l56) 807 829 { 808 830 int ret; 809 831 810 832 /* Use wm_adsp to load and apply the firmware patch and coefficient files */ 811 833 ret = wm_adsp_power_up(&cs35l56->dsp, true); 812 - if (ret) 834 + if (ret) { 813 835 dev_dbg(cs35l56->base.dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret); 814 - else 815 - cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); 836 + return; 837 + } 838 + 839 + cs35l56_write_cal(cs35l56); 840 + 841 + /* Always REINIT after applying patch or coefficients */ 842 + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); 816 843 } 817 844 818 845 static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing) ··· 902 873 regmap_clear_bits(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS, 903 874 CS35L56_FIRMWARE_MISSING); 904 875 cs35l56->base.fw_patched = true; 876 + 877 + if (cs35l56_write_cal(cs35l56) == 0) 878 + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); 905 879 906 880 err_unlock: 907 881 mutex_unlock(&cs35l56->base.irq_lock); ··· 1388 1356 1389 1357 init_completion(&cs35l56->init_completion); 1390 1358 mutex_init(&cs35l56->base.irq_lock); 1359 + cs35l56->base.cal_index = -1; 1391 1360 cs35l56->speaker_id = -ENOENT; 1392 1361 1393 1362 dev_set_drvdata(cs35l56->base.dev, cs35l56); ··· 1490 1457 if (ret) 1491 1458 return ret; 1492 1459 1460 + ret = cs35l56_get_calibration(&cs35l56->base); 1461 + if (ret) 1462 + return ret; 1463 + 1493 1464 if (!cs35l56->base.reset_gpio) { 1494 1465 dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n"); 1495 1466 cs35l56->soft_resetting = true; ··· 1578 1541 1579 1542 MODULE_DESCRIPTION("ASoC CS35L56 driver"); 1580 1543 MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED); 1544 + MODULE_IMPORT_NS(SND_SOC_CS_AMP_LIB); 1581 1545 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 1582 1546 MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>"); 1583 1547 MODULE_LICENSE("GPL");
+18 -9
sound/soc/codecs/wm_adsp.c
··· 1092 1092 dsp->fatal_error = false; 1093 1093 } 1094 1094 1095 + int wm_adsp_run(struct wm_adsp *dsp) 1096 + { 1097 + flush_work(&dsp->boot_work); 1098 + 1099 + return cs_dsp_run(&dsp->cs_dsp); 1100 + } 1101 + EXPORT_SYMBOL_GPL(wm_adsp_run); 1102 + 1103 + void wm_adsp_stop(struct wm_adsp *dsp) 1104 + { 1105 + cs_dsp_stop(&dsp->cs_dsp); 1106 + } 1107 + EXPORT_SYMBOL_GPL(wm_adsp_stop); 1108 + 1095 1109 int wm_adsp_event(struct snd_soc_dapm_widget *w, 1096 1110 struct snd_kcontrol *kcontrol, int event) 1097 1111 { 1098 1112 struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); 1099 1113 struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); 1100 1114 struct wm_adsp *dsp = &dsps[w->shift]; 1101 - int ret = 0; 1102 1115 1103 1116 switch (event) { 1104 1117 case SND_SOC_DAPM_POST_PMU: 1105 - flush_work(&dsp->boot_work); 1106 - ret = cs_dsp_run(&dsp->cs_dsp); 1107 - break; 1118 + return wm_adsp_run(dsp); 1108 1119 case SND_SOC_DAPM_PRE_PMD: 1109 - cs_dsp_stop(&dsp->cs_dsp); 1110 - break; 1120 + wm_adsp_stop(dsp); 1121 + return 0; 1111 1122 default: 1112 - break; 1123 + return 0; 1113 1124 } 1114 - 1115 - return ret; 1116 1125 } 1117 1126 EXPORT_SYMBOL_GPL(wm_adsp_event); 1118 1127
+2
sound/soc/codecs/wm_adsp.h
··· 98 98 irqreturn_t wm_halo_bus_error(int irq, void *data); 99 99 irqreturn_t wm_halo_wdt_expire(int irq, void *data); 100 100 101 + int wm_adsp_run(struct wm_adsp *dsp); 102 + void wm_adsp_stop(struct wm_adsp *dsp); 101 103 int wm_adsp_event(struct snd_soc_dapm_widget *w, 102 104 struct snd_kcontrol *kcontrol, int event); 103 105