mirror of
https://github.com/nxp-imx/linux-imx.git
synced 2025-09-03 02:16:09 +02:00

[ Upstream commit829e2a2312
] The data conversion is done rather by a wrong function. We convert to BE32, not from BE32. Although the end result must be same, this was complained by the compiler. Fix the code again and align with another similar function tas2563_apply_calib() that does already right. Fixes:3beddef84d
("ALSA: hda/tas2781: fix wrong calibrated data order") Reported-by: kernel test robot <lkp@intel.com> Closes: https://lore.kernel.org/oe-kbuild-all/202408141630.DiDUB8Z4-lkp@intel.com/ Link: https://patch.msgid.link/20240814100500.1944-1-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Sasha Levin <sashal@kernel.org>
887 lines
22 KiB
C
887 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// TAS2781 HDA I2C driver
|
|
//
|
|
// Copyright 2023 - 2024 Texas Instruments, Inc.
|
|
//
|
|
// Author: Shenghao Ding <shenghao-ding@ti.com>
|
|
// Current maintainer: Baojun Xu <baojun.xu@ti.com>
|
|
|
|
#include <asm/unaligned.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/crc8.h>
|
|
#include <linux/crc32.h>
|
|
#include <linux/efi.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
#include <sound/hda_codec.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tas2781.h>
|
|
#include <sound/tlv.h>
|
|
#include <sound/tas2781-tlv.h>
|
|
|
|
#include "hda_local.h"
|
|
#include "hda_auto_parser.h"
|
|
#include "hda_component.h"
|
|
#include "hda_jack.h"
|
|
#include "hda_generic.h"
|
|
|
|
#define TASDEVICE_SPEAKER_CALIBRATION_SIZE 20
|
|
|
|
/* No standard control callbacks for SNDRV_CTL_ELEM_IFACE_CARD
|
|
* Define two controls, one is Volume control callbacks, the other is
|
|
* flag setting control callbacks.
|
|
*/
|
|
|
|
/* Volume control callbacks for tas2781 */
|
|
#define ACARD_SINGLE_RANGE_EXT_TLV(xname, xreg, xshift, xmin, xmax, xinvert, \
|
|
xhandler_get, xhandler_put, tlv_array) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = (xname),\
|
|
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
|
|
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
|
|
.tlv.p = (tlv_array), \
|
|
.info = snd_soc_info_volsw_range, \
|
|
.get = xhandler_get, .put = xhandler_put, \
|
|
.private_value = (unsigned long)&(struct soc_mixer_control) \
|
|
{.reg = xreg, .rreg = xreg, .shift = xshift, \
|
|
.rshift = xshift, .min = xmin, .max = xmax, \
|
|
.invert = xinvert} }
|
|
|
|
/* Flag control callbacks for tas2781 */
|
|
#define ACARD_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = xname, \
|
|
.info = snd_ctl_boolean_mono_info, \
|
|
.get = xhandler_get, .put = xhandler_put, \
|
|
.private_value = xdata }
|
|
|
|
enum calib_data {
|
|
R0_VAL = 0,
|
|
INV_R0,
|
|
R0LOW,
|
|
POWER,
|
|
TLIM,
|
|
CALIB_MAX
|
|
};
|
|
|
|
struct tas2781_hda {
|
|
struct device *dev;
|
|
struct tasdevice_priv *priv;
|
|
struct snd_kcontrol *dsp_prog_ctl;
|
|
struct snd_kcontrol *dsp_conf_ctl;
|
|
struct snd_kcontrol *prof_ctl;
|
|
struct snd_kcontrol *snd_ctls[2];
|
|
};
|
|
|
|
static int tas2781_get_i2c_res(struct acpi_resource *ares, void *data)
|
|
{
|
|
struct tasdevice_priv *tas_priv = data;
|
|
struct acpi_resource_i2c_serialbus *sb;
|
|
|
|
if (i2c_acpi_get_i2c_resource(ares, &sb)) {
|
|
if (tas_priv->ndev < TASDEVICE_MAX_CHANNELS &&
|
|
sb->slave_address != TAS2781_GLOBAL_ADDR) {
|
|
tas_priv->tasdevice[tas_priv->ndev].dev_addr =
|
|
(unsigned int)sb->slave_address;
|
|
tas_priv->ndev++;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid)
|
|
{
|
|
struct acpi_device *adev;
|
|
struct device *physdev;
|
|
LIST_HEAD(resources);
|
|
const char *sub;
|
|
int ret;
|
|
|
|
adev = acpi_dev_get_first_match_dev(hid, NULL, -1);
|
|
if (!adev) {
|
|
dev_err(p->dev,
|
|
"Failed to find an ACPI device for %s\n", hid);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
acpi_dev_free_resource_list(&resources);
|
|
strscpy(p->dev_name, hid, sizeof(p->dev_name));
|
|
physdev = get_device(acpi_get_first_physical_node(adev));
|
|
acpi_dev_put(adev);
|
|
|
|
/* No side-effect to the playback even if subsystem_id is NULL*/
|
|
sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev));
|
|
if (IS_ERR(sub))
|
|
sub = NULL;
|
|
|
|
p->acpi_subsystem_id = sub;
|
|
|
|
put_device(physdev);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
dev_err(p->dev, "read acpi error, ret: %d\n", ret);
|
|
acpi_dev_put(adev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void tas2781_hda_playback_hook(struct device *dev, int action)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
|
|
dev_dbg(tas_hda->dev, "%s: action = %d\n", __func__, action);
|
|
switch (action) {
|
|
case HDA_GEN_PCM_ACT_OPEN:
|
|
pm_runtime_get_sync(dev);
|
|
mutex_lock(&tas_hda->priv->codec_lock);
|
|
tasdevice_tuning_switch(tas_hda->priv, 0);
|
|
tas_hda->priv->playback_started = true;
|
|
mutex_unlock(&tas_hda->priv->codec_lock);
|
|
break;
|
|
case HDA_GEN_PCM_ACT_CLOSE:
|
|
mutex_lock(&tas_hda->priv->codec_lock);
|
|
tasdevice_tuning_switch(tas_hda->priv, 1);
|
|
tas_hda->priv->playback_started = false;
|
|
mutex_unlock(&tas_hda->priv->codec_lock);
|
|
|
|
pm_runtime_mark_last_busy(dev);
|
|
pm_runtime_put_autosuspend(dev);
|
|
break;
|
|
default:
|
|
dev_dbg(tas_hda->dev, "Playback action not supported: %d\n",
|
|
action);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id;
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static 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 nr_profile = ucontrol->value.integer.value[0];
|
|
int max = tas_priv->rcabin.ncfgs - 1;
|
|
int val, ret = 0;
|
|
|
|
val = clamp(nr_profile, 0, max);
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
if (tas_priv->rcabin.profile_cfg_id != val) {
|
|
tas_priv->rcabin.profile_cfg_id = val;
|
|
ret = 1;
|
|
}
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tasdevice_info_programs(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_programs - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static int tasdevice_program_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
ucontrol->value.integer.value[0] = tas_priv->cur_prog;
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static 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);
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
if (tas_priv->cur_prog != val) {
|
|
tas_priv->cur_prog = val;
|
|
ret = 1;
|
|
}
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tasdevice_config_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
ucontrol->value.integer.value[0] = tas_priv->cur_conf;
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static 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);
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
if (tas_priv->cur_conf != val) {
|
|
tas_priv->cur_conf = val;
|
|
ret = 1;
|
|
}
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tas2781_amp_getvol(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
int ret;
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
ret = tasdevice_amp_getvol(tas_priv, ucontrol, mc);
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tas2781_amp_putvol(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
struct soc_mixer_control *mc =
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
int ret;
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
/* The check of the given value is in tasdevice_amp_putvol. */
|
|
ret = tasdevice_amp_putvol(tas_priv, ucontrol, mc);
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tas2781_force_fwload_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
ucontrol->value.integer.value[0] = (int)tas_priv->force_fwload_status;
|
|
dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__,
|
|
tas_priv->force_fwload_status ? "ON" : "OFF");
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2781_force_fwload_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
|
|
bool change, val = (bool)ucontrol->value.integer.value[0];
|
|
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
if (tas_priv->force_fwload_status == val)
|
|
change = false;
|
|
else {
|
|
change = true;
|
|
tas_priv->force_fwload_status = val;
|
|
}
|
|
dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__,
|
|
tas_priv->force_fwload_status ? "ON" : "OFF");
|
|
|
|
mutex_unlock(&tas_priv->codec_lock);
|
|
|
|
return change;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new tas2781_snd_controls[] = {
|
|
ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain", TAS2781_AMP_LEVEL,
|
|
1, 0, 20, 0, tas2781_amp_getvol,
|
|
tas2781_amp_putvol, amp_vol_tlv),
|
|
ACARD_SINGLE_BOOL_EXT("Speaker Force Firmware Load", 0,
|
|
tas2781_force_fwload_get, tas2781_force_fwload_put),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new tas2781_prof_ctrl = {
|
|
.name = "Speaker Profile Id",
|
|
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
|
.info = tasdevice_info_profile,
|
|
.get = tasdevice_get_profile_id,
|
|
.put = tasdevice_set_profile_id,
|
|
};
|
|
|
|
static const struct snd_kcontrol_new tas2781_dsp_prog_ctrl = {
|
|
.name = "Speaker Program Id",
|
|
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
|
.info = tasdevice_info_programs,
|
|
.get = tasdevice_program_get,
|
|
.put = tasdevice_program_put,
|
|
};
|
|
|
|
static const struct snd_kcontrol_new tas2781_dsp_conf_ctrl = {
|
|
.name = "Speaker Config Id",
|
|
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
|
.info = tasdevice_info_config,
|
|
.get = tasdevice_config_get,
|
|
.put = tasdevice_config_put,
|
|
};
|
|
|
|
static void tas2781_apply_calib(struct tasdevice_priv *tas_priv)
|
|
{
|
|
static const unsigned char page_array[CALIB_MAX] = {
|
|
0x17, 0x18, 0x18, 0x13, 0x18,
|
|
};
|
|
static const unsigned char rgno_array[CALIB_MAX] = {
|
|
0x74, 0x0c, 0x14, 0x70, 0x7c,
|
|
};
|
|
int offset = 0;
|
|
int i, j, rc;
|
|
__be32 data;
|
|
|
|
for (i = 0; i < tas_priv->ndev; i++) {
|
|
for (j = 0; j < CALIB_MAX; j++) {
|
|
data = cpu_to_be32(
|
|
*(uint32_t *)&tas_priv->cali_data.data[offset]);
|
|
rc = tasdevice_dev_bulk_write(tas_priv, i,
|
|
TASDEVICE_REG(0, page_array[j], rgno_array[j]),
|
|
(unsigned char *)&data, 4);
|
|
if (rc < 0)
|
|
dev_err(tas_priv->dev,
|
|
"chn %d calib %d bulk_wr err = %d\n",
|
|
i, j, rc);
|
|
offset += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update the calibrate data, including speaker impedance, f0, etc, into algo.
|
|
* Calibrate data is done by manufacturer in the factory. These data are used
|
|
* by Algo for calucating the speaker temperature, speaker membrance excursion
|
|
* and f0 in real time during playback.
|
|
*/
|
|
static int tas2781_save_calibration(struct tasdevice_priv *tas_priv)
|
|
{
|
|
efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d,
|
|
0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3);
|
|
static efi_char16_t efi_name[] = L"CALI_DATA";
|
|
struct tm *tm = &tas_priv->tm;
|
|
unsigned int attr, crc;
|
|
unsigned int *tmp_val;
|
|
efi_status_t status;
|
|
|
|
/* Lenovo devices */
|
|
if (tas_priv->catlog_id == LENOVO)
|
|
efi_guid = EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc, 0x09,
|
|
0x43, 0xa3, 0xf4, 0x31, 0x0a, 0x92);
|
|
|
|
tas_priv->cali_data.total_sz = 0;
|
|
/* Get real size of UEFI variable */
|
|
status = efi.get_variable(efi_name, &efi_guid, &attr,
|
|
&tas_priv->cali_data.total_sz, tas_priv->cali_data.data);
|
|
if (status == EFI_BUFFER_TOO_SMALL) {
|
|
/* Allocate data buffer of data_size bytes */
|
|
tas_priv->cali_data.data = devm_kzalloc(tas_priv->dev,
|
|
tas_priv->cali_data.total_sz, GFP_KERNEL);
|
|
if (!tas_priv->cali_data.data)
|
|
return -ENOMEM;
|
|
/* Get variable contents into buffer */
|
|
status = efi.get_variable(efi_name, &efi_guid, &attr,
|
|
&tas_priv->cali_data.total_sz,
|
|
tas_priv->cali_data.data);
|
|
}
|
|
if (status != EFI_SUCCESS)
|
|
return -EINVAL;
|
|
|
|
tmp_val = (unsigned int *)tas_priv->cali_data.data;
|
|
|
|
crc = crc32(~0, tas_priv->cali_data.data, 84) ^ ~0;
|
|
dev_dbg(tas_priv->dev, "cali crc 0x%08x PK tmp_val 0x%08x\n",
|
|
crc, tmp_val[21]);
|
|
|
|
if (crc == tmp_val[21]) {
|
|
time64_to_tm(tmp_val[20], 0, tm);
|
|
dev_dbg(tas_priv->dev, "%4ld-%2d-%2d, %2d:%2d:%2d\n",
|
|
tm->tm_year, tm->tm_mon, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
tasdevice_apply_calibration(tas_priv);
|
|
} else
|
|
tas_priv->cali_data.total_sz = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tas2781_hda_remove_controls(struct tas2781_hda *tas_hda)
|
|
{
|
|
struct hda_codec *codec = tas_hda->priv->codec;
|
|
|
|
if (tas_hda->dsp_prog_ctl)
|
|
snd_ctl_remove(codec->card, tas_hda->dsp_prog_ctl);
|
|
|
|
if (tas_hda->dsp_conf_ctl)
|
|
snd_ctl_remove(codec->card, tas_hda->dsp_conf_ctl);
|
|
|
|
for (int i = ARRAY_SIZE(tas_hda->snd_ctls) - 1; i >= 0; i--)
|
|
if (tas_hda->snd_ctls[i])
|
|
snd_ctl_remove(codec->card, tas_hda->snd_ctls[i]);
|
|
|
|
if (tas_hda->prof_ctl)
|
|
snd_ctl_remove(codec->card, tas_hda->prof_ctl);
|
|
}
|
|
|
|
static void tasdev_fw_ready(const struct firmware *fmw, void *context)
|
|
{
|
|
struct tasdevice_priv *tas_priv = context;
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(tas_priv->dev);
|
|
struct hda_codec *codec = tas_priv->codec;
|
|
int i, ret;
|
|
|
|
pm_runtime_get_sync(tas_priv->dev);
|
|
mutex_lock(&tas_priv->codec_lock);
|
|
|
|
ret = tasdevice_rca_parser(tas_priv, fmw);
|
|
if (ret)
|
|
goto out;
|
|
|
|
tas_hda->prof_ctl = snd_ctl_new1(&tas2781_prof_ctrl, tas_priv);
|
|
ret = snd_ctl_add(codec->card, tas_hda->prof_ctl);
|
|
if (ret) {
|
|
dev_err(tas_priv->dev,
|
|
"Failed to add KControl %s = %d\n",
|
|
tas2781_prof_ctrl.name, ret);
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(tas2781_snd_controls); i++) {
|
|
tas_hda->snd_ctls[i] = snd_ctl_new1(&tas2781_snd_controls[i],
|
|
tas_priv);
|
|
ret = snd_ctl_add(codec->card, tas_hda->snd_ctls[i]);
|
|
if (ret) {
|
|
dev_err(tas_priv->dev,
|
|
"Failed to add KControl %s = %d\n",
|
|
tas2781_snd_controls[i].name, ret);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
tasdevice_dsp_remove(tas_priv);
|
|
|
|
tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING;
|
|
scnprintf(tas_priv->coef_binaryname, 64, "TAS2XXX%04X.bin",
|
|
codec->core.subsystem_id & 0xffff);
|
|
ret = tasdevice_dsp_parser(tas_priv);
|
|
if (ret) {
|
|
dev_err(tas_priv->dev, "dspfw load %s error\n",
|
|
tas_priv->coef_binaryname);
|
|
tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
|
|
goto out;
|
|
}
|
|
|
|
tas_hda->dsp_prog_ctl = snd_ctl_new1(&tas2781_dsp_prog_ctrl,
|
|
tas_priv);
|
|
ret = snd_ctl_add(codec->card, tas_hda->dsp_prog_ctl);
|
|
if (ret) {
|
|
dev_err(tas_priv->dev,
|
|
"Failed to add KControl %s = %d\n",
|
|
tas2781_dsp_prog_ctrl.name, ret);
|
|
goto out;
|
|
}
|
|
|
|
tas_hda->dsp_conf_ctl = snd_ctl_new1(&tas2781_dsp_conf_ctrl,
|
|
tas_priv);
|
|
ret = snd_ctl_add(codec->card, tas_hda->dsp_conf_ctl);
|
|
if (ret) {
|
|
dev_err(tas_priv->dev,
|
|
"Failed to add KControl %s = %d\n",
|
|
tas2781_dsp_conf_ctrl.name, ret);
|
|
goto out;
|
|
}
|
|
|
|
tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK;
|
|
tasdevice_prmg_load(tas_priv, 0);
|
|
if (tas_priv->fmw->nr_programs > 0)
|
|
tas_priv->cur_prog = 0;
|
|
if (tas_priv->fmw->nr_configurations > 0)
|
|
tas_priv->cur_conf = 0;
|
|
|
|
/* If calibrated data occurs error, dsp will still works with default
|
|
* calibrated data inside algo.
|
|
*/
|
|
tasdevice_save_calibration(tas_priv);
|
|
|
|
tasdevice_tuning_switch(tas_hda->priv, 0);
|
|
tas_hda->priv->playback_started = true;
|
|
|
|
out:
|
|
mutex_unlock(&tas_hda->priv->codec_lock);
|
|
if (fmw)
|
|
release_firmware(fmw);
|
|
pm_runtime_mark_last_busy(tas_hda->dev);
|
|
pm_runtime_put_autosuspend(tas_hda->dev);
|
|
}
|
|
|
|
static int tas2781_hda_bind(struct device *dev, struct device *master,
|
|
void *master_data)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
struct hda_component *comps = master_data;
|
|
struct hda_codec *codec;
|
|
unsigned int subid;
|
|
int ret;
|
|
|
|
if (!comps || tas_hda->priv->index < 0 ||
|
|
tas_hda->priv->index >= HDA_MAX_COMPONENTS)
|
|
return -EINVAL;
|
|
|
|
comps = &comps[tas_hda->priv->index];
|
|
if (comps->dev)
|
|
return -EBUSY;
|
|
|
|
codec = comps->codec;
|
|
subid = codec->core.subsystem_id >> 16;
|
|
|
|
switch (subid) {
|
|
case 0x17aa:
|
|
tas_hda->priv->catlog_id = LENOVO;
|
|
break;
|
|
default:
|
|
tas_hda->priv->catlog_id = OTHERS;
|
|
break;
|
|
}
|
|
|
|
pm_runtime_get_sync(dev);
|
|
|
|
comps->dev = dev;
|
|
|
|
strscpy(comps->name, dev_name(dev), sizeof(comps->name));
|
|
|
|
ret = tascodec_init(tas_hda->priv, codec, THIS_MODULE, tasdev_fw_ready);
|
|
if (!ret)
|
|
comps->playback_hook = tas2781_hda_playback_hook;
|
|
|
|
pm_runtime_mark_last_busy(dev);
|
|
pm_runtime_put_autosuspend(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void tas2781_hda_unbind(struct device *dev,
|
|
struct device *master, void *master_data)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
struct hda_component *comps = master_data;
|
|
comps = &comps[tas_hda->priv->index];
|
|
|
|
if (comps->dev == dev) {
|
|
comps->dev = NULL;
|
|
memset(comps->name, 0, sizeof(comps->name));
|
|
comps->playback_hook = NULL;
|
|
}
|
|
|
|
tas2781_hda_remove_controls(tas_hda);
|
|
|
|
tasdevice_config_info_remove(tas_hda->priv);
|
|
tasdevice_dsp_remove(tas_hda->priv);
|
|
|
|
tas_hda->priv->fw_state = TASDEVICE_DSP_FW_PENDING;
|
|
}
|
|
|
|
static const struct component_ops tas2781_hda_comp_ops = {
|
|
.bind = tas2781_hda_bind,
|
|
.unbind = tas2781_hda_unbind,
|
|
};
|
|
|
|
static void tas2781_hda_remove(struct device *dev)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
|
|
component_del(tas_hda->dev, &tas2781_hda_comp_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);
|
|
}
|
|
|
|
static int tas2781_hda_i2c_probe(struct i2c_client *clt)
|
|
{
|
|
struct tas2781_hda *tas_hda;
|
|
const char *device_name;
|
|
int ret;
|
|
|
|
|
|
tas_hda = devm_kzalloc(&clt->dev, sizeof(*tas_hda), GFP_KERNEL);
|
|
if (!tas_hda)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(&clt->dev, tas_hda);
|
|
tas_hda->dev = &clt->dev;
|
|
|
|
tas_hda->priv = tasdevice_kzalloc(clt);
|
|
if (!tas_hda->priv)
|
|
return -ENOMEM;
|
|
|
|
if (strstr(dev_name(&clt->dev), "TIAS2781")) {
|
|
device_name = "TIAS2781";
|
|
tas_hda->priv->save_calibration = tas2781_save_calibration;
|
|
tas_hda->priv->apply_calibration = tas2781_apply_calib;
|
|
} else
|
|
return -ENODEV;
|
|
|
|
tas_hda->priv->irq_info.irq = clt->irq;
|
|
ret = tas2781_read_acpi(tas_hda->priv, device_name);
|
|
if (ret)
|
|
return dev_err_probe(tas_hda->dev, ret,
|
|
"Platform not supported\n");
|
|
|
|
ret = tasdevice_init(tas_hda->priv);
|
|
if (ret)
|
|
goto err;
|
|
|
|
pm_runtime_set_autosuspend_delay(tas_hda->dev, 3000);
|
|
pm_runtime_use_autosuspend(tas_hda->dev);
|
|
pm_runtime_mark_last_busy(tas_hda->dev);
|
|
pm_runtime_set_active(tas_hda->dev);
|
|
pm_runtime_get_noresume(tas_hda->dev);
|
|
pm_runtime_enable(tas_hda->dev);
|
|
|
|
pm_runtime_put_autosuspend(tas_hda->dev);
|
|
|
|
tas2781_reset(tas_hda->priv);
|
|
|
|
ret = component_add(tas_hda->dev, &tas2781_hda_comp_ops);
|
|
if (ret) {
|
|
dev_err(tas_hda->dev, "Register component failed: %d\n", ret);
|
|
pm_runtime_disable(tas_hda->dev);
|
|
}
|
|
|
|
err:
|
|
if (ret)
|
|
tas2781_hda_remove(&clt->dev);
|
|
return ret;
|
|
}
|
|
|
|
static void tas2781_hda_i2c_remove(struct i2c_client *clt)
|
|
{
|
|
tas2781_hda_remove(&clt->dev);
|
|
}
|
|
|
|
static int tas2781_runtime_suspend(struct device *dev)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
|
|
dev_dbg(tas_hda->dev, "Runtime Suspend\n");
|
|
|
|
mutex_lock(&tas_hda->priv->codec_lock);
|
|
|
|
/* The driver powers up the amplifiers at module load time.
|
|
* Stop the playback if it's unused.
|
|
*/
|
|
if (tas_hda->priv->playback_started) {
|
|
tasdevice_tuning_switch(tas_hda->priv, 1);
|
|
tas_hda->priv->playback_started = false;
|
|
}
|
|
|
|
mutex_unlock(&tas_hda->priv->codec_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2781_runtime_resume(struct device *dev)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
|
|
dev_dbg(tas_hda->dev, "Runtime Resume\n");
|
|
|
|
mutex_lock(&tas_hda->priv->codec_lock);
|
|
|
|
tasdevice_prmg_load(tas_hda->priv, tas_hda->priv->cur_prog);
|
|
|
|
/* If calibrated data occurs error, dsp will still works with default
|
|
* calibrated data inside algo.
|
|
*/
|
|
tasdevice_apply_calibration(tas_hda->priv);
|
|
|
|
mutex_unlock(&tas_hda->priv->codec_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2781_system_suspend(struct device *dev)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
|
|
dev_dbg(tas_hda->priv->dev, "System Suspend\n");
|
|
|
|
mutex_lock(&tas_hda->priv->codec_lock);
|
|
|
|
/* Shutdown chip before system suspend */
|
|
if (tas_hda->priv->playback_started)
|
|
tasdevice_tuning_switch(tas_hda->priv, 1);
|
|
|
|
mutex_unlock(&tas_hda->priv->codec_lock);
|
|
|
|
/*
|
|
* Reset GPIO may be shared, so cannot reset here.
|
|
* However beyond this point, amps may be powered down.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static int tas2781_system_resume(struct device *dev)
|
|
{
|
|
struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
|
|
int i;
|
|
|
|
dev_dbg(tas_hda->priv->dev, "System Resume\n");
|
|
|
|
mutex_lock(&tas_hda->priv->codec_lock);
|
|
|
|
for (i = 0; i < tas_hda->priv->ndev; i++) {
|
|
tas_hda->priv->tasdevice[i].cur_book = -1;
|
|
tas_hda->priv->tasdevice[i].cur_prog = -1;
|
|
tas_hda->priv->tasdevice[i].cur_conf = -1;
|
|
}
|
|
tas2781_reset(tas_hda->priv);
|
|
tasdevice_prmg_load(tas_hda->priv, tas_hda->priv->cur_prog);
|
|
|
|
/* If calibrated data occurs error, dsp will still work with default
|
|
* calibrated data inside algo.
|
|
*/
|
|
tasdevice_apply_calibration(tas_hda->priv);
|
|
|
|
if (tas_hda->priv->playback_started)
|
|
tasdevice_tuning_switch(tas_hda->priv, 0);
|
|
|
|
mutex_unlock(&tas_hda->priv->codec_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops tas2781_hda_pm_ops = {
|
|
RUNTIME_PM_OPS(tas2781_runtime_suspend, tas2781_runtime_resume, NULL)
|
|
SYSTEM_SLEEP_PM_OPS(tas2781_system_suspend, tas2781_system_resume)
|
|
};
|
|
|
|
static const struct i2c_device_id tas2781_hda_i2c_id[] = {
|
|
{ "tas2781-hda", 0 },
|
|
{}
|
|
};
|
|
|
|
static const struct acpi_device_id tas2781_acpi_hda_match[] = {
|
|
{"TIAS2781", 0 },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, tas2781_acpi_hda_match);
|
|
|
|
static struct i2c_driver tas2781_hda_i2c_driver = {
|
|
.driver = {
|
|
.name = "tas2781-hda",
|
|
.acpi_match_table = tas2781_acpi_hda_match,
|
|
.pm = &tas2781_hda_pm_ops,
|
|
},
|
|
.id_table = tas2781_hda_i2c_id,
|
|
.probe = tas2781_hda_i2c_probe,
|
|
.remove = tas2781_hda_i2c_remove,
|
|
};
|
|
module_i2c_driver(tas2781_hda_i2c_driver);
|
|
|
|
MODULE_DESCRIPTION("TAS2781 HDA Driver");
|
|
MODULE_AUTHOR("Shenghao Ding, TI, <shenghao-ding@ti.com>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_IMPORT_NS(SND_SOC_TAS2781_FMWLIB);
|