diff options
Diffstat (limited to 'sound/hda/codecs/side-codecs/tas2781_hda_i2c.c')
-rw-r--r-- | sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c new file mode 100644 index 000000000000..a0b132681804 --- /dev/null +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -0,0 +1,798 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// TAS2781 HDA I2C driver +// +// Copyright 2023 - 2025 Texas Instruments, Inc. +// +// Author: Shenghao Ding <shenghao-ding@ti.com> +// Current maintainer: Baojun Xu <baojun.xu@ti.com> + +#include <linux/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/pci_ids.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/tas2781-comlib-i2c.h> +#include <sound/tlv.h> +#include <sound/tas2770-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 "../generic.h" +#include "tas2781_hda.h" + +#define TAS2563_CAL_VAR_NAME_MAX 16 +#define TAS2563_CAL_ARRAY_SIZE 80 +#define TAS2563_CAL_DATA_SIZE 4 +#define TAS2563_MAX_CHANNELS 4 +#define TAS2563_CAL_CH_SIZE 20 + +#define TAS2563_CAL_R0_LOW TASDEVICE_REG(0, 0x0f, 0x48) +#define TAS2563_CAL_POWER TASDEVICE_REG(0, 0x0d, 0x3c) +#define TAS2563_CAL_INVR0 TASDEVICE_REG(0, 0x0f, 0x40) +#define TAS2563_CAL_TLIM TASDEVICE_REG(0, 0x10, 0x14) +#define TAS2563_CAL_R0 TASDEVICE_REG(0, 0x0f, 0x34) + +enum device_chip_id { + HDA_TAS2563, + HDA_TAS2770, + HDA_TAS2781, + HDA_OTHERS +}; + +struct tas2781_hda_i2c_priv { + struct snd_kcontrol *snd_ctls[2]; + int (*save_calibration)(struct tas2781_hda *h); + + int hda_chip_id; +}; + +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 != tas_priv->global_addr) { + tas_priv->tasdevice[tas_priv->ndev].dev_addr = + (unsigned int)sb->slave_address; + tas_priv->ndev++; + } + } + return 1; +} + +static const struct acpi_gpio_params speakerid_gpios = { 0, 0, false }; + +static const struct acpi_gpio_mapping tas2781_speaker_id_gpios[] = { + { "speakerid-gpios", &speakerid_gpios, 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; + uint32_t subid; + 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; + } + + physdev = get_device(acpi_get_first_physical_node(adev)); + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); + if (ret < 0) { + dev_err(p->dev, "Failed to get ACPI resource.\n"); + goto err; + } + sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); + if (IS_ERR(sub)) { + /* No subsys id in older tas2563 projects. */ + if (!strncmp(hid, "INT8866", sizeof("INT8866"))) + goto end_2563; + dev_err(p->dev, "Failed to get SUBSYS ID.\n"); + ret = PTR_ERR(sub); + goto err; + } + /* Speaker id was needed for ASUS projects. */ + ret = kstrtou32(sub, 16, &subid); + if (!ret && upper_16_bits(subid) == PCI_VENDOR_ID_ASUSTEK) { + ret = devm_acpi_dev_add_driver_gpios(p->dev, + tas2781_speaker_id_gpios); + if (ret < 0) + dev_err(p->dev, "Failed to add driver gpio %d.\n", + ret); + p->speaker_id = devm_gpiod_get(p->dev, "speakerid", GPIOD_IN); + if (IS_ERR(p->speaker_id)) { + dev_err(p->dev, "Failed to get Speaker id.\n"); + ret = PTR_ERR(p->speaker_id); + goto err; + } + } else { + p->speaker_id = NULL; + } + +end_2563: + acpi_dev_free_resource_list(&resources); + strscpy(p->dev_name, hid, sizeof(p->dev_name)); + put_device(physdev); + acpi_dev_put(adev); + + return 0; + +err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); + put_device(physdev); + 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_put_autosuspend(dev); + break; + default: + break; + } +} + +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); + + dev_dbg(tas_priv->dev, "%s: kcontrol %s: %ld\n", + __func__, kcontrol->id.name, ucontrol->value.integer.value[0]); + + 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); + + dev_dbg(tas_priv->dev, "%s: kcontrol %s: -> %ld\n", + __func__, kcontrol->id.name, ucontrol->value.integer.value[0]); + + /* 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: kcontrol %s: %d\n", + __func__, kcontrol->id.name, tas_priv->force_fwload_status); + + 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); + + dev_dbg(tas_priv->dev, "%s: kcontrol %s: %d -> %d\n", + __func__, kcontrol->id.name, + tas_priv->force_fwload_status, val); + + if (tas_priv->force_fwload_status == val) + change = false; + else { + change = true; + tas_priv->force_fwload_status = val; + } + + mutex_unlock(&tas_priv->codec_lock); + + return change; +} + +static const struct snd_kcontrol_new tas2770_snd_controls[] = { + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Volume", TAS2770_AMP_LEVEL, + 0, 0, 20, 0, tas2781_amp_getvol, + tas2781_amp_putvol, tas2770_amp_tlv), + ACARD_SINGLE_RANGE_EXT_TLV("Speaker Digital Volume", TAS2770_DVC_LEVEL, + 0, 0, 31, 0, tas2781_amp_getvol, + tas2781_amp_putvol, tas2770_dvc_tlv), +}; + +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 tasdevice_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 tasdevice_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 tasdevice_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 int tas2563_save_calibration(struct tas2781_hda *h) +{ + efi_guid_t efi_guid = tasdev_fct_efi_guid[LENOVO]; + char *vars[TASDEV_CALIB_N] = { + "R0_%d", "InvR0_%d", "R0_Low_%d", "Power_%d", "TLim_%d" + }; + efi_char16_t efi_name[TAS2563_CAL_VAR_NAME_MAX]; + unsigned long max_size = TAS2563_CAL_DATA_SIZE; + unsigned char var8[TAS2563_CAL_VAR_NAME_MAX]; + struct tasdevice_priv *p = h->hda_priv; + struct calidata *cd = &p->cali_data; + struct cali_reg *r = &cd->cali_reg_array; + unsigned int offset = 0; + unsigned char *data; + efi_status_t status; + unsigned int attr; + int ret, i, j, k; + + cd->cali_dat_sz_per_dev = TAS2563_CAL_DATA_SIZE * TASDEV_CALIB_N; + + /* extra byte for each device is the device number */ + cd->total_sz = (cd->cali_dat_sz_per_dev + 1) * p->ndev; + data = cd->data = devm_kzalloc(p->dev, cd->total_sz, + GFP_KERNEL); + if (!data) + return -ENOMEM; + + for (i = 0; i < p->ndev; ++i) { + data[offset] = i; + offset++; + for (j = 0; j < TASDEV_CALIB_N; ++j) { + ret = snprintf(var8, sizeof(var8), vars[j], i); + + if (ret < 0 || ret >= sizeof(var8) - 1) { + dev_err(p->dev, "%s: Read %s failed\n", + __func__, var8); + return -EINVAL; + } + /* + * Our variable names are ASCII by construction, but + * EFI names are wide chars. Convert and zero-pad. + */ + memset(efi_name, 0, sizeof(efi_name)); + for (k = 0; k < sizeof(var8) && var8[k]; k++) + efi_name[k] = var8[k]; + status = efi.get_variable(efi_name, + &efi_guid, &attr, &max_size, + &data[offset]); + if (status != EFI_SUCCESS || + max_size != TAS2563_CAL_DATA_SIZE) { + dev_warn(p->dev, + "Dev %d: Caldat[%d] read failed %ld\n", + i, j, status); + return -EINVAL; + } + offset += TAS2563_CAL_DATA_SIZE; + } + } + + if (cd->total_sz != offset) { + dev_err(p->dev, "%s: tot_size(%lu) and offset(%u) dismatch\n", + __func__, cd->total_sz, offset); + return -EINVAL; + } + + r->r0_reg = TAS2563_CAL_R0; + r->invr0_reg = TAS2563_CAL_INVR0; + r->r0_low_reg = TAS2563_CAL_R0_LOW; + r->pow_reg = TAS2563_CAL_POWER; + r->tlimit_reg = TAS2563_CAL_TLIM; + + /* + * TAS2781_FMWLIB supports two solutions of calibrated data. One is + * from the driver itself: driver reads the calibrated files directly + * during probe; The other from user space: during init of audio hal, + * the audio hal will pass the calibrated data via kcontrol interface. + * Driver will store this data in "struct calidata" for use. For hda + * device, calibrated data are usunally saved into UEFI. So Hda side + * codec driver use the mixture of these two solutions, driver reads + * the data from UEFI, then store this data in "struct calidata" for + * use. + */ + p->is_user_space_calidata = true; + + return 0; +} + +static void tas2781_hda_remove_controls(struct tas2781_hda *tas_hda) +{ + struct tas2781_hda_i2c_priv *hda_priv = tas_hda->hda_priv; + struct hda_codec *codec = tas_hda->priv->codec; + + snd_ctl_remove(codec->card, tas_hda->dsp_prog_ctl); + snd_ctl_remove(codec->card, tas_hda->dsp_conf_ctl); + + for (int i = ARRAY_SIZE(hda_priv->snd_ctls) - 1; i >= 0; i--) + snd_ctl_remove(codec->card, hda_priv->snd_ctls[i]); + + snd_ctl_remove(codec->card, tas_hda->prof_ctl); +} + +static void tasdev_add_kcontrols(struct tasdevice_priv *tas_priv, + struct snd_kcontrol **ctls, struct hda_codec *codec, + const struct snd_kcontrol_new *tas_snd_ctrls, int num_ctls) +{ + int i, ret; + + for (i = 0; i < num_ctls; i++) { + ctls[i] = snd_ctl_new1( + &tas_snd_ctrls[i], tas_priv); + ret = snd_ctl_add(codec->card, ctls[i]); + if (ret) { + dev_err(tas_priv->dev, + "Failed to add KControl %s = %d\n", + tas_snd_ctrls[i].name, ret); + break; + } + } +} + +static void tasdevice_dspfw_init(void *context) +{ + struct tasdevice_priv *tas_priv = context; + struct tas2781_hda *tas_hda = dev_get_drvdata(tas_priv->dev); + struct tas2781_hda_i2c_priv *hda_priv = tas_hda->hda_priv; + struct hda_codec *codec = tas_priv->codec; + int ret, spk_id; + + tasdevice_dsp_remove(tas_priv); + tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; + if (tas_priv->speaker_id != NULL) { + // Speaker id need to be checked for ASUS only. + spk_id = gpiod_get_value(tas_priv->speaker_id); + if (spk_id < 0) { + // Speaker id is not valid, use default. + dev_dbg(tas_priv->dev, "Wrong spk_id = %d\n", spk_id); + spk_id = 0; + } + snprintf(tas_priv->coef_binaryname, + sizeof(tas_priv->coef_binaryname), + "TAS2XXX%04X%d.bin", + lower_16_bits(codec->core.subsystem_id), + spk_id); + } else { + snprintf(tas_priv->coef_binaryname, + sizeof(tas_priv->coef_binaryname), + "TAS2XXX%04X.bin", + lower_16_bits(codec->core.subsystem_id)); + } + 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; + return; + } + tasdev_add_kcontrols(tas_priv, &tas_hda->dsp_prog_ctl, codec, + &tasdevice_dsp_prog_ctrl, 1); + tasdev_add_kcontrols(tas_priv, &tas_hda->dsp_conf_ctl, codec, + &tasdevice_dsp_conf_ctrl, 1); + + 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. + */ + hda_priv->save_calibration(tas_hda); +} + +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 tas2781_hda_i2c_priv *hda_priv = tas_hda->hda_priv; + struct hda_codec *codec = tas_priv->codec; + int 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_priv->fw_state = TASDEVICE_RCA_FW_OK; + tasdev_add_kcontrols(tas_priv, &tas_hda->prof_ctl, codec, + &tasdevice_prof_ctrl, 1); + + switch (hda_priv->hda_chip_id) { + case HDA_TAS2770: + tasdev_add_kcontrols(tas_priv, hda_priv->snd_ctls, codec, + &tas2770_snd_controls[0], + ARRAY_SIZE(tas2770_snd_controls)); + break; + case HDA_TAS2781: + tasdev_add_kcontrols(tas_priv, hda_priv->snd_ctls, codec, + &tas2781_snd_controls[0], + ARRAY_SIZE(tas2781_snd_controls)); + tasdevice_dspfw_init(context); + break; + case HDA_TAS2563: + tasdevice_dspfw_init(context); + break; + default: + break; + } + +out: + mutex_unlock(&tas_hda->priv->codec_lock); + release_firmware(fmw); + 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_parent *parent = master_data; + struct hda_component *comp; + struct hda_codec *codec; + unsigned int subid; + int ret; + + comp = hda_component_from_index(parent, tas_hda->priv->index); + if (!comp) + return -EINVAL; + + if (comp->dev) + return -EBUSY; + + codec = parent->codec; + subid = codec->core.subsystem_id >> 16; + + switch (subid) { + case 0x1028: + tas_hda->catlog_id = DELL; + break; + default: + tas_hda->catlog_id = LENOVO; + break; + } + + pm_runtime_get_sync(dev); + + comp->dev = dev; + + strscpy(comp->name, dev_name(dev), sizeof(comp->name)); + + ret = tascodec_init(tas_hda->priv, codec, THIS_MODULE, tasdev_fw_ready); + if (!ret) + comp->playback_hook = tas2781_hda_playback_hook; + + 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_parent *parent = master_data; + struct hda_component *comp; + + comp = hda_component_from_index(parent, tas_hda->priv->index); + if (comp && (comp->dev == dev)) { + comp->dev = NULL; + memset(comp->name, 0, sizeof(comp->name)); + comp->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 int tas2781_hda_i2c_probe(struct i2c_client *clt) +{ + struct tas2781_hda_i2c_priv *hda_priv; + 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; + + hda_priv = devm_kzalloc(&clt->dev, sizeof(*hda_priv), GFP_KERNEL); + if (!hda_priv) + return -ENOMEM; + + tas_hda->hda_priv = hda_priv; + + 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")) { + /* + * TAS2781, integrated on-chip DSP with + * global I2C address supported. + */ + device_name = "TIAS2781"; + hda_priv->hda_chip_id = HDA_TAS2781; + hda_priv->save_calibration = tas2781_save_calibration; + tas_hda->priv->global_addr = TAS2781_GLOBAL_ADDR; + } else if (strstarts(dev_name(&clt->dev), "i2c-TXNW2770")) { + /* + * TAS2770, has no on-chip DSP, so no calibration data + * required; has no global I2C address supported. + */ + device_name = "TXNW2770"; + hda_priv->hda_chip_id = HDA_TAS2770; + } else if (strstarts(dev_name(&clt->dev), + "i2c-TXNW2781:00-tas2781-hda.0")) { + device_name = "TXNW2781"; + hda_priv->save_calibration = tas2781_save_calibration; + tas_hda->priv->global_addr = TAS2781_GLOBAL_ADDR; + } else if (strstr(dev_name(&clt->dev), "INT8866")) { + /* + * TAS2563, integrated on-chip DSP with + * global I2C address supported. + */ + device_name = "INT8866"; + hda_priv->hda_chip_id = HDA_TAS2563; + hda_priv->save_calibration = tas2563_save_calibration; + tas_hda->priv->global_addr = TAS2563_GLOBAL_ADDR; + } else { + return -ENODEV; + } + + tas_hda->priv->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_enable(tas_hda->dev); + + tasdevice_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, &tas2781_hda_comp_ops); + return ret; +} + +static void tas2781_hda_i2c_remove(struct i2c_client *clt) +{ + tas2781_hda_remove(&clt->dev, &tas2781_hda_comp_ops); +} + +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); + + 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; + } + tasdevice_reset(tas_hda->priv); + tasdevice_prmg_load(tas_hda->priv, tas_hda->priv->cur_prog); + + 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" }, + {} +}; + +static const struct acpi_device_id tas2781_acpi_hda_match[] = { + {"INT8866", 0 }, + {"TIAS2781", 0 }, + {"TXNW2770", 0 }, + {"TXNW2781", 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"); +MODULE_IMPORT_NS("SND_HDA_SCODEC_TAS2781"); |