diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2023-11-03 03:34:14 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2023-11-03 03:34:14 +0300 |
commit | edd8e84ae9514e93368f56c3715b11af52df6c3b (patch) | |
tree | 2b37c874c009ccef2e38157c2fc6040324902673 /sound/pci/hda | |
parent | 4ea4ed22b57846facd9cb4af5f67cb7bd2792cf3 (diff) | |
parent | dc6e08b1a2ae262c23e14f5c259b4ca63a554e4f (diff) | |
download | linux-edd8e84ae9514e93368f56c3715b11af52df6c3b.tar.xz |
Merge tag 'sound-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai:
"Most of changes at this time are for ASoC, spread over ASoC core and
drivers due to the API prefix standardization.
Other than that, there have little change wrt API, rather lots of
driver-specific updates and fixes.
Some highlight below:
ASoC:
- Standardization of API prefix
- GPIO API usage improvements
- Support for HDA patches
- Lots of work on SOF, including crash dump support
- Fixes for noise when stopping some Sounwire CODECs
- Support for AMD platforms with es83xx, AMD ACP 6.3 and 7.0, Awinc
AT87390 and AW88399, many Intel platforms, many Mediatek platforms,
Qualcomm SM6115 and SC7180 platforms, Richtek RTQ9128 and Texas
Instruments TAS575x
HD-audio and USB-audio:
- Deferred probe support of audio component binding
- More fixes and enhancements for Cirrus subcodecs
- USB Scarlett2 mixer and McIntosh DSD quirk
Others:
- More enhancement of snd-aloop driver
- Update MAINTAINERS entry for linux-sound mailing list"
* tag 'sound-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (485 commits)
ALSA: hda: cs35l41: Fix missing error code in cs35l41_smart_amp()
ALSA: hda: cs35l41: mark cs35l41_verify_id() static
ASoC: codecs: wsa883x: make use of new mute_unmute_on_trigger flag
ASoC: soc-dai: add flag to mute and unmute stream during trigger
ASoC: ams-delta.c: use component after check
ASoC: amd: acp: select SND_SOC_AMD_ACP_LEGACY_COMMON for ACP63
ASoC: codecs: aw88399: fix typo in Kconfig select
ASoC: amd: acp: add ACPI dependency
ASoC: Intel: avs: Add rt5514 machine board
ASoC: Intel: avs: Add rt5514 machine board
ALSA: scarlett2: Add missing check with firmware version control
ALSA: virtio: use ack callback
ALSA: scarlett2: Remap Level Meter values
ALSA: scarlett2: Allow passing any output to line_out_remap()
ALSA: scarlett2: Add support for reading firmware version
ALSA: scarlett2: Rename Gen 3 config sets
ALSA: scarlett2: Rename scarlett_gen2 to scarlett2
ASoC: cs35l41: Detect CSPL errors when sending CSPL commands
ALSA: hda: cs35l41: Check CSPL state after loading firmware
ALSA: hda: cs35l41: Do not unload firmware before reset in system suspend
...
Diffstat (limited to 'sound/pci/hda')
-rw-r--r-- | sound/pci/hda/Kconfig | 18 | ||||
-rw-r--r-- | sound/pci/hda/Makefile | 4 | ||||
-rw-r--r-- | sound/pci/hda/cirrus_scodec.c | 73 | ||||
-rw-r--r-- | sound/pci/hda/cirrus_scodec.h | 13 | ||||
-rw-r--r-- | sound/pci/hda/cirrus_scodec_test.c | 370 | ||||
-rw-r--r-- | sound/pci/hda/cs35l41_hda.c | 316 | ||||
-rw-r--r-- | sound/pci/hda/cs35l41_hda.h | 3 | ||||
-rw-r--r-- | sound/pci/hda/cs35l41_hda_property.c | 11 | ||||
-rw-r--r-- | sound/pci/hda/cs35l56_hda.c | 24 | ||||
-rw-r--r-- | sound/pci/hda/hda_codec.c | 2 | ||||
-rw-r--r-- | sound/pci/hda/hda_component.h | 4 | ||||
-rw-r--r-- | sound/pci/hda/hda_controller.c | 2 | ||||
-rw-r--r-- | sound/pci/hda/hda_intel.c | 62 | ||||
-rw-r--r-- | sound/pci/hda/patch_realtek.c | 83 |
14 files changed, 866 insertions, 119 deletions
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 0d7502d6e060..21a90b3c4cc7 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -91,6 +91,22 @@ config SND_HDA_PATCH_LOADER start up. The "patch" file can be specified via patch module option, such as patch=hda-init. +config SND_HDA_CIRRUS_SCODEC + tristate + +config SND_HDA_CIRRUS_SCODEC_KUNIT_TEST + tristate "KUnit test for Cirrus side-codec library" if !KUNIT_ALL_TESTS + select SND_HDA_CIRRUS_SCODEC + select GPIOLIB + depends on KUNIT + default KUNIT_ALL_TESTS + help + This builds KUnit tests for the cirrus side-codec library. + For more information on KUnit and unit tests in general, + please refer to the KUnit documentation in + Documentation/dev-tools/kunit/. + If in doubt, say "N". + config SND_HDA_SCODEC_CS35L41 tristate select SND_HDA_GENERIC @@ -144,6 +160,7 @@ config SND_HDA_SCODEC_CS35L56_I2C select SND_HDA_GENERIC select SND_SOC_CS35L56_SHARED select SND_HDA_SCODEC_CS35L56 + select SND_HDA_CIRRUS_SCODEC select SND_HDA_CS_DSP_CONTROLS help Say Y or M here to include CS35L56 amplifier support with @@ -158,6 +175,7 @@ config SND_HDA_SCODEC_CS35L56_SPI select SND_HDA_GENERIC select SND_SOC_CS35L56_SHARED select SND_HDA_SCODEC_CS35L56 + select SND_HDA_CIRRUS_SCODEC select SND_HDA_CS_DSP_CONTROLS help Say Y or M here to include CS35L56 amplifier support with diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index f00fc9ed6096..793e296c3f64 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -28,6 +28,8 @@ snd-hda-codec-via-objs := patch_via.o snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o # side codecs +snd-hda-cirrus-scodec-objs := cirrus_scodec.o +snd-hda-cirrus-scodec-test-objs := cirrus_scodec_test.o snd-hda-scodec-cs35l41-objs := cs35l41_hda.o cs35l41_hda_property.o snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o @@ -56,6 +58,8 @@ obj-$(CONFIG_SND_HDA_CODEC_VIA) += snd-hda-codec-via.o obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o # side codecs +obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC) += snd-hda-cirrus-scodec.o +obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC_KUNIT_TEST) += snd-hda-cirrus-scodec-test.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o diff --git a/sound/pci/hda/cirrus_scodec.c b/sound/pci/hda/cirrus_scodec.c new file mode 100644 index 000000000000..8de3bc7448fa --- /dev/null +++ b/sound/pci/hda/cirrus_scodec.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Common code for Cirrus side-codecs. +// +// Copyright (C) 2021, 2023 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include <linux/dev_printk.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> + +#include "cirrus_scodec.h" + +int cirrus_scodec_get_speaker_id(struct device *dev, int amp_index, + int num_amps, int fixed_gpio_id) +{ + struct gpio_desc *speaker_id_desc; + int speaker_id = -ENOENT; + + if (fixed_gpio_id >= 0) { + dev_dbg(dev, "Found Fixed Speaker ID GPIO (index = %d)\n", fixed_gpio_id); + speaker_id_desc = gpiod_get_index(dev, NULL, fixed_gpio_id, GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + return speaker_id; + } + speaker_id = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + } else { + int base_index; + int gpios_per_amp; + int count; + int tmp; + int i; + + count = gpiod_count(dev, "spk-id"); + if (count > 0) { + speaker_id = 0; + gpios_per_amp = count / num_amps; + base_index = gpios_per_amp * amp_index; + + if (count % num_amps) + return -EINVAL; + + dev_dbg(dev, "Found %d Speaker ID GPIOs per Amp\n", gpios_per_amp); + + for (i = 0; i < gpios_per_amp; i++) { + speaker_id_desc = gpiod_get_index(dev, "spk-id", i + base_index, + GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + break; + } + tmp = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + if (tmp < 0) { + speaker_id = tmp; + break; + } + speaker_id |= tmp << i; + } + } + } + + dev_dbg(dev, "Speaker ID = %d\n", speaker_id); + + return speaker_id; +} +EXPORT_SYMBOL_NS_GPL(cirrus_scodec_get_speaker_id, SND_HDA_CIRRUS_SCODEC); + +MODULE_DESCRIPTION("HDA Cirrus side-codec library"); +MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/cirrus_scodec.h b/sound/pci/hda/cirrus_scodec.h new file mode 100644 index 000000000000..ba2041d8ef24 --- /dev/null +++ b/sound/pci/hda/cirrus_scodec.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2023 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef CIRRUS_SCODEC_H +#define CIRRUS_SCODEC_H + +int cirrus_scodec_get_speaker_id(struct device *dev, int amp_index, + int num_amps, int fixed_gpio_id); + +#endif /* CIRRUS_SCODEC_H */ diff --git a/sound/pci/hda/cirrus_scodec_test.c b/sound/pci/hda/cirrus_scodec_test.c new file mode 100644 index 000000000000..8ae373676bd1 --- /dev/null +++ b/sound/pci/hda/cirrus_scodec_test.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// KUnit test for the Cirrus side-codec library. +// +// Copyright (C) 2023 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include <kunit/test.h> +#include <linux/gpio/driver.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "cirrus_scodec.h" + +struct cirrus_scodec_test_gpio { + unsigned int pin_state; + struct gpio_chip chip; +}; + +struct cirrus_scodec_test_priv { + struct platform_device amp_pdev; + struct platform_device *gpio_pdev; + struct cirrus_scodec_test_gpio *gpio_priv; +}; + +static int cirrus_scodec_test_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return GPIO_LINE_DIRECTION_IN; +} + +static int cirrus_scodec_test_gpio_direction_in(struct gpio_chip *chip, + unsigned int offset) +{ + return 0; +} + +static int cirrus_scodec_test_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct cirrus_scodec_test_gpio *gpio_priv = gpiochip_get_data(chip); + + return !!(gpio_priv->pin_state & BIT(offset)); +} + +static int cirrus_scodec_test_gpio_direction_out(struct gpio_chip *chip, + unsigned int offset, int value) +{ + return -EOPNOTSUPP; +} + +static void cirrus_scodec_test_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ +} + +static int cirrus_scodec_test_gpio_set_config(struct gpio_chip *gc, + unsigned int offset, + unsigned long config) +{ + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_OUTPUT: + case PIN_CONFIG_OUTPUT_ENABLE: + return -EOPNOTSUPP; + default: + return 0; + } +} + +static const struct gpio_chip cirrus_scodec_test_gpio_chip = { + .label = "cirrus_scodec_test_gpio", + .owner = THIS_MODULE, + .request = gpiochip_generic_request, + .free = gpiochip_generic_free, + .get_direction = cirrus_scodec_test_gpio_get_direction, + .direction_input = cirrus_scodec_test_gpio_direction_in, + .get = cirrus_scodec_test_gpio_get, + .direction_output = cirrus_scodec_test_gpio_direction_out, + .set = cirrus_scodec_test_gpio_set, + .set_config = cirrus_scodec_test_gpio_set_config, + .base = -1, + .ngpio = 32, +}; + +static int cirrus_scodec_test_gpio_probe(struct platform_device *pdev) +{ + struct cirrus_scodec_test_gpio *gpio_priv; + int ret; + + gpio_priv = devm_kzalloc(&pdev->dev, sizeof(*gpio_priv), GFP_KERNEL); + if (!gpio_priv) + return -ENOMEM; + + /* GPIO core modifies our struct gpio_chip so use a copy */ + gpio_priv->chip = cirrus_scodec_test_gpio_chip; + ret = devm_gpiochip_add_data(&pdev->dev, &gpio_priv->chip, gpio_priv); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to add gpiochip\n"); + + dev_set_drvdata(&pdev->dev, gpio_priv); + + return 0; +} + +static struct platform_driver cirrus_scodec_test_gpio_driver = { + .driver.name = "cirrus_scodec_test_gpio_drv", + .probe = cirrus_scodec_test_gpio_probe, +}; + +/* software_node referencing the gpio driver */ +static const struct software_node cirrus_scodec_test_gpio_swnode = { + .name = "cirrus_scodec_test_gpio", +}; + +static int cirrus_scodec_test_create_gpio(struct kunit *test) +{ + struct cirrus_scodec_test_priv *priv = test->priv; + int ret; + + priv->gpio_pdev = platform_device_alloc(cirrus_scodec_test_gpio_driver.driver.name, -1); + if (!priv->gpio_pdev) + return -ENOMEM; + + ret = device_add_software_node(&priv->gpio_pdev->dev, &cirrus_scodec_test_gpio_swnode); + if (ret) { + platform_device_put(priv->gpio_pdev); + KUNIT_FAIL(test, "Failed to add swnode to gpio: %d\n", ret); + return ret; + } + + ret = platform_device_add(priv->gpio_pdev); + if (ret) { + platform_device_put(priv->gpio_pdev); + KUNIT_FAIL(test, "Failed to add gpio platform device: %d\n", ret); + return ret; + } + + priv->gpio_priv = dev_get_drvdata(&priv->gpio_pdev->dev); + if (!priv->gpio_priv) { + platform_device_put(priv->gpio_pdev); + KUNIT_FAIL(test, "Failed to get gpio private data\n"); + return -EINVAL; + } + + return 0; +} + +static void cirrus_scodec_test_set_gpio_ref_arg(struct software_node_ref_args *arg, + int gpio_num) +{ + struct software_node_ref_args template = + SOFTWARE_NODE_REFERENCE(&cirrus_scodec_test_gpio_swnode, gpio_num, 0); + + *arg = template; +} + +static int cirrus_scodec_test_set_spkid_swnode(struct kunit *test, + struct device *dev, + struct software_node_ref_args *args, + int num_args) +{ + const struct property_entry props_template[] = { + PROPERTY_ENTRY_REF_ARRAY_LEN("spk-id-gpios", args, num_args), + { } + }; + struct property_entry *props; + struct software_node *node; + + node = kunit_kzalloc(test, sizeof(*node), GFP_KERNEL); + if (!node) + return -ENOMEM; + + props = kunit_kzalloc(test, sizeof(props_template), GFP_KERNEL); + if (!props) + return -ENOMEM; + + memcpy(props, props_template, sizeof(props_template)); + node->properties = props; + + return device_add_software_node(dev, node); +} + +struct cirrus_scodec_test_spkid_param { + int num_amps; + int gpios_per_amp; + int num_amps_sharing; +}; + +static void cirrus_scodec_test_spkid_parse(struct kunit *test) +{ + struct cirrus_scodec_test_priv *priv = test->priv; + const struct cirrus_scodec_test_spkid_param *param = test->param_value; + int num_spk_id_refs = param->num_amps * param->gpios_per_amp; + struct software_node_ref_args *refs; + struct device *dev = &priv->amp_pdev.dev; + unsigned int v; + int i, ret; + + refs = kunit_kcalloc(test, num_spk_id_refs, sizeof(*refs), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, refs); + + for (i = 0, v = 0; i < num_spk_id_refs; ) { + cirrus_scodec_test_set_gpio_ref_arg(&refs[i++], v++); + + /* + * If amps are sharing GPIOs repeat the last set of + * GPIOs until we've done that number of amps. + * We have done all GPIOs for an amp when i is a multiple + * of gpios_per_amp. + * We have done all amps sharing the same GPIOs when i is + * a multiple of (gpios_per_amp * num_amps_sharing). + */ + if (!(i % param->gpios_per_amp) && + (i % (param->gpios_per_amp * param->num_amps_sharing))) + v -= param->gpios_per_amp; + } + + ret = cirrus_scodec_test_set_spkid_swnode(test, dev, refs, num_spk_id_refs); + KUNIT_EXPECT_EQ_MSG(test, ret, 0, "Failed to add swnode\n"); + + for (i = 0; i < param->num_amps; ++i) { + for (v = 0; v < (1 << param->gpios_per_amp); ++v) { + /* Set only the GPIO bits used by this amp */ + priv->gpio_priv->pin_state = + v << (param->gpios_per_amp * (i / param->num_amps_sharing)); + + ret = cirrus_scodec_get_speaker_id(dev, i, param->num_amps, -1); + KUNIT_EXPECT_EQ_MSG(test, ret, v, + "get_speaker_id failed amp:%d pin_state:%#x\n", + i, priv->gpio_priv->pin_state); + } + } +} + +static void cirrus_scodec_test_no_spkid(struct kunit *test) +{ + struct cirrus_scodec_test_priv *priv = test->priv; + struct device *dev = &priv->amp_pdev.dev; + int ret; + + ret = cirrus_scodec_get_speaker_id(dev, 0, 4, -1); + KUNIT_EXPECT_EQ(test, ret, -ENOENT); +} + +static void cirrus_scodec_test_dev_release(struct device *dev) +{ +} + +static int cirrus_scodec_test_case_init(struct kunit *test) +{ + struct cirrus_scodec_test_priv *priv; + int ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + test->priv = priv; + + /* Create dummy GPIO */ + ret = cirrus_scodec_test_create_gpio(test); + if (ret < 0) + return ret; + + /* Create dummy amp driver dev */ + priv->amp_pdev.name = "cirrus_scodec_test_amp_drv"; + priv->amp_pdev.id = -1; + priv->amp_pdev.dev.release = cirrus_scodec_test_dev_release; + ret = platform_device_register(&priv->amp_pdev); + KUNIT_ASSERT_GE_MSG(test, ret, 0, "Failed to register amp platform device\n"); + + return 0; +} + +static void cirrus_scodec_test_case_exit(struct kunit *test) +{ + struct cirrus_scodec_test_priv *priv = test->priv; + + if (priv->amp_pdev.name) + platform_device_unregister(&priv->amp_pdev); + + if (priv->gpio_pdev) { + device_remove_software_node(&priv->gpio_pdev->dev); + platform_device_unregister(priv->gpio_pdev); + } +} + +static int cirrus_scodec_test_suite_init(struct kunit_suite *suite) +{ + int ret; + + /* Register mock GPIO driver */ + ret = platform_driver_register(&cirrus_scodec_test_gpio_driver); + if (ret < 0) { + kunit_err(suite, "Failed to register gpio platform driver, %d\n", ret); + return ret; + } + + return 0; +} + +static void cirrus_scodec_test_suite_exit(struct kunit_suite *suite) +{ + platform_driver_unregister(&cirrus_scodec_test_gpio_driver); +} + +static const struct cirrus_scodec_test_spkid_param cirrus_scodec_test_spkid_param_cases[] = { + { .num_amps = 2, .gpios_per_amp = 1, .num_amps_sharing = 1 }, + { .num_amps = 2, .gpios_per_amp = 2, .num_amps_sharing = 1 }, + { .num_amps = 2, .gpios_per_amp = 3, .num_amps_sharing = 1 }, + { .num_amps = 2, .gpios_per_amp = 4, .num_amps_sharing = 1 }, + { .num_amps = 3, .gpios_per_amp = 1, .num_amps_sharing = 1 }, + { .num_amps = 3, .gpios_per_amp = 2, .num_amps_sharing = 1 }, + { .num_amps = 3, .gpios_per_amp = 3, .num_amps_sharing = 1 }, + { .num_amps = 3, .gpios_per_amp = 4, .num_amps_sharing = 1 }, + { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 1 }, + { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 1 }, + { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 1 }, + { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 1 }, + + /* Same GPIO shared by all amps */ + { .num_amps = 2, .gpios_per_amp = 1, .num_amps_sharing = 2 }, + { .num_amps = 2, .gpios_per_amp = 2, .num_amps_sharing = 2 }, + { .num_amps = 2, .gpios_per_amp = 3, .num_amps_sharing = 2 }, + { .num_amps = 2, .gpios_per_amp = 4, .num_amps_sharing = 2 }, + { .num_amps = 3, .gpios_per_amp = 1, .num_amps_sharing = 3 }, + { .num_amps = 3, .gpios_per_amp = 2, .num_amps_sharing = 3 }, + { .num_amps = 3, .gpios_per_amp = 3, .num_amps_sharing = 3 }, + { .num_amps = 3, .gpios_per_amp = 4, .num_amps_sharing = 3 }, + { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 4 }, + { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 4 }, + { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 4 }, + { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 4 }, + + /* Two sets of shared GPIOs */ + { .num_amps = 4, .gpios_per_amp = 1, .num_amps_sharing = 2 }, + { .num_amps = 4, .gpios_per_amp = 2, .num_amps_sharing = 2 }, + { .num_amps = 4, .gpios_per_amp = 3, .num_amps_sharing = 2 }, + { .num_amps = 4, .gpios_per_amp = 4, .num_amps_sharing = 2 }, +}; + +static void cirrus_scodec_test_spkid_param_desc(const struct cirrus_scodec_test_spkid_param *param, + char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "amps:%d gpios_per_amp:%d num_amps_sharing:%d", + param->num_amps, param->gpios_per_amp, param->num_amps_sharing); +} + +KUNIT_ARRAY_PARAM(cirrus_scodec_test_spkid, cirrus_scodec_test_spkid_param_cases, + cirrus_scodec_test_spkid_param_desc); + +static struct kunit_case cirrus_scodec_test_cases[] = { + KUNIT_CASE_PARAM(cirrus_scodec_test_spkid_parse, cirrus_scodec_test_spkid_gen_params), + KUNIT_CASE(cirrus_scodec_test_no_spkid), + { } /* terminator */ +}; + +static struct kunit_suite cirrus_scodec_test_suite = { + .name = "snd-hda-scodec-cs35l56-test", + .suite_init = cirrus_scodec_test_suite_init, + .suite_exit = cirrus_scodec_test_suite_exit, + .init = cirrus_scodec_test_case_init, + .exit = cirrus_scodec_test_case_exit, + .test_cases = cirrus_scodec_test_cases, +}; + +kunit_test_suite(cirrus_scodec_test_suite); + +MODULE_IMPORT_NS(SND_HDA_CIRRUS_SCODEC); +MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index c6031f744099..b2db8091f0ed 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -33,6 +33,9 @@ #define CAL_AMBIENT_DSP_CTL_NAME "CAL_AMBIENT" #define CAL_DSP_CTL_TYPE 5 #define CAL_DSP_CTL_ALG 205 +#define CS35L41_UUID "50d90cdc-3de4-4f18-b528-c7fe3b71f40d" +#define CS35L41_DSM_GET_MUTE 5 +#define CS35L41_NOTIFY_EVENT 0x91 static bool firmware_autostart = 1; module_param(firmware_autostart, bool, 0444); @@ -563,6 +566,31 @@ static void cs35l41_hda_play_start(struct device *dev) } +static void cs35l41_mute(struct device *dev, bool mute) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct regmap *reg = cs35l41->regmap; + + dev_dbg(dev, "Mute(%d:%d) Playback Started: %d\n", mute, cs35l41->mute_override, + cs35l41->playback_started); + + if (cs35l41->playback_started) { + if (mute || cs35l41->mute_override) { + dev_dbg(dev, "Muting\n"); + regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); + } else { + dev_dbg(dev, "Unmuting\n"); + if (cs35l41->firmware_running) { + regmap_multi_reg_write(reg, cs35l41_hda_unmute_dsp, + ARRAY_SIZE(cs35l41_hda_unmute_dsp)); + } else { + regmap_multi_reg_write(reg, cs35l41_hda_unmute, + ARRAY_SIZE(cs35l41_hda_unmute)); + } + } + } +} + static void cs35l41_hda_play_done(struct device *dev) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); @@ -570,15 +598,9 @@ static void cs35l41_hda_play_done(struct device *dev) dev_dbg(dev, "Play (Complete)\n"); - cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL, + cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, cs35l41->firmware_running); - if (cs35l41->firmware_running) { - regmap_multi_reg_write(reg, cs35l41_hda_unmute_dsp, - ARRAY_SIZE(cs35l41_hda_unmute_dsp)); - } else { - regmap_multi_reg_write(reg, cs35l41_hda_unmute, - ARRAY_SIZE(cs35l41_hda_unmute)); - } + cs35l41_mute(dev, false); } static void cs35l41_hda_pause_start(struct device *dev) @@ -588,8 +610,8 @@ static void cs35l41_hda_pause_start(struct device *dev) dev_dbg(dev, "Pause (Start)\n"); - regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); - cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL, + cs35l41_mute(dev, true); + cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, cs35l41->firmware_running); } @@ -708,43 +730,46 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi rx_slot); } -static int cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) +static int cs35l41_verify_id(struct cs35l41_hda *cs35l41, unsigned int *regid, unsigned int *reg_revid) { - int ret = 0; + unsigned int mtl_revid, chipid; + int ret; - mutex_lock(&cs35l41->fw_mutex); - if (cs35l41->firmware_running) { + ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, regid); + if (ret) { + dev_err_probe(cs35l41->dev, ret, "Get Device ID failed\n"); + return ret; + } - regcache_cache_only(cs35l41->regmap, false); + ret = regmap_read(cs35l41->regmap, CS35L41_REVID, reg_revid); + if (ret) { + dev_err_probe(cs35l41->dev, ret, "Get Revision ID failed\n"); + return ret; + } - ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); - if (ret) { - dev_warn(cs35l41->dev, "Unable to exit Hibernate."); - goto err; - } + mtl_revid = *reg_revid & CS35L41_MTLREVID_MASK; - /* Test key needs to be unlocked to allow the OTP settings to re-apply */ - cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); - ret = regcache_sync(cs35l41->regmap); - cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); - if (ret) { - dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); - goto err; - } + chipid = (mtl_revid % 2) ? CS35L41R_CHIP_ID : CS35L41_CHIP_ID; + if (*regid != chipid) { + dev_err(cs35l41->dev, "CS35L41 Device ID (%X). Expected ID %X\n", *regid, chipid); + return -ENODEV; + } - if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) - cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); + return 0; +} - cs35l41_shutdown_dsp(cs35l41); - cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); +static int cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) +{ + mutex_lock(&cs35l41->fw_mutex); + if (cs35l41->firmware_running) { + cs35l41->cs_dsp.running = false; + cs35l41->cs_dsp.booted = false; + cs35l41->firmware_running = false; } -err: - regcache_cache_only(cs35l41->regmap, true); regcache_mark_dirty(cs35l41->regmap); - mutex_unlock(&cs35l41->fw_mutex); - return ret; + return 0; } static int cs35l41_system_suspend_prep(struct device *dev) @@ -791,17 +816,44 @@ static int cs35l41_system_suspend(struct device *dev) /* Shutdown DSP before system suspend */ ret = cs35l41_ready_for_reset(cs35l41); - if (ret) dev_err(dev, "System Suspend Failed, not ready for Reset: %d\n", ret); - /* - * Reset GPIO may be shared, so cannot reset here. - * However beyond this point, amps may be powered down. - */ + if (cs35l41->reset_gpio) { + dev_info(cs35l41->dev, "Asserting Reset\n"); + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); + usleep_range(2000, 2100); + } + + dev_dbg(cs35l41->dev, "System Suspended\n"); + return ret; } +static int cs35l41_wait_boot_done(struct cs35l41_hda *cs35l41) +{ + unsigned int int_status; + int ret; + + ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, int_status, + int_status & CS35L41_OTP_BOOT_DONE, 1000, 100000); + if (ret) { + dev_err(cs35l41->dev, "Failed waiting for OTP_BOOT_DONE\n"); + return ret; + } + + ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); + if (ret || (int_status & CS35L41_OTP_BOOT_ERR)) { + dev_err(cs35l41->dev, "OTP Boot status %x error\n", + int_status & CS35L41_OTP_BOOT_ERR); + if (!ret) + ret = -EIO; + return ret; + } + + return 0; +} + static int cs35l41_system_resume(struct device *dev) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); @@ -815,12 +867,24 @@ static int cs35l41_system_resume(struct device *dev) } if (cs35l41->reset_gpio) { + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); usleep_range(2000, 2100); gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); } usleep_range(2000, 2100); + regcache_cache_only(cs35l41->regmap, false); + + regmap_write(cs35l41->regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); + usleep_range(2000, 2100); + + ret = cs35l41_wait_boot_done(cs35l41); + if (ret) + return ret; + + regcache_cache_only(cs35l41->regmap, true); + ret = pm_runtime_force_resume(dev); if (ret) { dev_err(dev, "System Resume Failed: Unable to runtime resume: %d\n", ret); @@ -882,6 +946,7 @@ err: static int cs35l41_runtime_resume(struct device *dev) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + unsigned int regid, reg_revid; int ret = 0; dev_dbg(cs35l41->dev, "Runtime Resume\n"); @@ -903,6 +968,10 @@ static int cs35l41_runtime_resume(struct device *dev) } } + ret = cs35l41_verify_id(cs35l41, ®id, ®_revid); + if (ret) + goto err; + /* Test key needs to be unlocked to allow the OTP settings to re-apply */ cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); ret = regcache_sync(cs35l41->regmap); @@ -915,6 +984,8 @@ static int cs35l41_runtime_resume(struct device *dev) if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); + dev_dbg(cs35l41->dev, "CS35L41 Resumed (%x), Revision: %02X\n", regid, reg_revid); + err: mutex_unlock(&cs35l41->fw_mutex); @@ -923,6 +994,7 @@ err: static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) { + unsigned int fw_status; __be32 halo_sts; int ret; @@ -956,6 +1028,24 @@ static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) goto clean_dsp; } + ret = regmap_read(cs35l41->regmap, CS35L41_DSP_MBOX_2, &fw_status); + if (ret < 0) { + dev_err(cs35l41->dev, + "Failed to read firmware status: %d\n", ret); + goto clean_dsp; + } + + switch (fw_status) { + case CSPL_MBOX_STS_RUNNING: + case CSPL_MBOX_STS_PAUSED: + break; + default: + dev_err(cs35l41->dev, "Firmware status is invalid: %u\n", + fw_status); + ret = -EINVAL; + goto clean_dsp; + } + ret = cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); if (ret) { dev_err(cs35l41->dev, "Error waiting for DSP to pause: %u\n", ret); @@ -993,6 +1083,15 @@ static int cs35l41_fw_load_ctl_get(struct snd_kcontrol *kcontrol, return 0; } +static int cs35l41_mute_override_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = cs35l41->mute_override; + return 0; +} + static void cs35l41_fw_load_work(struct work_struct *work) { struct cs35l41_hda *cs35l41 = container_of(work, struct cs35l41_hda, fw_load_work); @@ -1076,6 +1175,7 @@ static int cs35l41_create_controls(struct cs35l41_hda *cs35l41) { char fw_type_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; char fw_load_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char mute_override_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; struct snd_kcontrol_new fw_type_ctl = { .name = fw_type_ctl_name, .iface = SNDRV_CTL_ELEM_IFACE_CARD, @@ -1090,12 +1190,21 @@ static int cs35l41_create_controls(struct cs35l41_hda *cs35l41) .get = cs35l41_fw_load_ctl_get, .put = cs35l41_fw_load_ctl_put, }; + struct snd_kcontrol_new mute_override_ctl = { + .name = mute_override_ctl_name, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = snd_ctl_boolean_mono_info, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .get = cs35l41_mute_override_ctl_get, + }; int ret; scnprintf(fw_type_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s DSP1 Firmware Type", cs35l41->amp_name); scnprintf(fw_load_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s DSP1 Firmware Load", cs35l41->amp_name); + scnprintf(mute_override_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s Forced Mute Status", + cs35l41->amp_name); ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_type_ctl, cs35l41)); if (ret) { @@ -1113,9 +1222,65 @@ static int cs35l41_create_controls(struct cs35l41_hda *cs35l41) dev_dbg(cs35l41->dev, "Added Control %s\n", fw_load_ctl.name); + ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&mute_override_ctl, cs35l41)); + if (ret) { + dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", mute_override_ctl.name, + ret); + return ret; + } + + dev_dbg(cs35l41->dev, "Added Control %s\n", mute_override_ctl.name); + return 0; } +static bool cs35l41_dsm_supported(acpi_handle handle, unsigned int commands) +{ + guid_t guid; + + guid_parse(CS35L41_UUID, &guid); + + return acpi_check_dsm(handle, &guid, 0, BIT(commands)); +} + +static int cs35l41_get_acpi_mute_state(struct cs35l41_hda *cs35l41, acpi_handle handle) +{ + guid_t guid; + union acpi_object *ret; + int mute = -ENODEV; + + guid_parse(CS35L41_UUID, &guid); + + if (cs35l41_dsm_supported(handle, CS35L41_DSM_GET_MUTE)) { + ret = acpi_evaluate_dsm(handle, &guid, 0, CS35L41_DSM_GET_MUTE, NULL); + mute = *ret->buffer.pointer; + dev_dbg(cs35l41->dev, "CS35L41_DSM_GET_MUTE: %d\n", mute); + } + + dev_dbg(cs35l41->dev, "%s: %d\n", __func__, mute); + + return mute; +} + +static void cs35l41_acpi_device_notify(acpi_handle handle, u32 event, struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + int mute; + + if (event != CS35L41_NOTIFY_EVENT) + return; + + mute = cs35l41_get_acpi_mute_state(cs35l41, handle); + if (mute < 0) { + dev_warn(cs35l41->dev, "Unable to retrieve mute state: %d\n", mute); + return; + } + + dev_dbg(cs35l41->dev, "Requesting mute value: %d\n", mute); + cs35l41->mute_override = (mute > 0); + cs35l41_mute(cs35l41->dev, cs35l41->mute_override); +} + static int cs35l41_hda_bind(struct device *dev, struct device *master, void *master_data) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); @@ -1157,6 +1322,14 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas comps->playback_hook = cs35l41_hda_playback_hook; comps->pre_playback_hook = cs35l41_hda_pre_playback_hook; comps->post_playback_hook = cs35l41_hda_post_playback_hook; + comps->acpi_notify = cs35l41_acpi_device_notify; + comps->adev = cs35l41->dacpi; + + comps->acpi_notifications_supported = cs35l41_dsm_supported(acpi_device_handle(comps->adev), + CS35L41_DSM_GET_MUTE); + + cs35l41->mute_override = cs35l41_get_acpi_mute_state(cs35l41, + acpi_device_handle(cs35l41->dacpi)) > 0; mutex_unlock(&cs35l41->fw_mutex); @@ -1430,8 +1603,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i return -ENODEV; } + cs35l41->dacpi = adev; physdev = get_device(acpi_get_first_physical_node(adev)); - acpi_dev_put(adev); sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR(sub)) @@ -1541,6 +1714,7 @@ err: hw_cfg->valid = false; hw_cfg->gpio1.valid = false; hw_cfg->gpio2.valid = false; + acpi_dev_put(cs35l41->dacpi); put_physdev: put_device(physdev); @@ -1550,7 +1724,7 @@ put_physdev: int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, struct regmap *regmap) { - unsigned int int_sts, regid, reg_revid, mtl_revid, chipid, int_status; + unsigned int regid, reg_revid; struct cs35l41_hda *cs35l41; int ret; @@ -1584,47 +1758,22 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i } } if (cs35l41->reset_gpio) { + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); usleep_range(2000, 2100); gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); } usleep_range(2000, 2100); + regmap_write(cs35l41->regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); + usleep_range(2000, 2100); - ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, int_status, - int_status & CS35L41_OTP_BOOT_DONE, 1000, 100000); - if (ret) { - dev_err(cs35l41->dev, "Failed waiting for OTP_BOOT_DONE: %d\n", ret); - goto err; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_sts); - if (ret || (int_sts & CS35L41_OTP_BOOT_ERR)) { - dev_err(cs35l41->dev, "OTP Boot status %x error: %d\n", - int_sts & CS35L41_OTP_BOOT_ERR, ret); - ret = -EIO; - goto err; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); - if (ret) { - dev_err(cs35l41->dev, "Get Device ID failed: %d\n", ret); - goto err; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid); - if (ret) { - dev_err(cs35l41->dev, "Get Revision ID failed: %d\n", ret); + ret = cs35l41_wait_boot_done(cs35l41); + if (ret) goto err; - } - mtl_revid = reg_revid & CS35L41_MTLREVID_MASK; - - chipid = (mtl_revid % 2) ? CS35L41R_CHIP_ID : CS35L41_CHIP_ID; - if (regid != chipid) { - dev_err(cs35l41->dev, "CS35L41 Device ID (%X). Expected ID %X\n", regid, chipid); - ret = -ENODEV; + ret = cs35l41_verify_id(cs35l41, ®id, ®_revid); + if (ret) goto err; - } ret = cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); if (ret) @@ -1636,7 +1785,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap); if (ret) { - dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret); + dev_err_probe(cs35l41->dev, ret, "OTP Unpack failed\n"); goto err; } @@ -1644,10 +1793,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i if (ret) goto err; - ret = regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute, - ARRAY_SIZE(cs35l41_hda_mute)); - if (ret) - goto err; + cs35l41_mute(cs35l41->dev, true); INIT_WORK(&cs35l41->fw_load_work, cs35l41_fw_load_work); mutex_init(&cs35l41->fw_mutex); @@ -1667,9 +1813,8 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops); if (ret) { - dev_err(cs35l41->dev, "Register component failed: %d\n", ret); - pm_runtime_disable(cs35l41->dev); - goto err; + dev_err_probe(cs35l41->dev, ret, "Register component failed\n"); + goto err_pm; } dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", regid, reg_revid); @@ -1677,6 +1822,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i return 0; err_pm: + pm_runtime_dont_use_autosuspend(cs35l41->dev); pm_runtime_disable(cs35l41->dev); pm_runtime_put_noidle(cs35l41->dev); @@ -1684,6 +1830,7 @@ err: if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); gpiod_put(cs35l41->reset_gpio); + acpi_dev_put(cs35l41->dacpi); kfree(cs35l41->acpi_subsystem_id); return ret; @@ -1695,6 +1842,7 @@ void cs35l41_hda_remove(struct device *dev) struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); pm_runtime_get_sync(cs35l41->dev); + pm_runtime_dont_use_autosuspend(cs35l41->dev); pm_runtime_disable(cs35l41->dev); if (cs35l41->halo_initialized) @@ -1702,6 +1850,8 @@ void cs35l41_hda_remove(struct device *dev) component_del(cs35l41->dev, &cs35l41_hda_comp_ops); + acpi_dev_put(cs35l41->dacpi); + pm_runtime_put_noidle(cs35l41->dev); if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index b93bf762976e..ce3f2bb6ffd0 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -10,6 +10,7 @@ #ifndef __CS35L41_HDA_H__ #define __CS35L41_HDA_H__ +#include <linux/acpi.h> #include <linux/efi.h> #include <linux/regulator/consumer.h> #include <linux/gpio/consumer.h> @@ -70,6 +71,8 @@ struct cs35l41_hda { bool halo_initialized; bool playback_started; struct cs_dsp cs_dsp; + struct acpi_device *dacpi; + bool mute_override; }; enum halo_state { diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c index b62a4e6968e2..c83328971728 100644 --- a/sound/pci/hda/cs35l41_hda_property.c +++ b/sound/pci/hda/cs35l41_hda_property.c @@ -58,9 +58,16 @@ static int hp_vision_acpi_fix(struct cs35l41_hda *cs35l41, struct device *physde cs35l41->index = id; cs35l41->channel_index = 0; - cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 1, GPIOD_OUT_HIGH); + + /* + * This system has _DSD, it just contains an error, so we can still get the reset using + * the "reset" label. + */ + cs35l41->reset_gpio = fwnode_gpiod_get_index(acpi_fwnode_handle(cs35l41->dacpi), "reset", + cs35l41->index, GPIOD_OUT_LOW, + "cs35l41-reset"); cs35l41->speaker_id = -ENOENT; - hw_cfg->spk_pos = cs35l41->index ? 1 : 0; // right:left + hw_cfg->spk_pos = cs35l41->index ? 0 : 1; // right:left hw_cfg->gpio1.func = CS35L41_NOT_USED; hw_cfg->gpio1.valid = true; hw_cfg->gpio2.func = CS35L41_INTERRUPT; diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c index 7adc1d373d65..b61e1de8c4bf 100644 --- a/sound/pci/hda/cs35l56_hda.c +++ b/sound/pci/hda/cs35l56_hda.c @@ -16,6 +16,7 @@ #include <sound/core.h> #include <sound/hda_codec.h> #include <sound/tlv.h> +#include "cirrus_scodec.h" #include "cs35l56_hda.h" #include "hda_component.h" #include "hda_cs_dsp_ctl.h" @@ -869,7 +870,17 @@ static int cs35l56_hda_read_acpi(struct cs35l56_hda *cs35l56, int id) "Read ACPI _SUB failed(%ld): fallback to generic firmware\n", PTR_ERR(sub)); } else { - cs35l56->system_name = sub; + ret = cirrus_scodec_get_speaker_id(cs35l56->base.dev, cs35l56->index, nval, -1); + if (ret == -ENOENT) { + cs35l56->system_name = sub; + } else if (ret >= 0) { + cs35l56->system_name = kasprintf(GFP_KERNEL, "%s-spkid%d", sub, ret); + kfree(sub); + if (!cs35l56->system_name) + return -ENOMEM; + } else { + return ret; + } } cs35l56->base.reset_gpio = devm_gpiod_get_index_optional(cs35l56->base.dev, @@ -1024,7 +1035,18 @@ const struct dev_pm_ops cs35l56_hda_pm_ops = { }; EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56); +#if IS_ENABLED(CONFIG_SND_HDA_SCODEC_CS35L56_KUNIT_TEST) +/* Hooks to export static function to KUnit test */ + +int cs35l56_hda_test_hook_get_speaker_id(struct device *dev, int amp_index, int num_amps) +{ + return cs35l56_hda_get_speaker_id(dev, amp_index, num_amps); +} +EXPORT_SYMBOL_NS_GPL(cs35l56_hda_test_hook_get_speaker_id, SND_HDA_SCODEC_CS35L56); +#endif + MODULE_DESCRIPTION("CS35L56 HDA Driver"); +MODULE_IMPORT_NS(SND_HDA_CIRRUS_SCODEC); MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS); MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED); MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 33af707a65ab..01718b1fc9a7 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -88,7 +88,7 @@ struct hda_conn_list { struct list_head list; int len; hda_nid_t nid; - hda_nid_t conns[]; + hda_nid_t conns[] __counted_by(len); }; /* look up the cached results */ diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h index f170aec967c1..bbd6f0ed16c1 100644 --- a/sound/pci/hda/hda_component.h +++ b/sound/pci/hda/hda_component.h @@ -6,6 +6,7 @@ * Cirrus Logic International Semiconductor Ltd. */ +#include <linux/acpi.h> #include <linux/component.h> #define HDA_MAX_COMPONENTS 4 @@ -15,6 +16,9 @@ struct hda_component { struct device *dev; char name[HDA_MAX_NAME_SIZE]; struct hda_codec *codec; + struct acpi_device *adev; + bool acpi_notifications_supported; + void (*acpi_notify)(acpi_handle handle, u32 event, struct device *dev); void (*pre_playback_hook)(struct device *dev, int action); void (*playback_hook)(struct device *dev, int action); void (*post_playback_hook)(struct device *dev, int action); diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 406779625fb5..c42e9ffff9db 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -182,7 +182,7 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) if (err < 0) goto unlock; - snd_hdac_stream_setup(azx_stream(azx_dev)); + snd_hdac_stream_setup(azx_stream(azx_dev), false); stream_tag = azx_dev->core.stream_tag; /* CA-IBG chips need the playback stream starting from 1 */ diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 508c39bf4610..3c9e6e97ad0f 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -806,7 +806,7 @@ static unsigned int azx_via_get_position(struct azx *chip, mod_dma_pos = le32_to_cpu(*azx_dev->core.posbuf); mod_dma_pos %= azx_dev->core.period_bytes; - fifo_size = azx_stream(azx_dev)->fifo_size - 1; + fifo_size = azx_stream(azx_dev)->fifo_size; if (azx_dev->insufficient) { /* Link position never gather than FIFO size */ @@ -2129,6 +2129,36 @@ static int azx_probe(struct pci_dev *pci, pci_set_drvdata(pci, card); +#ifdef CONFIG_SND_HDA_I915 + /* bind with i915 if needed */ + if (chip->driver_caps & AZX_DCAPS_I915_COMPONENT) { + err = snd_hdac_i915_init(azx_bus(chip)); + if (err < 0) { + /* if the controller is bound only with HDMI/DP + * (for HSW and BDW), we need to abort the probe; + * for other chips, still continue probing as other + * codecs can be on the same link. + */ + if (HDA_CONTROLLER_IN_GPU(pci)) { + dev_err_probe(card->dev, err, + "HSW/BDW HD-audio HDMI/DP requires binding with gfx driver\n"); + + goto out_free; + } else { + /* don't bother any longer */ + chip->driver_caps &= ~AZX_DCAPS_I915_COMPONENT; + } + } + + /* HSW/BDW controllers need this power */ + if (HDA_CONTROLLER_IN_GPU(pci)) + hda->need_i915_power = true; + } +#else + if (HDA_CONTROLLER_IN_GPU(pci)) + dev_err(card->dev, "Haswell/Broadwell HDMI/DP must build in CONFIG_SND_HDA_I915\n"); +#endif + err = register_vga_switcheroo(chip); if (err < 0) { dev_err(card->dev, "Error registering vga_switcheroo client\n"); @@ -2156,11 +2186,6 @@ static int azx_probe(struct pci_dev *pci, } #endif /* CONFIG_SND_HDA_PATCH_LOADER */ -#ifndef CONFIG_SND_HDA_I915 - if (HDA_CONTROLLER_IN_GPU(pci)) - dev_err(card->dev, "Haswell/Broadwell HDMI/DP must build in CONFIG_SND_HDA_I915\n"); -#endif - if (schedule_probe) schedule_delayed_work(&hda->probe_work, 0); @@ -2170,6 +2195,7 @@ static int azx_probe(struct pci_dev *pci, return 0; out_free: + pci_set_drvdata(pci, NULL); snd_card_free(card); return err; } @@ -2257,30 +2283,6 @@ static int azx_probe_continue(struct azx *chip) to_hda_bus(bus)->bus_probing = 1; hda->probe_continued = 1; - /* bind with i915 if needed */ - if (chip->driver_caps & AZX_DCAPS_I915_COMPONENT) { - err = snd_hdac_i915_init(bus); - if (err < 0) { - /* if the controller is bound only with HDMI/DP - * (for HSW and BDW), we need to abort the probe; - * for other chips, still continue probing as other - * codecs can be on the same link. - */ - if (HDA_CONTROLLER_IN_GPU(pci)) { - dev_err(chip->card->dev, - "HSW/BDW HD-audio HDMI/DP requires binding with gfx driver\n"); - goto out_free; - } else { - /* don't bother any longer */ - chip->driver_caps &= ~AZX_DCAPS_I915_COMPONENT; - } - } - - /* HSW/BDW controllers need this power */ - if (HDA_CONTROLLER_IN_GPU(pci)) - hda->need_i915_power = true; - } - /* Request display power well for the HDA controller or codec. For * Haswell/Broadwell, both the display HDA controller and codec need * this power. For other platforms, like Baytrail/Braswell, only the diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 9677c09cf7a9..58006c8bcfb9 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -10,6 +10,7 @@ * Jonathan Woithe <jwoithe@just42.net> */ +#include <linux/acpi.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/slab.h> @@ -6704,12 +6705,91 @@ static void alc287_fixup_legion_15imhg05_speakers(struct hda_codec *codec, } } +#ifdef CONFIG_ACPI +static void comp_acpi_device_notify(acpi_handle handle, u32 event, void *data) +{ + struct hda_codec *cdc = data; + struct alc_spec *spec = cdc->spec; + int i; + + codec_info(cdc, "ACPI Notification %d\n", event); + + for (i = 0; i < HDA_MAX_COMPONENTS; i++) { + if (spec->comps[i].dev && spec->comps[i].acpi_notify) + spec->comps[i].acpi_notify(acpi_device_handle(spec->comps[i].adev), event, + spec->comps[i].dev); + } +} + +static int comp_bind_acpi(struct device *dev) +{ + struct hda_codec *cdc = dev_to_hda_codec(dev); + struct alc_spec *spec = cdc->spec; + bool support_notifications = false; + struct acpi_device *adev; + int ret; + int i; + + adev = spec->comps[0].adev; + if (!acpi_device_handle(adev)) + return 0; + + for (i = 0; i < HDA_MAX_COMPONENTS; i++) + support_notifications = support_notifications || + spec->comps[i].acpi_notifications_supported; + + if (support_notifications) { + ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + comp_acpi_device_notify, cdc); + if (ret < 0) { + codec_warn(cdc, "Failed to install notify handler: %d\n", ret); + return 0; + } + + codec_dbg(cdc, "Notify handler installed\n"); + } + + return 0; +} + +static void comp_unbind_acpi(struct device *dev) +{ + struct hda_codec *cdc = dev_to_hda_codec(dev); + struct alc_spec *spec = cdc->spec; + struct acpi_device *adev; + int ret; + + adev = spec->comps[0].adev; + if (!acpi_device_handle(adev)) + return; + + ret = acpi_remove_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + comp_acpi_device_notify); + if (ret < 0) + codec_warn(cdc, "Failed to uninstall notify handler: %d\n", ret); +} +#else +static int comp_bind_acpi(struct device *dev) +{ + return 0; +} + +static void comp_unbind_acpi(struct device *dev) +{ +} +#endif + static int comp_bind(struct device *dev) { struct hda_codec *cdc = dev_to_hda_codec(dev); struct alc_spec *spec = cdc->spec; + int ret; + + ret = component_bind_all(dev, spec->comps); + if (ret) + return ret; - return component_bind_all(dev, spec->comps); + return comp_bind_acpi(dev); } static void comp_unbind(struct device *dev) @@ -6717,6 +6797,7 @@ static void comp_unbind(struct device *dev) struct hda_codec *cdc = dev_to_hda_codec(dev); struct alc_spec *spec = cdc->spec; + comp_unbind_acpi(dev); component_unbind_all(dev, spec->comps); } |