diff options
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 | 315 | ||||
-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, 865 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..98a5123e9f50 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,23 @@ 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); + 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 +1082,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 +1174,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 +1189,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 +1221,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 +1321,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 +1602,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 +1713,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 +1723,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 +1757,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); + |