Path: blob/master/sound/hda/codecs/side-codecs/tas2781_hda.c
26481 views
// SPDX-License-Identifier: GPL-2.01//2// TAS2781 HDA Shared Lib for I2C&SPI driver3//4// Copyright 2025 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");3435static void tas2781_apply_calib(struct tasdevice_priv *p)36{37struct calidata *cali_data = &p->cali_data;38struct cali_reg *r = &cali_data->cali_reg_array;39unsigned char *data = cali_data->data;40unsigned int *tmp_val = (unsigned int *)data;41unsigned int cali_reg[TASDEV_CALIB_N] = {42TASDEVICE_REG(0, 0x17, 0x74),43TASDEVICE_REG(0, 0x18, 0x0c),44TASDEVICE_REG(0, 0x18, 0x14),45TASDEVICE_REG(0, 0x13, 0x70),46TASDEVICE_REG(0, 0x18, 0x7c),47};48unsigned int crc, oft, node_num;49unsigned char *buf;50int i, j, k, l;5152if (tmp_val[0] == 2781) {53/*54* New features were added in calibrated Data V3:55* 1. Added calibration registers address define in56* a node, marked as Device id == 0x80.57* New features were added in calibrated Data V2:58* 1. Added some the fields to store the link_id and59* uniqie_id for multi-link solutions60* 2. Support flexible number of devices instead of61* fixed one in V1.62* Layout of calibrated data V2 in UEFI(total 256 bytes):63* ChipID (2781, 4 bytes)64* Data-Group-Sum (4 bytes)65* TimeStamp of Calibration (4 bytes)66* for (i = 0; i < Data-Group-Sum; i++) {67* if (Data type != 0x80) (4 bytes)68* Calibrated Data of Device #i (20 bytes)69* else70* Calibration registers address (5*4 = 20 bytes)71* # V2: No reg addr in data grp section.72* # V3: Normally the last grp is the reg addr.73* }74* CRC (4 bytes)75* Reserved (the rest)76*/77crc = crc32(~0, data, (3 + tmp_val[1] * 6) * 4) ^ ~0;7879if (crc != tmp_val[3 + tmp_val[1] * 6]) {80cali_data->total_sz = 0;81dev_err(p->dev, "%s: CRC error\n", __func__);82return;83}84node_num = tmp_val[1];8586for (j = 0, k = 0; j < node_num; j++) {87oft = j * 6 + 3;88if (tmp_val[oft] == TASDEV_UEFI_CALI_REG_ADDR_FLG) {89for (i = 0; i < TASDEV_CALIB_N; i++) {90buf = &data[(oft + i + 1) * 4];91cali_reg[i] = TASDEVICE_REG(buf[1],92buf[2], buf[3]);93}94} else {95l = j * (cali_data->cali_dat_sz_per_dev + 1);96if (k >= p->ndev || l > oft * 4) {97dev_err(p->dev, "%s: dev sum error\n",98__func__);99cali_data->total_sz = 0;100return;101}102103data[l] = k;104oft++;105for (i = 0; i < TASDEV_CALIB_N * 4; i++)106data[l + i + 1] = data[4 * oft + i];107k++;108}109}110} else {111/*112* Calibration data is in V1 format.113* struct cali_data {114* char cali_data[20];115* }116*117* struct {118* struct cali_data cali_data[4];119* int TimeStamp of Calibration (4 bytes)120* int CRC (4 bytes)121* } ueft;122*/123crc = crc32(~0, data, 84) ^ ~0;124if (crc != tmp_val[21]) {125cali_data->total_sz = 0;126dev_err(p->dev, "%s: V1 CRC error\n", __func__);127return;128}129130for (j = p->ndev - 1; j >= 0; j--) {131l = j * (cali_data->cali_dat_sz_per_dev + 1);132for (i = TASDEV_CALIB_N * 4; i > 0 ; i--)133data[l + i] = data[p->index * 5 + i];134data[l+i] = j;135}136}137138if (p->dspbin_typ == TASDEV_BASIC) {139r->r0_reg = cali_reg[0];140r->invr0_reg = cali_reg[1];141r->r0_low_reg = cali_reg[2];142r->pow_reg = cali_reg[3];143r->tlimit_reg = cali_reg[4];144}145146p->is_user_space_calidata = true;147cali_data->total_sz = p->ndev * (cali_data->cali_dat_sz_per_dev + 1);148}149150/*151* Update the calibration data, including speaker impedance, f0, etc,152* into algo. Calibrate data is done by manufacturer in the factory.153* The data is used by Algo for calculating the speaker temperature,154* speaker membrane excursion and f0 in real time during playback.155* Calibration data format in EFI is V2, since 2024.156*/157int tas2781_save_calibration(struct tas2781_hda *hda)158{159/*160* GUID was used for data access in BIOS, it was provided by board161* manufactory.162*/163efi_guid_t efi_guid = tasdev_fct_efi_guid[LENOVO];164/*165* Some devices save the calibrated data into L"CALI_DATA",166* and others into L"SmartAmpCalibrationData".167*/168static efi_char16_t *efi_name[CALIBRATION_DATA_AREA_NUM] = {169L"CALI_DATA",170L"SmartAmpCalibrationData",171};172struct tasdevice_priv *p = hda->priv;173struct calidata *cali_data = &p->cali_data;174unsigned long total_sz = 0;175unsigned int attr, size;176unsigned char *data;177efi_status_t status;178int i;179180if (hda->catlog_id < LENOVO)181efi_guid = tasdev_fct_efi_guid[hda->catlog_id];182183cali_data->cali_dat_sz_per_dev = 20;184size = p->ndev * (cali_data->cali_dat_sz_per_dev + 1);185for (i = 0; i < CALIBRATION_DATA_AREA_NUM; i++) {186/* Get real size of UEFI variable */187status = efi.get_variable(efi_name[i], &efi_guid, &attr,188&total_sz, NULL);189cali_data->total_sz = total_sz > size ? total_sz : size;190if (status == EFI_BUFFER_TOO_SMALL) {191/* Allocate data buffer of data_size bytes */192data = cali_data->data = devm_kzalloc(p->dev,193cali_data->total_sz, GFP_KERNEL);194if (!data) {195status = -ENOMEM;196continue;197}198/* Get variable contents into buffer */199status = efi.get_variable(efi_name[i], &efi_guid,200&attr, &cali_data->total_sz, data);201}202/* Check whether get the calibrated data */203if (status == EFI_SUCCESS)204break;205}206207if (status != EFI_SUCCESS) {208cali_data->total_sz = 0;209return status;210}211212tas2781_apply_calib(p);213214return 0;215}216EXPORT_SYMBOL_NS_GPL(tas2781_save_calibration, "SND_HDA_SCODEC_TAS2781");217218void tas2781_hda_remove(struct device *dev,219const struct component_ops *ops)220{221struct tas2781_hda *tas_hda = dev_get_drvdata(dev);222223component_del(tas_hda->dev, ops);224225pm_runtime_get_sync(tas_hda->dev);226pm_runtime_disable(tas_hda->dev);227228pm_runtime_put_noidle(tas_hda->dev);229230tasdevice_remove(tas_hda->priv);231}232EXPORT_SYMBOL_NS_GPL(tas2781_hda_remove, "SND_HDA_SCODEC_TAS2781");233234int tasdevice_info_profile(struct snd_kcontrol *kcontrol,235struct snd_ctl_elem_info *uinfo)236{237struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);238239uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;240uinfo->count = 1;241uinfo->value.integer.min = 0;242uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1;243244return 0;245}246EXPORT_SYMBOL_NS_GPL(tasdevice_info_profile, "SND_HDA_SCODEC_TAS2781");247248int tasdevice_info_programs(struct snd_kcontrol *kcontrol,249struct snd_ctl_elem_info *uinfo)250{251struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);252253uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;254uinfo->count = 1;255uinfo->value.integer.min = 0;256uinfo->value.integer.max = tas_priv->fmw->nr_programs - 1;257258return 0;259}260EXPORT_SYMBOL_NS_GPL(tasdevice_info_programs, "SND_HDA_SCODEC_TAS2781");261262int tasdevice_info_config(struct snd_kcontrol *kcontrol,263struct snd_ctl_elem_info *uinfo)264{265struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);266struct tasdevice_fw *tas_fw = tas_priv->fmw;267268uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;269uinfo->count = 1;270uinfo->value.integer.min = 0;271uinfo->value.integer.max = tas_fw->nr_configurations - 1;272273return 0;274}275EXPORT_SYMBOL_NS_GPL(tasdevice_info_config, "SND_HDA_SCODEC_TAS2781");276277int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol,278struct snd_ctl_elem_value *ucontrol)279{280struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);281282ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id;283284dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,285kcontrol->id.name, tas_priv->rcabin.profile_cfg_id);286287return 0;288}289EXPORT_SYMBOL_NS_GPL(tasdevice_get_profile_id, "SND_HDA_SCODEC_TAS2781");290291int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol,292struct snd_ctl_elem_value *ucontrol)293{294struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);295int profile_id = ucontrol->value.integer.value[0];296int max = tas_priv->rcabin.ncfgs - 1;297int val, ret = 0;298299val = clamp(profile_id, 0, max);300301guard(mutex)(&tas_priv->codec_lock);302303dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,304kcontrol->id.name, tas_priv->rcabin.profile_cfg_id, val);305306if (tas_priv->rcabin.profile_cfg_id != val) {307tas_priv->rcabin.profile_cfg_id = val;308ret = 1;309}310311return ret;312}313EXPORT_SYMBOL_NS_GPL(tasdevice_set_profile_id, "SND_HDA_SCODEC_TAS2781");314315int tasdevice_program_get(struct snd_kcontrol *kcontrol,316struct snd_ctl_elem_value *ucontrol)317{318struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);319320ucontrol->value.integer.value[0] = tas_priv->cur_prog;321322dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,323kcontrol->id.name, tas_priv->cur_prog);324325return 0;326}327EXPORT_SYMBOL_NS_GPL(tasdevice_program_get, "SND_HDA_SCODEC_TAS2781");328329int tasdevice_program_put(struct snd_kcontrol *kcontrol,330struct snd_ctl_elem_value *ucontrol)331{332struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);333struct tasdevice_fw *tas_fw = tas_priv->fmw;334int nr_program = ucontrol->value.integer.value[0];335int max = tas_fw->nr_programs - 1;336int val, ret = 0;337338val = clamp(nr_program, 0, max);339340guard(mutex)(&tas_priv->codec_lock);341342dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,343kcontrol->id.name, tas_priv->cur_prog, val);344345if (tas_priv->cur_prog != val) {346tas_priv->cur_prog = val;347ret = 1;348}349350return ret;351}352EXPORT_SYMBOL_NS_GPL(tasdevice_program_put, "SND_HDA_SCODEC_TAS2781");353354int tasdevice_config_get(struct snd_kcontrol *kcontrol,355struct snd_ctl_elem_value *ucontrol)356{357struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);358359ucontrol->value.integer.value[0] = tas_priv->cur_conf;360361dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,362kcontrol->id.name, tas_priv->cur_conf);363364return 0;365}366EXPORT_SYMBOL_NS_GPL(tasdevice_config_get, "SND_HDA_SCODEC_TAS2781");367368int tasdevice_config_put(struct snd_kcontrol *kcontrol,369struct snd_ctl_elem_value *ucontrol)370{371struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);372struct tasdevice_fw *tas_fw = tas_priv->fmw;373int nr_config = ucontrol->value.integer.value[0];374int max = tas_fw->nr_configurations - 1;375int val, ret = 0;376377val = clamp(nr_config, 0, max);378379guard(mutex)(&tas_priv->codec_lock);380381dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,382kcontrol->id.name, tas_priv->cur_conf, val);383384if (tas_priv->cur_conf != val) {385tas_priv->cur_conf = val;386ret = 1;387}388389return ret;390}391EXPORT_SYMBOL_NS_GPL(tasdevice_config_put, "SND_HDA_SCODEC_TAS2781");392393MODULE_DESCRIPTION("TAS2781 HDA Driver");394MODULE_LICENSE("GPL");395MODULE_AUTHOR("Shenghao Ding, TI, <[email protected]>");396397398