mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Now move the all remaining codec drivers from sound/pci/hda to sound/hda/codecs subdirectory. Some drivers are put under the further vendor subdirectory, and the vendor helper code (*_helper.c) are put under helpers subdirectory. Also the sub-codec drivers are moved under a different subdirectory, sound/hda/codecs/sub-codecs, for distinguishing from the main HD-audio codec drivers. The prefix patch_ and hda_ as well as the suffix _helper are dropped from file names as they are mostly superfluous. No functional changes but just file path shuffling. Reviewed-by: Richard Fitzgerald <rf@opensource.cirrus.com> Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://patch.msgid.link/20250709160434.1859-7-tiwai@suse.de
379 lines
11 KiB
C
379 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// TAS2781 HDA Shared Lib for I2C&SPI driver
|
|
//
|
|
// Copyright 2025 Texas Instruments, Inc.
|
|
//
|
|
// Author: Shenghao Ding <shenghao-ding@ti.com>
|
|
|
|
#include <linux/component.h>
|
|
#include <linux/crc8.h>
|
|
#include <linux/crc32.h>
|
|
#include <linux/efi.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tas2781.h>
|
|
|
|
#include "tas2781_hda.h"
|
|
|
|
const efi_guid_t tasdev_fct_efi_guid[] = {
|
|
/* DELL */
|
|
EFI_GUID(0xcc92382d, 0x6337, 0x41cb, 0xa8, 0x8b, 0x8e, 0xce, 0x74,
|
|
0x91, 0xea, 0x9f),
|
|
/* HP */
|
|
EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a,
|
|
0xa3, 0x5d, 0xb3),
|
|
/* LENOVO & OTHERS */
|
|
EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc, 0x09, 0x43, 0xa3, 0xf4,
|
|
0x31, 0x0a, 0x92),
|
|
};
|
|
EXPORT_SYMBOL_NS_GPL(tasdev_fct_efi_guid, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
static void tas2781_apply_calib(struct tasdevice_priv *p)
|
|
{
|
|
struct calidata *cali_data = &p->cali_data;
|
|
struct cali_reg *r = &cali_data->cali_reg_array;
|
|
unsigned char *data = cali_data->data;
|
|
unsigned int *tmp_val = (unsigned int *)data;
|
|
unsigned int cali_reg[TASDEV_CALIB_N] = {
|
|
TASDEVICE_REG(0, 0x17, 0x74),
|
|
TASDEVICE_REG(0, 0x18, 0x0c),
|
|
TASDEVICE_REG(0, 0x18, 0x14),
|
|
TASDEVICE_REG(0, 0x13, 0x70),
|
|
TASDEVICE_REG(0, 0x18, 0x7c),
|
|
};
|
|
unsigned int crc, oft, node_num;
|
|
unsigned char *buf;
|
|
int i, j, k, l;
|
|
|
|
if (tmp_val[0] == 2781) {
|
|
/*
|
|
* New features were added in calibrated Data V3:
|
|
* 1. Added calibration registers address define in
|
|
* a node, marked as Device id == 0x80.
|
|
* New features were added in calibrated Data V2:
|
|
* 1. Added some the fields to store the link_id and
|
|
* uniqie_id for multi-link solutions
|
|
* 2. Support flexible number of devices instead of
|
|
* fixed one in V1.
|
|
* Layout of calibrated data V2 in UEFI(total 256 bytes):
|
|
* ChipID (2781, 4 bytes)
|
|
* Data-Group-Sum (4 bytes)
|
|
* TimeStamp of Calibration (4 bytes)
|
|
* for (i = 0; i < Data-Group-Sum; i++) {
|
|
* if (Data type != 0x80) (4 bytes)
|
|
* Calibrated Data of Device #i (20 bytes)
|
|
* else
|
|
* Calibration registers address (5*4 = 20 bytes)
|
|
* # V2: No reg addr in data grp section.
|
|
* # V3: Normally the last grp is the reg addr.
|
|
* }
|
|
* CRC (4 bytes)
|
|
* Reserved (the rest)
|
|
*/
|
|
crc = crc32(~0, data, (3 + tmp_val[1] * 6) * 4) ^ ~0;
|
|
|
|
if (crc != tmp_val[3 + tmp_val[1] * 6]) {
|
|
cali_data->total_sz = 0;
|
|
dev_err(p->dev, "%s: CRC error\n", __func__);
|
|
return;
|
|
}
|
|
node_num = tmp_val[1];
|
|
|
|
for (j = 0, k = 0; j < node_num; j++) {
|
|
oft = j * 6 + 3;
|
|
if (tmp_val[oft] == TASDEV_UEFI_CALI_REG_ADDR_FLG) {
|
|
for (i = 0; i < TASDEV_CALIB_N; i++) {
|
|
buf = &data[(oft + i + 1) * 4];
|
|
cali_reg[i] = TASDEVICE_REG(buf[1],
|
|
buf[2], buf[3]);
|
|
}
|
|
} else {
|
|
l = j * (cali_data->cali_dat_sz_per_dev + 1);
|
|
if (k >= p->ndev || l > oft * 4) {
|
|
dev_err(p->dev, "%s: dev sum error\n",
|
|
__func__);
|
|
cali_data->total_sz = 0;
|
|
return;
|
|
}
|
|
|
|
data[l] = k;
|
|
oft++;
|
|
for (i = 0; i < TASDEV_CALIB_N * 4; i++)
|
|
data[l + i + 1] = data[4 * oft + i];
|
|
k++;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* Calibration data is in V1 format.
|
|
* struct cali_data {
|
|
* char cali_data[20];
|
|
* }
|
|
*
|
|
* struct {
|
|
* struct cali_data cali_data[4];
|
|
* int TimeStamp of Calibration (4 bytes)
|
|
* int CRC (4 bytes)
|
|
* } ueft;
|
|
*/
|
|
crc = crc32(~0, data, 84) ^ ~0;
|
|
if (crc != tmp_val[21]) {
|
|
cali_data->total_sz = 0;
|
|
dev_err(p->dev, "%s: V1 CRC error\n", __func__);
|
|
return;
|
|
}
|
|
|
|
for (j = p->ndev - 1; j >= 0; j--) {
|
|
l = j * (cali_data->cali_dat_sz_per_dev + 1);
|
|
for (i = TASDEV_CALIB_N * 4; i > 0 ; i--)
|
|
data[l + i] = data[p->index * 5 + i];
|
|
data[l+i] = j;
|
|
}
|
|
}
|
|
|
|
if (p->dspbin_typ == TASDEV_BASIC) {
|
|
r->r0_reg = cali_reg[0];
|
|
r->invr0_reg = cali_reg[1];
|
|
r->r0_low_reg = cali_reg[2];
|
|
r->pow_reg = cali_reg[3];
|
|
r->tlimit_reg = cali_reg[4];
|
|
}
|
|
|
|
p->is_user_space_calidata = true;
|
|
cali_data->total_sz = p->ndev * (cali_data->cali_dat_sz_per_dev + 1);
|
|
}
|
|
|
|
/*
|
|
* Update the calibration data, including speaker impedance, f0, etc,
|
|
* into algo. Calibrate data is done by manufacturer in the factory.
|
|
* The data is used by Algo for calculating the speaker temperature,
|
|
* speaker membrane excursion and f0 in real time during playback.
|
|
* Calibration data format in EFI is V2, since 2024.
|
|
*/
|
|
int tas2781_save_calibration(struct tas2781_hda *hda)
|
|
{
|
|
/*
|
|
* GUID was used for data access in BIOS, it was provided by board
|
|
* manufactory.
|
|
*/
|
|
efi_guid_t efi_guid = tasdev_fct_efi_guid[LENOVO];
|
|
static efi_char16_t efi_name[] = TASDEVICE_CALIBRATION_DATA_NAME;
|
|
struct tasdevice_priv *p = hda->priv;
|
|
struct calidata *cali_data = &p->cali_data;
|
|
unsigned long total_sz = 0;
|
|
unsigned int attr, size;
|
|
unsigned char *data;
|
|
efi_status_t status;
|
|
|
|
if (hda->catlog_id < LENOVO)
|
|
efi_guid = tasdev_fct_efi_guid[hda->catlog_id];
|
|
|
|
cali_data->cali_dat_sz_per_dev = 20;
|
|
size = p->ndev * (cali_data->cali_dat_sz_per_dev + 1);
|
|
/* Get real size of UEFI variable */
|
|
status = efi.get_variable(efi_name, &efi_guid, &attr, &total_sz, NULL);
|
|
cali_data->total_sz = total_sz > size ? total_sz : size;
|
|
if (status == EFI_BUFFER_TOO_SMALL) {
|
|
/* Allocate data buffer of data_size bytes */
|
|
data = p->cali_data.data = devm_kzalloc(p->dev,
|
|
p->cali_data.total_sz, GFP_KERNEL);
|
|
if (!data) {
|
|
p->cali_data.total_sz = 0;
|
|
return -ENOMEM;
|
|
}
|
|
/* Get variable contents into buffer */
|
|
status = efi.get_variable(efi_name, &efi_guid, &attr,
|
|
&p->cali_data.total_sz, data);
|
|
}
|
|
if (status != EFI_SUCCESS) {
|
|
p->cali_data.total_sz = 0;
|
|
return status;
|
|
}
|
|
|
|
tas2781_apply_calib(p);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tas2781_save_calibration, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
void tas2781_hda_remove(struct device *dev,
|
|
const struct component_ops *ops)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
|
|
component_del(tas_hda->dev, ops);
|
|
|
|
pm_runtime_get_sync(tas_hda->dev);
|
|
pm_runtime_disable(tas_hda->dev);
|
|
|
|
pm_runtime_put_noidle(tas_hda->dev);
|
|
|
|
tasdevice_remove(tas_hda->priv);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tas2781_hda_remove, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_info_profile(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_info_profile, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_info_programs(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = tas_priv->fmw->nr_programs - 1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_info_programs, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_info_config(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
struct tasdevice_fw *tas_fw = tas_priv->fmw;
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = tas_fw->nr_configurations - 1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_info_config, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id;
|
|
|
|
dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,
|
|
kcontrol->id.name, tas_priv->rcabin.profile_cfg_id);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_get_profile_id, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
int profile_id = ucontrol->value.integer.value[0];
|
|
int max = tas_priv->rcabin.ncfgs - 1;
|
|
int val, ret = 0;
|
|
|
|
val = clamp(profile_id, 0, max);
|
|
|
|
guard(mutex)(&tas_priv->codec_lock);
|
|
|
|
dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,
|
|
kcontrol->id.name, tas_priv->rcabin.profile_cfg_id, val);
|
|
|
|
if (tas_priv->rcabin.profile_cfg_id != val) {
|
|
tas_priv->rcabin.profile_cfg_id = val;
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_set_profile_id, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_program_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
ucontrol->value.integer.value[0] = tas_priv->cur_prog;
|
|
|
|
dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,
|
|
kcontrol->id.name, tas_priv->cur_prog);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_program_get, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_program_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
struct tasdevice_fw *tas_fw = tas_priv->fmw;
|
|
int nr_program = ucontrol->value.integer.value[0];
|
|
int max = tas_fw->nr_programs - 1;
|
|
int val, ret = 0;
|
|
|
|
val = clamp(nr_program, 0, max);
|
|
|
|
guard(mutex)(&tas_priv->codec_lock);
|
|
|
|
dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,
|
|
kcontrol->id.name, tas_priv->cur_prog, val);
|
|
|
|
if (tas_priv->cur_prog != val) {
|
|
tas_priv->cur_prog = val;
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_program_put, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_config_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
ucontrol->value.integer.value[0] = tas_priv->cur_conf;
|
|
|
|
dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d\n", __func__,
|
|
kcontrol->id.name, tas_priv->cur_conf);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_config_get, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
int tasdevice_config_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
struct tasdevice_fw *tas_fw = tas_priv->fmw;
|
|
int nr_config = ucontrol->value.integer.value[0];
|
|
int max = tas_fw->nr_configurations - 1;
|
|
int val, ret = 0;
|
|
|
|
val = clamp(nr_config, 0, max);
|
|
|
|
guard(mutex)(&tas_priv->codec_lock);
|
|
|
|
dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", __func__,
|
|
kcontrol->id.name, tas_priv->cur_conf, val);
|
|
|
|
if (tas_priv->cur_conf != val) {
|
|
tas_priv->cur_conf = val;
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(tasdevice_config_put, "SND_HDA_SCODEC_TAS2781");
|
|
|
|
MODULE_DESCRIPTION("TAS2781 HDA Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Shenghao Ding, TI, <shenghao-ding@ti.com>");
|