11cad8725SRichard Fitzgerald // SPDX-License-Identifier: GPL-2.0-only
21cad8725SRichard Fitzgerald //
31cad8725SRichard Fitzgerald // Common code for Cirrus Logic Smart Amplifiers
41cad8725SRichard Fitzgerald //
51cad8725SRichard Fitzgerald // Copyright (C) 2024 Cirrus Logic, Inc. and
61cad8725SRichard Fitzgerald // Cirrus Logic International Semiconductor Ltd.
71cad8725SRichard Fitzgerald
81cad8725SRichard Fitzgerald #include <asm/byteorder.h>
917786231SRichard Fitzgerald #include <kunit/static_stub.h>
101cad8725SRichard Fitzgerald #include <linux/dev_printk.h>
111cad8725SRichard Fitzgerald #include <linux/efi.h>
121cad8725SRichard Fitzgerald #include <linux/firmware/cirrus/cs_dsp.h>
131cad8725SRichard Fitzgerald #include <linux/module.h>
141cad8725SRichard Fitzgerald #include <linux/slab.h>
151cad8725SRichard Fitzgerald #include <linux/types.h>
161cad8725SRichard Fitzgerald #include <sound/cs-amp-lib.h>
171cad8725SRichard Fitzgerald
181cad8725SRichard Fitzgerald #define CS_AMP_CAL_GUID \
191cad8725SRichard Fitzgerald EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3)
201cad8725SRichard Fitzgerald
211cad8725SRichard Fitzgerald #define CS_AMP_CAL_NAME L"CirrusSmartAmpCalibrationData"
221cad8725SRichard Fitzgerald
cs_amp_write_cal_coeff(struct cs_dsp * dsp,const struct cirrus_amp_cal_controls * controls,const char * ctl_name,u32 val)231cad8725SRichard Fitzgerald static int cs_amp_write_cal_coeff(struct cs_dsp *dsp,
241cad8725SRichard Fitzgerald const struct cirrus_amp_cal_controls *controls,
251cad8725SRichard Fitzgerald const char *ctl_name, u32 val)
261cad8725SRichard Fitzgerald {
271cad8725SRichard Fitzgerald struct cs_dsp_coeff_ctl *cs_ctl;
281cad8725SRichard Fitzgerald __be32 beval = cpu_to_be32(val);
291cad8725SRichard Fitzgerald int ret;
301cad8725SRichard Fitzgerald
3117786231SRichard Fitzgerald KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val);
3217786231SRichard Fitzgerald
331cad8725SRichard Fitzgerald if (IS_REACHABLE(CONFIG_FW_CS_DSP)) {
341cad8725SRichard Fitzgerald mutex_lock(&dsp->pwr_lock);
351cad8725SRichard Fitzgerald cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id);
361cad8725SRichard Fitzgerald ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval));
371cad8725SRichard Fitzgerald mutex_unlock(&dsp->pwr_lock);
381cad8725SRichard Fitzgerald
391cad8725SRichard Fitzgerald if (ret < 0) {
401cad8725SRichard Fitzgerald dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret);
411cad8725SRichard Fitzgerald return ret;
421cad8725SRichard Fitzgerald }
431cad8725SRichard Fitzgerald
441cad8725SRichard Fitzgerald return 0;
451cad8725SRichard Fitzgerald }
461cad8725SRichard Fitzgerald
471cad8725SRichard Fitzgerald return -ENODEV;
481cad8725SRichard Fitzgerald }
491cad8725SRichard Fitzgerald
_cs_amp_write_cal_coeffs(struct cs_dsp * dsp,const struct cirrus_amp_cal_controls * controls,const struct cirrus_amp_cal_data * data)501cad8725SRichard Fitzgerald static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
511cad8725SRichard Fitzgerald const struct cirrus_amp_cal_controls *controls,
521cad8725SRichard Fitzgerald const struct cirrus_amp_cal_data *data)
531cad8725SRichard Fitzgerald {
541cad8725SRichard Fitzgerald int ret;
551cad8725SRichard Fitzgerald
561cad8725SRichard Fitzgerald dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n",
571cad8725SRichard Fitzgerald data->calAmbient, data->calStatus, data->calR);
581cad8725SRichard Fitzgerald
59e2d7ad71SSimon Trimmer if (list_empty(&dsp->ctl_list)) {
60e2d7ad71SSimon Trimmer dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n");
61e2d7ad71SSimon Trimmer return -ENOENT;
62e2d7ad71SSimon Trimmer }
63e2d7ad71SSimon Trimmer
641cad8725SRichard Fitzgerald ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient);
651cad8725SRichard Fitzgerald if (ret)
661cad8725SRichard Fitzgerald return ret;
671cad8725SRichard Fitzgerald
681cad8725SRichard Fitzgerald ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR);
691cad8725SRichard Fitzgerald if (ret)
701cad8725SRichard Fitzgerald return ret;
711cad8725SRichard Fitzgerald
721cad8725SRichard Fitzgerald ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus);
731cad8725SRichard Fitzgerald if (ret)
741cad8725SRichard Fitzgerald return ret;
751cad8725SRichard Fitzgerald
761cad8725SRichard Fitzgerald ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1);
771cad8725SRichard Fitzgerald if (ret)
781cad8725SRichard Fitzgerald return ret;
791cad8725SRichard Fitzgerald
801cad8725SRichard Fitzgerald return 0;
811cad8725SRichard Fitzgerald }
821cad8725SRichard Fitzgerald
831cad8725SRichard Fitzgerald /**
841cad8725SRichard Fitzgerald * cs_amp_write_cal_coeffs - Write calibration data to firmware controls.
851cad8725SRichard Fitzgerald * @dsp: Pointer to struct cs_dsp.
861cad8725SRichard Fitzgerald * @controls: Pointer to definition of firmware controls to be written.
871cad8725SRichard Fitzgerald * @data: Pointer to calibration data.
881cad8725SRichard Fitzgerald *
891cad8725SRichard Fitzgerald * Returns: 0 on success, else negative error value.
901cad8725SRichard Fitzgerald */
cs_amp_write_cal_coeffs(struct cs_dsp * dsp,const struct cirrus_amp_cal_controls * controls,const struct cirrus_amp_cal_data * data)911cad8725SRichard Fitzgerald int cs_amp_write_cal_coeffs(struct cs_dsp *dsp,
921cad8725SRichard Fitzgerald const struct cirrus_amp_cal_controls *controls,
931cad8725SRichard Fitzgerald const struct cirrus_amp_cal_data *data)
941cad8725SRichard Fitzgerald {
9517786231SRichard Fitzgerald if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
961cad8725SRichard Fitzgerald return _cs_amp_write_cal_coeffs(dsp, controls, data);
971cad8725SRichard Fitzgerald else
981cad8725SRichard Fitzgerald return -ENODEV;
991cad8725SRichard Fitzgerald }
1001cad8725SRichard Fitzgerald EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, SND_SOC_CS_AMP_LIB);
1011cad8725SRichard Fitzgerald
cs_amp_get_efi_variable(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)1021cad8725SRichard Fitzgerald static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name,
1031cad8725SRichard Fitzgerald efi_guid_t *guid,
1041cad8725SRichard Fitzgerald unsigned long *size,
1051cad8725SRichard Fitzgerald void *buf)
1061cad8725SRichard Fitzgerald {
1071cad8725SRichard Fitzgerald u32 attr;
1081cad8725SRichard Fitzgerald
10917786231SRichard Fitzgerald KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf);
11017786231SRichard Fitzgerald
111dc268085SRichard Fitzgerald if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
1121cad8725SRichard Fitzgerald return efi.get_variable(name, guid, &attr, size, buf);
1131cad8725SRichard Fitzgerald
1141cad8725SRichard Fitzgerald return EFI_NOT_FOUND;
1151cad8725SRichard Fitzgerald }
1161cad8725SRichard Fitzgerald
cs_amp_get_cal_efi_buffer(struct device * dev)1171cad8725SRichard Fitzgerald static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev)
1181cad8725SRichard Fitzgerald {
1191cad8725SRichard Fitzgerald struct cirrus_amp_efi_data *efi_data;
1201cad8725SRichard Fitzgerald unsigned long data_size = 0;
1211cad8725SRichard Fitzgerald u8 *data;
1221cad8725SRichard Fitzgerald efi_status_t status;
1231cad8725SRichard Fitzgerald int ret;
1241cad8725SRichard Fitzgerald
1251cad8725SRichard Fitzgerald /* Get real size of UEFI variable */
1261cad8725SRichard Fitzgerald status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, NULL);
1271cad8725SRichard Fitzgerald if (status != EFI_BUFFER_TOO_SMALL)
1281cad8725SRichard Fitzgerald return ERR_PTR(-ENOENT);
1291cad8725SRichard Fitzgerald
1301cad8725SRichard Fitzgerald if (data_size < sizeof(*efi_data)) {
1311cad8725SRichard Fitzgerald dev_err(dev, "EFI cal variable truncated\n");
1321cad8725SRichard Fitzgerald return ERR_PTR(-EOVERFLOW);
1331cad8725SRichard Fitzgerald }
1341cad8725SRichard Fitzgerald
1351cad8725SRichard Fitzgerald /* Get variable contents into buffer */
1361cad8725SRichard Fitzgerald data = kmalloc(data_size, GFP_KERNEL);
1371cad8725SRichard Fitzgerald if (!data)
1381cad8725SRichard Fitzgerald return ERR_PTR(-ENOMEM);
1391cad8725SRichard Fitzgerald
1401cad8725SRichard Fitzgerald status = cs_amp_get_efi_variable(CS_AMP_CAL_NAME, &CS_AMP_CAL_GUID, &data_size, data);
1411cad8725SRichard Fitzgerald if (status != EFI_SUCCESS) {
1421cad8725SRichard Fitzgerald ret = -EINVAL;
1431cad8725SRichard Fitzgerald goto err;
1441cad8725SRichard Fitzgerald }
1451cad8725SRichard Fitzgerald
1461cad8725SRichard Fitzgerald efi_data = (struct cirrus_amp_efi_data *)data;
1471cad8725SRichard Fitzgerald dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count);
1481cad8725SRichard Fitzgerald
1491cad8725SRichard Fitzgerald if ((efi_data->count > 128) ||
1501cad8725SRichard Fitzgerald offsetof(struct cirrus_amp_efi_data, data[efi_data->count]) > data_size) {
1511cad8725SRichard Fitzgerald dev_err(dev, "EFI cal variable truncated\n");
1521cad8725SRichard Fitzgerald ret = -EOVERFLOW;
1531cad8725SRichard Fitzgerald goto err;
1541cad8725SRichard Fitzgerald }
1551cad8725SRichard Fitzgerald
1561cad8725SRichard Fitzgerald return efi_data;
1571cad8725SRichard Fitzgerald
1581cad8725SRichard Fitzgerald err:
1591cad8725SRichard Fitzgerald kfree(data);
1601cad8725SRichard Fitzgerald dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret);
1611cad8725SRichard Fitzgerald
1621cad8725SRichard Fitzgerald return ERR_PTR(ret);
1631cad8725SRichard Fitzgerald }
1641cad8725SRichard Fitzgerald
cs_amp_cal_target_u64(const struct cirrus_amp_cal_data * data)1651cad8725SRichard Fitzgerald static u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data)
1661cad8725SRichard Fitzgerald {
1671cad8725SRichard Fitzgerald return ((u64)data->calTarget[1] << 32) | data->calTarget[0];
1681cad8725SRichard Fitzgerald }
1691cad8725SRichard Fitzgerald
_cs_amp_get_efi_calibration_data(struct device * dev,u64 target_uid,int amp_index,struct cirrus_amp_cal_data * out_data)1701cad8725SRichard Fitzgerald static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
1711cad8725SRichard Fitzgerald struct cirrus_amp_cal_data *out_data)
1721cad8725SRichard Fitzgerald {
1731cad8725SRichard Fitzgerald struct cirrus_amp_efi_data *efi_data;
1741cad8725SRichard Fitzgerald struct cirrus_amp_cal_data *cal = NULL;
1751cad8725SRichard Fitzgerald int i, ret;
1761cad8725SRichard Fitzgerald
1771cad8725SRichard Fitzgerald efi_data = cs_amp_get_cal_efi_buffer(dev);
1781cad8725SRichard Fitzgerald if (IS_ERR(efi_data))
1791cad8725SRichard Fitzgerald return PTR_ERR(efi_data);
1801cad8725SRichard Fitzgerald
1811cad8725SRichard Fitzgerald if (target_uid) {
1821cad8725SRichard Fitzgerald for (i = 0; i < efi_data->count; ++i) {
1831cad8725SRichard Fitzgerald u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]);
1841cad8725SRichard Fitzgerald
185*bb448556SRichard Fitzgerald /* Skip empty entries */
186*bb448556SRichard Fitzgerald if (!efi_data->data[i].calTime[0] && !efi_data->data[i].calTime[1])
187*bb448556SRichard Fitzgerald continue;
188*bb448556SRichard Fitzgerald
1891cad8725SRichard Fitzgerald /* Skip entries with unpopulated silicon ID */
1901cad8725SRichard Fitzgerald if (cal_target == 0)
1911cad8725SRichard Fitzgerald continue;
1921cad8725SRichard Fitzgerald
1931cad8725SRichard Fitzgerald if (cal_target == target_uid) {
1941cad8725SRichard Fitzgerald cal = &efi_data->data[i];
1951cad8725SRichard Fitzgerald break;
1961cad8725SRichard Fitzgerald }
1971cad8725SRichard Fitzgerald }
1981cad8725SRichard Fitzgerald }
1991cad8725SRichard Fitzgerald
200*bb448556SRichard Fitzgerald if (!cal && (amp_index >= 0) && (amp_index < efi_data->count) &&
201*bb448556SRichard Fitzgerald (efi_data->data[amp_index].calTime[0] || efi_data->data[amp_index].calTime[1])) {
2021cad8725SRichard Fitzgerald u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]);
2031cad8725SRichard Fitzgerald
2041cad8725SRichard Fitzgerald /*
2051cad8725SRichard Fitzgerald * Treat unpopulated cal_target as a wildcard.
2061cad8725SRichard Fitzgerald * If target_uid != 0 we can only get here if cal_target == 0
2071cad8725SRichard Fitzgerald * or it didn't match any cal_target value.
2081cad8725SRichard Fitzgerald * If target_uid == 0 it is a wildcard.
2091cad8725SRichard Fitzgerald */
2101cad8725SRichard Fitzgerald if ((cal_target == 0) || (target_uid == 0))
2111cad8725SRichard Fitzgerald cal = &efi_data->data[amp_index];
2121cad8725SRichard Fitzgerald else
2131cad8725SRichard Fitzgerald dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index);
2141cad8725SRichard Fitzgerald }
2151cad8725SRichard Fitzgerald
2161cad8725SRichard Fitzgerald if (cal) {
2171cad8725SRichard Fitzgerald memcpy(out_data, cal, sizeof(*out_data));
2181cad8725SRichard Fitzgerald ret = 0;
2191cad8725SRichard Fitzgerald } else {
2201cad8725SRichard Fitzgerald dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid);
2211cad8725SRichard Fitzgerald ret = -ENOENT;
2221cad8725SRichard Fitzgerald }
2231cad8725SRichard Fitzgerald
2241cad8725SRichard Fitzgerald kfree(efi_data);
2251cad8725SRichard Fitzgerald
2261cad8725SRichard Fitzgerald return ret;
2271cad8725SRichard Fitzgerald }
2281cad8725SRichard Fitzgerald
2291cad8725SRichard Fitzgerald /**
2301cad8725SRichard Fitzgerald * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI.
2311cad8725SRichard Fitzgerald * @dev: struct device of the caller.
2321cad8725SRichard Fitzgerald * @target_uid: UID to match, or zero to ignore UID matching.
2331cad8725SRichard Fitzgerald * @amp_index: Entry index to use, or -1 to prevent lookup by index.
2341cad8725SRichard Fitzgerald * @out_data: struct cirrus_amp_cal_data where the entry will be copied.
2351cad8725SRichard Fitzgerald *
2361cad8725SRichard Fitzgerald * This function can perform 3 types of lookup:
2371cad8725SRichard Fitzgerald *
2381cad8725SRichard Fitzgerald * (target_uid > 0, amp_index >= 0)
2391cad8725SRichard Fitzgerald * UID search with fallback to using the array index.
2401cad8725SRichard Fitzgerald * Search the calibration data for a non-zero calTarget that matches
2411cad8725SRichard Fitzgerald * target_uid, and if found return that entry. Else, if the entry at
2421cad8725SRichard Fitzgerald * [amp_index] has calTarget == 0, return that entry. Else fail.
2431cad8725SRichard Fitzgerald *
2441cad8725SRichard Fitzgerald * (target_uid > 0, amp_index < 0)
2451cad8725SRichard Fitzgerald * UID search only.
2461cad8725SRichard Fitzgerald * Search the calibration data for a non-zero calTarget that matches
2471cad8725SRichard Fitzgerald * target_uid, and if found return that entry. Else fail.
2481cad8725SRichard Fitzgerald *
2491cad8725SRichard Fitzgerald * (target_uid == 0, amp_index >= 0)
2501cad8725SRichard Fitzgerald * Array index fetch only.
2511cad8725SRichard Fitzgerald * Return the entry at [amp_index].
2521cad8725SRichard Fitzgerald *
2531cad8725SRichard Fitzgerald * An array lookup will be skipped if amp_index exceeds the number of
2541cad8725SRichard Fitzgerald * entries in the calibration array, and in this case the return will
2551cad8725SRichard Fitzgerald * be -ENOENT. An out-of-range amp_index does not prevent matching by
2561cad8725SRichard Fitzgerald * target_uid - it has the same effect as passing amp_index < 0.
2571cad8725SRichard Fitzgerald *
2581cad8725SRichard Fitzgerald * If the EFI data is too short to be a valid entry, or the entry count
2591cad8725SRichard Fitzgerald * in the EFI data overflows the actual length of the data, this function
2601cad8725SRichard Fitzgerald * returns -EOVERFLOW.
2611cad8725SRichard Fitzgerald *
2621cad8725SRichard Fitzgerald * Return: 0 if the entry was found, -ENOENT if no entry was found,
2631cad8725SRichard Fitzgerald * -EOVERFLOW if the EFI file is corrupt, else other error value.
2641cad8725SRichard Fitzgerald */
cs_amp_get_efi_calibration_data(struct device * dev,u64 target_uid,int amp_index,struct cirrus_amp_cal_data * out_data)2651cad8725SRichard Fitzgerald int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index,
2661cad8725SRichard Fitzgerald struct cirrus_amp_cal_data *out_data)
2671cad8725SRichard Fitzgerald {
26817786231SRichard Fitzgerald if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST))
2691cad8725SRichard Fitzgerald return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data);
2701cad8725SRichard Fitzgerald else
2711cad8725SRichard Fitzgerald return -ENOENT;
2721cad8725SRichard Fitzgerald }
2731cad8725SRichard Fitzgerald EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, SND_SOC_CS_AMP_LIB);
2741cad8725SRichard Fitzgerald
27517786231SRichard Fitzgerald static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = {
27617786231SRichard Fitzgerald .get_efi_variable = cs_amp_get_efi_variable,
27717786231SRichard Fitzgerald .write_cal_coeff = cs_amp_write_cal_coeff,
27817786231SRichard Fitzgerald };
27917786231SRichard Fitzgerald
28017786231SRichard Fitzgerald const struct cs_amp_test_hooks * const cs_amp_test_hooks =
28117786231SRichard Fitzgerald PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST), &cs_amp_test_hook_ptrs);
28217786231SRichard Fitzgerald EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, SND_SOC_CS_AMP_LIB);
28317786231SRichard Fitzgerald
2841cad8725SRichard Fitzgerald MODULE_DESCRIPTION("Cirrus Logic amplifier library");
2851cad8725SRichard Fitzgerald MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
2861cad8725SRichard Fitzgerald MODULE_LICENSE("GPL");
2871cad8725SRichard Fitzgerald MODULE_IMPORT_NS(FW_CS_DSP);
288