|
| 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 <[email protected]>"); |
| 262 | +MODULE_LICENSE("GPL"); |
| 263 | +MODULE_IMPORT_NS(FW_CS_DSP); |
0 commit comments