diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h index 61e00017c9aa..e9aa86d76049 100644 --- a/include/sound/cs-amp-lib.h +++ b/include/sound/cs-amp-lib.h @@ -58,6 +58,9 @@ int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_ int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, const struct cirrus_amp_cal_data *in_data); int cs_amp_get_vendor_spkid(struct device *dev); +const char *cs_amp_devm_get_vendor_specific_variant_id(struct device *dev, + int ssid_vendor, + int ssid_device); struct dentry *cs_amp_create_debugfs(struct device *dev); static inline u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index b4d183e7501d..9f8c99dfb798 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,10 @@ #define HP_CALIBRATION_EFI_GUID \ EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93) +#define DELL_SSIDEXV2_EFI_NAME L"SSIDexV2Data" +#define DELL_SSIDEXV2_EFI_GUID \ + EFI_GUID(0x6a5f35df, 0x1432, 0x4656, 0x85, 0x97, 0x31, 0x04, 0xd5, 0xbf, 0x3a, 0xb0) + static const struct cs_amp_lib_cal_efivar { efi_char16_t *name; efi_guid_t *guid; @@ -304,6 +309,29 @@ static int cs_amp_convert_efi_status(efi_status_t status) } } +static void *cs_amp_alloc_get_efi_variable(efi_char16_t *name, + efi_guid_t *guid, + u32 *returned_attr) +{ + efi_status_t status; + unsigned long size = 0; + + status = cs_amp_get_efi_variable(name, guid, NULL, &size, NULL); + if (status != EFI_BUFFER_TOO_SMALL) + return ERR_PTR(cs_amp_convert_efi_status(status)); + + /* Over-alloc to ensure strings are always NUL-terminated */ + void *buf __free(kfree) = kzalloc(size + 1, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + status = cs_amp_get_efi_variable(name, guid, returned_attr, &size, buf); + if (status != EFI_SUCCESS) + return ERR_PTR(cs_amp_convert_efi_status(status)); + + return_ptr(buf); +} + static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev, efi_char16_t **name, efi_guid_t **guid, @@ -705,6 +733,89 @@ int cs_amp_get_vendor_spkid(struct device *dev) } EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB"); +static const char *cs_amp_devm_get_dell_ssidex(struct device *dev, + int ssid_vendor, int ssid_device) +{ + unsigned int hex_prefix; + char audio_id[4]; + char delim; + char *p; + int ret; + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) && + !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) + return ERR_PTR(-ENOENT); + + char *ssidex_buf __free(kfree) = cs_amp_alloc_get_efi_variable(DELL_SSIDEXV2_EFI_NAME, + &DELL_SSIDEXV2_EFI_GUID, + NULL); + ret = PTR_ERR_OR_ZERO(ssidex_buf); + if (ret == -ENOENT) + return ERR_PTR(-ENOENT); + else if (ret < 0) + return ssidex_buf; + + /* + * SSIDExV2 string is a series of underscore delimited fields. + * First field is all or part of the SSID. Second field should be + * a 2-character audio hardware id, followed by other identifiers. + * Older models did not have the 2-character audio id, so reject + * the string if the second field is not 2 characters. + */ + ret = sscanf(ssidex_buf, "%8x_%2s%c", &hex_prefix, audio_id, &delim); + if (ret < 2) + return ERR_PTR(-ENOENT); + + if ((ret == 3) && (delim != '_')) + return ERR_PTR(-ENOENT); + + if (strlen(audio_id) != 2) + return ERR_PTR(-ENOENT); + + p = devm_kstrdup(dev, audio_id, GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + return p; +} + +/** + * cs_amp_devm_get_vendor_specific_variant_id - get variant ID string + * @dev: pointer to struct device + * @ssid_vendor: PCI Subsystem Vendor (-1 if unknown) + * @ssid_device: PCI Subsystem Device (-1 if unknown) + * + * Known vendor-specific hardware identifiers are checked and if one is + * found its content is returned as a NUL-terminated string. The returned + * string is devm-managed. + * + * The returned string is not guaranteed to be globally unique. + * Generally it should be combined with some other qualifier, such as + * PCI SSID, to create a globally unique ID. + * + * If the caller has a PCI SSID it should pass it in @ssid_vendor and + * @ssid_device. If the vendor-spefic ID contains this SSID it will be + * stripped from the returned string to prevent duplication. + * + * If the caller does not have a PCI SSID, pass -1 for @ssid_vendor and + * @ssid_device. + * + * Return: + * * a pointer to a devm-managed string + * * ERR_PTR(-ENOENT) if no vendor-specific qualifier + * * ERR_PTR error value + */ +const char *cs_amp_devm_get_vendor_specific_variant_id(struct device *dev, + int ssid_vendor, + int ssid_device) +{ + if ((ssid_vendor == PCI_VENDOR_ID_DELL) || (ssid_vendor < 0)) + return cs_amp_devm_get_dell_ssidex(dev, ssid_vendor, ssid_device); + + return ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL_NS_GPL(cs_amp_devm_get_vendor_specific_variant_id, "SND_SOC_CS_AMP_LIB"); + /** * cs_amp_create_debugfs - create a debugfs directory for a device *