Skip to content

Commit 769c1b7

Browse files
rfvirgilbroonie
authored andcommitted
ASoC: cs35l56: Prevent races when soft-resetting using SPI control
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: 8a731fd ("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>
1 parent fe08b7d commit 769c1b7

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

include/sound/cs35l56.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <linux/firmware/cirrus/cs_dsp.h>
1313
#include <linux/regulator/consumer.h>
1414
#include <linux/regmap.h>
15+
#include <linux/spi/spi.h>
1516
#include <sound/cs-amp-lib.h>
1617

1718
#define CS35L56_DEVID 0x0000000
@@ -61,6 +62,7 @@
6162
#define CS35L56_IRQ1_MASK_8 0x000E0AC
6263
#define CS35L56_IRQ1_MASK_18 0x000E0D4
6364
#define CS35L56_IRQ1_MASK_20 0x000E0DC
65+
#define CS35L56_DSP_MBOX_1_RAW 0x0011000
6466
#define CS35L56_DSP_VIRTUAL1_MBOX_1 0x0011020
6567
#define CS35L56_DSP_VIRTUAL1_MBOX_2 0x0011024
6668
#define CS35L56_DSP_VIRTUAL1_MBOX_3 0x0011028
@@ -224,6 +226,7 @@
224226
#define CS35L56_HALO_STATE_SHUTDOWN 1
225227
#define CS35L56_HALO_STATE_BOOT_DONE 2
226228

229+
#define CS35L56_MBOX_CMD_PING 0x0A000000
227230
#define CS35L56_MBOX_CMD_AUDIO_PLAY 0x0B000001
228231
#define CS35L56_MBOX_CMD_AUDIO_PAUSE 0x0B000002
229232
#define CS35L56_MBOX_CMD_AUDIO_REINIT 0x0B000003
@@ -254,6 +257,16 @@
254257
#define CS35L56_NUM_BULK_SUPPLIES 3
255258
#define CS35L56_NUM_DSP_REGIONS 5
256259

260+
/* Additional margin for SYSTEM_RESET to control port ready on SPI */
261+
#define CS35L56_SPI_RESET_TO_PORT_READY_US (CS35L56_CONTROL_PORT_READY_US + 2500)
262+
263+
struct cs35l56_spi_payload {
264+
__be32 addr;
265+
__be16 pad;
266+
__be32 value;
267+
} __packed;
268+
static_assert(sizeof(struct cs35l56_spi_payload) == 10);
269+
257270
struct cs35l56_base {
258271
struct device *dev;
259272
struct regmap *regmap;
@@ -269,13 +282,31 @@ struct cs35l56_base {
269282
s8 cal_index;
270283
struct cirrus_amp_cal_data cal_data;
271284
struct gpio_desc *reset_gpio;
285+
struct cs35l56_spi_payload *spi_payload_buf;
272286
};
273287

274288
static inline bool cs35l56_is_otp_register(unsigned int reg)
275289
{
276290
return (reg >> 16) == 3;
277291
}
278292

293+
static inline int cs35l56_init_config_for_spi(struct cs35l56_base *cs35l56,
294+
struct spi_device *spi)
295+
{
296+
cs35l56->spi_payload_buf = devm_kzalloc(&spi->dev,
297+
sizeof(*cs35l56->spi_payload_buf),
298+
GFP_KERNEL | GFP_DMA);
299+
if (!cs35l56->spi_payload_buf)
300+
return -ENOMEM;
301+
302+
return 0;
303+
}
304+
305+
static inline bool cs35l56_is_spi(struct cs35l56_base *cs35l56)
306+
{
307+
return IS_ENABLED(CONFIG_SPI_MASTER) && !!cs35l56->spi_payload_buf;
308+
}
309+
279310
extern const struct regmap_config cs35l56_regmap_i2c;
280311
extern const struct regmap_config cs35l56_regmap_spi;
281312
extern const struct regmap_config cs35l56_regmap_sdw;

sound/pci/hda/cs35l56_hda_spi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ static int cs35l56_hda_spi_probe(struct spi_device *spi)
2222
return -ENOMEM;
2323

2424
cs35l56->base.dev = &spi->dev;
25+
ret = cs35l56_init_config_for_spi(&cs35l56->base, spi);
26+
if (ret)
27+
return ret;
2528

2629
#ifdef CS35L56_WAKE_HOLD_TIME_US
2730
cs35l56->base.can_hibernate = true;

sound/soc/codecs/cs35l56-shared.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <linux/gpio/consumer.h>
1111
#include <linux/regmap.h>
1212
#include <linux/regulator/consumer.h>
13+
#include <linux/spi/spi.h>
1314
#include <linux/types.h>
1415
#include <sound/cs-amp-lib.h>
1516

@@ -303,6 +304,79 @@ void cs35l56_wait_min_reset_pulse(void)
303304
}
304305
EXPORT_SYMBOL_NS_GPL(cs35l56_wait_min_reset_pulse, "SND_SOC_CS35L56_SHARED");
305306

307+
static const struct {
308+
u32 addr;
309+
u32 value;
310+
} cs35l56_spi_system_reset_stages[] = {
311+
{ .addr = CS35L56_DSP_VIRTUAL1_MBOX_1, .value = CS35L56_MBOX_CMD_SYSTEM_RESET },
312+
/* The next write is necessary to delimit the soft reset */
313+
{ .addr = CS35L56_DSP_MBOX_1_RAW, .value = CS35L56_MBOX_CMD_PING },
314+
};
315+
316+
static void cs35l56_spi_issue_bus_locked_reset(struct cs35l56_base *cs35l56_base,
317+
struct spi_device *spi)
318+
{
319+
struct cs35l56_spi_payload *buf = cs35l56_base->spi_payload_buf;
320+
struct spi_transfer t = {
321+
.tx_buf = buf,
322+
.len = sizeof(*buf),
323+
};
324+
struct spi_message m;
325+
int i, ret;
326+
327+
for (i = 0; i < ARRAY_SIZE(cs35l56_spi_system_reset_stages); i++) {
328+
buf->addr = cpu_to_be32(cs35l56_spi_system_reset_stages[i].addr);
329+
buf->value = cpu_to_be32(cs35l56_spi_system_reset_stages[i].value);
330+
spi_message_init_with_transfers(&m, &t, 1);
331+
ret = spi_sync_locked(spi, &m);
332+
if (ret)
333+
dev_warn(cs35l56_base->dev, "spi_sync failed: %d\n", ret);
334+
335+
usleep_range(CS35L56_SPI_RESET_TO_PORT_READY_US,
336+
2 * CS35L56_SPI_RESET_TO_PORT_READY_US);
337+
}
338+
}
339+
340+
static void cs35l56_spi_system_reset(struct cs35l56_base *cs35l56_base)
341+
{
342+
struct spi_device *spi = to_spi_device(cs35l56_base->dev);
343+
unsigned int val;
344+
int read_ret, ret;
345+
346+
/*
347+
* There must not be any other SPI bus activity while the amp is
348+
* soft-resetting.
349+
*/
350+
ret = spi_bus_lock(spi->controller);
351+
if (ret) {
352+
dev_warn(cs35l56_base->dev, "spi_bus_lock failed: %d\n", ret);
353+
return;
354+
}
355+
356+
cs35l56_spi_issue_bus_locked_reset(cs35l56_base, spi);
357+
spi_bus_unlock(spi->controller);
358+
359+
/*
360+
* Check firmware boot by testing for a response in MBOX_2.
361+
* HALO_STATE cannot be trusted yet because the reset sequence
362+
* can leave it with stale state. But MBOX is reset.
363+
* The regmap must remain in cache-only until the chip has
364+
* booted, so use a bypassed read.
365+
*/
366+
ret = read_poll_timeout(regmap_read_bypassed, read_ret,
367+
(val > 0) && (val < 0xffffffff),
368+
CS35L56_HALO_STATE_POLL_US,
369+
CS35L56_HALO_STATE_TIMEOUT_US,
370+
false,
371+
cs35l56_base->regmap,
372+
CS35L56_DSP_VIRTUAL1_MBOX_2,
373+
&val);
374+
if (ret) {
375+
dev_err(cs35l56_base->dev, "SPI reboot timed out(%d): MBOX2=%#x\n",
376+
read_ret, val);
377+
}
378+
}
379+
306380
static const struct reg_sequence cs35l56_system_reset_seq[] = {
307381
REG_SEQ0(CS35L56_DSP1_HALO_STATE, 0),
308382
REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_SYSTEM_RESET),
@@ -315,6 +389,12 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire)
315389
* accesses other than the controlled system reset sequence below.
316390
*/
317391
regcache_cache_only(cs35l56_base->regmap, true);
392+
393+
if (cs35l56_is_spi(cs35l56_base)) {
394+
cs35l56_spi_system_reset(cs35l56_base);
395+
return;
396+
}
397+
318398
regmap_multi_reg_write_bypassed(cs35l56_base->regmap,
319399
cs35l56_system_reset_seq,
320400
ARRAY_SIZE(cs35l56_system_reset_seq));

sound/soc/codecs/cs35l56-spi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ static int cs35l56_spi_probe(struct spi_device *spi)
3333

3434
cs35l56->base.dev = &spi->dev;
3535
cs35l56->base.can_hibernate = true;
36+
ret = cs35l56_init_config_for_spi(&cs35l56->base, spi);
37+
if (ret)
38+
return ret;
3639

3740
ret = cs35l56_common_probe(cs35l56);
3841
if (ret != 0)

0 commit comments

Comments
 (0)