lkml.org 
[lkml]   [2016]   [Oct]   [21]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[RFC 13/14] SoundWire: Add stream and port configuration
    Date
    From: Sanyog Kale <sanyog.r.kale@intel.com>

    This patch adds APIs for stream and port configurations for SoundWire
    bus driver.

    Signed-off-by: Hardik Shah <hardik.t.shah@intel.com>
    Signed-off-by: Sanyog Kale <sanyog.r.kale@intel.com>
    Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.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
    \
     
     \ /
      Last update: 2016-10-21 14:42    [W:5.019 / U:0.012 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site