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 CIRRUS_LOGIC_CALIBRATION_EFI_NAME L"CirrusSmartAmpCalibrationData" 20 #define CIRRUS_LOGIC_CALIBRATION_EFI_GUID \ 21 EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3) 22 23 #define LENOVO_SPEAKER_ID_EFI_NAME L"SdwSpeaker" 24 #define LENOVO_SPEAKER_ID_EFI_GUID \ 25 EFI_GUID(0x48df970e, 0xe27f, 0x460a, 0xb5, 0x86, 0x77, 0x19, 0x80, 0x1d, 0x92, 0x82) 26 27 #define HP_SPEAKER_ID_EFI_NAME L"HPSpeakerID" 28 #define HP_SPEAKER_ID_EFI_GUID \ 29 EFI_GUID(0xc49593a4, 0xd099, 0x419b, 0xa2, 0xc3, 0x67, 0xe9, 0x80, 0xe6, 0x1d, 0x1e) 30 31 #define HP_CALIBRATION_EFI_NAME L"SmartAmpCalibrationData" 32 #define HP_CALIBRATION_EFI_GUID \ 33 EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93) 34 35 static const struct cs_amp_lib_cal_efivar { 36 efi_char16_t *name; 37 efi_guid_t *guid; 38 } cs_amp_lib_cal_efivars[] = { 39 { 40 .name = HP_CALIBRATION_EFI_NAME, 41 .guid = &HP_CALIBRATION_EFI_GUID, 42 }, 43 { 44 .name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME, 45 .guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID, 46 }, 47 }; 48 49 static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, 50 const struct cirrus_amp_cal_controls *controls, 51 const char *ctl_name, u32 val) 52 { 53 struct cs_dsp_coeff_ctl *cs_ctl; 54 __be32 beval = cpu_to_be32(val); 55 int ret; 56 57 KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); 58 59 if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { 60 mutex_lock(&dsp->pwr_lock); 61 cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); 62 ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); 63 mutex_unlock(&dsp->pwr_lock); 64 65 if (ret < 0) { 66 dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); 67 return ret; 68 } 69 70 return 0; 71 } 72 73 return -ENODEV; 74 } 75 76 static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp, 77 const struct cirrus_amp_cal_controls *controls, 78 const struct cirrus_amp_cal_data *data) 79 { 80 int ret; 81 82 dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n", 83 data->calAmbient, data->calStatus, data->calR); 84 85 if (list_empty(&dsp->ctl_list)) { 86 dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n"); 87 return -ENOENT; 88 } 89 90 ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient); 91 if (ret) 92 return ret; 93 94 ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR); 95 if (ret) 96 return ret; 97 98 ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus); 99 if (ret) 100 return ret; 101 102 ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1); 103 if (ret) 104 return ret; 105 106 return 0; 107 } 108 109 /** 110 * cs_amp_write_cal_coeffs - Write calibration data to firmware controls. 111 * @dsp: Pointer to struct cs_dsp. 112 * @controls: Pointer to definition of firmware controls to be written. 113 * @data: Pointer to calibration data. 114 * 115 * Returns: 0 on success, else negative error value. 116 */ 117 int cs_amp_write_cal_coeffs(struct cs_dsp *dsp, 118 const struct cirrus_amp_cal_controls *controls, 119 const struct cirrus_amp_cal_data *data) 120 { 121 if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) 122 return _cs_amp_write_cal_coeffs(dsp, controls, data); 123 else 124 return -ENODEV; 125 } 126 EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, "SND_SOC_CS_AMP_LIB"); 127 128 static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, 129 efi_guid_t *guid, 130 unsigned long *size, 131 void *buf) 132 { 133 u32 attr; 134 135 KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf); 136 137 if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) 138 return efi.get_variable(name, guid, &attr, size, buf); 139 140 return EFI_NOT_FOUND; 141 } 142 143 static int cs_amp_convert_efi_status(efi_status_t status) 144 { 145 switch (status) { 146 case EFI_SUCCESS: 147 return 0; 148 case EFI_NOT_FOUND: 149 return -ENOENT; 150 case EFI_BUFFER_TOO_SMALL: 151 return -EFBIG; 152 case EFI_UNSUPPORTED: 153 case EFI_ACCESS_DENIED: 154 case EFI_SECURITY_VIOLATION: 155 return -EACCES; 156 default: 157 return -EIO; 158 } 159 } 160 161 static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev) 162 { 163 struct cirrus_amp_efi_data *efi_data; 164 unsigned long data_size = 0; 165 u8 *data; 166 efi_status_t status; 167 int i, ret; 168 169 /* Find EFI variable and get size */ 170 for (i = 0; i < ARRAY_SIZE(cs_amp_lib_cal_efivars); i++) { 171 status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, 172 cs_amp_lib_cal_efivars[i].guid, 173 &data_size, NULL); 174 if (status == EFI_BUFFER_TOO_SMALL) 175 break; 176 } 177 178 if (status != EFI_BUFFER_TOO_SMALL) 179 return ERR_PTR(-ENOENT); 180 181 if (data_size < sizeof(*efi_data)) { 182 dev_err(dev, "EFI cal variable truncated\n"); 183 return ERR_PTR(-EOVERFLOW); 184 } 185 186 /* Get variable contents into buffer */ 187 data = kmalloc(data_size, GFP_KERNEL); 188 if (!data) 189 return ERR_PTR(-ENOMEM); 190 191 status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, 192 cs_amp_lib_cal_efivars[i].guid, 193 &data_size, data); 194 if (status != EFI_SUCCESS) { 195 ret = -EINVAL; 196 goto err; 197 } 198 199 efi_data = (struct cirrus_amp_efi_data *)data; 200 dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count); 201 202 if ((efi_data->count > 128) || 203 struct_size(efi_data, data, efi_data->count) > data_size) { 204 dev_err(dev, "EFI cal variable truncated\n"); 205 ret = -EOVERFLOW; 206 goto err; 207 } 208 209 return efi_data; 210 211 err: 212 kfree(data); 213 dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret); 214 215 return ERR_PTR(ret); 216 } 217 218 static u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) 219 { 220 return ((u64)data->calTarget[1] << 32) | data->calTarget[0]; 221 } 222 223 static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 224 struct cirrus_amp_cal_data *out_data) 225 { 226 struct cirrus_amp_efi_data *efi_data; 227 struct cirrus_amp_cal_data *cal = NULL; 228 int i, ret; 229 230 efi_data = cs_amp_get_cal_efi_buffer(dev); 231 if (IS_ERR(efi_data)) 232 return PTR_ERR(efi_data); 233 234 if (target_uid) { 235 for (i = 0; i < efi_data->count; ++i) { 236 u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]); 237 238 /* Skip empty entries */ 239 if (!efi_data->data[i].calTime[0] && !efi_data->data[i].calTime[1]) 240 continue; 241 242 /* Skip entries with unpopulated silicon ID */ 243 if (cal_target == 0) 244 continue; 245 246 if (cal_target == target_uid) { 247 cal = &efi_data->data[i]; 248 break; 249 } 250 } 251 } 252 253 if (!cal && (amp_index >= 0) && (amp_index < efi_data->count) && 254 (efi_data->data[amp_index].calTime[0] || efi_data->data[amp_index].calTime[1])) { 255 u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]); 256 257 /* 258 * Treat unpopulated cal_target as a wildcard. 259 * If target_uid != 0 we can only get here if cal_target == 0 260 * or it didn't match any cal_target value. 261 * If target_uid == 0 it is a wildcard. 262 */ 263 if ((cal_target == 0) || (target_uid == 0)) 264 cal = &efi_data->data[amp_index]; 265 else 266 dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index); 267 } 268 269 if (cal) { 270 memcpy(out_data, cal, sizeof(*out_data)); 271 ret = 0; 272 } else { 273 dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid); 274 ret = -ENOENT; 275 } 276 277 kfree(efi_data); 278 279 return ret; 280 } 281 282 /** 283 * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI. 284 * @dev: struct device of the caller. 285 * @target_uid: UID to match, or zero to ignore UID matching. 286 * @amp_index: Entry index to use, or -1 to prevent lookup by index. 287 * @out_data: struct cirrus_amp_cal_data where the entry will be copied. 288 * 289 * This function can perform 3 types of lookup: 290 * 291 * (target_uid > 0, amp_index >= 0) 292 * UID search with fallback to using the array index. 293 * Search the calibration data for a non-zero calTarget that matches 294 * target_uid, and if found return that entry. Else, if the entry at 295 * [amp_index] has calTarget == 0, return that entry. Else fail. 296 * 297 * (target_uid > 0, amp_index < 0) 298 * UID search only. 299 * Search the calibration data for a non-zero calTarget that matches 300 * target_uid, and if found return that entry. Else fail. 301 * 302 * (target_uid == 0, amp_index >= 0) 303 * Array index fetch only. 304 * Return the entry at [amp_index]. 305 * 306 * An array lookup will be skipped if amp_index exceeds the number of 307 * entries in the calibration array, and in this case the return will 308 * be -ENOENT. An out-of-range amp_index does not prevent matching by 309 * target_uid - it has the same effect as passing amp_index < 0. 310 * 311 * If the EFI data is too short to be a valid entry, or the entry count 312 * in the EFI data overflows the actual length of the data, this function 313 * returns -EOVERFLOW. 314 * 315 * Return: 0 if the entry was found, -ENOENT if no entry was found, 316 * -EOVERFLOW if the EFI file is corrupt, else other error value. 317 */ 318 int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, 319 struct cirrus_amp_cal_data *out_data) 320 { 321 if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) 322 return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data); 323 else 324 return -ENOENT; 325 } 326 EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); 327 328 struct cs_amp_spkid_efi { 329 efi_char16_t *name; 330 efi_guid_t *guid; 331 u8 values[2]; 332 }; 333 334 static int cs_amp_get_efi_byte_spkid(struct device *dev, const struct cs_amp_spkid_efi *info) 335 { 336 efi_status_t status; 337 unsigned long size; 338 u8 spkid; 339 int i, ret; 340 341 size = sizeof(spkid); 342 status = cs_amp_get_efi_variable(info->name, info->guid, &size, &spkid); 343 ret = cs_amp_convert_efi_status(status); 344 if (ret < 0) 345 return ret; 346 347 if (size == 0) 348 return -ENOENT; 349 350 for (i = 0; i < ARRAY_SIZE(info->values); i++) { 351 if (info->values[i] == spkid) 352 return i; 353 } 354 355 dev_err(dev, "EFI speaker ID bad value %#x\n", spkid); 356 357 return -EINVAL; 358 } 359 360 static const struct cs_amp_spkid_efi cs_amp_spkid_byte_types[] = { 361 { 362 .name = LENOVO_SPEAKER_ID_EFI_NAME, 363 .guid = &LENOVO_SPEAKER_ID_EFI_GUID, 364 .values = { 0xd0, 0xd1 }, 365 }, 366 { 367 .name = HP_SPEAKER_ID_EFI_NAME, 368 .guid = &HP_SPEAKER_ID_EFI_GUID, 369 .values = { 0x30, 0x31 }, 370 }, 371 }; 372 373 /** 374 * cs_amp_get_vendor_spkid - get a speaker ID from vendor-specific storage 375 * @dev: pointer to struct device 376 * 377 * Known vendor-specific methods of speaker ID are checked and if one is 378 * found its speaker ID value is returned. 379 * 380 * Return: >=0 is a valid speaker ID. -ENOENT if a vendor-specific method 381 * was not found. -EACCES if the vendor-specific storage could not 382 * be read. Other error values indicate that the data from the 383 * vendor-specific storage was found but could not be understood. 384 */ 385 int cs_amp_get_vendor_spkid(struct device *dev) 386 { 387 int i, ret; 388 389 if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) && 390 !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) 391 return -ENOENT; 392 393 for (i = 0; i < ARRAY_SIZE(cs_amp_spkid_byte_types); i++) { 394 ret = cs_amp_get_efi_byte_spkid(dev, &cs_amp_spkid_byte_types[i]); 395 if (ret != -ENOENT) 396 return ret; 397 } 398 399 return -ENOENT; 400 } 401 EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB"); 402 403 static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = { 404 .get_efi_variable = cs_amp_get_efi_variable, 405 .write_cal_coeff = cs_amp_write_cal_coeff, 406 }; 407 408 const struct cs_amp_test_hooks * const cs_amp_test_hooks = 409 PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST), &cs_amp_test_hook_ptrs); 410 EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, "SND_SOC_CS_AMP_LIB"); 411 412 MODULE_DESCRIPTION("Cirrus Logic amplifier library"); 413 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); 414 MODULE_LICENSE("GPL"); 415 MODULE_IMPORT_NS("FW_CS_DSP"); 416