brintos

brintos / linux-shallow public Read only

0
0
Text · 8.5 KiB · 51b128c Raw
288 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2//3// Common code for Cirrus Logic Smart Amplifiers4//5// Copyright (C) 2024 Cirrus Logic, Inc. and6//               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 23static 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 50static 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 */91int 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	else98		return -ENODEV;99}100EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, SND_SOC_CS_AMP_LIB);101 102static 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 117static 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 158err: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 165static 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 170static 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 == 0207		 * 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		else213			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 matches241 *	target_uid, and if found return that entry. Else, if the entry at242 *	[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 matches247 *	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 of254 * entries in the calibration array, and in this case the return will255 * be -ENOENT. An out-of-range amp_index does not prevent matching by256 * 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 count259 * in the EFI data overflows the actual length of the data, this function260 * 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 */265int 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	else271		return -ENOENT;272}273EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, SND_SOC_CS_AMP_LIB);274 275static 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 280const 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);282EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, SND_SOC_CS_AMP_LIB);283 284MODULE_DESCRIPTION("Cirrus Logic amplifier library");285MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");286MODULE_LICENSE("GPL");287MODULE_IMPORT_NS(FW_CS_DSP);288