[alsa-devel] [PATCH 00/18] soundwire: code hardening and suspend-resume support
this patchset applies on top of "[PATCH 00/14] soundwire: intel: implement new ASoC interfaces". It implements a series of improvements for: a) interrupt handling on Intel platforms in MSI mode b) race conditions on codec probe and enumeration c) suspend-resume issues (clock-stop mode not supported for now) d) underflow handling e) updates to the stream state machine which did not support valid ALSA transitions.
These patches were tested extensively on 4 different platforms and are viewed as required for any sort of SoundWire-based product.
Bard Liao (3): soundwire: intel/cadence: fix timeouts in MSI mode soundwire: stream: only prepare stream when it is configured. soundwire: intel: reinitialize IP+DSP in .prepare()
Pierre-Louis Bossart (15): soundwire: fix race between driver probe and update_status callback soundwire: bus: add PM/no-PM versions of read/write functions soundwire: bus: write Slave Device Number without runtime_pm soundwire: intel: add helpers for link power down and shim wake soundwire: intel: Add basic power management support soundwire: intel: add pm_runtime support soundwire: intel: reset pm_runtime status during system resume soundwire: bus: add helper to reset Slave status to UNATTACHED soundwire: intel: call helper to reset Slave states on resume soundwire: bus: check first if Slaves become UNATTACHED soundwire: add enumeration_complete signaling soundwire: intel: disable pm_runtime when removing a master soundwire: bus: disable pm_runtime in sdw_slave_delete soundwire: stream: update state machine and add state checks soundwire: stream: do not update parameters during DISABLED-PREPARED transition
Documentation/driver-api/soundwire/stream.rst | 63 +++- drivers/soundwire/bus.c | 157 +++++++-- drivers/soundwire/bus.h | 3 + drivers/soundwire/bus_type.c | 5 + drivers/soundwire/cadence_master.c | 17 +- drivers/soundwire/cadence_master.h | 4 + drivers/soundwire/intel.c | 328 ++++++++++++++++-- drivers/soundwire/intel.h | 2 + drivers/soundwire/intel_init.c | 45 ++- drivers/soundwire/slave.c | 3 + drivers/soundwire/stream.c | 64 +++- include/linux/soundwire/sdw.h | 1 + include/linux/soundwire/sdw_intel.h | 2 + 13 files changed, 623 insertions(+), 71 deletions(-)
From: Bard Liao yung-chuan.liao@linux.intel.com
The existing code uses one pair of interrupt handler/thread per link but at the hardware level the interrupt is shared. This works fine for legacy PCI interrupts, but leads to timeouts in MSI (Message-Signaled Interrupt) mode, likely due to edges being lost.
This patch unifies interrupt handling for all links with a single threaded IRQ handler. The handler is simplified to the bare minimum of detecting a SoundWire interrupt, and the thread takes care of dealing with interrupt sources. This partition follows the model used for the SOF IPC on HDaudio platforms, where similar timeout issues were noticed and doing all the interrupt handling/clearing in the thread improved reliability/stability.
Validation results with 4 links active in parallel show a night-and-day improvement with no timeouts noticed even during stress tests. Latency and quality of service are not affected by the change - mostly because events on a SoundWire link are throttled by the bus frame rate (typically 8..48kHz).
Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/cadence_master.c | 17 ++++++----- drivers/soundwire/cadence_master.h | 4 +++ drivers/soundwire/intel.c | 17 +---------- drivers/soundwire/intel.h | 2 ++ drivers/soundwire/intel_init.c | 45 ++++++++++++++++++++++++++++- include/linux/soundwire/sdw_intel.h | 2 ++ 6 files changed, 62 insertions(+), 25 deletions(-)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index fed21e2b2277..362fb6e49bfe 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -17,6 +17,7 @@ #include <linux/soundwire/sdw.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include <linux/workqueue.h> #include "bus.h" #include "cadence_master.h"
@@ -745,7 +746,7 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) CDNS_MCP_INT_SLAVE_MASK, 0);
int_status &= ~CDNS_MCP_INT_SLAVE_MASK; - ret = IRQ_WAKE_THREAD; + schedule_work(&cdns->work); }
cdns_writel(cdns, CDNS_MCP_INTSTAT, int_status); @@ -754,13 +755,14 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) EXPORT_SYMBOL(sdw_cdns_irq);
/** - * sdw_cdns_thread() - Cadence irq thread handler - * @irq: irq number - * @dev_id: irq context + * To update slave status in a work since we will need to handle + * other interrupts eg. CDNS_MCP_INT_RX_WL during the update slave + * process. */ -irqreturn_t sdw_cdns_thread(int irq, void *dev_id) +static void cdns_update_slave_status_work(struct work_struct *work) { - struct sdw_cdns *cdns = dev_id; + struct sdw_cdns *cdns = + container_of(work, struct sdw_cdns, work); u32 slave0, slave1;
dev_dbg_ratelimited(cdns->dev, "Slave status change\n"); @@ -777,9 +779,7 @@ irqreturn_t sdw_cdns_thread(int irq, void *dev_id) cdns_updatel(cdns, CDNS_MCP_INTMASK, CDNS_MCP_INT_SLAVE_MASK, CDNS_MCP_INT_SLAVE_MASK);
- return IRQ_HANDLED; } -EXPORT_SYMBOL(sdw_cdns_thread);
/* * init routines @@ -1187,6 +1187,7 @@ int sdw_cdns_probe(struct sdw_cdns *cdns) init_completion(&cdns->tx_complete); cdns->bus.port_ops = &cdns_port_ops;
+ INIT_WORK(&cdns->work, cdns_update_slave_status_work); return 0; } EXPORT_SYMBOL(sdw_cdns_probe); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 001457cbe5ad..0f108fd31fd9 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -126,6 +126,10 @@ struct sdw_cdns {
bool link_up; unsigned int msg_count; + + struct work_struct work; + + struct list_head list; };
#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus) diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index cad1c0b64ee3..c918a26d0772 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -1095,6 +1095,7 @@ static int intel_master_probe(struct sdw_master_device *md, void *link_ctx) sdw->cdns.msg_count = 0; sdw->cdns.bus.dev = &md->dev; sdw->cdns.bus.link_id = md->link_id; + sdw->link_res->cdns = &sdw->cdns;
sdw_cdns_probe(&sdw->cdns);
@@ -1119,21 +1120,7 @@ static int intel_master_probe(struct sdw_master_device *md, void *link_ctx) return 0; }
- /* Acquire IRQ */ - ret = request_threaded_irq(sdw->link_res->irq, - sdw_cdns_irq, sdw_cdns_thread, - IRQF_SHARED, KBUILD_MODNAME, &sdw->cdns); - if (ret < 0) { - dev_err(sdw->cdns.dev, "unable to grab IRQ %d, disabling device\n", - sdw->link_res->irq); - goto err_init; - } - return 0; - -err_init: - sdw_delete_bus_master(&sdw->cdns.bus); - return ret; }
static int intel_master_startup(struct sdw_master_device *md) @@ -1190,7 +1177,6 @@ static int intel_master_startup(struct sdw_master_device *md) err_interrupt: sdw_cdns_enable_interrupt(&sdw->cdns, false); err_init: - free_irq(sdw->link_res->irq, sdw); sdw_delete_bus_master(&sdw->cdns.bus); return ret; } @@ -1204,7 +1190,6 @@ static int intel_master_remove(struct sdw_master_device *md) if (!sdw->cdns.bus.prop.hw_disabled) { intel_debugfs_exit(sdw); sdw_cdns_enable_interrupt(&sdw->cdns, false); - free_irq(sdw->link_res->irq, sdw); snd_soc_unregister_component(sdw->cdns.dev); } sdw_delete_bus_master(&sdw->cdns.bus); diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h index cfab2f00214d..2ae4fa748686 100644 --- a/drivers/soundwire/intel.h +++ b/drivers/soundwire/intel.h @@ -25,6 +25,8 @@ struct sdw_intel_link_res { int irq; const struct sdw_intel_ops *ops; struct device *dev; + struct sdw_cdns *cdns; + struct list_head list; };
#define SDW_INTEL_QUIRK_MASK_BUS_DISABLE BIT(1) diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c index 14ffe9ce2929..ed2a83989c72 100644 --- a/drivers/soundwire/intel_init.c +++ b/drivers/soundwire/intel_init.c @@ -11,8 +11,10 @@ #include <linux/export.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/interrupt.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_intel.h> +#include "cadence_master.h" #include "intel.h"
#define SDW_LINK_TYPE 4 /* from Intel ACPI documentation */ @@ -161,6 +163,32 @@ void sdw_intel_enable_irq(void __iomem *mmio_base, bool enable) } EXPORT_SYMBOL(sdw_intel_enable_irq);
+static irqreturn_t sdw_intel_irq(int irq, void *dev_id) +{ + struct sdw_intel_ctx *ctx = dev_id; + u32 int_status; + + int_status = readl(ctx->mmio_base + HDA_DSP_REG_ADSPIS2); + if (int_status & HDA_DSP_REG_ADSPIC2_SNDW) { + sdw_intel_enable_irq(ctx->mmio_base, false); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t sdw_intel_thread(int irq, void *dev_id) +{ + struct sdw_intel_ctx *ctx = dev_id; + struct sdw_intel_link_res *link; + + list_for_each_entry(link, &ctx->link_list, list) + sdw_cdns_irq(irq, link->cdns); + + sdw_intel_enable_irq(ctx->mmio_base, true); + return IRQ_HANDLED; +} + static struct sdw_intel_ctx *sdw_intel_probe_controller(struct sdw_intel_res *res) { @@ -171,6 +199,7 @@ static struct sdw_intel_ctx u32 link_mask; int count; int i; + int ret;
if (!res) return NULL; @@ -196,10 +225,13 @@ static struct sdw_intel_ctx ctx->mmio_base = res->mmio_base; ctx->link_mask = res->link_mask; ctx->handle = res->handle; + ctx->irq = res->irq;
link = ctx->links; link_mask = ctx->link_mask;
+ INIT_LIST_HEAD(&ctx->link_list); + /* Create SDW Master devices */ for (i = 0; i < count; i++, link++) { if (link_mask && !(link_mask & BIT(i))) @@ -220,12 +252,22 @@ static struct sdw_intel_ctx + (SDW_LINK_SIZE * i); link->shim = res->mmio_base + SDW_SHIM_BASE; link->alh = res->mmio_base + SDW_ALH_BASE; - link->irq = res->irq; link->ops = res->ops; link->dev = res->dev;
/* let the SoundWire master driver to its probe */ md->driver->probe(md, link); + + list_add_tail(&link->list, &ctx->link_list); + } + + ret = request_threaded_irq(ctx->irq, + sdw_intel_irq, sdw_intel_thread, + IRQF_SHARED, KBUILD_MODNAME, ctx); + if (ret < 0) { + dev_err(&adev->dev, "unable to grab IRQ %d, disabling device\n", + res->irq); + goto err; }
return ctx; @@ -373,6 +415,7 @@ EXPORT_SYMBOL(sdw_intel_startup); */ void sdw_intel_exit(struct sdw_intel_ctx *ctx) { + free_irq(ctx->irq, ctx); sdw_intel_cleanup(ctx); kfree(ctx); } diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h index 36e2b522a749..373947003cea 100644 --- a/include/linux/soundwire/sdw_intel.h +++ b/include/linux/soundwire/sdw_intel.h @@ -94,7 +94,9 @@ struct sdw_intel_ctx { void __iomem *mmio_base; u32 link_mask; acpi_handle handle; + int irq; struct sdw_intel_link_res *links; + struct list_head link_list; };
/*
The driver probe takes care of basic initialization and is invoked when a Slave becomes attached, after a match between the Slave DevID registers and ACPI/DT entries.
The update_status callback is invoked when a Slave state changes, e.g. when it is assigned a non-zero Device Number and it reports with an ATTACHED/ALERT state.
The state change detection is usually hardware-based and based on the SoundWire frame rate (e.g. double-digit microseconds) while the probe is a pure software operation, which may involve a kernel module load. In corner cases, it's possible that the state changes before the probe completes.
This patch suggests the use of wait_for_completion to avoid races on startup, so that the update_status callback does not rely on invalid pointers/data structures.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/bus.c | 24 +++++++++++++++++++++--- drivers/soundwire/bus.h | 1 + drivers/soundwire/bus_type.c | 5 +++++ drivers/soundwire/slave.c | 2 ++ 4 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 4b22ee996a65..903aee258800 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -961,10 +961,28 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) static int sdw_update_slave_status(struct sdw_slave *slave, enum sdw_slave_status status) { - if (slave->ops && slave->ops->update_status) - return slave->ops->update_status(slave, status); + unsigned long time;
- return 0; + if (!slave->ops || !slave->ops->update_status) + return 0; + + if (!slave->probed) { + /* + * the slave status update is typically handled in an + * interrupt thread, which can race with the driver + * probe, e.g. when a module needs to be loaded. + * + * make sure the probe is complete before updating + * status. + */ + time = wait_for_completion_timeout(&slave->probe_complete, + msecs_to_jiffies(DEFAULT_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Probe not complete, timed out\n"); + return -ETIMEDOUT; + } + } + return slave->ops->update_status(slave, status); }
/** diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index cb482da914da..acb8d11a4c84 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -5,6 +5,7 @@ #define __SDW_BUS_H
#define DEFAULT_BANK_SWITCH_TIMEOUT 3000 +#define DEFAULT_PROBE_TIMEOUT 2000
#if IS_ENABLED(CONFIG_ACPI) int sdw_acpi_find_slaves(struct sdw_bus *bus); diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index cf33f63773f0..3d52b72654c5 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -121,6 +121,11 @@ static int sdw_slave_drv_probe(struct device *dev) slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout, slave->prop.clk_stop_timeout);
+ slave->probed = true; + complete(&slave->probe_complete); + + dev_dbg(dev, "probe complete\n"); + return 0; }
diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index c87267f12a3b..81b94cd3985e 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -52,6 +52,8 @@ static int sdw_slave_add(struct sdw_bus *bus, slave->bus = bus; slave->status = SDW_SLAVE_UNATTACHED; slave->dev_num = 0; + init_completion(&slave->probe_complete); + slave->probed = false;
mutex_lock(&bus->bus_lock); list_add_tail(&slave->node, &bus->slaves);
Add support for pm_runtime with the appropriate error checks for sdw_write/read functions, e.g. when pm_runtime is not supported.
Also expose internal functions without pm_runtime support, which are required to perform any sort of suspend/resume operation, as well as any enumeration tasks.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/bus.c | 68 +++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 16 deletions(-)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 903aee258800..7f431677f152 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -317,6 +317,46 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, return 0; }
+/* + * Read/Write IO functions. + * no_pm versions can only be called by the bus, e.g. while enumerating or + * handling suspend-resume sequences. + * all clients need to use the pm versions + */ + +static int +sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_READ, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +static int +sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_WRITE, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +int sdw_write_no_pm(struct sdw_slave *slave, u32 addr, u8 value) +{ + return sdw_nwrite_no_pm(slave, addr, 1, &value); +} + /** * sdw_nread() - Read "n" contiguous SDW Slave registers * @slave: SDW Slave @@ -326,19 +366,17 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, */ int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { - struct sdw_msg msg; int ret;
- ret = sdw_fill_msg(&msg, slave, addr, count, - slave->dev_num, SDW_MSG_FLAG_READ, val); - if (ret < 0) - return ret; - ret = pm_runtime_get_sync(slave->bus->dev); - if (ret < 0) + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(slave->bus->dev); return ret; + } + + ret = sdw_nread_no_pm(slave, addr, count, val);
- ret = sdw_transfer(slave->bus, &msg); + pm_runtime_mark_last_busy(slave->bus->dev); pm_runtime_put(slave->bus->dev);
return ret; @@ -354,19 +392,17 @@ EXPORT_SYMBOL(sdw_nread); */ int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { - struct sdw_msg msg; int ret;
- ret = sdw_fill_msg(&msg, slave, addr, count, - slave->dev_num, SDW_MSG_FLAG_WRITE, val); - if (ret < 0) - return ret; - ret = pm_runtime_get_sync(slave->bus->dev); - if (ret < 0) + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(slave->bus->dev); return ret; + } + + ret = sdw_nwrite_no_pm(slave, addr, count, val);
- ret = sdw_transfer(slave->bus, &msg); + pm_runtime_mark_last_busy(slave->bus->dev); pm_runtime_put(slave->bus->dev);
return ret;
While handling the Device0, we can safely use sdw_write_no_pm.
This move will also helps us track that all other usages of sdw_write() happen when the Slave is already enumerated.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 7f431677f152..0e761efd32f9 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -513,7 +513,7 @@ static int sdw_assign_device_num(struct sdw_slave *slave) slave->dev_num = 0; }
- ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num); + ret = sdw_write_no_pm(slave, SDW_SCP_DEVNUMBER, dev_num); if (ret < 0) { dev_err(&slave->dev, "Program device_num %d failed: %d\n", dev_num, ret);
These routines are required for power management
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/intel.c | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index c918a26d0772..fe3a8b3b2c04 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -362,6 +362,59 @@ static int intel_shim_init(struct sdw_intel *sdw) return ret; }
+static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable) +{ + void __iomem *shim = sdw->link_res->shim; + unsigned int link_id = sdw->instance; + u16 wake_en, wake_sts; + + if (wake_enable) { + /* Enable the wakeup */ + intel_writew(shim, SDW_SHIM_WAKEEN, + (SDW_SHIM_WAKEEN_ENABLE << link_id)); + } else { + /* Disable the wake up interrupt */ + wake_en = intel_readw(shim, SDW_SHIM_WAKEEN); + wake_en &= ~(SDW_SHIM_WAKEEN_ENABLE << link_id); + intel_writew(shim, SDW_SHIM_WAKEEN, wake_en); + + /* Clear wake status */ + wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS); + wake_sts |= (SDW_SHIM_WAKEEN_ENABLE << link_id); + intel_writew(shim, SDW_SHIM_WAKESTS_STATUS, wake_sts); + } +} + +static int intel_link_power_down(struct sdw_intel *sdw) +{ + int link_control, spa_mask, cpa_mask, ret; + unsigned int link_id = sdw->instance; + void __iomem *shim = sdw->link_res->shim; + u16 ioctl; + + /* Glue logic */ + ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); + ioctl |= SDW_SHIM_IOCTL_BKE; + ioctl |= SDW_SHIM_IOCTL_COE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl &= ~(SDW_SHIM_IOCTL_MIF); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + /* Link power down sequence */ + link_control = intel_readl(shim, SDW_SHIM_LCTL); + spa_mask = ~(SDW_SHIM_LCTL_SPA << link_id); + cpa_mask = (SDW_SHIM_LCTL_CPA << link_id); + link_control &= spa_mask; + + ret = intel_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); + if (ret < 0) + return ret; + + sdw->cdns.link_up = false; + return 0; +} + /* * PDI routines */
Implement suspend/resume capabilities (not runtime_pm for now) The resume part is essentially a full-blown re-enumeration.
When S0ix is supported, we will select clock stop mode when the ACPI target state is S0, and tear down the link for S3.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/intel.c | 75 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index fe3a8b3b2c04..aeef0c0fc443 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -1253,10 +1253,85 @@ static int intel_master_remove(struct sdw_master_device *md) return 0; }
+/* + * PM calls + */ + +#ifdef CONFIG_PM + +static int intel_suspend(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + ret = sdw_cdns_enable_interrupt(cdns, false); + if (ret < 0) { + dev_err(dev, "cannot disable interrupts on suspend\n"); + return ret; + } + + ret = intel_link_power_down(sdw); + if (ret) { + dev_err(dev, "Link power down failed: %d", ret); + return ret; + } + + intel_shim_wake(sdw, false); + + return 0; +} + +static int intel_resume(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence during resume\n"); + return ret; + } + + return ret; +} + +#endif + +static const struct dev_pm_ops intel_pm = { + SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume) +}; + struct sdw_md_driver intel_sdw_driver = { .driver = { .name = "intel-sdw", .owner = THIS_MODULE, + .pm = &intel_pm, }, .probe = intel_master_probe, .startup = intel_master_startup,
Add basic hooks in DAI .startup and .shutdown callbacks.
The SoundWire IP should be powered between those two calls. The power dependencies between SoundWire and DSP are handled with the parent/child relationship, before the SoundWire master device becomes active the parent device will become active and power-up the shared rails.
For now the strategy is to rely on complete enumeration when the device becomes active, so the code is a copy/paste of the sequence for system suspend/resume. In future patches, the strategy will optionally be to rely on clock stop if the enumeration time is prohibitive or when the devices connected to a link can signal a wake.
A module parameter is added to make integration of new Slave devices easier, to e.g. keep the device active or prevent clock-stop.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/intel.c | 108 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 4 deletions(-)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index aeef0c0fc443..650581348732 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/io.h> +#include <linux/pm_runtime.h> #include <linux/platform_device.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -21,6 +22,20 @@ #include "bus.h" #include "intel.h"
+/* + * debug/config flags for the Intel SoundWire Master. + * + * Since we may have multiple masters active, we can have up to 8 + * flags reused in each byte, with master0 using the ls-byte, etc. + */ + +#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0) +#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1) + +static int md_flags; +module_param_named(sdw_md_flags, md_flags, int, 0444); +MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)"); + /* Intel SHIM Registers Definition */ #define SDW_SHIM_LCAP 0x0 #define SDW_SHIM_LCTL 0x4 @@ -742,10 +757,16 @@ static int sdw_stream_setup(struct snd_pcm_substream *substream, static int intel_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - /* - * TODO: add pm_runtime support here, the startup callback - * will make sure the IP is 'active' - */ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = pm_runtime_get_sync(cdns->dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(cdns->dev, + "pm_runtime_get_sync failed in %s, ret %d\n", + __func__, ret); + pm_runtime_put_noidle(cdns->dev); + }
return sdw_stream_setup(substream, dai); } @@ -924,6 +945,7 @@ static void intel_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sdw_cdns_dma_data *dma; + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
dma = snd_soc_dai_get_dma_data(dai, substream); if (!dma) @@ -931,6 +953,9 @@ static void intel_shutdown(struct snd_pcm_substream *substream,
snd_soc_dai_set_dma_data(dai, substream, NULL); kfree(dma); + + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); }
static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai, @@ -1180,6 +1205,7 @@ static int intel_master_startup(struct sdw_master_device *md) { struct sdw_cdns_stream_config config; struct sdw_intel *sdw; + int link_flags; int ret;
sdw = md->pdata; @@ -1225,6 +1251,17 @@ static int intel_master_startup(struct sdw_master_device *md)
intel_debugfs_init(sdw);
+ /* Enable runtime PM */ + link_flags = md_flags >> (sdw->cdns.bus.link_id * 8); + if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) { + pm_runtime_set_autosuspend_delay(&md->dev, 3000); + pm_runtime_use_autosuspend(&md->dev); + pm_runtime_mark_last_busy(&md->dev); + + pm_runtime_set_active(&md->dev); + pm_runtime_enable(&md->dev); + } + return 0;
err_interrupt: @@ -1288,6 +1325,35 @@ static int intel_suspend(struct device *dev) return 0; }
+static int intel_suspend_runtime(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + ret = sdw_cdns_enable_interrupt(cdns, false); + if (ret < 0) { + dev_err(dev, "cannot disable interrupts on suspend\n"); + return ret; + } + + ret = intel_link_power_down(sdw); + if (ret) { + dev_err(dev, "Link power down failed: %d", ret); + return ret; + } + + intel_shim_wake(sdw, false); + + return 0; +} + static int intel_resume(struct device *dev) { struct sdw_cdns *cdns = dev_get_drvdata(dev); @@ -1321,10 +1387,44 @@ static int intel_resume(struct device *dev) return ret; }
+static int intel_resume_runtime(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence during resume\n"); + return ret; + } + + return ret; +} + #endif
static const struct dev_pm_ops intel_pm = { SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume) + SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL) };
struct sdw_md_driver intel_sdw_driver = {
The system resume does the entire bus re-initialization and brings it to full-power. If the device was pm_runtime suspended, there is no need to run the pm_runtime resume sequence after the system runtime.
Follow the documentation from runtime_pm.rst, and conditionally disable, set_active and re-enable the device on system resume.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/intel.c | 30 ++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 1 + 2 files changed, 31 insertions(+)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 650581348732..c3cd7d2d5a34 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -1298,6 +1298,7 @@ static int intel_master_remove(struct sdw_master_device *md)
static int intel_suspend(struct device *dev) { + struct sdw_master_device *md = to_sdw_master_device(dev); struct sdw_cdns *cdns = dev_get_drvdata(dev); struct sdw_intel *sdw = cdns_to_intel(cdns); int ret; @@ -1308,6 +1309,20 @@ static int intel_suspend(struct device *dev) return 0; }
+ if (pm_runtime_status_suspended(dev)) { + dev_dbg(dev, + "%s: pm_runtime status: suspended\n", + __func__); + + /* + * keep track of the state for the system resume, where + * we will need to reset the pm_runtime status to active + */ + md->pm_runtime_suspended = true; + + return 0; + } + ret = sdw_cdns_enable_interrupt(cdns, false); if (ret < 0) { dev_err(dev, "cannot disable interrupts on suspend\n"); @@ -1356,6 +1371,7 @@ static int intel_suspend_runtime(struct device *dev)
static int intel_resume(struct device *dev) { + struct sdw_master_device *md = to_sdw_master_device(dev); struct sdw_cdns *cdns = dev_get_drvdata(dev); struct sdw_intel *sdw = cdns_to_intel(cdns); int ret; @@ -1366,6 +1382,20 @@ static int intel_resume(struct device *dev) return 0; }
+ if (md->pm_runtime_suspended) { + dev_dbg(dev, + "%s: pm_runtime status was suspended, forcing active\n", + __func__); + + /* follow required sequence from runtime_pm.rst */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_enable(dev); + + md->pm_runtime_suspended = false; + } + ret = intel_init(sdw); if (ret) { dev_err(dev, "%s failed: %d", __func__, ret); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index d756e41c9466..1950d2b467bd 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -577,6 +577,7 @@ struct sdw_master_device { struct device dev; int link_id; struct sdw_md_driver *driver; + bool pm_runtime_suspended; void *pdata; /* core does not touch */ };
When resuming, we need to re-enumerate and restart from UNATTACHED.
This will help implement a more robust state machine avoiding race conditions on resume.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/bus.c | 24 ++++++++++++++++++++++++ drivers/soundwire/bus.h | 2 ++ 2 files changed, 26 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 0e761efd32f9..17c744c0ff4c 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -1107,3 +1107,27 @@ int sdw_handle_slave_status(struct sdw_bus *bus, return ret; } EXPORT_SYMBOL(sdw_handle_slave_status); + +void sdw_clear_slave_status(struct sdw_bus *bus) +{ + struct sdw_slave *slave; + int i; + + /* Check all non-zero devices */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (slave->status != SDW_SLAVE_UNATTACHED) + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + } +} +EXPORT_SYMBOL(sdw_clear_slave_status); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index acb8d11a4c84..cdbd11292cc3 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -165,4 +165,6 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) return sdw_write(slave, addr, tmp); }
+void sdw_clear_slave_status(struct sdw_bus *bus); + #endif /* __SDW_BUS_H */
This helps make sure they are all UNATTACHED.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/intel.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index c3cd7d2d5a34..36e09e273eda 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -1435,6 +1435,9 @@ static int intel_resume_runtime(struct device *dev) return ret; }
+ /* make sure all Slaves are tagged as UNATTACHED */ + sdw_clear_slave_status(&sdw->cdns.bus); + ret = sdw_cdns_enable_interrupt(cdns, true); if (ret < 0) { dev_err(dev, "cannot enable interrupts during resume\n");
Before checking for the presence of Device0, we first need to clean-up the internal state of Slaves that are no longer attached.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/bus.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 17c744c0ff4c..65521c2ed2e1 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -1033,6 +1033,24 @@ int sdw_handle_slave_status(struct sdw_bus *bus, struct sdw_slave *slave; int i, ret = 0;
+ /* first check if any Slaves fell off the bus */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (status[i] == SDW_SLAVE_UNATTACHED && + slave->status != SDW_SLAVE_UNATTACHED) + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + } + if (status[0] == SDW_SLAVE_ATTACHED) { dev_dbg(bus->dev, "Slave attached, programming device number\n"); ret = sdw_program_device_num(bus);
First add the init_completion() and complete() calls to test their symmetry.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/bus.c | 19 +++++++++++++++++++ drivers/soundwire/slave.c | 1 + 2 files changed, 20 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 65521c2ed2e1..b18110726273 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -637,6 +637,25 @@ static void sdw_modify_slave_status(struct sdw_slave *slave, enum sdw_slave_status status) { mutex_lock(&slave->bus->bus_lock); + + dev_vdbg(&slave->dev, + "%s: changing status slave %d status %d new status %d\n", + __func__, slave->dev_num, slave->status, status); + + if (status == SDW_SLAVE_UNATTACHED) { + dev_dbg(&slave->dev, + "%s: initializing completion for Slave %d\n", + __func__, slave->dev_num); + + init_completion(&slave->enumeration_complete); + + } else if (status == SDW_SLAVE_ATTACHED) { + dev_dbg(&slave->dev, + "%s: signaling completion for Slave %d\n", + __func__, slave->dev_num); + + complete(&slave->enumeration_complete); + } slave->status = status; mutex_unlock(&slave->bus->bus_lock); } diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index 81b94cd3985e..5c6c744e0713 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -51,6 +51,7 @@ static int sdw_slave_add(struct sdw_bus *bus, slave->dev.type = &sdw_slave_type; slave->bus = bus; slave->status = SDW_SLAVE_UNATTACHED; + init_completion(&slave->enumeration_complete); slave->dev_num = 0; init_completion(&slave->probe_complete); slave->probed = false;
Prevent race conditions between remove and resume by disabling pm_runtime.
Note that this only takes care of pm_runtime at the Master level, the same precautions are needed when removing a Slave device.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/intel.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 36e09e273eda..f0f9a6252522 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -1275,6 +1275,8 @@ static int intel_master_remove(struct sdw_master_device *md) { struct sdw_intel *sdw;
+ pm_runtime_disable(&md->dev); + sdw = md->pdata;
if (!sdw->cdns.bus.prop.hw_disabled) {
Before removing the slave device, disable pm_runtime to prevent any race condition with the resume being executed after the bus and slave devices are removed.
Since this pm_runtime_disable() is handled in common routines, implementations of Slave drivers do not need to call it in their .remove() routine.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/bus.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index b18110726273..3b99521aa0c0 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -113,6 +113,8 @@ static int sdw_delete_slave(struct device *dev, void *data) struct sdw_slave *slave = to_sdw_slave_device(dev); struct sdw_bus *bus = slave->bus;
+ pm_runtime_disable(dev); + sdw_slave_debugfs_exit(slave);
mutex_lock(&bus->bus_lock);
The state machine and notes don't accurately explain or allow transitions from STREAM_DEPREPARED and STREAM_DISABLED.
Add more explanations and allow for more transitions as a result of a trigger_stop(), trigger_suspend() and prepare(), depending on the ALSA/ASoC layer behavior defined by the INFO_RESUME and INFO_PAUSE flags.
Also add basic checks to help debug inconsistent states and illegal state machine transitions.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- Documentation/driver-api/soundwire/stream.rst | 63 +++++++++++++------ drivers/soundwire/stream.c | 38 +++++++++++ 2 files changed, 83 insertions(+), 18 deletions(-)
diff --git a/Documentation/driver-api/soundwire/stream.rst b/Documentation/driver-api/soundwire/stream.rst index 5351bd2f34a8..9b7418ff8d59 100644 --- a/Documentation/driver-api/soundwire/stream.rst +++ b/Documentation/driver-api/soundwire/stream.rst @@ -156,22 +156,27 @@ Below shows the SoundWire stream states and state transition diagram. :: +-----------+ +------------+ +----------+ +----------+ | ALLOCATED +---->| CONFIGURED +---->| PREPARED +---->| ENABLED | | STATE | | STATE | | STATE | | STATE | - +-----------+ +------------+ +----------+ +----+-----+ - ^ - | - | - v - +----------+ +------------+ +----+-----+ + +-----------+ +------------+ +---+--+---+ +----+-----+ + ^ ^ ^ + | | | + __| |___________ | + | | | + v | v + +----------+ +-----+------+ +-+--+-----+ | RELEASED |<----------+ DEPREPARED |<-------+ DISABLED | | STATE | | STATE | | STATE | +----------+ +------------+ +----------+
-NOTE: State transition between prepare and deprepare is supported in Spec -but not in the software (subsystem) +NOTE: State transitions between ``SDW_STREAM_ENABLED`` and +``SDW_STREAM_DISABLED`` are only relevant when then INFO_PAUSE flag is +supported at the ALSA/ASoC level. Likewise the transition between +``SDW_DISABLED_STATE`` and ``SDW_PREPARED_STATE`` depends on the +INFO_RESUME flag.
-NOTE2: Stream state transition checks need to be handled by caller -framework, for example ALSA/ASoC. No checks for stream transition exist in -SoundWire subsystem. +NOTE2: The framework implements basic state transition checks, but +does not e.g. check if a transition from DISABLED to ENABLED is valid +on a specific platform. Such tests need to be added at the ALSA/ASoC +level.
Stream State Operations ----------------------- @@ -246,6 +251,9 @@ SDW_STREAM_PREPARED
Prepare state of stream. Operations performed before entering in this state:
+ (0) Steps 1 and 2 are omitted in the case of a resume operation, + where the bus bandwidth is known. + (1) Bus parameters such as bandwidth, frame shape, clock frequency, are computed based on current stream as well as already active stream(s) on Bus. Re-computation is required to accommodate current @@ -270,13 +278,15 @@ Prepare state of stream. Operations performed before entering in this state: After all above operations are successful, stream state is set to ``SDW_STREAM_PREPARED``.
-Bus implements below API for PREPARE state which needs to be called once per -stream. From ASoC DPCM framework, this stream state is linked to -.prepare() operation. +Bus implements below API for PREPARE state which needs to be called +once per stream. From ASoC DPCM framework, this stream state is linked +to .prepare() operation. Since the .trigger() operations may not +follow the .prepare(), a direct transitions from +``SDW_STREAM_PREPARED`` to ``SDW_STREAM_DEPREPARED`` is allowed.
.. code-block:: c
- int sdw_prepare_stream(struct sdw_stream_runtime * stream); + int sdw_prepare_stream(struct sdw_stream_runtime * stream, bool resume);
SDW_STREAM_ENABLED @@ -332,6 +342,14 @@ Bus implements below API for DISABLED state which needs to be called once per stream. From ASoC DPCM framework, this stream state is linked to .trigger() stop operation.
+When the INFO_PAUSE flag is supported, a direct transition to +``SDW_STREAM_ENABLED`` is allowed. + +For resume operations where ASoC will use the .prepare() callback, the +stream can transition from ``SDW_STREAM_DISABLED`` to +``SDW_STREAM_PREPARED``, with all required settings restored but +without updating the bandwidth and bit allocation. + .. code-block:: c
int sdw_disable_stream(struct sdw_stream_runtime * stream); @@ -353,9 +371,18 @@ state: After all above operations are successful, stream state is set to ``SDW_STREAM_DEPREPARED``.
-Bus implements below API for DEPREPARED state which needs to be called once -per stream. From ASoC DPCM framework, this stream state is linked to -.trigger() stop operation. +Bus implements below API for DEPREPARED state which needs to be called +once per stream. ALSA/ASoC do not have a concept of 'deprepare', and +the mapping from this stream state to ALSA/ASoC operation may be +implementation specific. + +When the INFO_PAUSE flag is supported, the stream state is linked to +the .hw_free() operation - the stream is not deprepared on a +TRIGGER_STOP. + +Other implementations may transition to the ``SDW_STREAM_DEPREPARED`` +state on TRIGGER_STOP, should they require a transition through the +``SDW_STREAM_PREPARED`` state.
.. code-block:: c
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index e69f94a8c3a8..0a074d445b8d 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1553,10 +1553,21 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
+ if (stream->state != SDW_STREAM_CONFIGURED && + stream->state != SDW_STREAM_DEPREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_prepare_stream(stream); + if (ret < 0) pr_err("Prepare for stream:%s failed: %d\n", stream->name, ret);
+state_err: sdw_release_bus_lock(stream); return ret; } @@ -1621,10 +1632,19 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
+ if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_enable_stream(stream); if (ret < 0) pr_err("Enable for stream:%s failed: %d\n", stream->name, ret);
+state_err: sdw_release_bus_lock(stream); return ret; } @@ -1697,10 +1717,18 @@ int sdw_disable_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
+ if (stream->state != SDW_STREAM_ENABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_disable_stream(stream); if (ret < 0) pr_err("Disable for stream:%s failed: %d\n", stream->name, ret);
+state_err: sdw_release_bus_lock(stream); return ret; } @@ -1755,10 +1783,20 @@ int sdw_deprepare_stream(struct sdw_stream_runtime *stream) }
sdw_acquire_bus_lock(stream); + + if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_deprepare_stream(stream); if (ret < 0) pr_err("De-prepare for stream:%d failed: %d\n", ret, ret);
+state_err: sdw_release_bus_lock(stream); return ret; }
From: Bard Liao yung-chuan.liao@linux.intel.com
We don't need to prepare the stream again if the stream is already prepared.
sdw_prepare_stream() could be called multiple times without calling sdw_deprepare_stream(). We call sdw_prepare_stream() in the prepare dai ops and sdw_deprepare_stream() in the hw_free dai ops. If an xrun happens, sdw_prepare_stream() will be called but sdw_deprepare_stream() will not, which results in an imbalance and an invalid total bandwidth.
Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/stream.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 0a074d445b8d..38375c9124e4 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1544,7 +1544,7 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) */ int sdw_prepare_stream(struct sdw_stream_runtime *stream) { - int ret = 0; + int ret;
if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); @@ -1553,6 +1553,11 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
+ if (stream->state == SDW_STREAM_PREPARED) { + ret = 0; + goto state_err; + } + if (stream->state != SDW_STREAM_CONFIGURED && stream->state != SDW_STREAM_DEPREPARED && stream->state != SDW_STREAM_DISABLED) {
After a system suspend, the ALSA/ASoC core will invoke the .prepare() callback and a TRIGGER_START when INFO_RESUME is not supported.
Likewise, when an underflow occurs, the .prepare callback will be invoked.
In both cases, the stream can be in DISABLED mode, and will transition into the PREPARED mode. We however don't want the bus bandwidth to be recomputed.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/stream.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 38375c9124e4..3447ef7d55e3 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1460,7 +1460,8 @@ static void sdw_release_bus_lock(struct sdw_stream_runtime *stream) } }
-static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) +static int _sdw_prepare_stream(struct sdw_stream_runtime *stream, + bool update_params) { struct sdw_master_runtime *m_rt; struct sdw_bus *bus = NULL; @@ -1480,6 +1481,9 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) return -EINVAL; }
+ if (!update_params) + goto program_params; + /* Increment cumulative bus bandwidth */ /* TODO: Update this during Device-Device support */ bus->params.bandwidth += m_rt->stream->params.rate * @@ -1495,6 +1499,7 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) } }
+program_params: /* Program params */ ret = sdw_program_params(bus); if (ret < 0) { @@ -1544,6 +1549,7 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) */ int sdw_prepare_stream(struct sdw_stream_runtime *stream) { + bool update_params = true; int ret;
if (!stream) { @@ -1567,7 +1573,16 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream) goto state_err; }
- ret = _sdw_prepare_stream(stream); + /* + * when the stream is DISABLED, this means sdw_prepare_stream() + * is called as a result of an underflow or a resume operation. + * In this case, the bus parameters shall not be recomputed, but + * still need to be re-applied + */ + if (stream->state == SDW_STREAM_DISABLED) + update_params = false; + + ret = _sdw_prepare_stream(stream, update_params);
if (ret < 0) pr_err("Prepare for stream:%s failed: %d\n", stream->name, ret);
From: Bard Liao yung-chuan.liao@linux.intel.com
The .prepare() callback is invoked for normal streaming, underflows or during the system resume transition. In the latter case, the context for the ALH PDIs is lost, and the DSP is not initialized properly either, but the bus parameters don't need to be recomputed.
To avoid keeping track of state variables, it's simpler to just reinitialize the SHIM/ALH/Cadence/DSP settings in .prepare.
Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/soundwire/intel.c | 40 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index f0f9a6252522..ec6c58635a99 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -117,6 +117,8 @@ struct sdw_intel { struct sdw_cdns cdns; int instance; struct sdw_intel_link_res *link_res; + struct snd_pcm_hw_params *hw_params; + struct sdw_cdns_pdi *pdi; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; #endif @@ -813,6 +815,8 @@ static int intel_hw_params(struct snd_pcm_substream *substream, intel_pdi_alh_configure(sdw, pdi); sdw_cdns_config_stream(cdns, ch, dir, pdi);
+ sdw->pdi = pdi; + sdw->hw_params = params;
/* Inform DSP about PDI stream number */ ret = intel_params_stream(sdw, substream, dai, params, @@ -856,7 +860,11 @@ static int intel_hw_params(struct snd_pcm_substream *substream, static int intel_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dma_data *dma; + int ch, dir; + int ret;
dma = snd_soc_dai_get_dma_data(dai, substream); if (!dma) { @@ -865,7 +873,35 @@ static int intel_prepare(struct snd_pcm_substream *substream, return -EIO; }
- return sdw_prepare_stream(dma->stream); + /* + * .prepare() is called after system resume, where we need to + * reinitialize the SHIM/ALH/Cadence IP. To avoid dealing with + * complicated state machines, we just re-initialize in all + * cases since there are no side effects. + */ + + /* configure stream */ + ch = params_channels(sdw->hw_params); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = SDW_DATA_DIR_RX; + else + dir = SDW_DATA_DIR_TX; + + intel_pdi_shim_configure(sdw, sdw->pdi); + intel_pdi_alh_configure(sdw, sdw->pdi); + sdw_cdns_config_stream(cdns, ch, dir, sdw->pdi); + + /* Inform DSP about PDI stream number */ + ret = intel_params_stream(sdw, substream, dai, sdw->hw_params, + sdw->instance, + sdw->pdi->intel_alh_id); + if (ret) + goto err; + + ret = sdw_prepare_stream(dma->stream); + +err: + return ret; }
static int intel_trigger(struct snd_pcm_substream *substream, int cmd, @@ -936,6 +972,8 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) return ret; }
+ sdw->hw_params = NULL; + sdw->pdi = NULL; sdw_release_stream(dma->stream);
return 0;
participants (1)
-
Pierre-Louis Bossart