[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1477053673-16021-14-git-send-email-hardik.t.shah@intel.com>
Date: Fri, 21 Oct 2016 18:11:11 +0530
From: Hardik Shah <hardik.t.shah@...el.com>
To: alsa-devel@...a-project.org, linux-kernel@...r.kernel.org
Cc: tiwai@...e.de, pierre-louis.bossart@...ux.intel.com,
broonie@...nel.org, lgirdwood@...il.com, plai@...eaurora.org,
patches.audio@...el.com, Sanyog Kale <sanyog.r.kale@...el.com>,
Hardik Shah <hardik.t.shah@...el.com>
Subject: [RFC 13/14] SoundWire: Add stream and port configuration
From: Sanyog Kale <sanyog.r.kale@...el.com>
This patch adds APIs for stream and port configurations for SoundWire
bus driver.
Signed-off-by: Hardik Shah <hardik.t.shah@...el.com>
Signed-off-by: Sanyog Kale <sanyog.r.kale@...el.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@...ux.intel.com>
---
sound/sdw/sdw.c | 774 ++++++++++++++++++++++++++++++++++++++++++++++++++
sound/sdw/sdw_priv.h | 449 +++++++++++++++++++++++++++++
2 files changed, 1223 insertions(+)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
index 71d2550..ffbec9e 100644
--- a/sound/sdw/sdw.c
+++ b/sound/sdw/sdw.c
@@ -2358,6 +2358,780 @@ static enum sdw_clk_stop_mode sdw_slv_get_clk_stp_mode(struct sdw_slave *slave)
}
/**
+ * snd_sdw_release_stream_tag: Free the already assigned stream tag.
+ * Reverses effect of "sdw_alloc_stream_tag"
+ *
+ * @stream_tag: Stream tag to be freed.
+ */
+void snd_sdw_release_stream_tag(unsigned int stream_tag)
+{
+ int i;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+
+ /* Acquire core lock */
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Get stream tag data structure */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tag == stream_tags[i].stream_tag) {
+
+ /* Reference count update */
+ sdw_dec_ref_count(&stream_tags[i].ref_count);
+
+ if (stream_tags[i].ref_count == 0)
+ /* Free up resources */
+ kfree(stream_tags[i].sdw_rt);
+ }
+ }
+
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_sdw_release_stream_tag);
+
+/**
+ * snd_sdw_alloc_stream_tag: Allocates unique stream_tag. Stream tag is
+ * a unique identifier for each SoundWire stream across all SoundWire
+ * bus instances. Stream tag is a software concept defined by bus
+ * driver for stream management and not by MIPI SoundWire Spec. Each
+ * SoundWire Stream is individually configured and controlled using the
+ * stream tag. Multiple Master(s) and Slave(s) associated with the
+ * stream, uses stream tag as an identifier. All the operations on the
+ * stream e.g. stream configuration, port configuration, prepare and
+ * enable of the ports are done based on stream tag. This API shall be
+ * called once per SoundWire stream either by the Master or Slave
+ * associated with the stream.
+ *
+ * @stream_tag: Stream tag returned by bus driver.
+ */
+int snd_sdw_alloc_stream_tag(unsigned int *stream_tag)
+{
+ int i;
+ int ret = -EINVAL;
+ struct sdw_runtime *sdw_rt;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+
+ /* Acquire core lock */
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Allocate new stream tag and initialize resources */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (!stream_tags[i].ref_count) {
+
+ *stream_tag = stream_tags[i].stream_tag;
+
+ /* Initialize stream lock */
+ mutex_init(&stream_tags[i].stream_lock);
+
+ /* Allocate resources for stream runtime handle */
+ sdw_rt = kzalloc(sizeof(*sdw_rt), GFP_KERNEL);
+ if (!sdw_rt) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* Reference count update */
+ sdw_inc_ref_count(&stream_tags[i].ref_count);
+
+ /* Initialize Master and Slave list */
+ INIT_LIST_HEAD(&sdw_rt->slv_rt_list);
+ INIT_LIST_HEAD(&sdw_rt->mstr_rt_list);
+
+ /* Change stream state to ALLOC */
+ sdw_rt->stream_state = SDW_STATE_STRM_ALLOC;
+
+ stream_tags[i].sdw_rt = sdw_rt;
+
+ ret = 0;
+ break;
+ }
+ }
+out:
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_alloc_stream_tag);
+
+/**
+ * sdw_config_mstr_stream: Checks if master runtime handle already
+ * available, if not allocates and initialize Master runtime handle.
+ *
+ * @mstr: Master handle
+ * @stream_config: Stream configuration for the SoundWire audio stream.
+ * @sdw_rt: Stream runtime handle.
+ *
+ * Returns Master runtime handle.
+ */
+static struct sdw_mstr_runtime *sdw_config_mstr_stream(struct sdw_master *mstr,
+ struct sdw_stream_config *stream_config,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_mstr_runtime *mstr_rt = NULL;
+ struct sdw_stream_params *str_p;
+
+ /* Retrieve Master handle if already available */
+ list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) {
+ if (mstr_rt->mstr == mstr)
+ return mstr_rt;
+ }
+
+ /* Allocate resources for Master runtime handle */
+ mstr_rt = kzalloc(sizeof(*mstr_rt), GFP_KERNEL);
+ if (!mstr_rt)
+ goto out;
+
+ /* Initialization of Master runtime handle */
+ INIT_LIST_HEAD(&mstr_rt->port_rt_list);
+ INIT_LIST_HEAD(&mstr_rt->slv_rt_list);
+ list_add_tail(&mstr_rt->mstr_strm_node, &sdw_rt->mstr_rt_list);
+ list_add_tail(&mstr_rt->mstr_node, &mstr->mstr_rt_list);
+
+ /* Update PCM parameters for Master */
+ mstr_rt->direction = stream_config->direction;
+ str_p = &mstr_rt->stream_params;
+ str_p->rate = stream_config->frame_rate;
+ str_p->channel_count = stream_config->channel_count;
+ str_p->bps = stream_config->bps;
+
+ /* Add reference for Master device handle */
+ mstr_rt->mstr = mstr;
+
+ /* Add reference for stream runtime handle */
+ mstr_rt->sdw_rt = sdw_rt;
+
+out:
+ return mstr_rt;
+}
+
+/**
+ * sdw_config_slave_stream: Allocate and initialize slave runtime handle.
+ *
+ * @slave: Slave handle
+ * @stream_config: Stream configuration for the SoundWire audio stream.
+ * @sdw_rt: Stream runtime handle.
+ *
+ * Returns Slave runtime handle.
+ */
+static struct sdw_slv_runtime *sdw_config_slv_stream(
+ struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_stream_params *str_p;
+
+ /* Allocate resources for Slave runtime handle */
+ slv_rt = kzalloc(sizeof(*slv_rt), GFP_KERNEL);
+ if (!slv_rt)
+ goto out;
+
+ /* Initialization of Slave runtime handle */
+ INIT_LIST_HEAD(&slv_rt->port_rt_list);
+
+ /* Update PCM parameters for Slave */
+ slv_rt->direction = stream_config->direction;
+ str_p = &slv_rt->stream_params;
+ str_p->rate = stream_config->frame_rate;
+ str_p->channel_count = stream_config->channel_count;
+ str_p->bps = stream_config->bps;
+
+ /* Add reference for Slave device handle */
+ slv_rt->slv = slave;
+
+ /* Add reference for stream runtime handle */
+ slv_rt->sdw_rt = sdw_rt;
+
+out:
+ return slv_rt;
+}
+
+/**
+ * sdw_release_mstr_stream: Removes entry from master runtime list and free
+ * up resources.
+ *
+ * @mstr: Master handle.
+ * @sdw_rt: Master runtime handle.
+ */
+static void sdw_release_mstr_stream(struct sdw_master *mstr,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_mstr_runtime *mstr_rt, *__mstr_rt;
+
+ /* Retrieve Master runtime handle */
+ list_for_each_entry_safe(mstr_rt, __mstr_rt, &sdw_rt->mstr_rt_list,
+ mstr_strm_node) {
+
+ if (mstr_rt->mstr == mstr) {
+
+ if (mstr_rt->direction == SDW_DATA_DIR_OUT)
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->tx_ref_count);
+ else
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->rx_ref_count);
+
+ /* Remove node from the list */
+ list_del(&mstr_rt->mstr_strm_node);
+ list_del(&mstr_rt->mstr_node);
+
+ pm_runtime_mark_last_busy(&mstr->dev);
+ pm_runtime_put_sync_autosuspend(&mstr->dev);
+
+ /* Free up Master runtime handle resources */
+ kfree(mstr_rt);
+ }
+ }
+}
+
+/**
+ * sdw_release_slv_stream: Removes entry from slave runtime list and free up
+ * resources.
+ *
+ * @slave: Slave handle.
+ * @sdw_rt: Stream runtime handle.
+ */
+static void sdw_release_slv_stream(struct sdw_slave *slave,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_slv_runtime *slv_rt, *__slv_rt;
+
+ /* Retrieve Slave runtime handle */
+ list_for_each_entry_safe(slv_rt, __slv_rt, &sdw_rt->slv_rt_list,
+ slave_strm_node) {
+
+ if (slv_rt->slv == slave) {
+
+ if (slv_rt->direction == SDW_DATA_DIR_OUT)
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->tx_ref_count);
+ else
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->rx_ref_count);
+
+ /* Remove node from the list */
+ list_del(&slv_rt->slave_strm_node);
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put_sync_autosuspend(&slave->dev);
+
+ /* Free up Slave runtime handle resources */
+ kfree(slv_rt);
+ }
+ }
+}
+
+/**
+ * snd_sdw_release_stream: De-associates Master(s) and Slave(s) from stream.
+ * Reverse effect of the sdw_config_stream. Master calls this with
+ * Slave handle as NULL, Slave calls this with Master handle as NULL.
+ *
+ * @mstr: Master handle,
+ * @slave: SoundWire Slave handle, Null if stream configuration is called by
+ * Master driver.
+ *
+ * @stream_tag: Stream_tag representing the audio stream. All Masters and
+ * Slaves part of the same stream has same stream tag. So Bus driver
+ * holds information of all Masters and Slaves associated with stream
+ * tag.
+ */
+int snd_sdw_release_stream(struct sdw_master *mstr,
+ struct sdw_slave *slave,
+ unsigned int stream_tag)
+{
+ int i;
+ struct sdw_runtime *sdw_rt = NULL;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+
+ /* Retrieve master handle if called by Slave */
+ if (!mstr)
+ mstr = slave->mstr;
+
+ /* Retrieve stream runtime handle */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tags[i].stream_tag == stream_tag) {
+ sdw_rt = stream_tags[i].sdw_rt;
+ break;
+ }
+ }
+
+ if (!sdw_rt) {
+ dev_err(&mstr->dev, "Invalid stream tag\n");
+ return -EINVAL;
+ }
+
+ /* Call release API of Master/Slave */
+ if (!slave)
+ sdw_release_mstr_stream(mstr, sdw_rt);
+ else
+ sdw_release_slv_stream(slave, sdw_rt);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_release_stream);
+
+/**
+ * snd_sdw_config_stream: Configures the SoundWire stream. All the Master(s)
+ * and Slave(s) associated with the stream calls this API with
+ * "sdw_stream_config". This API configures SoundWire stream based on
+ * "sdw_stream_config" provided by each Master(s) and Slave(s)
+ * associated with the stream. Master calls this function with Slave
+ * handle as NULL, Slave calls this with Master handle as NULL.
+ *
+ * @mstr: Master handle.
+ * @slave: SoundWire Slave handle, Null if stream configuration is called by
+ * Master driver.
+ *
+ * @stream_config: Stream configuration for the SoundWire audio stream.
+ * @stream_tag: Stream_tag representing the audio stream. All Masters and
+ * Slaves part of the same stream has same stream tag. So Bus driver
+ * holds information of all Masters and Slaves associated with stream
+ * tag.
+ */
+int snd_sdw_config_stream(struct sdw_master *mstr,
+ struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ unsigned int stream_tag)
+{
+ int i;
+ int ret = 0;
+ struct sdw_runtime *sdw_rt = NULL;
+ struct sdw_mstr_runtime *mstr_rt = NULL;
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+ struct sdw_stream_tag *stream = NULL;
+
+ /* Retrieve master handle if called by Slave */
+ if (!mstr)
+ mstr = slave->mstr;
+
+ /* Retrieve stream runtime handle */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tags[i].stream_tag == stream_tag) {
+ sdw_rt = stream_tags[i].sdw_rt;
+ stream = &stream_tags[i];
+ break;
+ }
+ }
+
+ if (!sdw_rt) {
+ dev_err(&mstr->dev, "Valid stream tag not found\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Acquire stream lock */
+ mutex_lock(&stream->stream_lock);
+
+ /* Get and Initialize Master runtime handle */
+ mstr_rt = sdw_config_mstr_stream(mstr, stream_config, sdw_rt);
+ if (!mstr_rt) {
+ dev_err(&mstr->dev, "Master runtime configuration failed\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Initialize Slave runtime handle */
+ if (slave) {
+ slv_rt = sdw_config_slv_stream(slave, stream_config, sdw_rt);
+ if (!slv_rt) {
+ dev_err(&mstr->dev, "Slave runtime configuration failed\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ }
+
+ /*
+ * Stream params will be stored based on Tx only, since there can be
+ * only one Tx and multiple Rx, There can be multiple Tx if there is
+ * aggregation on Tx. That is handled by adding the channels to
+ * stream_params for each aggregated Tx slaves
+ */
+ if (!sdw_rt->tx_ref_count && stream_config->direction ==
+ SDW_DATA_DIR_OUT) {
+ sdw_rt->stream_params.rate = stream_config->frame_rate;
+ sdw_rt->stream_params.channel_count =
+ stream_config->channel_count;
+ sdw_rt->stream_params.bps = stream_config->bps;
+ /* Reference count update */
+ sdw_inc_ref_count(&sdw_rt->tx_ref_count);
+ }
+
+ /*
+ * Normally there will be only one Tx in system, multiple Tx can
+ * only be there if we support aggregation. In that case there may
+ * be multiple slave or masters handing different channels of same
+ * Tx stream.
+ */
+ else if (sdw_rt->tx_ref_count && stream_config->direction ==
+ SDW_DATA_DIR_OUT) {
+ if (sdw_rt->stream_params.rate !=
+ stream_config->frame_rate) {
+ dev_err(&mstr->dev, "Frame rate for aggregated devices not matching\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (sdw_rt->stream_params.bps != stream_config->bps) {
+ dev_err(&mstr->dev, "bps for aggregated devices not matching\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /*
+ * Number of channels gets added, since both devices will be
+ * supporting different channels. Like one Codec supporting
+ * L and other supporting R channel.
+ */
+ sdw_rt->stream_params.channel_count +=
+ stream_config->channel_count;
+
+ /* Reference count update */
+ sdw_inc_ref_count(&sdw_rt->tx_ref_count);
+ } else
+ /* Reference count update */
+ sdw_inc_ref_count(&sdw_rt->rx_ref_count);
+
+ sdw_rt->type = stream_config->type;
+
+ /* Change stream state to CONFIG */
+ sdw_rt->stream_state = SDW_STATE_STRM_CONFIG;
+
+ /*
+ * Slaves are added to two list, This is because bandwidth is
+ * calculated for two masters individually, while Ports are enabled
+ * of all the aggregated masters and slaves part of the same stream
+ * tag simultaneously.
+ */
+ if (slave) {
+ list_add_tail(&slv_rt->slave_strm_node, &sdw_rt->slv_rt_list);
+ list_add_tail(&slv_rt->slave_mstr_node, &mstr_rt->slv_rt_list);
+ }
+
+ /* Release stream lock */
+ mutex_unlock(&stream->stream_lock);
+
+ if (slave)
+ pm_runtime_get_sync(&slave->dev);
+ else
+ pm_runtime_get_sync(&mstr->dev);
+
+ return ret;
+
+error:
+ mutex_unlock(&stream->stream_lock);
+ kfree(mstr_rt);
+ kfree(slv_rt);
+out:
+ return ret;
+
+}
+EXPORT_SYMBOL_GPL(snd_sdw_config_stream);
+
+/**
+ * sdw_check_dpn_caps: Check Master and Slave port capabilities. This performs
+ * PCM parameter check based on PCM
+ * parameters received in stream.
+ * @dpn_cap: Capabilities of Master or Slave port.
+ * @strm_params: Stream PCM parameters.
+ */
+static int sdw_check_dpn_caps(struct sdw_dpn_caps *dpn_cap,
+ struct sdw_stream_params *strm_prms)
+{
+ struct sdw_port_aud_mode_prop *mode_prop =
+ dpn_cap->mode_properties;
+ int i, value;
+
+ /* Check for sampling frequency */
+ if (mode_prop->num_sample_rate_cfgs) {
+ for (i = 0; i < mode_prop->num_sample_rate_cfgs; i++) {
+ value = mode_prop->sample_rate_buf[i];
+ if (strm_prms->rate == value)
+ break;
+ }
+
+ if (i == mode_prop->num_sample_rate_cfgs)
+ return -EINVAL;
+ } else {
+
+ if ((strm_prms->rate < mode_prop->min_sample_rate)
+ || (strm_prms->rate >
+ mode_prop->max_sample_rate)) {
+ return -EINVAL;
+ }
+ }
+
+ /* Check for bit rate */
+ if (dpn_cap->num_bps) {
+ for (i = 0; i < dpn_cap->num_bps; i++) {
+ value = dpn_cap->bps_buf[i];
+ if (strm_prms->bps == value)
+ break;
+ }
+
+ if (i == dpn_cap->num_bps)
+ return -EINVAL;
+
+ } else {
+
+ if ((strm_prms->bps < dpn_cap->min_bps)
+ || (strm_prms->bps > dpn_cap->max_bps))
+ return -EINVAL;
+ }
+
+ /* Check for number of channels */
+ if (dpn_cap->num_ch_cnt) {
+ for (i = 0; i < dpn_cap->num_ch_cnt; i++) {
+ value = dpn_cap->ch_cnt_buf[i];
+ if (strm_prms->bps == value)
+ break;
+ }
+
+ if (i == dpn_cap->num_ch_cnt)
+ return -EINVAL;
+
+ } else {
+
+ if ((strm_prms->channel_count < dpn_cap->min_ch_cnt) ||
+ (strm_prms->channel_count > dpn_cap->max_ch_cnt))
+ return -EINVAL;
+
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_mstr_port_configuration: Master Port configuration. This performs
+ * all the port related configuration including allocation port
+ * structure memory, assign PCM parameters and add port node in master
+ * runtime list.
+ *
+ * @mstr: Master handle.
+ * @sdw_rt: Stream runtime information.
+ * @ports_config: Port configuration for Slave.
+ */
+static int sdw_mstr_port_configuration(struct sdw_master *mstr,
+ struct sdw_runtime *sdw_rt,
+ struct sdw_ports_config *ports_config)
+{
+ struct sdw_mstr_runtime *mstr_rt = NULL;
+ struct sdw_port_runtime *port_rt;
+ int found = 0;
+ int i;
+ int ret = 0, pn = 0;
+ struct sdw_dpn_caps *dpn_cap = mstr->caps.sdw_dpn_caps;
+
+ /* Get Master device handle */
+ list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) {
+ if (mstr_rt->mstr == mstr) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_err(&mstr->dev, "Master not found for this port\n");
+ return -EINVAL;
+ }
+
+ /* Allocate resources for port runtime handle */
+ port_rt = kzalloc((sizeof(*port_rt) * ports_config->num_ports),
+ GFP_KERNEL);
+ if (!port_rt)
+ return -ENOMEM;
+
+ /* Check master capabilities */
+ if (!dpn_cap)
+ return -EINVAL;
+
+ /* Iterate for number of ports to perform initialization */
+ for (i = 0; i < ports_config->num_ports; i++) {
+ port_rt[i].channel_mask = ports_config->port_config[i].ch_mask;
+ port_rt[i].port_num = pn = ports_config->port_config[i].num;
+
+ /* Perform capability check for master port */
+ ret = sdw_check_dpn_caps(&dpn_cap[pn], &mstr_rt->stream_params);
+ if (ret < 0) {
+ dev_err(&mstr->dev, "Master capabilities check failed ret = %d\n", ret);
+ goto error;
+ }
+
+ /* Add node to port runtime list */
+ list_add_tail(&port_rt[i].port_node, &mstr_rt->port_rt_list);
+ }
+
+ return ret;
+
+error:
+ kfree(port_rt);
+ return ret;
+}
+
+struct sdw_dpn_caps *sdw_get_slv_dpn_cap(struct sdw_slave_caps *slv_cap,
+ enum sdw_data_direction direction,
+ unsigned int port_num)
+{
+ int i;
+ struct sdw_dpn_caps *dpn_cap;
+ u8 num_ports;
+ bool port_found = 0;
+
+ if (direction == SDW_DATA_DIR_OUT)
+ num_ports = slv_cap->num_src_ports;
+ else
+ num_ports = slv_cap->num_sink_ports;
+
+ for (i = 0; i < num_ports; i++) {
+ dpn_cap = &slv_cap->dpn_caps[direction][i];
+
+ if (dpn_cap->port_number == port_num) {
+ port_found = 1;
+ break;
+ }
+ }
+
+ if (!port_found)
+ return NULL;
+
+ return dpn_cap;
+
+}
+
+/**
+ * sdw_config_slv_port: Slave Port configuration. This performs
+ * all the port related configuration including allocation port
+ * structure memory, assign PCM parameters and add port node in slave
+ * runtime list.
+ * @slave: Slave handle.
+ * @sdw_rt: Stream runtime information.
+ * @ports_config: Port configuration for Slave.
+ */
+static int sdw_config_slv_port(struct sdw_slave *slave,
+ struct sdw_runtime *sdw_rt,
+ struct sdw_ports_config *ports_config)
+{
+ struct sdw_slv_runtime *slv_rt;
+ struct sdw_port_runtime *port_rt;
+ struct sdw_dpn_caps *dpn_cap;
+ int found = 0, ret = 0;
+ int i, pn;
+
+ /* Get Slave device handle */
+ list_for_each_entry(slv_rt, &sdw_rt->slv_rt_list, slave_strm_node) {
+ if (slv_rt->slv == slave) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_err(&slave->mstr->dev, "Slave not found for this port\n");
+ return -EINVAL;
+ }
+
+ /* Check whether slave capabilities are valid or invalid */
+ if (!slave->priv.slave_cap_updated) {
+ dev_err(&slave->mstr->dev, "Slave capabilities not updated\n");
+ return -EINVAL;
+ }
+
+ /* Allocate resources for port runtime handle */
+ port_rt = kzalloc((sizeof(*port_rt) * ports_config->num_ports),
+ GFP_KERNEL);
+ if (!port_rt)
+ return -ENOMEM;
+
+ /* Assign PCM parameters */
+ for (i = 0; i < ports_config->num_ports; i++) {
+ port_rt[i].channel_mask = ports_config->port_config[i].ch_mask;
+ port_rt[i].port_num = pn =
+ ports_config->port_config[i].num;
+
+ dpn_cap = sdw_get_slv_dpn_cap(&slave->priv.caps,
+ slv_rt->direction,
+ ports_config->port_config[i].num);
+ if (!dpn_cap) {
+ ret = -EINVAL;
+ dev_err(&slave->mstr->dev, "Slave port capabilities not found ret = %d\n", ret);
+ goto error;
+ }
+
+ /* Perform capability check for slave port */
+ ret = sdw_check_dpn_caps(dpn_cap, &slv_rt->stream_params);
+ if (ret < 0) {
+ dev_err(&slave->mstr->dev, "Slave capabilities check failed ret = %d\n", ret);
+ goto error;
+ }
+
+ /* Add node to port runtime list */
+ list_add_tail(&port_rt[i].port_node, &slv_rt->port_rt_list);
+ }
+
+ return ret;
+
+error:
+ kfree(port_rt);
+ return ret;
+}
+
+/**
+ * snd_sdw_config_ports: Configures Master or Slave Port(s) associated with
+ * the stream. All the Master(s) and Slave(s) associated with the
+ * stream calls this API with "sdw_ports_config". Master calls this
+ * function with Slave handle as NULL, Slave calls this with Master
+ * handle as NULL.
+ *
+ * @mstr: Master handle where the Slave is connected.
+ * @slave: Slave handle.
+ * @ports_config: Port configuration for each Port of SoundWire Slave.
+ * @stream_tag: Stream tag, where this Port is connected.
+ */
+int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave,
+ struct sdw_ports_config *ports_config,
+ unsigned int stream_tag)
+{
+ int ret;
+ int i;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+ struct sdw_runtime *sdw_rt = NULL;
+ struct sdw_stream_tag *stream = NULL;
+
+ /* Retrieve master handle if called by Slave */
+ if (!mstr)
+ mstr = slave->mstr;
+
+ /* Retrieve stream runtime handle */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tags[i].stream_tag == stream_tag) {
+ sdw_rt = stream_tags[i].sdw_rt;
+ stream = &stream_tags[i];
+ break;
+ }
+ }
+
+ if (!sdw_rt) {
+ dev_err(&mstr->dev, "Invalid stream tag\n");
+ return -EINVAL;
+ }
+
+ /* Acquire stream lock */
+ mutex_lock(&stream->stream_lock);
+
+ /* Perform Master/Slave port configuration */
+ if (!slave)
+ ret = sdw_mstr_port_configuration(mstr, sdw_rt, ports_config);
+ else
+ ret = sdw_config_slv_port(slave, sdw_rt, ports_config);
+
+ /* Release stream lock */
+ mutex_unlock(&stream->stream_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_config_ports);
+
+/**
* snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the
* SCP_CTRL register with clock_stop_now bit set.
*
diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h
index bbba27a..fc738b8 100644
--- a/sound/sdw/sdw_priv.h
+++ b/sound/sdw/sdw_priv.h
@@ -68,6 +68,31 @@
#define SDW_NUM_OF_MSG3_XFRD 3
#define SDW_NUM_OF_MSG4_XFRD 4
+/**
+ * Below values are not defined in MIPI standard. Completely arbitrary
+ * values that can be changed at will.
+ */
+#define SDW_MAX_STREAM_TAG_KEY_SIZE 80
+#define SDW_NUM_STREAM_TAGS 100 /* Max number of stream tags */
+#define SDW_DOUBLE_RATE_FACTOR 2 /* Double rate */
+
+/* TODO: Description to be provided for SDW_FREQ_MOD_FACTOR */
+#define SDW_FREQ_MOD_FACTOR 3000
+
+/**
+ * SDW_STRM_RATE_GROUPING is place holder number used to hold the frame rate
+ * used in grouping stream for efficiently calculating bandwidth. All the
+ * streams with same frame rates belong to same group. This number is
+ * dynamically increased if the group count number increases above 12.
+ */
+#define SDW_STRM_RATE_GROUPING 12
+
+/* Size of buffer in bytes. */
+#define SDW_BUF_SIZE1 1
+#define SDW_BUF_SIZE2 2
+#define SDW_BUF_SIZE3 3
+#define SDW_BUF_SIZE4 4
+
/* Maximum number of Data Ports. */
#define SDW_MAX_DATA_PORTS 15
@@ -80,6 +105,8 @@
*/
#define SDW_INTR_STAT_READ_MAX_TRIES 10
+extern struct snd_sdw_core snd_sdw_core;
+
/**
* sdw_driver: Structure to typecast both Master and Slave driver to generic
* SoundWire driver, to find out the driver type.
@@ -95,6 +122,278 @@ struct sdw_driver {
container_of(d, struct sdw_driver, driver)
/**
+ * sdw_stream_state: Stream state maintained by bus driver for performing
+ * stream operations.
+ *
+ * @SDW_STATE_STRM_ALLOC: New stream is allocated.
+ * @SDW_STATE_STRM_CONFIG: Stream is configured. PCM/PDM parameters of the
+ * Stream is updated to bus driver.
+ *
+ * @SDW_STATE_STRM_PREPARE: Stream is Prepared. All the ports of Master and
+ * Slave associated with this stream is prepared for enabling.
+ *
+ * @SDW_STATE_STRM_ENABLE: Stream is enabled. All the ports of Master and
+ * Slave associated with this stream are enable and now stream is
+ * active.
+ *
+ * @SDW_STATE_STRM_DISABLE: Stream in disabled state, All the ports of
+ * Master and Slave associated with the stream are disabled, and stream
+ * is not active on bus.
+ *
+ * @SDW_STATE_STRM_DEPREPARE: Stream in de-prepare state. All the ports of
+ * Master and Slave associated with the stream are de-prepared.
+ *
+ * @SDW_STATE_STRM_RELEASE: Stream in release state. Stream is not having
+ * any PCM/PDM configuration. There is not Free state for stream, since
+ * memory for the stream gets freed, and there is no way to update
+ * stream as free.
+ */
+enum sdw_stream_state {
+ SDW_STATE_STRM_ALLOC = 0,
+ SDW_STATE_STRM_CONFIG = 1,
+ SDW_STATE_STRM_PREPARE = 2,
+ SDW_STATE_STRM_ENABLE = 3,
+ SDW_STATE_STRM_DISABLE = 4,
+ SDW_STATE_STRM_DEPREPARE = 5,
+ SDW_STATE_STRM_RELEASE = 6,
+};
+
+/**
+ * sdw_update_bus_ops: Operations performed by bus driver for stream state
+ * transitions. Some of the operations are performed on individual
+ * streams, while some are global operations affecting all the streams
+ * on the bus.
+ *
+ * @SDW_BUS_PORT_PRE: Perform all the operations which is to be done before
+ * initiating the bank switch for stream getting enabled. Master and
+ * Slave driver may need to perform some operations before bank switch.
+ * Call Master and Slave handlers to accomplish device specific
+ * operations before initiating bank switch.
+ *
+ * @SDW_BUS_BANK_SWITCH: Initiate the bank switch operation by broadcasting
+ * SCP_FrameCtrl register. Depending upon the Master implementation
+ * broadcast will be finished as a part of this state, or Master may
+ * set some register as a part of PORT_POST below operation after which
+ * broadcast will be finished. Initiation of the broadcast message is
+ * done as part of this operation. Broadcast message gets transmitted
+ * on the bus during this or next operation is Master dependent.
+ *
+ * @SDW_BUS_PORT_POST: Perform all the operations which are do be done after
+ * initiating the Bank switch. Call Master and Slave handlers to
+ * perform Post bank switch operation.
+ *
+ * @SDW_BUS_BANK_SWITCH_WAIT: Bus driver waits here for the Bank switch to
+ * be completed. This is used for Master(s) running in aggregation mode
+ * where pre and post operations are performed before and after Bank
+ * switch operation. The Bank switch message broadcast will happen only
+ * when clock is enabled which is done as part of post Bank switch
+ * operation. After post Bank switch operation, bus driver waits for
+ * response of Bank switch. The bus driver provides SDW_BUS_PORT_PRE
+ * and SDW_BUS_PORT_POST for Bank switch operation which are as per
+ * Master implementation.
+ *
+ * @SDW_BUS_PORT_DIS_CHN: Disable all the ports of the alternate bank
+ * (unused bank) after the bank switch. Once Bank switch operation is
+ * successful, the running stream(s) enabled port channels on previous
+ * bank needs to be disabled for both Master(s) and Slave(s).
+ */
+enum sdw_update_bus_ops {
+ SDW_BUS_PORT_PRE,
+ SDW_BUS_BANK_SWITCH,
+ SDW_BUS_PORT_POST,
+ SDW_BUS_BANK_SWITCH_WAIT,
+ SDW_BUS_PORT_DIS_CHN,
+};
+
+/**
+ * sdw_stream_tag: Stream tag represents the unique SoundWire Audio stream.
+ * All the ports of the Master(s) and Slave(s) part of the same stream
+ * tags gets enabled/disabled as a part of single bank Switch.If
+ * samples of the stream are split between the Master(s), its Master
+ * responsibility of synchronizing the bank switch of two individual
+ * Masters.
+ *
+ * @stream_tag: Unique stream tag number.
+ * @stream_lock: Lock for stream.
+ * @ref_count: Number of times stream tag is allocated. Stream tag is is
+ * available for allocation if reference count is 0.
+ *
+ * @sdw_rt: Holds the stream runtime information.
+ */
+struct sdw_stream_tag {
+ int stream_tag;
+ struct mutex stream_lock;
+ int ref_count;
+ struct sdw_runtime *sdw_rt;
+};
+
+/**
+ * sdw_stream_params: Stream parameters.
+ *
+ * @rate: Sampling frequency
+ * @channel_count: Number of channels.
+ * @bps: bits per sample.
+ */
+struct sdw_stream_params {
+ unsigned int rate;
+ unsigned int channel_count;
+ unsigned int bps;
+};
+
+/**
+ * sdw_port_runtime: Holds the port parameters for each of the Master(s)
+ * Slave(s) port associated with the stream.
+ *
+ * @port_num: Port number.
+ * @channel_mask: Channels of the Stream handled by this port.
+ * @transport_params: Transport parameters of port.
+ * @port_params: Port parameters
+ * @port_node: Port runtime is added to the Port List of Master(s) or
+ * Slave(s) associated with stream. Node to add the Port runtime to
+ * Master(s) or Slave(s) list.
+ */
+struct sdw_port_runtime {
+ int port_num;
+ int channel_mask;
+ struct sdw_transport_params transport_params;
+ struct sdw_port_params port_params;
+ struct list_head port_node;
+};
+
+/**
+ * sdw_slave_runtime: Holds the Stream parameters for the Slave associated
+ * with the stream.
+ *
+ * @slv: Slave handle associated with this Stream.
+ * @sdw_rt: Stream handle to which this Slave stream is associated.
+ * @direction: Port Direction of the Slave for this Stream. Slave is
+ * transmitting the Data or receiving the Data.
+ *
+ * @stream_params: Stream parameters for Slave.
+ * @port_rt_list: List of Slave Ports associated with this Stream.
+ * @slave_strm_node: Stream runtime data structure maintains list of all the
+ * Slave runtime instances associated with stream. This is the node to
+ * add Slave runtime instance to that list. This list is used for the
+ * stream configuration.
+ *
+ * @slave_mstr_node: Master runtime data structure maintains list of all the
+ * Slave runtime instances. This is the node to add Slave runtime
+ * instance to that list. This list is used for Bandwidth calculation
+ * per bus. Slave runtime instance gets added to two list one for
+ * stream configuration and other for bandwidth calculation. Stream
+ * configuration is per stream where there may be multiple Masters and
+ * Slave associated with Stream. Bandwidth calculation is per Bus,
+ * where there is Single Master and Multiple Slaves associated with
+ * bus.
+ */
+struct sdw_slv_runtime {
+ struct sdw_slave *slv;
+ struct sdw_runtime *sdw_rt;
+ int direction;
+ struct sdw_stream_params stream_params;
+ struct list_head port_rt_list;
+ struct list_head slave_strm_node;
+ struct list_head slave_mstr_node;
+};
+
+/**
+ * sdw_bus_runtime: This structure holds the transport params and BW
+ * required by the stream on the bus. There may be multiple bus
+ * associated with the stream. This holds bus specific parameters of
+ * stream. TODO: Currently sdw_bus_runtime is part of sdw_mstr_runtime
+ * Master handle. Once stream between Slave to Slave is supported by
+ * bus driver, this needs to be made part of sdw_runtime handle.
+ *
+ * @stream_bw: Bus Bandwidth required by this stream (bps).
+ * @hstart: Horizontal Start column for this stream.
+ * @hstop: Horizontal stop column for this stream.
+ * @block_offset: Block offset for this stream.
+ * @sub_block_offset: Sub Block offset for this stream.
+ */
+struct sdw_bus_runtime {
+ unsigned int stream_bw;
+ int hstart;
+ int hstop;
+ int block_offset;
+ int sub_block_offset;
+
+};
+
+/**
+ * sdw_mstr_runtime: Holds the Stream parameters for the Master associated
+ * with the stream.
+ *
+ * @mstr: Master handle associated with this stream.
+ * @sdw_rt: Stream handle to which this Master stream is associated.
+ * @stream_params: Stream parameters.
+ * @port_rt_list: List of this Master Ports associated with this Stream.
+ * @mstr_strm_node: Stream runtime data structure maintains list of all the
+ * Master runtime instances associated with stream. This is the node to
+ * add Master runtime instance to that list. This list is used for the
+ * stream configuration.
+ *
+ * @mstr_node: Master data structure maintains list of all the Master
+ * runtime instances. This is the node to add Master runtime instance
+ * to to that list. This list is used for Bandwidth calculation per
+ * bus. Master runtime instance gets added to two list one for stream
+ * configuration and other for bandwidth calculation. Stream
+ * configuration is per stream where there may be multiple Masters and
+ * Slave associated with Stream. Bandwidth calculation is per Bus,
+ * where there is Single Master and Multiple Slaves associated with
+ * bus.
+ *
+ * @slv_rt_list: List of the Slave_runtime instances associated with this
+ * Master_runtime. Its list of all the Slave(s) stream associated with
+ * this Master. There may be stereo stream from Master to two Slaves,
+ * where L and R samples from Master is received by two different
+ * Slave(s), so this list contains the runtime structure associated
+ * with both Slaves.
+ *
+ * @bus_rt: Bus parameters for the stream. There may be multiple bus
+ * associated with stream. This bus_rt is for current Master.
+ */
+struct sdw_mstr_runtime {
+ struct sdw_master *mstr;
+ struct sdw_runtime *sdw_rt;
+ int direction;
+ struct sdw_stream_params stream_params;
+ struct list_head port_rt_list;
+ struct list_head mstr_strm_node;
+ struct list_head mstr_node;
+ struct list_head slv_rt_list;
+ struct sdw_bus_runtime bus_rt;
+};
+
+/**
+ * sdw_runtime: This structure holds runtime information for each unique
+ * SoundWire stream.
+ *
+ * @tx_ref_count: Number of Transmit devices of stream. This may include
+ * multiple Master(s) and Slave(s) based on how stream samples are
+ * split between Mater and Slaves.
+ * @rx_ref_count: Number of Receive devices of stream. This may include
+ * multiple Master(s) and Slave(s) based on how stream samples are
+ * split between Mater and Slaves.
+ *
+ * @stream_params: Steam parameters.
+ * @slv_rt_list: List of the slaves part of this stream.
+ * @mstr_rt_list: List of Masters part of this stream.
+ * @type: Stream type PCM or PDM.This is not SoundWire concept, its used
+ * inside bus driver for efficient BW management.
+ *
+ * @stream_state: Current State of the stream.
+ */
+struct sdw_runtime {
+ int tx_ref_count;
+ int rx_ref_count;
+ struct sdw_stream_params stream_params;
+ struct list_head slv_rt_list;
+ struct list_head mstr_rt_list;
+ enum sdw_stream_type type;
+ enum sdw_stream_state stream_state;
+};
+
+/**
* sdw_slv_status: List of Slave status.
*
* @node: Node for adding status to list of Slave status.
@@ -110,6 +409,20 @@ struct sdw_slv_status {
*
* @bus_node: Node to add the bus in the sdw_core list.
* @mstr: Master reference for the bus.
+ * @clk_state: State of the clock.
+ * @active_bank: Current bank in use.
+ * @max_clk_dr_freq: Maximum double rate clock frequency. This is maximum
+ * double clock rate supported per bus.
+ * @curr_clk_dr_freq: Current double rate clock frequency in use. This is
+ * current clock rate at which bus is running.
+ *
+ * @clk_div: Current clock divider in use.
+ * @bandwidth: Total bandwidth.
+ * @system_interval: Bus System interval (Stream Synchronization Point).
+ * @stream_interval: Stream interval.
+ * @frame_freq: SoundWire Frame frequency on bus.
+ * @col: Active columns.
+ * @row: Active rows.
* @status_thread: Thread to process the Slave status.
* @kworker: Worker for updating the Slave status.
* @kwork: Work for worker
@@ -121,16 +434,48 @@ struct sdw_slv_status {
* context, spinlock is used to put the status reported by Master into
* the status list which is processed by bus driver in thread context
* later.
+ *
+ * @data: Data to be provided by bus driver for calling xfer_msg_deferred
+ * callback of Master driver.
+
*/
struct sdw_bus {
struct list_head bus_node;
struct sdw_master *mstr;
+ unsigned int clk_state;
+ unsigned int active_bank;
+ unsigned int max_dr_clk_freq;
+ unsigned int curr_dr_clk_freq;
+ unsigned int clk_div;
+ unsigned int bandwidth;
+ unsigned int system_interval;
+ unsigned int stream_interval;
+ unsigned int frame_freq;
+ unsigned int col;
+ unsigned int row;
struct task_struct *status_thread;
struct kthread_worker kworker;
struct kthread_work kwork;
struct list_head status_list;
spinlock_t spinlock;
+ struct sdw_deferred_xfer_data data;
+};
+
+/**
+ * sdw_row_col_pair: Information for each row column pair. This is used by
+ * bus driver for quick BW calculation.
+ *
+ * @row: Number of rows.
+ * @col: Number of columns
+ * @control_bits: Number of controls bits for this row-column pair.
+ * @data_bits: Number of controls bits for this row-column pair.
+ */
+struct sdw_row_col_pair {
+ int row;
+ int col;
+ int control_bits;
+ int data_bits;
};
/**
@@ -138,11 +483,17 @@ struct sdw_bus {
* spawned across masters and has list of bus structure per every
* Master registered.
*
+ * @row_col_pair: Array holding all row-column pair possible as per MIPI
+ * 1.1 Spec. This is used for quick reference for BW calculation
+ * algorithm.
+ *
* @bus_list: List of all the bus instance.
* @core_mutex: Global lock for all bus instances.
* @idr: For identifying the registered buses.
*/
struct snd_sdw_core {
+ struct sdw_stream_tag stream_tags[SDW_NUM_STREAM_TAGS];
+ struct sdw_row_col_pair row_col_pair[MAX_NUM_ROW_COLS];
struct list_head bus_list;
struct mutex core_mutex;
struct idr idr;
@@ -168,6 +519,58 @@ void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg,
struct sdw_deferred_xfer_data *data);
/**
+ * sdw_index_to_col: Structure holding mapping of numbers to columns.
+ *
+ * @index: Holds index to number of columns.
+ * @col: Holds actual columns.
+ */
+struct sdw_index_to_col {
+ int index;
+ int col;
+};
+
+/**
+ * sdw_index_to_row: Structure holding mapping of numbers to rows.
+ *
+ * @index: Holds index to number of rows.
+ * @row: Holds actual rows.
+ */
+struct sdw_index_to_row {
+ int index;
+ int row;
+};
+
+/**
+ * sdw_group_params: Structure holding temporary variable while computing
+ * transport parameters of Master(s) and Slave(s).
+ *
+ * @rate: Holds stream rate.
+ * @full_bw: Holds full bandwidth per group.
+ * @payload_bw: Holds payload bandwidth per group.
+ * @hwidth: Holds hwidth per group.
+ */
+struct sdw_group_params {
+ int rate;
+ int full_bw;
+ int payload_bw;
+ int hwidth;
+};
+
+/**
+ * sdw_group_count: Structure holding group count and stream rate array
+ * while computing transport parameters of Master(s) and Slave(s).
+ *
+ * @group_count: Holds actual group count.
+ * @max_size: Holds maximum capacity of array.
+ * @stream_rates: Pointer to stream rates.
+ */
+struct sdw_group_count {
+ unsigned int group_count;
+ unsigned int max_size;
+ unsigned int *stream_rates;
+};
+
+/**
* sdw_enable_disable_dpn_intr: Enable or Disable Slave Data Port interrupt.
* This is called by bus driver before prepare and after deprepare of
* the ports.
@@ -182,6 +585,52 @@ void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg,
int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num,
int port_direction, bool enable);
+/**
+ * sdw_create_row_col_pair: Initialization of bandwidth related operations.
+ * This is required to have fast path for the BW calculation when a new
+ * stream is prepared or deprepared. This is called only once as part
+ * of SoundWire Bus driver getting initialized.
+ */
+void sdw_create_row_col_pair(void);
+
+/**
+ * sdw_init_bus_params: Sets up bus data structure for BW calculation. This
+ * is called once per each Master interface registration to the
+ * SoundWire bus.
+ *
+ * @sdw_bus: Bus handle.
+ */
+void sdw_init_bus_params(struct sdw_bus *sdw_bus);
+
+/**
+ * sdw_get_slv_dpn_caps: Get the data port capabilities based on the port
+ * number and port direction.
+ *
+ * @slv_cap: Slave capabilities.
+ * @direction: Port data direction.
+ * @port_num: Port number.
+ */
+struct sdw_dpn_caps *sdw_get_slv_dpn_cap(struct sdw_slave_caps *slv_cap,
+ enum sdw_data_direction direction,
+ unsigned int port_num);
+
+/* Return bus structure */
+static inline struct sdw_bus *sdw_master_to_bus(struct sdw_master *mstr)
+{
+ return mstr->bus;
+}
+
+/* Reference count increment */
+static inline void sdw_inc_ref_count(int *ref_count)
+{
+ (*ref_count)++;
+}
+
+/* Reference count decrement */
+static inline void sdw_dec_ref_count(int *ref_count)
+{
+ (*ref_count)--;
+}
/*
* Helper function for bus driver to write messages. Since bus driver
--
1.7.9.5
Powered by blists - more mailing lists