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 <kunit/static_stub.h> 10 #include <linux/dev_printk.h> 11 #include <linux/efi.h> 12 #include <linux/firmware/cirrus/cs_dsp.h> 13 #include <linux/module.h> 14 #include <linux/overflow.h> 15 #include <linux/slab.h> 16 #include <linux/types.h> 17 #include <sound/cs-amp-lib.h> 18 19 #define CS_AMP_CAL_GUID \ 20 EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3) 21 22 #define CS_AMP_CAL_NAME L"CirrusSmartAmpCalibrationData" 23 24 static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, 25 const struct cirrus_amp_cal_controls *controls, 26 const char *ctl_name, u32 val) 27 { 28 struct cs_dsp_coeff_ctl *cs_ctl; 29 __be32 beval = cpu_to_be32(val); 30 int ret; 31 32 KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); 33 34 if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { 35 mutex_lock(&dsp->pwr_lock); 36 cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); 37 ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); 38 mutex_unlock(&dsp->pwr_lock); 39 40 if (ret < 0) { 41 dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); 42 return ret; 43 } 44 45 return 0; 46 } 47 48 return -ENODEV; 49 } 50 51 static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp, 52 const struct cirrus_amp_cal_controls *controls, 53 const struct cirrus_amp_cal_data *data) 54 { 55 int ret; 56 57 dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n", 58 data->calAmbient, data->calStatus, data->calR); 59 60 if (list_empty(&dsp->ctl_list)) { 61 dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n"); 62 return -ENOENT; 63 } 64 65 ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient); 66 if (ret) 67 return ret; 68 69 ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR); 70 if (ret) 71 return ret; 72 73 ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus); 74 if (ret) 75 return ret; 76 77 ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1); 78 if (ret) 79 return ret; 80 81 return 0; 82 } 83 84 /** 85 * cs_amp_write_cal_coeffs - Write calibration data to firmware controls. 86 * @dsp: Pointer to struct cs_dsp. 87 * @controls: Pointer to definition of firmware controls to be written. 88 * @data: Pointer to calibration data. 89 * 90 * Returns: 0 on success, else negative error value. 91 */ 92 int cs_amp_write_cal_coeffs(struct cs_dsp *dsp, 93 const struct cirrus_amp_cal_controls *controls, 94 const struct cirrus_amp_cal_data *data) 95 { 96 if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) 97 return _cs_amp_write_cal_coeffs(dsp, controls, data); 98 else 99 return -ENODEV; 100 } 101 EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, "SND_SOC_CS_AMP_LIB"); 102 103 static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, 104 efi_guid_t *guid, 105 unsigned long *size, 106 void *buf) 107 { 108 u32 attr; 109 110 KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf); 111 112 if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) 113 return efi.get_variable(name, guid, &attr, size, buf); 114 115 return EFI_NOT_FOUND; 116 } 117 118 static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) 119 { 120 struct cirrus_amp_efi_data *efi_data; 121 unsigned long data_size = 0; 122 u8 *data; 123 efi_status_t status; 124 int ret; 125 126 /* Get real size of UEFI variable */ 127 status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, NULL); 128 if (status != EFI_BUFFER_TOO_SMALL) 129 return ERR_PTR(-ENOENT); 130 131 if (data_size < sizeof(*efi_data)) { 132 dev_err(dev, "EFI cal variable truncated\n"); 133 return ERR_PTR(-EOVERFLOW); 134 } 135 136 /* Get variable contents into buffer */ 137 data = kmalloc(data_size, GFP_KERNEL); 138 if (!data) 139 return ERR_PTR(-ENOMEM); 140 141 status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, data); 142 if (status != EFI_SUCCESS) { 143 ret = -EINVAL; 144 goto err; 145 } 146 147 efi_data = (struct cirrus_amp_efi_data *)data; 148 dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count); 149 150 if ((efi_data->count > 128) || 151 struct_size(efi_data, data, efi_data->count) > data_size) { 152 dev_err(dev, "EFI cal variable truncated\n"); 153 ret = -EOVERFLOW; 154 goto err; 155 } 156 157 return efi_data; 158 159 err: 160 kfree(data); 161 dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret); 162 163 return ERR_PTR(ret); 164 } 165 166 static u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) 167 { 168 return ((u64)data->calTarget[1] << 32) | data->calTarget[0]; 169 } 170 171 static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 172 struct cirrus_amp_cal_data *out_data) 173 { 174 struct cirrus_amp_efi_data *efi_data; 175 struct cirrus_amp_cal_data *cal = NULL; 176 int i, ret; 177 178 efi_data = cs_amp_get_cal_efi_buffer(dev); 179 if (IS_ERR(efi_data)) 180 return PTR_ERR(efi_data); 181 182 if (target_uid) { 183 for (i = 0; i < efi_data->count; ++i) { 184 u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]); 185 186 /* Skip empty entries */ 187 if (!efi_data->data[i].calTime[0] && !efi_data->data[i].calTime[1]) 188 continue; 189 190 /* Skip entries with unpopulated silicon ID */ 191 if (cal_target == 0) 192 continue; 193 194 if (cal_target == target_uid) { 195 cal = &efi_data->data[i]; 196 break; 197 } 198 } 199 } 200 201 if (!cal && (amp_index >= 0) && (amp_index < efi_data->count) && 202 (efi_data->data[amp_index].calTime[0] || efi_data->data[amp_index].calTime[1])) { 203 u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]); 204 205 /* 206 * Treat unpopulated cal_target as a wildcard. 207 * If target_uid != 0 we can only get here if cal_target == 0 208 * or it didn't match any cal_target value. 209 * If target_uid == 0 it is a wildcard. 210 */ 211 if ((cal_target == 0) || (target_uid == 0)) 212 cal = &efi_data->data[amp_index]; 213 else 214 dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index); 215 } 216 217 if (cal) { 218 memcpy(out_data, cal, sizeof(*out_data)); 219 ret = 0; 220 } else { 221 dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid); 222 ret = -ENOENT; 223 } 224 225 kfree(efi_data); 226 227 return ret; 228 } 229 230 /** 231 * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI. 232 * @dev: struct device of the caller. 233 * @target_uid: UID to match, or zero to ignore UID matching. 234 * @amp_index: Entry index to use, or -1 to prevent lookup by index. 235 * @out_data: struct cirrus_amp_cal_data where the entry will be copied. 236 * 237 * This function can perform 3 types of lookup: 238 * 239 * (target_uid > 0, amp_index >= 0) 240 * UID search with fallback to using the array index. 241 * Search the calibration data for a non-zero calTarget that matches 242 * target_uid, and if found return that entry. Else, if the entry at 243 * [amp_index] has calTarget == 0, return that entry. Else fail. 244 * 245 * (target_uid > 0, amp_index < 0) 246 * UID search only. 247 * Search the calibration data for a non-zero calTarget that matches 248 * target_uid, and if found return that entry. Else fail. 249 * 250 * (target_uid == 0, amp_index >= 0) 251 * Array index fetch only. 252 * Return the entry at [amp_index]. 253 * 254 * An array lookup will be skipped if amp_index exceeds the number of 255 * entries in the calibration array, and in this case the return will 256 * be -ENOENT. An out-of-range amp_index does not prevent matching by 257 * target_uid - it has the same effect as passing amp_index < 0. 258 * 259 * If the EFI data is too short to be a valid entry, or the entry count 260 * in the EFI data overflows the actual length of the data, this function 261 * returns -EOVERFLOW. 262 * 263 * Return: 0 if the entry was found, -ENOENT if no entry was found, 264 * -EOVERFLOW if the EFI file is corrupt, else other error value. 265 */ 266 int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 267 struct cirrus_amp_cal_data *out_data) 268 { 269 if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) 270 return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data); 271 else 272 return -ENOENT; 273 } 274 EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); 275 276 static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = { 277 .get_efi_variable = cs_amp_get_efi_variable, 278 .write_cal_coeff = cs_amp_write_cal_coeff, 279 }; 280 281 const struct cs_amp_test_hooks * const cs_amp_test_hooks = 282 PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST), &cs_amp_test_hook_ptrs); 283 EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, "SND_SOC_CS_AMP_LIB"); 284 285 MODULE_DESCRIPTION("Cirrus Logic amplifier library"); 286 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 287 MODULE_LICENSE("GPL"); 288 MODULE_IMPORT_NS("FW_CS_DSP"); 289