When SPI is used for control, the driver must hold the SPI bus lock
while issuing the sequence of writes to perform a soft reset.
>From the time the driver writes the SYSTEM_RESET command until the
driver does a write to terminate the reset, there must not be any
activity on the SPI bus lines. If there is any SPI activity during the
soft-reset, another soft-reset will be triggered. The state of the SPI
chip select is irrelevant.
A repeated soft-reset does not in itself cause any problems, and it is
not an infinite loop. The problem is a race between these resets and
the driver polling for boot completion. There is a time window between
soft resets where the driver could read HALO_STATE as 2 (fully booted)
while the chip is actually soft-resetting. Although this window is
small, it is long enough that it is possible to hit it in normal
operation.
To prevent this race and ensure the chip really is fully booted, the
driver calls spi_bus_lock() to prevent other activity while resetting.
It then issues the SYSTEM_RESET mailbox command. After allowing
sufficient time for reset to take effect, the driver issues a PING
mailbox command, which will force completion of the full soft-reset
sequence. The SPI bus lock can then be released. The mailbox is
checked for any boot or wakeup response from the firmware, before the
value in HALO_STATE will be trusted.
This does not affect SoundWire or I2C control.
Fixes: 8a731fd37f
("ASoC: cs35l56: Move utility functions to shared file")
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Link: https://patch.msgid.link/20250225131843.113752-3-rf@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
87 lines
2.2 KiB
C
87 lines
2.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
//
|
|
// CS35L56 HDA audio driver SPI binding
|
|
//
|
|
// Copyright (C) 2023 Cirrus Logic, Inc. and
|
|
// Cirrus Logic International Semiconductor Ltd.
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#include "cs35l56_hda.h"
|
|
|
|
static int cs35l56_hda_spi_probe(struct spi_device *spi)
|
|
{
|
|
const struct spi_device_id *id = spi_get_device_id(spi);
|
|
struct cs35l56_hda *cs35l56;
|
|
int ret;
|
|
|
|
cs35l56 = devm_kzalloc(&spi->dev, sizeof(*cs35l56), GFP_KERNEL);
|
|
if (!cs35l56)
|
|
return -ENOMEM;
|
|
|
|
cs35l56->base.dev = &spi->dev;
|
|
ret = cs35l56_init_config_for_spi(&cs35l56->base, spi);
|
|
if (ret)
|
|
return ret;
|
|
|
|
#ifdef CS35L56_WAKE_HOLD_TIME_US
|
|
cs35l56->base.can_hibernate = true;
|
|
#endif
|
|
cs35l56->base.regmap = devm_regmap_init_spi(spi, &cs35l56_regmap_spi);
|
|
if (IS_ERR(cs35l56->base.regmap)) {
|
|
ret = PTR_ERR(cs35l56->base.regmap);
|
|
dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = cs35l56_hda_common_probe(cs35l56, id->driver_data, spi_get_chipselect(spi, 0));
|
|
if (ret)
|
|
return ret;
|
|
ret = cs35l56_irq_request(&cs35l56->base, spi->irq);
|
|
if (ret < 0)
|
|
cs35l56_hda_remove(cs35l56->base.dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cs35l56_hda_spi_remove(struct spi_device *spi)
|
|
{
|
|
cs35l56_hda_remove(&spi->dev);
|
|
}
|
|
|
|
static const struct spi_device_id cs35l56_hda_spi_id[] = {
|
|
{ "cs35l54-hda", 0x3554 },
|
|
{ "cs35l56-hda", 0x3556 },
|
|
{ "cs35l57-hda", 0x3557 },
|
|
{}
|
|
};
|
|
|
|
static const struct acpi_device_id cs35l56_acpi_hda_match[] = {
|
|
{ "CSC3554", 0 },
|
|
{ "CSC3556", 0 },
|
|
{ "CSC3557", 0 },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, cs35l56_acpi_hda_match);
|
|
|
|
static struct spi_driver cs35l56_hda_spi_driver = {
|
|
.driver = {
|
|
.name = "cs35l56-hda",
|
|
.acpi_match_table = cs35l56_acpi_hda_match,
|
|
.pm = &cs35l56_hda_pm_ops,
|
|
},
|
|
.id_table = cs35l56_hda_spi_id,
|
|
.probe = cs35l56_hda_spi_probe,
|
|
.remove = cs35l56_hda_spi_remove,
|
|
};
|
|
module_spi_driver(cs35l56_hda_spi_driver);
|
|
|
|
MODULE_DESCRIPTION("HDA CS35L56 SPI driver");
|
|
MODULE_IMPORT_NS("SND_HDA_SCODEC_CS35L56");
|
|
MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED");
|
|
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
|
|
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
|
|
MODULE_LICENSE("GPL");
|