Commit 57b1659f authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge tag 'soundwire-5.18-rc1' of...

Merge tag 'soundwire-5.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire into char-misc-next

Vinod writes:

soundwire updates for 5.17-rc1

 - stream handling refactoring and renaming to make it consistent
   in the core
 - runtime pm suport for qcom driver
 - in band wake up interrupt support for qcom driver

* tag 'soundwire-5.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: (27 commits)
  soundwire: qcom: use __maybe_unused for swrm_runtime_resume()
  soundwire: qcom: constify static struct qcom_swrm_data global variables
  soundwire: qcom: add in-band wake up interrupt support
  dt-bindings: soundwire: qcom: document optional wake irq
  soundwire: qcom: add runtime pm support
  soundwire: stream: make enable/disable/deprepare idempotent
  soundwire: stream: sdw_stream_add_ functions can be called multiple times
  soundwire: stream: introduce sdw_slave_rt_find() helper
  soundwire: stream: separate alloc and config within sdw_stream_add_xxx()
  soundwire: stream: move list addition to sdw_slave_alloc_rt()
  soundwire: stream: rename and move master/slave_rt_free routines
  soundwire: stream: group sdw_stream_ functions
  soundwire: stream: split sdw_alloc_slave_rt() in alloc and config
  soundwire: stream: move sdw_alloc_slave_rt() before 'master' helpers
  soundwire: stream: split sdw_alloc_master_rt() in alloc and config
  soundwire: stream: simplify sdw_alloc_master_rt()
  soundwire: stream: group sdw_port and sdw_master/slave_port functions
  soundwire: stream: add 'slave' prefix for port range checks
  soundwire: stream: split alloc and config in two functions
  soundwire: stream: split port allocation and configuration loops
  ...
