Path: blob/master/sound/hda/codecs/side-codecs/tas2781_hda.c
51706 views
// SPDX-License-Identifier: GPL-2.01//2// TAS2781 HDA Shared Lib for I2C&SPI driver3//4// Copyright 2025 - 2026 Texas Instruments, Inc.5//6// Author: Shenghao Ding <[email protected]>78#include <linux/component.h>9#include <linux/crc8.h>10#include <linux/crc32.h>11#include <linux/efi.h>12#include <linux/firmware.h>13#include <linux/i2c.h>14#include <linux/pm_runtime.h>15#include <sound/soc.h>16#include <sound/tas2781.h>1718#include "tas2781_hda.h"1920#define CALIBRATION_DATA_AREA_NUM 22122const efi_guid_t tasdev_fct_efi_guid[] = {23/* DELL */24EFI_GUID(0xcc92382d, 0x6337, 0x41cb, 0xa8, 0x8b, 0x8e, 0xce, 0x74,250x91, 0xea, 0x9f),26/* HP */27EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a,280xa3, 0x5d, 0xb3),29/* LENOVO & OTHERS */30EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc, 0x09, 0x43, 0xa3, 0xf4,310x31, 0x0a, 0x92),32};33EXPORT_SYMBOL_NS_GPL(tasdev_fct_efi_guid, "SND_HDA_SCODEC_TAS2781");3435/*36* The order of calibrated-data writing function is a bit different from the37* order in UEFI. Here is the conversion to match the order of calibrated-data38* writing function.39*/40static void cali_cnv(unsigned char *data, unsigned int base, int offset)41{42struct cali_reg reg_data;4344memcpy(®_data, &data[base], sizeof(reg_data));45/* the data order has to be swapped between r0_low_reg and inv0_reg */46swap(reg_data.r0_low_reg, reg_data.invr0_reg);4748cpu_to_be32_array((__force __be32 *)(data + offset + 1),49(u32 *)®_data, TASDEV_CALIB_N);50}5152static void tas2781_apply_calib(struct tasdevice_priv *p)53{54struct calidata *cali_data = &p->cali_data;55struct cali_reg *r = &cali_data->cali_reg_array;56unsigned char *data = cali_data->data;57unsigned int *tmp_val = (unsigned int *)data;58unsigned int cali_reg[TASDEV_CALIB_N] = {59TASDEVICE_REG(0, 0x17, 0x74),60TASDEVICE_REG(0, 0x18, 0x0c),61TASDEVICE_REG(0, 0x18, 0x14),62TASDEVICE_REG(0, 0x13, 0x70),63TASDEVICE_REG(0, 0x18, 0x7c),64};65unsigned int crc, oft, node_num;66unsigned char *buf;67int i, j, k, l;6869if (tmp_val[0] == 2781) {70/*71* New features were added in calibrated Data V3:72* 1. Added calibration registers address define in73* a node, marked as Device id == 0x80.74* New features were added in calibrated Data V2:75* 1. Added some the fields to store the link_id and76* uniqie_id for multi-link solutions77* 2. Support flexible number of devices instead of78* fixed one in V1.79* Layout of calibrated data V2 in UEFI(total 256 bytes):80* ChipID (2781, 4 bytes)81* Data-Group-Sum (4 bytes)82* TimeStamp of Calibration (4 bytes)83* for (i = 0; i < Data-Group-Sum; i++) {84* if (Data type != 0x80) (4 bytes)85* Calibrated Data of Device #i (20 bytes)86* else87* Calibration registers address (5*4 = 20 bytes)88* # V2: No reg addr in data grp section.89* # V3: Normally the last grp is the reg addr.90* }91* CRC (4 bytes)92* Reserved (the rest)93*/94crc = crc32(~0, data, (3 + tmp_val[1] * 6) * 4) ^ ~0;9596if (crc != tmp_val[3 + tmp_val[1] * 6]) {97cali_data->total_sz = 0;98dev_err(p->dev, "%s: CRC error\n", __func__);99return;100}101node_num = tmp_val[1];102103for (j = 0, k = 0; j < node_num; j++) {104oft = j * 6 + 3;105if (tmp_val[oft] == TASDEV_UEFI_CALI_REG_ADDR_FLG) {106for (i = 0; i < TASDEV_CALIB_N; i++) {107buf = &data[(oft + i + 1) * 4];108cali_reg[i] = TASDEVICE_REG(buf[1],109buf[2], buf[3]);110}111} else {112l = j * (cali_data->cali_dat_sz_per_dev + 1);113if (k >= p->ndev || l > oft * 4) {114dev_err(p->dev, "%s: dev sum error\n",115__func__);116cali_data->total_sz = 0;117return;118}119120data[l] = k;121oft++;122cali_cnv(data, 4 * oft, l);123k++;124}125}126} else {127/*128* Calibration data is in V1 format.129* struct cali_data {130* char cali_data[20];131* }132*133* struct {134* struct cali_data cali_data[4];135* int TimeStamp of Calibration (4 bytes)136* int CRC (4 bytes)137* } ueft;138*/139crc = crc32(~0, data, 84) ^ ~0;140if (crc != tmp_val[21]) {141cali_data->total_sz = 0;142dev_err(p->dev, "%s: V1 CRC error\n", __func__);143return;144}145146for (j = p->ndev - 1; j >= 0; j--) {147l = j * (cali_data->cali_dat_sz_per_dev + 1);148cali_cnv(data, cali_data->cali_dat_sz_per_dev * j, l);149data[l] = j;150}151}152153if (p->dspbin_typ == TASDEV_BASIC) {154r->r0_reg = cali_reg[0];155r->invr0_reg = cali_reg[1];156r->r0_low_reg = cali_reg[2];157r->pow_reg = cali_reg[3];158r->tlimit_reg = cali_reg[4];159}160161cali_data->total_sz = p->ndev * (cali_data->cali_dat_sz_per_dev + 1);162}163164/*165* Update the calibration data, including speaker impedance, f0, etc,166* into algo. Calibrate data is done by manufacturer in the factory.167* The data is used by Algo for calculating the speaker temperature,168* speaker membrane excursion and f0 in real time during playback.169* Calibration data format in EFI is V2, since 2024.170*/171int tas2781_save_calibration(struct tas2781_hda *hda)172{173/*174* GUID was used for data access in BIOS, it was provided by board175* manufactory.176*/177efi_guid_t efi_guid = tasdev_fct_efi_guid[LENOVO];178/*179* Some devices save the calibrated data into L"CALI_DATA",180* and others into L"SmartAmpCalibrationData".181*/182static efi_char16_t *efi_name[CALIBRATION_DATA_AREA_NUM] = {183L"CALI_DATA",184L"SmartAmpCalibrationData",185};186struct tasdevice_priv *p = hda->priv;187struct calidata *cali_data = &p->cali_data;188unsigned long total_sz = 0;189unsigned int attr, size;190unsigned char *data;191efi_status_t status;192int i;193194if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) {195dev_err(p->dev, "%s: NO EFI FOUND!\n", __func__);196return -EINVAL;197}198199if (hda->catlog_id < LENOVO)200efi_guid = tasdev_fct_efi_guid[hda->catlog_id];201202cali_data->cali_dat_sz_per_dev = 20;203size = p->ndev * (cali_data->cali_dat_sz_per_dev + 1);204for (i = 0; i < CALIBRATION_DATA_AREA_NUM; i++) {205/* Get real size of UEFI variable */206status = efi.get_variable(efi_name[i], &efi_guid, &attr,207&total_sz, NULL);208cali_data->total_sz = total_sz > size ? total_sz : size;209if (status == EFI_BUFFER_TOO_SMALL) {210/* Allocate data buffer of data_size bytes */211data = cali_data->data = devm_kzalloc(p->dev,212cali_data->total_sz, GFP_KERNEL);213if (!data) {214status = -ENOMEM;215continue;216}217/*218* Set to an invalid value before the calibrated data219* is stored into it, for the default value is 0, which220* means the first device.221*/222data[0] = 0xff;223/* Get variable contents into buffer */224status = efi.get_variable(efi_name[i], &efi_guid,225&attr, &cali_data->total_sz, data);226}227/* Check whether get the calibrated data */228if (status == EFI_SUCCESS)229break;230}231232if (status != EFI_SUCCESS) {233cali_data->total_sz = 0;234return status;235}236237tas2781_apply_calib(p);238239return 0;240}241EXPORT_SYMBOL_NS_GPL(tas2781_save_calibration, "SND_HDA_SCODEC_TAS2781");242243void tas2781_hda_remove(struct device *dev,244const struct component_ops *ops)245{246struct tas2781_hda *tas_hda = dev_get_drvdata(dev);247248component_del(tas_hda->dev, ops);249250pm_runtime_get_sync(tas_hda->dev);251pm_runtime_disable(tas_hda->dev);252253pm_runtime_put_noidle(tas_hda->dev);254255tasdevice_remove(tas_hda->priv);256}257EXPORT_SYMBOL_NS_GPL(tas2781_hda_remove, "SND_HDA_SCODEC_TAS2781");258259int tasdevice_info_profile(struct snd_kcontrol *kcontrol,260struct snd_ctl_elem_info *uinfo)261{262struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);263264uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;265uinfo->count = 1;266uinfo->value.integer.min = 0;267uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1;268269return 0;270}271EXPORT_SYMBOL_NS_GPL(tasdevice_info_profile, "SND_HDA_SCODEC_TAS2781");272273int tasdevice_info_programs(struct snd_kcontrol *kcontrol,274struct snd_ctl_elem_info *uinfo)275{276struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);277278uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;279uinfo->count = 1;280uinfo->value.integer.min = 0;281uinfo->value.integer.max = tas_priv->fmw->nr_programs - 1;282283return 0;284}285EXPORT_SYMBOL_NS_GPL(tasdevice_info_programs, "SND_HDA_SCODEC_TAS2781");286287int tasdevice_info_config(struct snd_kcontrol *kcontrol,288struct snd_ctl_elem_info *uinfo)289{290struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);291struct tasdevice_fw *tas_fw = tas_priv->fmw;292293uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;294uinfo->count = 1;295uinfo->value.integer.min = 0;296uinfo->value.integer.max = tas_fw->nr_configurations - 1;297298return 0;299}300EXPORT_SYMBOL_NS_GPL(tasdevice_info_config, "SND_HDA_SCODEC_TAS2781");301302int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol,303struct snd_ctl_elem_value *ucontrol)304{305struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);306307ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id;308309dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,310kcontrol->id.name, tas_priv->rcabin.profile_cfg_id);311312return 0;313}314EXPORT_SYMBOL_NS_GPL(tasdevice_get_profile_id, "SND_HDA_SCODEC_TAS2781");315316int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol,317struct snd_ctl_elem_value *ucontrol)318{319struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);320int profile_id = ucontrol->value.integer.value[0];321int max = tas_priv->rcabin.ncfgs - 1;322int val, ret = 0;323324val = clamp(profile_id, 0, max);325326guard(mutex)(&tas_priv->codec_lock);327328dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,329kcontrol->id.name, tas_priv->rcabin.profile_cfg_id, val);330331if (tas_priv->rcabin.profile_cfg_id != val) {332tas_priv->rcabin.profile_cfg_id = val;333ret = 1;334}335336return ret;337}338EXPORT_SYMBOL_NS_GPL(tasdevice_set_profile_id, "SND_HDA_SCODEC_TAS2781");339340int tasdevice_program_get(struct snd_kcontrol *kcontrol,341struct snd_ctl_elem_value *ucontrol)342{343struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);344345ucontrol->value.integer.value[0] = tas_priv->cur_prog;346347dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,348kcontrol->id.name, tas_priv->cur_prog);349350return 0;351}352EXPORT_SYMBOL_NS_GPL(tasdevice_program_get, "SND_HDA_SCODEC_TAS2781");353354int tasdevice_program_put(struct snd_kcontrol *kcontrol,355struct snd_ctl_elem_value *ucontrol)356{357struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);358struct tasdevice_fw *tas_fw = tas_priv->fmw;359int nr_program = ucontrol->value.integer.value[0];360int max = tas_fw->nr_programs - 1;361int val, ret = 0;362363val = clamp(nr_program, 0, max);364365guard(mutex)(&tas_priv->codec_lock);366367dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,368kcontrol->id.name, tas_priv->cur_prog, val);369370if (tas_priv->cur_prog != val) {371tas_priv->cur_prog = val;372ret = 1;373}374375return ret;376}377EXPORT_SYMBOL_NS_GPL(tasdevice_program_put, "SND_HDA_SCODEC_TAS2781");378379int tasdevice_config_get(struct snd_kcontrol *kcontrol,380struct snd_ctl_elem_value *ucontrol)381{382struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);383384ucontrol->value.integer.value[0] = tas_priv->cur_conf;385386dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,387kcontrol->id.name, tas_priv->cur_conf);388389return 0;390}391EXPORT_SYMBOL_NS_GPL(tasdevice_config_get, "SND_HDA_SCODEC_TAS2781");392393int tasdevice_config_put(struct snd_kcontrol *kcontrol,394struct snd_ctl_elem_value *ucontrol)395{396struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);397struct tasdevice_fw *tas_fw = tas_priv->fmw;398int nr_config = ucontrol->value.integer.value[0];399int max = tas_fw->nr_configurations - 1;400int val, ret = 0;401402val = clamp(nr_config, 0, max);403404guard(mutex)(&tas_priv->codec_lock);405406dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,407kcontrol->id.name, tas_priv->cur_conf, val);408409if (tas_priv->cur_conf != val) {410tas_priv->cur_conf = val;411ret = 1;412}413414return ret;415}416EXPORT_SYMBOL_NS_GPL(tasdevice_config_put, "SND_HDA_SCODEC_TAS2781");417418MODULE_DESCRIPTION("TAS2781 HDA Driver");419MODULE_LICENSE("GPL");420MODULE_AUTHOR("Shenghao Ding, TI, <[email protected]>");421422423