~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/sound/soc/codecs/cs-amp-lib.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

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

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php