| // SPDX-License-Identifier: GPL-2.0-only |
| // |
| // Common code for Cirrus Logic Smart Amplifiers |
| // |
| // Copyright (C) 2024 Cirrus Logic, Inc. and |
| // Cirrus Logic International Semiconductor Ltd. |
| |
| #include <asm/byteorder.h> |
| #include <kunit/static_stub.h> |
| #include <linux/debugfs.h> |
| #include <linux/dev_printk.h> |
| #include <linux/efi.h> |
| #include <linux/firmware/cirrus/cs_dsp.h> |
| #include <linux/math64.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/overflow.h> |
| #include <linux/slab.h> |
| #include <linux/timekeeping.h> |
| #include <linux/types.h> |
| #include <sound/cs-amp-lib.h> |
| |
| #define CIRRUS_LOGIC_CALIBRATION_EFI_NAME L"CirrusSmartAmpCalibrationData" |
| #define CIRRUS_LOGIC_CALIBRATION_EFI_GUID \ |
| EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3) |
| |
| #define LENOVO_SPEAKER_ID_EFI_NAME L"SdwSpeaker" |
| #define LENOVO_SPEAKER_ID_EFI_GUID \ |
| EFI_GUID(0x48df970e, 0xe27f, 0x460a, 0xb5, 0x86, 0x77, 0x19, 0x80, 0x1d, 0x92, 0x82) |
| |
| #define HP_SPEAKER_ID_EFI_NAME L"HPSpeakerID" |
| #define HP_SPEAKER_ID_EFI_GUID \ |
| EFI_GUID(0xc49593a4, 0xd099, 0x419b, 0xa2, 0xc3, 0x67, 0xe9, 0x80, 0xe6, 0x1d, 0x1e) |
| |
| #define HP_CALIBRATION_EFI_NAME L"SmartAmpCalibrationData" |
| #define HP_CALIBRATION_EFI_GUID \ |
| EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93) |
| |
| static const struct cs_amp_lib_cal_efivar { |
| efi_char16_t *name; |
| efi_guid_t *guid; |
| } cs_amp_lib_cal_efivars[] = { |
| { |
| .name = HP_CALIBRATION_EFI_NAME, |
| .guid = &HP_CALIBRATION_EFI_GUID, |
| }, |
| { |
| .name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME, |
| .guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID, |
| }, |
| }; |
| |
| #define CS_AMP_CAL_DEFAULT_EFI_ATTR \ |
| (EFI_VARIABLE_NON_VOLATILE | \ |
| EFI_VARIABLE_BOOTSERVICE_ACCESS | \ |
| EFI_VARIABLE_RUNTIME_ACCESS) |
| |
| /* Offset from Unix time to Windows time (100ns since 1 Jan 1601) */ |
| #define UNIX_TIME_TO_WINDOWS_TIME_OFFSET 116444736000000000ULL |
| |
| static DEFINE_MUTEX(cs_amp_efi_cal_write_lock); |
| |
| static u64 cs_amp_time_now_in_windows_time(void) |
| { |
| u64 time_in_100ns = div_u64(ktime_get_real_ns(), 100); |
| |
| return time_in_100ns + UNIX_TIME_TO_WINDOWS_TIME_OFFSET; |
| } |
| |
| static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, |
| const struct cirrus_amp_cal_controls *controls, |
| const char *ctl_name, u32 val) |
| { |
| struct cs_dsp_coeff_ctl *cs_ctl; |
| __be32 beval = cpu_to_be32(val); |
| int ret; |
| |
| KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); |
| |
| if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { |
| mutex_lock(&dsp->pwr_lock); |
| cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); |
| ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); |
| mutex_unlock(&dsp->pwr_lock); |
| |
| if (ret < 0) { |
| dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| return -ENODEV; |
| } |
| |
| static int cs_amp_read_cal_coeff(struct cs_dsp *dsp, |
| const struct cirrus_amp_cal_controls *controls, |
| const char *ctl_name, u32 *val) |
| { |
| struct cs_dsp_coeff_ctl *cs_ctl; |
| __be32 beval; |
| int ret; |
| |
| KUNIT_STATIC_STUB_REDIRECT(cs_amp_read_cal_coeff, dsp, controls, ctl_name, val); |
| |
| if (!IS_REACHABLE(CONFIG_FW_CS_DSP)) |
| return -ENODEV; |
| |
| scoped_guard(mutex, &dsp->pwr_lock) { |
| cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); |
| ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, &beval, sizeof(beval)); |
| } |
| |
| if (ret < 0) { |
| dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); |
| return ret; |
| } |
| |
| *val = be32_to_cpu(beval); |
| |
| return 0; |
| } |
| |
| static int _cs_amp_write_cal_coeffs(struct cs_dsp *dsp, |
| const struct cirrus_amp_cal_controls *controls, |
| const struct cirrus_amp_cal_data *data) |
| { |
| int ret; |
| |
| dev_dbg(dsp->dev, "Calibration: Ambient=%#x, Status=%#x, CalR=%d\n", |
| data->calAmbient, data->calStatus, data->calR); |
| |
| if (list_empty(&dsp->ctl_list)) { |
| dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n"); |
| return -ENOENT; |
| } |
| |
| ret = cs_amp_write_cal_coeff(dsp, controls, controls->ambient, data->calAmbient); |
| if (ret) |
| return ret; |
| |
| ret = cs_amp_write_cal_coeff(dsp, controls, controls->calr, data->calR); |
| if (ret) |
| return ret; |
| |
| ret = cs_amp_write_cal_coeff(dsp, controls, controls->status, data->calStatus); |
| if (ret) |
| return ret; |
| |
| ret = cs_amp_write_cal_coeff(dsp, controls, controls->checksum, data->calR + 1); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int _cs_amp_read_cal_coeffs(struct cs_dsp *dsp, |
| const struct cirrus_amp_cal_controls *controls, |
| struct cirrus_amp_cal_data *data) |
| { |
| u64 time; |
| u32 val; |
| int ret; |
| |
| if (list_empty(&dsp->ctl_list)) { |
| dev_info(dsp->dev, "Calibration disabled due to missing firmware controls\n"); |
| return -ENOENT; |
| } |
| |
| ret = cs_amp_read_cal_coeff(dsp, controls, controls->ambient, &val); |
| if (ret) |
| return ret; |
| |
| data->calAmbient = (s8)val; |
| |
| ret = cs_amp_read_cal_coeff(dsp, controls, controls->calr, &val); |
| if (ret) |
| return ret; |
| |
| data->calR = (u16)val; |
| |
| ret = cs_amp_read_cal_coeff(dsp, controls, controls->status, &val); |
| if (ret) |
| return ret; |
| |
| data->calStatus = (u8)val; |
| |
| /* Fill in timestamp */ |
| time = cs_amp_time_now_in_windows_time(); |
| data->calTime[0] = (u32)time; |
| data->calTime[1] = (u32)(time >> 32); |
| |
| return 0; |
| } |
| |
| /** |
| * cs_amp_write_cal_coeffs - Write calibration data to firmware controls. |
| * @dsp: Pointer to struct cs_dsp. |
| * @controls: Pointer to definition of firmware controls to be written. |
| * @data: Pointer to calibration data. |
| * |
| * Returns: 0 on success, else negative error value. |
| */ |
| int cs_amp_write_cal_coeffs(struct cs_dsp *dsp, |
| const struct cirrus_amp_cal_controls *controls, |
| const struct cirrus_amp_cal_data *data) |
| { |
| if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) |
| return _cs_amp_write_cal_coeffs(dsp, controls, data); |
| else |
| return -ENODEV; |
| } |
| EXPORT_SYMBOL_NS_GPL(cs_amp_write_cal_coeffs, "SND_SOC_CS_AMP_LIB"); |
| |
| /** |
| * cs_amp_read_cal_coeffs - Read calibration data from firmware controls. |
| * @dsp: Pointer to struct cs_dsp. |
| * @controls: Pointer to definition of firmware controls to be read. |
| * @data: Pointer to calibration data where results will be written. |
| * |
| * Returns: 0 on success, else negative error value. |
| */ |
| int cs_amp_read_cal_coeffs(struct cs_dsp *dsp, |
| const struct cirrus_amp_cal_controls *controls, |
| struct cirrus_amp_cal_data *data) |
| { |
| if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) |
| return _cs_amp_read_cal_coeffs(dsp, controls, data); |
| else |
| return -ENODEV; |
| } |
| EXPORT_SYMBOL_NS_GPL(cs_amp_read_cal_coeffs, "SND_SOC_CS_AMP_LIB"); |
| |
| /** |
| * cs_amp_write_ambient_temp - write value to calibration ambient temperature |
| * @dsp: Pointer to struct cs_dsp. |
| * @controls: Pointer to definition of firmware controls to be read. |
| * @temp: Temperature in degrees celcius. |
| * |
| * Returns: 0 on success, else negative error value. |
| */ |
| int cs_amp_write_ambient_temp(struct cs_dsp *dsp, |
| const struct cirrus_amp_cal_controls *controls, |
| u32 temp) |
| { |
| if (IS_REACHABLE(CONFIG_FW_CS_DSP) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) |
| return cs_amp_write_cal_coeff(dsp, controls, controls->ambient, temp); |
| else |
| return -ENODEV; |
| } |
| EXPORT_SYMBOL_NS_GPL(cs_amp_write_ambient_temp, "SND_SOC_CS_AMP_LIB"); |
| |
| static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, |
| efi_guid_t *guid, |
| u32 *returned_attr, |
| unsigned long *size, |
| void *buf) |
| { |
| u32 attr; |
| |
| if (!returned_attr) |
| returned_attr = &attr; |
| |
| KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, |
| returned_attr, size, buf); |
| |
| if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) |
| return efi.get_variable(name, guid, returned_attr, size, buf); |
| |
| return EFI_NOT_FOUND; |
| } |
| |
| static efi_status_t cs_amp_set_efi_variable(efi_char16_t *name, |
| efi_guid_t *guid, |
| u32 attr, |
| unsigned long size, |
| void *buf) |
| { |
| KUNIT_STATIC_STUB_REDIRECT(cs_amp_set_efi_variable, name, guid, attr, size, buf); |
| |
| if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE)) |
| return EFI_NOT_FOUND; |
| |
| return efi.set_variable(name, guid, attr, size, buf); |
| } |
| |
| static int cs_amp_convert_efi_status(efi_status_t status) |
| { |
| switch (status) { |
| case EFI_SUCCESS: |
| return 0; |
| case EFI_NOT_FOUND: |
| return -ENOENT; |
| case EFI_BUFFER_TOO_SMALL: |
| return -EFBIG; |
| case EFI_WRITE_PROTECTED: |
| case EFI_UNSUPPORTED: |
| case EFI_ACCESS_DENIED: |
| case EFI_SECURITY_VIOLATION: |
| return -EACCES; |
| default: |
| return -EIO; |
| } |
| } |
| |
| static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev, |
| efi_char16_t **name, |
| efi_guid_t **guid, |
| u32 *attr) |
| { |
| struct cirrus_amp_efi_data *efi_data; |
| unsigned long data_size = 0; |
| u8 *data; |
| efi_status_t status; |
| int i, ret; |
| |
| /* Find EFI variable and get size */ |
| for (i = 0; i < ARRAY_SIZE(cs_amp_lib_cal_efivars); i++) { |
| status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, |
| cs_amp_lib_cal_efivars[i].guid, |
| attr, &data_size, NULL); |
| if (status == EFI_BUFFER_TOO_SMALL) |
| break; |
| } |
| |
| if (status != EFI_BUFFER_TOO_SMALL) |
| return ERR_PTR(-ENOENT); |
| |
| if (name) |
| *name = cs_amp_lib_cal_efivars[i].name; |
| |
| if (guid) |
| *guid = cs_amp_lib_cal_efivars[i].guid; |
| |
| if (data_size < sizeof(*efi_data)) { |
| dev_err(dev, "EFI cal variable truncated\n"); |
| return ERR_PTR(-EOVERFLOW); |
| } |
| |
| /* Get variable contents into buffer */ |
| data = kmalloc(data_size, GFP_KERNEL); |
| if (!data) |
| return ERR_PTR(-ENOMEM); |
| |
| status = cs_amp_get_efi_variable(cs_amp_lib_cal_efivars[i].name, |
| cs_amp_lib_cal_efivars[i].guid, |
| attr, &data_size, data); |
| if (status != EFI_SUCCESS) { |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| efi_data = (struct cirrus_amp_efi_data *)data; |
| dev_dbg(dev, "Calibration: Size=%d, Amp Count=%d\n", efi_data->size, efi_data->count); |
| |
| if ((efi_data->count > 128) || |
| struct_size(efi_data, data, efi_data->count) > data_size) { |
| dev_err(dev, "EFI cal variable truncated\n"); |
| ret = -EOVERFLOW; |
| goto err; |
| } |
| |
| /* This could be zero-filled space pre-allocated by the BIOS */ |
| if (efi_data->size == 0) |
| efi_data->size = data_size; |
| |
| return efi_data; |
| |
| err: |
| kfree(data); |
| dev_err(dev, "Failed to read calibration data from EFI: %d\n", ret); |
| |
| return ERR_PTR(ret); |
| } |
| |
| static int cs_amp_set_cal_efi_buffer(struct device *dev, |
| efi_char16_t *name, |
| efi_guid_t *guid, |
| u32 attr, |
| struct cirrus_amp_efi_data *data) |
| { |
| efi_status_t status; |
| |
| status = cs_amp_set_efi_variable(name, guid, attr, |
| struct_size(data, data, data->count), data); |
| |
| return cs_amp_convert_efi_status(status); |
| } |
| |
| static int _cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, |
| struct cirrus_amp_cal_data *out_data) |
| { |
| struct cirrus_amp_efi_data *efi_data; |
| struct cirrus_amp_cal_data *cal = NULL; |
| int i, ret; |
| |
| efi_data = cs_amp_get_cal_efi_buffer(dev, NULL, NULL, NULL); |
| if (IS_ERR(efi_data)) |
| return PTR_ERR(efi_data); |
| |
| if (target_uid) { |
| for (i = 0; i < efi_data->count; ++i) { |
| u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[i]); |
| |
| /* Skip empty entries */ |
| if (!efi_data->data[i].calTime[0] && !efi_data->data[i].calTime[1]) |
| continue; |
| |
| /* Skip entries with unpopulated silicon ID */ |
| if (cal_target == 0) |
| continue; |
| |
| if (cal_target == target_uid) { |
| cal = &efi_data->data[i]; |
| break; |
| } |
| } |
| } |
| |
| if (!cal && (amp_index >= 0) && (amp_index < efi_data->count) && |
| (efi_data->data[amp_index].calTime[0] || efi_data->data[amp_index].calTime[1])) { |
| u64 cal_target = cs_amp_cal_target_u64(&efi_data->data[amp_index]); |
| |
| /* |
| * Treat unpopulated cal_target as a wildcard. |
| * If target_uid != 0 we can only get here if cal_target == 0 |
| * or it didn't match any cal_target value. |
| * If target_uid == 0 it is a wildcard. |
| */ |
| if ((cal_target == 0) || (target_uid == 0)) |
| cal = &efi_data->data[amp_index]; |
| else |
| dev_warn(dev, "Calibration entry %d does not match silicon ID", amp_index); |
| } |
| |
| if (cal) { |
| memcpy(out_data, cal, sizeof(*out_data)); |
| ret = 0; |
| } else { |
| dev_warn(dev, "No calibration for silicon ID %#llx\n", target_uid); |
| ret = -ENOENT; |
| } |
| |
| kfree(efi_data); |
| |
| return ret; |
| } |
| |
| static int _cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, |
| const struct cirrus_amp_cal_data *in_data) |
| { |
| u64 cal_target = cs_amp_cal_target_u64(in_data); |
| unsigned long num_entries; |
| struct cirrus_amp_efi_data *data __free(kfree) = NULL; |
| efi_char16_t *name = CIRRUS_LOGIC_CALIBRATION_EFI_NAME; |
| efi_guid_t *guid = &CIRRUS_LOGIC_CALIBRATION_EFI_GUID; |
| u32 attr = CS_AMP_CAL_DEFAULT_EFI_ATTR; |
| int i, ret; |
| |
| if (cal_target == 0) |
| return -EINVAL; |
| |
| data = cs_amp_get_cal_efi_buffer(dev, &name, &guid, &attr); |
| ret = PTR_ERR_OR_ZERO(data); |
| if (ret == -ENOENT) { |
| data = NULL; |
| goto alloc_new; |
| } else if (ret) { |
| return ret; |
| } |
| |
| /* |
| * If the EFI variable is just zero-filled reserved space the count |
| * must be set. |
| */ |
| if (data->count == 0) |
| data->count = (data->size - sizeof(data)) / sizeof(data->data[0]); |
| |
| if (amp_index < 0) { |
| /* Is there already a slot for this target? */ |
| for (amp_index = 0; amp_index < data->count; amp_index++) { |
| if (cs_amp_cal_target_u64(&data->data[amp_index]) == cal_target) |
| break; |
| } |
| |
| /* Else find an empty slot */ |
| if (amp_index >= data->count) { |
| for (amp_index = 0; amp_index < data->count; amp_index++) { |
| if ((data->data[amp_index].calTime[0] == 0) && |
| (data->data[amp_index].calTime[1] == 0)) |
| break; |
| } |
| } |
| } else { |
| /* |
| * If the index is forced there could be another active |
| * slot with the same calTarget. So deduplicate. |
| */ |
| for (i = 0; i < data->count; i++) { |
| if (i == amp_index) |
| continue; |
| |
| if ((data->data[i].calTime[0] == 0) && (data->data[i].calTime[1] == 0)) |
| continue; |
| |
| if (cs_amp_cal_target_u64(&data->data[i]) == cal_target) |
| memset(data->data[i].calTime, 0, sizeof(data->data[i].calTime)); |
| } |
| } |
| |
| alloc_new: |
| if (amp_index < 0) |
| amp_index = 0; |
| |
| num_entries = max(num_amps, amp_index + 1); |
| if (!data || (data->count < num_entries)) { |
| struct cirrus_amp_efi_data *old_data __free(kfree) = no_free_ptr(data); |
| unsigned int new_data_size = struct_size(data, data, num_entries); |
| |
| data = kzalloc(new_data_size, GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| if (old_data) |
| memcpy(data, old_data, struct_size(old_data, data, old_data->count)); |
| |
| data->count = num_entries; |
| data->size = new_data_size; |
| } |
| |
| data->data[amp_index] = *in_data; |
| ret = cs_amp_set_cal_efi_buffer(dev, name, guid, attr, data); |
| if (ret) { |
| dev_err(dev, "Failed writing calibration to EFI: %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * cs_amp_get_efi_calibration_data - get an entry from calibration data in EFI. |
| * @dev: struct device of the caller. |
| * @target_uid: UID to match, or zero to ignore UID matching. |
| * @amp_index: Entry index to use, or -1 to prevent lookup by index. |
| * @out_data: struct cirrus_amp_cal_data where the entry will be copied. |
| * |
| * This function can perform 3 types of lookup: |
| * |
| * (target_uid > 0, amp_index >= 0) |
| * UID search with fallback to using the array index. |
| * Search the calibration data for a non-zero calTarget that matches |
| * target_uid, and if found return that entry. Else, if the entry at |
| * [amp_index] has calTarget == 0, return that entry. Else fail. |
| * |
| * (target_uid > 0, amp_index < 0) |
| * UID search only. |
| * Search the calibration data for a non-zero calTarget that matches |
| * target_uid, and if found return that entry. Else fail. |
| * |
| * (target_uid == 0, amp_index >= 0) |
| * Array index fetch only. |
| * Return the entry at [amp_index]. |
| * |
| * An array lookup will be skipped if amp_index exceeds the number of |
| * entries in the calibration array, and in this case the return will |
| * be -ENOENT. An out-of-range amp_index does not prevent matching by |
| * target_uid - it has the same effect as passing amp_index < 0. |
| * |
| * If the EFI data is too short to be a valid entry, or the entry count |
| * in the EFI data overflows the actual length of the data, this function |
| * returns -EOVERFLOW. |
| * |
| * Return: 0 if the entry was found, -ENOENT if no entry was found, |
| * -EOVERFLOW if the EFI file is corrupt, else other error value. |
| */ |
| int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_index, |
| struct cirrus_amp_cal_data *out_data) |
| { |
| if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) |
| return _cs_amp_get_efi_calibration_data(dev, target_uid, amp_index, out_data); |
| else |
| return -ENOENT; |
| } |
| EXPORT_SYMBOL_NS_GPL(cs_amp_get_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); |
| |
| /** |
| * cs_amp_set_efi_calibration_data - write a calibration data entry to EFI. |
| * @dev: struct device of the caller. |
| * @amp_index: Entry index to use, or -1 to use any available slot. |
| * @num_amps: Maximum number of amps to reserve slots for, or -1 to ignore. |
| * @in_data: struct cirrus_amp_cal_data entry to be written to EFI. |
| * |
| * If a Vendor-specific variable exists it will be updated, |
| * else if the Cirrus variable exists it will be updated |
| * else the Cirrus variable will be created. |
| * |
| * If amp_index >= 0 the data will be placed in this entry of the calibration |
| * data array, overwriting what was in that entry. Any other entries with the |
| * same calTarget will be marked empty. |
| * |
| * If amp_index < 0 and in_data->calTarget matches any existing entry, that |
| * entry will be overwritten. Else the first available free entry will be used, |
| * extending the size of the EFI variable if there are no free entries. |
| * |
| * If num_amps > 0 the EFI variable will be sized to contain at least this |
| * many calibration entries, with any new entries marked empty. |
| * |
| * Return: 0 if the write was successful, -EFBIG if space could not be made in |
| * the EFI file to add the entry, -EACCES if it was not possible to |
| * read or write the EFI variable. |
| */ |
| int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, |
| const struct cirrus_amp_cal_data *in_data) |
| { |
| if (IS_ENABLED(CONFIG_EFI) || IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) { |
| scoped_guard(mutex, &cs_amp_efi_cal_write_lock) { |
| return _cs_amp_set_efi_calibration_data(dev, amp_index, |
| num_amps, in_data); |
| } |
| } |
| |
| return -ENOENT; |
| } |
| EXPORT_SYMBOL_NS_GPL(cs_amp_set_efi_calibration_data, "SND_SOC_CS_AMP_LIB"); |
| |
| struct cs_amp_spkid_efi { |
| efi_char16_t *name; |
| efi_guid_t *guid; |
| u8 values[2]; |
| }; |
| |
| static int cs_amp_get_efi_byte_spkid(struct device *dev, const struct cs_amp_spkid_efi *info) |
| { |
| efi_status_t status; |
| unsigned long size; |
| u8 spkid; |
| int i, ret; |
| |
| size = sizeof(spkid); |
| status = cs_amp_get_efi_variable(info->name, info->guid, NULL, &size, &spkid); |
| ret = cs_amp_convert_efi_status(status); |
| if (ret < 0) |
| return ret; |
| |
| if (size == 0) |
| return -ENOENT; |
| |
| for (i = 0; i < ARRAY_SIZE(info->values); i++) { |
| if (info->values[i] == spkid) |
| return i; |
| } |
| |
| dev_err(dev, "EFI speaker ID bad value %#x\n", spkid); |
| |
| return -EINVAL; |
| } |
| |
| static const struct cs_amp_spkid_efi cs_amp_spkid_byte_types[] = { |
| { |
| .name = LENOVO_SPEAKER_ID_EFI_NAME, |
| .guid = &LENOVO_SPEAKER_ID_EFI_GUID, |
| .values = { 0xd0, 0xd1 }, |
| }, |
| { |
| .name = HP_SPEAKER_ID_EFI_NAME, |
| .guid = &HP_SPEAKER_ID_EFI_GUID, |
| .values = { 0x30, 0x31 }, |
| }, |
| }; |
| |
| /** |
| * cs_amp_get_vendor_spkid - get a speaker ID from vendor-specific storage |
| * @dev: pointer to struct device |
| * |
| * Known vendor-specific methods of speaker ID are checked and if one is |
| * found its speaker ID value is returned. |
| * |
| * Return: >=0 is a valid speaker ID. -ENOENT if a vendor-specific method |
| * was not found. -EACCES if the vendor-specific storage could not |
| * be read. Other error values indicate that the data from the |
| * vendor-specific storage was found but could not be understood. |
| */ |
| int cs_amp_get_vendor_spkid(struct device *dev) |
| { |
| int i, ret; |
| |
| if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) && |
| !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) |
| return -ENOENT; |
| |
| for (i = 0; i < ARRAY_SIZE(cs_amp_spkid_byte_types); i++) { |
| ret = cs_amp_get_efi_byte_spkid(dev, &cs_amp_spkid_byte_types[i]); |
| if (ret != -ENOENT) |
| return ret; |
| } |
| |
| return -ENOENT; |
| } |
| EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB"); |
| |
| /** |
| * cs_amp_create_debugfs - create a debugfs directory for a device |
| * |
| * @dev: pointer to struct device |
| * |
| * Creates a node under "cirrus_logic" in the root of the debugfs filesystem. |
| * This is for Cirrus-specific debugfs functionality to be grouped in a |
| * defined way, independently of the debugfs provided by ALSA/ASoC. |
| * The general ALSA/ASoC debugfs may not be enabled, and does not necessarily |
| * have a stable layout or naming convention. |
| * |
| * Return: Pointer to the dentry for the created directory, or -ENODEV. |
| */ |
| struct dentry *cs_amp_create_debugfs(struct device *dev) |
| { |
| struct dentry *dir; |
| |
| dir = debugfs_lookup("cirrus_logic", NULL); |
| if (!dir) |
| dir = debugfs_create_dir("cirrus_logic", NULL); |
| |
| return debugfs_create_dir(dev_name(dev), dir); |
| } |
| EXPORT_SYMBOL_NS_GPL(cs_amp_create_debugfs, "SND_SOC_CS_AMP_LIB"); |
| |
| static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = { |
| .get_efi_variable = cs_amp_get_efi_variable, |
| .set_efi_variable = cs_amp_set_efi_variable, |
| .write_cal_coeff = cs_amp_write_cal_coeff, |
| .read_cal_coeff = cs_amp_read_cal_coeff, |
| }; |
| |
| const struct cs_amp_test_hooks * const cs_amp_test_hooks = |
| PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST), &cs_amp_test_hook_ptrs); |
| EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, "SND_SOC_CS_AMP_LIB"); |
| |
| MODULE_DESCRIPTION("Cirrus Logic amplifier library"); |
| MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); |
| MODULE_LICENSE("GPL"); |
| MODULE_IMPORT_NS("FW_CS_DSP"); |