parents 4dee7a71 266fa946
......@@ -22,7 +22,19 @@ board specific bus parameters.
- interrupts:
Usage: required
Value type: <prop-encoded-array>
Definition: should specify the SoundWire Controller IRQ
Definition: should specify the SoundWire Controller core and optional
wake IRQ
- interrupt-names:
Usage: Optional
Value type: boolean
Value type: <stringlist>
Definition: should be "core" for core and "wakeup" for wake interrupt.
- wakeup-source:
Usage: Optional
Value type: boolean
Definition: should specify if SoundWire Controller is wake up capable.
- clock-names:
Usage: required
......
......@@ -1749,9 +1749,12 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
continue;
if (status[i] == SDW_SLAVE_UNATTACHED &&
slave->status != SDW_SLAVE_UNATTACHED)
slave->status != SDW_SLAVE_UNATTACHED) {
dev_warn(&slave->dev, "Slave %d state check1: UNATTACHED, status was %d\n",
i, slave->status);
sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
}
}
if (status[0] == SDW_SLAVE_ATTACHED) {
dev_dbg(bus->dev, "Slave attached, programming device number\n");
......@@ -1785,6 +1788,9 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
if (slave->status == SDW_SLAVE_UNATTACHED)
break;
dev_warn(&slave->dev, "Slave %d state check2: UNATTACHED, status was %d\n",
i, slave->status);
sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
break;
......
......@@ -448,8 +448,8 @@ static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
/* 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);
wake_sts |= (SDW_SHIM_WAKESTS_STATUS << link_id);
intel_writew(shim, SDW_SHIM_WAKESTS, wake_sts);
}
mutex_unlock(sdw->link_res->shim_lock);
}
......
......@@ -180,7 +180,8 @@ static struct sdw_intel_ctx
if (!res)
return NULL;
if (acpi_bus_get_device(res->handle, &adev))
adev = acpi_fetch_acpi_dev(res->handle);
if (!adev)
return NULL;
if (!res->count)
......@@ -294,13 +295,13 @@ static struct sdw_intel_ctx
static int
sdw_intel_startup_controller(struct sdw_intel_ctx *ctx)
{
struct acpi_device *adev;
struct acpi_device *adev = acpi_fetch_acpi_dev(ctx->handle);
struct sdw_intel_link_dev *ldev;
u32 caps;
u32 link_mask;
int i;
if (acpi_bus_get_device(ctx->handle, &adev))
if (!adev)
return -EINVAL;
/* Check SNDWLCAP.LCOUNT */
......
......@@ -11,8 +11,10 @@
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/pm_wakeirq.h>
#include <linux/slimbus.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_registers.h>
......@@ -20,6 +22,9 @@
#include <sound/soc.h>
#include "bus.h"
#define SWRM_COMP_SW_RESET 0x008
#define SWRM_COMP_STATUS 0x014
#define SWRM_FRM_GEN_ENABLED BIT(0)
#define SWRM_COMP_HW_VERSION 0x00
#define SWRM_COMP_CFG_ADDR 0x04
#define SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK BIT(1)
......@@ -29,6 +34,7 @@
#define SWRM_COMP_PARAMS_RD_FIFO_DEPTH GENMASK(19, 15)
#define SWRM_COMP_PARAMS_DOUT_PORTS_MASK GENMASK(4, 0)
#define SWRM_COMP_PARAMS_DIN_PORTS_MASK GENMASK(9, 5)
#define SWRM_COMP_MASTER_ID 0x104
#define SWRM_INTERRUPT_STATUS 0x200
#define SWRM_INTERRUPT_STATUS_RMSK GENMASK(16, 0)
#define SWRM_INTERRUPT_STATUS_SLAVE_PEND_IRQ BIT(0)
......@@ -111,6 +117,13 @@
#define SWR_MAX_CMD_ID 14
#define MAX_FIFO_RD_RETRY 3
#define SWR_OVERFLOW_RETRY_COUNT 30
#define SWRM_LINK_STATUS_RETRY_CNT 100
enum {
MASTER_ID_WSA = 1,
MASTER_ID_RX,
MASTER_ID_TX
};
struct qcom_swrm_port_config {
u8 si;
......@@ -142,6 +155,7 @@ struct qcom_swrm_ctrl {
u8 rd_cmd_id;
int irq;
unsigned int version;
int wake_irq;
int num_din_ports;
int num_dout_ports;
int cols_index;
......@@ -159,6 +173,7 @@ struct qcom_swrm_ctrl {
u32 slave_status;
u32 wr_fifo_depth;
u32 rd_fifo_depth;
bool clock_stop_not_supported;
};
struct qcom_swrm_data {
......@@ -166,12 +181,12 @@ struct qcom_swrm_data {
u32 default_rows;
};
static struct qcom_swrm_data swrm_v1_3_data = {
static const struct qcom_swrm_data swrm_v1_3_data = {
.default_rows = 48,
.default_cols = 16,
};
static struct qcom_swrm_data swrm_v1_5_data = {
static const struct qcom_swrm_data swrm_v1_5_data = {
.default_rows = 50,
.default_cols = 16,
};
......@@ -490,6 +505,30 @@ static int qcom_swrm_enumerate(struct sdw_bus *bus)
return 0;
}
static irqreturn_t qcom_swrm_wake_irq_handler(int irq, void *dev_id)
{
struct qcom_swrm_ctrl *swrm = dev_id;
int ret;
ret = pm_runtime_get_sync(swrm->dev);
if (ret < 0 && ret != -EACCES) {
dev_err_ratelimited(swrm->dev,
"pm_runtime_get_sync failed in %s, ret %d\n",
__func__, ret);
pm_runtime_put_noidle(swrm->dev);
}
if (swrm->wake_irq > 0) {
if (!irqd_irq_disabled(irq_get_irq_data(swrm->wake_irq)))
disable_irq_nosync(swrm->wake_irq);
}
pm_runtime_mark_last_busy(swrm->dev);
pm_runtime_put_autosuspend(swrm->dev);
return IRQ_HANDLED;
}
static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
{
struct qcom_swrm_ctrl *swrm = dev_id;
......@@ -497,6 +536,7 @@ static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
u32 i;
int devnum;
int ret = IRQ_HANDLED;
clk_prepare_enable(swrm->hclk);
swrm->reg_read(swrm, SWRM_INTERRUPT_STATUS, &intr_sts);
intr_sts_masked = intr_sts & swrm->intr_mask;
......@@ -604,6 +644,7 @@ static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
intr_sts_masked = intr_sts & swrm->intr_mask;
} while (intr_sts_masked);
clk_disable_unprepare(swrm->hclk);
return ret;
}
......@@ -1017,6 +1058,15 @@ static int qcom_swrm_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *codec_dai;
int ret, i;
ret = pm_runtime_get_sync(ctrl->dev);
if (ret < 0 && ret != -EACCES) {
dev_err_ratelimited(ctrl->dev,
"pm_runtime_get_sync failed in %s, ret %d\n",
__func__, ret);
pm_runtime_put_noidle(ctrl->dev);
return ret;
}
sruntime = sdw_alloc_stream(dai->name);
if (!sruntime)
return -ENOMEM;
......@@ -1044,6 +1094,9 @@ static void qcom_swrm_shutdown(struct snd_pcm_substream *substream,
sdw_release_stream(ctrl->sruntime[dai->id]);
ctrl->sruntime[dai->id] = NULL;
pm_runtime_mark_last_busy(ctrl->dev);
pm_runtime_put_autosuspend(ctrl->dev);
}
static const struct snd_soc_dai_ops qcom_swrm_pdm_dai_ops = {
......@@ -1197,12 +1250,23 @@ static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl)
static int swrm_reg_show(struct seq_file *s_file, void *data)
{
struct qcom_swrm_ctrl *swrm = s_file->private;
int reg, reg_val;
int reg, reg_val, ret;
ret = pm_runtime_get_sync(swrm->dev);
if (ret < 0 && ret != -EACCES) {
dev_err_ratelimited(swrm->dev,
"pm_runtime_get_sync failed in %s, ret %d\n",
__func__, ret);
pm_runtime_put_noidle(swrm->dev);
}
for (reg = 0; reg <= SWR_MSTR_MAX_REG_ADDR; reg += 4) {
swrm->reg_read(swrm, reg, &reg_val);
seq_printf(s_file, "0x%.3x: 0x%.2x\n", reg, reg_val);
}
pm_runtime_mark_last_busy(swrm->dev);
pm_runtime_put_autosuspend(swrm->dev);
return 0;
}
......@@ -1267,6 +1331,7 @@ static int qcom_swrm_probe(struct platform_device *pdev)
ctrl->bus.ops = &qcom_swrm_ops;
ctrl->bus.port_ops = &qcom_swrm_port_ops;
ctrl->bus.compute_params = &qcom_swrm_compute_params;
ctrl->bus.clk_stop_timeout = 300;
ret = qcom_swrm_get_port_config(ctrl);
if (ret)
......@@ -1301,6 +1366,18 @@ static int qcom_swrm_probe(struct platform_device *pdev)
goto err_clk;
}
ctrl->wake_irq = of_irq_get(dev->of_node, 1);
if (ctrl->wake_irq > 0) {
ret = devm_request_threaded_irq(dev, ctrl->wake_irq, NULL,
qcom_swrm_wake_irq_handler,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"swr_wake_irq", ctrl);
if (ret) {
dev_err(dev, "Failed to request soundwire wake irq\n");
goto err_init;
}
}
ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode);
if (ret) {
dev_err(dev, "Failed to register Soundwire controller (%d)\n",
......@@ -1319,6 +1396,21 @@ static int qcom_swrm_probe(struct platform_device *pdev)
(ctrl->version >> 24) & 0xff, (ctrl->version >> 16) & 0xff,
ctrl->version & 0xffff);
pm_runtime_set_autosuspend_delay(dev, 3000);
pm_runtime_use_autosuspend(dev);
pm_runtime_mark_last_busy(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
/* Clk stop is not supported on WSA Soundwire masters */
if (ctrl->version <= 0x01030000) {
ctrl->clock_stop_not_supported = true;
} else {
ctrl->reg_read(ctrl, SWRM_COMP_MASTER_ID, &val);
if (val == MASTER_ID_WSA)
ctrl->clock_stop_not_supported = true;
}
#ifdef CONFIG_DEBUG_FS
ctrl->debugfs = debugfs_create_dir("qualcomm-sdw", ctrl->bus.debugfs);
debugfs_create_file("qualcomm-registers", 0400, ctrl->debugfs, ctrl,
......@@ -1345,6 +1437,115 @@ static int qcom_swrm_remove(struct platform_device *pdev)
return 0;
}
static bool swrm_wait_for_frame_gen_enabled(struct qcom_swrm_ctrl *swrm)
{
int retry = SWRM_LINK_STATUS_RETRY_CNT;
int comp_sts;
do {
swrm->reg_read(swrm, SWRM_COMP_STATUS, &comp_sts);
if (comp_sts & SWRM_FRM_GEN_ENABLED)
return true;
usleep_range(500, 510);
} while (retry--);
dev_err(swrm->dev, "%s: link status not %s\n", __func__,
comp_sts && SWRM_FRM_GEN_ENABLED ? "connected" : "disconnected");
return false;
}
static int __maybe_unused swrm_runtime_resume(struct device *dev)
{
struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dev);
int ret;
if (ctrl->wake_irq > 0) {
if (!irqd_irq_disabled(irq_get_irq_data(ctrl->wake_irq)))
disable_irq_nosync(ctrl->wake_irq);
}
clk_prepare_enable(ctrl->hclk);
if (ctrl->clock_stop_not_supported) {
reinit_completion(&ctrl->enumeration);
ctrl->reg_write(ctrl, SWRM_COMP_SW_RESET, 0x01);
usleep_range(100, 105);
qcom_swrm_init(ctrl);
usleep_range(100, 105);
if (!swrm_wait_for_frame_gen_enabled(ctrl))
dev_err(ctrl->dev, "link failed to connect\n");
/* wait for hw enumeration to complete */
wait_for_completion_timeout(&ctrl->enumeration,
msecs_to_jiffies(TIMEOUT_MS));
qcom_swrm_get_device_status(ctrl);
sdw_handle_slave_status(&ctrl->bus, ctrl->status);
} else {
ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
ctrl->reg_write(ctrl, SWRM_INTERRUPT_CLEAR,
SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET);
ctrl->intr_mask |= SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
ctrl->reg_write(ctrl, SWRM_INTERRUPT_MASK_ADDR, ctrl->intr_mask);
ctrl->reg_write(ctrl, SWRM_INTERRUPT_CPU_EN, ctrl->intr_mask);
usleep_range(100, 105);
if (!swrm_wait_for_frame_gen_enabled(ctrl))
dev_err(ctrl->dev, "link failed to connect\n");
ret = sdw_bus_exit_clk_stop(&ctrl->bus);
if (ret < 0)
dev_err(ctrl->dev, "bus failed to exit clock stop %d\n", ret);
}
return 0;
}
static int __maybe_unused swrm_runtime_suspend(struct device *dev)
{
struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dev);
int ret;
if (!ctrl->clock_stop_not_supported) {
/* Mask bus clash interrupt */
ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
ctrl->reg_write(ctrl, SWRM_INTERRUPT_MASK_ADDR, ctrl->intr_mask);
ctrl->reg_write(ctrl, SWRM_INTERRUPT_CPU_EN, ctrl->intr_mask);
/* Prepare slaves for clock stop */
ret = sdw_bus_prep_clk_stop(&ctrl->bus);
if (ret < 0 && ret != -ENODATA) {
dev_err(dev, "prepare clock stop failed %d", ret);
return ret;
}
ret = sdw_bus_clk_stop(&ctrl->bus);
if (ret < 0 && ret != -ENODATA) {
dev_err(dev, "bus clock stop failed %d", ret);
return ret;
}
}
clk_disable_unprepare(ctrl->hclk);
usleep_range(300, 305);
if (ctrl->wake_irq > 0) {
if (irqd_irq_disabled(irq_get_irq_data(ctrl->wake_irq)))
enable_irq(ctrl->wake_irq);
}
return 0;
}
static const struct dev_pm_ops swrm_dev_pm_ops = {
SET_RUNTIME_PM_OPS(swrm_runtime_suspend, swrm_runtime_resume, NULL)
};
static const struct of_device_id qcom_swrm_of_match[] = {
{ .compatible = "qcom,soundwire-v1.3.0", .data = &swrm_v1_3_data },
{ .compatible = "qcom,soundwire-v1.5.1", .data = &swrm_v1_5_data },
......@@ -1359,6 +1560,7 @@ static struct platform_driver qcom_swrm_driver = {
.driver = {
.name = "qcom-soundwire",
.of_match_table = qcom_swrm_of_match,
.pm = &swrm_dev_pm_ops,
}
};
module_platform_driver(qcom_swrm_driver);
......
......@@ -865,117 +865,177 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
return ret;
}
/**
* sdw_release_stream() - Free the assigned stream runtime
*
* @stream: SoundWire stream runtime
*
* sdw_release_stream should be called only once per stream
*/
void sdw_release_stream(struct sdw_stream_runtime *stream)
static struct sdw_port_runtime *sdw_port_alloc(struct list_head *port_list)
{
kfree(stream);
struct sdw_port_runtime *p_rt;
p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL);
if (!p_rt)
return NULL;
list_add_tail(&p_rt->port_node, port_list);
return p_rt;
}
EXPORT_SYMBOL(sdw_release_stream);
/**
* sdw_alloc_stream() - Allocate and return stream runtime
*
* @stream_name: SoundWire stream name
*
* Allocates a SoundWire stream runtime instance.
* sdw_alloc_stream should be called only once per stream. Typically
* invoked from ALSA/ASoC machine/platform driver.
*/
struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name)
static int sdw_port_config(struct sdw_port_runtime *p_rt,
struct sdw_port_config *port_config,
int port_index)
{
struct sdw_stream_runtime *stream;
p_rt->ch_mask = port_config[port_index].ch_mask;
p_rt->num = port_config[port_index].num;
stream = kzalloc(sizeof(*stream), GFP_KERNEL);
if (!stream)
return NULL;
/*
* TODO: Check port capabilities for requested configuration
*/
stream->name = stream_name;
INIT_LIST_HEAD(&stream->master_list);
stream->state = SDW_STREAM_ALLOCATED;
stream->m_rt_count = 0;
return 0;
}
return stream;
static void sdw_port_free(struct sdw_port_runtime *p_rt)
{
list_del(&p_rt->port_node);
kfree(p_rt);
}
EXPORT_SYMBOL(sdw_alloc_stream);
static struct sdw_master_runtime
*sdw_find_master_rt(struct sdw_bus *bus,
static bool sdw_slave_port_allocated(struct sdw_slave_runtime *s_rt)
{
return !list_empty(&s_rt->port_list);
}
static void sdw_slave_port_free(struct sdw_slave *slave,
struct sdw_stream_runtime *stream)
{
struct sdw_port_runtime *p_rt, *_p_rt;
struct sdw_master_runtime *m_rt;
struct sdw_slave_runtime *s_rt;
/* Retrieve Bus handle if already available */
list_for_each_entry(m_rt, &stream->master_list, stream_node) {
if (m_rt->bus == bus)
return m_rt;
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
if (s_rt->slave != slave)
continue;
list_for_each_entry_safe(p_rt, _p_rt,
&s_rt->port_list, port_node) {
sdw_port_free(p_rt);
}
}
}
}
return NULL;
static int sdw_slave_port_alloc(struct sdw_slave *slave,
struct sdw_slave_runtime *s_rt,
unsigned int num_config)
{
struct sdw_port_runtime *p_rt;
int i;
/* Iterate for number of ports to perform initialization */
for (i = 0; i < num_config; i++) {
p_rt = sdw_port_alloc(&s_rt->port_list);
if (!p_rt)
return -ENOMEM;
}
return 0;
}
/**
* sdw_alloc_master_rt() - Allocates and initialize Master runtime handle
*
* @bus: SDW bus instance
* @stream_config: Stream configuration
* @stream: Stream runtime handle.
*
* This function is to be called with bus_lock held.
*/
static struct sdw_master_runtime
*sdw_alloc_master_rt(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
static int sdw_slave_port_is_valid_range(struct device *dev, int num)
{
struct sdw_master_runtime *m_rt;
if (!SDW_VALID_PORT_RANGE(num)) {
dev_err(dev, "SoundWire: Invalid port number :%d\n", num);
return -EINVAL;
}
return 0;
}
static int sdw_slave_port_config(struct sdw_slave *slave,
struct sdw_slave_runtime *s_rt,
struct sdw_port_config *port_config)
{
struct sdw_port_runtime *p_rt;
int ret;
int i;
i = 0;
list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
/*
* check if Master is already allocated (as a result of Slave adding
* it first), if so skip allocation and go to configure
* TODO: Check valid port range as defined by DisCo/
* slave
*/
m_rt = sdw_find_master_rt(bus, stream);
if (m_rt)
goto stream_config;
ret = sdw_slave_port_is_valid_range(&slave->dev, port_config[i].num);
if (ret < 0)
return ret;
m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
if (!m_rt)
return NULL;
ret = sdw_port_config(p_rt, port_config, i);
if (ret < 0)
return ret;
i++;
}
/* Initialization of Master runtime handle */
INIT_LIST_HEAD(&m_rt->port_list);
INIT_LIST_HEAD(&m_rt->slave_rt_list);
list_add_tail(&m_rt->stream_node, &stream->master_list);
return 0;
}
list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
static bool sdw_master_port_allocated(struct sdw_master_runtime *m_rt)
{
return !list_empty(&m_rt->port_list);
}
stream_config:
m_rt->ch_count = stream_config->ch_count;
m_rt->bus = bus;
m_rt->stream = stream;
m_rt->direction = stream_config->direction;
static void sdw_master_port_free(struct sdw_master_runtime *m_rt)
{
struct sdw_port_runtime *p_rt, *_p_rt;
return m_rt;
list_for_each_entry_safe(p_rt, _p_rt, &m_rt->port_list, port_node) {
sdw_port_free(p_rt);
}
}
static int sdw_master_port_alloc(struct sdw_master_runtime *m_rt,
unsigned int num_ports)
{
struct sdw_port_runtime *p_rt;
int i;
/* Iterate for number of ports to perform initialization */
for (i = 0; i < num_ports; i++) {
p_rt = sdw_port_alloc(&m_rt->port_list);
if (!p_rt)
return -ENOMEM;
}
return 0;
}
static int sdw_master_port_config(struct sdw_master_runtime *m_rt,
struct sdw_port_config *port_config)
{
struct sdw_port_runtime *p_rt;
int ret;
int i;
i = 0;
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
ret = sdw_port_config(p_rt, port_config, i);
if (ret < 0)
return ret;
i++;
}
return 0;
}
/**
* sdw_alloc_slave_rt() - Allocate and initialize Slave runtime handle.
* sdw_slave_rt_alloc() - Allocate a Slave runtime handle.
*
* @slave: Slave handle
* @stream_config: Stream configuration
* @stream: Stream runtime handle
* @m_rt: Master runtime handle
*
* This function is to be called with bus_lock held.
*/
static struct sdw_slave_runtime
*sdw_alloc_slave_rt(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
*sdw_slave_rt_alloc(struct sdw_slave *slave,
struct sdw_master_runtime *m_rt)
{
struct sdw_slave_runtime *s_rt;
......@@ -984,154 +1044,156 @@ static struct sdw_slave_runtime
return NULL;
INIT_LIST_HEAD(&s_rt->port_list);
s_rt->ch_count = stream_config->ch_count;
s_rt->direction = stream_config->direction;
s_rt->slave = slave;
list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
return s_rt;
}
static void sdw_master_port_release(struct sdw_bus *bus,
struct sdw_master_runtime *m_rt)
/**
* sdw_slave_rt_config() - Configure a Slave runtime handle.
*
* @s_rt: Slave runtime handle
* @stream_config: Stream configuration
*
* This function is to be called with bus_lock held.
*/
static int sdw_slave_rt_config(struct sdw_slave_runtime *s_rt,
struct sdw_stream_config *stream_config)
{
struct sdw_port_runtime *p_rt, *_p_rt;
s_rt->ch_count = stream_config->ch_count;
s_rt->direction = stream_config->direction;
list_for_each_entry_safe(p_rt, _p_rt, &m_rt->port_list, port_node) {
list_del(&p_rt->port_node);
kfree(p_rt);
}
return 0;
}
static void sdw_slave_port_release(struct sdw_bus *bus,
struct sdw_slave *slave,
static struct sdw_slave_runtime *sdw_slave_rt_find(struct sdw_slave *slave,
struct sdw_stream_runtime *stream)
{
struct sdw_port_runtime *p_rt, *_p_rt;
struct sdw_slave_runtime *s_rt, *_s_rt;
struct sdw_master_runtime *m_rt;
struct sdw_slave_runtime *s_rt;
list_for_each_entry(m_rt, &stream->master_list, stream_node) {
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
if (s_rt->slave != slave)
continue;
list_for_each_entry_safe(p_rt, _p_rt,
&s_rt->port_list, port_node) {
list_del(&p_rt->port_node);
kfree(p_rt);
}
/* Retrieve Slave runtime handle */
list_for_each_entry_safe(s_rt, _s_rt,
&m_rt->slave_rt_list, m_rt_node) {
if (s_rt->slave == slave)
return s_rt;
}
}
return NULL;
}
/**
* sdw_release_slave_stream() - Free Slave(s) runtime handle
* sdw_slave_rt_free() - Free Slave(s) runtime handle
*
* @slave: Slave handle.
* @stream: Stream runtime handle.
*
* This function is to be called with bus_lock held.
*/
static void sdw_release_slave_stream(struct sdw_slave *slave,
static void sdw_slave_rt_free(struct sdw_slave *slave,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt, *_s_rt;
struct sdw_master_runtime *m_rt;
struct sdw_slave_runtime *s_rt;
list_for_each_entry(m_rt, &stream->master_list, stream_node) {
/* Retrieve Slave runtime handle */
list_for_each_entry_safe(s_rt, _s_rt,
&m_rt->slave_rt_list, m_rt_node) {
if (s_rt->slave == slave) {
s_rt = sdw_slave_rt_find(slave, stream);
if (s_rt) {
list_del(&s_rt->m_rt_node);
kfree(s_rt);
return;
}
}
}
}
/**
* sdw_release_master_stream() - Free Master runtime handle
*
* @m_rt: Master runtime node
* @stream: Stream runtime handle.
*
* This function is to be called with bus_lock held
* It frees the Master runtime handle and associated Slave(s) runtime
* handle. If this is called first then sdw_release_slave_stream() will have
* no effect as Slave(s) runtime handle would already be freed up.
*/
static void sdw_release_master_stream(struct sdw_master_runtime *m_rt,
static struct sdw_master_runtime
*sdw_master_rt_find(struct sdw_bus *bus,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt, *_s_rt;
struct sdw_master_runtime *m_rt;
list_for_each_entry_safe(s_rt, _s_rt, &m_rt->slave_rt_list, m_rt_node) {
sdw_slave_port_release(s_rt->slave->bus, s_rt->slave, stream);
sdw_release_slave_stream(s_rt->slave, stream);
/* Retrieve Bus handle if already available */
list_for_each_entry(m_rt, &stream->master_list, stream_node) {
if (m_rt->bus == bus)
return m_rt;
}
list_del(&m_rt->stream_node);
list_del(&m_rt->bus_node);
kfree(m_rt);
return NULL;
}
/**
* sdw_stream_remove_master() - Remove master from sdw_stream
* sdw_master_rt_alloc() - Allocates a Master runtime handle
*
* @bus: SDW Bus instance
* @stream: SoundWire stream
* @bus: SDW bus instance
* @stream: Stream runtime handle.
*
* This removes and frees port_rt and master_rt from a stream
* This function is to be called with bus_lock held.
*/
int sdw_stream_remove_master(struct sdw_bus *bus,
static struct sdw_master_runtime
*sdw_master_rt_alloc(struct sdw_bus *bus,
struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt, *_m_rt;
struct sdw_master_runtime *m_rt;
mutex_lock(&bus->bus_lock);
m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
if (!m_rt)
return NULL;
list_for_each_entry_safe(m_rt, _m_rt,
&stream->master_list, stream_node) {
if (m_rt->bus != bus)
continue;
/* Initialization of Master runtime handle */
INIT_LIST_HEAD(&m_rt->port_list);
INIT_LIST_HEAD(&m_rt->slave_rt_list);
list_add_tail(&m_rt->stream_node, &stream->master_list);
sdw_master_port_release(bus, m_rt);
sdw_release_master_stream(m_rt, stream);
stream->m_rt_count--;
}
list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
if (list_empty(&stream->master_list))
stream->state = SDW_STREAM_RELEASED;
m_rt->bus = bus;
m_rt->stream = stream;
mutex_unlock(&bus->bus_lock);
return m_rt;
}
/**
* sdw_master_rt_config() - Configure Master runtime handle
*
* @m_rt: Master runtime handle
* @stream_config: Stream configuration
*
* This function is to be called with bus_lock held.
*/
static int sdw_master_rt_config(struct sdw_master_runtime *m_rt,
struct sdw_stream_config *stream_config)
{
m_rt->ch_count = stream_config->ch_count;
m_rt->direction = stream_config->direction;
return 0;
}
EXPORT_SYMBOL(sdw_stream_remove_master);
/**
* sdw_stream_remove_slave() - Remove slave from sdw_stream
* sdw_master_rt_free() - Free Master runtime handle
*
* @slave: SDW Slave instance
* @stream: SoundWire stream
* @m_rt: Master runtime node
* @stream: Stream runtime handle.
*
* This removes and frees port_rt and slave_rt from a stream
* This function is to be called with bus_lock held
* It frees the Master runtime handle and associated Slave(s) runtime
* handle. If this is called first then sdw_slave_rt_free() will have
* no effect as Slave(s) runtime handle would already be freed up.
*/
int sdw_stream_remove_slave(struct sdw_slave *slave,
static void sdw_master_rt_free(struct sdw_master_runtime *m_rt,
struct sdw_stream_runtime *stream)
{
mutex_lock(&slave->bus->bus_lock);
sdw_slave_port_release(slave->bus, slave, stream);
sdw_release_slave_stream(slave, stream);
struct sdw_slave_runtime *s_rt, *_s_rt;
mutex_unlock(&slave->bus->bus_lock);
list_for_each_entry_safe(s_rt, _s_rt, &m_rt->slave_rt_list, m_rt_node) {
sdw_slave_port_free(s_rt->slave, stream);
sdw_slave_rt_free(s_rt->slave, stream);
}
return 0;
list_del(&m_rt->stream_node);
list_del(&m_rt->bus_node);
kfree(m_rt);
}
EXPORT_SYMBOL(sdw_stream_remove_slave);
/**
* sdw_config_stream() - Configure the allocated stream
......@@ -1172,248 +1234,12 @@ static int sdw_config_stream(struct device *dev,
stream->params.rate = stream_config->frame_rate;
stream->params.bps = stream_config->bps;
/* TODO: Update this check during Device-device support */
if (is_slave)
stream->params.ch_count += stream_config->ch_count;
return 0;
}
static int sdw_is_valid_port_range(struct device *dev,
struct sdw_port_runtime *p_rt)
{
if (!SDW_VALID_PORT_RANGE(p_rt->num)) {
dev_err(dev,
"SoundWire: Invalid port number :%d\n", p_rt->num);
return -EINVAL;
}
return 0;
}
static struct sdw_port_runtime
*sdw_port_alloc(struct device *dev,
struct sdw_port_config *port_config,
int port_index)
{
struct sdw_port_runtime *p_rt;
p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL);
if (!p_rt)
return NULL;
p_rt->ch_mask = port_config[port_index].ch_mask;
p_rt->num = port_config[port_index].num;
return p_rt;
}
static int sdw_master_port_config(struct sdw_bus *bus,
struct sdw_master_runtime *m_rt,
struct sdw_port_config *port_config,
unsigned int num_ports)
{
struct sdw_port_runtime *p_rt;
int i;
/* Iterate for number of ports to perform initialization */
for (i = 0; i < num_ports; i++) {
p_rt = sdw_port_alloc(bus->dev, port_config, i);
if (!p_rt)
return -ENOMEM;
/*
* TODO: Check port capabilities for requested
* configuration (audio mode support)
*/
list_add_tail(&p_rt->port_node, &m_rt->port_list);
}
return 0;
}
static int sdw_slave_port_config(struct sdw_slave *slave,
struct sdw_slave_runtime *s_rt,
struct sdw_port_config *port_config,
unsigned int num_config)
{
struct sdw_port_runtime *p_rt;
int i, ret;
/* Iterate for number of ports to perform initialization */
for (i = 0; i < num_config; i++) {
p_rt = sdw_port_alloc(&slave->dev, port_config, i);
if (!p_rt)
return -ENOMEM;
/*
* TODO: Check valid port range as defined by DisCo/
* slave
*/
ret = sdw_is_valid_port_range(&slave->dev, p_rt);
if (ret < 0) {
kfree(p_rt);
return ret;
}
/*
* TODO: Check port capabilities for requested
* configuration (audio mode support)
*/
list_add_tail(&p_rt->port_node, &s_rt->port_list);
}
return 0;
}
/**
* sdw_stream_add_master() - Allocate and add master runtime to a stream
*
* @bus: SDW Bus instance
* @stream_config: Stream configuration for audio stream
* @port_config: Port configuration for audio stream
* @num_ports: Number of ports
* @stream: SoundWire stream
*/
int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt;
int ret;
mutex_lock(&bus->bus_lock);
/*
* For multi link streams, add the second master only if
* the bus supports it.
* Check if bus->multi_link is set
*/
if (!bus->multi_link && stream->m_rt_count > 0) {
dev_err(bus->dev,
"Multilink not supported, link %d\n", bus->link_id);
ret = -EINVAL;
goto unlock;
}
m_rt = sdw_alloc_master_rt(bus, stream_config, stream);
if (!m_rt) {
dev_err(bus->dev,
"Master runtime config failed for stream:%s\n",
stream->name);
ret = -ENOMEM;
goto unlock;
}
ret = sdw_config_stream(bus->dev, stream, stream_config, false);
if (ret)
goto stream_error;
ret = sdw_master_port_config(bus, m_rt, port_config, num_ports);
if (ret)
goto stream_error;
stream->m_rt_count++;
goto unlock;
stream_error:
sdw_release_master_stream(m_rt, stream);
unlock:
mutex_unlock(&bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_stream_add_master);
/**
* sdw_stream_add_slave() - Allocate and add master/slave runtime to a stream
*
* @slave: SDW Slave instance
* @stream_config: Stream configuration for audio stream
* @stream: SoundWire stream
* @port_config: Port configuration for audio stream
* @num_ports: Number of ports
*
* It is expected that Slave is added before adding Master
* to the Stream.
*
*/
int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt;
struct sdw_master_runtime *m_rt;
int ret;
mutex_lock(&slave->bus->bus_lock);
/*
* If this API is invoked by Slave first then m_rt is not valid.
* So, allocate m_rt and add Slave to it.
*/
m_rt = sdw_alloc_master_rt(slave->bus, stream_config, stream);
if (!m_rt) {
dev_err(&slave->dev,
"alloc master runtime failed for stream:%s\n",
stream->name);
ret = -ENOMEM;
goto error;
}
s_rt = sdw_alloc_slave_rt(slave, stream_config, stream);
if (!s_rt) {
dev_err(&slave->dev,
"Slave runtime config failed for stream:%s\n",
stream->name);
ret = -ENOMEM;
goto stream_error;
}
ret = sdw_config_stream(&slave->dev, stream, stream_config, true);
if (ret) {
/*
* sdw_release_master_stream will release s_rt in slave_rt_list in
* stream_error case, but s_rt is only added to slave_rt_list
* when sdw_config_stream is successful, so free s_rt explicitly
* when sdw_config_stream is failed.
*/
kfree(s_rt);
goto stream_error;
}
list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
ret = sdw_slave_port_config(slave, s_rt, port_config, num_ports);
if (ret)
goto stream_error;
/*
* Change stream state to CONFIGURED on first Slave add.
* Bus is not aware of number of Slave(s) in a stream at this
* point so cannot depend on all Slave(s) to be added in order to
* change stream state to CONFIGURED.
*/
stream->state = SDW_STREAM_CONFIGURED;
goto error;
stream_error:
/*
* we hit error so cleanup the stream, release all Slave(s) and
* Master runtime
*/
sdw_release_master_stream(m_rt, stream);
error:
mutex_unlock(&slave->bus->bus_lock);
return ret;
/* TODO: Update this check during Device-device support */
if (is_slave)
stream->params.ch_count += stream_config->ch_count;
return 0;
}
EXPORT_SYMBOL(sdw_stream_add_slave);
/**
* sdw_get_slave_dpn_prop() - Get Slave port capabilities
......@@ -1679,6 +1505,11 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
if (stream->state == SDW_STREAM_ENABLED) {
ret = 0;
goto state_err;
}
if (stream->state != SDW_STREAM_PREPARED &&
stream->state != SDW_STREAM_DISABLED) {
pr_err("%s: %s: inconsistent state state %d\n",
......@@ -1762,6 +1593,11 @@ int sdw_disable_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
if (stream->state == SDW_STREAM_DISABLED) {
ret = 0;
goto state_err;
}
if (stream->state != SDW_STREAM_ENABLED) {
pr_err("%s: %s: inconsistent state state %d\n",
__func__, stream->name, stream->state);
......@@ -1837,6 +1673,11 @@ int sdw_deprepare_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
if (stream->state == SDW_STREAM_DEPREPARED) {
ret = 0;
goto state_err;
}
if (stream->state != SDW_STREAM_PREPARED &&
stream->state != SDW_STREAM_DISABLED) {
pr_err("%s: %s: inconsistent state state %d\n",
......@@ -1873,6 +1714,32 @@ static int set_stream(struct snd_pcm_substream *substream,
return ret;
}
/**
* sdw_alloc_stream() - Allocate and return stream runtime
*
* @stream_name: SoundWire stream name
*
* Allocates a SoundWire stream runtime instance.
* sdw_alloc_stream should be called only once per stream. Typically
* invoked from ALSA/ASoC machine/platform driver.
*/
struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name)
{
struct sdw_stream_runtime *stream;
stream = kzalloc(sizeof(*stream), GFP_KERNEL);
if (!stream)
return NULL;
stream->name = stream_name;
INIT_LIST_HEAD(&stream->master_list);
stream->state = SDW_STREAM_ALLOCATED;
stream->m_rt_count = 0;
return stream;
}
EXPORT_SYMBOL(sdw_alloc_stream);
/**
* sdw_startup_stream() - Startup SoundWire stream
*
......@@ -1949,3 +1816,270 @@ void sdw_shutdown_stream(void *sdw_substream)
set_stream(substream, NULL);
}
EXPORT_SYMBOL(sdw_shutdown_stream);
/**
* sdw_release_stream() - Free the assigned stream runtime
*
* @stream: SoundWire stream runtime
*
* sdw_release_stream should be called only once per stream
*/
void sdw_release_stream(struct sdw_stream_runtime *stream)
{
kfree(stream);
}
EXPORT_SYMBOL(sdw_release_stream);
/**
* sdw_stream_add_master() - Allocate and add master runtime to a stream
*
* @bus: SDW Bus instance
* @stream_config: Stream configuration for audio stream
* @port_config: Port configuration for audio stream
* @num_ports: Number of ports
* @stream: SoundWire stream
*/
int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt;
bool alloc_master_rt = true;
int ret;
mutex_lock(&bus->bus_lock);
/*
* For multi link streams, add the second master only if
* the bus supports it.
* Check if bus->multi_link is set
*/
if (!bus->multi_link && stream->m_rt_count > 0) {
dev_err(bus->dev,
"Multilink not supported, link %d\n", bus->link_id);
ret = -EINVAL;
goto unlock;
}
/*
* check if Master is already allocated (e.g. as a result of Slave adding
* it first), if so skip allocation and go to configuration
*/
m_rt = sdw_master_rt_find(bus, stream);
if (m_rt) {
alloc_master_rt = false;
goto skip_alloc_master_rt;
}
m_rt = sdw_master_rt_alloc(bus, stream);
if (!m_rt) {
dev_err(bus->dev, "Master runtime alloc failed for stream:%s\n", stream->name);
ret = -ENOMEM;
goto unlock;
}
skip_alloc_master_rt:
if (sdw_master_port_allocated(m_rt))
goto skip_alloc_master_port;
ret = sdw_master_port_alloc(m_rt, num_ports);
if (ret)
goto alloc_error;
stream->m_rt_count++;
skip_alloc_master_port:
ret = sdw_master_rt_config(m_rt, stream_config);
if (ret < 0)
goto unlock;
ret = sdw_config_stream(bus->dev, stream, stream_config, false);
if (ret)
goto unlock;
ret = sdw_master_port_config(m_rt, port_config);
goto unlock;
alloc_error:
/*
* we only cleanup what was allocated in this routine
*/
if (alloc_master_rt)
sdw_master_rt_free(m_rt, stream);
unlock:
mutex_unlock(&bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_stream_add_master);
/**
* sdw_stream_remove_master() - Remove master from sdw_stream
*
* @bus: SDW Bus instance
* @stream: SoundWire stream
*
* This removes and frees port_rt and master_rt from a stream
*/
int sdw_stream_remove_master(struct sdw_bus *bus,
struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt, *_m_rt;
mutex_lock(&bus->bus_lock);
list_for_each_entry_safe(m_rt, _m_rt,
&stream->master_list, stream_node) {
if (m_rt->bus != bus)
continue;
sdw_master_port_free(m_rt);
sdw_master_rt_free(m_rt, stream);
stream->m_rt_count--;
}
if (list_empty(&stream->master_list))
stream->state = SDW_STREAM_RELEASED;
mutex_unlock(&bus->bus_lock);
return 0;
}
EXPORT_SYMBOL(sdw_stream_remove_master);
/**
* sdw_stream_add_slave() - Allocate and add master/slave runtime to a stream
*
* @slave: SDW Slave instance
* @stream_config: Stream configuration for audio stream
* @stream: SoundWire stream
* @port_config: Port configuration for audio stream
* @num_ports: Number of ports
*
* It is expected that Slave is added before adding Master
* to the Stream.
*
*/
int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt;
struct sdw_master_runtime *m_rt;
bool alloc_master_rt = true;
bool alloc_slave_rt = true;
int ret;
mutex_lock(&slave->bus->bus_lock);
/*
* check if Master is already allocated, if so skip allocation
* and go to configuration
*/
m_rt = sdw_master_rt_find(slave->bus, stream);
if (m_rt) {
alloc_master_rt = false;
goto skip_alloc_master_rt;
}
/*
* If this API is invoked by Slave first then m_rt is not valid.
* So, allocate m_rt and add Slave to it.
*/
m_rt = sdw_master_rt_alloc(slave->bus, stream);
if (!m_rt) {
dev_err(&slave->dev, "Master runtime alloc failed for stream:%s\n", stream->name);
ret = -ENOMEM;
goto unlock;
}
skip_alloc_master_rt:
s_rt = sdw_slave_rt_find(slave, stream);
if (s_rt)
goto skip_alloc_slave_rt;
s_rt = sdw_slave_rt_alloc(slave, m_rt);
if (!s_rt) {
dev_err(&slave->dev, "Slave runtime alloc failed for stream:%s\n", stream->name);
alloc_slave_rt = false;
ret = -ENOMEM;
goto alloc_error;
}
skip_alloc_slave_rt:
if (sdw_slave_port_allocated(s_rt))
goto skip_port_alloc;
ret = sdw_slave_port_alloc(slave, s_rt, num_ports);
if (ret)
goto alloc_error;
skip_port_alloc:
ret = sdw_master_rt_config(m_rt, stream_config);
if (ret)
goto unlock;
ret = sdw_slave_rt_config(s_rt, stream_config);
if (ret)
goto unlock;
ret = sdw_config_stream(&slave->dev, stream, stream_config, true);
if (ret)
goto unlock;
ret = sdw_slave_port_config(slave, s_rt, port_config);
if (ret)
goto unlock;
/*
* Change stream state to CONFIGURED on first Slave add.
* Bus is not aware of number of Slave(s) in a stream at this
* point so cannot depend on all Slave(s) to be added in order to
* change stream state to CONFIGURED.
*/
stream->state = SDW_STREAM_CONFIGURED;
goto unlock;
alloc_error:
/*
* we only cleanup what was allocated in this routine. The 'else if'
* is intentional, the 'master_rt_free' will call sdw_slave_rt_free()
* internally.
*/
if (alloc_master_rt)
sdw_master_rt_free(m_rt, stream);
else if (alloc_slave_rt)
sdw_slave_rt_free(slave, stream);
unlock:
mutex_unlock(&slave->bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_stream_add_slave);
/**
* sdw_stream_remove_slave() - Remove slave from sdw_stream
*
* @slave: SDW Slave instance
* @stream: SoundWire stream
*
* This removes and frees port_rt and slave_rt from a stream
*/
int sdw_stream_remove_slave(struct sdw_slave *slave,
struct sdw_stream_runtime *stream)
{
mutex_lock(&slave->bus->bus_lock);
sdw_slave_port_free(slave, stream);
sdw_slave_rt_free(slave, stream);
mutex_unlock(&slave->bus->bus_lock);
return 0;
}
EXPORT_SYMBOL(sdw_stream_remove_slave);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment